liquid_feedback_frontend
changeset 1309:32cc544d5a5b
Cumulative patch for upcoming frontend version 4
line diff
1.1 --- a/LICENSE Thu Jun 23 03:30:57 2016 +0200 1.2 +++ b/LICENSE Sun Jul 15 14:07:29 2018 +0200 1.3 @@ -23,14 +23,14 @@ 1.4 1.5 3rd party license information: 1.6 1.7 ------------------------------------------------------------------------------ 1.8 +============================================================================= 1.9 1.10 Some of the icons used in LiquidFeedback (except national flags) are from 1.11 Silk icon set 1.3 by Mark James. [ http://www.famfamfam.com/lab/icons/silk/ ] 1.12 His work is licensed under a Creative Commons Attribution 2.5 License. 1.13 [ http://creativecommons.org/licenses/by/2.5/ ] 1.14 1.15 ------------------------------------------------------------------------------ 1.16 +============================================================================= 1.17 1.18 The emoticons are taken from the following web pages: 1.19 1.20 @@ -44,7 +44,7 @@ 1.21 unless such conditions are required by law." 1.22 The orange and red smiley are modified versions of Face-sad. 1.23 1.24 ------------------------------------------------------------------------------ 1.25 +============================================================================= 1.26 1.27 The "jquery" library is licensed as follows: 1.28 1.29 @@ -86,7 +86,7 @@ 1.30 own licenses; we recommend you read them, as their terms may differ from 1.31 the terms above. 1.32 1.33 ------------------------------------------------------------------------------ 1.34 +============================================================================= 1.35 1.36 "LESS - Leaner CSS" is licensed as follows: 1.37 1.38 @@ -269,7 +269,7 @@ 1.39 1.40 END OF TERMS AND CONDITIONS 1.41 1.42 ------------------------------------------------------------------------------ 1.43 +============================================================================= 1.44 1.45 The collection of LDAP error codes (mldap_errorcodes[]) has been derived 1.46 from the file ldap.h that is part of OpenLDAP Software. OpenLDAP's license 1.47 @@ -348,5 +348,296 @@ 1.48 1.49 End of OpenLDAP's license information 1.50 1.51 +============================================================================= 1.52 + 1.53 +"wysihtml" is licensed as follows: 1.54 + 1.55 +The MIT License (MIT) 1.56 + 1.57 +Copyright (C) 2012-2016 XING AG, Voog and contributors 1.58 + 1.59 +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 1.60 + 1.61 +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 1.62 + 1.63 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1.64 + 1.65 +============================================================================= 1.66 + 1.67 +The Roboto font family is licensed as follows: 1.68 + 1.69 +Apache License 1.70 + 1.71 +Version 2.0, January 2004 1.72 + 1.73 +http://www.apache.org/licenses/ 1.74 + 1.75 +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1.76 + 1.77 +1. Definitions. 1.78 + 1.79 +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 1.80 + 1.81 +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 1.82 + 1.83 +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 1.84 + 1.85 +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 1.86 + 1.87 +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 1.88 + 1.89 +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 1.90 + 1.91 +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 1.92 + 1.93 +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 1.94 + 1.95 +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 1.96 + 1.97 +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 1.98 + 1.99 +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 1.100 + 1.101 +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 1.102 + 1.103 +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 1.104 + 1.105 +You must give any other recipients of the Work or Derivative Works a copy of this License; and 1.106 +You must cause any modified files to carry prominent notices stating that You changed the files; and 1.107 +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 1.108 +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 1.109 + 1.110 +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 1.111 +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 1.112 + 1.113 +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 1.114 + 1.115 +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 1.116 + 1.117 +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 1.118 + 1.119 +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 1.120 + 1.121 +END OF TERMS AND CONDITIONS 1.122 + 1.123 +============================================================================= 1.124 + 1.125 +The "mdl" library is licensed as follows: 1.126 + 1.127 + 1.128 + 1.129 + Apache License 1.130 + Version 2.0, January 2004 1.131 + http://www.apache.org/licenses/ 1.132 + 1.133 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1.134 + 1.135 + 1. Definitions. 1.136 + 1.137 + "License" shall mean the terms and conditions for use, reproduction, 1.138 + and distribution as defined by Sections 1 through 9 of this document. 1.139 + 1.140 + "Licensor" shall mean the copyright owner or entity authorized by 1.141 + the copyright owner that is granting the License. 1.142 + 1.143 + "Legal Entity" shall mean the union of the acting entity and all 1.144 + other entities that control, are controlled by, or are under common 1.145 + control with that entity. For the purposes of this definition, 1.146 + "control" means (i) the power, direct or indirect, to cause the 1.147 + direction or management of such entity, whether by contract or 1.148 + otherwise, or (ii) ownership of fifty percent (50%) or more of the 1.149 + outstanding shares, or (iii) beneficial ownership of such entity. 1.150 + 1.151 + "You" (or "Your") shall mean an individual or Legal Entity 1.152 + exercising permissions granted by this License. 1.153 + 1.154 + "Source" form shall mean the preferred form for making modifications, 1.155 + including but not limited to software source code, documentation 1.156 + source, and configuration files. 1.157 + 1.158 + "Object" form shall mean any form resulting from mechanical 1.159 + transformation or translation of a Source form, including but 1.160 + not limited to compiled object code, generated documentation, 1.161 + and conversions to other media types. 1.162 + 1.163 + "Work" shall mean the work of authorship, whether in Source or 1.164 + Object form, made available under the License, as indicated by a 1.165 + copyright notice that is included in or attached to the work 1.166 + (an example is provided in the Appendix below). 1.167 + 1.168 + "Derivative Works" shall mean any work, whether in Source or Object 1.169 + form, that is based on (or derived from) the Work and for which the 1.170 + editorial revisions, annotations, elaborations, or other modifications 1.171 + represent, as a whole, an original work of authorship. For the purposes 1.172 + of this License, Derivative Works shall not include works that remain 1.173 + separable from, or merely link (or bind by name) to the interfaces of, 1.174 + the Work and Derivative Works thereof. 1.175 + 1.176 + "Contribution" shall mean any work of authorship, including 1.177 + the original version of the Work and any modifications or additions 1.178 + to that Work or Derivative Works thereof, that is intentionally 1.179 + submitted to Licensor for inclusion in the Work by the copyright owner 1.180 + or by an individual or Legal Entity authorized to submit on behalf of 1.181 + the copyright owner. For the purposes of this definition, "submitted" 1.182 + means any form of electronic, verbal, or written communication sent 1.183 + to the Licensor or its representatives, including but not limited to 1.184 + communication on electronic mailing lists, source code control systems, 1.185 + and issue tracking systems that are managed by, or on behalf of, the 1.186 + Licensor for the purpose of discussing and improving the Work, but 1.187 + excluding communication that is conspicuously marked or otherwise 1.188 + designated in writing by the copyright owner as "Not a Contribution." 1.189 + 1.190 + "Contributor" shall mean Licensor and any individual or Legal Entity 1.191 + on behalf of whom a Contribution has been received by Licensor and 1.192 + subsequently incorporated within the Work. 1.193 + 1.194 + 2. Grant of Copyright License. Subject to the terms and conditions of 1.195 + this License, each Contributor hereby grants to You a perpetual, 1.196 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 1.197 + copyright license to reproduce, prepare Derivative Works of, 1.198 + publicly display, publicly perform, sublicense, and distribute the 1.199 + Work and such Derivative Works in Source or Object form. 1.200 + 1.201 + 3. Grant of Patent License. Subject to the terms and conditions of 1.202 + this License, each Contributor hereby grants to You a perpetual, 1.203 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 1.204 + (except as stated in this section) patent license to make, have made, 1.205 + use, offer to sell, sell, import, and otherwise transfer the Work, 1.206 + where such license applies only to those patent claims licensable 1.207 + by such Contributor that are necessarily infringed by their 1.208 + Contribution(s) alone or by combination of their Contribution(s) 1.209 + with the Work to which such Contribution(s) was submitted. If You 1.210 + institute patent litigation against any entity (including a 1.211 + cross-claim or counterclaim in a lawsuit) alleging that the Work 1.212 + or a Contribution incorporated within the Work constitutes direct 1.213 + or contributory patent infringement, then any patent licenses 1.214 + granted to You under this License for that Work shall terminate 1.215 + as of the date such litigation is filed. 1.216 + 1.217 + 4. Redistribution. You may reproduce and distribute copies of the 1.218 + Work or Derivative Works thereof in any medium, with or without 1.219 + modifications, and in Source or Object form, provided that You 1.220 + meet the following conditions: 1.221 + 1.222 + (a) You must give any other recipients of the Work or 1.223 + Derivative Works a copy of this License; and 1.224 + 1.225 + (b) You must cause any modified files to carry prominent notices 1.226 + stating that You changed the files; and 1.227 + 1.228 + (c) You must retain, in the Source form of any Derivative Works 1.229 + that You distribute, all copyright, patent, trademark, and 1.230 + attribution notices from the Source form of the Work, 1.231 + excluding those notices that do not pertain to any part of 1.232 + the Derivative Works; and 1.233 + 1.234 + (d) If the Work includes a "NOTICE" text file as part of its 1.235 + distribution, then any Derivative Works that You distribute must 1.236 + include a readable copy of the attribution notices contained 1.237 + within such NOTICE file, excluding those notices that do not 1.238 + pertain to any part of the Derivative Works, in at least one 1.239 + of the following places: within a NOTICE text file distributed 1.240 + as part of the Derivative Works; within the Source form or 1.241 + documentation, if provided along with the Derivative Works; or, 1.242 + within a display generated by the Derivative Works, if and 1.243 + wherever such third-party notices normally appear. The contents 1.244 + of the NOTICE file are for informational purposes only and 1.245 + do not modify the License. You may add Your own attribution 1.246 + notices within Derivative Works that You distribute, alongside 1.247 + or as an addendum to the NOTICE text from the Work, provided 1.248 + that such additional attribution notices cannot be construed 1.249 + as modifying the License. 1.250 + 1.251 + You may add Your own copyright statement to Your modifications and 1.252 + may provide additional or different license terms and conditions 1.253 + for use, reproduction, or distribution of Your modifications, or 1.254 + for any such Derivative Works as a whole, provided Your use, 1.255 + reproduction, and distribution of the Work otherwise complies with 1.256 + the conditions stated in this License. 1.257 + 1.258 + 5. Submission of Contributions. Unless You explicitly state otherwise, 1.259 + any Contribution intentionally submitted for inclusion in the Work 1.260 + by You to the Licensor shall be under the terms and conditions of 1.261 + this License, without any additional terms or conditions. 1.262 + Notwithstanding the above, nothing herein shall supersede or modify 1.263 + the terms of any separate license agreement you may have executed 1.264 + with Licensor regarding such Contributions. 1.265 + 1.266 + 6. Trademarks. This License does not grant permission to use the trade 1.267 + names, trademarks, service marks, or product names of the Licensor, 1.268 + except as required for reasonable and customary use in describing the 1.269 + origin of the Work and reproducing the content of the NOTICE file. 1.270 + 1.271 + 7. Disclaimer of Warranty. Unless required by applicable law or 1.272 + agreed to in writing, Licensor provides the Work (and each 1.273 + Contributor provides its Contributions) on an "AS IS" BASIS, 1.274 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 1.275 + implied, including, without limitation, any warranties or conditions 1.276 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 1.277 + PARTICULAR PURPOSE. You are solely responsible for determining the 1.278 + appropriateness of using or redistributing the Work and assume any 1.279 + risks associated with Your exercise of permissions under this License. 1.280 + 1.281 + 8. Limitation of Liability. In no event and under no legal theory, 1.282 + whether in tort (including negligence), contract, or otherwise, 1.283 + unless required by applicable law (such as deliberate and grossly 1.284 + negligent acts) or agreed to in writing, shall any Contributor be 1.285 + liable to You for damages, including any direct, indirect, special, 1.286 + incidental, or consequential damages of any character arising as a 1.287 + result of this License or out of the use or inability to use the 1.288 + Work (including but not limited to damages for loss of goodwill, 1.289 + work stoppage, computer failure or malfunction, or any and all 1.290 + other commercial damages or losses), even if such Contributor 1.291 + has been advised of the possibility of such damages. 1.292 + 1.293 + 9. Accepting Warranty or Additional Liability. While redistributing 1.294 + the Work or Derivative Works thereof, You may choose to offer, 1.295 + and charge a fee for, acceptance of support, warranty, indemnity, 1.296 + or other liability obligations and/or rights consistent with this 1.297 + License. However, in accepting such obligations, You may act only 1.298 + on Your own behalf and on Your sole responsibility, not on behalf 1.299 + of any other Contributor, and only if You agree to indemnify, 1.300 + defend, and hold each Contributor harmless for any liability 1.301 + incurred by, or claims asserted against, such Contributor by reason 1.302 + of your accepting any such warranty or additional liability. 1.303 + 1.304 + END OF TERMS AND CONDITIONS 1.305 + 1.306 + APPENDIX: How to apply the Apache License to your work. 1.307 + 1.308 + To apply the Apache License to your work, attach the following 1.309 + boilerplate notice, with the fields enclosed by brackets "[]" 1.310 + replaced with your own identifying information. (Don't include 1.311 + the brackets!) The text should be enclosed in the appropriate 1.312 + comment syntax for the file format. We also recommend that a 1.313 + file or class name and description of purpose be included on the 1.314 + same "printed page" as the copyright notice for easier 1.315 + identification within third-party archives. 1.316 + 1.317 + Copyright 2015 Google Inc 1.318 + 1.319 + Licensed under the Apache License, Version 2.0 (the "License"); 1.320 + you may not use this file except in compliance with the License. 1.321 + You may obtain a copy of the License at 1.322 + 1.323 + http://www.apache.org/licenses/LICENSE-2.0 1.324 + 1.325 + Unless required by applicable law or agreed to in writing, software 1.326 + distributed under the License is distributed on an "AS IS" BASIS, 1.327 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.328 + See the License for the specific language governing permissions and 1.329 + limitations under the License. 1.330 + 1.331 + All code in any directories or sub-directories that end with *.html or 1.332 + *.css is licensed under the Creative Commons Attribution International 1.333 + 4.0 License, which full text can be found here: 1.334 + https://creativecommons.org/licenses/by/4.0/legalcode. 1.335 + 1.336 + As an exception to this license, all html or css that is generated by 1.337 + the software at the direction of the user is copyright the user. The 1.338 + user has full ownership and control over such content, including 1.339 + whether and how they wish to license it. 1.340 + 1.341 +============================================================================= 1.342 1.343 END OF LICENSE FILE
2.1 --- a/app/main/_filter/20_session.lua Thu Jun 23 03:30:57 2016 +0200 2.2 +++ b/app/main/_filter/20_session.lua Sun Jul 15 14:07:29 2018 +0200 2.3 @@ -1,4 +1,4 @@ 2.4 -local cookie = request.get_cookie{ name = "liquid_feedback_session" } 2.5 +local cookie = request.get_cookie{ name = config.cookie_name or "liquid_feedback_session" } 2.6 2.7 if cookie then 2.8 app.session = Session:by_ident(cookie) 2.9 @@ -6,12 +6,12 @@ 2.10 if not app.session then 2.11 app.session = Session:new() 2.12 request.set_cookie{ 2.13 - name = "liquid_feedback_session", 2.14 + name = config.cookie_name or "liquid_feedback_session", 2.15 value = app.session.ident 2.16 } 2.17 end 2.18 2.19 -request.set_csrf_secret(app.session.additional_secret) 2.20 +request.set_csrf_secret(app.session:additional_secret_for("csrf")) 2.21 2.22 locale.set{ lang = app.session.lang or config.default_lang or "en" } 2.23
3.1 --- a/app/main/_filter/21_auth.lua Thu Jun 23 03:30:57 2016 +0200 3.2 +++ b/app/main/_filter/21_auth.lua Sun Jul 15 14:07:29 2018 +0200 3.3 @@ -2,26 +2,59 @@ 3.4 local view = request.get_view() 3.5 local action = request.get_action() 3.6 3.7 -local auth_needed = not ( 3.8 - module == 'index' 3.9 - and ( 3.10 -view == "login" 3.11 - or action == "login" 3.12 - or view == "register" 3.13 - or action == "register" 3.14 - or action == "cancel_register" 3.15 - or view == "about" 3.16 - or view == "reset_password" 3.17 - or action == "reset_password" 3.18 - or view == "send_login" 3.19 - or action == "send_login" 3.20 - or view == "confirm_notify_email" 3.21 - or action == "confirm_notify_email" 3.22 - or view == "menu" 3.23 - or action == "set_lang" 3.24 - or view == "404" 3.25 - ) 3.26 -) 3.27 +local auth_needed = true 3.28 + 3.29 +if module == 'index' and ( 3.30 + view == 'index' 3.31 + or view == "login" 3.32 + or action == "login" 3.33 + or view == "register" 3.34 + or action == "register" 3.35 + or action == "cancel_register" 3.36 + or view == "about" 3.37 + or view == "reset_password" 3.38 + or action == "reset_password" 3.39 + or view == "send_login" 3.40 + or action == "send_login" 3.41 + or view == "confirm_notify_email" 3.42 + or action == "confirm_notify_email" 3.43 + or view == "menu" 3.44 + or action == "set_lang" 3.45 + or view == "403" 3.46 + or view == "404" 3.47 + or view == "405" 3.48 +) then 3.49 + auth_needed = false 3.50 +end 3.51 + 3.52 +if module == "registration" then 3.53 + auth_needed = false 3.54 +end 3.55 + 3.56 +if module == "style" then 3.57 + auth_needed = false 3.58 +end 3.59 + 3.60 +if module == "help" then 3.61 + auth_needed = false 3.62 +end 3.63 + 3.64 +if module == "oauth2" and ( 3.65 + view == "validate" 3.66 + or view == "token" 3.67 + or view == "session" 3.68 + or view == "register" 3.69 +) then 3.70 + auth_needed = false 3.71 +end 3.72 + 3.73 +if module == "oauth2_client" then 3.74 + auth_needed = false 3.75 +end 3.76 + 3.77 +if module == "api" then 3.78 + auth_needed = false 3.79 +end 3.80 3.81 if app.session:has_access("anonymous") then 3.82 3.83 @@ -41,6 +74,7 @@ 3.84 or module == "index" and view == "search" 3.85 or module == "index" and view == "usage_terms" 3.86 or module == "help" and view == "introduction" 3.87 + or module == "style" 3.88 then 3.89 auth_needed = false 3.90 end 3.91 @@ -94,11 +128,21 @@ 3.92 3.93 if auth_needed and app.session.member == nil then 3.94 trace.debug("Not authenticated yet.") 3.95 + local params = json.object() 3.96 + for key, val in pairs(request.get_param_strings()) do 3.97 + if type(val) == "string" then 3.98 + params[key] = val 3.99 + else 3.100 + -- shouldn't happen 3.101 + error("array type params not implemented") 3.102 + end 3.103 + end 3.104 request.redirect{ 3.105 module = 'index', view = 'login', params = { 3.106 redirect_module = module, 3.107 redirect_view = view, 3.108 - redirect_id = param.get_id() 3.109 + redirect_id = param.get_id(), 3.110 + redirect_params = params 3.111 } 3.112 } 3.113 elseif auth_needed and app.session.member.locked then
4.1 --- a/app/main/_filter_view/30_navigation.lua Thu Jun 23 03:30:57 2016 +0200 4.2 +++ b/app/main/_filter_view/30_navigation.lua Sun Jul 15 14:07:29 2018 +0200 4.3 @@ -1,78 +1,118 @@ 4.4 -slot.select ( 'instance_name', function () 4.5 - slot.put(" @ ") 4.6 - slot.put ( encode.html ( config.instance_name ) ) 4.7 -end) 4.8 - 4.9 - 4.10 -slot.select ( 'navigation_right', function () 4.11 +execute.inner() 4.12 4.13 - if app.session:has_access ("anonymous") and not (app.session.needs_delegation_check) then 4.14 - 4.15 - ui.form { 4.16 - attr = { class = "inline search" }, 4.17 - method = "get", 4.18 - module = "index", view = "search", 4.19 - content = function () 4.20 - 4.21 - ui.field.text { 4.22 - attr = { placeholder = _"search" }, 4.23 - name = "q" 4.24 - } 4.25 - 4.26 - end 4.27 - } 4.28 - 4.29 - ui.link { 4.30 - attr = { class = "searchLink" }, 4.31 - module = "index", view = "search", content = function () 4.32 - ui.image { static = "icons/16/magnifier.png" } 4.33 - end 4.34 - } 4.35 - 4.36 +local for_meta_navigation = false 4.37 +if config.meta_navigation_items_func and config.meta_navigation_html_func then 4.38 + for_meta_navigation = 4.39 + request.get_module() == "index" and ( 4.40 + request.get_view() == "login" 4.41 + or request.get_view() == "register" 4.42 + or request.get_view() == "reset_password" 4.43 + or request.get_view() == "send_login" 4.44 + ) 4.45 + or (request.get_module() == "registration") 4.46 + or (request.get_module() == "member" and request.get_view() == "show" and param.get_id() == app.session.member_id) 4.47 + or (request.get_module() == "member" and request.get_view() == "history" and param.get_id() == app.session.member_id) 4.48 + or (request.get_module() == "member" and ( 4.49 + string.match(request.get_view(), "^settings") 4.50 + or string.match(request.get_view(), "^edit") 4.51 + )) 4.52 + local items = config.meta_navigation_items_func(app.session.member, for_meta_navigation and "login" or "LiquidFeedback") 4.53 + local meta_navigation = config.meta_navigation_html_func(items) 4.54 + slot.put_into("meta_navigation", meta_navigation) 4.55 + local meta_navigation_style = config.meta_navigation_style_func(items) 4.56 + slot.put_into("meta_navigation_style", meta_navigation_style) 4.57 + if config.meta_navigation_extra_style_func then 4.58 + local meta_navigation_extra_style = config.meta_navigation_extra_style_func(items) 4.59 + slot.put_into("meta_navigation_style", meta_navigation_extra_style) 4.60 end 4.61 - 4.62 - if app.session.member == nil then 4.63 - 4.64 - slot.put ( " " ) 4.65 - 4.66 - ui.link { 4.67 - text = _"Login", 4.68 - module = 'index', 4.69 - view = 'login', 4.70 - params = { 4.71 - redirect_module = request.get_module(), 4.72 - redirect_view = request.get_view(), 4.73 - redirect_id = param.get_id() 4.74 - } 4.75 - } 4.76 - 4.77 - slot.put ( " " ) 4.78 - end 4.79 - 4.80 - if app.session.member == nil and not config.registration_disabled then 4.81 - 4.82 - ui.link { 4.83 - text = _"Registration", 4.84 - module = 'index', 4.85 - view = 'register' 4.86 - } 4.87 + local meta_navigation_script = config.meta_navigation_script_func(items) 4.88 + slot.put_into("script", meta_navigation_script) 4.89 +end 4.90 + 4.91 +if not config.meta_navigation_items_func or not config.meta_navigation_html_func then 4.92 + slot.select ( 'header_bar', function () 4.93 + ui.tag{ tag = "header", attr = { class = "mdl-layout__header mdl-layout__header--seamed" }, content = function() 4.94 + ui.container{ attr = { class = "mdl-layout__header-row" }, content = function() 4.95 + ui.link{ module = "index", view = "index", attr = { class = "mdl-layout-title" }, content = "LiquidFeedback" } 4.96 + ui.tag{ attr = { class = "mdl-layout-spacer" }, content = "" } 4.97 4.98 - end 4.99 - 4.100 - 4.101 - if app.session.member then 4.102 - 4.103 - slot.put ( " " ) 4.104 - 4.105 - ui.tag { attr = { id = "member_menu" }, content = function() 4.106 - util.micro_avatar(app.session.member) 4.107 + if app.session:has_access ("anonymous") then 4.108 + ui.form { attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--expandable mdl-textfield--floating-label mdl-textfield--align-right" }, method = "get", module = "index", view = "search", content = function () 4.109 + ui.tag{ tag = "label", attr = { class = "mdl-button mdl-js-button mdl-button--icon", ["for"] = "fixed-header-drawer-exp" }, content = function() 4.110 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "search" } 4.111 + end } 4.112 + ui.container{ attr = { class = "mdl-textfield__expandable-holder" }, content = function() 4.113 + ui.tag{ tag = "input", attr = { class = "mdl-textfield__input", type = "text", name = "q", id = "fixed-header-drawer-exp" }, content = "" } 4.114 + end } 4.115 + end } 4.116 + end 4.117 + 4.118 + if app.session.member == nil and not ( 4.119 + request.get_module() == "index" and (request.get_view() == "login" or request.get_view() == "reset_password" or request.get_view() == "send_login") 4.120 + ) and not config.meta_navigation_html_func then 4.121 + local redirect_params = json.object() 4.122 + for key, val in pairs(request.get_param_strings()) do 4.123 + if type(val) == "string" then 4.124 + redirect_params[key] = val 4.125 + else 4.126 + -- shouldn't happen 4.127 + error("array type params not implemented") 4.128 + end 4.129 + end 4.130 + ui.tag{ tag = "nav", attr = { class = "mdl-navigation" }, content = function() 4.131 + local link = { 4.132 + content = function() 4.133 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "exit_to_app" } 4.134 + slot.put(" ") 4.135 + ui.tag{ attr = { class = "mdl-layout--large-screen-only" }, content = function() 4.136 + ui.tag{ content = _"Login" } 4.137 + end } 4.138 + end, 4.139 + text = _"Login", 4.140 + attr = { class = "mdl-navigation__link" } 4.141 + } 4.142 + if config.login and config.login.method == "oauth2" then 4.143 + link.module = "oauth2_client" 4.144 + link.view = "redirect" 4.145 + link.params = { provider = config.login.provider } 4.146 + else 4.147 + link.module = 'index' 4.148 + link.view = 'login' 4.149 + link.params = { 4.150 + redirect_module = request.get_module(), 4.151 + redirect_view = request.get_view(), 4.152 + redirect_id = param.get_id(), 4.153 + redirect_params = redirect_params 4.154 + } 4.155 + end 4.156 + ui.link(link) 4.157 + end } 4.158 + end 4.159 + 4.160 + if app.session.member and not ( 4.161 + config.meta_navigation_items_func and config.meta_navigation_html_func 4.162 + ) then 4.163 + ui.tag{ tag = "nav", attr = { class = "mdl-navigation" }, content = function() 4.164 + ui.tag{ tag = "span", module = "member", view = "show", id = app.session.member.id, attr = { id = "lf-member-menu", class = "mdl-navigation__link" }, content = function() 4.165 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "person" } 4.166 + ui.tag{ attr = { class = "mdl-layout--large-screen-only" }, content = function() 4.167 + ui.tag{ content = app.session.member.name } 4.168 + end } 4.169 + end } 4.170 + 4.171 + ui.tag { tag = "ul", attr = { class = "mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect", ["for"] = "lf-member-menu" }, content = function() 4.172 + execute.view{ module = "member", view = "_menu", params = { item_class = "mdl-menu__item", link_class = "mdl-menu__link" } } 4.173 + end } 4.174 + end } 4.175 + 4.176 + end -- if app.session.member 4.177 + end } 4.178 end } 4.179 - 4.180 - end -- if app.session.member 4.181 - 4.182 -end) 4.183 + end) 4.184 +end 4.185 4.186 -- show notifications about things the user should take care of 4.187 +--[[ 4.188 if app.session.member then 4.189 execute.view{ 4.190 module = "index", view = "_sidebar_notifications", params = { 4.191 @@ -80,34 +120,48 @@ 4.192 } 4.193 } 4.194 end 4.195 +--]] 4.196 4.197 slot.select ("footer", function () 4.198 + ui.tag{ tag = "li", content = function() 4.199 + ui.link{ content = _"Quick guide", module = "help", view = "introduction" } 4.200 + end } 4.201 if app.session.member_id and app.session.member.admin then 4.202 - ui.link { 4.203 - text = _"System settings", 4.204 - module = 'admin', 4.205 - view = 'index' 4.206 - } 4.207 - slot.put(" · ") 4.208 + ui.tag{ tag = "li", content = function() 4.209 + if config.admin_link then 4.210 + ui.link(config.admin_link) 4.211 + else 4.212 + ui.link{ content = _"System settings", module = "admin", view = "index" } 4.213 + end 4.214 + end } 4.215 end 4.216 - ui.link{ 4.217 - text = _"About site", 4.218 - module = 'index', 4.219 - view = 'about' 4.220 - } 4.221 - if config.use_terms then 4.222 - slot.put(" · ") 4.223 + ui.tag{ tag = "li", content = function() 4.224 ui.link{ 4.225 - text = _"Use terms", 4.226 + text = _"About site", 4.227 module = 'index', 4.228 - view = 'usage_terms' 4.229 + view = 'about' 4.230 } 4.231 + end } 4.232 + if not config.extra_footer_func then 4.233 + if config.use_terms and app.session.member then 4.234 + ui.tag{ tag = "li", content = function() 4.235 + ui.link{ 4.236 + text = _"Use terms", 4.237 + module = 'index', 4.238 + view = 'usage_terms' 4.239 + } 4.240 + end } 4.241 + end 4.242 end 4.243 - slot.put(" · ") 4.244 - ui.link{ 4.245 - text = _"LiquidFeedback", 4.246 - external = "http://www.liquidfeedback.org/" 4.247 - } 4.248 + if config.extra_footer_func then 4.249 + config.extra_footer_func() 4.250 + end 4.251 + ui.tag{ tag = "li", content = function() 4.252 + ui.link{ 4.253 + text = _"LiquidFeedback", 4.254 + external = "http://www.liquidfeedback.org/" 4.255 + } 4.256 + end } 4.257 end) 4.258 4.259 if not config.enable_debug_trace then 4.260 @@ -117,4 +171,19 @@ 4.261 end 4.262 4.263 4.264 -execute.inner() 4.265 + 4.266 +if app.current_initiative then 4.267 + app.current_issue = app.current_initiative.issue 4.268 +end 4.269 + 4.270 +if app.current_issue then 4.271 + app.current_area = app.current_issue.area 4.272 +end 4.273 + 4.274 +if app.current_area then 4.275 + app.current_unit = app.current_area.unit 4.276 +end 4.277 + 4.278 +if not for_meta_navigation then 4.279 + execute.view{ module = "index", view = "_drawer" } 4.280 +end
5.1 --- a/app/main/_filter_view/34_stylesheet.lua Thu Jun 23 03:30:57 2016 +0200 5.2 +++ b/app/main/_filter_view/34_stylesheet.lua Sun Jul 15 14:07:29 2018 +0200 5.3 @@ -1,16 +1,3 @@ 5.4 -local value 5.5 -if app.session.member then 5.6 - local setting_key = "liquidfeedback_frontend_stylesheet_url" 5.7 - local setting = Setting:by_pk(app.session.member.id, setting_key) 5.8 - value = setting and setting.value 5.9 -end 5.10 - 5.11 -if value then 5.12 - slot.put_into("stylesheet_url", value) 5.13 -else 5.14 - slot.put_into("stylesheet_url", request.get_relative_baseurl() .. "static/lf3.css") 5.15 -end 5.16 - 5.17 execute.inner() 5.18 5.19 if config.footer_html then
6.1 --- a/app/main/_layout/default.html Thu Jun 23 03:30:57 2016 +0200 6.2 +++ b/app/main/_layout/default.html Sun Jul 15 14:07:29 2018 +0200 6.3 @@ -1,276 +1,759 @@ 6.4 -<html> 6.5 -<head> 6.6 - <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 6.7 - <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> 6.8 - <title><!-- WEBMCP SLOTNODIV html_title --></title> 6.9 - <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/static/gregor.js/gregor.css" /> 6.10 - <link rel="stylesheet" type="text/css" href="<!-- WEBMCP SLOTNODIV stylesheet_url -->" /> 6.11 - <!-- WEBMCP SLOTNODIV html_head --> 6.12 - <script src="__BASEURL__/static/js/jquery-1.11.1.min.js"></script> 6.13 - <script type="text/javascript">jsFail = true;</script> 6.14 - <![if !IE]> 6.15 - <script type="text/javascript">jsFail = false;</script> 6.16 - <![endif]> 6.17 - <script type="text/javascript" src="__BASEURL__/static/js/jsprotect.js"></script> 6.18 - <script type="text/javascript" src="__BASEURL__/static/js/partialload.js"></script> 6.19 - <script type="text/javascript">var ui_tabs_active = {};</script> 6.20 -</head> 6.21 -<body style=""> 6.22 -<div class="head_outer"> 6.23 - <div class="head"> 6.24 - <div class="nav"> 6.25 - <!--WEBMCP SLOTNODIV navigation --> 6.26 - <!--WEBMCP SLOTNODIV navigation_right --> 6.27 - <!--WEBMCP SLOTNODIV notification --> 6.28 - </div> 6.29 - 6.30 - <a class="logo" href="__BASEURL__/"> 6.31 - <span class="liquid">Liquid</span><span class="feedback">Feedback</span> 6.32 - <span class="instanceName"><!-- WEBMCP SLOTNODIV instance_name --></span> 6.33 - </a> 6.34 - 6.35 - 6.36 - </div> 6.37 -</div> 6.38 - 6.39 -<div class="page"> 6.40 - <div class="layout_notice" id="layout_notice" onclick="document.getElementById('layout_notice').style.display='none';"> 6.41 - <!-- WEBMCP SLOT notice --> 6.42 - </div> 6.43 - 6.44 - <div class="layout_warning" id="layout_warning" onclick="document.getElementById('layout_warning').style.display='none';"> 6.45 - <!-- WEBMCP SLOT warning --> 6.46 - </div> 6.47 - 6.48 - <div class="layout_error" id="layout_error" onclick="document.getElementById('layout_error').style.display='none';"> 6.49 - <!-- WEBMCP SLOT error --> 6.50 - </div> 6.51 - 6.52 - <div class="layout_motd" id="layout_motd" onclick="document.getElementById('layout_motd').style.display='none';"> 6.53 - <!-- WEBMCP SLOT motd --> 6.54 - </div> 6.55 - 6.56 - <div class="title_outer"> 6.57 - <!-- WEBMCP SLOT title --> 6.58 - </div> 6.59 - 6.60 - <!-- WEBMCP SLOT tabs --> 6.61 - 6.62 - <!-- WEBMCP SLOTNODIV actions --> 6.63 - 6.64 - <div id="swiper_tabs" class="swiper_tabs" style="display: none;"> 6.65 -<!-- <div><a id="tab-0" href="#" onclick="slider.to(0); return false;"><img src="__BASEURL__/static/icons/16/chart_organisation.png" /></a></div>--> 6.66 - <div><a id="tab-0" href="#" onclick="slider.to(0); return false;"><img src="__BASEURL__/static/icons/16/text_list_bullets.png" width="32" height="32" /></a></div> 6.67 - <div><a id="tab-1" href="#" onclick="slider.to(1); return false;"><img src="__BASEURL__/static/icons/48/info.png" width="32" height="32" /></a></div> 6.68 - <div><a id="tab-2" href="#" onclick="slider.to(2); return false;"><img src="__BASEURL__/static/icons/16/group.png" width="32" height="32" /></a></div> 6.69 - </div> 6.70 - 6.71 - <div id="swiper_info"><!-- WEBMCP SLOTNODIV swiper_info --></div> 6.72 - <div id="swiper" class="swiper" style="position: absolute; width: 100%;"> 6.73 - <div id="swiper_wrap" class="swiper_wrap"> 6.74 - </div> 6.75 - </div> 6.76 - 6.77 - <div class="content"> 6.78 - <div class="sidebar"> 6.79 - <!-- WEBMCP SLOTNODIV sidebar --> 6.80 - </div> 6.81 - 6.82 - <div class="main_outer"> 6.83 - <!-- WEBMCP SLOTNODIV slideshow --> 6.84 - <div class="main"> 6.85 - <!-- WEBMCP SLOTNODIV default --> 6.86 - <!-- WEBMCP SLOTNODIV extra --> 6.87 - </div> 6.88 - </div> 6.89 - </div> 6.90 - <br style="clear: both;" /> 6.91 - <div class="footer"> 6.92 - <!-- WEBMCP SLOTNODIV footer --> 6.93 - </div> 6.94 -</div> 6.95 - 6.96 - <div id="trace"> 6.97 - <!-- WEBMCP SLOTNODIV trace_button --> 6.98 - <div id="trace_content" style="display: none;"> 6.99 - <tt id="system_error"><!-- WEBMCP SLOT system_error --></tt> 6.100 - <h1>System trace (for computer programmers purposes)</h1> 6.101 - <br /> 6.102 - <!-- WEBMCP SLOT trace --> 6.103 - <div class="trace_close" onclick="document.getElementById('trace_show').style.display='block';document.getElementById('trace_content').style.display='none';"> 6.104 - close 6.105 - </div> 6.106 - </div> 6.107 - </div> 6.108 - <script> 6.109 - $(".trace_view > .trace_list").hide(); 6.110 - $(".trace_head").click(function() { 6.111 - var el = this.nextSibling 6.112 - if (el) $(el).toggle(); 6.113 - }); 6.114 - </script> 6.115 - 6.116 - <!-- WEBMCP SLOTNODIV script --> 6.117 - 6.118 - <script> 6.119 - 6.120 - var slider; 6.121 - 6.122 - function initSlider () { 6.123 - 6.124 - var els = [ 6.125 - $( '.main, .extra' ), 6.126 - $( '.tab-notification, .tab-whatcanido' ), 6.127 - $( '.tab-members' ) 6.128 - ]; 6.129 - 6.130 - var sidebarFound = false; 6.131 - for ( i = 1; i < els.length; i++) { 6.132 - if (els[i].length > 0) sidebarFound = true; 6.133 - } 6.134 - 6.135 - if (sidebarFound) $("#swiper_tabs").show(); 6.136 - 6.137 - var elsCount = 3; 6.138 - 6.139 - var slidePos; 6.140 - 6.141 - function slideTo ( pos ) { 6.142 - if ( typeof ( slidePos ) != "undefined" ) { 6.143 - els[ slidePos ].hide(); 6.144 - $ ( "#tab-" + slidePos ).removeClass ( "active" ); 6.145 - } 6.146 - slidePos = pos; 6.147 - els[ slidePos ].show(); 6.148 - $ ( "#tab-" + slidePos ).addClass ( "active" ); 6.149 - if (pos == 1) { 6.150 - $("#swiper_info").hide(); 6.151 - } 6.152 - } 6.153 - 6.154 - function slideNext () { 6.155 - var pos = slidePos + 1; 6.156 - if ( pos > elsCount - 1 ) { 6.157 - pos = elsCount - 1; 6.158 - } else { 6.159 - $( "#swiper").css("left", "400px"); 6.160 - $( "#swiper").animate({ "left": "0px" }, 200); 6.161 - } 6.162 - slideTo ( pos ); 6.163 - } 6.164 - 6.165 - function slidePrev () { 6.166 - var pos = slidePos - 1; 6.167 - if ( pos < 0 ) { 6.168 - pos = 0; 6.169 - } else { 6.170 - $( "#swiper").css("left", "-400px"); 6.171 - $( "#swiper").animate({ "left": "0px" }, 200); 6.172 - } 6.173 - slideTo ( pos ); 6.174 - } 6.175 - 6.176 - function exit() { 6.177 - for ( i = 0; i < els.length; i++) { 6.178 - els[i].show(); 6.179 - } 6.180 - $( ".main_outer" ).append ( $( ".main" ).detach() ); 6.181 - $( ".extra_outer" ).append ( $( ".extra" ).detach() ); 6.182 - $( ".sidebar" ).append ( $(els[1]).detach() ); 6.183 - $( ".sidebar" ).append ( $(els[2]).detach() ); 6.184 - $( ".page" ).append ( $('.footer') ); 6.185 - $( "body" ).append ( $('#trace') ); 6.186 - 6.187 - } 6.188 - 6.189 - var touchStartX; 6.190 - var touchStartY; 6.191 - var isScrolling; 6.192 - 6.193 - function touchDown ( e ) { 6.194 - touchStartX = e.originalEvent.touches[0].pageX; 6.195 - touchStartY = e.originalEvent.touches[0].pageY; 6.196 - isScrolling = undefined; 6.197 - } 6.198 - 6.199 - function touchMove ( e ) { 6.200 - var diffX = touchStartX - e.originalEvent.changedTouches[0].pageX; 6.201 - var diffY = touchStartY - e.originalEvent.changedTouches[0].pageY; 6.202 - if ( typeof( isScrolling ) == 'undefined' ) { 6.203 - isScrolling = Math.abs ( diffY ) > Math.abs ( diffX ); 6.204 - } 6.205 - if ( ! isScrolling ) { 6.206 - $( "#swiper").css("left", -diffX + "px"); 6.207 - e.preventDefault(); 6.208 - } 6.209 - } 6.210 - 6.211 - function touchUp ( e ) { 6.212 - var diffX = touchStartX - e.originalEvent.changedTouches[0].pageX; 6.213 - var diffY = touchStartY - e.originalEvent.changedTouches[0].pageY; 6.214 - 6.215 - if ( isScrolling ) { 6.216 - // vertical scrolling 6.217 - return; 6.218 - } 6.219 - 6.220 - if ( Math.abs ( diffX ) < 100 ) { 6.221 - // go back not enough 6.222 - $( "#swiper").animate({ 6.223 - "left": "0px", 6.224 - }, 200); 6.225 - return; 6.226 - } 6.227 - var direction = diffX < 0 ? "right" : "left"; 6.228 - 6.229 - if ( direction == "left" ) { 6.230 - slideNext(); 6.231 - } else { 6.232 - slidePrev(); 6.233 - } 6.234 - } 6.235 - 6.236 - for ( i = 0; i < els.length; i++) { 6.237 - var el = els[i].detach(); 6.238 - el.hide(); 6.239 - $('#swiper_wrap').append ( el ) ; 6.240 - } 6.241 - 6.242 - $('#swiper_wrap').append ( $('.footer') ); 6.243 - $('#swiper_wrap').append ( $('#trace') ); 6.244 - slideTo ( 0 ); 6.245 - 6.246 - if ( 'ontouchstart' in document.documentElement ) { 6.247 - $( "body" ).on( "touchstart", touchDown ); 6.248 - $( "body" ).on( "touchmove", touchMove ); 6.249 - $( "body" ).on( "touchend", touchUp ); 6.250 - } 6.251 - 6.252 - return { 6.253 - to: slideTo, 6.254 - exit: exit 6.255 - } 6.256 - 6.257 - } 6.258 - 6.259 - function resizeHandler() { 6.260 - if ( $(window).width() < 768 ) { 6.261 - if ( typeof ( slider ) == "undefined" ) { 6.262 - slider = initSlider(); 6.263 - } 6.264 - } else { 6.265 - if ( typeof ( slider ) != "undefined" ) { 6.266 - slider.exit(); 6.267 - slider = undefined; 6.268 - } 6.269 - } 6.270 - 6.271 - } 6.272 - 6.273 - $( window ).resize( resizeHandler ); 6.274 - resizeHandler(); 6.275 - 6.276 - </script> 6.277 - 6.278 -</body> 6.279 -</html> 6.280 +<!DOCTYPE html> 6.281 +<html> 6.282 +<head> 6.283 + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 6.284 + <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> 6.285 + <title><!-- WEBMCP SLOTNODIV html_title --></title> 6.286 + <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/style/style.css" /> 6.287 + <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/static/gregor.js/gregor.css" /> 6.288 + <!-- WEBMCP SLOTNODIV html_head --> 6.289 + <script type="text/javascript">jsFail = true;</script> 6.290 + <![if !IE]> 6.291 + <script type="text/javascript">jsFail = false;</script> 6.292 + <![endif]--> 6.293 + <script type="text/javascript" src="__BASEURL__/static/mdl/material.js"></script> 6.294 + <script type="text/javascript" src="__BASEURL__/static/js/jsprotect.js"></script> 6.295 + <script type="text/javascript" src="__BASEURL__/static/js/partialload.js"></script> 6.296 + <style> 6.297 + @font-face { 6.298 + font-family: "Roboto"; 6.299 + src: url("__BASEURL__/static/font/Roboto-Regular.ttf"); 6.300 + } 6.301 + @font-face { 6.302 + font-family: "Roboto Bold"; 6.303 + src: url("__BASEURL__/static/font/Roboto-Bold.ttf"); 6.304 + } 6.305 + @font-face { 6.306 + font-family: "Roboto Slab"; 6.307 + src: url("__BASEURL__/static/font/RobotoSlab-Regular.ttf"); 6.308 + } 6.309 + @font-face { 6.310 + font-family: 'Material Icons'; 6.311 + font-style: normal; 6.312 + font-weight: 400; 6.313 + src: url("__BASEURL__/static/font/material.woff2") format('woff2'); 6.314 + } 6.315 + 6.316 + .material-icons { 6.317 + font-family: 'Material Icons'; 6.318 + font-weight: normal; 6.319 + font-style: normal; 6.320 + font-size: 24px; 6.321 + line-height: 1; 6.322 + letter-spacing: normal; 6.323 + text-transform: none; 6.324 + display: inline-block; 6.325 + white-space: nowrap; 6.326 + word-wrap: normal; 6.327 + direction: ltr; 6.328 + -webkit-font-feature-settings: 'liga'; 6.329 + -webkit-font-smoothing: antialiased; 6.330 + } 6.331 + .material-icons.icon-green { 6.332 + color: #4CAF50; 6.333 + } 6.334 + .material-icons.icon-yellow { 6.335 + color: #FFEB3B; 6.336 + } 6.337 + .material-icons.icon-red { 6.338 + color: #F44336; 6.339 + } 6.340 + .mapboxgl-popup-content { 6.341 + max-width: 200px; 6.342 + } 6.343 + body { 6.344 + font-family: "Roboto"; 6.345 + background: #fff; 6.346 + } 6.347 + strong { 6.348 + font-family: "Roboto Bold"; 6.349 + } 6.350 + .slot_error { 6.351 + margin: 20px 20px 0 20px; 6.352 + background: #fed; 6.353 + padding: 20px; 6.354 + } 6.355 + .slot_notice { 6.356 + margin: 20px 20px 0 20px; 6.357 + background: #efd; 6.358 + padding: 20px; 6.359 + } 6.360 + h1 { 6.361 + font-size: 24px; 6.362 + margin-bottom: 0px; 6.363 + } 6.364 + 6.365 + .mdl-layout__container { 6.366 + max-width: 1600px; 6.367 + height: auto; 6.368 + background: #f7f7f7; 6.369 + } 6.370 + .mdl-layout__header-row { 6.371 + padding-left: 20px !important; 6.372 + padding-right: 10px !important; 6.373 + } 6.374 + .mdl-layout__content { 6.375 + flex-shrink: 0; 6.376 + width: 100%; 6.377 + } 6.378 + #lf-member-menu { 6.379 + cursor: pointer; 6.380 + } 6.381 + .notifications { 6.382 + display: block; 6.383 + text-align: center; 6.384 + } 6.385 + .page-content { 6.386 + xdisplay: flex; 6.387 + width: 100%; 6.388 + } 6.389 + .map, .ui_field_location { 6.390 + border: none; 6.391 + width: 100%; 6.392 + height: auto; 6.393 + max-height: 80vh; 6.394 + position: relative; 6.395 + border-radius: 2px; 6.396 + } 6.397 + .map:before { 6.398 + content: ""; 6.399 + display: block; 6.400 + padding-top: 100%; 6.401 + } 6.402 + .map iframe { 6.403 + position: absolute; 6.404 + top: 0; 6.405 + left: 0; 6.406 + right: 0; 6.407 + bottom: 0; 6.408 + height: 100%; 6.409 + } 6.410 + .axto-logo { 6.411 + width: 100%; 6.412 + } 6.413 + .mdl-card__title-text a.issue { 6.414 + font-size: 18px; 6.415 + line-height: 20px; 6.416 + } 6.417 + .mdl-list__item .initiative_name { 6.418 + font-size: 18px; 6.419 + line-height: normal; 6.420 + } 6.421 + .initiatives.mdl-list { 6.422 + margin-top: 10px; 6.423 + margin-bottom: 0px; 6.424 + padding-bottom: 0px; 6.425 + } 6.426 + .initiatives .mdl-list__item--three-line { 6.427 + min-height: 0; 6.428 + padding-top: 0; 6.429 + padding-bottom: 0; 6.430 + } 6.431 + .mdl-data-table__fullwidth { 6.432 + width: 100%; 6.433 + } 6.434 + .mdl-textfield__fullwidth { 6.435 + width: 100%; 6.436 + } 6.437 + 6.438 + .mdl-card { 6.439 + min-height: 20px; 6.440 + overflow: visible; 6.441 + display: block; 6.442 + margin-bottom: 16px; 6.443 + } 6.444 + 6.445 + .mdl-card__fullwidth { 6.446 + width: 100%; 6.447 + } 6.448 + .mdl-card__title { 6.449 + display: block; 6.450 + } 6.451 + .mdl-card__title > h2, 6.452 + .mdl-card__title > a { 6.453 + display: inline-block; 6.454 + vertical-align: top; 6.455 + } 6.456 + .mdl-card__subtitle-text { 6.457 + display: block; 6.458 + } 6.459 + .mdl-card__content { 6.460 + font-size: 1rem; 6.461 + padding: 16px; 6.462 + } 6.463 + .mdl-card__content.mdl-card--no-bottom-pad { 6.464 + padding-bottom: 0; 6.465 + } 6.466 + .mdl-card__actions.mdl-card--action-border, 6.467 + .mdl-card__content.mdl-card--border { 6.468 + border-bottom: 1px solid rgba(0, 0, 0, 0.1); 6.469 + } 6.470 + .mdl-card__actions.mdl-card--notice, 6.471 + .mdl-card__content.mdl-card--notice { 6.472 + background: #fff9c4; 6.473 + } 6.474 + .mdl-card--has-fab { 6.475 + position: relative; 6.476 + } 6.477 + .mdl-special-card { 6.478 + background: #fff; 6.479 + margin-bottom: 16px; 6.480 + border-radius: 2px; 6.481 + } 6.482 + 6.483 + .mdl-navigation__head { 6.484 + font-family: "Roboto Bold"; 6.485 + } 6.486 + .mdl-list__item--three-line { 6.487 + height: auto; 6.488 + min-height: 88px; 6.489 + } 6.490 + .mdl-list__item--three-line .mdl-list__item-primary-content { 6.491 + height: auto; 6.492 + min-height: 52px; 6.493 + } 6.494 + .mdl-chip.clickable { 6.495 + cursor: pointer; 6.496 + } 6.497 + .mdl-chip.unit, 6.498 + .mdl-chip.area, 6.499 + .mdl-chip.issue { 6.500 + padding: 1px 6px; 6.501 + line-height: 24px; 6.502 + height: 24px; 6.503 + vertical-align: bottom; 6.504 + margin-right: 4px; 6.505 + margin-bottom: 8px; 6.506 + } 6.507 + .mdl-chip.unit i, 6.508 + .mdl-chip.area i, 6.509 + .mdl-chip.issue i { 6.510 + font-size: 18px; 6.511 + color: #444; 6.512 + } 6.513 + .mdl-chip.unit { 6.514 + xbackground: rgba(76,175,80,0.2); 6.515 + } 6.516 + .mdl-chip.area { 6.517 + xbackground: rgba(76,175,80,0.2); 6.518 + } 6.519 + .mdl-chip.issue { 6.520 + xbackground: rgba(76,175,80,0.2); 6.521 + } 6.522 + .mdl-menu { 6.523 + z-index: -9999; 6.524 + } 6.525 + .mdl-menu__item { 6.526 + padding: 0; 6.527 + } 6.528 + .mdl-menu__link { 6.529 + display: block; 6.530 + padding: 0 16px; 6.531 + min-height: 48px; 6.532 + text-decoration: none; 6.533 + color: #444; 6.534 + } 6.535 + .mdl-tabs__tab-bar { 6.536 + justify-content: flex-start; 6.537 + } 6.538 + .mdl-tabs__tab.is-active { 6.539 + color: rgba(0,0,0, 0.87); 6.540 + } 6.541 + .mdl-data-table--condensed tbody tr { 6.542 + height: 24px; 6.543 + } 6.544 + .mdl-data-table--condensed td { 6.545 + padding-top: 0; 6.546 + padding-bottom: 0; 6.547 + height: 24px; 6.548 + } 6.549 + .mdl-button--underlined { 6.550 + text-decoration: underline; 6.551 + } 6.552 + .right { 6.553 + text-align: right; 6.554 + } 6.555 + .mdl-tabs__left { 6.556 + display: inline-block; 6.557 + width: auto; 6.558 + } 6.559 + .float-left { 6.560 + float: left; 6.561 + } 6.562 + .float-right { 6.563 + float: right; 6.564 + } 6.565 + .clear-left { 6.566 + clear: left; 6.567 + } 6.568 + .clear-right { 6.569 + clear: right; 6.570 + } 6.571 + .float-right .mdl-menu__container { 6.572 + margin-right: 20px; 6.573 + } 6.574 + .inline { 6.575 + display: inline-block; 6.576 + } 6.577 + .warning { 6.578 + background-color: #fff9c4; 6.579 + color: #212121; 6.580 + padding: 16px; 6.581 + border-radius: 2px; 6.582 + } 6.583 + .toolbar a { 6.584 + color: #000; 6.585 + display: inline-block; 6.586 + border: 1px solid #ccc; 6.587 + position: relative; 6.588 + } 6.589 + 6.590 + .toolbar a.wysihtml-command-active { 6.591 + background: #000; 6.592 + color: #fff; 6.593 + 6.594 + } 6.595 + 6.596 + .toolbar a span.crossed { 6.597 + position: absolute; 6.598 + top: 0; 6.599 + left: 8px; 6.600 + font-weight: bold; 6.601 + font-size: 200%; 6.602 + line-height: 80%; 6.603 + } 6.604 + .toolbar a span.head_level { 6.605 + position: absolute; 6.606 + right: 0; 6.607 + bottom: 0; 6.608 + } 6.609 + 6.610 + textarea { 6.611 + font-family: sans-serif; 6.612 + font-weight: normal !important; 6.613 + width: 100%; 6.614 + height: 40ex; 6.615 + } 6.616 + .bargraph { 6.617 + display: inline-block; 6.618 + vertical-align: top; 6.619 + height: 9px; 6.620 + } 6.621 + 6.622 + .bargraph div { 6.623 + margin: 0; 6.624 + padding: 0; 6.625 + display: inline-block; 6.626 + height: 9px; 6.627 + } 6.628 + .draft { 6.629 + font-family: 'Roboto Slab'; 6.630 + font-weight: 300; 6.631 + overflow: auto; 6.632 + clear: left; 6.633 + } 6.634 + .draft img { 6.635 + float: right; 6.636 + margin-left: 1em; 6.637 + } 6.638 + 6.639 + .draft .landscape img { 6.640 + width: 50%; 6.641 + } 6.642 + 6.643 + .draft .portrait img { 6.644 + width: 33.333%; 6.645 + } 6.646 + 6.647 + @media (max-width: 960px) { 6.648 + .draft img { 6.649 + float: none; 6.650 + margin-left: 0; 6.651 + } 6.652 + .draft a.portrait, 6.653 + .draft a.landscape { 6.654 + width: 100%; 6.655 + display: block; 6.656 + text-align: center; 6.657 + margin-bottom: 2ex; 6.658 + } 6.659 + .draft .landscape img { 6.660 + width: 100%; 6.661 + } 6.662 + .draft .portrait img { 6.663 + width: 66.666%; 6.664 + } 6.665 + } 6.666 + 6.667 + .draft h1 { 6.668 + font-size: 150%; 6.669 + margin-bottom: 0; 6.670 + } 6.671 + .draft h2 { 6.672 + font-size: 133%; 6.673 + margin-bottom: 0; 6.674 + } 6.675 + .draft h3 { 6.676 + font-size: 125%; 6.677 + margin-bottom: 0; 6.678 + } 6.679 + .draft > a:first-child + h1, 6.680 + .draft > h1:first-child, 6.681 + .draft > h2:first-child, 6.682 + .draft > h3:first-child { 6.683 + margin-top: 0; 6.684 + } 6.685 + @media (max-width: 960px) { 6.686 + .draft > a:first-child + h1 { 6.687 + margin-top: 20px; 6.688 + } 6.689 + } 6.690 + 6.691 + .folded { 6.692 + position: relative; 6.693 + } 6.694 + .folded .suggestion-content { 6.695 + max-height: 180px; 6.696 + overflow: hidden; 6.697 + position: relative; 6.698 + } 6.699 + .folded .suggestion-content:before { 6.700 + content: ""; 6.701 + width: 100%; 6.702 + height: 25%; 6.703 + position: absolute; 6.704 + left: 0; 6.705 + bottom: 0; 6.706 + background: linear-gradient(rgba(0,0,0,0), #fff); 6.707 + } 6.708 + .suggestion-less, 6.709 + .suggestion-more { 6.710 + display: none; 6.711 + } 6.712 + .folded .suggestion-more, 6.713 + .unfolded .suggestion-less { 6.714 + display: inline-block; 6.715 + } 6.716 + 6.717 + .diff .diff_removed { 6.718 + text-decoration: line-through; 6.719 + background: #f00; 6.720 + } 6.721 + 6.722 + .diff .diff_added { 6.723 + text-decoration: underline; 6.724 + background: #0f0; 6.725 + } 6.726 + 6.727 + .material-icons { 6.728 + vertical-align: middle; 6.729 + } 6.730 + .material-icons-small { 6.731 + font-size: 18px; 6.732 + } 6.733 + 6.734 + a.mdl-layout-title { 6.735 + color: #fff; 6.736 + text-decoration: none; 6.737 + } 6.738 + 6.739 + .what-can-i-do-here .mdl-card__content { 6.740 + font-size: 18px; 6.741 + } 6.742 + 6.743 + .what-can-i-do-here .mdl-card__content ul { 6.744 + font-size: 1rem; 6.745 + } 6.746 + 6.747 + .what-can-i-do-here ul { 6.748 + margin-top: 0.5em; 6.749 + margin-bottom: 0; 6.750 + padding-left: 1.5em 6.751 + } 6.752 + 6.753 + 6.754 + 6.755 + .lf-filter .mdl-button { 6.756 + padding-right: 4px; 6.757 + } 6.758 + 6.759 + .phase-current { 6.760 + font-family: "Roboto Bold"; 6.761 + } 6.762 + 6.763 + .phase-info { 6.764 + padding-left: 27px; 6.765 + margin-bottom: 1ex; 6.766 + } 6.767 + 6.768 + .clickable { 6.769 + cursor: pointer; 6.770 + } 6.771 + 6.772 + .slot_title { 6.773 + margin-left: 16px; 6.774 + margin-top: 16px; 6.775 + font-size: 18px; 6.776 + } 6.777 + 6.778 + .slot_title > a, 6.779 + .slot_title > span { 6.780 + display: inline-block; 6.781 + padding: 0 4px 0 0; 6.782 + 6.783 + } 6.784 + 6.785 + .slot_title a.home { 6.786 + margin: 0; 6.787 + } 6.788 + 6.789 + .home i { 6.790 + font-size: 32px; 6.791 + } 6.792 + 6.793 + /* fix missing contrast */ 6.794 + .mdl-list__item--three-line .mdl-list__item-text-body, 6.795 + .mdl-card__supporting-text, 6.796 + .mdl-card__subtitle-text { 6.797 + color: rgba(0,0,0, 0.75); 6.798 + } 6.799 + 6.800 + .mdl-textfield__label { 6.801 + color: rgba(0,0,0, 0.5); 6.802 + } 6.803 + 6.804 + .mdl-list__item--three-line .mdl-list__item-text-body { 6.805 + height: auto; 6.806 + padding-bottom: 10px; 6.807 + } 6.808 + 6.809 + select { 6.810 + font-family: 'Roboto','Helvetica','Arial',sans-serif; 6.811 + background-color: transparent; 6.812 + padding: 5px 0; 6.813 + border: none; 6.814 + border-bottom: 1px solid rgba(0,0,0, 0.12); 6.815 + font-size: 1rem; 6.816 + } 6.817 + 6.818 + 6.819 + .initiative_list .mdl-list__item { 6.820 + display: block; 6.821 + } 6.822 + .initiative_list .mdl-list__item-avatar { 6.823 + float: left; 6.824 + margin-right: 8px; 6.825 + } 6.826 + .initiative_list .mdl-list__item-avatar i { 6.827 + padding-left: 8px; 6.828 + } 6.829 + .initiative_list .mdl-list__item-avatar.positive { 6.830 + background: #4caf50; 6.831 + } 6.832 + .initiative_list .mdl-list__item-avatar.positive i { 6.833 + padding-top: 8px; 6.834 + vertical-align: top; 6.835 + } 6.836 + .initiative_list .mdl-list__item-avatar.negative { 6.837 + background: #f44336; 6.838 + } 6.839 + .initiative_list .mdl-list__item-avatar.negative i { 6.840 + padding-top: 10px; 6.841 + vertical-align: top; 6.842 + } 6.843 + 6.844 + .initiative_pie { 6.845 + float: left; 6.846 + margin-right: 15px; 6.847 + margin-bottom: 15px; 6.848 + } 6.849 + 6.850 + .initiative .result { 6.851 + font-family: "Roboto Bold"; 6.852 + } 6.853 + 6.854 + .admitted_info, .not_admitted_info { 6.855 + margin-left: 15px; 6.856 + } 6.857 + 6.858 + .admitted_info table th, 6.859 + .not_admitted_info table th { 6.860 + text-align: left; 6.861 + padding-right: 10px; 6.862 + } 6.863 + 6.864 + .admitted_info table td, 6.865 + .not_admitted_info table td { 6.866 + text-align: right; 6.867 + padding-right: 5px; 6.868 + } 6.869 + 6.870 + .admitted 6.871 + 6.872 + .event_info i { 6.873 + font-size: 18px; 6.874 + color: #444; 6.875 + margin-right: 4px; 6.876 + } 6.877 + 6.878 + .ui_list_row label.mdl-radio { 6.879 + display: inline; 6.880 + } 6.881 + 6.882 + .member_image_photo { 6.883 + margin-top: 2ex; 6.884 + margin-bottom: 2ex; 6.885 + } 6.886 + 6.887 + 6.888 +/************************************************************************* 6.889 + * Voting 6.890 + */ 6.891 +.main .section #voting_form .sectionRow:last-child { 6.892 + border-radius: 0; 6.893 + margin-bottom: 0; 6.894 +} 6.895 +#voting { 6.896 + margin-top: 3ex; 6.897 + position: relative; 6.898 + margin-bottom: 2ex; 6.899 +} 6.900 +#voting .approval, 6.901 +#voting .abstention, 6.902 +#voting .disapproval { 6.903 + border: 1px #ccc solid; 6.904 + margin-bottom: 3ex; 6.905 + padding: 1ex; 6.906 + padding-bottom: 1ex; 6.907 + border-radius: 2px; 6.908 + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12); 6.909 +} 6.910 +#voting .movable { 6.911 + cursor: pointer; 6.912 +} 6.913 +#voting .disapproval { 6.914 + margin-bottom: 2ex; 6.915 +} 6.916 +#voting .approval { 6.917 + background-color: #9f9; 6.918 +} 6.919 +#voting .approval .movable { 6.920 + background-color: #dfd; 6.921 +} 6.922 +#voting .abstention { 6.923 + background-color: #eee; 6.924 +} 6.925 +#voting .abstention .movable { 6.926 + background-color: #fff; 6.927 +} 6.928 +#voting .disapproval { 6.929 + background-color: #f88; 6.930 +} 6.931 +#voting .disapproval .movable { 6.932 + background-color: #fbb; 6.933 +} 6.934 +#voting .movable { 6.935 + position: relative; 6.936 + border: 1px black solid; 6.937 + margin-top: 1ex; 6.938 + padding: 0; 6.939 + border-radius: 2px; 6.940 + min-height: 64px; 6.941 + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12); 6.942 +} 6.943 +#voting .initiative_name { 6.944 + display: inline-block; 6.945 + margin: 5px; 6.946 + font-family: "Roboto Bold"; 6.947 +} 6.948 + 6.949 +#voting .voting_form_active .movable { 6.950 + cursor: pointer; 6.951 + vertical-align: middle; 6.952 + cursor: move; 6.953 +} 6.954 +#voting .voting_form_active .clickable { 6.955 + cursor: auto; 6.956 +} 6.957 +#voting .voting_form_active a.clickable { 6.958 + cursor: pointer; 6.959 +} 6.960 + 6.961 + </style> 6.962 +<!-- WEBMCP SLOTNODIV meta_navigation_style --> 6.963 + 6.964 +</head> 6.965 +<body style=""> 6.966 +<!-- WEBMCP SLOTNODIV meta_navigation --> 6.967 + 6.968 +<div class="mdl-layout mdl-js-layout mdl-shadow--2dp mdl-layout--fixed-header"> 6.969 + <!-- WEBMCP SLOTNODIV header_bar --> 6.970 + <main class="mdl-layout__content"> 6.971 + <!-- WEBMCP SLOTNODIV notificationx --> 6.972 + <!-- WEBMCP SLOT notice --> 6.973 + <!-- WEBMCP SLOT warning --> 6.974 + <!-- WEBMCP SLOT error --> 6.975 + <!-- WEBMCP SLOT motd --> 6.976 + <section class="mdl-layout__tab-panel is-active" id="scroll-tab-1"> 6.977 + <div class="page-content"> 6.978 + <!-- WEBMCP SLOT title --> 6.979 + <!-- WEBMCP SLOT filter --> 6.980 + <!-- WEBMCP SLOTNODIV default --> 6.981 + <!-- WEBMCP SLOTNODIV extra --> 6.982 + <!-- WEBMCP SLOTNODIV sidebar --> 6.983 + </div> 6.984 + </section> 6.985 + <br /> 6.986 + <br /> 6.987 + <br /> 6.988 + <br /> 6.989 + <footer class="mdl-mini-footer"> 6.990 + <div class="mdl-mini-footer__left-section"> 6.991 + <ul class="mdl-mini-footer__link-list"> 6.992 + <!-- WEBMCP SLOTNODIV footer --> 6.993 + </ul> 6.994 + </div> 6.995 + </footer> 6.996 + <br /><br /> 6.997 + <div id="trace"> 6.998 + <!-- WEBMCP SLOTNODIV trace_button --> 6.999 + <div id="trace_content" style="display: none;"> 6.1000 + <div id="system_error"><!-- WEBMCP SLOT system_error --></div> 6.1001 + <h1>System trace (for computer programmers purposes)</h1> 6.1002 + <br /> 6.1003 + <!-- WEBMCP SLOT trace --> 6.1004 + <div class="trace_close" onclick="document.getElementById('trace_show').style.display='block';document.getElementById('trace_content').style.display='none';"> 6.1005 + close 6.1006 + </div> 6.1007 + </div> 6.1008 + </div> 6.1009 + </main> 6.1010 +</div> 6.1011 + 6.1012 +<div class="head_outer"> 6.1013 + <div class="head"> 6.1014 + <div class="nav"> 6.1015 + <!--WEBMCP SLOTNODIV navigation --> 6.1016 + </div> 6.1017 + 6.1018 + </div> 6.1019 +</div> 6.1020 + 6.1021 +<!-- WEBMCP SLOTNODIV dialog --> 6.1022 + 6.1023 +<!-- WEBMCP SLOT tabs --> 6.1024 + 6.1025 +<script> 6.1026 +/* 6.1027 + $(".trace_view > .trace_list").hide(); 6.1028 + $(".trace_head").click(function() { 6.1029 + var el = this.nextSibling 6.1030 + if (el) $(el).toggle(); 6.1031 + }); 6.1032 + */ 6.1033 +</script> 6.1034 + 6.1035 +<!-- WEBMCP SLOTNODIV script --> 6.1036 + 6.1037 +</body> 6.1038 +</html>
7.1 --- a/app/main/_layout/system_error.html Thu Jun 23 03:30:57 2016 +0200 7.2 +++ b/app/main/_layout/system_error.html Sun Jul 15 14:07:29 2018 +0200 7.3 @@ -3,19 +3,10 @@ 7.4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 7.5 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> 7.6 <title><!-- WEBMCP SLOTNODIV html_title --></title> 7.7 - <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/static/gregor.js/gregor.css" /> 7.8 - <link rel="stylesheet" type="text/css" href="__BASEURL__/static/lf3.css" /> 7.9 <!-- WEBMCP SLOTNODIV html_head --> 7.10 - <script src="__BASEURL__/static/js/jquery-1.11.1.min.js"></script> 7.11 - <script type="text/javascript">jsFail = true;</script> 7.12 - <![if !IE]> 7.13 - <script type="text/javascript">jsFail = false;</script> 7.14 - <![endif]> 7.15 - <script type="text/javascript" src="__BASEURL__/static/js/jsprotect.js"></script> 7.16 - <script type="text/javascript" src="__BASEURL__/static/js/partialload.js"></script> 7.17 - <script type="text/javascript">var ui_tabs_active = {};</script> 7.18 </head> 7.19 <body style=""> 7.20 +<!-- WEBMCP SLOTNODIV meta_navigation --> 7.21 <div class="head_outer"> 7.22 <div class="head"> 7.23 <div class="nav">
8.1 --- a/app/main/_prefork/10_init.lua Thu Jun 23 03:30:57 2016 +0200 8.2 +++ b/app/main/_prefork/10_init.lua Sun Jul 15 14:07:29 2018 +0200 8.3 @@ -1,4 +1,4 @@ 8.4 -config.app_version = "3.2.1" 8.5 +config.app_version = "4.0.0-pre" 8.6 8.7 -- TODO abstraction 8.8 -- get record by id 8.9 @@ -64,6 +64,61 @@ 8.10 config.ldap = {} 8.11 end 8.12 8.13 +if config.oauth2 then 8.14 + local scopes = { 8.15 + { scope = "authentication", name = { de = "Identität feststellen (nur Screen-Name)", en = "Determine identity (screen name only)" } }, 8.16 + { scope = "identification", name = { de = "Identität feststellen", en = "Determine identity" } }, 8.17 + { scope = "notify_email", name = { de = "E-Mail-Adresse für Benachrichtigungen verwenden", en = "Use email address for notifications" } }, 8.18 + { scope = "read_contents", name = { de = "Inhalte lesen", en = "Read content" } }, 8.19 + { scope = "read_authors", name = { de = "Autorennamen lesen", en = "Read author names" } }, 8.20 + { scope = "read_ratings", name = { de = "Bewertungen lesen", en = "Read ratings" } }, 8.21 + { scope = "read_identities", name = { de = "Identitäten lesen", en = "Read identities" } }, 8.22 + { scope = "read_profiles", name = { de = "Profile lesen", en = "Read profiles" } }, 8.23 + { scope = "post", name = { de = "Neue Inhalte veröffentlichen", en = "Post new content" } }, 8.24 + { scope = "rate", name = { de = "Bewertungen vornehmen", en = "Do ratings" } }, 8.25 + { scope = "vote", name = { de = "Abstimmen", en = "Vote" } }, 8.26 + { scope = "delegate", name = { de = "Delegieren", en = "Delegate" } }, 8.27 + { scope = "profile", name = { de = "Eigenes Profil lesen", en = "Read your profile" } }, 8.28 + { scope = "settings", name = { de = "Einstellungen einsehen", en = "Read your settings" } }, 8.29 + { scope = "update_name", name = { de = "Screen-Namen ändern", en = "Update screen name" } }, 8.30 + { scope = "update_notify_email", name = { de = "E-Mail-Adresse für Benachrichtigungen ändern", en = "Update notify email address" } }, 8.31 + { scope = "update_profile", name = { de = "Profil bearbeiten", en = "Update your profile" } }, 8.32 + { scope = "update_settings", name = { de = "Benutzereinstellungen ändern", en = "Update your settings" } } 8.33 + } 8.34 + local s = config.oauth2.available_scopes or {} 8.35 + for i, scope in ipairs(scopes) do 8.36 + s[#s+1] = scope 8.37 + end 8.38 + config.oauth2.available_scopes = s 8.39 + if not config.oauth2.endpoint_magic then 8.40 + config.oauth2.endpoint_magic = "liquidfeedback_client/redirection_endpoint" 8.41 + end 8.42 + if not config.oauth2.manifest_magic then 8.43 + config.oauth2.manifest_magic = "liquidfeedback_client/manifest" 8.44 + end 8.45 + if not config.oauth2.host_func then 8.46 + config.oauth2.host_func = function(domain) return extos.pfilter(nil, "host", "-t", "TXT", domain) end 8.47 + end 8.48 + if not config.oauth2.authorization_code_lifetime then 8.49 + config.oauth2.authorization_code_lifetime = 5 * 60 8.50 + end 8.51 + if not config.oauth2.refresh_token_lifetime then 8.52 + config.oauth2.refresh_token_lifetime = 60 * 60 * 24 * 30 * 3 8.53 + end 8.54 + if not config.oauth2.refresh_pause then 8.55 + config.oauth2.refresh_pause = 60 8.56 + end 8.57 + if not config.oauth2.refresh_grace_period then 8.58 + config.oauth2.refresh_grace_period = 60 8.59 + end 8.60 + if not config.oauth2.access_token_lifetime then 8.61 + config.oauth2.access_token_lifetime = 60 * 60 8.62 + end 8.63 + if not config.oauth2.dynamic_registration_lifetime then 8.64 + config.oauth2.dynamic_registration_lifetime = 60 * 60 * 24 8.65 + end 8.66 +end 8.67 + 8.68 if not config.database then 8.69 config.database = { engine='postgresql', dbname='liquid_feedback' } 8.70 end 8.71 @@ -135,10 +190,22 @@ 8.72 8.73 request.set_absolute_baseurl(config.absolute_base_url) 8.74 8.75 +-- TODO remove style cache 8.76 + 8.77 listen(listen_options) 8.78 8.79 listen{ 8.80 { 8.81 + proto = "main", 8.82 + name = "process_event_stream", 8.83 + handler = function(poll) 8.84 + Event:process_stream(poll) 8.85 + end 8.86 + } 8.87 +} 8.88 + 8.89 +listen{ 8.90 + { 8.91 proto = "interval", 8.92 name = "send_pending_notifications", 8.93 delay = 5, 8.94 @@ -149,11 +216,6 @@ 8.95 end 8.96 end 8.97 while true do 8.98 - if not Event:send_next_notification() then 8.99 - break 8.100 - end 8.101 - end 8.102 - while true do 8.103 if not InitiativeForNotification:notify_next_member() then 8.104 break 8.105 end
9.1 --- a/app/main/admin/_action/area_update.lua Thu Jun 23 03:30:57 2016 +0200 9.2 +++ b/app/main/admin/_action/area_update.lua Sun Jul 15 14:07:29 2018 +0200 9.3 @@ -1,6 +1,6 @@ 9.4 local area = Area:by_id(param.get_id()) or Area:new() 9.5 9.6 -param.update(area, "unit_id", "name", "description", "external_reference", "active") 9.7 +param.update(area, "unit_id", "name", "description", "external_reference", "quorum_standard", "quorum_issues", "quorum_time", "quorum_exponent", "quorum_factor", "active") 9.8 9.9 if #area.name < 1 then 9.10 slot.select("error", function()
10.1 --- a/app/main/admin/_action/member_update.lua Thu Jun 23 03:30:57 2016 +0200 10.2 +++ b/app/main/admin/_action/member_update.lua Sun Jul 15 14:07:29 2018 +0200 10.3 @@ -2,7 +2,21 @@ 10.4 10.5 local member = Member:by_id(id) or Member:new() 10.6 10.7 -param.update(member, "identification", "notify_email", "admin") 10.8 +param.update(member, "identification", "admin") 10.9 + 10.10 +local notify_email = param.get("notify_email") 10.11 +if notify_email == "" then 10.12 + notify_email = nil 10.13 +end 10.14 + 10.15 +member.notify_email = notify_email 10.16 + 10.17 +local notify_email_unconfirmed = param.get("notify_email_unconfirmed") 10.18 +if notify_email_unconfirmed == "" then 10.19 + notify_email_unconfirmed = nil 10.20 +end 10.21 + 10.22 +member.notify_email_unconfirmed = notify_email_unconfirmed 10.23 10.24 local locked = param.get("locked", atom.boolean) 10.25 if locked ~= nil then 10.26 @@ -40,10 +54,18 @@ 10.27 local privilege = Privilege:new() 10.28 privilege.member_id = member.id 10.29 privilege.unit_id = config.single_unit_id 10.30 + privilege.initiative_right = true 10.31 privilege.voting_right = true 10.32 privilege:save() 10.33 end 10.34 10.35 +if not id then 10.36 + local profile = MemberProfile:new() 10.37 + profile.member_id = member.id 10.38 + profile.profile = json.object() 10.39 + profile:save() 10.40 +end 10.41 + 10.42 local units = Unit:new_selector() 10.43 :add_field("privilege.member_id NOTNULL", "privilege_exists") 10.44 :add_field("privilege.voting_right", "voting_right")
11.1 --- a/app/main/admin/_action/policy_update.lua Thu Jun 23 03:30:57 2016 +0200 11.2 +++ b/app/main/admin/_action/policy_update.lua Sun Jul 15 14:07:29 2018 +0200 11.3 @@ -4,8 +4,8 @@ 11.4 policy, 11.5 "index", "name", "description", "active", 11.6 "min_admission_time", "max_admission_time", "discussion_time", "verification_time", "voting_time", 11.7 - "issue_quorum_num", "issue_quorum_den", 11.8 - "initiative_quorum_num", "initiative_quorum_den", 11.9 + "issue_quorum", "issue_quorum_num", "issue_quorum_den", 11.10 + "initiative_quorum", "initiative_quorum_num", "initiative_quorum_den", 11.11 "direct_majority_num", "direct_majority_den", "direct_majority_strict", "direct_majority_positive", "direct_majority_non_negative", 11.12 "indirect_majority_num", "indirect_majority_den", "indirect_majority_strict", "indirect_majority_positive", "indirect_majority_non_negative", 11.13 "no_reverse_beat_path", "no_multistage_majority", "polling"
12.1 --- a/app/main/admin/_filter/90_admin.lua Thu Jun 23 03:30:57 2016 +0200 12.2 +++ b/app/main/admin/_filter/90_admin.lua Sun Jul 15 14:07:29 2018 +0200 12.3 @@ -1,5 +1,5 @@ 12.4 if not app.session.member.admin then 12.5 - error('access denied') 12.6 + return execute.view { module = "index", view = "403" } 12.7 end 12.8 12.9 if config.admin_logger then
13.1 --- a/app/main/admin/area_show.lua Thu Jun 23 03:30:57 2016 +0200 13.2 +++ b/app/main/admin/area_show.lua Sun Jul 15 14:07:29 2018 +0200 13.3 @@ -63,6 +63,11 @@ 13.4 connecting_records = area.allowed_policies or {}, 13.5 foreign_reference = "id", 13.6 } 13.7 + ui.field.text{ label = _"Admission quorum standard", name = "quorum_standard", value = hint and 10 or nil } 13.8 + ui.field.text{ label = _"Admission quorum issues", name = "quorum_issues", value = hint and 10 or nil } 13.9 + ui.field.text{ label = _"Admission quorum time", name = "quorum_time", value = hint and "60 days" or nil } 13.10 + ui.field.text{ label = _"Admission quorum exponent", name = "quorum_exponent", value = hint and 0.5 or nil } 13.11 + ui.field.text{ label = _"Admission qourum factor", name = "quorum_factor", value = hint and 2 or nil } 13.12 slot.put("<br /><br />") 13.13 ui.field.boolean{ label = _"Active?", name = "active", value = hint and true or nil } 13.14 ui.submit{ text = _"update area" }
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 14.2 +++ b/app/main/admin/invite_list.lua Sun Jul 15 14:07:29 2018 +0200 14.3 @@ -0,0 +1,18 @@ 14.4 +local members = Member:new_selector():exec() 14.5 + 14.6 +ui.list{ 14.7 + records = members, 14.8 + columns = { 14.9 + { 14.10 + name = "id", 14.11 + }, 14.12 + { 14.13 + name = "invite_code", 14.14 + }, 14.15 + { 14.16 + content = function(member) 14.17 + ui.link{ content = _"Invite letter", module = "admin", view = "invite_pdf", id = member.id } 14.18 + end 14.19 + } 14.20 + } 14.21 +}
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 15.2 +++ b/app/main/admin/invite_pdf.lua Sun Jul 15 14:07:29 2018 +0200 15.3 @@ -0,0 +1,25 @@ 15.4 +local id = param.get_id() 15.5 + 15.6 +local member = Member:by_id(id) 15.7 + 15.8 + 15.9 +local luatex = require("luatex") 15.10 +luatex.temp_dir = WEBMCP_BASE_PATH .. "tmp" 15.11 + 15.12 +local tex = luatex.new_document() 15.13 + 15.14 +local template = config.invitation.template 15.15 + 15.16 +if type(template) == "function" then 15.17 + template = template(member) 15.18 +else 15.19 + template = template:gsub("#{invite_code}", member.invite_code) 15.20 + template = template:gsub("#{url}", request.get_absolute_baseurl()) 15.21 +end 15.22 + 15.23 +tex(template) 15.24 + 15.25 +local pdf = tex:get_pdf() 15.26 + 15.27 +slot.set_layout(nil, "application/pdf") 15.28 +slot.put_into("data", pdf)
16.1 --- a/app/main/admin/member_edit.lua Thu Jun 23 03:30:57 2016 +0200 16.2 +++ b/app/main/admin/member_edit.lua Sun Jul 15 14:07:29 2018 +0200 16.3 @@ -41,7 +41,8 @@ 16.4 16.5 ui.sectionRow( function() 16.6 ui.field.text{ label = _"Identification", name = "identification" } 16.7 - ui.field.text{ label = _"Notification email", name = "notify_email" } 16.8 + ui.field.text{ label = _"Notification email (confirmed)", name = "notify_email" } 16.9 + ui.field.text{ label = _"Notification email (unconfirmed)", name = "notify_email_unconfirmed" } 16.10 if member and member.activated then 16.11 ui.field.text{ label = _"Screen name", name = "name" } 16.12 end
17.1 --- a/app/main/admin/policy_show.lua Thu Jun 23 03:30:57 2016 +0200 17.2 +++ b/app/main/admin/policy_show.lua Sun Jul 15 14:07:29 2018 +0200 17.3 @@ -38,9 +38,11 @@ 17.4 ui.field.text{ label = _"Verification time", name = "verification_time", value = hint and "15 days" or nil } 17.5 ui.field.text{ label = _"Voting time", name = "voting_time", value = hint and "15 days" or nil } 17.6 17.7 - ui.field.text{ label = _"Issue quorum numerator", name = "issue_quorum_num", value = hint and "10" or nil } 17.8 - ui.field.text{ label = _"Issue quorum denominator", name = "issue_quorum_den", value = hint and "100" or nil } 17.9 + ui.field.text{ label = _"Issue quorum", name = "issue_quorum", value = hint and "1" or nil } 17.10 + ui.field.text{ label = _"Issue quorum numerator", name = "issue_quorum_num", value = hint and "1" or nil } 17.11 + ui.field.text{ label = _"Issue quorum denominator", name = "issue_quorum_den", value = hint and "10" or nil } 17.12 17.13 + ui.field.text{ label = _"Initiative quorum absolute", name = "initiative_quorum", value = hint and "1" or nil } 17.14 ui.field.text{ label = _"Initiative quorum numerator", name = "initiative_quorum_num", value = hint and "10" or nil } 17.15 ui.field.text{ label = _"Initiative quorum denominator", name = "initiative_quorum_den", value = hint and "100" or nil } 17.16
18.1 --- a/app/main/admin/unit_edit.lua Thu Jun 23 03:30:57 2016 +0200 18.2 +++ b/app/main/admin/unit_edit.lua Sun Jul 15 14:07:29 2018 +0200 18.3 @@ -1,5 +1,7 @@ 18.4 local id = param.get_id() 18.5 18.6 +local hint = not id 18.7 + 18.8 local unit = Unit:by_id(id) 18.9 18.10 ui.titleAdmin(_"Organizational unit") 18.11 @@ -40,7 +42,7 @@ 18.12 ui.field.text{ label = _"Name", name = "name" } 18.13 ui.field.text{ label = _"Description", name = "description", multiline = true } 18.14 ui.field.text{ label = _"External reference", name = "external_reference" } 18.15 - ui.field.boolean{ label = _"Active?", name = "active" } 18.16 + ui.field.boolean{ label = _"Active?", name = "active", value = hint and true or nil } 18.17 18.18 slot.put("<br />") 18.19 ui.submit{ text = _"update unit" }
19.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 19.2 +++ b/app/main/admin/verification.lua Sun Jul 15 14:07:29 2018 +0200 19.3 @@ -0,0 +1,134 @@ 19.4 +local verification = Verification:by_id(param.get_id()) 19.5 + 19.6 +local data = {} 19.7 + 19.8 +for i, field in ipairs(config.verification.fields) do 19.9 + table.insert(data, { 19.10 + label = field.label, 19.11 + value = verification.request_data[field.name] 19.12 + }) 19.13 +end 19.14 + 19.15 +table.insert(data, { 19.16 + label = _"IP address", 19.17 + value = verification.request_origin.ip 19.18 +}) 19.19 + 19.20 +table.insert(data, { 19.21 + label = _"Hostname", 19.22 + value = verification.request_origin.hostname 19.23 +}) 19.24 + 19.25 +if verification.verified then 19.26 + table.insert(data, { 19.27 + label = _"Requested at", 19.28 + value = format.timestamp(verification.requested) 19.29 + }) 19.30 +end 19.31 + 19.32 +if verification.requesting_member_id then 19.33 + table.insert(data, { 19.34 + label = _"Requested by account", 19.35 + value = verification.requesting_member_id 19.36 + }) 19.37 +end 19.38 + 19.39 +if verification.verified then 19.40 + table.insert(data, { 19.41 + label = _"Verified at", 19.42 + value = format.timestamp(verification.verified) 19.43 + }) 19.44 +end 19.45 + 19.46 +if verification.denied then 19.47 + table.insert(data, { 19.48 + label = _"Denied at", 19.49 + value = format.timestamp(verification.denied) 19.50 + }) 19.51 +end 19.52 + 19.53 +if verification.verifying_member_id then 19.54 + table.insert(data, { 19.55 + label = _"Verified by account", 19.56 + value = verification.verifying_member_id 19.57 + }) 19.58 +end 19.59 + 19.60 +if verification.comment then 19.61 + table.insert(data, { 19.62 + label = _"Comment", 19.63 + value = verification.comment 19.64 + }) 19.65 +end 19.66 + 19.67 +if verification.verified_member_id then 19.68 + table.insert(data, { 19.69 + label = _"Used by account", 19.70 + value = verification.veried_member_id 19.71 + }) 19.72 +end 19.73 + 19.74 +ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 19.75 + ui.list{ 19.76 + attr = { class = "mdl-data-table mdl-js-data-table mdl-shadow--2dp" }, 19.77 + records = data, 19.78 + columns = { 19.79 + { 19.80 + label_attr = { class = "mdl-data-table__cell--non-numeric" }, 19.81 + field_attr = { class = "mdl-data-table__cell--non-numeric" }, 19.82 + label = _"Field", 19.83 + content = function(record) 19.84 + ui.tag{ content = record.label } 19.85 + end 19.86 + }, 19.87 + { 19.88 + label_attr = { class = "mdl-data-table__cell--non-numeric" }, 19.89 + field_attr = { class = "mdl-data-table__cell--non-numeric" }, 19.90 + label = _"Value", 19.91 + content = function(record) 19.92 + ui.tag{ content = record.value } 19.93 + end 19.94 + }, 19.95 + } 19.96 + } 19.97 +end } 19.98 + 19.99 +if not verification.verification_data and not verification.denied then 19.100 + ui.form{ 19.101 + module = "admin", action = "verification_update", id = verification.id, 19.102 + record = verification, 19.103 + content = function() 19.104 + ui.field.text{ 19.105 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 19.106 + attr = { id = "lf-verification_data", class = "mdl-textfield__input" }, 19.107 + label = _"Verification data", 19.108 + name = "verification_data" 19.109 + } 19.110 + slot.put("<br /><br />") 19.111 + ui.tag{ 19.112 + tag = "input", 19.113 + attr = { 19.114 + type = "submit", 19.115 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 19.116 + value = _"Verify account" 19.117 + } 19.118 + } 19.119 + slot.put(" ") 19.120 + ui.tag{ 19.121 + tag = "input", 19.122 + attr = { 19.123 + type = "submit", 19.124 + name = "deny", 19.125 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--accent", 19.126 + value = _"Deny request" 19.127 + } 19.128 + } 19.129 + slot.put(" ") 19.130 + ui.link{ 19.131 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 19.132 + content = _"Cancel", 19.133 + module = "admin", view = "verification_list" 19.134 + } 19.135 + end 19.136 + } 19.137 +end
20.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 20.2 +++ b/app/main/admin/verification_list.lua Sun Jul 15 14:07:29 2018 +0200 20.3 @@ -0,0 +1,70 @@ 20.4 +ui.heading{ level = 1, content = _"Verification requests" } 20.5 + 20.6 +if not config.verification or not config.verification.fields then 20.7 + return 20.8 +end 20.9 + 20.10 + 20.11 +local columns = {} 20.12 + 20.13 +for i, field in ipairs(config.verification.fields) do 20.14 + table.insert(columns, { 20.15 + label_attr = { class = "mdl-data-table__cell--non-numeric" }, 20.16 + field_attr = { class = "mdl-data-table__cell--non-numeric" }, 20.17 + label = field.label, 20.18 + content = function(record) 20.19 + ui.tag{ content = record.request_data[field.name] } 20.20 + end 20.21 + }) 20.22 +end 20.23 + 20.24 +table.insert(columns, { 20.25 + label = _"verified", 20.26 + name = "verified" 20.27 +}) 20.28 + 20.29 +table.insert(columns, { 20.30 + label = _"denied", 20.31 + name = "denied" 20.32 +}) 20.33 + 20.34 +table.insert(columns, { 20.35 + content = function(record) 20.36 + ui.link{ content = _"show", module = "admin", view = "verification", id = record.id } 20.37 + end 20.38 +}) 20.39 + 20.40 +local new_verifications = Verification:new_selector():add_where("verified ISNULL and denied ISNULL"):exec() 20.41 +local verified_verifications = Verification:new_selector():add_where("verified NOTNULL"):exec() 20.42 +local denied_verifications = Verification:new_selector():add_where("denied NOTNULL"):exec() 20.43 + 20.44 +ui.container{ attr = { class = "mdl-tabs mdl-js-tabs mdl-js-ripple-effect" }, content = function() 20.45 + ui.container{ attr = { class = "mdl-tabs__tab-bar" }, content = function() 20.46 + ui.link{ content = _"new requests", external = "#new_requests", attr = { class = "mdl-tabs__tab is-active" } } 20.47 + ui.link{ content = _"verified", external = "#verified", attr = { class = "mdl-tabs__tab" } } 20.48 + ui.link{ content = _"denied", external = "#denied", attr = { class = "mdl-tabs__tab" } } 20.49 + end } 20.50 + slot.put("<br />") 20.51 + ui.container{ attr = { class = "mdl-tabs__panel is-active", id = "new_requests" }, content = function() 20.52 + ui.list{ 20.53 + records = new_verifications, 20.54 + attr = { class = "mdl-data-table mdl-js-data-table mdl-shadow--2dp" }, 20.55 + columns = columns 20.56 + } 20.57 + end } 20.58 + ui.container{ attr = { class = "mdl-tabs__panel", id = "verified" }, content = function() 20.59 + ui.list{ 20.60 + records = verified_verifications, 20.61 + attr = { class = "mdl-data-table mdl-js-data-table mdl-shadow--2dp" }, 20.62 + columns = columns 20.63 + } 20.64 + end } 20.65 + ui.container{ attr = { class = "mdl-tabs__panel", id = "denied" }, content = function() 20.66 + ui.list{ 20.67 + records = denied_verifications, 20.68 + attr = { class = "mdl-data-table mdl-js-data-table mdl-shadow--2dp" }, 20.69 + columns = columns 20.70 + } 20.71 + end } 20.72 +end } 20.73 +
21.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 21.2 +++ b/app/main/agent/_action/accept.lua Sun Jul 15 14:07:29 2018 +0200 21.3 @@ -0,0 +1,26 @@ 21.4 +local controlled_id = param.get("controlled_id") 21.5 + 21.6 +local agent = Agent:by_pk(controlled_id, app.session.member_id) 21.7 + 21.8 +if not agent then 21.9 + print("A") 21.10 + return false 21.11 +end 21.12 + 21.13 +if agent.accepted ~= nil then 21.14 + print("B") 21.15 + return false 21.16 +end 21.17 + 21.18 +if param.get("rejected") then 21.19 + print("C") 21.20 + agent.accepted = false 21.21 +elseif param.get("accepted") then 21.22 + print("D") 21.23 + agent.accepted = true 21.24 +else 21.25 + print("E") 21.26 + return false 21.27 +end 21.28 +print("F") 21.29 +agent:save()
22.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 22.2 +++ b/app/main/agent/show.lua Sun Jul 15 14:07:29 2018 +0200 22.3 @@ -0,0 +1,100 @@ 22.4 +local controlled_id = param.get("controlled_id") 22.5 + 22.6 + 22.7 +ui.titleMember(_"Account access") 22.8 + 22.9 +ui.grid{ content = function() 22.10 + 22.11 + ui.cell_main{ content = function() 22.12 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 22.13 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 22.14 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Account access" } 22.15 + end } 22.16 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 22.17 + 22.18 + local agent = Agent:new_selector() 22.19 + :add_where{ "controller_id = ?", app.session.member_id } 22.20 + :add_where{ "controlled_id = ?", controlled_id } 22.21 + :optional_object_mode() 22.22 + :exec() 22.23 + 22.24 + if agent then 22.25 + 22.26 + if agent.accepted == nil then 22.27 + ui.container{ content = _"You have been granted access privileges for the following account:" } 22.28 + elseif agent.accepted == true then 22.29 + ui.container{ content = _"You have accepted access privileges for the following account:" } 22.30 + elseif agent.accepted == false then 22.31 + ui.container{ content = _"You have rejected access privileges for the following account:" } 22.32 + end 22.33 + 22.34 + slot.put("<br>") 22.35 + ui.link{ 22.36 + content = agent.controllee.display_name, 22.37 + module = "member", view = "show", id = agent.controlled_id 22.38 + } 22.39 + slot.put("<br><br>") 22.40 + 22.41 + ui.form{ 22.42 + attr = { class = "wide" }, 22.43 + module = "agent", 22.44 + action = "accept", 22.45 + params = { controlled_id = controlled_id }, 22.46 + routing = { 22.47 + ok = { 22.48 + mode = "redirect", 22.49 + module = "agent", 22.50 + view = "show", 22.51 + params = { controlled_id = controlled_id }, 22.52 + } 22.53 + }, 22.54 + content = function() 22.55 + 22.56 + if agent.accepted == nil then 22.57 + ui.tag{ 22.58 + tag = "input", 22.59 + attr = { 22.60 + type = "submit", 22.61 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 22.62 + value = _"Accept access privilege", 22.63 + name = "accepted" 22.64 + }, 22.65 + content = "" 22.66 + } 22.67 + slot.put(" ") 22.68 + ui.tag{ 22.69 + tag = "input", 22.70 + attr = { 22.71 + type = "submit", 22.72 + class = "mdl-button mdl-js-button mdl-button--raised", 22.73 + value = _"Reject access privilege", 22.74 + name = "rejected" 22.75 + }, 22.76 + content = "" 22.77 + } 22.78 + end 22.79 + slot.put(" ") 22.80 + ui.link { 22.81 + attr = { class = "mdl-button mdl-js-button" }, 22.82 + module = "index", view = "index", 22.83 + content = _"Cancel" 22.84 + } 22.85 + end 22.86 + } 22.87 + 22.88 + end 22.89 + 22.90 + end } 22.91 + end } 22.92 + end } 22.93 + 22.94 + ui.cell_sidebar{ content = function() 22.95 + execute.view { 22.96 + module = "member", view = "_sidebar_whatcanido", params = { 22.97 + member = app.session.member 22.98 + } 22.99 + } 22.100 + end } 22.101 + 22.102 +end } 22.103 +
23.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 23.2 +++ b/app/main/api/_filter/30_auth.lua Sun Jul 15 14:07:29 2018 +0200 23.3 @@ -0,0 +1,48 @@ 23.4 +local public_access_scopes = { 23.5 + anonymous = "read_contents", 23.6 + authors_pseudonymous = "read_contents read_authors", 23.7 + all_pseudonymous = "read_contents read_authors read_ratings", 23.8 + everything = "read_contents read_authors read_ratings read_identities read_profiles" 23.9 +} 23.10 + 23.11 +local access_token, access_token_err = util.get_access_token() 23.12 + 23.13 +if access_token_err then 23.14 + if access_token_err == "header_and_param" then 23.15 + return util.api_error(400, "Unauthorized", "invalid_request", "Access token passed both via header and param") 23.16 + end 23.17 + return util.api_error(500, "Internal server error", "internal_error", "Internal server error") 23.18 +end 23.19 + 23.20 +local scope 23.21 + 23.22 +if access_token then 23.23 + local token = Token:by_token_type_and_token("access", access_token) 23.24 + if token then 23.25 + app.access_token = token 23.26 + scope = token.scope 23.27 + else 23.28 + return util.api_error(401, "Unauthorized", "invalid_token", "The access token is invalid or expired") 23.29 + end 23.30 +end 23.31 + 23.32 +if not scope then 23.33 + scope = public_access_scopes[config.public_access] 23.34 +end 23.35 + 23.36 +if not scope then 23.37 + return util.api_error(403, "Forbidden", "insufficient_scope", "Public access is not allowed at this instance.") 23.38 +end 23.39 + 23.40 +app.scopes = {} 23.41 + 23.42 +for scope in string.gmatch(scope, "[^ ]+") do 23.43 + local match = string.match(scope, "(.+)_detached") 23.44 + app.scopes[match or scope] = true 23.45 +end 23.46 + 23.47 +if not next(app.scopes) then 23.48 + return util.api_error(403, "Forbidden", "insufficient_scope", "No valid scope found") 23.49 +end 23.50 + 23.51 +execute.inner()
24.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 24.2 +++ b/app/main/api/_issue.lua Sun Jul 15 14:07:29 2018 +0200 24.3 @@ -0,0 +1,22 @@ 24.4 +local issues = param.get("issues", "table") 24.5 + 24.6 + 24.7 +local fields = { "id", "area_id", "policy_id", "admin_notice", "external_reference", "state", "phase_finished", "created", "accepted", "half_frozen", "fully_frozen", "closed", "cleaned", "min_admission_time", "max_admission_time", "discussion_time", "verification_time", "voting_time", "latest_snapshot_id", "admission_snapshot_id", "half_freeze_snapshot_id", "full_freeze_snapshot_id", "population", "voter_count", "status_quo_schulze_rank" } 24.8 + 24.9 +local r = json.array() 24.10 + 24.11 +for i, issue in ipairs(issues) do 24.12 + local ir = json.object() 24.13 + for j, field in ipairs(fields) do 24.14 + local value = issue[field] 24.15 + if value == nil then 24.16 + value = json.null 24.17 + else 24.18 + value = tostring(value) 24.19 + end 24.20 + ir[field] = value 24.21 + end 24.22 + r[#r+1] = ir 24.23 +end 24.24 + 24.25 +return r
25.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 25.2 +++ b/app/main/api/_member.lua Sun Jul 15 14:07:29 2018 +0200 25.3 @@ -0,0 +1,45 @@ 25.4 +local members = param.get("members", "table") 25.5 + 25.6 +local include_profile = param.get("include_profile", atom.boolean) 25.7 + 25.8 +if include_profile and not app.scopes.read_profiles then 25.9 + return util.api_error(403, "Forbidden", "insufficient_scope", "Scope read_profiles required") 25.10 +end 25.11 + 25.12 +local fields = {} 25.13 + 25.14 +if app.scopes.read_authors or app.scopes.read_identities then 25.15 + fields = { "id", "created", "last_activity", "admin", "name", "location" } 25.16 +end 25.17 + 25.18 +if app.scopes.read_identities then 25.19 + fields[#fields+1] = "identification" 25.20 +end 25.21 + 25.22 +local r = json.array() 25.23 + 25.24 +if app.scopes.read_identities then 25.25 + 25.26 + if include_profile then 25.27 + members:load("profile") 25.28 + end 25.29 + 25.30 + for i, member in ipairs(members) do 25.31 + local m = json.object() 25.32 + for j, field in ipairs(fields) do 25.33 + local value = member[field] 25.34 + if value == nil then 25.35 + value = json.null 25.36 + else 25.37 + value = tostring(value) 25.38 + end 25.39 + m[field] = value 25.40 + end 25.41 + if include_profile then 25.42 + m.profile = execute.chunk{ module = "api", chunk = "_profile", params = { profile = member.profile } } 25.43 + end 25.44 + r[#r+1] = m 25.45 + end 25.46 +end 25.47 + 25.48 +return r
26.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 26.2 +++ b/app/main/api/_profile.lua Sun Jul 15 14:07:29 2018 +0200 26.3 @@ -0,0 +1,22 @@ 26.4 +local profile = param.get("profile", "table") 26.5 + 26.6 +local r = json.object() 26.7 + 26.8 +for i, field in ipairs(config.member_profile_fields) do 26.9 + if profile.profile[field.id] then 26.10 + r[field.id] = profile.profile[field.id] or json.null 26.11 + end 26.12 +end 26.13 +--[[ 26.14 +if profile.statement then 26.15 + if request.get_param{ name = "statement_format" } == "html" then 26.16 + r.statement = profile:get_content("html") 26.17 + r.statement_format = "html" 26.18 + else 26.19 + r.statement = profile.statement 26.20 + r.statement_format = profile.formatting_engine 26.21 + end 26.22 +end 26.23 +--]] 26.24 + 26.25 +return r
27.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 27.2 +++ b/app/main/api/_settings.lua Sun Jul 15 14:07:29 2018 +0200 27.3 @@ -0,0 +1,11 @@ 27.4 +local settings = param.get("settings", "table") 27.5 + 27.6 +local r = json.object() 27.7 + 27.8 +for i, field in ipairs(config.member_settings_fields) do 27.9 + if settings.settings[field.id] ~= nil then 27.10 + r[field.id] = settings.settings[field.id] 27.11 + end 27.12 +end 27.13 + 27.14 +return r
28.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 28.2 +++ b/app/main/api/application.lua Sun Jul 15 14:07:29 2018 +0200 28.3 @@ -0,0 +1,40 @@ 28.4 +slot.set_layout(nil, "application/json") 28.5 + 28.6 +local r = json.array() 28.7 + 28.8 +local system_applications = SystemApplication:get_all() 28.9 + 28.10 +r[#r+1] = json.object{ 28.11 + type = "system", 28.12 + name = "LiquidFeedback", 28.13 + base_url = request.get_absolute_baseurl(), 28.14 + manifest_url = request.get_absolute_baseurl() .. "api/1/info", 28.15 + cert_common_name = config.oauth2.cert_common_name 28.16 +} 28.17 + 28.18 +for i, system_application in ipairs(system_applications) do 28.19 + r[#r+1] = json.object{ 28.20 + type = "system", 28.21 + name = system_application.name, 28.22 + base_url = system_application.base_url, 28.23 + manifest_url = system_application.manifest_url, 28.24 + cert_common_name = system_application.cert_common_name 28.25 + } 28.26 +end 28.27 + 28.28 +if app.access_token then 28.29 + 28.30 + local member_applications = MemberApplication:by_member_id_with_domain(app.access_token.member_id) 28.31 + 28.32 + for i, member_application in ipairs(member_applications) do 28.33 + r[#r+1] = json.object{ 28.34 + type = "dynamic", 28.35 + name = "https://" .. member_application.domain .. "/", 28.36 + base_url = "https://" .. member_application.domain .. "/", 28.37 + manifest_url = "https://" .. member_application.domain .. "/" .. config.oauth2.manifest_magic 28.38 + } 28.39 + end 28.40 + 28.41 +end 28.42 + 28.43 +slot.put_into("data", json.export(json.object{ result = r }))
29.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 29.2 +++ b/app/main/api/event.lua Sun Jul 15 14:07:29 2018 +0200 29.3 @@ -0,0 +1,132 @@ 29.4 +slot.set_layout(nil, "application/json") 29.5 + 29.6 +local r = json.object{ 29.7 + result = json.array{} 29.8 +} 29.9 + 29.10 +local selector = Event:new_selector() 29.11 + 29.12 +local member_id = param.get("member_id", atom.integer) 29.13 +local other_member_id = param.get("member_id", atom.integer) 29.14 +local scope = param.get("scope") 29.15 +local issue_id = param.get("issue_id", atom.integer) 29.16 +local state = param.get("state") 29.17 +local initiative_id = param.get("initiative_id", atom.integer) 29.18 + 29.19 +local include_members = param.get("include_members", atom.boolean) 29.20 +local include_other_members = param.get("include_other_members", atom.boolean) 29.21 +local include_profile = param.get("include_profile", atom.boolean) 29.22 +local include_issues = param.get("include_issues", atom.boolean) 29.23 +local include_initiatives = param.get("include_initiatives", atom.boolean) 29.24 +local include_drafts = param.get("include_drafts", atom.boolean) 29.25 +local include_suggestions = param.get("include_suggestions", atom.boolean) 29.26 + 29.27 +if member_id then 29.28 + selector:add_where{ "member_id = ?", member_id } 29.29 +end 29.30 + 29.31 +if other_member_id then 29.32 + selector:add_where{ "other_member_id = ?", other_member_id } 29.33 +end 29.34 + 29.35 +if scope then 29.36 + selector:add_where{ "scope = ?", scope } 29.37 +end 29.38 + 29.39 +if issue_id then 29.40 + selector:add_where{ "issue_id = ?", issue_id } 29.41 +end 29.42 + 29.43 +if scope then 29.44 + selector:add_where{ "scope = ?", scope } 29.45 +end 29.46 + 29.47 +if initiative_id then 29.48 + selector:add_where{ "initiative_id = ?", initiative_id } 29.49 +end 29.50 + 29.51 +selector:add_order_by("id DESC") 29.52 + 29.53 +local events = selector:exec() 29.54 + 29.55 +local member_ids = {} 29.56 +local issue_ids = {} 29.57 +local initiative_ids = {} 29.58 +local draft_ids = {} 29.59 +local suggestion_ids = {} 29.60 + 29.61 +for i, event in ipairs(events) do 29.62 + local e = json.object() 29.63 + e.id = event.id 29.64 + e.occurrence = format.timestamp(event.occurrence) 29.65 + e.event = event.event 29.66 + e.member_id = event.member_id 29.67 + e.other_member_id = event.other_member_id 29.68 + e.scope = event.scope 29.69 + e.issue_id = event.issue_id 29.70 + e.state = event.state 29.71 + e.initiative_id = event.initiative_id 29.72 + e.draft_id = event.draft_id 29.73 + e.suggestion_id = event.suggestion_id 29.74 + e.value = event.value 29.75 + if include_members and e.member_id then 29.76 + member_ids[e.member_id] = true 29.77 + end 29.78 + if include_other_members and e.other_member_id then 29.79 + member_ids[e.member_id] = true 29.80 + end 29.81 + if include_issues and e.issue_id then 29.82 + issue_ids[e.issue_id] = true 29.83 + end 29.84 + if include_initiatives and e.initiative_id then 29.85 + initiative_ids[e.initiative_id] = true 29.86 + end 29.87 + if include_drafts and e.draft_id then 29.88 + draft_ids[e.draft_id] = true 29.89 + end 29.90 + if include_suggestions and e.suggestion_id then 29.91 + suggestion_ids[e.suggestion_id] = true 29.92 + end 29.93 + r.result[#r.result+1] = e 29.94 +end 29.95 + 29.96 +function util.keys_to_array(tbl) 29.97 + local r = {} 29.98 + for k, v in pairs(tbl) do 29.99 + r[#r+1] = k 29.100 + end 29.101 + return r 29.102 +end 29.103 + 29.104 +function util.array_to_json_object(tbl, key) 29.105 + local r = json.object() 29.106 + for i, v in ipairs(tbl) do 29.107 + r[v[key]] = v 29.108 + end 29.109 + return r 29.110 +end 29.111 + 29.112 +if next(member_ids) then 29.113 + local members = Member:by_ids(util.keys_to_array(member_ids)) 29.114 + r.members = util.array_to_json_object( 29.115 + execute.chunk{ module = "api", chunk = "_member", params = { members = members, include_profile = include_profile } }, 29.116 + "id" 29.117 + ) 29.118 + if r.members == false then 29.119 + return 29.120 + end 29.121 +end 29.122 + 29.123 +if next(issue_ids) then 29.124 + local issues = Issue:by_ids(util.keys_to_array(issue_ids)) 29.125 + r.issues = util.array_to_json_object( 29.126 + execute.chunk{ module = "api", chunk = "_issue", params = { issues = issues } }, 29.127 + "id" 29.128 + ) 29.129 + if r.issues == false then 29.130 + return 29.131 + end 29.132 +end 29.133 + 29.134 + 29.135 +slot.put_into("data", json.export(r))
30.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 30.2 +++ b/app/main/api/info.lua Sun Jul 15 14:07:29 2018 +0200 30.3 @@ -0,0 +1,53 @@ 30.4 +slot.set_layout(nil, "application/json") 30.5 + 30.6 +local scope_string 30.7 + 30.8 +local scopes_list = {} 30.9 +for scope in pairs(app.scopes) do 30.10 + scopes_list[#scopes_list+1] = scope 30.11 +end 30.12 +local scopes_string = table.concat(scopes_list, " ") 30.13 + 30.14 +local result = {} 30.15 + 30.16 +local r = json.object{ 30.17 + service = "LiquidFeedback", 30.18 + core_version = db:query("SELECT * from liquid_feedback_version;")[1].string, 30.19 + api_version = config.app_version, 30.20 + client_tls_dn = request.get_header("X-SSL-DN"), 30.21 + scope = scopes_string 30.22 +} 30.23 + 30.24 +if app.scopes.identification or app.scopes.authentication then 30.25 + r.member_id = app.access_token.member_id 30.26 + if app.access_token.member.role then 30.27 + r.member_is_role = true 30.28 + end 30.29 + if app.access_token.session then 30.30 + r.real_member_id = app.access_token.session.real_member_id 30.31 + end 30.32 + if param.get("include_member", atom.boolean) then 30.33 + local member = app.access_token.member 30.34 + result.member = json.object{ 30.35 + id = member.id, 30.36 + name = member.name 30.37 + } 30.38 + if app.access_token.session and app.access_token.session.real_member then 30.39 + result.real_member = json.object{ 30.40 + id = app.access_token.session.real_member.id, 30.41 + name = app.access_token.session.real_member.name, 30.42 + } 30.43 + end 30.44 + if app.scopes.identification then 30.45 + result.member.identification = member.identification 30.46 + if app.access_token.session and app.access_token.session.real_member then 30.47 + result.real_member.identification = app.access_token.session.real_member.identification 30.48 + end 30.49 + end 30.50 + end 30.51 +end 30.52 + 30.53 +result.result = r 30.54 + 30.55 +slot.put_into("data", json.export(result)) 30.56 +slot.put_into("data", "\n")
31.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 31.2 +++ b/app/main/api/instance.lua Sun Jul 15 14:07:29 2018 +0200 31.3 @@ -0,0 +1,33 @@ 31.4 +local navigation 31.5 + 31.6 +if param.get("include_navigation") then 31.7 + 31.8 + local items = config.meta_navigation_items_func( 31.9 + app.access_token and app.access_token.member or nil, 31.10 + param.get("client_id"), 31.11 + param.get("login_url") 31.12 + ) 31.13 + 31.14 + navigation = json.array() 31.15 + for i, item in ipairs(items) do 31.16 + navigation[#navigation+1] = json.object{ 31.17 + name = item.name, 31.18 + description = item.description, 31.19 + url = item.url, 31.20 + active = item.active 31.21 + } 31.22 + end 31.23 + 31.24 +end 31.25 + 31.26 +local result = json.object{ 31.27 + name = config.instance_name, 31.28 + slogan = config.meta_navigation_slogan, 31.29 + home_url = config.meta_navigation_home_url, 31.30 + logo_url = config.meta_navigation_logo_url, 31.31 + logo_alt_text = config.meta_navigation_logo_alt_text, 31.32 + navigation = navigation 31.33 +} 31.34 + 31.35 +slot.set_layout(nil, "application/json") 31.36 +slot.put_into("data", json.export(json.object{ result = result }))
32.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 32.2 +++ b/app/main/api/member.lua Sun Jul 15 14:07:29 2018 +0200 32.3 @@ -0,0 +1,20 @@ 32.4 +slot.set_layout(nil, "application/json") 32.5 + 32.6 +local r = json.object{ 32.7 + result = json.array() 32.8 +} 32.9 + 32.10 +local selector = Member:new_selector() 32.11 + :add_where("activated NOTNULL") 32.12 + :add_order_by("id") 32.13 + 32.14 +if param.get("id") then 32.15 + selector:add_where{ "id = ?", param.get("id") } 32.16 +end 32.17 + 32.18 +local members = selector:exec() 32.19 +local r.result = execute.chunk{ module = "api", chunk = "_member", params = { members = members } } 32.20 + 32.21 + 32.22 +slot.put_into("data", json.export(r)) 32.23 +slot.put_into("data", "\n")
33.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 33.2 +++ b/app/main/api/navigation.lua Sun Jul 15 14:07:29 2018 +0200 33.3 @@ -0,0 +1,34 @@ 33.4 +local access_token = param.get("access_token") 33.5 +local client_id = param.get("client_id") 33.6 +local login_url = param.get("login_url") 33.7 +local format = param.get("format") 33.8 + 33.9 +if format ~= "html" and format ~= "raw_html" then 33.10 + format = "json" 33.11 +end 33.12 + 33.13 +local items = config.meta_navigation_items_func(app.access_token and app.access_token.member or nil, client_id, login_url) 33.14 + 33.15 +if format == "json" then 33.16 + slot.set_layout(nil, "application/json") 33.17 + local r = json.array() 33.18 + for i, item in ipairs(items) do 33.19 + r[#r+1] = json.object{ 33.20 + name = item.name, 33.21 + description = item.description, 33.22 + url = item.url, 33.23 + active = item.active 33.24 + } 33.25 + end 33.26 + slot.put_into("data", json.export(json.object{ result = r })) 33.27 +elseif format == "html" then 33.28 + slot.set_layout(nil, "application/json") 33.29 + local html = config.meta_navigation_style_func(items) .. config.meta_navigation_html_func(items) .. config.meta_navigation_script_func(items) 33.30 + slot.put_into("data", json.export(json.object{ result = html })) 33.31 +elseif format == "raw_html" then 33.32 + slot.set_layout(nil, "text/html") 33.33 + local html = config.meta_navigation_style_func(items) .. config.meta_navigation_html_func(items) .. config.meta_navigation_script_func(items) 33.34 + slot.put_into("data", html) 33.35 +end 33.36 + 33.37 +
34.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 34.2 +++ b/app/main/api/notify_email.lua Sun Jul 15 14:07:29 2018 +0200 34.3 @@ -0,0 +1,16 @@ 34.4 +slot.set_layout(nil, "application/json") 34.5 + 34.6 +local r = json.object{} 34.7 + 34.8 +if not app.scopes.notify_email then 34.9 + return util.api_error(403, "Forbidden", "insufficient_scope", "Scope notify_email required") 34.10 +end 34.11 + 34.12 +if app.access_token.member.notify_email ~= "" then 34.13 + r.notify_email = app.access_token.member.notify_email 34.14 +else 34.15 + r.notify_email = json.null 34.16 +end 34.17 + 34.18 +slot.put_into("data", json.export(json.object{ result = r })) 34.19 +slot.put_into("data", "\n")
35.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 35.2 +++ b/app/main/api/profile.lua Sun Jul 15 14:07:29 2018 +0200 35.3 @@ -0,0 +1,53 @@ 35.4 +slot.set_layout(nil, "application/json") 35.5 + 35.6 +local r = json.object{} 35.7 + 35.8 +if request.is_post() then 35.9 + if not app.scopes.update_profile then 35.10 + return util.api_error(403, "Forbidden", "insufficient_scope", "Scope update_profile required") 35.11 + end 35.12 + local profile = app.access_token.member.profile 35.13 + local fields = json.import(param.get("update")) 35.14 + if not fields then 35.15 + return util.api_error(400, "Bad Request", "profile_data_expected", "JSON object with updated profile data expected") 35.16 + end 35.17 + for i, field in ipairs(config.member_profile_fields) do 35.18 + if json.type(fields, field.id) ~= "nil" then 35.19 + local value = fields[field.id] 35.20 + if value ~= nil and (field.type == "string" or field.type == "text") and json.type(value) ~= "string" then 35.21 + return util.api_error(400, "Bad Request", "string_expected", "JSON encoded string value expected") 35.22 + end 35.23 + profile.profile[field.id] = value 35.24 + end 35.25 + end 35.26 + profile:save() 35.27 + r.status = 'ok' 35.28 + slot.put_into("data", json.export(r)) 35.29 + slot.put_into("data", "\n") 35.30 +else 35.31 + local member_id = tonumber(param.get("member_id")) 35.32 + local profile 35.33 + if member_id then 35.34 + if not app.scopes.read_profiles then 35.35 + return util.api_error(403, "Forbidden", "insufficient_scope", "Scope profile required") 35.36 + end 35.37 + local member = Member:by_id(member_id) 35.38 + if not member then 35.39 + return util.api_error(400, "Bad Request", "member_not_found", "No member with requested member_id") 35.40 + end 35.41 + profile = member.profile 35.42 + elseif app.access_token then 35.43 + if not app.scopes.profile and not app.scopes.read_profiles then 35.44 + return util.api_error(403, "Forbidden", "insufficient_scope", "Scope profile required") 35.45 + end 35.46 + profile = app.access_token.member.profile 35.47 + else 35.48 + return util.api_error(400, "Bad Request", "no_member_id", "No member_id requested") 35.49 + end 35.50 + if profile then 35.51 + r = execute.chunk{ module = "api", chunk = "_profile", params = { profile = profile } } 35.52 + end 35.53 + slot.put_into("data", json.export(json.object{ result = r })) 35.54 + slot.put_into("data", "\n") 35.55 +end 35.56 +
36.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 36.2 +++ b/app/main/api/profile_info.lua Sun Jul 15 14:07:29 2018 +0200 36.3 @@ -0,0 +1,16 @@ 36.4 +slot.set_layout(nil, "application/json") 36.5 + 36.6 +local r = json.object() 36.7 + 36.8 +r.result = json.array() 36.9 +for i, field in ipairs(config.member_profile_fields) do 36.10 + table.insert(r.result, json.object{ 36.11 + id = field.id, 36.12 + name = field.name, 36.13 + type = field.type 36.14 + }) 36.15 +end 36.16 + 36.17 +slot.put_into("data", json.export(r)) 36.18 +slot.put_into("data", "\n") 36.19 +
37.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 37.2 +++ b/app/main/api/settings.lua Sun Jul 15 14:07:29 2018 +0200 37.3 @@ -0,0 +1,50 @@ 37.4 +slot.set_layout(nil, "application/json") 37.5 + 37.6 +if not app.access_token then 37.7 + return util.api_error(400, "Forbidden", "insufficient_scope", "Scope 'settings' required") 37.8 +end 37.9 + 37.10 +local r = json.object{} 37.11 + 37.12 +if request.is_post() then 37.13 + if not app.scopes.update_settings then 37.14 + return util.api_error(403, "Forbidden", "insufficient_scope", "Scope update_settings required") 37.15 + end 37.16 + local settings = app.access_token.member.settings 37.17 + if not settings then 37.18 + settings = MemberSettings:new() 37.19 + settings.member_id = app.access_token.member_id 37.20 + settings.settings = json.object() 37.21 + end 37.22 + local fields = json.import(param.get("update")) 37.23 + if not fields then 37.24 + return util.api_error(400, "Bad Request", "settings_data_expected", "JSON object with updated settings data expected") 37.25 + end 37.26 + for i, field in ipairs(config.member_settings_fields) do 37.27 + if json.type(fields, field.id) ~= "nil" then 37.28 + local value = fields[field.id] 37.29 + if value ~= nil then 37.30 + if (field.type == "string" or field.type == "text") and json.type(value) ~= "string" then 37.31 + return util.api_error(400, "Bad Request", "string_expected", "JSON encoded string value expected") 37.32 + end 37.33 + if (field.type == "boolean") and json.type(value) ~= "boolean" then 37.34 + return util.api_error(400, "Bad Request", "boolean_expected", "JSON encoded boolean value expected") 37.35 + end 37.36 + end 37.37 + settings.settings[field.id] = value 37.38 + end 37.39 + end 37.40 + settings:save() 37.41 + r.status = 'ok' 37.42 + slot.put_into("data", json.export(r)) 37.43 + slot.put_into("data", "\n") 37.44 +else 37.45 + if not app.scopes.settings then 37.46 + return util.api_error(403, "Forbidden", "insufficient_scope", "Scope 'settings' required") 37.47 + end 37.48 + local settings = app.access_token.member.settings or json.object() 37.49 + r = execute.chunk{ module = "api", chunk = "_settings", params = { settings = settings } } 37.50 + slot.put_into("data", json.export(json.object{ result = r })) 37.51 + slot.put_into("data", "\n") 37.52 +end 37.53 +
38.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 38.2 +++ b/app/main/api/settings_info.lua Sun Jul 15 14:07:29 2018 +0200 38.3 @@ -0,0 +1,16 @@ 38.4 +slot.set_layout(nil, "application/json") 38.5 + 38.6 +local r = json.object() 38.7 + 38.8 +r.result = json.array() 38.9 +for i, field in ipairs(config.member_settings_fields) do 38.10 + table.insert(r.result, json.object{ 38.11 + id = field.id, 38.12 + name = field.name, 38.13 + type = field.type 38.14 + }) 38.15 +end 38.16 + 38.17 +slot.put_into("data", json.export(r)) 38.18 +slot.put_into("data", "\n") 38.19 +
39.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 39.2 +++ b/app/main/api/style.lua Sun Jul 15 14:07:29 2018 +0200 39.3 @@ -0,0 +1,24 @@ 39.4 +slot.set_layout(nil, "application/json") 39.5 + 39.6 + 39.7 +local r = json.object{ 39.8 + color = json.object() 39.9 +} 39.10 + 39.11 +local style = execute.chunk{ module = "style", chunk = "_style", params = { style = config.style } } 39.12 + 39.13 +if style.color_md then 39.14 + r.color.md = {} 39.15 + for k, v in pairs(style.color_md) do 39.16 + r.color.md[k] = v 39.17 + end 39.18 +end 39.19 + 39.20 +if style.color_rgb then 39.21 + r.color.rgb = {} 39.22 + for k, v in pairs(style.color_rgb) do 39.23 + r.color.rgb[k] = v 39.24 + end 39.25 +end 39.26 + 39.27 +slot.put_into("data", json.export(json.object{ result = r }))
40.1 --- a/app/main/area/_head.lua Thu Jun 23 03:30:57 2016 +0200 40.2 +++ b/app/main/area/_head.lua Sun Jul 15 14:07:29 2018 +0200 40.3 @@ -36,4 +36,4 @@ 40.4 } 40.5 end } 40.6 40.7 -end ) 40.8 \ No newline at end of file 40.9 +end )
41.1 --- a/app/main/area/_sidebar_members.lua Thu Jun 23 03:30:57 2016 +0200 41.2 +++ b/app/main/area/_sidebar_members.lua Sun Jul 15 14:07:29 2018 +0200 41.3 @@ -4,7 +4,6 @@ 41.4 41.5 local area = param.get("area", "table") 41.6 local members_selector = Member:new_selector() 41.7 - :join("membership", nil, { "membership.member_id = member.id AND membership.area_id = ?", area.id }) 41.8 :add_where("member.active") 41.9 :limit(50) 41.10
42.1 --- a/app/main/area/_sidebar_whatcanido.lua Thu Jun 23 03:30:57 2016 +0200 42.2 +++ b/app/main/area/_sidebar_whatcanido.lua Sun Jul 15 14:07:29 2018 +0200 42.3 @@ -1,10 +1,9 @@ 42.4 -local member = param.get ( "member", "table" ) or app.session.member 42.5 - 42.6 local area = param.get ( "area", "table" ) 42.7 +area:load_delegation_info_once_for_member_id(app.session.member_id) 42.8 42.9 local participating_trustee_id 42.10 local participating_trustee_name 42.11 -if member then 42.12 +if app.session.member then 42.13 if area.delegation_info.first_trustee_participation then 42.14 participating_trustee_id = area.delegation_info.first_trustee_id 42.15 participating_trustee_name = area.delegation_info.first_trustee_name 42.16 @@ -14,232 +13,192 @@ 42.17 end 42.18 end 42.19 42.20 -ui.sidebar ( "tab-whatcanido", function () 42.21 +ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 42.22 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 42.23 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"What can I do here?" } 42.24 + end } 42.25 + ui.container{ attr = { class = "what-can-i-do-here" }, content = function() 42.26 42.27 - ui.sidebarHeadWhatCanIDo() 42.28 - 42.29 - if member and not app.session.member:has_voting_right_for_unit_id(area.unit_id) then 42.30 - ui.sidebarSection( _"You are not entitled to vote in this unit" ) 42.31 - end 42.32 - 42.33 - if member and app.session.member:has_voting_right_for_unit_id(area.unit_id) then 42.34 + if app.session.member and app.session.member:has_voting_right_for_unit_id(area.unit_id) then 42.35 42.36 - if not app.session.member.disable_notifications then 42.37 - 42.38 - local ignored_area = IgnoredArea:by_pk(app.session.member_id, area.id) 42.39 + if not app.session.member.disable_notifications then 42.40 + 42.41 + local ignored_area = IgnoredArea:by_pk(app.session.member_id, area.id) 42.42 42.43 - if not ignored_area then 42.44 - ui.sidebarSection ( function () 42.45 - 42.46 - ui.heading { 42.47 - level = 3, 42.48 - content = _"You are receiving updates by email for this subject area" 42.49 - } 42.50 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.51 - ui.tag { tag = "li", content = function () 42.52 - ui.tag { content = function () 42.53 - ui.link { 42.54 - module = "area", action = "update_ignore", 42.55 - params = { area_id = area.id }, 42.56 - routing = { default = { 42.57 - mode = "redirect", module = "area", view = "show", id = area.id 42.58 - } }, 42.59 - text = _"unsubscribe from update emails about this area" 42.60 - } 42.61 + if not ignored_area then 42.62 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 42.63 + ui.tag{ content = _"You are receiving updates by email for this subject area" } 42.64 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.65 + ui.tag { tag = "li", content = function () 42.66 + ui.tag { content = function () 42.67 + ui.link { 42.68 + module = "area", action = "update_ignore", 42.69 + params = { area_id = area.id }, 42.70 + routing = { default = { 42.71 + mode = "redirect", module = "index", view = "index", params = { unit = area.unit_id, area = area.id } 42.72 + } }, 42.73 + text = _"unsubscribe from update emails about this area" 42.74 + } 42.75 + end } 42.76 end } 42.77 end } 42.78 end } 42.79 - end ) 42.80 - end 42.81 + end 42.82 + 42.83 + if ignored_area then 42.84 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 42.85 + ui.tag{ content = _"I want to stay informed" } 42.86 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.87 + ui.tag { tag = "li", content = function () 42.88 + ui.tag { content = function () 42.89 + ui.link { 42.90 + module = "area", action = "update_ignore", 42.91 + params = { area_id = area.id, delete = true }, 42.92 + routing = { default = { 42.93 + mode = "redirect", module = "index", view = "index", params = { unit = area.unit_id, area = area.id } 42.94 + } }, 42.95 + text = _"subscribe for update emails about this area" 42.96 + } 42.97 + end } 42.98 + end } 42.99 + end } 42.100 + end } 42.101 + end 42.102 42.103 - if ignored_area then 42.104 - ui.sidebarSection ( function () 42.105 - 42.106 - ui.heading { 42.107 - level = 3, 42.108 - content = _"I want to stay informed" 42.109 - } 42.110 + else 42.111 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 42.112 + ui.tag{ content = _"I want to stay informed about this subject area" } 42.113 ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.114 ui.tag { tag = "li", content = function () 42.115 ui.tag { content = function () 42.116 + ui.tag{ content = _"Edit your global " } 42.117 ui.link { 42.118 - module = "area", action = "update_ignore", 42.119 - params = { area_id = area.id, delete = true }, 42.120 - routing = { default = { 42.121 - mode = "redirect", module = "area", view = "show", id = area.id 42.122 - } }, 42.123 - text = _"subscribe for update emails about this area" 42.124 + module = "member", view = "settings_notification", 42.125 + params = { return_to = "area", return_to_area_id = area.id }, 42.126 + text = _"notification settings" 42.127 } 42.128 + ui.tag{ content = _" to receive updates by email" } 42.129 end } 42.130 end } 42.131 end } 42.132 - end ) 42.133 - end 42.134 - 42.135 - else 42.136 - ui.sidebarSection ( function () 42.137 - 42.138 - ui.heading { 42.139 - level = 3, 42.140 - content = _"I want to stay informed about this subject area" 42.141 - } 42.142 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.143 - ui.tag { tag = "li", content = function () 42.144 - ui.tag { content = function () 42.145 - ui.tag{ content = _"Edit your global " } 42.146 - ui.link { 42.147 - module = "member", view = "settings_notification", 42.148 - params = { return_to = "area", return_to_area_id = area.id }, 42.149 - text = _"notification settings" 42.150 - } 42.151 - ui.tag{ content = _" to receive updates by email" } 42.152 - end } 42.153 - end } 42.154 - end } 42.155 - end ) 42.156 - end 42.157 - 42.158 - if area.delegation_info.own_participation then 42.159 - ui.sidebarSection ( function () 42.160 - ui.image{ attr = { class = "right" }, static = "icons/48/star.png" } 42.161 - ui.heading { 42.162 - level = 3, 42.163 - content = _"You are subscribed for this subject area" 42.164 - } 42.165 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.166 - ui.tag { tag = "li", content = function () 42.167 - ui.tag { content = function () 42.168 - ui.link { 42.169 - module = "membership", action = "update", 42.170 - routing = { default = { 42.171 - mode = "redirect", module = "area", view = "show", id = area.id 42.172 - } }, 42.173 - params = { area_id = area.id, delete = true }, 42.174 - text = _"unsubscribe" 42.175 - } 42.176 - end } 42.177 - end } 42.178 end } 42.179 - end ) 42.180 - end 42.181 - 42.182 - if not area.delegation_info.own_participation then 42.183 - ui.sidebarSection ( function () 42.184 + end 42.185 42.186 - ui.heading { 42.187 - level = 3, 42.188 - content = _"I want to participate in this subject area" 42.189 - } 42.190 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.191 - ui.tag { tag = "li", content = function () 42.192 - ui.tag { content = function () 42.193 - ui.link { 42.194 - module = "membership", action = "update", 42.195 - routing = { default = { 42.196 - mode = "redirect", module = "area", view = "show", id = area.id 42.197 - } }, 42.198 - params = { area_id = area.id }, 42.199 - text = _"subscribe" 42.200 - } 42.201 - end } 42.202 + if app.session.member:has_voting_right_for_unit_id ( area.unit_id ) then 42.203 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 42.204 + ui.tag{ content = _"I want to vote" } 42.205 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.206 + ui.tag { tag = "li", content = _"check the issues on the right, and click on 'Vote now' to vote on an issue which is in voting phase." } 42.207 end } 42.208 end } 42.209 - end ) 42.210 - end 42.211 + end 42.212 + 42.213 + if app.session.member and not app.session.member:has_voting_right_for_unit_id(area.unit_id) then 42.214 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = _"You are not entitled to vote in this unit" } 42.215 + end 42.216 42.217 - 42.218 - 42.219 - ui.sidebarSection ( function () 42.220 - 42.221 + if app.session.member and app.session.member:has_voting_right_for_unit_id(area.unit_id) then 42.222 + 42.223 + if not config.disable_delegations then 42.224 + 42.225 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 42.226 + 42.227 + if not area.delegation_info.first_trustee_id then 42.228 + ui.tag{ content = _"I want to delegate this subject area" } 42.229 + else 42.230 + ui.container { attr = { class = "right" }, content = function() 42.231 + local member = Member:by_id(area.delegation_info.first_trustee_id) 42.232 + execute.view{ 42.233 + module = "member_image", 42.234 + view = "_show", 42.235 + params = { 42.236 + member = member, 42.237 + image_type = "avatar", 42.238 + show_dummy = true 42.239 + } 42.240 + } 42.241 + end } 42.242 + ui.tag{ content = _"You delegated this subject area" } 42.243 + end 42.244 42.245 - if not area.delegation_info.first_trustee_id then 42.246 - ui.heading{ level = 3, content = _"I want to delegate this subject area" } 42.247 - else 42.248 - ui.container { attr = { class = "right" }, content = function() 42.249 - local member = Member:by_id(area.delegation_info.first_trustee_id) 42.250 - execute.view{ 42.251 - module = "member_image", 42.252 - view = "_show", 42.253 - params = { 42.254 - member = member, 42.255 - image_type = "avatar", 42.256 - show_dummy = true 42.257 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.258 + if area.delegation_info.own_delegation_scope == "unit" then 42.259 + ui.tag { tag = "li", content = function () 42.260 + ui.link { 42.261 + module = "delegation", view = "show", params = { 42.262 + unit_id = area.unit_id, 42.263 + }, 42.264 + content = _("change/revoke delegation of organizational unit") 42.265 + } 42.266 + end } 42.267 + end 42.268 + 42.269 + if area.delegation_info.own_delegation_scope == nil then 42.270 + ui.tag { tag = "li", content = function () 42.271 + ui.link { 42.272 + module = "delegation", view = "show", params = { 42.273 + area_id = area.id 42.274 + }, 42.275 + content = _"choose subject area delegatee" 42.276 + } 42.277 + end } 42.278 + elseif area.delegation_info.own_delegation_scope == "area" then 42.279 + ui.tag { tag = "li", content = function () 42.280 + ui.link { 42.281 + module = "delegation", view = "show", params = { 42.282 + area_id = area.id 42.283 + }, 42.284 + content = _"change/revoke area delegation" 42.285 + } 42.286 + end } 42.287 + else 42.288 + ui.tag { tag = "li", content = function () 42.289 + ui.link { 42.290 + module = "delegation", view = "show", params = { 42.291 + area_id = area.id 42.292 + }, 42.293 + content = _"change/revoke delegation only for this subject area" 42.294 + } 42.295 + end } 42.296 + end 42.297 + end } 42.298 + end } 42.299 + end 42.300 + 42.301 + if app.session.member:has_initiative_right_for_unit_id ( area.unit_id ) then 42.302 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 42.303 + ui.tag{ 42.304 + content = _("I want to start a new initiative", { 42.305 + area_name = area.name 42.306 + } ) 42.307 } 42.308 - } 42.309 - end } 42.310 - ui.heading{ level = 3, content = _"You delegated this subject area" } 42.311 - end 42.312 - 42.313 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.314 - if area.delegation_info.own_delegation_scope == "unit" then 42.315 - ui.tag { tag = "li", content = function () 42.316 - ui.link { 42.317 - module = "delegation", view = "show", params = { 42.318 - unit_id = area.unit_id, 42.319 - }, 42.320 - content = _("change/revoke delegation of organizational unit") 42.321 - } 42.322 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.323 + ui.tag { tag = "li", content = _"Take a look through the existing issues. Maybe someone else started a debate on your topic (and you can join it) or the topic has been decided already in the past." } 42.324 + ui.tag { tag = "li", content = function () 42.325 + ui.tag { content = function () 42.326 + ui.tag { content = _"If you cannot find any appropriate existing issue, " } 42.327 + ui.link { 42.328 + module = "initiative", view = "new", 42.329 + params = { area_id = area.id }, 42.330 + text = _"start an initiative in a new issue" 42.331 + } 42.332 + end } 42.333 + end } 42.334 + end } 42.335 end } 42.336 end 42.337 - 42.338 - if area.delegation_info.own_delegation_scope == nil then 42.339 - ui.tag { tag = "li", content = function () 42.340 - ui.link { 42.341 - module = "delegation", view = "show", params = { 42.342 - area_id = area.id 42.343 - }, 42.344 - content = _"choose subject area delegatee" 42.345 - } 42.346 - end } 42.347 - elseif area.delegation_info.own_delegation_scope == "area" then 42.348 - ui.tag { tag = "li", content = function () 42.349 - ui.link { 42.350 - module = "delegation", view = "show", params = { 42.351 - area_id = area.id 42.352 - }, 42.353 - content = _"change/revoke area delegation" 42.354 - } 42.355 - end } 42.356 - else 42.357 - ui.tag { tag = "li", content = function () 42.358 - ui.link { 42.359 - module = "delegation", view = "show", params = { 42.360 - area_id = area.id 42.361 - }, 42.362 - content = _"change/revoke delegation only for this subject area" 42.363 - } 42.364 - end } 42.365 - end 42.366 - end } 42.367 - end ) 42.368 - 42.369 - 42.370 - 42.371 - 42.372 - if app.session.member:has_voting_right_for_unit_id ( area.unit_id ) then 42.373 - ui.sidebarSection ( function () 42.374 - ui.heading { 42.375 - level = 3, 42.376 - content = _("I want to start a new initiative", { 42.377 - area_name = area.name 42.378 - } ) 42.379 - } 42.380 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 42.381 - ui.tag { tag = "li", content = _"Take a look through the existing issues. Maybe someone else started a debate on your topic (and you can join it) or the topic has been decided already in the past." } 42.382 - ui.tag { tag = "li", content = function () 42.383 - ui.tag { content = function () 42.384 - ui.tag { content = _"If you cannot find any appropriate existing issue, " } 42.385 - ui.link { 42.386 - module = "initiative", view = "new", 42.387 - params = { area_id = area.id }, 42.388 - text = _"start an initiative in a new issue" 42.389 - } 42.390 - end } 42.391 + 42.392 + end 42.393 + else 42.394 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 42.395 + ui.tag{ content = _"You are not entitled to vote in this unit" } 42.396 + ui.tag{ tag = "ul", content = function() 42.397 + ui.tag{ tag = "li", content = function() 42.398 + ui.link{ module = "index", view = "login", content = _"Login" } 42.399 end } 42.400 end } 42.401 - end ) 42.402 + end } 42.403 end 42.404 - else 42.405 - end 42.406 + end } 42.407 42.408 -end ) 42.409 \ No newline at end of file 42.410 +end }
43.1 --- a/app/main/area/show.lua Thu Jun 23 03:30:57 2016 +0200 43.2 +++ b/app/main/area/show.lua Sun Jul 15 14:07:29 2018 +0200 43.3 @@ -6,59 +6,68 @@ 43.4 return 43.5 end 43.6 43.7 +app.current_area = area 43.8 + 43.9 + 43.10 area:load_delegation_info_once_for_member_id(app.session.member_id) 43.11 43.12 app.html_title.title = area.name 43.13 app.html_title.subtitle = _("Area") 43.14 43.15 -execute.view { 43.16 - module = "area", view = "_head", params = { 43.17 - area = area, member = app.session.member 43.18 - } 43.19 -} 43.20 +ui.container{ attr = { class = "mdl-grid" }, content = function() 43.21 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 43.22 + ui.heading{ content = area.unit.name .. " / " .. area.name } 43.23 43.24 -execute.view { 43.25 - module = "area", view = "_sidebar_whatcanido", params = { 43.26 - area = area 43.27 - } 43.28 -} 43.29 + execute.view { 43.30 + module = "area", view = "_head", params = { 43.31 + area = area, member = app.session.member 43.32 + } 43.33 + } 43.34 + 43.35 + execute.view { 43.36 + module = "area", view = "_sidebar_whatcanido", params = { 43.37 + area = area 43.38 + } 43.39 + } 43.40 43.41 -execute.view { 43.42 - module = "area", view = "_sidebar_members", params = { 43.43 - area = area 43.44 - } 43.45 -} 43.46 + execute.view { 43.47 + module = "area", view = "_sidebar_members", params = { 43.48 + area = area 43.49 + } 43.50 + } 43.51 43.52 -local function getOpenIssuesSelector() 43.53 - return area:get_reference_selector("issues") 43.54 - :add_order_by("coalesce(issue.fully_frozen + issue.voting_time, issue.half_frozen + issue.verification_time, issue.accepted + issue.discussion_time, issue.created + issue.max_admission_time) - now()") 43.55 -end 43.56 + local function getOpenIssuesSelector() 43.57 + return area:get_reference_selector("issues") 43.58 + :add_order_by("coalesce(issue.fully_frozen + issue.voting_time, issue.half_frozen + issue.verification_time, issue.accepted + issue.discussion_time, issue.created + issue.max_admission_time) - now()") 43.59 + end 43.60 43.61 -local admission_selector = getOpenIssuesSelector() 43.62 - :add_where("issue.state = 'admission'"); 43.63 + local admission_selector = getOpenIssuesSelector() 43.64 + :add_where("issue.state = 'admission'"); 43.65 43.66 -local discussion_selector = getOpenIssuesSelector() 43.67 - :add_where("issue.state = 'discussion'"); 43.68 + local discussion_selector = getOpenIssuesSelector() 43.69 + :add_where("issue.state = 'discussion'"); 43.70 43.71 -local verification_selector = getOpenIssuesSelector() 43.72 - :add_where("issue.state = 'verification'"); 43.73 + local verification_selector = getOpenIssuesSelector() 43.74 + :add_where("issue.state = 'verification'"); 43.75 43.76 -local voting_selector = getOpenIssuesSelector() 43.77 - :add_where("issue.state = 'voting'"); 43.78 + local voting_selector = getOpenIssuesSelector() 43.79 + :add_where("issue.state = 'voting'"); 43.80 43.81 43.82 -local closed_selector = area:get_reference_selector("issues") 43.83 - :add_where("issue.closed NOTNULL") 43.84 - :add_order_by("issue.closed DESC") 43.85 + local closed_selector = area:get_reference_selector("issues") 43.86 + :add_where("issue.closed NOTNULL") 43.87 + :add_order_by("issue.closed DESC") 43.88 43.89 -local members_selector = area:get_reference_selector("members"):add_where("member.active") 43.90 -local delegations_selector = area:get_reference_selector("delegations") 43.91 - :join("member", "truster", "truster.id = delegation.truster_id AND truster.active") 43.92 - :join("member", "trustee", "trustee.id = delegation.trustee_id AND trustee.active") 43.93 + local members_selector = area:get_reference_selector("members"):add_where("member.active") 43.94 + local delegations_selector = area:get_reference_selector("delegations") 43.95 + :join("member", "truster", "truster.id = delegation.truster_id AND truster.active") 43.96 + :join("member", "trustee", "trustee.id = delegation.trustee_id AND trustee.active") 43.97 43.98 43.99 -execute.view { 43.100 - module = "issue", 43.101 - view = "_list2", 43.102 - params = { for_area = area } 43.103 -} 43.104 + execute.view { 43.105 + module = "issue", 43.106 + view = "_list", 43.107 + params = { for_area = area } 43.108 + } 43.109 + end } 43.110 +end }
44.1 --- a/app/main/contact/list.lua Thu Jun 23 03:30:57 2016 +0200 44.2 +++ b/app/main/contact/list.lua Sun Jul 15 14:07:29 2018 +0200 44.3 @@ -5,97 +5,112 @@ 44.4 44.5 ui.title(_"Contacts") 44.6 44.7 +ui.grid{ content = function() 44.8 + ui.cell_main{ content = function() 44.9 44.10 -ui.paginate{ 44.11 - selector = contacts_selector, 44.12 - content = function() 44.13 - local contacts = contacts_selector:exec() 44.14 - if #contacts == 0 then 44.15 - ui.field.text{ value = _"You didn't save any member as contact yet." } 44.16 - else 44.17 - ui.list{ 44.18 - records = contacts, 44.19 - columns = { 44.20 - { 44.21 - label = _"Name", 44.22 - content = function(record) 44.23 - ui.link{ 44.24 - text = record.other_member.name, 44.25 - module = "member", 44.26 - view = "show", 44.27 - id = record.other_member_id 44.28 - } 44.29 - end 44.30 - }, 44.31 - { 44.32 - label = _"Published", 44.33 - content = function(record) 44.34 - ui.field.boolean{ value = record.public } 44.35 - end 44.36 - }, 44.37 - { 44.38 - content = function(record) 44.39 - if record.public then 44.40 - ui.link{ 44.41 - attr = { class = "action" }, 44.42 - text = _"Hide", 44.43 - module = "contact", 44.44 - action = "add_member", 44.45 - id = record.other_member_id, 44.46 - params = { public = false }, 44.47 - routing = { 44.48 - default = { 44.49 - mode = "redirect", 44.50 - module = request.get_module(), 44.51 - view = request.get_view(), 44.52 - id = request.get_id_string(), 44.53 - params = request.get_param_strings() 44.54 - } 44.55 - } 44.56 - } 44.57 - else 44.58 - ui.link{ 44.59 - attr = { class = "action" }, 44.60 - text = _"Publish", 44.61 - module = "contact", 44.62 - action = "add_member", 44.63 - id = record.other_member_id, 44.64 - params = { public = true }, 44.65 - routing = { 44.66 - default = { 44.67 - mode = "redirect", 44.68 - module = request.get_module(), 44.69 - view = request.get_view(), 44.70 - id = request.get_id_string(), 44.71 - params = request.get_param_strings() 44.72 - } 44.73 - } 44.74 - } 44.75 - end 44.76 - end 44.77 - }, 44.78 - { 44.79 - content = function(record) 44.80 - ui.link{ 44.81 - attr = { class = "action" }, 44.82 - text = _"Remove", 44.83 - module = "contact", 44.84 - action = "remove_member", 44.85 - id = record.other_member_id, 44.86 - routing = { 44.87 - default = { 44.88 - mode = "redirect", 44.89 - module = request.get_module(), 44.90 - view = request.get_view(), 44.91 - id = request.get_id_string(), 44.92 - params = request.get_param_strings() 44.93 - } 44.94 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 44.95 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 44.96 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Contacts" } 44.97 + end } 44.98 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 44.99 + 44.100 + 44.101 + ui.paginate{ 44.102 + selector = contacts_selector, 44.103 + content = function() 44.104 + local contacts = contacts_selector:exec() 44.105 + if #contacts == 0 then 44.106 + ui.field.text{ value = _"You didn't save any member as contact yet." } 44.107 + else 44.108 + ui.list{ 44.109 + attr = { class = "mdl-data-table mdl-js-data-table mdl-shadow--2dp" }, 44.110 + records = contacts, 44.111 + columns = { 44.112 + { 44.113 + label = _"Name", 44.114 + content = function(record) 44.115 + ui.link{ 44.116 + text = record.other_member.name, 44.117 + module = "member", 44.118 + view = "show", 44.119 + id = record.other_member_id 44.120 + } 44.121 + end 44.122 + }, 44.123 + { 44.124 + label = _"Published", 44.125 + content = function(record) 44.126 + ui.field.boolean{ value = record.public } 44.127 + end 44.128 + }, 44.129 + { 44.130 + content = function(record) 44.131 + if record.public then 44.132 + ui.link{ 44.133 + attr = { class = "action" }, 44.134 + text = _"Hide", 44.135 + module = "contact", 44.136 + action = "add_member", 44.137 + id = record.other_member_id, 44.138 + params = { public = false }, 44.139 + routing = { 44.140 + default = { 44.141 + mode = "redirect", 44.142 + module = request.get_module(), 44.143 + view = request.get_view(), 44.144 + id = request.get_id_string(), 44.145 + params = request.get_param_strings() 44.146 + } 44.147 + } 44.148 + } 44.149 + else 44.150 + ui.link{ 44.151 + attr = { class = "action" }, 44.152 + text = _"Publish", 44.153 + module = "contact", 44.154 + action = "add_member", 44.155 + id = record.other_member_id, 44.156 + params = { public = true }, 44.157 + routing = { 44.158 + default = { 44.159 + mode = "redirect", 44.160 + module = request.get_module(), 44.161 + view = request.get_view(), 44.162 + id = request.get_id_string(), 44.163 + params = request.get_param_strings() 44.164 + } 44.165 + } 44.166 + } 44.167 + end 44.168 + end 44.169 + }, 44.170 + { 44.171 + content = function(record) 44.172 + ui.link{ 44.173 + attr = { class = "action" }, 44.174 + text = _"Remove", 44.175 + module = "contact", 44.176 + action = "remove_member", 44.177 + id = record.other_member_id, 44.178 + routing = { 44.179 + default = { 44.180 + mode = "redirect", 44.181 + module = request.get_module(), 44.182 + view = request.get_view(), 44.183 + id = request.get_id_string(), 44.184 + params = request.get_param_strings() 44.185 + } 44.186 + } 44.187 + } 44.188 + end 44.189 + }, 44.190 } 44.191 } 44.192 end 44.193 - }, 44.194 + end 44.195 } 44.196 - } 44.197 - end 44.198 - end 44.199 -} 44.200 + 44.201 + end } 44.202 + end } 44.203 + end } 44.204 +end }
45.1 --- a/app/main/delegation/_action/update.lua Thu Jun 23 03:30:57 2016 +0200 45.2 +++ b/app/main/delegation/_action/update.lua Sun Jul 15 14:07:29 2018 +0200 45.3 @@ -1,3 +1,7 @@ 45.4 +if config.disable_delegations then 45.5 + return 45.6 +end 45.7 + 45.8 local truster_id = app.session.member.id 45.9 45.10 local trustee_id = param.get("trustee_id", atom.integer) 45.11 @@ -58,7 +62,7 @@ 45.12 end 45.13 45.14 if not app.session.member:has_voting_right_for_unit_id(check_unit_id) then 45.15 - error("access denied") 45.16 + return execute.view { module = "index", view = "403" } 45.17 end 45.18 45.19 if not delegation then
46.1 --- a/app/main/delegation/_info.lua Thu Jun 23 03:30:57 2016 +0200 46.2 +++ b/app/main/delegation/_info.lua Sun Jul 15 14:07:29 2018 +0200 46.3 @@ -174,7 +174,7 @@ 46.4 if info.own_participation then 46.5 if issue and issue.fully_frozen then 46.6 ui.link{ 46.7 - attr = { class = "right" }, 46.8 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 46.9 module = "vote", view = "list", params = { 46.10 issue_id = issue.id 46.11 }, 46.12 @@ -184,8 +184,9 @@ 46.13 } 46.14 else 46.15 if issue then 46.16 - local text = _"you are interested" 46.17 - ui.image { attr = { class = "star", title = text, alt = text }, static = "icons/48/eye.png" } 46.18 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "star" } 46.19 + slot.put(" ") 46.20 + ui.tag{ content = _"you are interested" } 46.21 if not issue.closed and info.own_participation and info.weight and info.weight > 1 then 46.22 ui.link { 46.23 attr = { class = "right" }, content = "+" .. (info.weight - 1), 46.24 @@ -198,11 +199,7 @@ 46.25 local text = _"you are subscribed" 46.26 ui.image { attr = { class = "icon24 star", title = text, alt = text }, static = "icons/48/star.png" } 46.27 end 46.28 - if not for_title then 46.29 - slot.put("<br />") 46.30 - else 46.31 - slot.put(" ") 46.32 - end 46.33 + slot.put(" ") 46.34 end 46.35 end 46.36 end 46.37 @@ -260,3 +257,15 @@ 46.38 end 46.39 end 46.40 46.41 +if issue and app.session.member and issue.fully_frozen and not issue.closed and not issue.member_info.direct_voted and app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then 46.42 + ui.link{ 46.43 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 46.44 + module = "vote", view = "list", params = { 46.45 + issue_id = issue.id 46.46 + }, 46.47 + content = function () 46.48 + ui.tag { content = _"vote now" } 46.49 + end 46.50 + } 46.51 + else 46.52 +end
47.1 --- a/app/main/delegation/show.lua Thu Jun 23 03:30:57 2016 +0200 47.2 +++ b/app/main/delegation/show.lua Sun Jul 15 14:07:29 2018 +0200 47.3 @@ -113,283 +113,303 @@ 47.4 ui.script{ static = "js/update_delegation_info.js" } 47.5 47.6 47.7 -ui.section( function () 47.8 - 47.9 - ui.sectionHead( function () 47.10 - ui.heading{ level = 1, content = head_text } 47.11 - end ) 47.12 - 47.13 - ui.sectionRow( function () 47.14 +ui.container{ attr = { class = "mdl-grid" }, content = function() 47.15 + ui.container{ attr = { class = "mdl-cell mdl-cell--8-col" }, content = function() 47.16 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 47.17 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 47.18 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = head_text } 47.19 + end } 47.20 + 47.21 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 47.22 + 47.23 + 47.24 + ui.form{ 47.25 + attr = { class = "wide section", id = "delegationForm" }, 47.26 + module = "delegation", 47.27 + action = "update", 47.28 + params = { 47.29 + unit_id = unit and unit.id or nil, 47.30 + area_id = area and area.id or nil, 47.31 + issue_id = issue and issue.id or nil, 47.32 + initiative_id = initiative_id 47.33 + }, 47.34 + routing = { 47.35 + default = { 47.36 + mode = "redirect", 47.37 + module = area and "area" or initiative and "initiative" or issue and "issue" or "unit", 47.38 + view = "show", 47.39 + id = area and area.id or initiative and initiative.id or issue and issue.id or unit.id, 47.40 + } 47.41 + }, 47.42 + content = function() 47.43 + local records 47.44 + if issue then 47.45 + local delegate_name = "" 47.46 + local scope = _"no delegation set" 47.47 + local area_delegation = Delegation:by_pk(app.session.member_id, nil, issue.area_id) 47.48 + if area_delegation then 47.49 + delegate_name = area_delegation.trustee and area_delegation.trustee.name or _"abandoned" 47.50 + scope = _"area" 47.51 + else 47.52 + local unit_delegation = Delegation:by_pk(app.session.member_id, issue.area.unit_id) 47.53 + if unit_delegation then 47.54 + delegate_name = unit_delegation.trustee.name 47.55 + scope = config.single_unit_id and _"global" or _"unit" 47.56 + end 47.57 + end 47.58 + local text_apply 47.59 + local text_abandon 47.60 + if config.single_unit_id then 47.61 + text_apply = _("Apply global or area delegation for this issue (Currently: #{delegate_name} [#{scope}])", { delegate_name = delegate_name, scope = scope }) 47.62 + text_abandon = _"Abandon unit and area delegations for this issue" 47.63 + else 47.64 + text_apply = _("Apply unit or area delegation for this issue (Currently: #{delegate_name} [#{scope}])", { delegate_name = delegate_name, scope = scope }) 47.65 + text_abandon = _"Abandon unit and area delegations for this issue" 47.66 + end 47.67 + 47.68 + records = { 47.69 + { id = -1, name = text_apply }, 47.70 + { id = 0, name = text_abandon } 47.71 + } 47.72 + elseif area then 47.73 + local delegate_name = "" 47.74 + local scope = _"no delegation set" 47.75 + local unit_delegation = Delegation:by_pk(app.session.member_id, area.unit_id) 47.76 + if unit_delegation then 47.77 + delegate_name = unit_delegation.trustee.name 47.78 + scope = config.single_unit_id and _"global" or _"unit" 47.79 + end 47.80 + local text_apply 47.81 + local text_abandon 47.82 + if config.single_unit_id then 47.83 + text_apply = _("Apply global delegation for this area (Currently: #{delegate_name} [#{scope}])", { delegate_name = delegate_name, scope = scope }) 47.84 + text_abandon = _"Abandon global delegation for this area" 47.85 + else 47.86 + text_apply = _("Apply unit delegation for this area (Currently: #{delegate_name} [#{scope}])", { delegate_name = delegate_name, scope = scope }) 47.87 + text_abandon = _"Abandon unit delegation for this area" 47.88 + end 47.89 + records = { 47.90 + { 47.91 + id = -1, 47.92 + name = text_apply 47.93 + }, 47.94 + { 47.95 + id = 0, 47.96 + name = text_abandon 47.97 + } 47.98 + } 47.99 + 47.100 + else 47.101 + records = { 47.102 + { 47.103 + id = -1, 47.104 + name = _"No delegation" 47.105 + } 47.106 + } 47.107 47.108 - ui.form{ 47.109 - attr = { class = "wide section", id = "delegationForm" }, 47.110 - module = "delegation", 47.111 - action = "update", 47.112 - params = { 47.113 - unit_id = unit and unit.id or nil, 47.114 - area_id = area and area.id or nil, 47.115 - issue_id = issue and issue.id or nil, 47.116 - initiative_id = initiative_id 47.117 - }, 47.118 - routing = { 47.119 - default = { 47.120 - mode = "redirect", 47.121 - module = area and "area" or initiative and "initiative" or issue and "issue" or "unit", 47.122 - view = "show", 47.123 - id = area and area.id or initiative and initiative.id or issue and issue.id or unit.id, 47.124 - } 47.125 - }, 47.126 - content = function() 47.127 - local records 47.128 - if issue then 47.129 - local delegate_name = "" 47.130 - local scope = _"no delegation set" 47.131 - local area_delegation = Delegation:by_pk(app.session.member_id, nil, issue.area_id) 47.132 - if area_delegation then 47.133 - delegate_name = area_delegation.trustee and area_delegation.trustee.name or _"abandoned" 47.134 - scope = _"area" 47.135 - else 47.136 - local unit_delegation = Delegation:by_pk(app.session.member_id, issue.area.unit_id) 47.137 - if unit_delegation then 47.138 - delegate_name = unit_delegation.trustee.name 47.139 - scope = config.single_unit_id and _"global" or _"unit" 47.140 + end 47.141 + -- add current trustee 47.142 + if current_trustee_id then 47.143 + records[#records+1] = {id="_", name= "--- " .. _"Current delegatee" .. " ---"} 47.144 + records[#records+1] = { id = current_trustee_id, name = current_trustee_name } 47.145 + end 47.146 + -- add initiative authors 47.147 + if initiative then 47.148 + records[#records+1] = {id="_", name= "--- " .. _"Initiators" .. " ---"} 47.149 + for i,record in ipairs(initiative.initiators) do 47.150 + records[#records+1] = record.member 47.151 + end 47.152 + end 47.153 + -- add saved members 47.154 + if #contact_members > 0 then 47.155 + records[#records+1] = {id="_", name= "--- " .. _"Saved contacts" .. " ---"} 47.156 + for i, record in ipairs(contact_members) do 47.157 + records[#records+1] = record 47.158 + end 47.159 + end 47.160 + 47.161 + local disabled_records = {} 47.162 + disabled_records["_"] = true 47.163 + disabled_records[app.session.member_id] = true 47.164 + 47.165 + local value = current_trustee_id 47.166 + if preview_trustee_id then 47.167 + value = preview_trustee_id 47.168 + end 47.169 + if preview_trustee_id == nil and delegation and not delegation.trustee_id then 47.170 + value = 0 47.171 + end 47.172 + 47.173 + ui.tag{ content = _"Choose your delegatee" } 47.174 + 47.175 + ui.field.select{ 47.176 + attr = { onchange = "updateDelegationInfo();" }, 47.177 + name = "trustee_id", 47.178 + foreign_records = records, 47.179 + foreign_id = "id", 47.180 + foreign_name = "name", 47.181 + disabled_records = disabled_records, 47.182 + value = value 47.183 + } 47.184 + slot.put("<br />") 47.185 + 47.186 + ui.container{ content = _"You can choose only members which you have been saved as contact before." } 47.187 + 47.188 + ui.field.hidden{ name = "preview" } 47.189 + 47.190 + slot.put("<br />") 47.191 + ui.tag { tag = "input", content = "", attr = { 47.192 + type = "submit", 47.193 + value = _"Save", 47.194 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 47.195 + } } 47.196 + 47.197 + slot.put(" ") 47.198 + if initiative then 47.199 + ui.link{ 47.200 + module = "initiative", 47.201 + view = "show", 47.202 + id = initiative.id, 47.203 + attr = { class = "mdl-button mdl-js-button mdl-button--underlined" }, 47.204 + content = function() 47.205 + slot.put(_"Cancel") 47.206 + end, 47.207 + } 47.208 + elseif issue then 47.209 + ui.link{ 47.210 + module = "issue", 47.211 + view = "show", 47.212 + id = issue.id, 47.213 + attr = { class = "mdl-button mdl-js-button mdl-button--underlined" }, 47.214 + content = function() 47.215 + slot.put(_"Cancel") 47.216 + end, 47.217 + } 47.218 + elseif area then 47.219 + ui.link{ 47.220 + module = "index", 47.221 + view = "index", 47.222 + params = { unit = area.unit_id, area = area.id }, 47.223 + attr = { class = "mdl-button mdl-js-button mdl-button--underlined" }, 47.224 + content = function() 47.225 + slot.put(_"Cancel") 47.226 + end, 47.227 + } 47.228 + else 47.229 + ui.link{ 47.230 + module = "index", 47.231 + view = "index", 47.232 + attr = { class = "mdl-button mdl-js-button mdl-button--underlined" }, 47.233 + content = function() 47.234 + slot.put(_"Cancel") 47.235 + end, 47.236 + } 47.237 + end 47.238 + 47.239 end 47.240 - end 47.241 - local text_apply 47.242 - local text_abandon 47.243 - if config.single_unit_id then 47.244 - text_apply = _("Apply global or area delegation for this issue (Currently: #{delegate_name} [#{scope}])", { delegate_name = delegate_name, scope = scope }) 47.245 - text_abandon = _"Abandon unit and area delegations for this issue" 47.246 - else 47.247 - text_apply = _("Apply unit or area delegation for this issue (Currently: #{delegate_name} [#{scope}])", { delegate_name = delegate_name, scope = scope }) 47.248 - text_abandon = _"Abandon unit and area delegations for this issue" 47.249 - end 47.250 - 47.251 - records = { 47.252 - { id = -1, name = text_apply }, 47.253 - { id = 0, name = text_abandon } 47.254 - } 47.255 - elseif area then 47.256 - local delegate_name = "" 47.257 - local scope = _"no delegation set" 47.258 - local unit_delegation = Delegation:by_pk(app.session.member_id, area.unit_id) 47.259 - if unit_delegation then 47.260 - delegate_name = unit_delegation.trustee.name 47.261 - scope = config.single_unit_id and _"global" or _"unit" 47.262 - end 47.263 - local text_apply 47.264 - local text_abandon 47.265 - if config.single_unit_id then 47.266 - text_apply = _("Apply global delegation for this area (Currently: #{delegate_name} [#{scope}])", { delegate_name = delegate_name, scope = scope }) 47.267 - text_abandon = _"Abandon global delegation for this area" 47.268 - else 47.269 - text_apply = _("Apply unit delegation for this area (Currently: #{delegate_name} [#{scope}])", { delegate_name = delegate_name, scope = scope }) 47.270 - text_abandon = _"Abandon unit delegation for this area" 47.271 - end 47.272 - records = { 47.273 - { 47.274 - id = -1, 47.275 - name = text_apply 47.276 - }, 47.277 - { 47.278 - id = 0, 47.279 - name = text_abandon 47.280 - } 47.281 - } 47.282 - 47.283 - else 47.284 - records = { 47.285 - { 47.286 - id = -1, 47.287 - name = _"No delegation" 47.288 - } 47.289 } 47.290 47.291 - end 47.292 - -- add current trustee 47.293 - if current_trustee_id then 47.294 - records[#records+1] = {id="_", name= "--- " .. _"Current delegatee" .. " ---"} 47.295 - records[#records+1] = { id = current_trustee_id, name = current_trustee_name } 47.296 - end 47.297 - -- add initiative authors 47.298 - if initiative then 47.299 - records[#records+1] = {id="_", name= "--- " .. _"Initiators" .. " ---"} 47.300 - for i,record in ipairs(initiative.initiators) do 47.301 - records[#records+1] = record.member 47.302 - end 47.303 - end 47.304 - -- add saved members 47.305 - if #contact_members > 0 then 47.306 - records[#records+1] = {id="_", name= "--- " .. _"Saved contacts" .. " ---"} 47.307 - for i, record in ipairs(contact_members) do 47.308 - records[#records+1] = record 47.309 - end 47.310 - end 47.311 - 47.312 - local disabled_records = {} 47.313 - disabled_records["_"] = true 47.314 - disabled_records[app.session.member_id] = true 47.315 - 47.316 - local value = current_trustee_id 47.317 - if preview_trustee_id then 47.318 - value = preview_trustee_id 47.319 - end 47.320 - if preview_trustee_id == nil and delegation and not delegation.trustee_id then 47.321 - value = 0 47.322 - end 47.323 + end } 47.324 + end } 47.325 + end } 47.326 + -- ------------------------ 47.327 47.328 - ui.heading{ level = 2, content = _"Choose your delegatee" } 47.329 - 47.330 - ui.field.select{ 47.331 - attr = { onchange = "updateDelegationInfo();" }, 47.332 - name = "trustee_id", 47.333 - foreign_records = records, 47.334 - foreign_id = "id", 47.335 - foreign_name = "name", 47.336 - disabled_records = disabled_records, 47.337 - value = value 47.338 - } 47.339 - 47.340 - ui.container{ content = _"You can choose only members which you have been saved as contact before." } 47.341 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col mdl-cell--4-col-desktop" }, content = function() 47.342 + ui.container{ attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 47.343 47.344 - ui.field.hidden{ name = "preview" } 47.345 - 47.346 - slot.put("<br />") 47.347 - ui.tag { tag = "input", content = "", attr = { 47.348 - type = "submit", 47.349 - value = _"Save", 47.350 - class = "btn btn-default", 47.351 - } } 47.352 - 47.353 - slot.put("<br /><br /><br />") 47.354 - if initiative then 47.355 - ui.link{ 47.356 - module = "initiative", 47.357 - view = "show", 47.358 - id = initiative.id, 47.359 - content = function() 47.360 - slot.put(_"Cancel") 47.361 - end, 47.362 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 47.363 + ui.container{ 47.364 + attr = { class = "mdl-card__title-text" }, 47.365 + content = _"Preview of delegation" 47.366 } 47.367 - elseif issue then 47.368 - ui.link{ 47.369 - module = "issue", 47.370 - view = "show", 47.371 - id = issue.id, 47.372 - content = function() 47.373 - slot.put(_"Cancel") 47.374 - end, 47.375 - } 47.376 - elseif area then 47.377 - ui.link{ 47.378 - module = "area", 47.379 - view = "show", 47.380 - id = area.id, 47.381 - content = function() 47.382 - slot.put(_"Cancel") 47.383 - end, 47.384 - } 47.385 - else 47.386 - ui.link{ 47.387 - module = "index", 47.388 - view = "index", 47.389 - content = function() 47.390 - slot.put(_"Cancel") 47.391 - end, 47.392 - } 47.393 - end 47.394 + end } 47.395 47.396 - end 47.397 - } 47.398 - 47.399 -end ) end ) 47.400 --- ------------------------ 47.401 - 47.402 -ui.sidebar( "tab-members", function () 47.403 - 47.404 -ui.sidebarHead( function () 47.405 - ui.heading { level = 1, content = _"Preview of delegation" } 47.406 -end ) 47.407 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 47.408 + local preview_inherit = false 47.409 + local tmp_preview_trustee_id = preview_trustee_id 47.410 + if preview_trustee_id == -1 then 47.411 + preview_inherit = true 47.412 + tmp_preview_trustee_id = nil 47.413 + end 47.414 + local delegation_chain = Member:new_selector() 47.415 + :add_field("delegation_chain.*") 47.416 + :join({ "delegation_chain(?,?,?,?,?,?)", app.session.member.id, unit_id, area_id, issue_id, tmp_preview_trustee_id, preview_inherit }, "delegation_chain", "member.id = delegation_chain.member_id") 47.417 + :add_order_by("index") 47.418 + :exec() 47.419 47.420 -local preview_inherit = false 47.421 -local tmp_preview_trustee_id = preview_trustee_id 47.422 -if preview_trustee_id == -1 then 47.423 - preview_inherit = true 47.424 - tmp_preview_trustee_id = nil 47.425 -end 47.426 -local delegation_chain = Member:new_selector() 47.427 - :add_field("delegation_chain.*") 47.428 - :join({ "delegation_chain(?,?,?,?,?,?)", app.session.member.id, unit_id, area_id, issue_id, tmp_preview_trustee_id, preview_inherit }, "delegation_chain", "member.id = delegation_chain.member_id") 47.429 - :add_order_by("index") 47.430 - :exec() 47.431 + for i, record in ipairs(delegation_chain) do 47.432 + local style 47.433 + local overridden = (not issue or issue.state ~= 'voting') and record.overridden 47.434 + ui.sidebarSection( function () 47.435 + if record.scope_in then 47.436 + if not overridden then 47.437 + local text = _"delegated to" 47.438 + ui.image{ 47.439 + attr = { class = "delegation_arrow", alt = text, title = text }, 47.440 + static = "delegation_arrow_24_vertical.png" 47.441 + } 47.442 + else 47.443 + local text = _"delegated to" 47.444 + ui.image{ 47.445 + attr = { class = "delegation_arrow delegation_arrow_overridden", alt = text, title = text }, 47.446 + static = "delegation_arrow_24_vertical.png" 47.447 + } 47.448 + end 47.449 + ui.tag{ 47.450 + attr = { class = "delegation_scope" .. (overridden and " delegation_scope_overridden" or "") }, 47.451 + content = function() 47.452 + if record.scope_in == "unit" then 47.453 + slot.put(config.single_object_mode and _"Global delegation" or _"Unit delegation") 47.454 + elseif record.scope_in == "area" then 47.455 + slot.put(_"Area delegation") 47.456 + elseif record.scope_in == "issue" then 47.457 + slot.put(_"Issue delegation") 47.458 + end 47.459 + end 47.460 + } 47.461 + end 47.462 + ui.container{ 47.463 + attr = { class = overridden and "delegation_overridden" or "" }, 47.464 + content = function() 47.465 + execute.view{ 47.466 + module = "member", 47.467 + view = "_show_thumb", 47.468 + params = { member = record } 47.469 + } 47.470 + end 47.471 + } 47.472 + if issue and issue.state ~= 'voting' and record.participation and not record.overridden then 47.473 + ui.container{ 47.474 + attr = { class = "delegation_participation" }, 47.475 + content = function() 47.476 + if i == #delegation_chain then 47.477 + ui.tag{ content = _"This member is currently participating in this issue." } 47.478 + else 47.479 + ui.tag{ content = _"This member is participating, the remaining delegation chain is suspended during discussing." } 47.480 + end 47.481 + end 47.482 + } 47.483 + end 47.484 + slot.put("<br style='clear: left'/>") 47.485 + end ) 47.486 + end 47.487 47.488 -for i, record in ipairs(delegation_chain) do 47.489 - local style 47.490 - local overridden = (not issue or issue.state ~= 'voting') and record.overridden 47.491 - ui.sidebarSection( function () 47.492 - if record.scope_in then 47.493 - if not overridden then 47.494 - local text = _"delegated to" 47.495 - ui.image{ 47.496 - attr = { class = "delegation_arrow", alt = text, title = text }, 47.497 - static = "delegation_arrow_24_vertical.png" 47.498 - } 47.499 - else 47.500 - local text = _"delegated to" 47.501 - ui.image{ 47.502 - attr = { class = "delegation_arrow delegation_arrow_overridden", alt = text, title = text }, 47.503 - static = "delegation_arrow_24_vertical.png" 47.504 - } 47.505 - end 47.506 - ui.tag{ 47.507 - attr = { class = "delegation_scope" .. (overridden and " delegation_scope_overridden" or "") }, 47.508 - content = function() 47.509 - if record.scope_in == "unit" then 47.510 - slot.put(config.single_object_mode and _"Global delegation" or _"Unit delegation") 47.511 - elseif record.scope_in == "area" then 47.512 - slot.put(_"Area delegation") 47.513 - elseif record.scope_in == "issue" then 47.514 - slot.put(_"Issue delegation") 47.515 + if preview_trustee_id == 0 or not preview_trustee_id == null and delegation and not delegation.trustee_id then 47.516 + ui.image{ 47.517 + static = "icons/16/table_go_crossed.png" 47.518 + } 47.519 + if issue_id then 47.520 + slot.put(_"Delegation turned off for issue") 47.521 + elseif area_id then 47.522 + slot.put(_"Delegation turned off for area") 47.523 end 47.524 end 47.525 - } 47.526 - end 47.527 - ui.container{ 47.528 - attr = { class = overridden and "delegation_overridden" or "" }, 47.529 - content = function() 47.530 - execute.view{ 47.531 - module = "member", 47.532 - view = "_show_thumb", 47.533 - params = { member = record } 47.534 - } 47.535 - end 47.536 - } 47.537 - if issue and issue.state ~= 'voting' and record.participation and not record.overridden then 47.538 - ui.container{ 47.539 - attr = { class = "delegation_participation" }, 47.540 - content = function() 47.541 - if i == #delegation_chain then 47.542 - ui.tag{ content = _"This member is currently participating in this issue." } 47.543 - else 47.544 - ui.tag{ content = _"This member is participating, the remaining delegation chain is suspended during discussing." } 47.545 - end 47.546 - end 47.547 - } 47.548 - end 47.549 - slot.put("<br style='clear: left'/>") 47.550 - end ) 47.551 -end 47.552 - 47.553 -if preview_trustee_id == 0 or not preview_trustee_id == null and delegation and not delegation.trustee_id then 47.554 - ui.image{ 47.555 - static = "icons/16/table_go_crossed.png" 47.556 - } 47.557 - if issue_id then 47.558 - slot.put(_"Delegation turned off for issue") 47.559 - elseif area_id then 47.560 - slot.put(_"Delegation turned off for area") 47.561 - end 47.562 -end 47.563 + 47.564 + end } 47.565 + 47.566 + end } 47.567 + 47.568 + end } 47.569 47.570 47.571 -end ) 47.572 \ No newline at end of file 47.573 +end }
48.1 --- a/app/main/draft/_action/add.lua Thu Jun 23 03:30:57 2016 +0200 48.2 +++ b/app/main/draft/_action/add.lua Sun Jul 15 14:07:29 2018 +0200 48.3 @@ -1,8 +1,31 @@ 48.4 +local draft_text = param.get("content") 48.5 + 48.6 +if not draft_text then 48.7 + return false 48.8 +end 48.9 + 48.10 +local draft_text = util.wysihtml_preproc(draft_text) 48.11 + 48.12 +local valid_html, error_message = util.html_is_safe(draft_text) 48.13 +if not valid_html then 48.14 + slot.put_into("error", _("Draft contains invalid formatting or character sequence: #{error_message}", { error_message = error_message }) ) 48.15 + return false 48.16 +end 48.17 + 48.18 +if config.initiative_abstract then 48.19 + local abstract = param.get("abstract") 48.20 + if not abstract then 48.21 + return false 48.22 + end 48.23 + abstract = encode.html(abstract) 48.24 + draft_text = abstract .. "<!--END_OF_ABSTRACT-->" .. draft_text 48.25 +end 48.26 + 48.27 return Draft:update_content( 48.28 app.session.member.id, 48.29 param.get("initiative_id", atom.integer), 48.30 param.get("formatting_engine"), 48.31 - param.get("content"), 48.32 + draft_text, 48.33 nil, 48.34 param.get("preview") or param.get("edit") 48.35 )
49.1 --- a/app/main/draft/_show.lua Thu Jun 23 03:30:57 2016 +0200 49.2 +++ b/app/main/draft/_show.lua Sun Jul 15 14:07:29 2018 +0200 49.3 @@ -10,7 +10,11 @@ 49.4 content = draft.content 49.5 } 49.6 else 49.7 - slot.put(draft:get_content("html")) 49.8 + if draft.formatting_engine == "html" or not draft.formatting_engine then 49.9 + slot.put(draft.content) 49.10 + else 49.11 + slot.put(draft:get_content("html")) 49.12 + end 49.13 end 49.14 end 49.15 }
50.1 --- a/app/main/draft/diff.lua Thu Jun 23 03:30:57 2016 +0200 50.2 +++ b/app/main/draft/diff.lua Sun Jul 15 14:07:29 2018 +0200 50.3 @@ -33,41 +33,8 @@ 50.4 end 50.5 50.6 50.7 -execute.view{ module = "issue", view = "_sidebar_state", params = { 50.8 - initiative = initiative 50.9 -} } 50.10 - 50.11 -execute.view { 50.12 - module = "issue", view = "_sidebar_issue", 50.13 - params = { 50.14 - issue = initiative.issue, 50.15 - highlight_initiative_id = initiative.id 50.16 - } 50.17 -} 50.18 - 50.19 -execute.view { 50.20 - module = "issue", view = "_sidebar_whatcanido", 50.21 - params = { initiative = initiative } 50.22 -} 50.23 - 50.24 -execute.view { 50.25 - module = "issue", view = "_sidebar_members", params = { 50.26 - issue = initiative.issue, initiative = initiative 50.27 - } 50.28 -} 50.29 - 50.30 - 50.31 - 50.32 -execute.view { 50.33 - module = "issue", view = "_head", params = { 50.34 - issue = initiative.issue 50.35 - } 50.36 -} 50.37 - 50.38 - 50.39 - 50.40 -local old_draft_content = string.gsub(string.gsub(old_draft.content, "\n", " ###ENTER###\n"), " ", "\n") 50.41 -local new_draft_content = string.gsub(string.gsub(new_draft.content, "\n", " ###ENTER###\n"), " ", "\n") 50.42 +local old_draft_content = string.gsub(string.gsub(util.html_to_text(old_draft.content), "\n", " ###ENTER###\n"), " ", "\n") 50.43 +local new_draft_content = string.gsub(string.gsub(util.html_to_text(new_draft.content), "\n", " ###ENTER###\n"), " ", "\n") 50.44 50.45 local key = multirand.string(24, "0123456789abcdefghijklmnopqrstuvwxyz") 50.46 50.47 @@ -84,7 +51,7 @@ 50.48 new_draft_file:write("\n") 50.49 new_draft_file:close() 50.50 50.51 -local output, err, status = extos.pfilter(nil, "sh", "-c", "diff -U 1000000000 '" .. old_draft_filename .. "' '" .. new_draft_filename .. "' | grep -v ^--- | grep -v ^+++ | grep -v ^@") 50.52 +local output, err, status = extos.pfilter(nil, "sh", "-c", "diff -a -U 1000000000 '" .. old_draft_filename .. "' '" .. new_draft_filename .. "' | grep --binary-files=text -v ^--- | grep --binary-files=text -v ^+++ | grep --binary-files=text -v ^@") 50.53 50.54 os.remove(old_draft_filename) 50.55 os.remove(new_draft_filename) 50.56 @@ -116,93 +83,133 @@ 50.57 if not state_changed then 50.58 slot.put(" ") 50.59 end 50.60 - slot.put(encode.html(line)) 50.61 + --slot.put(encode.html(line)) 50.62 + slot.put(line) 50.63 else 50.64 slot.put("<br />") 50.65 end 50.66 end 50.67 50.68 -ui.section( function() 50.69 - ui.sectionHead( function() 50.70 - ui.link{ 50.71 - module = "initiative", view = "show", id = initiative.id, 50.72 - content = function () 50.73 +execute.view{ module = "issue", view = "_head", params = { issue = initiative.issue } } 50.74 + 50.75 +ui.grid{ content = function() 50.76 + ui.cell_main{ content = function() 50.77 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 50.78 + 50.79 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function () 50.80 ui.heading { 50.81 - level = 1, 50.82 - content = initiative.display_name 50.83 - } 50.84 - end 50.85 - } 50.86 - ui.heading{ level = 2, content = _("Comparision of revisions #{id1} and #{id2}", { 50.87 - id1 = old_draft.id, 50.88 - id2 = new_draft.id 50.89 - } ) } 50.90 - end ) 50.91 - 50.92 - if app.session.member_id and not new_draft.initiative.revoked then 50.93 - local supporter = app.session.member:get_reference_selector("supporters") 50.94 - :add_where{ "initiative_id = ?", new_draft.initiative_id } 50.95 - :optional_object_mode() 50.96 - :exec() 50.97 - if supporter and supporter.draft_id ~= new_draft.id then 50.98 - ui.sectionRow("draft_updated_info", function() 50.99 - ui.container{ 50.100 - attr = { class = "info" }, 50.101 - content = _"The draft of this initiative has been updated!" 50.102 + attr = { class = "mdl-card__title-text" }, 50.103 + content = function() 50.104 + ui.link{ 50.105 + module = "initiative", view = "show", id = initiative.id, 50.106 + content = initiative.display_name 50.107 + } 50.108 + end 50.109 } 50.110 - slot.put(" ") 50.111 - ui.link{ 50.112 - text = _"refresh my support", 50.113 - module = "initiative", 50.114 - action = "add_support", 50.115 - id = new_draft.initiative.id, 50.116 - params = { draft_id = new_draft.id }, 50.117 - routing = { 50.118 - default = { 50.119 - mode = "redirect", 50.120 - module = "initiative", 50.121 - view = "show", 50.122 - id = new_draft.initiative.id 50.123 - } 50.124 + ui.container{ content = _("Comparision of revisions #{id1} and #{id2}", { 50.125 + id1 = old_draft.id, 50.126 + id2 = new_draft.id 50.127 + } ) } 50.128 + end } 50.129 + 50.130 + if app.session.member_id and not new_draft.initiative.revoked then 50.131 + local supporter = app.session.member:get_reference_selector("supporters") 50.132 + :add_where{ "initiative_id = ?", new_draft.initiative_id } 50.133 + :optional_object_mode() 50.134 + :exec() 50.135 + if supporter and supporter.draft_id == old_draft.id and new_draft.id == initiative.current_draft.id then 50.136 + ui.container { 50.137 + attr = { class = "mdl-card__content mdl-card--no-bottom-pad mdl-card--notice" }, 50.138 + content = _"The draft of this initiative has been updated!" 50.139 } 50.140 - } 50.141 + ui.container { 50.142 + attr = { class = "mdl-card__actions mdl-card--action-border mdl-card--notice" }, 50.143 + content = function () 50.144 + ui.link{ 50.145 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 50.146 + text = _"refresh my support", 50.147 + module = "initiative", 50.148 + action = "add_support", 50.149 + id = new_draft.initiative.id, 50.150 + params = { draft_id = new_draft.id }, 50.151 + routing = { 50.152 + default = { 50.153 + mode = "redirect", 50.154 + module = "initiative", 50.155 + view = "show", 50.156 + id = new_draft.initiative.id 50.157 + } 50.158 + } 50.159 + } 50.160 50.161 - slot.put(" · ") 50.162 - 50.163 - ui.link{ 50.164 - text = _"remove my support", 50.165 - module = "initiative", 50.166 - action = "remove_support", 50.167 - id = new_draft.initiative.id, 50.168 - routing = { 50.169 - default = { 50.170 - mode = "redirect", 50.171 - module = "initiative", 50.172 - view = "show", 50.173 - id = new_draft.initiative.id 50.174 - } 50.175 + slot.put(" ") 50.176 + 50.177 + ui.link{ 50.178 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 50.179 + text = _"remove my support", 50.180 + module = "initiative", 50.181 + action = "remove_support", 50.182 + id = new_draft.initiative.id, 50.183 + routing = { 50.184 + default = { 50.185 + mode = "redirect", 50.186 + module = "initiative", 50.187 + view = "show", 50.188 + id = new_draft.initiative.id 50.189 + } 50.190 + } 50.191 + } 50.192 + 50.193 + slot.put(" ") 50.194 + 50.195 + ui.link{ 50.196 + attr = { class = "mdl-button mdl-js-button" }, 50.197 + text = _"cancel", 50.198 + module = "initiative", 50.199 + view = "show", 50.200 + id = new_draft.initiative.id, 50.201 + } 50.202 + end 50.203 } 50.204 - } 50.205 - 50.206 - end ) 50.207 - end 50.208 - end 50.209 - 50.210 - ui.sectionRow( function() 50.211 + end 50.212 + end 50.213 50.214 - if not status then 50.215 - ui.field.text{ value = _"The drafts do not differ" } 50.216 - else 50.217 - ui.container{ 50.218 - tag = "div", 50.219 - attr = { class = "diff" }, 50.220 - content = function() 50.221 - output = output:gsub("[^\n\r]+", function(line) 50.222 - process_line(line) 50.223 - end) 50.224 + ui.container { 50.225 + attr = { class = "draft mdl-card__content mdl-card--border" }, 50.226 + content = function () 50.227 + if not status then 50.228 + ui.field.text{ value = _"The drafts do not differ" } 50.229 + else 50.230 + ui.container{ 50.231 + tag = "div", 50.232 + attr = { class = "diff" }, 50.233 + content = function() 50.234 + output = output:gsub("[^\n\r]+", function(line) 50.235 + process_line(line) 50.236 + end) 50.237 + end 50.238 + } 50.239 + end 50.240 end 50.241 } 50.242 - end 50.243 + end } 50.244 + end } 50.245 + ui.cell_sidebar{ content = function() 50.246 + 50.247 + execute.view{ module = "issue", view = "_sidebar", params = { 50.248 + initiative = initiative, 50.249 + issue = initiative.issue 50.250 + } } 50.251 50.252 - end ) 50.253 -end ) 50.254 + execute.view { 50.255 + module = "issue", view = "_sidebar_whatcanido", 50.256 + params = { initiative = initiative } 50.257 + } 50.258 + 50.259 + execute.view { 50.260 + module = "issue", view = "_sidebar_members", params = { 50.261 + issue = initiative.issue, initiative = initiative 50.262 + } 50.263 + } 50.264 + end } 50.265 +end }
51.1 --- a/app/main/draft/new.lua Thu Jun 23 03:30:57 2016 +0200 51.2 +++ b/app/main/draft/new.lua Sun Jul 15 14:07:29 2018 +0200 51.3 @@ -13,25 +13,16 @@ 51.4 return 51.5 end 51.6 51.7 - 51.8 -execute.view{ 51.9 - module = "issue", view = "_head", params = { 51.10 - issue = initiative.issue, 51.11 - initiative = initiative 51.12 - } 51.13 -} 51.14 - 51.15 -execute.view { 51.16 - module = "issue", view = "_sidebar_issue", 51.17 - params = { 51.18 - issue = initiative.issue, 51.19 - } 51.20 -} 51.21 - 51.22 - 51.23 +local draft = initiative.current_draft 51.24 +if config.initiative_abstract then 51.25 + draft.abstract = string.match(draft.content, "(.+)<!%--END_OF_ABSTRACT%-->") 51.26 + if draft.abstract then 51.27 + draft.content = string.match(draft.content, "<!%--END_OF_ABSTRACT%-->(.*)") 51.28 + end 51.29 +end 51.30 51.31 ui.form{ 51.32 - record = initiative.current_draft, 51.33 + record = draft, 51.34 attr = { class = "vertical section" }, 51.35 module = "draft", 51.36 action = "add", 51.37 @@ -46,103 +37,119 @@ 51.38 }, 51.39 content = function() 51.40 51.41 - ui.sectionHead( function() 51.42 - ui.heading { level = 1, content = initiative.display_name } 51.43 - end) 51.44 - 51.45 - if param.get("preview") then 51.46 - ui.sectionRow( function() 51.47 - ui.field.hidden{ name = "formatting_engine", value = param.get("formatting_engine") } 51.48 - ui.field.hidden{ name = "content", value = param.get("content") } 51.49 - local formatting_engine 51.50 - if config.enforce_formatting_engine then 51.51 - formatting_engine = config.enforce_formatting_engine 51.52 - else 51.53 - formatting_engine = param.get("formatting_engine") 51.54 - end 51.55 - ui.container{ 51.56 - attr = { class = "draft" }, 51.57 - content = function() 51.58 - slot.put(format.wiki_text(param.get("content"), formatting_engine)) 51.59 - end 51.60 - } 51.61 + ui.grid{ content = function() 51.62 + ui.cell_main{ content = function() 51.63 + ui.container{ attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 51.64 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 51.65 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = initiative.display_name } 51.66 + end } 51.67 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 51.68 + if param.get("preview") then 51.69 + ui.sectionRow( function() 51.70 + if config.initiative_abstract then 51.71 + ui.field.hidden{ name = "abstract", value = param.get("abstract") } 51.72 + ui.container{ 51.73 + attr = { class = "abstract" }, 51.74 + content = param.get("abstract") 51.75 + } 51.76 + slot.put("<br />") 51.77 + end 51.78 + local draft_text = param.get("content") 51.79 + local draft_text = util.wysihtml_preproc(draft_text) 51.80 + ui.field.hidden{ name = "content", value = draft_text } 51.81 + ui.container{ 51.82 + attr = { class = "draft" }, 51.83 + content = function() 51.84 + slot.put(draft_text) 51.85 + end 51.86 + } 51.87 + slot.put("<br />") 51.88 51.89 - slot.put("<br />") 51.90 - ui.tag{ 51.91 - tag = "input", 51.92 - attr = { 51.93 - type = "submit", 51.94 - class = "btn btn-default", 51.95 - value = _'Publish now' 51.96 - }, 51.97 - content = "" 51.98 - } 51.99 - slot.put("<br />") 51.100 - slot.put("<br />") 51.101 + ui.tag{ 51.102 + tag = "input", 51.103 + attr = { 51.104 + type = "submit", 51.105 + class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored", 51.106 + value = _'Publish now' 51.107 + }, 51.108 + content = "" 51.109 + } 51.110 + slot.put(" ") 51.111 + 51.112 + ui.tag{ 51.113 + tag = "input", 51.114 + attr = { 51.115 + type = "submit", 51.116 + name = "edit", 51.117 + class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect", 51.118 + value = _'Edit again' 51.119 + }, 51.120 + content = "" 51.121 + } 51.122 + slot.put(" ") 51.123 51.124 - ui.tag{ 51.125 - tag = "input", 51.126 - attr = { 51.127 - type = "submit", 51.128 - name = "edit", 51.129 - class = "btn-link", 51.130 - value = _'Edit again' 51.131 - }, 51.132 - content = "" 51.133 - } 51.134 - slot.put(" | ") 51.135 - ui.link{ 51.136 - content = _"Cancel", 51.137 - module = "initiative", 51.138 - view = "show", 51.139 - id = initiative.id 51.140 - } 51.141 - end ) 51.142 + ui.link{ 51.143 + attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" }, 51.144 + content = _"Cancel", 51.145 + module = "initiative", 51.146 + view = "show", 51.147 + id = initiative.id 51.148 + } 51.149 + end ) 51.150 51.151 - else 51.152 - ui.sectionRow( function() 51.153 - execute.view{ module = "initiative", view = "_sidebar_wikisyntax" } 51.154 - 51.155 - if not config.enforce_formatting_engine then 51.156 - ui.field.select{ 51.157 - label = _"Wiki engine", 51.158 - name = "formatting_engine", 51.159 - foreign_records = config.formatting_engines, 51.160 - attr = {id = "formatting_engine"}, 51.161 - foreign_id = "id", 51.162 - foreign_name = "name" 51.163 - } 51.164 - end 51.165 - 51.166 - ui.heading{ level = 2, content = _"Enter your proposal and/or reasons" } 51.167 + else 51.168 + ui.sectionRow( function() 51.169 + if config.initiative_abstract then 51.170 + ui.container { content = _"Enter abstract:" } 51.171 + ui.container{ attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--expandable mdl-textfield__fullwidth" }, content = function() 51.172 + ui.field.text{ 51.173 + name = "abstract", 51.174 + multiline = true, 51.175 + attr = { id = "abstract", style = "height: 20ex; width: 100%;" }, 51.176 + value = param.get("abstract") 51.177 + } 51.178 + end } 51.179 + end 51.180 + 51.181 + ui.container { content = _"Enter your proposal and/or reasons:" } 51.182 + ui.field.wysihtml{ 51.183 + name = "content", 51.184 + multiline = true, 51.185 + attr = { id = "draft", style = "height: 50ex; width: 100%;" }, 51.186 + value = param.get("content") 51.187 + } 51.188 + if not issue or issue.state == "admission" or issue.state == "discussion" then 51.189 + ui.container { content = _"You can change your text again anytime during admission and discussion phase" } 51.190 + else 51.191 + ui.container { content = _"You cannot change your text again later, because this issue is already in verfication phase!" } 51.192 + end 51.193 + slot.put("<br />") 51.194 51.195 - ui.field.text{ 51.196 - name = "content", 51.197 - multiline = true, 51.198 - attr = { style = "height: 50ex; width: 100%;" }, 51.199 - value = param.get("content") 51.200 - } 51.201 - ui.tag{ 51.202 - tag = "input", 51.203 - attr = { 51.204 - type = "submit", 51.205 - name = "preview", 51.206 - class = "btn btn-default", 51.207 - value = _'Preview' 51.208 - }, 51.209 - content = "" 51.210 - } 51.211 - slot.put("<br />") 51.212 - slot.put("<br />") 51.213 - 51.214 - ui.link{ 51.215 - content = _"Cancel", 51.216 - module = "initiative", 51.217 - view = "show", 51.218 - id = initiative.id 51.219 - } 51.220 - 51.221 - end ) 51.222 - end 51.223 + ui.tag{ 51.224 + tag = "input", 51.225 + attr = { 51.226 + type = "submit", 51.227 + name = "preview", 51.228 + class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored", 51.229 + value = _'Preview' 51.230 + }, 51.231 + content = "" 51.232 + } 51.233 + slot.put(" ") 51.234 + 51.235 + ui.link{ 51.236 + content = _"Cancel", 51.237 + module = "initiative", 51.238 + view = "show", 51.239 + id = initiative.id, 51.240 + attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" } 51.241 + } 51.242 + 51.243 + end ) 51.244 + end 51.245 + end } 51.246 + end } 51.247 + end } 51.248 + end } 51.249 end 51.250 }
52.1 --- a/app/main/draft/show.lua Thu Jun 23 03:30:57 2016 +0200 52.2 +++ b/app/main/draft/show.lua Sun Jul 15 14:07:29 2018 +0200 52.3 @@ -1,4 +1,12 @@ 52.4 -local draft = Draft:new_selector():add_where{ "id = ?", param.get_id() }:single_object_mode():exec() 52.5 +local draft = Draft:new_selector():add_where{ "id = ?", param.get_id() }:optional_object_mode():exec() 52.6 + 52.7 +if not draft then 52.8 + execute.view { module = "index", view = "404" } 52.9 + request.set_status("404 Not Found") 52.10 + return 52.11 +end 52.12 + 52.13 + 52.14 local source = param.get("source", atom.boolean) 52.15 52.16 execute.view{
53.1 --- a/app/main/help/introduction.lua Thu Jun 23 03:30:57 2016 +0200 53.2 +++ b/app/main/help/introduction.lua Sun Jul 15 14:07:29 2018 +0200 53.3 @@ -1,51 +1,36 @@ 53.4 ui.title(_"Introduction") 53.5 53.6 --- show the user what can be done 53.7 -execute.view { module = "index", view = "_sidebar_whatcanido" } 53.8 +ui.grid{ content = function() 53.9 + ui.cell_main{ content = function() 53.10 53.11 -ui.section(function() 53.12 - ui.sectionHead(function() 53.13 - ui.heading{ level = 1, content = _"Structured discussion" } 53.14 - end) 53.15 - ui.sectionRow(function() 53.16 - ui.heading{ level = 2, content = _"Initiatives and issues" } 53.17 - ui.tag{ tag = "p", content = _"[introduction] iniatives and issues" } 53.18 - ui.heading{ level = 2, content = _"Subject areas" } 53.19 - ui.tag{ tag = "p", content = _"[introduction] subject areas" } 53.20 - ui.heading{ level = 2, content = _"Organizational units" } 53.21 - ui.tag{ tag = "p", content = _"[introduction] organizational units" } 53.22 - ui.heading{ level = 2, content = _"Rules of procedure" } 53.23 - ui.tag{ tag = "p", content = _"[introduction] rules of procedure" } 53.24 - end ) 53.25 -end ) 53.26 -ui.section(function() 53.27 - ui.sectionHead(function() 53.28 - ui.heading{ level = 1, content = _"4 phases of a decision" } 53.29 - end) 53.30 - ui.sectionRow(function() 53.31 - ui.heading{ level = 2, content = _"(1) Admission phase" } 53.32 - ui.tag{ tag = "p", content = _"[introduction] phase 1 admission" } 53.33 - ui.heading{ level = 2, content = _"(2) Discussion phase" } 53.34 - ui.tag{ tag = "p", content = _"[introduction] phase 2 discussion" } 53.35 - ui.heading{ level = 2, content = _"(3) Verification phase" } 53.36 - ui.tag{ tag = "p", content = _"[introduction] phase 3 verification" } 53.37 - ui.heading{ level = 2, content = _"(4) Voting phase" } 53.38 - ui.tag{ tag = "p", content = _"[introduction] phase 4 voting" } 53.39 - end) 53.40 -end) 53.41 -ui.section(function() 53.42 - ui.sectionHead(function() 53.43 - ui.heading{ level = 1, content = _"Vote delegation" } 53.44 - end) 53.45 - ui.sectionRow(function() 53.46 - ui.tag{ tag = "p", content = _"[introduction] vote delegation" } 53.47 - end) 53.48 -end) 53.49 -ui.section(function() 53.50 - ui.sectionHead(function() 53.51 - ui.heading{ level = 1, content = _"Preference voting" } 53.52 - end) 53.53 - ui.sectionRow(function() 53.54 - ui.tag{ tag = "p", content = _"[introduction] preference voting" } 53.55 - end) 53.56 -end) 53.57 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 53.58 + ui.container{ attr = { class = "mdl-card__title mdl-card--has-fab mdl-card--border" }, content = function () 53.59 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 1, content = _"Quick guide" } 53.60 + end } 53.61 + ui.container { attr = { class = "draft mdl-card__content mdl-card--border" }, content = function() 53.62 + ui.heading{ level = 2, content = _"Initiatives and issues" } 53.63 + ui.tag{ tag = "p", content = _"[introduction] iniatives and issues" } 53.64 + ui.heading{ level = 2, content = _"Subject areas" } 53.65 + ui.tag{ tag = "p", content = _"[introduction] subject areas" } 53.66 + ui.heading{ level = 2, content = _"Organizational units" } 53.67 + ui.tag{ tag = "p", content = _"[introduction] organizational units" } 53.68 + ui.heading{ level = 2, content = _"Rules of procedure" } 53.69 + ui.tag{ tag = "p", content = _"[introduction] rules of procedure" } 53.70 + ui.heading{ level = 2, content = _"Admission phase" } 53.71 + ui.tag{ tag = "p", content = _"[introduction] phase 1 admission" } 53.72 + ui.heading{ level = 2, content = _"Discussion phase" } 53.73 + ui.tag{ tag = "p", content = _"[introduction] phase 2 discussion" } 53.74 + ui.heading{ level = 2, content = _"Verification phase" } 53.75 + ui.tag{ tag = "p", content = _"[introduction] phase 3 verification" } 53.76 + ui.heading{ level = 2, content = _"Voting phase" } 53.77 + ui.tag{ tag = "p", content = _"[introduction] phase 4 voting" } 53.78 + ui.heading{ level = 2, content = _"Vote delegation" } 53.79 + ui.tag{ tag = "p", content = _"[introduction] vote delegation" } 53.80 + ui.heading{ level = 2, content = _"Preference voting" } 53.81 + ui.tag{ tag = "p", content = _"[introduction] preference voting" } 53.82 + 53.83 + end } 53.84 + end } 53.85 + end } 53.86 +end } 53.87 +
54.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 54.2 +++ b/app/main/http_options.lua Sun Jul 15 14:07:29 2018 +0200 54.3 @@ -0,0 +1,17 @@ 54.4 +-- TODO workaround, needs to be resolved in WebMCP's request.handler 54.5 +if not request._route then 54.6 + return 54.7 +end 54.8 + 54.9 +if request.get_module() == "oauth2" and request.get_view() == "session" then 54.10 + local origin = request.get_header("Origin") 54.11 + if origin then 54.12 + request.add_header("Access-Control-Allow-Origin", origin) 54.13 + end 54.14 + request.add_header("Access-Control-Allow-Credentials", "true") 54.15 + request.add_header("Access-Control-Max-Age", "0") 54.16 +else 54.17 + request.add_header("Access-Control-Allow-Origin", "*") 54.18 +end 54.19 + 54.20 +request.add_header("Access-Control-Allow-Headers", "Authorization")
55.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 55.2 +++ b/app/main/index/403.lua Sun Jul 15 14:07:29 2018 +0200 55.3 @@ -0,0 +1,21 @@ 55.4 +request.set_status("403 Forbidden") 55.5 + 55.6 +ui.title("403 Forbidden") 55.7 + 55.8 +ui.grid{ content = function() 55.9 + 55.10 + ui.cell_main{ content = function() 55.11 + 55.12 + ui.container { attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 55.13 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 55.14 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Forbidden" } 55.15 + end } 55.16 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 55.17 + ui.link{ 55.18 + content = _"Go back to home page", 55.19 + module = "index", view = "index" 55.20 + } 55.21 + end } 55.22 + end } 55.23 + end } 55.24 +end }
56.1 --- a/app/main/index/404.lua Thu Jun 23 03:30:57 2016 +0200 56.2 +++ b/app/main/index/404.lua Sun Jul 15 14:07:29 2018 +0200 56.3 @@ -1,13 +1,21 @@ 56.4 +request.set_status("404 Not found") 56.5 + 56.6 ui.title("404 Not found") 56.7 56.8 -ui.section(function() 56.9 - ui.sectionHead(function() 56.10 - ui.heading{ level = 1, content = _"Page not found" } 56.11 - end) 56.12 - ui.sectionRow(function() 56.13 - ui.link{ 56.14 - content = _"Go back to home page", 56.15 - module = "index", view = "index" 56.16 - } 56.17 - end) 56.18 -end) 56.19 \ No newline at end of file 56.20 +ui.grid{ content = function() 56.21 + 56.22 + ui.cell_main{ content = function() 56.23 + 56.24 + ui.container { attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 56.25 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 56.26 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Page not found" } 56.27 + end } 56.28 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 56.29 + ui.link{ 56.30 + content = _"Go back to home page", 56.31 + module = "index", view = "index" 56.32 + } 56.33 + end } 56.34 + end } 56.35 + end } 56.36 +end }
57.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 57.2 +++ b/app/main/index/405.lua Sun Jul 15 14:07:29 2018 +0200 57.3 @@ -0,0 +1,21 @@ 57.4 +request.set_status("405 Method Not Allowed") 57.5 + 57.6 +ui.title("405 Method Not Allowed") 57.7 + 57.8 +ui.grid{ content = function() 57.9 + 57.10 + ui.cell_main{ content = function() 57.11 + 57.12 + ui.container { attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 57.13 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 57.14 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Method not allowed" } 57.15 + end } 57.16 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 57.17 + ui.link{ 57.18 + content = _"Go back to home page", 57.19 + module = "index", view = "index" 57.20 + } 57.21 + end } 57.22 + end } 57.23 + end } 57.24 +end }
58.1 --- a/app/main/index/_action/login.lua Thu Jun 23 03:30:57 2016 +0200 58.2 +++ b/app/main/index/_action/login.lua Sun Jul 15 14:07:29 2018 +0200 58.3 @@ -99,6 +99,7 @@ 58.4 member:save() 58.5 app.session.member = member 58.6 app.session:save() 58.7 + 58.8 trace.debug('User authenticated') 58.9 if config.etherpad then 58.10 do_etherpad_auth(member) 58.11 @@ -116,9 +117,7 @@ 58.12 ui.tag { content = _"to show more info and learn what you can do" } 58.13 end ) 58.14 else 58.15 - slot.select("error", function() 58.16 - ui.tag{ content = _'Invalid login name or password!' } 58.17 - end) 58.18 + slot.put_into("error_code", "invalid_credentials") 58.19 trace.debug('User NOT authenticated') 58.20 return false 58.21 end
59.1 --- a/app/main/index/_action/logout.lua Thu Jun 23 03:30:57 2016 +0200 59.2 +++ b/app/main/index/_action/logout.lua Sun Jul 15 14:07:29 2018 +0200 59.3 @@ -9,3 +9,7 @@ 59.4 } 59.5 end 59.6 end 59.7 + 59.8 +if config.meta_navigation_logout_url then 59.9 + request.redirect{ external = config.meta_navigation_logout_url } 59.10 +end
60.1 --- a/app/main/index/_action/register.lua Thu Jun 23 03:30:57 2016 +0200 60.2 +++ b/app/main/index/_action/register.lua Sun Jul 15 14:07:29 2018 +0200 60.3 @@ -4,13 +4,13 @@ 60.4 60.5 if app.session.authority == "ldap" then 60.6 if not config.ldap.member or config.ldap.member.registration ~= "manual" then 60.7 - error("access denied") 60.8 + return execute.view { module = "index", view = "403" } 60.9 end 60.10 member = ldap.create_member(app.session.authority_uid, true) 60.11 60.12 else 60.13 if config.registration_disabled then 60.14 - error("registration disabled") 60.15 + return execute.view { module = "index", view = "403" } 60.16 end 60.17 member = Member:new_selector() 60.18 :add_where{ "invite_code = ?", code } 60.19 @@ -21,38 +21,58 @@ 60.20 :exec() 60.21 end 60.22 60.23 - 60.24 + 60.25 if not member then 60.26 slot.put_into("error", _"The code you've entered is invalid") 60.27 request.redirect{ 60.28 mode = "forward", 60.29 module = "index", 60.30 - view = "register" 60.31 + view = "register", params = { 60.32 + redirect_module = param.get("redirect_module"), 60.33 + redirect_view = param.get("redirect_view"), 60.34 + redirect_id = param.get("redirect_id"), 60.35 + redirect_params = param.get("redirect_params") 60.36 + } 60.37 } 60.38 return false 60.39 end 60.40 60.41 local notify_email = param.get("notify_email") 60.42 60.43 -if not util.is_profile_field_locked(member, "notify_email") and notify_email then 60.44 +if not util.is_profile_field_locked(member, "notify_email") and not member.notify_email and notify_email then 60.45 if #notify_email < 5 then 60.46 slot.put_into("error", _"Email address too short!") 60.47 request.redirect{ 60.48 mode = "redirect", 60.49 module = "index", 60.50 view = "register", 60.51 - params = { code = member.invite_code } 60.52 + params = { 60.53 + code = member.invite_code, 60.54 + skip = param.get("skip"), 60.55 + redirect_module = param.get("redirect_module"), 60.56 + redirect_view = param.get("redirect_view"), 60.57 + redirect_id = param.get("redirect_id"), 60.58 + redirect_params = param.get("redirect_params") 60.59 + } 60.60 } 60.61 return false 60.62 end 60.63 end 60.64 60.65 -if member and not util.is_profile_field_locked(member, "notify_email") and not notify_email then 60.66 +if member and not util.is_profile_field_locked(member, "notify_email") and not member.notify_email and not notify_email then 60.67 request.redirect{ 60.68 mode = "redirect", 60.69 module = "index", 60.70 view = "register", 60.71 - params = { code = member.invite_code, step = 1 } 60.72 + params = { 60.73 + code = member.invite_code, 60.74 + skip = param.get("skip"), 60.75 + step = 1, 60.76 + redirect_module = param.get("redirect_module"), 60.77 + redirect_view = param.get("redirect_view"), 60.78 + redirect_id = param.get("redirect_id"), 60.79 + redirect_params = param.get("redirect_params") 60.80 + } 60.81 } 60.82 return false 60.83 end 60.84 @@ -71,7 +91,12 @@ 60.85 params = { 60.86 code = member.invite_code, 60.87 notify_email = notify_email, 60.88 - step = 1 60.89 + step = 1, 60.90 + skip = param.get("skip"), 60.91 + redirect_module = param.get("redirect_module"), 60.92 + redirect_view = param.get("redirect_view"), 60.93 + redirect_id = param.get("redirect_id"), 60.94 + redirect_params = param.get("redirect_params") 60.95 } 60.96 } 60.97 return false 60.98 @@ -87,7 +112,12 @@ 60.99 params = { 60.100 code = member.invite_code, 60.101 notify_email = notify_email, 60.102 - step = 1 60.103 + step = 1, 60.104 + skip = param.get("skip"), 60.105 + redirect_module = param.get("redirect_module"), 60.106 + redirect_view = param.get("redirect_view"), 60.107 + redirect_id = param.get("redirect_id"), 60.108 + redirect_params = param.get("redirect_params") 60.109 } 60.110 } 60.111 return false 60.112 @@ -105,7 +135,12 @@ 60.113 params = { 60.114 code = member.invite_code, 60.115 notify_email = notify_email, 60.116 - step = 1 60.117 + step = 1, 60.118 + skip = param.get("skip"), 60.119 + redirect_module = param.get("redirect_module"), 60.120 + redirect_view = param.get("redirect_view"), 60.121 + redirect_id = param.get("redirect_id"), 60.122 + redirect_params = param.get("redirect_params") 60.123 } 60.124 } 60.125 return false 60.126 @@ -124,7 +159,12 @@ 60.127 code = member.invite_code, 60.128 notify_email = notify_email, 60.129 name = member.name, 60.130 - step = 1 60.131 + step = 1, 60.132 + skip = param.get("skip"), 60.133 + redirect_module = param.get("redirect_module"), 60.134 + redirect_view = param.get("redirect_view"), 60.135 + redirect_id = param.get("redirect_id"), 60.136 + redirect_params = param.get("redirect_params") 60.137 } 60.138 } 60.139 return false 60.140 @@ -141,7 +181,12 @@ 60.141 code = member.invite_code, 60.142 notify_email = notify_email, 60.143 name = member.name, 60.144 - step = 1 60.145 + step = 1, 60.146 + skip = param.get("skip"), 60.147 + redirect_module = param.get("redirect_module"), 60.148 + redirect_view = param.get("redirect_view"), 60.149 + redirect_id = param.get("redirect_id"), 60.150 + redirect_params = param.get("redirect_params") 60.151 } 60.152 } 60.153 return false 60.154 @@ -158,7 +203,12 @@ 60.155 code = member.invite_code, 60.156 notify_email = notify_email, 60.157 name = member.name, 60.158 - step = 1 60.159 + step = 1, 60.160 + skip = param.get("skip"), 60.161 + redirect_module = param.get("redirect_module"), 60.162 + redirect_view = param.get("redirect_view"), 60.163 + redirect_id = param.get("redirect_id"), 60.164 + redirect_params = param.get("redirect_params") 60.165 } 60.166 } 60.167 return false 60.168 @@ -169,10 +219,16 @@ 60.169 if step > 2 then 60.170 60.171 for i, checkbox in ipairs(config.use_terms_checkboxes) do 60.172 - local accepted = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) 60.173 - if not accepted then 60.174 - slot.put_into("error", checkbox.not_accepted_error) 60.175 - return false 60.176 + local member_useterms = MemberUseterms:new_selector() 60.177 + :add_where{ "member_id = ?", member.id } 60.178 + :add_where{ "contract_identifier = ?", checkbox.name } 60.179 + :exec() 60.180 + if #member_useterms == 0 then 60.181 + local accepted = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) 60.182 + if not accepted then 60.183 + slot.put_into("error", checkbox.not_accepted_error) 60.184 + return false 60.185 + end 60.186 end 60.187 end 60.188 60.189 @@ -190,7 +246,12 @@ 60.190 code = member.invite_code, 60.191 notify_email = notify_email, 60.192 name = member.name, 60.193 - login = member.login 60.194 + login = member.login, 60.195 + skip = param.get("skip"), 60.196 + redirect_module = param.get("redirect_module"), 60.197 + redirect_view = param.get("redirect_view"), 60.198 + redirect_id = param.get("redirect_id"), 60.199 + redirect_params = param.get("redirect_params") 60.200 } 60.201 } 60.202 --]] 60.203 @@ -219,7 +280,7 @@ 60.204 member.name = name 60.205 end 60.206 60.207 - if notify_email ~= member.notify_email then 60.208 + if not member.notify_email then 60.209 local success = member:set_notify_email(notify_email) 60.210 if not success then 60.211 slot.put_into("error", _"Can't send confirmation email") 60.212 @@ -231,7 +292,10 @@ 60.213 60.214 for i, checkbox in ipairs(config.use_terms_checkboxes) do 60.215 local accepted = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) 60.216 - member:set_setting("use_terms_checkbox_" .. checkbox.name, "accepted at " .. tostring(now)) 60.217 + local member_useterms = MemberUseterms:new() 60.218 + member_useterms.member_id = member.id 60.219 + member_useterms.contract_identifier = checkbox.name 60.220 + member_useterms:save() 60.221 end 60.222 60.223 member.activated = 'now' 60.224 @@ -239,12 +303,24 @@ 60.225 member.last_activity = 'now' 60.226 member:save() 60.227 60.228 - slot.put_into("notice", _"You've successfully registered and you can login now with your login and password!") 60.229 + if not member.profile then 60.230 + local profile = MemberProfile:new() 60.231 + profile.member_id = member.id 60.232 + profile.profile = json.object() 60.233 + profile:save() 60.234 + end 60.235 + 60.236 + slot.put_into("notice", _"Registration succeeded") 60.237 + 60.238 + app.session.member_id = member.id 60.239 + app.session:save() 60.240 60.241 request.redirect{ 60.242 mode = "redirect", 60.243 - module = "index", 60.244 - view = "login", 60.245 + module = param.get("redirect_module") or "index", 60.246 + view = param.get("redirect_view") or "index", 60.247 + id = param.get("redirect_id"), 60.248 + params = param.get("redirect_params") 60.249 } 60.250 end 60.251
61.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 61.2 +++ b/app/main/index/_drawer.lua Sun Jul 15 14:07:29 2018 +0200 61.3 @@ -0,0 +1,141 @@ 61.4 +local member = app.session.member 61.5 +local units 61.6 +if member then 61.7 + units = member:get_reference_selector("units"):add_order_by("name"):exec() 61.8 + units:load_delegation_info_once_for_member_id(member.id) 61.9 +else 61.10 + units = Unit:new_selector():add_where("active"):add_order_by("name"):exec() 61.11 +end 61.12 + 61.13 +slot.select("drawer", function() 61.14 + ui.container{ attr = { class = "mdl-layout__drawer" }, content = function() 61.15 + ui.tag{ tag = "nav", attr = { class = "mdl-navigation" }, content = function () 61.16 + ui.link{ content = config.instance_name, attr = { class = "mdl-navigation__link mdl-menu__item--full-bleed-divider mdl-navigation__head" }, module = "index", view = "index" } 61.17 + 61.18 + if #units > 0 then 61.19 + for i, unit in ipairs(units) do 61.20 + local class = "mdl-navigation__link mdl-navigation__head" 61.21 + if i == #units then 61.22 + class = class .. " mdl-menu__item--full-bleed-divider" 61.23 + end 61.24 + ui.link{ attr = { class = class }, content = unit.name, module = "unit", view = "show", id = unit.id } 61.25 + local areas = unit.areas 61.26 + for i, area in ipairs(areas) do 61.27 + local class = "mdl-navigation__link mdl-menu__item--small" 61.28 + if i == #areas then 61.29 + class = class .. " mdl-menu__item--full-bleed-divider" 61.30 + end 61.31 + ui.link{ attr = { class = class }, module = "area", view = "show", id = area.id, content = function() 61.32 + ui.tag{ content = "⤷" } 61.33 + slot.put(" ") 61.34 + ui.tag{ content = area.name } 61.35 + end } 61.36 + end 61.37 + end 61.38 + end 61.39 + 61.40 + ui.link{ attr = { class = "mdl-navigation__link mdl-menu__item--full-bleed-divider" }, content = _"Member list", module = "member", view = "list" } 61.41 + ui.link{ attr = { class = "mdl-navigation__link" }, content = _"Quick guide", module = "help", view = "introduction" } 61.42 + 61.43 + if app.session.member_id and app.session.member.admin then 61.44 + ui.link{ attr = { class = "mdl-navigation__link mdl-menu__item--full-bleed-divider" }, content = _"System settings", module = "admin", view = "index" } 61.45 + end 61.46 + 61.47 + end } 61.48 + end } 61.49 +end) 61.50 + 61.51 +if true then 61.52 + return 61.53 +end 61.54 + 61.55 +for i, unit in ipairs(units) do 61.56 + 61.57 + ui.sidebar ( "tab-whatcanido units", function () 61.58 + 61.59 + local areas_selector = Area:new_selector() 61.60 + :reset_fields() 61.61 + :add_field("area.id", nil, { "grouped" }) 61.62 + :add_field("area.unit_id", nil, { "grouped" }) 61.63 + :add_field("area.name", nil, { "grouped" }) 61.64 + :add_where{ "area.unit_id = ?", unit.id } 61.65 + :add_where{ "area.active" } 61.66 + :add_order_by("area.name") 61.67 + 61.68 + local areas = areas_selector:exec() 61.69 + if member then 61.70 + areas:load_delegation_info_once_for_member_id(member.id) 61.71 + end 61.72 + 61.73 + if #areas > 0 then 61.74 + 61.75 + ui.container { 61.76 + attr = { class = "sidebarHead" }, 61.77 + content = function () 61.78 + ui.heading { level = 2, content = function () 61.79 + ui.link { 61.80 + attr = { class = "unit" }, 61.81 + module = "unit", view = "show", id = unit.id, 61.82 + content = unit.name 61.83 + } 61.84 + 61.85 + if member then 61.86 + local delegation = Delegation:by_pk(member.id, unit.id, nil, nil) 61.87 + 61.88 + if delegation then 61.89 + ui.link { 61.90 + module = "delegation", view = "show", params = { 61.91 + unit_id = unit.id 61.92 + }, 61.93 + attr = { class = "delegation_info" }, 61.94 + content = function () 61.95 + ui.delegation(delegation.trustee_id, delegation.trustee.name) 61.96 + end 61.97 + } 61.98 + end 61.99 + end 61.100 + end } 61.101 + 61.102 + end 61.103 + } 61.104 + 61.105 + 61.106 + ui.tag { tag = "div", attr = { class = "areas areas-" .. unit.id }, content = function () 61.107 + 61.108 + for i, area in ipairs(areas) do 61.109 + 61.110 + ui.tag { tag = "div", attr = { class = "sidebarRow" }, content = function () 61.111 + 61.112 + if member then 61.113 + local delegation = Delegation:by_pk(member.id, nil, area.id, nil) 61.114 + 61.115 + if delegation then 61.116 + ui.link { 61.117 + module = "delegation", view = "show", params = { 61.118 + area_id = area.id 61.119 + }, 61.120 + attr = { class = "delegation_info" }, 61.121 + content = function () 61.122 + ui.delegation(delegation.trustee_id, delegation.trustee_id and delegation.trustee.name) 61.123 + end 61.124 + } 61.125 + end 61.126 + end 61.127 + 61.128 + slot.put ( " " ) 61.129 + 61.130 + ui.link { 61.131 + attr = { class = "area" }, 61.132 + module = "area", view = "show", id = area.id, 61.133 + content = area.name 61.134 + } 61.135 + 61.136 + 61.137 + end } 61.138 + end 61.139 + end } 61.140 + end 61.141 + end ) 61.142 +end 61.143 + 61.144 +
62.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 62.2 +++ b/app/main/index/_head.lua Sun Jul 15 14:07:29 2018 +0200 62.3 @@ -0,0 +1,142 @@ 62.4 +local unit_id = config.single_unit_id or tonumber(request.get_param{ name = "unit" }) 62.5 +if unit_id == "all" then 62.6 + unit_id = nil 62.7 +end 62.8 +local unit 62.9 +if unit_id then 62.10 + unit = Unit:by_id(unit_id) 62.11 +end 62.12 +local area_id = config.single_area_id or tonumber(request.get_param{ name = "area" }) 62.13 +if area_id == "all" then 62.14 + area_id = nil 62.15 +end 62.16 +local area 62.17 +if area_id then 62.18 + area = Area:by_id(area_id) 62.19 +end 62.20 +if area and unit and area.unit_id ~= unit.id then 62.21 + area_id = nil 62.22 +end 62.23 +if area and area.unit_id == unit_id then 62.24 + if app.session.member_id then 62.25 + area:load_delegation_info_once_for_member_id(app.session.member_id) 62.26 + end 62.27 + 62.28 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 62.29 + if unit then 62.30 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 62.31 + if not config.single_area_id then 62.32 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = unit.name .. " / " .. area.name } 62.33 + else 62.34 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = unit.name } 62.35 + end 62.36 + end } 62.37 + end 62.38 + if area.description and #(area.description) > 0 then 62.39 + if not config.single_area_id then 62.40 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = area.description } 62.41 + else 62.42 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = unit.description } 62.43 + end 62.44 + end 62.45 + if not (config.voting_only and config.disable_delegations) and app.session.member_id then 62.46 + ui.container{ attr = { class = "mdl-card__actions" }, content = function() 62.47 + 62.48 + if app.session.member_id then 62.49 + if area.delegation_info.first_trustee_id then 62.50 + local member = Member:by_id(area.delegation_info.first_trustee_id) 62.51 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "forward" } 62.52 + execute.view{ 62.53 + module = "member_image", 62.54 + view = "_show", 62.55 + params = { 62.56 + member = member, 62.57 + image_type = "avatar", 62.58 + show_dummy = true 62.59 + } 62.60 + } 62.61 + end 62.62 + 62.63 + if not config.disable_delegations then 62.64 + if area.delegation_info.own_delegation_scope == "unit" then 62.65 + ui.link { 62.66 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 62.67 + module = "delegation", view = "show", params = { 62.68 + unit_id = area.unit_id, 62.69 + }, 62.70 + content = _("change/revoke delegation of organizational unit") 62.71 + } 62.72 + slot.put(" ") 62.73 + end 62.74 + if area.delegation_info.own_delegation_scope == nil then 62.75 + local text 62.76 + if config.single_area_id then 62.77 + text = _"choose delegatee" 62.78 + else 62.79 + text = _"choose subject area delegatee" 62.80 + end 62.81 + ui.link { 62.82 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 62.83 + module = "delegation", view = "show", params = { 62.84 + area_id = area.id 62.85 + }, 62.86 + content = function() 62.87 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "forward" } 62.88 + ui.tag{ content = text } 62.89 + end 62.90 + } 62.91 + slot.put(" ") 62.92 + elseif area.delegation_info.own_delegation_scope == "area" then 62.93 + local text 62.94 + if config.single_area_id then 62.95 + text = _"change/revoke delegation" 62.96 + else 62.97 + text = _"change/revoke area delegation" 62.98 + end 62.99 + ui.link { 62.100 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 62.101 + module = "delegation", view = "show", params = { 62.102 + area_id = area.id 62.103 + }, 62.104 + content = text 62.105 + } 62.106 + slot.put(" ") 62.107 + else 62.108 + ui.link { 62.109 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 62.110 + module = "delegation", view = "show", params = { 62.111 + area_id = area.id 62.112 + }, 62.113 + content = _"change/revoke delegation only for this subject area" 62.114 + } 62.115 + slot.put(" ") 62.116 + end 62.117 + end 62.118 + end 62.119 + if not config.voting_only and app.session.member_id and app.session.member:has_initiative_right_for_unit_id ( area.unit_id ) then 62.120 + ui.link { 62.121 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 62.122 + module = "initiative", view = "new", 62.123 + params = { area_id = area.id }, 62.124 + content = function() 62.125 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "add" } 62.126 + ui.tag{ content = _"new issue" } 62.127 + end 62.128 + } 62.129 + end 62.130 + end } 62.131 + end 62.132 + end } 62.133 +elseif unit then 62.134 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 62.135 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 62.136 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = unit.name } 62.137 + end } 62.138 + if unit.description and #(unit.description) > 0 then 62.139 + ui.container{ attr = { class = "mdl-card__supporting-text mdl-card--border" }, content = unit.description } 62.140 + end 62.141 + --ui.container{ attr = { class = "mdl-card__actions mdl-card--border" }, content = function() 62.142 + --end } 62.143 + end } 62.144 +end 62.145 +
63.1 --- a/app/main/index/_lang_chooser.lua Thu Jun 23 03:30:57 2016 +0200 63.2 +++ b/app/main/index/_lang_chooser.lua Sun Jul 15 14:07:29 2018 +0200 63.3 @@ -0,0 +1,31 @@ 63.4 +ui.tag{ tag = "button", attr = { id = "lf-lang-menu", class = "mdl-button mdl-js-button float-right" }, content = function() 63.5 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "language" } 63.6 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "translate" } 63.7 +end } 63.8 + 63.9 +ui.tag { tag = "ul", attr = { class = "mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect", ["data-mdl-for"] = "lf-lang-menu" }, content = function() 63.10 + for i, lang in ipairs(config.enabled_languages) do 63.11 + local langcode 63.12 + locale.do_with({ lang = lang }, function() 63.13 + langcode = _("[Name of Language]") 63.14 + end) 63.15 + ui.tag{ tag = "li", attr = { class = "mdl-menu__item" }, content = function() 63.16 + ui.link{ 63.17 + content = langcode, 63.18 + attr = { class = "mdl-menu__link" }, 63.19 + module = "index", 63.20 + action = "set_lang", 63.21 + params = { lang = lang }, 63.22 + routing = { 63.23 + default = { 63.24 + mode = "redirect", 63.25 + module = request.get_module(), 63.26 + view = request.get_view(), 63.27 + id = request.get_id_string(), 63.28 + params = request.get_param_strings() 63.29 + } 63.30 + } 63.31 + } 63.32 + end } 63.33 + end 63.34 +end }
64.1 --- a/app/main/index/_sidebar_members.lua Thu Jun 23 03:30:57 2016 +0200 64.2 +++ b/app/main/index/_sidebar_members.lua Sun Jul 15 14:07:29 2018 +0200 64.3 @@ -1,38 +1,60 @@ 64.4 +local limit = 50 64.5 + 64.6 if not app.session:has_access("all_pseudonymous") then 64.7 return 64.8 end 64.9 64.10 -local member_count = MemberCount:get() 64.11 +local unit_id = request.get_param{ name = "unit" } 64.12 +if unit_id == "all" then 64.13 + unit_id = nil 64.14 +end 64.15 64.16 -if not member_count then 64.17 - return 64.18 +local selector = Member:new_selector() 64.19 + :add_where("active") 64.20 + :add_order_by("last_login DESC NULLS LAST, id DESC") 64.21 + 64.22 +if unit_id then 64.23 + selector:join("privilege", nil, "privilege.member_id = member.id") 64.24 + selector:add_where{ "privilege.unit_id = ?", unit_id } 64.25 end 64.26 64.27 -ui.sidebar ( "tab-members", function () 64.28 - ui.sidebarHead( function() 64.29 - ui.heading { 64.30 - level = 2, 64.31 - content = _("Registered members (#{count})", { count = member_count }) 64.32 +local member_count = selector:count() 64.33 + 64.34 +selector:limit(limit) 64.35 + 64.36 + 64.37 +ui.container{ attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 64.38 + 64.39 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 64.40 + local text 64.41 + if unit_id then 64.42 + text = _("Eligible members (#{count})", { count = selector:count() }) 64.43 + else 64.44 + text = _("Registered members (#{count})", { count = selector:count() }) 64.45 + end 64.46 + ui.container{ 64.47 + attr = { class = "mdl-card__title-text" }, 64.48 + content = text 64.49 } 64.50 - end ) 64.51 - 64.52 - local selector = Member:new_selector() 64.53 - :add_where("active") 64.54 - :add_order_by("last_login DESC NULLS LAST, id DESC") 64.55 - :limit(50) 64.56 + end } 64.57 64.58 - execute.view { 64.59 - module = 'member', view = '_list', params = { 64.60 - members_selector = selector, 64.61 - no_filter = true, no_paginate = true, 64.62 - member_class = "sidebarRow sidebarRowNarrow" 64.63 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 64.64 + execute.view { 64.65 + module = 'member', view = '_list', params = { 64.66 + members_selector = selector, 64.67 + no_filter = true, no_paginate = true, 64.68 + member_class = "sidebarRow sidebarRowNarrow" 64.69 + } 64.70 } 64.71 - } 64.72 + end } 64.73 64.74 - ui.link { 64.75 - attr = { class = "sidebarRow moreLink" }, 64.76 - text = _"Show full member list", 64.77 - module = "member", view = "list" 64.78 - } 64.79 - 64.80 -end ) 64.81 + if member_count > limit then 64.82 + ui.container{ attr = { class = "mdl-card__actions mdl-card--border" }, content = function() 64.83 + ui.link { 64.84 + attr = { class = "mdl-button mdl-js-button" }, 64.85 + text = _"Show full member list", 64.86 + module = "member", view = "list" 64.87 + } 64.88 + end } 64.89 + end 64.90 +end }
65.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 65.2 +++ b/app/main/index/_sidebar_motd.lua Sun Jul 15 14:07:29 2018 +0200 65.3 @@ -0,0 +1,26 @@ 65.4 +if app.session.member and config.motd_intern or config.motd_extern then 65.5 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 65.6 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 65.7 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Message of the day" } 65.8 + end } 65.9 + ui.container{ attr = { class = "mdl-card__content what-can-i-do-here" }, content = function() 65.10 + if app.session.member and config.motd_intern then 65.11 + ui.container{ 65.12 + attr = { class = "draft motd" }, 65.13 + content = function() 65.14 + slot.put(config.motd_intern) 65.15 + end 65.16 + } 65.17 + end 65.18 + if config.motd_extern then 65.19 + ui.container{ 65.20 + attr = { class = "draft motd" }, 65.21 + content = function() 65.22 + slot.put(config.motd_extern) 65.23 + end 65.24 + } 65.25 + end 65.26 + end } 65.27 + end } 65.28 + slot.put("<br />") 65.29 +end
66.1 --- a/app/main/index/_sidebar_motd_intern.lua Thu Jun 23 03:30:57 2016 +0200 66.2 +++ b/app/main/index/_sidebar_motd_intern.lua Sun Jul 15 14:07:29 2018 +0200 66.3 @@ -1,10 +1,15 @@ 66.4 if config.motd_intern then 66.5 - slot.select("motd", function() 66.6 - ui.container{ 66.7 - attr = { class = "wiki motd" }, 66.8 - content = function() 66.9 - slot.put(config.motd_intern) 66.10 - end 66.11 - } 66.12 - end ) 66.13 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 66.14 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 66.15 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Message of the day" } 66.16 + end } 66.17 + ui.container{ attr = { class = "mdl-card__content what-can-i-do-here" }, content = function() 66.18 + ui.container{ 66.19 + attr = { class = "wiki motd" }, 66.20 + content = function() 66.21 + slot.put(config.motd_intern) 66.22 + end 66.23 + } 66.24 + end } 66.25 + end } 66.26 end
67.1 --- a/app/main/index/_sidebar_motd_public.lua Thu Jun 23 03:30:57 2016 +0200 67.2 +++ b/app/main/index/_sidebar_motd_public.lua Sun Jul 15 14:07:29 2018 +0200 67.3 @@ -1,10 +1,7 @@ 67.4 if config.motd_public then 67.5 - slot.select("motd", function() 67.6 - ui.container{ 67.7 - attr = { class = "wiki motd" }, 67.8 - content = function() 67.9 - slot.put(config.motd_public) 67.10 - end 67.11 - } 67.12 - end ) 67.13 + ui.container{ attr = { class = "mdl-special-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 67.14 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 67.15 + slot.put(config.motd_public) 67.16 + end } 67.17 + end } 67.18 end
68.1 --- a/app/main/index/_sidebar_notifications.lua Thu Jun 23 03:30:57 2016 +0200 68.2 +++ b/app/main/index/_sidebar_notifications.lua Sun Jul 15 14:07:29 2018 +0200 68.3 @@ -7,6 +7,18 @@ 68.4 } 68.5 end 68.6 68.7 +local agents = Agent:new_selector() 68.8 + :add_where{ "controller_id = ?", app.session.member_id } 68.9 + :add_where{ "accepted ISNULL" } 68.10 + :exec() 68.11 +for i, agent in ipairs(agents) do 68.12 + local member = Member:by_id(agent.controlled_id) 68.13 + notification_links[#notification_links+1] = { 68.14 + module = "agent", view = "show", params = { controlled_id = agent.controlled_id }, 68.15 + text = _("Account access invitation from '#{member_name}'", { member_name = member.name }) 68.16 + } 68.17 +end 68.18 + 68.19 if config.check_delegations_interval_soft then 68.20 local member = Member:new_selector() 68.21 :add_where({ "id = ?", app.session.member_id }) 68.22 @@ -99,28 +111,19 @@ 68.23 } 68.24 end 68.25 68.26 -local mode = param.get("mode") or "view" 68.27 - 68.28 if #notification_links > 0 then 68.29 - if mode == "link" then 68.30 - slot.select("notification", function () 68.31 - local text = _"notifications" 68.32 - ui.link { 68.33 - attr = { class = "notifications", title = text }, 68.34 - module = "index", view = "notifications", 68.35 - content = function () 68.36 - ui.image { attr = { class = "icon", alt = text }, static = "icons/48/bell.png" } 68.37 - ui.tag { attr = { class = "count" }, content = #notification_links } 68.38 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 68.39 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 68.40 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Notifications" } 68.41 + end } 68.42 + ui.container{ attr = { class = "mdl-card__content what-can-i-do-here" }, content = function() 68.43 + ui.tag{ tag = "ul", attr = { class = "ul" }, content = function() 68.44 + for i, notification_link in ipairs(notification_links) do 68.45 + ui.tag{ tag = "li", content = function() 68.46 + ui.link(notification_link) 68.47 + end } 68.48 end 68.49 - } 68.50 - end ) 68.51 - elseif mode == "view" then 68.52 - ui.tag{ tag = "ul", attr = { class = "ul" }, content = function() 68.53 - for i, notification_link in ipairs(notification_links) do 68.54 - ui.tag{ tag = "li", content = function() 68.55 - ui.link(notification_link) 68.56 - end } 68.57 - end 68.58 + end } 68.59 end } 68.60 - end 68.61 + end } 68.62 end
69.1 --- a/app/main/index/_sidebar_units.lua Thu Jun 23 03:30:57 2016 +0200 69.2 +++ b/app/main/index/_sidebar_units.lua Sun Jul 15 14:07:29 2018 +0200 69.3 @@ -1,3 +1,5 @@ 69.4 +if true then return end 69.5 + 69.6 local member = param.get ( "member", "table" ) 69.7 local units 69.8 if member then 69.9 @@ -30,14 +32,6 @@ 69.10 :add_where{ "area.active" } 69.11 :add_order_by("area.name") 69.12 69.13 - if member then 69.14 - areas_selector:left_join ( 69.15 - "membership", nil, 69.16 - { "membership.area_id = area.id AND membership.member_id = ?", member.id } 69.17 - ) 69.18 - areas_selector:add_field("membership.member_id NOTNULL", "subscribed", { "grouped" }) 69.19 - end 69.20 - 69.21 local areas = areas_selector:exec() 69.22 if member then 69.23 areas:load_delegation_info_once_for_member_id(member.id) 69.24 @@ -78,21 +72,9 @@ 69.25 69.26 ui.tag { tag = "div", attr = { class = "areas areas-" .. unit.id }, content = function () 69.27 69.28 - local any_subscribed = false 69.29 - local subscribed_count = 0 69.30 for i, area in ipairs(areas) do 69.31 69.32 - local class = "sidebarRow" 69.33 - class = class .. (not area.subscribed and " disabled" or "") 69.34 - 69.35 - ui.tag { tag = "div", attr = { class = class }, content = function () 69.36 - 69.37 - if area.subscribed then 69.38 - local text = _"subscribed" 69.39 - ui.image { attr = { class = "icon16 star", alt = text, title = text }, static = "icons/48/star.png" } 69.40 - any_subscribed = true 69.41 - subscribed_count = subscribed_count +1 69.42 - end 69.43 + ui.tag { tag = "div", attr = { class = "sidebarRow" }, content = function () 69.44 69.45 if member then 69.46 local delegation = Delegation:by_pk(member.id, nil, area.id, nil) 69.47 @@ -121,31 +103,6 @@ 69.48 69.49 end } 69.50 end 69.51 - if subscribed_count < #areas then 69.52 - local text 69.53 - if any_subscribed then 69.54 - text = _"show other subject areas" 69.55 - else 69.56 - text = _"show subject areas" 69.57 - end 69.58 - ui.script{ script = "$('.areas-" .. unit.id .. "').addClass('folded');" } 69.59 - ui.tag { tag = "div", attr = { class = "sidebarRow moreLink whenfolded" }, content = function () 69.60 - ui.link { 69.61 - attr = { 69.62 - onclick = "$('.areas-" .. unit.id .. "').removeClass('folded'); return false;" 69.63 - }, 69.64 - text = text 69.65 - } 69.66 - end } 69.67 - ui.tag { tag = "div", attr = { class = "sidebarRow moreLink whenunfolded" }, content = function () 69.68 - ui.link { 69.69 - attr = { 69.70 - onclick = "$('.areas-" .. unit.id .. "').addClass('folded'); return false;" 69.71 - }, 69.72 - text = _"collapse subject areas" 69.73 - } 69.74 - end } 69.75 - end 69.76 end } 69.77 end 69.78 end )
70.1 --- a/app/main/index/_sidebar_whatcanido.lua Thu Jun 23 03:30:57 2016 +0200 70.2 +++ b/app/main/index/_sidebar_whatcanido.lua Sun Jul 15 14:07:29 2018 +0200 70.3 @@ -1,84 +1,116 @@ 70.4 -ui.sidebar ( "tab-whatcanido", function () 70.5 - 70.6 - ui.sidebarHeadWhatCanIDo() 70.7 +ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 70.8 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 70.9 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"What can I do here?" } 70.10 + end } 70.11 + ui.container{ attr = { class = "what-can-i-do-here" }, content = function() 70.12 70.13 - if app.session.member then 70.14 - ui.sidebarSection( function() 70.15 - ui.heading { level = 3, content = _"I want to know whats going on" } 70.16 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.17 - ui.tag { tag = "li", content = _"take a look on the issues (see left)" } 70.18 - ui.tag { tag = "li", content = _"by default only those issues are shown, for which your are eligible to participate (change filters on top of the list)" } 70.19 - end } 70.20 - end ) 70.21 - ui.sidebarSection( function() 70.22 - ui.heading { level = 3, content = _"I want to stay informed" } 70.23 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.24 - ui.tag { tag = "li", content = function () 70.25 - ui.tag{ content = _"check your " } 70.26 - ui.link{ 70.27 - module = "member", view = "settings_notification", 70.28 - params = { return_to = "home" }, 70.29 - text = _"notifications settings" 70.30 - } 70.31 - end } 70.32 - ui.tag { tag = "li", content = function () 70.33 - ui.tag{ content = _"subscribe subject areas or add your interested to issues and you will be notified about changes (follow the instruction on the area or issue page)" } 70.34 - end } 70.35 - end } 70.36 - end ) 70.37 - ui.sidebarSection( function() 70.38 - ui.heading { level = 3, content = _"I want to start a new initiative" } 70.39 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.40 - ui.tag { tag = "li", content = _"open the appropriate subject area for your issue and follow the instruction on that page." } 70.41 - end } 70.42 - end ) 70.43 - ui.sidebarSection( function() 70.44 - ui.heading { level = 3, content = _"I want to delegate my vote" } 70.45 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.46 - ui.tag { tag = "li", content = _"open the organizational unit, subject area or issue you like to delegate and follow the instruction on that page." } 70.47 - end } 70.48 - end ) 70.49 - ui.sidebarSection( function() 70.50 - ui.heading { level = 3, content = _"I want to take a look at other organizational units" } 70.51 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.52 - ui.tag { tag = "li", content = function () 70.53 - ui.link{ 70.54 - module = "unit", view = "list", 70.55 - text = _"show all units" 70.56 - } 70.57 - end } 70.58 - end } 70.59 - end ) 70.60 - if config.download_dir then 70.61 - ui.sidebarSection( function() 70.62 - ui.heading { level = 3, content = _"I want to download all data" } 70.63 + if app.session.member then 70.64 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 70.65 + ui.tag{ content = _"I want to know whats going on" } 70.66 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.67 + ui.tag { tag = "li", content = _"take a look on the issues (see right)" } 70.68 + ui.tag { tag = "li", content = _"by default only those issues are shown, for which your are eligible to participate (change filters on top of the list)" } 70.69 + end } 70.70 + end } 70.71 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 70.72 + ui.tag{ content = _"I want to stay informed" } 70.73 ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.74 ui.tag { tag = "li", content = function () 70.75 + ui.tag{ content = _"check your " } 70.76 ui.link{ 70.77 - module = "index", view = "download", 70.78 - text = _"download database" 70.79 + module = "member", view = "settings_notification", 70.80 + params = { return_to = "home" }, 70.81 + text = _"notifications settings" 70.82 } 70.83 end } 70.84 + if not config.voting_only then 70.85 + ui.tag { tag = "li", content = function () 70.86 + ui.tag{ content = _"subscribe subject areas or add your interested to issues and you will be notified about changes (follow the instruction on the area or issue page)" } 70.87 + end } 70.88 + end 70.89 + end } 70.90 + end } 70.91 + if app.session.member.has_voting_right then 70.92 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 70.93 + ui.tag{ content = _"I want to vote" } 70.94 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.95 + ui.tag { tag = "li", content = _"check the issues on the right, and click on 'Vote now' to vote on an issue which is in voting phase." } 70.96 + end } 70.97 + end } 70.98 + if not config.disable_delegations then 70.99 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 70.100 + ui.tag{ content = _"I want to delegate my vote" } 70.101 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.102 + ui.tag { tag = "li", content = _"open the organizational unit, subject area or issue you like to delegate and follow the instruction on that page." } 70.103 + end } 70.104 + end } 70.105 + end 70.106 + end 70.107 + if not config.voting_only and app.session.member.has_initiative_right then 70.108 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 70.109 + ui.tag{ content = _"I want to start a new initiative" } 70.110 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.111 + ui.tag { tag = "li", content = _"open the appropriate subject area for your issue and follow the instruction on that page." } 70.112 + end } 70.113 + end } 70.114 + end 70.115 + if not config.single_unit_id then 70.116 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 70.117 + ui.tag{ content = _"I want to take a look at other organizational units" } 70.118 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.119 + ui.tag { tag = "li", content = function () 70.120 + ui.link{ 70.121 + module = "unit", view = "list", 70.122 + text = _"show all units" 70.123 + } 70.124 + end } 70.125 + end } 70.126 + end } 70.127 + end 70.128 + if config.download_dir then 70.129 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 70.130 + ui.tag{ content = _"I want to download all data" } 70.131 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.132 + ui.tag { tag = "li", content = function () 70.133 + ui.link{ 70.134 + module = "index", view = "download", 70.135 + text = _"download database" 70.136 + } 70.137 + end } 70.138 + end } 70.139 + end } 70.140 + end 70.141 + end 70.142 + if not app.session.member then 70.143 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 70.144 + ui.tag{ content = _"You are not entitled to vote in this unit" } 70.145 + ui.tag{ tag = "ul", content = function() 70.146 + ui.tag{ tag = "li", content = function() 70.147 + ui.link{ module = "index", view = "login", content = _"Login" } 70.148 + end } 70.149 + end } 70.150 + end } 70.151 + end 70.152 + if not config.voting_only then 70.153 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 70.154 + ui.tag{ content = _"I want to learn more about LiquidFeedback" } 70.155 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.156 + ui.tag { tag = "li", content = function() 70.157 + ui.link { module = "help", view = "introduction", content = _"structured discussion" } 70.158 + end } 70.159 + ui.tag { tag = "li", content = function() 70.160 + ui.link { module = "help", view = "introduction", content = _"4 phases of a decision" } 70.161 + end } 70.162 + if not config.disable_delegations then 70.163 + ui.tag { tag = "li", content = function() 70.164 + ui.link { module = "help", view = "introduction", content = _"vote delegation" } 70.165 + end } 70.166 + end 70.167 + ui.tag { tag = "li", content = function() 70.168 + ui.link { module = "help", view = "introduction", content = _"preference voting" } 70.169 + end } 70.170 end } 70.171 - end ) 70.172 + end } 70.173 end 70.174 - end 70.175 - ui.sidebarSection( function() 70.176 - ui.heading { level = 3, content = _"I want to learn more about LiquidFeedback" } 70.177 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 70.178 - ui.tag { tag = "li", content = function() 70.179 - ui.link { module = "help", view = "introduction", content = _"structured discussion" } 70.180 - end } 70.181 - ui.tag { tag = "li", content = function() 70.182 - ui.link { module = "help", view = "introduction", content = _"4 phases of a decision" } 70.183 - end } 70.184 - ui.tag { tag = "li", content = function() 70.185 - ui.link { module = "help", view = "introduction", content = _"vote delegation" } 70.186 - end } 70.187 - ui.tag { tag = "li", content = function() 70.188 - ui.link { module = "help", view = "introduction", content = _"preference voting" } 70.189 - end } 70.190 - end } 70.191 - end ) 70.192 - 70.193 -end ) 70.194 + end } 70.195 +end }
71.1 --- a/app/main/index/about.lua Thu Jun 23 03:30:57 2016 +0200 71.2 +++ b/app/main/index/about.lua Sun Jul 15 14:07:29 2018 +0200 71.3 @@ -1,103 +1,104 @@ 71.4 ui.title(_"About site") 71.5 71.6 -ui.section( function() 71.7 +ui.grid{ content = function() 71.8 + ui.cell_full{ content = function() 71.9 71.10 - ui.sectionHead( function() 71.11 - ui.heading{ level = 1, content = _"About site" } 71.12 - end ) 71.13 - 71.14 - ui.sectionRow( function() 71.15 + ui.container { attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 71.16 71.17 - ui.heading{ level = 3, content = _"This service is provided by:" } 71.18 - slot.put(config.app_service_provider) 71.19 - 71.20 - end ) 71.21 - 71.22 - ui.sectionRow( function() 71.23 - 71.24 - ui.heading{ level = 3, content = _"This service is provided using the following software components:" } 71.25 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 71.26 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"This service is provided by:" } 71.27 + end } 71.28 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 71.29 + slot.put(config.app_service_provider) 71.30 + end } 71.31 + end } 71.32 + 71.33 + ui.container { attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 71.34 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 71.35 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"This service is provided using the following software components:" } 71.36 + end } 71.37 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 71.38 71.39 - local tmp = { 71.40 - { 71.41 - name = "LiquidFeedback Frontend", 71.42 - url = "http://www.public-software-group.org/liquid_feedback", 71.43 - version = config.app_version, 71.44 - license = "MIT/X11", 71.45 - license_url = "http://www.public-software-group.org/licenses" 71.46 - }, 71.47 - { 71.48 - name = "LiquidFeedback Core", 71.49 - url = "http://www.public-software-group.org/liquid_feedback", 71.50 - version = db:query("SELECT * from liquid_feedback_version;")[1].string, 71.51 - license = "MIT/X11", 71.52 - license_url = "http://www.public-software-group.org/licenses" 71.53 - }, 71.54 - { 71.55 - name = "WebMCP", 71.56 - url = "http://www.public-software-group.org/webmcp", 71.57 - version = WEBMCP_VERSION or _WEBMCP_VERSION, 71.58 - license = "MIT/X11", 71.59 - license_url = "http://www.public-software-group.org/licenses" 71.60 - } 71.61 - } 71.62 - 71.63 - if _MOONBRIDGE_VERSION then 71.64 - tmp[#tmp+1] = { 71.65 - name = "Moonbridge", 71.66 - url = "http://www.public-software-group.org/moonbridge", 71.67 - version = _MOONBRIDGE_VERSION, 71.68 - license = "MIT/X11", 71.69 - license_url = "http://www.public-software-group.org/licenses" 71.70 - } 71.71 - end 71.72 - 71.73 - tmp[#tmp+1] = { 71.74 - name = "Lua", 71.75 - url = "http://www.lua.org", 71.76 - version = _VERSION:gsub("Lua ", ""), 71.77 - license = "MIT/X11", 71.78 - license_url = "http://www.lua.org/license.html" 71.79 - } 71.80 - 71.81 - tmp[#tmp+1] = { 71.82 - name = "PostgreSQL", 71.83 - url = "http://www.postgresql.org/", 71.84 - version = db:query("SELECT version();")[1].version:gsub("PostgreSQL ", ""):gsub("on.*", ""), 71.85 - license = "PostgreSQL License", 71.86 - license_url = "http://www.postgresql.org/about/licence" 71.87 - } 71.88 + local tmp = { 71.89 + { 71.90 + name = "LiquidFeedback Frontend", 71.91 + url = "http://www.public-software-group.org/liquid_feedback", 71.92 + version = config.app_version, 71.93 + license = "MIT/X11", 71.94 + license_url = "http://www.public-software-group.org/licenses" 71.95 + }, 71.96 + { 71.97 + name = "LiquidFeedback Core", 71.98 + url = "http://www.public-software-group.org/liquid_feedback", 71.99 + version = db:query("SELECT * from liquid_feedback_version;")[1].string, 71.100 + license = "MIT/X11", 71.101 + license_url = "http://www.public-software-group.org/licenses" 71.102 + }, 71.103 + { 71.104 + name = "WebMCP", 71.105 + url = "http://www.public-software-group.org/webmcp", 71.106 + version = WEBMCP_VERSION or _WEBMCP_VERSION, 71.107 + license = "MIT/X11", 71.108 + license_url = "http://www.public-software-group.org/licenses" 71.109 + } 71.110 + } 71.111 + 71.112 + if _MOONBRIDGE_VERSION then 71.113 + tmp[#tmp+1] = { 71.114 + name = "Moonbridge", 71.115 + url = "http://www.public-software-group.org/moonbridge", 71.116 + version = _MOONBRIDGE_VERSION, 71.117 + license = "MIT/X11", 71.118 + license_url = "http://www.public-software-group.org/licenses" 71.119 + } 71.120 + end 71.121 + 71.122 + tmp[#tmp+1] = { 71.123 + name = "Lua", 71.124 + url = "http://www.lua.org", 71.125 + version = _VERSION:gsub("Lua ", ""), 71.126 + license = "MIT/X11", 71.127 + license_url = "http://www.lua.org/license.html" 71.128 + } 71.129 + 71.130 + tmp[#tmp+1] = { 71.131 + name = "PostgreSQL", 71.132 + url = "http://www.postgresql.org/", 71.133 + version = db:query("SELECT version();")[1].version:gsub("PostgreSQL ", ""):gsub("on.*", ""), 71.134 + license = "PostgreSQL License", 71.135 + license_url = "http://www.postgresql.org/about/licence" 71.136 + } 71.137 71.138 - ui.list{ 71.139 - records = tmp, 71.140 - columns = { 71.141 - { 71.142 - content = function(record) 71.143 - ui.link{ 71.144 - content = record.name, 71.145 - external = record.url 71.146 + ui.list{ 71.147 + records = tmp, 71.148 + columns = { 71.149 + { 71.150 + content = function(record) 71.151 + ui.link{ 71.152 + content = record.name, 71.153 + external = record.url 71.154 + } 71.155 + end 71.156 + }, 71.157 + { 71.158 + content = function(record) ui.field.text{ value = record.version } end 71.159 + }, 71.160 + { 71.161 + content = function(record) 71.162 + ui.link{ 71.163 + content = record.license, 71.164 + external = record.license_url 71.165 + } 71.166 + end 71.167 + 71.168 } 71.169 - end 71.170 - }, 71.171 - { 71.172 - content = function(record) ui.field.text{ value = record.version } end 71.173 - }, 71.174 - { 71.175 - content = function(record) 71.176 - ui.link{ 71.177 - content = record.license, 71.178 - external = record.license_url 71.179 - } 71.180 - end 71.181 + } 71.182 + } 71.183 71.184 - } 71.185 - } 71.186 - } 71.187 - 71.188 - end ) 71.189 - 71.190 - ui.sectionRow( function() 71.191 - ui.heading{ level = 3, content = "3rd party license information:" } 71.192 - slot.put('Some of the icons used in Liquid Feedback are from <a href="http://www.famfamfam.com/lab/icons/silk/">Silk icon set 1.3</a> by Mark James. His work is licensed under a <a href="http://creativecommons.org/licenses/by/2.5/">Creative Commons Attribution 2.5 License.</a>') 71.193 - 71.194 - end ) 71.195 -end ) 71.196 + slot.put("<br />") 71.197 + ui.container{ content = "3rd party license information:" } 71.198 + slot.put('Some of the icons used in Liquid Feedback are from <a href="http://www.famfamfam.com/lab/icons/silk/">Silk icon set 1.3</a> by Mark James. His work is licensed under a <a href="http://creativecommons.org/licenses/by/2.5/">Creative Commons Attribution 2.5 License.</a>') 71.199 + end } 71.200 + end } 71.201 + end } 71.202 +end }
72.1 --- a/app/main/index/document.lua Thu Jun 23 03:30:57 2016 +0200 72.2 +++ b/app/main/index/document.lua Sun Jul 15 14:07:29 2018 +0200 72.3 @@ -1,5 +1,5 @@ 72.4 if not config.document_dir then 72.5 - error("feature not enabled") 72.6 + return execute.view { module = "index", view = "404" } 72.7 end 72.8 72.9 slot.put_into("title", _"Download documents") 72.10 @@ -54,4 +54,4 @@ 72.11 end 72.12 } 72.13 } 72.14 -} 72.15 \ No newline at end of file 72.16 +}
73.1 --- a/app/main/index/document_file.lua Thu Jun 23 03:30:57 2016 +0200 73.2 +++ b/app/main/index/document_file.lua Sun Jul 15 14:07:29 2018 +0200 73.3 @@ -1,5 +1,5 @@ 73.4 if not config.document_dir then 73.5 - error("feature not enabled") 73.6 + return execute.view { module = "index", view = "404" } 73.7 end 73.8 73.9 local filename = param.get("filename")
74.1 --- a/app/main/index/download.lua Thu Jun 23 03:30:57 2016 +0200 74.2 +++ b/app/main/index/download.lua Sun Jul 15 14:07:29 2018 +0200 74.3 @@ -1,5 +1,5 @@ 74.4 if not config.download_dir then 74.5 - error("feature not enabled") 74.6 + return execute.view { module = "index", view = "404" } 74.7 end 74.8 74.9 local file_list = extos.listdir(config.download_dir) 74.10 @@ -58,4 +58,4 @@ 74.11 } 74.12 } 74.13 end) 74.14 -end) 74.15 \ No newline at end of file 74.16 +end)
75.1 --- a/app/main/index/download_file.lua Thu Jun 23 03:30:57 2016 +0200 75.2 +++ b/app/main/index/download_file.lua Sun Jul 15 14:07:29 2018 +0200 75.3 @@ -1,5 +1,5 @@ 75.4 if not config.download_dir then 75.5 - error("feature not enabled") 75.6 + return execute.view { module = "index", view = "404" } 75.7 end 75.8 75.9 local filename = param.get("filename")
76.1 --- a/app/main/index/index.lua Thu Jun 23 03:30:57 2016 +0200 76.2 +++ b/app/main/index/index.lua Sun Jul 15 14:07:29 2018 +0200 76.3 @@ -1,114 +1,88 @@ 76.4 -local function getIssuesSelector() 76.5 - return Issue:new_selector() 76.6 - :add_order_by([[ 76.7 - coalesce( 76.8 - issue.fully_frozen + issue.voting_time, 76.9 - issue.half_frozen + issue.verification_time, 76.10 - issue.accepted + issue.discussion_time, 76.11 - issue.created + issue.max_admission_time 76.12 - ) - now() 76.13 - ]]) 76.14 +if not app.session:has_access("anonymous") then 76.15 + slot.put("<br /> Closed user group, please login.<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br />") 76.16 + return 76.17 +end 76.18 + 76.19 +local unit_id = request.get_param{ name = "unit" } 76.20 +local area_id = request.get_param{ name = "area" } 76.21 + 76.22 +if unit_id == "all" then 76.23 + unit_id = nil 76.24 +end 76.25 + 76.26 +if area_id == "all" then 76.27 + area_id = nil 76.28 +end 76.29 + 76.30 +local unit 76.31 +local area 76.32 + 76.33 +if unit_id then 76.34 + unit = Unit:by_id(unit_id) 76.35 +end 76.36 + 76.37 +if area_id then 76.38 + area = Area:by_id(area_id) 76.39 end 76.40 76.41 ---[[ 76.42 -ui.title( function () 76.43 - ui.link { attr = { class = "home" }, module = "index", view = "index", text = _"Home" } 76.44 -end) 76.45 ---]] 76.46 - 76.47 -ui.title() 76.48 +ui.grid{ content = function() 76.49 + ui.cell_main{ content = function() 76.50 76.51 -if false then 76.52 -slot.select ( "tabs", function () 76.53 - 76.54 - ui.tag { 76.55 - attr = { onclick = "showTab(0);" }, 76.56 - content = "units", 76.57 - } 76.58 - slot.put ( " " ) 76.59 - ui.tag { 76.60 - attr = { onclick = "showTab(1);" }, 76.61 - content = "timeline" 76.62 - } 76.63 - slot.put ( " " ) 76.64 - ui.tag { 76.65 - attr = { onclick = "showTab(2);" }, 76.66 - content = "what" 76.67 - } 76.68 - slot.put ( " " ) 76.69 - ui.tag { 76.70 - attr = { onclick = "showTab(3);" }, 76.71 - content = "members" 76.72 - } 76.73 - 76.74 -end ) 76.75 + execute.view{ module = "index", view = "_sidebar_motd_public" } 76.76 + 76.77 + execute.view{ module = "issue", view = "_list" } 76.78 + end } 76.79 76.80 -ui.script { script = [[ 76.81 - 76.82 - var tabs = ["tab1", "main", "tab2", "tab3"] 76.83 - var currentId; 76.84 - 76.85 - function showTab(id) { 76.86 - var tabId = tabs[id]; 76.87 - $('.tab').hide(); 76.88 - $('.main').hide(); 76.89 - $('.' + tabId).show(); 76.90 - currentId = id; 76.91 - }; 76.92 - 76.93 - showTab(0); 76.94 - 76.95 - $(function(){ 76.96 - // Bind the swipeHandler callback function to the swipe event on div.box 76.97 - $( "body" ).on( "swiperight", function swipeHandler( event ) { 76.98 - newId = currentId - 1; 76.99 - if (newId < 0) return; 76.100 - showTab(newId); 76.101 - } ) 76.102 - $( "body" ).on( "swipeleft", function swipeHandler( event ) { 76.103 - newId = currentId + 1; 76.104 - if (newId > tabs.length - 1) return; 76.105 - showTab(newId); 76.106 - } ) 76.107 - }); 76.108 - 76.109 -]]} 76.110 -end 76.111 - 76.112 + ui.cell_sidebar{ content = function() 76.113 + execute.view{ module = "index", view = "_sidebar_motd" } 76.114 + if app.session.member then 76.115 + execute.view{ module = "index", view = "_sidebar_notifications" } 76.116 + end 76.117 + if config.firstlife then 76.118 + ui.container{ attr = { class = "map mdl-special-card mdl-shadow--2dp pos-before-main" }, content = function() 76.119 + ui.tag{ tag = "iframe", attr = { src = config.firstlife.areaviewer_url .. "?" .. config.firstlife.coordinates .. "&domain=" .. request.get_absolute_baseurl(), class = "map" }, content = "" } 76.120 + end } 76.121 + end 76.122 + if config.map then 76.123 + local initiatives = Initiative:new_selector():exec() 76.124 + local geo_objects = {} 76.125 + for i, initiative in ipairs(initiatives) do 76.126 + if initiative.location and initiative.location.coordinates then 76.127 + local geo_object = { 76.128 + lon = initiative.location.coordinates[1], 76.129 + lat = initiative.location.coordinates[2], 76.130 + label = "i" .. initiative.id, 76.131 + description = slot.use_temporary(function() 76.132 + ui.link{ module = "initiative", view = "show", id = initiative.id, text = initiative.display_name } 76.133 + end), 76.134 + type = "initiative" 76.135 + } 76.136 + table.insert(geo_objects, geo_object) 76.137 + end 76.138 + end 76.139 + if ontomap_get_instances then 76.140 + local instances = ontomap_get_instances() 76.141 + for i, instance in ipairs(instances) do 76.142 + table.insert(geo_objects, instance) 76.143 + end 76.144 + end 76.145 + ui.container{ attr = { class = "map mdl-special-card mdl-shadow--2dp pos-before-main" }, content = function() 76.146 + ui.map(geo_objects) 76.147 + end } 76.148 + end 76.149 + if config.logo then 76.150 + config.logo() 76.151 + end 76.152 + if area then 76.153 + execute.view{ module = "area", view = "_sidebar_whatcanido", params = { area = area } } 76.154 + elseif unit then 76.155 + execute.view{ module = "unit", view = "_sidebar_whatcanido", params = { unit = unit } } 76.156 + else 76.157 + execute.view{ module = "index", view = "_sidebar_whatcanido" } 76.158 + end 76.159 + 76.160 + execute.view { module = "index", view = "_sidebar_members" } 76.161 + 76.162 + end } 76.163 +end } 76.164 76.165 -if app.session.member then 76.166 - execute.view{ module = "index", view = "_sidebar_motd_intern" } 76.167 -else 76.168 - execute.view{ module = "index", view = "_sidebar_motd_public" } 76.169 -end 76.170 - 76.171 -if app.session:has_access("anonymous") then 76.172 - -- show the units the member has voting privileges for 76.173 - execute.view { 76.174 - module = "index", view = "_sidebar_units", params = { 76.175 - member = app.session.member 76.176 - } 76.177 - } 76.178 -end 76.179 - 76.180 --- show the user what can be done 76.181 -execute.view { module = "index", view = "_sidebar_whatcanido" } 76.182 - 76.183 --- show active members 76.184 -if app.session:has_access("all_pseudonymous") then 76.185 - execute.view{ module = "index", view = "_sidebar_members" } 76.186 -end 76.187 - 76.188 -if app.session:has_access("anonymous") then 76.189 - 76.190 - if not app.session.member then 76.191 --- execute.view { 76.192 --- module = "slideshow", view = "_index" 76.193 --- } 76.194 - end 76.195 - 76.196 - execute.view { 76.197 - module = "issue", view = "_list2", params = { } 76.198 - } 76.199 - 76.200 -end -- if app.session:has_access "anonymous" 76.201 \ No newline at end of file
77.1 --- a/app/main/index/login.lua Thu Jun 23 03:30:57 2016 +0200 77.2 +++ b/app/main/index/login.lua Sun Jul 15 14:07:29 2018 +0200 77.3 @@ -8,90 +8,131 @@ 77.4 ui.title(_"Login") 77.5 app.html_title.title = _"Login" 77.6 77.7 -execute.view{ module = "index", view = "_sidebar_motd_public" } 77.8 +ui.container{ attr = { class = "mdl-grid" }, content = function() 77.9 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 77.10 + execute.view{ module = "index", view = "_sidebar_motd_public" } 77.11 77.12 -ui.section(function() 77.13 + execute.view{ module = "index", view = "_lang_chooser" } 77.14 + 77.15 + ui.heading{ level = 1, content = _"Login" } 77.16 77.17 -ui.sectionHead(function() 77.18 - ui.heading{ level = 1, content = _"Login" } 77.19 - ui.container { attr = { class = "right" }, content = function() 77.20 - for i, lang in ipairs(config.enabled_languages) do 77.21 - local langcode 77.22 - locale.do_with({ lang = lang }, function() 77.23 - langcode = _("[Name of Language]") 77.24 - end) 77.25 - 77.26 - if i > 1 then 77.27 - slot.put(" | ") 77.28 + local redirect_params = {} 77.29 + local redirect_params_string = param.get("redirect_params") 77.30 + 77.31 + if redirect_params_string then 77.32 + local tmp = json.import(redirect_params_string) 77.33 + if type(tmp) == "table" then 77.34 + for k, v in pairs(tmp) do 77.35 + if type(v) == "string" then 77.36 + redirect_params[k] = v 77.37 + end 77.38 + end 77.39 end 77.40 - 77.41 - ui.link{ 77.42 - content = function() 77.43 - ui.tag{ content = langcode } 77.44 - end, 77.45 - module = "index", 77.46 - action = "set_lang", 77.47 - params = { lang = lang }, 77.48 - routing = { 77.49 - default = { 77.50 - mode = "redirect", 77.51 - module = request.get_module(), 77.52 - view = request.get_view(), 77.53 - id = request.get_id_string(), 77.54 - params = request.get_param_strings() 77.55 + end 77.56 + 77.57 + ui.form{ 77.58 + module = 'index', 77.59 + action = 'login', 77.60 + routing = { 77.61 + ok = { 77.62 + mode = 'redirect', 77.63 + module = param.get("redirect_module") or "index", 77.64 + view = param.get("redirect_view") or "index", 77.65 + id = param.get("redirect_id"), 77.66 + params = redirect_params 77.67 + }, 77.68 + error = { 77.69 + mode = 'redirect', 77.70 + module = "index", 77.71 + view = "login", 77.72 + params = { 77.73 + redirect_module = param.get("redirect_module"), 77.74 + redirect_view = param.get("redirect_view"), 77.75 + redirect_id = param.get("redirect_id"), 77.76 + redirect_params = param.get("redirect_params") 77.77 } 77.78 } 77.79 - } 77.80 - end 77.81 - end } 77.82 -end) 77.83 -ui.form{ 77.84 - module = 'index', 77.85 - action = 'login', 77.86 - routing = { 77.87 - ok = { 77.88 - mode = 'redirect', 77.89 - module = param.get("redirect_module") or "index", 77.90 - view = param.get("redirect_view") or "index", 77.91 - id = param.get("redirect_id"), 77.92 - }, 77.93 - error = { 77.94 - mode = 'forward', 77.95 - module = 'index', 77.96 - view = 'login', 77.97 - } 77.98 - }, 77.99 - content = function() 77.100 - ui.sectionRow(function() 77.101 - ui.field.text{ 77.102 - attr = { id = "username_field" }, 77.103 - label = _'Login name', 77.104 - name = 'login', 77.105 - value = '' 77.106 - } 77.107 - ui.script{ script = 'document.getElementById("username_field").focus();' } 77.108 - ui.field.password{ 77.109 - label = _'Password', 77.110 - name = 'password', 77.111 - value = '' 77.112 - } 77.113 - ui.container { attr = { class = "actions" }, content = function() 77.114 + }, 77.115 + content = function() 77.116 + if slot.get_content("error_code") == "invalid_credentials" then 77.117 + ui.container{ attr = { class = "warning" }, content = _"Invalid login name or password!" } 77.118 + end 77.119 + ui.field.text{ 77.120 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 77.121 + attr = { id = "lf-login__username", class = "mdl-textfield__input" }, 77.122 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-login__username" }, 77.123 + label = _'Login name', 77.124 + name = 'login', 77.125 + value = '' 77.126 + } 77.127 + slot.put("<br />") 77.128 + ui.field.password{ 77.129 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 77.130 + attr = { id = "lf-login__password", class = "mdl-textfield__input" }, 77.131 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-login__password" }, 77.132 + label = _'Password', 77.133 + name = 'password', 77.134 + value = '' 77.135 + } 77.136 + slot.put("<br /><br />") 77.137 ui.tag{ 77.138 tag = "input", 77.139 attr = { 77.140 type = "submit", 77.141 - class = "btn btn-default", 77.142 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 77.143 value = _'Login' 77.144 - }, 77.145 - content = "" 77.146 + } 77.147 + } 77.148 + slot.put(" ") 77.149 + ui.link{ 77.150 + attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" }, 77.151 + module = "index", view = "index", text = _"Cancel" 77.152 } 77.153 - slot.put("<br />") 77.154 - slot.put("<br />") 77.155 - ui.link{ module = "index", view = "reset_password", text = _"Forgot password?" } 77.156 - slot.put(" ") 77.157 - ui.link{ module = "index", view = "send_login", text = _"Forgot login name?" } 77.158 - end } 77.159 - end ) 77.160 - end 77.161 -} 77.162 -end ) 77.163 \ No newline at end of file 77.164 + if not config.disable_registration then 77.165 + slot.put(" ") 77.166 + ui.link{ 77.167 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 77.168 + module = "index", view = "register", text = _"No account yet?", params = { 77.169 + redirect_module = param.get("redirect_module"), 77.170 + redirect_view = param.get("redirect_view"), 77.171 + redirect_id = param.get("redirect_id"), 77.172 + redirect_params = param.get("redirect_params") 77.173 + } 77.174 + } 77.175 + end 77.176 + if config.self_registration then 77.177 + slot.put(" ") 77.178 + ui.link{ 77.179 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 77.180 + module = "registration", view = "register", text = _"No account yet?", params = { 77.181 + redirect_module = param.get("redirect_module"), 77.182 + redirect_view = param.get("redirect_view"), 77.183 + redirect_id = param.get("redirect_id"), 77.184 + redirect_params = param.get("redirect_params") 77.185 + } 77.186 + } 77.187 + end 77.188 + slot.put("<br /><br />") 77.189 + ui.link{ 77.190 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 77.191 + module = "index", view = "reset_password", text = _"Forgot password?", params = { 77.192 + redirect_module = param.get("redirect_module"), 77.193 + redirect_view = param.get("redirect_view"), 77.194 + redirect_id = param.get("redirect_id"), 77.195 + redirect_params = param.get("redirect_params") 77.196 + } 77.197 + } 77.198 + slot.put(" ") 77.199 + ui.link{ 77.200 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 77.201 + module = "index", view = "send_login", text = _"Forgot login name?", params = { 77.202 + redirect_module = param.get("redirect_module"), 77.203 + redirect_view = param.get("redirect_view"), 77.204 + redirect_id = param.get("redirect_id"), 77.205 + redirect_params = param.get("redirect_params") 77.206 + } 77.207 + } 77.208 + end 77.209 + } 77.210 + end } 77.211 +end }
78.1 --- a/app/main/index/register.lua Thu Jun 23 03:30:57 2016 +0200 78.2 +++ b/app/main/index/register.lua Sun Jul 15 14:07:29 2018 +0200 78.3 @@ -5,7 +5,7 @@ 78.4 end 78.5 78.6 if config.registration_disabled and not ldap_uid then 78.7 - error("registration disabled") 78.8 + return execute.view { module = "index", view = "404" } 78.9 end 78.10 78.11 execute.view{ module = "index", view = "_lang_chooser" } 78.12 @@ -33,28 +33,21 @@ 78.13 78.14 78.15 78.16 -ui.section( function() 78.17 - 78.18 +ui.container{ attr = { class = "mdl-grid" }, content = function() 78.19 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 78.20 + ui.heading{ level = 1, content = _"Account registration" } 78.21 +--[[ 78.22 if not code and not ldap_uid then 78.23 - ui.title(_"Registration (step 1 of 3: Invite code)") 78.24 - ui.sectionHead( function() 78.25 - ui.heading { level = 1, content = _"Invite code" } 78.26 - end ) 78.27 + ui.heading{ level = 1, content = _"Registration (step 1 of 3: Invite code)" } 78.28 elseif (not member.notify_email and not notify_email) 78.29 or (not member.name and not name) 78.30 or (not member.login and not login and not member.authority) 78.31 or step == 1 then 78.32 - ui.title(_"Registration (step 2 of 3: Personal information)") 78.33 - ui.sectionHead( function() 78.34 - ui.heading { level = 1, content = _"Check and enter personal data" } 78.35 - end ) 78.36 + ui.heading { level = 1, content = _"Registration (step 2 of 3: Personal information)" } 78.37 else 78.38 - ui.title(_"Registration (step 3 of 3: Terms of use and password)") 78.39 - ui.sectionHead( function() 78.40 - ui.heading { level = 1, content = _"Read and accept the terms and choose a password" } 78.41 - end ) 78.42 + ui.heading { level = 1, content = _"Registration (step 3 of 3: Terms of use and password)" } 78.43 end 78.44 - 78.45 +--]] 78.46 ui.sectionRow( function() 78.47 ui.form{ 78.48 attr = { class = "wide" }, 78.49 @@ -64,20 +57,34 @@ 78.50 code = code, 78.51 notify_email = notify_email, 78.52 name = name, 78.53 - login = login 78.54 + login = login, 78.55 + skip = param.get("skip"), 78.56 + redirect_module = param.get("redirect_module"), 78.57 + redirect_view = param.get("redirect_view"), 78.58 + redirect_id = param.get("redirect_id"), 78.59 + redirect_params = param.get("redirect_params") 78.60 }, 78.61 content = function() 78.62 78.63 if not code and not ldap_uid then 78.64 ui.field.hidden{ name = "step", value = 1 } 78.65 - ui.heading { level = 2, content = _"Please enter the invite code you've received" } 78.66 + ui.tag { tag = "p", content = _"Please enter the invite code you've received" } 78.67 ui.field.text{ 78.68 - name = 'code', 78.69 - value = param.get("invite") 78.70 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 78.71 + attr = { id = "lf-register__code", class = "mdl-textfield__input" }, 78.72 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__code" }, 78.73 + label = _'Invite code', 78.74 + name = 'code', 78.75 + value = '' 78.76 } 78.77 - ui.submit{ 78.78 - text = _'proceed with registration', 78.79 - attr = { class = "btn btn-default" } 78.80 + slot.put("<br /><br />") 78.81 + ui.tag{ 78.82 + tag = "input", 78.83 + attr = { 78.84 + type = "submit", 78.85 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 78.86 + value = _'proceed with registration' 78.87 + } 78.88 } 78.89 slot.put(" ") 78.90 else 78.91 @@ -94,125 +101,182 @@ 78.92 78.93 execute.view{ module = "member", view = "_profile", params = { member = member, for_registration = true } } 78.94 78.95 - if not util.is_profile_field_locked(member, "notify_email") then 78.96 - ui.heading { level = 2, content = _'Email address' } 78.97 + slot.put("<br /><br />") 78.98 + 78.99 + if not util.is_profile_field_locked(member, "notify_email") and not member.notify_email then 78.100 ui.tag{ 78.101 tag = "p", 78.102 content = _"Please enter your email address. This address will be used for automatic notifications (if you request them) and in case you've lost your password. This address will not be published. After registration you will receive an email with a confirmation link." 78.103 } 78.104 ui.field.text{ 78.105 - name = 'notify_email', 78.106 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 78.107 + attr = { id = "lf-register__code", class = "mdl-textfield__input" }, 78.108 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__code" }, 78.109 + label = _'Email address', 78.110 + name = 'notify_email', 78.111 value = param.get("notify_email") or member.notify_email 78.112 } 78.113 end 78.114 if not util.is_profile_field_locked(member, "name") then 78.115 - ui.heading { level = 2, content = _'Screen name' } 78.116 ui.tag{ 78.117 tag = "p", 78.118 content = _"Please choose a name, i.e. your real name or your nick name. This name will be shown to others to identify you." 78.119 } 78.120 ui.field.text{ 78.121 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 78.122 + attr = { id = "lf-register__code", class = "mdl-textfield__input" }, 78.123 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__code" }, 78.124 + label = _'Screen name', 78.125 name = 'name', 78.126 value = param.get("name") or member.name 78.127 } 78.128 end 78.129 if not util.is_profile_field_locked(member, "login") then 78.130 - ui.heading { level = 2, content = _'Login name' } 78.131 ui.tag{ 78.132 tag = "p", 78.133 content = _"Please choose a login name. This name will not be shown to others and is used only by you to login into the system. The login name is case sensitive." 78.134 } 78.135 ui.field.text{ 78.136 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 78.137 + attr = { id = "lf-register__code", class = "mdl-textfield__input" }, 78.138 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__code" }, 78.139 + label = _'Login name', 78.140 name = 'login', 78.141 value = param.get("login") or member.login 78.142 } 78.143 end 78.144 - ui.submit{ 78.145 - text = _'proceed with registration', 78.146 - attr = { class = "btn btn-default" } 78.147 - } 78.148 - slot.put(" ") 78.149 - ui.link{ 78.150 - content = _"one step back", 78.151 - module = "index", 78.152 - view = "register", 78.153 - params = { 78.154 - invite = code 78.155 + slot.put("<br /><br />") 78.156 + ui.tag{ 78.157 + tag = "input", 78.158 + attr = { 78.159 + type = "submit", 78.160 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 78.161 + value = _'proceed with registration' 78.162 } 78.163 } 78.164 - else 78.165 - 78.166 + if param.get("skip") ~= "1" then 78.167 + slot.put(" ") 78.168 + ui.link{ 78.169 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 78.170 + content = _"one step back", 78.171 + module = "index", 78.172 + view = "register", 78.173 + params = { 78.174 + invite = code, 78.175 + redirect_module = param.get("redirect_module"), 78.176 + redirect_view = param.get("redirect_view"), 78.177 + redirect_id = param.get("redirect_id"), 78.178 + redirect_params = param.get("redirect_params") 78.179 + } 78.180 + } 78.181 + end 78.182 + else 78.183 ui.field.hidden{ name = "step", value = "3" } 78.184 - ui.container{ 78.185 - attr = { class = "wiki use_terms" }, 78.186 - content = function() 78.187 - slot.put(config.use_terms) 78.188 - end 78.189 - } 78.190 + 78.191 + local need_to_accept_terms = false 78.192 78.193 for i, checkbox in ipairs(config.use_terms_checkboxes) do 78.194 - slot.put("<br />") 78.195 - ui.tag{ 78.196 - tag = "div", 78.197 + local member_useterms = MemberUseterms:new_selector() 78.198 + :add_where{ "member_id = ?", member.id } 78.199 + :add_where{ "contract_identifier = ?", checkbox.name } 78.200 + :exec() 78.201 + if #member_useterms == 0 then 78.202 + need_to_accept_terms = true 78.203 + end 78.204 + end 78.205 + 78.206 + if need_to_accept_terms then 78.207 + ui.container{ 78.208 + attr = { class = "wiki use_terms" }, 78.209 content = function() 78.210 + slot.put(config.use_terms) 78.211 + end 78.212 + } 78.213 + 78.214 + for i, checkbox in ipairs(config.use_terms_checkboxes) do 78.215 + local member_useterms = MemberUseterms:new_selector() 78.216 + :add_where{ "member_id = ?", member.id } 78.217 + :add_where{ "contract_identifier = ?", checkbox.name } 78.218 + :exec() 78.219 + if #member_useterms == 0 then 78.220 + slot.put("<br />") 78.221 ui.tag{ 78.222 - tag = "input", 78.223 - attr = { 78.224 - type = "checkbox", 78.225 - id = "use_terms_checkbox_" .. checkbox.name, 78.226 - name = "use_terms_checkbox_" .. checkbox.name, 78.227 - value = "1", 78.228 - style = "float: left;", 78.229 - checked = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) and "checked" or nil 78.230 - } 78.231 - } 78.232 - slot.put(" ") 78.233 - ui.tag{ 78.234 - tag = "label", 78.235 - attr = { ['for'] = "use_terms_checkbox_" .. checkbox.name }, 78.236 - content = function() slot.put(checkbox.html) end 78.237 + tag = "div", 78.238 + content = function() 78.239 + ui.tag{ 78.240 + tag = "input", 78.241 + attr = { 78.242 + type = "checkbox", 78.243 + id = "use_terms_checkbox_" .. checkbox.name, 78.244 + name = "use_terms_checkbox_" .. checkbox.name, 78.245 + value = "1", 78.246 + style = "float: left;", 78.247 + checked = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) and "checked" or nil 78.248 + } 78.249 + } 78.250 + slot.put(" ") 78.251 + ui.tag{ 78.252 + tag = "label", 78.253 + attr = { ['for'] = "use_terms_checkbox_" .. checkbox.name }, 78.254 + content = function() slot.put(checkbox.html) end 78.255 + } 78.256 + end 78.257 } 78.258 end 78.259 - } 78.260 - end 78.261 + end 78.262 78.263 - slot.put("<br />") 78.264 - 78.265 + slot.put("<br />") 78.266 + end 78.267 + 78.268 member.notify_email = notify_email or member.notify_email 78.269 member.name = name or member.name 78.270 member.login = login or member.login 78.271 78.272 - ui.heading { level = 2, content = _"Personal information" } 78.273 - execute.view{ module = "member", view = "_profile", params = { 78.274 - member = member, include_private_data = true 78.275 - } } 78.276 - ui.field.text{ 78.277 - readonly = true, 78.278 - label = _'Login name', 78.279 - name = 'login', 78.280 - value = member.login 78.281 - } 78.282 +-- ui.heading { level = 2, content = _"Personal information" } 78.283 +-- execute.view{ module = "member", view = "_profile", params = { 78.284 +-- member = member, include_private_data = true 78.285 +-- } } 78.286 +-- ui.field.text{ 78.287 +-- readonly = true, 78.288 +-- label = _'Login name', 78.289 +-- name = 'login', 78.290 +-- value = member.login 78.291 +-- } 78.292 78.293 if not (member.authority == "ldap") then 78.294 - ui.heading { level = 2, content = _'Password' } 78.295 ui.tag{ 78.296 tag = "p", 78.297 content = _"Please choose a password and enter it twice. The password is case sensitive." 78.298 } 78.299 ui.field.password{ 78.300 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 78.301 + attr = { id = "lf-register__code", class = "mdl-textfield__input" }, 78.302 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__code" }, 78.303 + label = _'Password', 78.304 name = 'password1', 78.305 } 78.306 + slot.put("<br />") 78.307 ui.field.password{ 78.308 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 78.309 + attr = { id = "lf-register__code", class = "mdl-textfield__input" }, 78.310 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__code" }, 78.311 + label = _'Repeat password', 78.312 name = 'password2', 78.313 } 78.314 end 78.315 78.316 - ui.submit{ 78.317 - text = _'activate account', 78.318 - attr = { class = "btn btn-default" } 78.319 + slot.put("<br /><br />") 78.320 + ui.tag{ 78.321 + tag = "input", 78.322 + attr = { 78.323 + type = "submit", 78.324 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 78.325 + value = _'activate account' 78.326 + } 78.327 } 78.328 slot.put(" ") 78.329 ui.link{ 78.330 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 78.331 content = _"one step back", 78.332 module = "index", 78.333 view = "register", 78.334 @@ -220,8 +284,13 @@ 78.335 code = code, 78.336 notify_email = notify_email, 78.337 name = name, 78.338 - login = login, 78.339 - step = 1 78.340 + login = login, 78.341 + skip = param.get("skip"), 78.342 + step = 1, 78.343 + redirect_module = param.get("redirect_module"), 78.344 + redirect_view = param.get("redirect_view"), 78.345 + redirect_id = param.get("redirect_id"), 78.346 + redirect_params = param.get("redirect_params") 78.347 } 78.348 } 78.349 end 78.350 @@ -232,13 +301,20 @@ 78.351 slot.put("<br /><br />") 78.352 78.353 ui.link{ 78.354 - content = _"cancel registration", 78.355 + attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" }, 78.356 + content = _"cancel", 78.357 module = "index", 78.358 action = "cancel_register", 78.359 routing = { default = { 78.360 - mode = "redirect", module = "index", view = "index" 78.361 + mode = "redirect", module = "index", view = "login", params = { 78.362 + redirect_module = param.get("redirect_module"), 78.363 + redirect_view = param.get("redirect_view"), 78.364 + redirect_id = param.get("redirect_id"), 78.365 + redirect_params = param.get("redirect_params") 78.366 + } 78.367 } } 78.368 } 78.369 end ) 78.370 -end ) 78.371 + end } 78.372 +end } 78.373
79.1 --- a/app/main/index/reset_password.lua Thu Jun 23 03:30:57 2016 +0200 79.2 +++ b/app/main/index/reset_password.lua Sun Jul 15 14:07:29 2018 +0200 79.3 @@ -1,15 +1,11 @@ 79.4 -execute.view{ module = "index", view = "_lang_chooser" } 79.5 79.6 ui.title(_"Reset password") 79.7 79.8 -ui.section( function() 79.9 +ui.container{ attr = { class = "mdl-grid" }, content = function() 79.10 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 79.11 + execute.view{ module = "index", view = "_lang_chooser" } 79.12 79.13 - ui.sectionHead( function() 79.14 - ui.heading{ level = 1, content = _"Reset password" } 79.15 - end ) 79.16 - 79.17 - ui.sectionRow( function() 79.18 - 79.19 + ui.heading{ level = 1, content = _"Forgot password?" } 79.20 79.21 local secret = param.get("secret") 79.22 79.23 @@ -23,39 +19,57 @@ 79.24 module = "index", 79.25 action = "reset_password", 79.26 routing = { 79.27 - ok = { 79.28 + default = { 79.29 mode = "redirect", 79.30 module = "index", 79.31 - view = "index" 79.32 + view = "login", params = { 79.33 + redirect_module = param.get("redirect_module"), 79.34 + redirect_view = param.get("redirect_view"), 79.35 + redirect_id = param.get("redirect_id"), 79.36 + redirect_params = param.get("redirect_params") 79.37 + } 79.38 } 79.39 }, 79.40 content = function() 79.41 - ui.field.text{ 79.42 - label = _"login name", 79.43 - name = "login" 79.44 + ui.field.text{ 79.45 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 79.46 + attr = { id = "lf-login__username", class = "mdl-textfield__input" }, 79.47 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-login__username" }, 79.48 + label = _'Login name', 79.49 + name = 'login', 79.50 + value = '' 79.51 } 79.52 + slot.put("<br />") 79.53 79.54 - ui.container { attr = { class = "actions" }, content = function() 79.55 - ui.tag{ 79.56 - tag = "input", 79.57 - attr = { 79.58 - type = "submit", 79.59 - class = "btn btn-default", 79.60 + slot.put("<br /><br />") 79.61 + ui.tag{ 79.62 + tag = "input", 79.63 + attr = { 79.64 + type = "submit", 79.65 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 79.66 value = _"Request password reset link" 79.67 - }, 79.68 - content = "" 79.69 + } 79.70 + } 79.71 + slot.put(" ") 79.72 + ui.link{ 79.73 + attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" }, 79.74 + module = "index", view = "login", text = _"Cancel", params = { 79.75 + redirect_module = param.get("redirect_module"), 79.76 + redirect_view = param.get("redirect_view"), 79.77 + redirect_id = param.get("redirect_id"), 79.78 + redirect_params = param.get("redirect_params") 79.79 } 79.80 - slot.put("<br /><br />") 79.81 - ui.link{ module = "index", view = "send_login", text = _"Forgot login name?" } 79.82 - slot.put(" ") 79.83 - ui.link{ 79.84 - content = function() 79.85 - slot.put(_"Cancel") 79.86 - end, 79.87 - module = "index", 79.88 - view = "login" 79.89 - } 79.90 - end } 79.91 + } 79.92 + slot.put("<br /><br />") 79.93 + ui.link{ 79.94 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 79.95 + module = "index", view = "send_login", text = _"Forgot login name?", params = { 79.96 + redirect_module = param.get("redirect_module"), 79.97 + redirect_view = param.get("redirect_view"), 79.98 + redirect_id = param.get("redirect_id"), 79.99 + redirect_params = param.get("redirect_params") 79.100 + } 79.101 + } 79.102 end 79.103 } 79.104 79.105 @@ -120,5 +134,5 @@ 79.106 } 79.107 79.108 end 79.109 - end ) 79.110 -end ) 79.111 \ No newline at end of file 79.112 + end } 79.113 +end }
80.1 --- a/app/main/index/search.lua Thu Jun 23 03:30:57 2016 +0200 80.2 +++ b/app/main/index/search.lua Sun Jul 15 14:07:29 2018 +0200 80.3 @@ -94,7 +94,7 @@ 80.4 if count > 0 then 80.5 execute.view{ 80.6 module = "issue", 80.7 - view = "_list2", 80.8 + view = "_list", 80.9 params = { 80.10 search = search_string, 80.11 no_filter = true
81.1 --- a/app/main/index/send_login.lua Thu Jun 23 03:30:57 2016 +0200 81.2 +++ b/app/main/index/send_login.lua Sun Jul 15 14:07:29 2018 +0200 81.3 @@ -1,54 +1,63 @@ 81.4 ui.title(_"Recover login name") 81.5 81.6 -ui.section( function() 81.7 +ui.container{ attr = { class = "mdl-grid" }, content = function() 81.8 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 81.9 + execute.view{ module = "index", view = "_lang_chooser" } 81.10 81.11 - ui.sectionHead( function() 81.12 - ui.heading{ level = 1, content = _"Request email with login name" } 81.13 - end ) 81.14 - 81.15 - ui.sectionRow( function() 81.16 + ui.heading{ level = 1, content = _"Forgot login name?" } 81.17 81.18 ui.tag{ 81.19 tag = 'p', 81.20 content = _'Please enter your email address. You will receive an email with your login name.' 81.21 } 81.22 + 81.23 ui.form{ 81.24 attr = { class = "vertical" }, 81.25 module = "index", 81.26 action = "send_login", 81.27 routing = { 81.28 - ok = { 81.29 + default = { 81.30 mode = "redirect", 81.31 module = "index", 81.32 - view = "index" 81.33 + view = "login", params = { 81.34 + redirect_module = param.get("redirect_module"), 81.35 + redirect_view = param.get("redirect_view"), 81.36 + redirect_id = param.get("redirect_id"), 81.37 + redirect_params = param.get("redirect_params") 81.38 + } 81.39 } 81.40 }, 81.41 content = function() 81.42 - ui.field.text{ 81.43 - label = _"Email address", 81.44 - name = "email" 81.45 + ui.field.text{ 81.46 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 81.47 + attr = { id = "lf-login__username", class = "mdl-textfield__input" }, 81.48 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-login__username" }, 81.49 + label = _'Email address', 81.50 + name = 'email', 81.51 + value = '' 81.52 } 81.53 + slot.put("<br />") 81.54 81.55 - ui.container { attr = { class = "actions" }, content = function() 81.56 - ui.tag{ 81.57 - tag = "input", 81.58 - attr = { 81.59 - type = "submit", 81.60 - class = "btn btn-default", 81.61 + slot.put("<br /><br />") 81.62 + ui.tag{ 81.63 + tag = "input", 81.64 + attr = { 81.65 + type = "submit", 81.66 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 81.67 value = _"Request email with login name" 81.68 - }, 81.69 - content = "" 81.70 } 81.71 - slot.put("<br /><br />") 81.72 - ui.link{ 81.73 - content = function() 81.74 - slot.put(_"Cancel") 81.75 - end, 81.76 - module = "index", 81.77 - view = "login" 81.78 - } 81.79 - end } 81.80 + } 81.81 + slot.put(" ") 81.82 + ui.link{ 81.83 + attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" }, 81.84 + module = "index", view = "login", text = _"Cancel", params = { 81.85 + redirect_module = param.get("redirect_module"), 81.86 + redirect_view = param.get("redirect_view"), 81.87 + redirect_id = param.get("redirect_id"), 81.88 + redirect_params = param.get("redirect_params") 81.89 + } 81.90 + } 81.91 end 81.92 } 81.93 - end ) 81.94 -end ) 81.95 \ No newline at end of file 81.96 + end } 81.97 +end }
82.1 --- a/app/main/initiative/_action/add_initiator.lua Thu Jun 23 03:30:57 2016 +0200 82.2 +++ b/app/main/initiative/_action/add_initiator.lua Sun Jul 15 14:07:29 2018 +0200 82.3 @@ -8,7 +8,7 @@ 82.4 82.5 local initiator = Initiator:by_pk(initiative.id, app.session.member.id) 82.6 if not initiator or initiator.accepted ~= true then 82.7 - error("access denied") 82.8 + return execute.view { module = "index", view = "403" } 82.9 end 82.10 82.11 -- TODO important m1 selectors returning result _SET_!
83.1 --- a/app/main/initiative/_action/add_support.lua Thu Jun 23 03:30:57 2016 +0200 83.2 +++ b/app/main/initiative/_action/add_support.lua Sun Jul 15 14:07:29 2018 +0200 83.3 @@ -7,7 +7,7 @@ 83.4 local issue = initiative:get_reference_selector("issue"):for_share():single_object_mode():exec() 83.5 83.6 if not app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then 83.7 - error("access denied") 83.8 + return execute.view { module = "index", view = "403" } 83.9 end 83.10 83.11 if issue.closed then
84.1 --- a/app/main/initiative/_action/create.lua Thu Jun 23 03:30:57 2016 +0200 84.2 +++ b/app/main/initiative/_action/create.lua Sun Jul 15 14:07:29 2018 +0200 84.3 @@ -25,7 +25,7 @@ 84.4 end 84.5 84.6 if not app.session.member:has_voting_right_for_unit_id(area.unit_id) then 84.7 - error("access denied") 84.8 + return execute.view { module = "index", view = "403" } 84.9 end 84.10 84.11 local policy_id = param.get("policy_id", atom.integer) 84.12 @@ -44,15 +44,15 @@ 84.13 return false 84.14 end 84.15 if policy.polling and not app.session.member:has_polling_right_for_unit_id(area.unit_id) then 84.16 - error("no polling right for this unit") 84.17 + return execute.view { module = "index", view = "403" } 84.18 end 84.19 - 84.20 if not area:get_reference_selector("allowed_policies") 84.21 :add_where{ "policy.id = ?", policy_id } 84.22 :optional_object_mode() 84.23 :exec() 84.24 then 84.25 - error("policy not allowed") 84.26 + slot.put_into("error", "policy not allowed") 84.27 + return false 84.28 end 84.29 end 84.30 84.31 @@ -82,22 +82,6 @@ 84.32 return false 84.33 end 84.34 84.35 -local formatting_engine 84.36 -if config.enforce_formatting_engine then 84.37 - formatting_engine = config.enforce_formatting_engine 84.38 -else 84.39 - formatting_engine = param.get("formatting_engine") 84.40 - local formatting_engine_valid = false 84.41 - for i, fe in ipairs(config.formatting_engines) do 84.42 - if formatting_engine == fe.id then 84.43 - formatting_engine_valid = true 84.44 - end 84.45 - end 84.46 - if not formatting_engine_valid then 84.47 - error("invalid formatting engine!") 84.48 - end 84.49 -end 84.50 - 84.51 local timing 84.52 if not issue and policy.free_timeable then 84.53 local free_timing_string = util.trim(param.get("free_timing")) 84.54 @@ -109,7 +93,8 @@ 84.55 if config.free_timing and config.free_timing.available_func then 84.56 available_timings = config.free_timing.available_func(policy) 84.57 if available_timings == false then 84.58 - error("error in free timing config") 84.59 + slot.put_into("error", "error in free timing config") 84.60 + return false 84.61 end 84.62 end 84.63 if available_timings then 84.64 @@ -126,10 +111,39 @@ 84.65 end 84.66 timing = config.free_timing.calculate_func(policy, free_timing_string) 84.67 if not timing then 84.68 - error("error in free timing config") 84.69 + slot.put_into("error", "error in free timing config") 84.70 + return false 84.71 end 84.72 end 84.73 84.74 +local draft_text = param.get("draft") 84.75 + 84.76 +if not draft_text then 84.77 + return false 84.78 +end 84.79 + 84.80 +local draft_text = util.wysihtml_preproc(draft_text) 84.81 + 84.82 +local valid_html, error_message = util.html_is_safe(draft_text) 84.83 +if not valid_html then 84.84 + slot.put_into("error", _("Draft contains invalid formatting or character sequence: #{error_message}", { error_message = error_message }) ) 84.85 + return false 84.86 +end 84.87 + 84.88 +if config.initiative_abstract then 84.89 + local abstract = param.get("abstract") 84.90 + if not abstract then 84.91 + return false 84.92 + end 84.93 + abstract = encode.html(abstract) 84.94 + draft_text = abstract .. "<!--END_OF_ABSTRACT-->" .. draft_text 84.95 +end 84.96 + 84.97 +local location = param.get("location") 84.98 +if location == "" then 84.99 + location = nil 84.100 +end 84.101 + 84.102 if param.get("preview") or param.get("edit") then 84.103 return 84.104 end 84.105 @@ -177,7 +191,8 @@ 84.106 local draft = Draft:new() 84.107 draft.initiative_id = initiative.id 84.108 draft.formatting_engine = formatting_engine 84.109 -draft.content = param.get("draft") 84.110 +draft.content = draft_text 84.111 +draft.location = location 84.112 draft.author_id = app.session.member.id 84.113 draft:save() 84.114
85.1 --- a/app/main/initiative/_action/reject_initiator_invitation.lua Thu Jun 23 03:30:57 2016 +0200 85.2 +++ b/app/main/initiative/_action/reject_initiator_invitation.lua Sun Jul 15 14:07:29 2018 +0200 85.3 @@ -13,7 +13,7 @@ 85.4 end 85.5 85.6 if initiator.accepted ~= nil then 85.7 - error("access denied") 85.8 + return execute.view { module = "index", view = "403" } 85.9 end 85.10 85.11 initiator.accepted = false
86.1 --- a/app/main/initiative/_action/remove_initiator.lua Thu Jun 23 03:30:57 2016 +0200 86.2 +++ b/app/main/initiative/_action/remove_initiator.lua Sun Jul 15 14:07:29 2018 +0200 86.3 @@ -20,11 +20,11 @@ 86.4 local initiator_todelete = Initiator:by_pk(initiative.id, param.get("member_id", atom.integer)) 86.5 86.6 if not (initiator and initiator.accepted) and not (initiator.member_id == initiator_todelete.member_id) then 86.7 - error("access denied") 86.8 + return execute.view { module = "index", view = "403" } 86.9 end 86.10 86.11 if initiator_todelete.accepted == false and initiator.member_id ~= initiator_todelete.member_id then 86.12 - error("access denied") 86.13 + return execute.view { module = "index", view = "403" } 86.14 end 86.15 86.16 local initiators = initiative
87.1 --- a/app/main/initiative/_action/revoke.lua Thu Jun 23 03:30:57 2016 +0200 87.2 +++ b/app/main/initiative/_action/revoke.lua Sun Jul 15 14:07:29 2018 +0200 87.3 @@ -2,7 +2,7 @@ 87.4 87.5 local initiator = Initiator:by_pk(initiative.id, app.session.member.id) 87.6 if not initiator or initiator.accepted ~= true then 87.7 - error("access denied") 87.8 + return execute.view { module = "index", view = "403" } 87.9 end 87.10 87.11 -- TODO important m1 selectors returning result _SET_! 87.12 @@ -29,7 +29,7 @@ 87.13 if suggested_initiative_id ~= -1 then 87.14 local suggested_initiative = Initiative:by_id(suggested_initiative_id) 87.15 if not suggested_initiative then 87.16 - error("object not found") 87.17 + return false 87.18 end 87.19 if initiative.id == suggested_initiative.id then 87.20 slot.put_into("error", _"You can't suggest the initiative you are revoking")
88.1 --- a/app/main/initiative/_bargraph.lua Thu Jun 23 03:30:57 2016 +0200 88.2 +++ b/app/main/initiative/_bargraph.lua Sun Jul 15 14:07:29 2018 +0200 88.3 @@ -43,14 +43,17 @@ 88.4 local max_value = initiative.issue.population or 0 88.5 local quorum 88.6 if initiative.issue.accepted then 88.7 - quorum = initiative.issue.policy.initiative_quorum_num / initiative.issue.policy.initiative_quorum_den 88.8 + quorum = initiative.issue.initiative_quorum or 0 88.9 else 88.10 - quorum = initiative.issue.policy.issue_quorum_num / initiative.issue.policy.issue_quorum_den 88.11 + quorum = initiative.issue.issue_quorum or 0 88.12 + end 88.13 + if quorum > max_value then 88.14 + max_value = quorum 88.15 end 88.16 ui.bargraph{ 88.17 max_value = max_value, 88.18 width = 100, 88.19 - quorum = max_value * quorum, 88.20 + quorum = quorum, 88.21 quorum_color = "#00F", 88.22 bars = { 88.23 { color = "#5a5", value = (initiative.satisfied_supporter_count or 0) },
89.1 --- a/app/main/initiative/_head.lua Thu Jun 23 03:30:57 2016 +0200 89.2 +++ b/app/main/initiative/_head.lua Sun Jul 15 14:07:29 2018 +0200 89.3 @@ -19,180 +19,166 @@ 89.4 local initiators = initiators_members_selector:exec() 89.5 89.6 89.7 -ui.sectionHead( "initiativeInfo", function () 89.8 +ui.container{ attr = { class = "mdl-card__title mdl-card--has-fab mdl-card--border" }, content = function () 89.9 + 89.10 + ui.heading { 89.11 + attr = { class = "mdl-card__title-text" }, 89.12 + level = 2, 89.13 + content = function() 89.14 + ui.tag{ content = initiative.display_name } 89.15 + end 89.16 + } 89.17 89.18 - ui.heading { 89.19 - level = 1, 89.20 - content = initiative.display_name 89.21 - } 89.22 - 89.23 - ui.container { attr = { class = "support" }, content = function () 89.24 - if initiative.supporter_count == nil then 89.25 - ui.tag { 89.26 - attr = { class = "supporterCount" }, 89.27 - content = _"[calculating]" 89.28 + if app.session.member and app.session.member:has_voting_right_for_unit_id(initiative.issue.area.unit_id) then 89.29 + if not initiative.issue.closed and not initiative.member_info.supported then 89.30 + if not initiative.issue.fully_frozen then 89.31 + ui.link { 89.32 + attr = { class = "mdl-button mdl-js-button mdl-button--fab mdl-button--colored" , 89.33 + style = "position: absolute; right: 20px; bottom: -27px;" 89.34 + }, 89.35 + module = "initiative", action = "add_support", 89.36 + routing = { default = { 89.37 + mode = "redirect", module = "initiative", view = "show", id = initiative.id 89.38 + } }, 89.39 + id = initiative.id, 89.40 + content = function() 89.41 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "thumb_up" } 89.42 + end 89.43 } 89.44 - elseif initiative.issue.closed == nil then 89.45 - ui.tag { 89.46 - attr = { class = "satisfiedSupporterCount" }, 89.47 - content = _("#{count} supporter", { count = initiative.satisfied_supporter_count }) 89.48 - } 89.49 - if initiative.potential_supporter_count and 89.50 - initiative.potential_supporter_count > 0 89.51 - then 89.52 - slot.put ( " " ) 89.53 - ui.tag { 89.54 - attr = { class = "potentialSupporterCount" }, 89.55 - content = _("(+ #{count} potential)", { count = initiative.potential_supporter_count }) 89.56 + end 89.57 + end 89.58 + if initiative.issue.fully_frozen and not initiative.issue.closed and not initiative.issue.member_info.direct_voted then 89.59 + ui.link { 89.60 + attr = { class = "mdl-button mdl-js-button mdl-button--fab mdl-button--colored" , 89.61 + style = "position: absolute; right: 20px; bottom: -27px;" 89.62 + }, 89.63 + module = "vote", view = "list", 89.64 + params = { issue_id = initiative.issue_id }, 89.65 + content = function() 89.66 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = config.voting_icon or "mail_outline" } 89.67 + end 89.68 + } 89.69 + end 89.70 + end 89.71 +end } 89.72 + 89.73 +ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function () 89.74 + 89.75 + if not config.voting_only and app.session:has_access("authors_pseudonymous") then 89.76 + for i, member in ipairs(initiators) do 89.77 + if i > 1 then 89.78 + slot.put(" ") 89.79 + end 89.80 + util.micro_avatar( member ) 89.81 + end -- for i, member 89.82 + end 89.83 + 89.84 + if member then 89.85 + ui.container { attr = { class = "mySupport float-right right" }, content = function () 89.86 + if initiative.issue.fully_frozen then 89.87 + slot.put("<br />") 89.88 + if initiative.issue.member_info.direct_voted then 89.89 + ui.tag { content = _"You have voted" } 89.90 + slot.put("<br />") 89.91 + if not initiative.issue.closed then 89.92 + ui.link { 89.93 + module = "vote", view = "list", 89.94 + params = { issue_id = initiative.issue.id }, 89.95 + text = _"change vote" 89.96 + } 89.97 + else 89.98 + ui.link { 89.99 + module = "vote", view = "list", 89.100 + params = { issue_id = initiative.issue.id }, 89.101 + text = _"show vote" 89.102 + } 89.103 + end 89.104 + slot.put(" ") 89.105 + elseif active_trustee_id then 89.106 + ui.tag { content = _"You have voted via delegation" } 89.107 + ui.link { 89.108 + content = _"Show voting ballot", 89.109 + module = "vote", view = "list", params = { 89.110 + issue_id = initiative.issue.id, member_id = active_trustee_id 89.111 + } 89.112 + } 89.113 + elseif not initiative.issue.closed then 89.114 + ui.link { 89.115 + attr = { class = "btn btn-default" }, 89.116 + module = "vote", view = "list", 89.117 + params = { issue_id = initiative.issue.id }, 89.118 + text = _"vote now" 89.119 } 89.120 end 89.121 - 89.122 - end 89.123 - 89.124 - slot.put ( "<br />" ) 89.125 - 89.126 + elseif initiative.member_info.supported then 89.127 + ui.container{ content = function() 89.128 + ui.tag{ content = _"You are supporter" } 89.129 + slot.put(" ") 89.130 + if initiative.member_info.satisfied then 89.131 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "thumb_up" } 89.132 + else 89.133 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "thumb_up" } 89.134 + end 89.135 + end } 89.136 + if initiative.issue.member_info.own_participation then 89.137 + ui.link { 89.138 + attr = { class = "btn-link" }, 89.139 + module = "initiative", action = "remove_support", 89.140 + routing = { default = { 89.141 + mode = "redirect", module = "initiative", view = "show", id = initiative.id 89.142 + } }, 89.143 + id = initiative.id, 89.144 + text = _"remove my support" 89.145 + } 89.146 + 89.147 + else 89.148 + 89.149 + ui.link { 89.150 + module = "delegation", view = "show", params = { 89.151 + issue_id = initiative.issue_id, 89.152 + initiative_id = initiative.id 89.153 + }, 89.154 + content = _"via delegation" 89.155 + } 89.156 + 89.157 + end 89.158 + 89.159 + slot.put(" ") 89.160 + 89.161 + end 89.162 + end } 89.163 + 89.164 + end 89.165 + 89.166 + if config.initiative_abstract then 89.167 + local abstract = string.match(initiative.current_draft.content, "(.+)<!%--END_OF_ABSTRACT%-->") 89.168 + if abstract then 89.169 + ui.container{ 89.170 + attr = { class = "abstract", style = "padding-right: 140px;" }, 89.171 + content = function() slot.put(abstract) end 89.172 + } 89.173 + end 89.174 + end 89.175 + 89.176 + ui.container { attr = { class = "support" }, content = function () 89.177 + 89.178 + if not config.voting_only then 89.179 execute.view { 89.180 module = "initiative", view = "_bargraph", params = { 89.181 initiative = initiative 89.182 } 89.183 } 89.184 - end } 89.185 - 89.186 - if member then 89.187 - ui.container { attr = { class = "mySupport right" }, content = function () 89.188 - if initiative.issue.fully_frozen then 89.189 - if initiative.issue.member_info.direct_voted then 89.190 - --ui.image { attr = { class = "icon48 right" }, static = "icons/48/voted_ok.png" } 89.191 - ui.tag { content = _"You have voted" } 89.192 - slot.put("<br />") 89.193 - if not initiative.issue.closed then 89.194 - ui.link { 89.195 - module = "vote", view = "list", 89.196 - params = { issue_id = initiative.issue.id }, 89.197 - text = _"change vote" 89.198 - } 89.199 - else 89.200 - ui.link { 89.201 - module = "vote", view = "list", 89.202 - params = { issue_id = initiative.issue.id }, 89.203 - text = _"show vote" 89.204 - } 89.205 - end 89.206 - slot.put(" ") 89.207 - elseif active_trustee_id then 89.208 - ui.tag { content = _"You have voted via delegation" } 89.209 - ui.link { 89.210 - content = _"Show voting ballot", 89.211 - module = "vote", view = "list", params = { 89.212 - issue_id = initiative.issue.id, member_id = active_trustee_id 89.213 - } 89.214 - } 89.215 - elseif not initiative.issue.closed then 89.216 - ui.link { 89.217 - attr = { class = "btn btn-default" }, 89.218 - module = "vote", view = "list", 89.219 - params = { issue_id = initiative.issue.id }, 89.220 - text = _"vote now" 89.221 - } 89.222 - end 89.223 - elseif initiative.member_info.supported then 89.224 - if initiative.member_info.satisfied then 89.225 - ui.image { attr = { class = "icon48 right" }, static = "icons/32/support_satisfied.png" } 89.226 - else 89.227 - ui.image { attr = { class = "icon48 right" }, static = "icons/32/support_unsatisfied.png" } 89.228 - end 89.229 - ui.container { content = _"You are supporter" } 89.230 + slot.put(" ") 89.231 89.232 - if initiative.issue.member_info.own_participation then 89.233 - ui.link { 89.234 - attr = { class = "btn-link" }, 89.235 - module = "initiative", action = "remove_support", 89.236 - routing = { default = { 89.237 - mode = "redirect", module = "initiative", view = "show", id = initiative.id 89.238 - } }, 89.239 - id = initiative.id, 89.240 - text = _"remove my support" 89.241 - } 89.242 - 89.243 - else 89.244 - 89.245 - ui.link { 89.246 - module = "delegation", view = "show", params = { 89.247 - issue_id = initiative.issue_id, 89.248 - initiative_id = initiative.id 89.249 - }, 89.250 - content = _"via delegation" 89.251 - } 89.252 - 89.253 - end 89.254 - 89.255 - slot.put(" ") 89.256 - 89.257 - 89.258 - elseif not initiative.issue.closed then 89.259 - ui.link { 89.260 - attr = { class = "btn btn-default" }, 89.261 - module = "initiative", action = "add_support", 89.262 - routing = { default = { 89.263 - mode = "redirect", module = "initiative", view = "show", id = initiative.id 89.264 - } }, 89.265 - id = initiative.id, 89.266 - text = _"add my support" 89.267 - } 89.268 - 89.269 - end 89.270 - end } 89.271 - 89.272 + ui.supporter_count(initiative) 89.273 end 89.274 89.275 - slot.put("<br style='clear: both;'/>") 89.276 - 89.277 - ui.container { 89.278 - attr = { class = "initiators" }, 89.279 - content = function () 89.280 - 89.281 - if app.session:has_access("authors_pseudonymous") then 89.282 - for i, member in ipairs(initiators) do 89.283 - if i > 1 then 89.284 - slot.put(" ") 89.285 - end 89.286 - util.micro_avatar(member) 89.287 - if member.accepted == nil then 89.288 - slot.put ( " " ) 89.289 - ui.tag { content = _"(invited)" } 89.290 - end 89.291 - end -- for i, member 89.292 - 89.293 - end 89.294 - 89.295 - end 89.296 - } -- ui.container "initiators" 89.297 + end } 89.298 + 89.299 +end } 89.300 89.301 - ui.container { 89.302 - attr = { class = "links" }, 89.303 - content = function () 89.304 - 89.305 - local drafts_count = initiative:get_reference_selector("drafts"):count() 89.306 - ui.link { 89.307 - content = _("suggestions (#{count}) ↓", { 89.308 - count = # ( initiative.suggestions ) 89.309 - }), 89.310 - external = "#suggestions" 89.311 - } 89.312 +execute.view { 89.313 + module = "initiative", view = "_sidebar_state", 89.314 + params = { initiative = initiative } 89.315 +} 89.316 89.317 - slot.put ( " | " ) 89.318 - 89.319 - ui.link{ 89.320 - module = "initiative", view = "history", id = initiative.id, 89.321 - content = _("draft history (#{count})", { count = drafts_count }) 89.322 - } 89.323 - 89.324 - end 89.325 - } -- ui.containers "links" 89.326 - end ) 89.327 - 89.328 - execute.view { 89.329 - module = "initiative", view = "_sidebar_state", 89.330 - params = { initiative = initiative } 89.331 - } 89.332 -
90.1 --- a/app/main/initiative/_list.lua Thu Jun 23 03:30:57 2016 +0200 90.2 +++ b/app/main/initiative/_list.lua Sun Jul 15 14:07:29 2018 +0200 90.3 @@ -1,7 +1,8 @@ 90.4 -local member = param.get("member", "table") or app.session.member 90.5 +local for_member = param.get("for_member", "table") 90.6 90.7 local initiatives = param.get("initiatives", "table") 90.8 -local highlight_initiative_id = param.get ( "highlight_initiative_id", "number" ) 90.9 +local ommit_initiative_id = param.get ( "ommit_initiative_id", "number" ) 90.10 + 90.11 90.12 local for_initiative = param.get("initiative", "table") 90.13 90.14 @@ -13,7 +14,7 @@ 90.15 90.16 ui.tag { 90.17 tag = "ul", 90.18 - attr = { class = "initiatives" }, 90.19 + attr = { class = "initiatives mdl-list" }, 90.20 content = function () 90.21 local last_group 90.22 for i, initiative in ipairs(initiatives) do 90.23 @@ -29,51 +30,57 @@ 90.24 group = "not_admitted" 90.25 end 90.26 end 90.27 - if not for_initiative and group ~= last_group and not for_event then 90.28 + if not for_initiative and group ~= last_group and not for_event and not for_member then 90.29 90.30 local text 90.31 if group == "admitted" then 90.32 if initiative.issue.state == "finished_with_winner" then 90.33 text = _"Competing initiatives in pairwise comparison to winner:" 90.34 - else 90.35 + elseif initiative.issue.voter_count and initiative.issue.voter_count > 0 then 90.36 text = _"Competing initiatives in pairwise comparison to best initiative:" 90.37 end 90.38 end 90.39 - if group == "not_admitted" then 90.40 + if group == "not_admitted" and initiative.issue.state ~= "canceled_no_initiative_admitted" then 90.41 text = _("Competing initiatives failed the 2nd quorum (#{num}/#{den}):", { 90.42 num = initiative.issue.policy.initiative_quorum_num, 90.43 den = initiative.issue.policy.initiative_quorum_den 90.44 } ) 90.45 end 90.46 if text then 90.47 - slot.put("<br />") 90.48 - ui.container { attr = { class = "result" }, content = text } 90.49 + ui.tag{ tag = "li", attr = { class = "mdl-list__item" }, content = function() 90.50 + ui.container{ attr = { class = "mdl-list__item-primary-content" }, content = text } 90.51 + end } 90.52 end 90.53 last_group = group 90.54 end 90.55 90.56 - local class = "" 90.57 - if highlight_initiative_id == initiative.id then 90.58 - class = "highlighted" 90.59 - end 90.60 - if app.session.member then 90.61 - if initiative.member_info.supported then 90.62 - class = class .. " supported" 90.63 - end 90.64 - if initiative.member_info.satisfied then 90.65 - class = class .. " satisfied" 90.66 + if ommit_initiative_id ~= initiative.id then 90.67 + local class = "mdl-list__item mdl-list__item--three-line" 90.68 + if app.session.member then 90.69 + if initiative.member_info.supported then 90.70 + class = class .. " supported" 90.71 + end 90.72 + if initiative.member_info.satisfied then 90.73 + class = class .. " satisfied" 90.74 + end 90.75 end 90.76 + ui.tag { 90.77 + tag = "li", attr = { class = class }, 90.78 + content = function () 90.79 + if i == 1 and not ommit_initiative_id and not for_member and ( 90.80 + initiative.issue.state == "finished_with_winner" 90.81 + or initiative.issue.state == "finished_without_winner" 90.82 + ) then 90.83 + util.initiative_pie(initiative) 90.84 + end 90.85 + execute.view { 90.86 + module = "initiative", view = "_list_element", params = { 90.87 + initiative = initiative, for_event = for_event, for_member = for_member 90.88 + } 90.89 + } 90.90 + end 90.91 + } 90.92 end 90.93 - ui.tag { 90.94 - tag = "li", attr = { class = class }, 90.95 - content = function () 90.96 - execute.view { 90.97 - module = "initiative", view = "_list_element", params = { 90.98 - initiative = initiative, for_event = for_event 90.99 - } 90.100 - } 90.101 - end 90.102 - } 90.103 end 90.104 end 90.105 -} 90.106 \ No newline at end of file 90.107 +}
91.1 --- a/app/main/initiative/_list_element.lua Thu Jun 23 03:30:57 2016 +0200 91.2 +++ b/app/main/initiative/_list_element.lua Sun Jul 15 14:07:29 2018 +0200 91.3 @@ -1,14 +1,28 @@ 91.4 local initiative = param.get("initiative", "table") 91.5 local for_event = param.get("for_event", atom.boolean) 91.6 +local for_member = param.get("for_member", "table") 91.7 91.8 local issue = initiative.issue 91.9 91.10 -local class = "initiative" 91.11 +if initiative.vote_grade ~= nil then 91.12 + if initiative.vote_grade > 0 then 91.13 + local text = _"voted yes" 91.14 + ui.container{ attr = { class = "mdl-list__item-avatar positive" }, content = function() 91.15 + ui.tag{ tag = "i", attr = { class = "material-icons", title = text }, content = "thumb_up" } 91.16 + end } 91.17 + elseif initiative.vote_grade == 0 then 91.18 + elseif initiative.vote_grade < 0 then 91.19 + local text = _"voted no" 91.20 + ui.container{ attr = { class = "mdl-list__item-avatar negative" }, content = function() 91.21 + ui.tag{ tag = "i", attr = { class = "material-icons", title = text }, content = "thumb_down" } 91.22 + end } 91.23 + end 91.24 +end 91.25 91.26 +local class = "initiative mdl-list__item-primary-content" 91.27 if initiative.rank == 1 then 91.28 class = class .. " rank1" 91.29 end 91.30 - 91.31 if initiative.revoked then 91.32 class = class .. " revoked" 91.33 end 91.34 @@ -16,88 +30,112 @@ 91.35 ui.container{ 91.36 attr = { class = class }, 91.37 content = function () 91.38 - if initiative.rank ~= 1 and not for_event then 91.39 - execute.view { 91.40 - module = "initiative", view = "_bargraph", params = { 91.41 - initiative = initiative, 91.42 - battled_initiative = issue.initiatives[1] 91.43 - } 91.44 - } 91.45 - slot.put(" ") 91.46 - end 91.47 - ui.tag { 91.48 + ui.container { 91.49 attr = { class = "initiative_name" }, 91.50 content = function() 91.51 + if not for_member and app.session.member then 91.52 + if initiative.member_info.supported then 91.53 + if initiative.member_info.satisfied then 91.54 + ui.tag{ tag = "i", attr = { id = "lf-initiative__support-" .. initiative.id, class = "material-icons material-icons-small" }, content = "thumb_up" } 91.55 + --ui.container { attr = { class = "mdl-tooltip", ["for"] = "lf-initiative__support-" .. initiative.id }, content = _"You are supporter of this initiative" } 91.56 + else 91.57 + ui.tag{ tag = "i", attr = { id = "lf-initiative__support-" .. initiative.id, class = "material-icons material-icons-small mdl-color-text--orange-500" }, content = "thumb_up" } 91.58 + --ui.container { attr = { class = "mdl-tooltip", ["for"] = "lf-initiative__support-" .. initiative.id }, content = _"supporter with restricting suggestions" } 91.59 + end 91.60 + slot.put(" ") 91.61 + end 91.62 + end 91.63 ui.link { 91.64 text = initiative.display_name, 91.65 module = "initiative", view = "show", id = initiative.id 91.66 } 91.67 - slot.put(" ") 91.68 - if initiative.vote_grade ~= nil then 91.69 - if initiative.vote_grade > 0 then 91.70 - local text = _"voted yes" 91.71 - ui.image { attr = { class = "icon16", title = text, alt = text }, static = "icons/32/support_satisfied.png" } 91.72 - elseif initiative.vote_grade == 0 then 91.73 - elseif initiative.vote_grade < 0 then 91.74 - local text = _"voted no" 91.75 - ui.image { attr = { class = "icon16", title = text, alt = text }, static = "icons/32/voted_no.png" } 91.76 + end 91.77 + } 91.78 + ui.container{ attr = { class = "mdl-list__item-text-body" }, content = function() 91.79 + local draft_content = initiative.current_draft.content 91.80 + if config.initiative_abstract then 91.81 + local abstract = string.match(draft_content, "(.+)<!%--END_OF_ABSTRACT%-->") 91.82 + if abstract then 91.83 + slot.put(abstract) 91.84 + end 91.85 + end 91.86 + if not config.voting_only then 91.87 + if app.session:has_access("authors_pseudonymous") then 91.88 + local initiator_members = initiative:get_reference_selector("initiating_members") 91.89 + :add_field("initiator.accepted", "accepted") 91.90 + :add_order_by("member.name") 91.91 + :add_where("initiator.accepted") 91.92 + :exec() 91.93 + 91.94 + local initiators = {} 91.95 + for i, member in ipairs(initiator_members) do 91.96 + if member.accepted then 91.97 + initiators[#initiators+1] = member.name 91.98 + end 91.99 end 91.100 - elseif app.session.member then 91.101 - if initiative.member_info.supported then 91.102 - if initiative.member_info.satisfied then 91.103 - local text = _"supporter" 91.104 - ui.image { attr = { class = "icon16", title = text, alt = text }, static = "icons/32/support_satisfied.png" } 91.105 - else 91.106 - local text = _"supporter with restricting suggestions" 91.107 - ui.image { attr = { class = "icon16", title = text, alt = text }, static = "icons/32/support_unsatisfied.png" } 91.108 - end 91.109 - end 91.110 + ui.tag{ content = _"by" } 91.111 + slot.put(" ") 91.112 + ui.tag{ content = table.concat(initiators, ", ") } 91.113 + slot.put("<br />") 91.114 end 91.115 end 91.116 - } 91.117 + if initiative.rank ~= 1 and (issue.voter_count == nil or issue.voter_count > 0) and not for_event then 91.118 + if not config.voting_only or issue.closed then 91.119 + execute.view { 91.120 + module = "initiative", view = "_bargraph", params = { 91.121 + initiative = initiative, 91.122 + battled_initiative = issue.initiatives[1] 91.123 + } 91.124 + } 91.125 + 91.126 + slot.put(" ") 91.127 + 91.128 + ui.supporter_count(initiative) 91.129 + end 91.130 + end 91.131 + 91.132 + if initiative.positive_votes ~= nil then 91.133 + 91.134 + local result_text 91.135 + 91.136 + if issue.voter_count == 0 then 91.137 + result_text = _("No votes (0)", { result = result }) 91.138 + 91.139 + elseif initiative.rank == 1 and not for_event then 91.140 + local result = "" 91.141 + if initiative.eligible then 91.142 + result = _("Reached #{sign}#{num}/#{den}", { 91.143 + sign = issue.policy.direct_majority_strict and ">" or "≥", 91.144 + num = issue.policy.direct_majority_num, 91.145 + den = issue.policy.direct_majority_den 91.146 + }) 91.147 + else 91.148 + result = _("Failed #{sign}#{num}/#{den}", { 91.149 + sign = issue.policy.direct_majority_strict and ">" or "≥", 91.150 + num = issue.policy.direct_majority_num, 91.151 + den = issue.policy.direct_majority_den 91.152 + }) 91.153 + end 91.154 + local neutral_count = issue.voter_count - initiative.positive_votes - initiative.negative_votes 91.155 + 91.156 + result_text = _("#{result}: #{yes_count} Yes (#{yes_percent}), #{no_count} No (#{no_percent}), #{neutral_count} Abstention (#{neutral_percent})", { 91.157 + result = result, 91.158 + yes_count = initiative.positive_votes, 91.159 + yes_percent = format.percent_floor(initiative.positive_votes, issue.voter_count), 91.160 + neutral_count = neutral_count, 91.161 + neutral_percent = format.percent_floor(neutral_count, issue.voter_count), 91.162 + no_count = initiative.negative_votes, 91.163 + no_percent = format.percent_floor(initiative.negative_votes, issue.voter_count) 91.164 + }) 91.165 + 91.166 + end 91.167 + 91.168 + ui.container { attr = { class = "result" }, content = result_text } 91.169 + 91.170 + end 91.171 + 91.172 + end } 91.173 91.174 end 91.175 } 91.176 91.177 -if initiative.rank == 1 91.178 - and issue.voter_count 91.179 - and initiative.positive_votes ~= nil 91.180 - and initiative.negative_votes ~= nil 91.181 - and not for_event 91.182 -then 91.183 - local result = "" 91.184 - if initiative.eligible then 91.185 - result = _("Reached #{sign}#{num}/#{den}", { 91.186 - sign = issue.policy.direct_majority_strict and ">" or "≥", 91.187 - num = issue.policy.direct_majority_num, 91.188 - den = issue.policy.direct_majority_den 91.189 - }) 91.190 - else 91.191 - result = _("Failed #{sign}#{num}/#{den}", { 91.192 - sign = issue.policy.direct_majority_strict and ">" or "≥", 91.193 - num = issue.policy.direct_majority_num, 91.194 - den = issue.policy.direct_majority_den 91.195 - }) 91.196 - end 91.197 - local neutral_count = issue.voter_count - initiative.positive_votes - initiative.negative_votes 91.198 - 91.199 - local result_text 91.200 - 91.201 - if issue.voter_count > 0 then 91.202 - result_text = _("#{result}: #{yes_count} Yes (#{yes_percent}), #{no_count} No (#{no_percent}), #{neutral_count} Abstention (#{neutral_percent})", { 91.203 - result = result, 91.204 - yes_count = initiative.positive_votes, 91.205 - yes_percent = format.percent_floor(initiative.positive_votes, issue.voter_count), 91.206 - neutral_count = neutral_count, 91.207 - neutral_percent = format.percent_floor(neutral_count, issue.voter_count), 91.208 - no_count = initiative.negative_votes, 91.209 - no_percent = format.percent_floor(initiative.negative_votes, issue.voter_count) 91.210 - }) 91.211 - else 91.212 - result_text = _("#{result}: No votes (0)", { result = result }) 91.213 - end 91.214 - 91.215 - ui.container { attr = { class = "result" }, content = result_text } 91.216 - 91.217 -end 91.218 -
92.1 --- a/app/main/initiative/_sidebar_history.lua Thu Jun 23 03:30:57 2016 +0200 92.2 +++ b/app/main/initiative/_sidebar_history.lua Sun Jul 15 14:07:29 2018 +0200 92.3 @@ -7,7 +7,7 @@ 92.4 ui.heading { level = 1, content = _"History" } 92.5 end ) 92.6 execute.view { 92.7 - module = "issue", view = "_list2", params = { 92.8 + module = "issue", view = "_list", params = { 92.9 for_initiative = initiative, for_sidebar = true 92.10 } 92.11 }
93.1 --- a/app/main/initiative/_sidebar_state.lua Thu Jun 23 03:30:57 2016 +0200 93.2 +++ b/app/main/initiative/_sidebar_state.lua Sun Jul 15 14:07:29 2018 +0200 93.3 @@ -15,6 +15,9 @@ 93.4 negative_votes - 93.5 positive_votes 93.6 local head_text 93.7 + 93.8 + util.initiative_pie( initiative ) 93.9 + 93.10 if initiative.winner then 93.11 head_text = _"Approved" 93.12 elseif initiative.rank then 93.13 @@ -23,8 +26,6 @@ 93.14 head_text = _"Rejected" 93.15 end 93.16 93.17 - util.initiative_pie( initiative ) 93.18 - 93.19 ui.heading { level = 1, content = head_text } 93.20 93.21 ui.tag { tag = "table", content = function () 93.22 @@ -38,6 +39,16 @@ 93.23 } 93.24 ui.tag { tag = "th", content = _"Yes" } 93.25 end } 93.26 + ui.tag { tag = "tr", attr = { class = "no" }, content = function () 93.27 + ui.tag { tag = "td", content = 93.28 + tostring(negative_votes) 93.29 + } 93.30 + ui.tag { tag = "th", content = _"No" } 93.31 + ui.tag { tag = "td", content = 93.32 + format.percent_floor(negative_votes, max_value) 93.33 + } 93.34 + ui.tag { tag = "th", content = _"No" } 93.35 + end } 93.36 ui.tag { tag = "tr", attr = { class = "abstention" }, content = function () 93.37 ui.tag { tag = "td", content = 93.38 tostring(abstention_votes) 93.39 @@ -48,16 +59,6 @@ 93.40 } 93.41 ui.tag { tag = "th", content = _"Abstention" } 93.42 end } 93.43 - ui.tag { tag = "tr", attr = { class = "no" }, content = function () 93.44 - ui.tag { tag = "td", content = 93.45 - tostring(negative_votes) 93.46 - } 93.47 - ui.tag { tag = "th", content = _"No" } 93.48 - ui.tag { tag = "td", content = 93.49 - format.percent_floor(negative_votes, max_value) 93.50 - } 93.51 - ui.tag { tag = "th", content = _"No" } 93.52 - end } 93.53 end } 93.54 end 93.55 } 93.56 @@ -108,4 +109,4 @@ 93.57 end 93.58 } 93.59 end 93.60 - 93.61 \ No newline at end of file 93.62 +
94.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 94.2 +++ b/app/main/initiative/_suggestions.lua Sun Jul 15 14:07:29 2018 +0200 94.3 @@ -0,0 +1,358 @@ 94.4 +local initiative = param.get("initiative", "table") 94.5 +local direct_supporter 94.6 +if app.session.member_id then 94.7 + direct_supporter = initiative.issue.member_info.own_participation and initiative.member_info.supported 94.8 +end 94.9 + 94.10 + 94.11 +ui.link { attr = { name = "suggestions" }, text = "" } 94.12 + 94.13 + 94.14 +ui.container { 94.15 + attr = { class = "section suggestions" }, 94.16 + content = function () 94.17 + 94.18 + if # ( initiative.suggestions ) > 0 then 94.19 + 94.20 + ui.heading { 94.21 + level = 1, 94.22 + content = _("Suggestions for improvement (#{count})", { count = # ( initiative.suggestions ) } ) 94.23 + } 94.24 + ui.container { content = _"written and rated by the supportes of this initiative to improve the proposal and its reasons" } 94.25 + slot.put("<br />") 94.26 + 94.27 + for i, suggestion in ipairs(initiative.suggestions) do 94.28 + 94.29 + local opinion = Opinion:by_pk(app.session.member_id, suggestion.id) 94.30 + 94.31 + local class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp not-folded" 94.32 + if suggestion.id == param.get("suggestion_id", atom.number) then 94.33 + class = class .. " highlighted" 94.34 + end 94.35 + if member and not initiative.issue.fully_frozen and not initiative.issue.closed and initiative.member_info.supported then 94.36 + class = class .. " rateable" 94.37 + end 94.38 + 94.39 + ui.link { attr = { name = "s" .. suggestion.id }, text = "" } 94.40 + ui.tag { tag = "div", attr = { class = class, id = "s" .. suggestion.id }, content = function () 94.41 + ui.tag{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 94.42 + ui.heading { level = 2, 94.43 + attr = { class = "mdl-card__title-text" }, 94.44 + content = format.string(suggestion.name, { 94.45 + truncate_at = 160, truncate_suffix = true 94.46 + }) } 94.47 + 94.48 + if opinion then 94.49 + 94.50 + ui.container { attr = { class = "mdl-card__content"}, content = function() 94.51 + local class = "" 94.52 + local text = "" 94.53 + 94.54 + if opinion.degree == 2 then 94.55 + class = "must" 94.56 + text = _"must" 94.57 + elseif opinion.degree == 1 then 94.58 + class = "should" 94.59 + text = _"should" 94.60 + elseif opinion.degree == 0 then 94.61 + class = "neutral" 94.62 + text = _"neutral" 94.63 + elseif opinion.degree == -1 then 94.64 + class = "shouldnot" 94.65 + text = _"should not" 94.66 + elseif opinion.degree == -2 then 94.67 + class = "mustnot" 94.68 + text = _"must not" 94.69 + end 94.70 + 94.71 + ui.tag { 94.72 + attr = { class = class }, 94.73 + content = text 94.74 + } 94.75 + 94.76 + slot.put ( " " ) 94.77 + 94.78 + if 94.79 + (opinion.degree > 0 and not opinion.fulfilled) 94.80 + or (opinion.degree < 0 and opinion.fulfilled) 94.81 + then 94.82 + ui.tag{ content = _"but" } 94.83 + else 94.84 + ui.tag{ content = _"and" } 94.85 + end 94.86 + 94.87 + slot.put ( " " ) 94.88 + 94.89 + local class = "" 94.90 + local text = "" 94.91 + 94.92 + if opinion.fulfilled then 94.93 + class = "implemented" 94.94 + text = _"is implemented" 94.95 + else 94.96 + class = "notimplemented" 94.97 + text = _"is not implemented" 94.98 + end 94.99 + 94.100 + ui.tag { 94.101 + attr = { class = class }, 94.102 + content = text 94.103 + } 94.104 + 94.105 + if 94.106 + (opinion.degree > 0 and not opinion.fulfilled) 94.107 + or (opinion.degree < 0 and opinion.fulfilled) 94.108 + then 94.109 + if math.abs(opinion.degree) > 1 then 94.110 + slot.put(" !!") 94.111 + else 94.112 + slot.put(" !") 94.113 + end 94.114 + else 94.115 + slot.put(" ✓") 94.116 + end 94.117 + 94.118 + end } 94.119 + 94.120 + end 94.121 + 94.122 + end } 94.123 + 94.124 + ui.container{ attr = { class = "suggestion-content" }, content = function() 94.125 + 94.126 + local plus2 = (suggestion.plus2_unfulfilled_count or 0) 94.127 + + (suggestion.plus2_fulfilled_count or 0) 94.128 + local plus1 = (suggestion.plus1_unfulfilled_count or 0) 94.129 + + (suggestion.plus1_fulfilled_count or 0) 94.130 + local minus1 = (suggestion.minus1_unfulfilled_count or 0) 94.131 + + (suggestion.minus1_fulfilled_count or 0) 94.132 + local minus2 = (suggestion.minus2_unfulfilled_count or 0) 94.133 + + (suggestion.minus2_fulfilled_count or 0) 94.134 + 94.135 + local with_opinion = plus2 + plus1 + minus1 + minus2 94.136 + 94.137 + local neutral = (suggestion.initiative.supporter_count or 0) 94.138 + - with_opinion 94.139 + 94.140 + local neutral2 = with_opinion 94.141 + - (suggestion.plus2_fulfilled_count or 0) 94.142 + - (suggestion.plus1_fulfilled_count or 0) 94.143 + - (suggestion.minus1_fulfilled_count or 0) 94.144 + - (suggestion.minus2_fulfilled_count or 0) 94.145 + 94.146 + ui.container { 94.147 + attr = { class = "mdl-card__content mdl-card--border suggestionInfo" }, 94.148 + content = function () 94.149 + 94.150 + if app.session:has_access("authors_pseudonymous") then 94.151 + util.micro_avatar ( suggestion.author ) 94.152 + end 94.153 + 94.154 + if with_opinion > 0 then 94.155 + ui.container { attr = { class = "suggestion-rating" }, content = function () 94.156 + ui.tag { content = _"collective rating:" } 94.157 + slot.put(" ") 94.158 + ui.bargraph{ 94.159 + max_value = suggestion.initiative.supporter_count, 94.160 + width = 100, 94.161 + bars = { 94.162 + { color = "#0a0", value = plus2 }, 94.163 + { color = "#8a8", value = plus1 }, 94.164 + { color = "#eee", value = neutral }, 94.165 + { color = "#a88", value = minus1 }, 94.166 + { color = "#a00", value = minus2 }, 94.167 + } 94.168 + } 94.169 + slot.put(" | ") 94.170 + ui.tag { content = _"implemented:" } 94.171 + slot.put ( " " ) 94.172 + ui.bargraph{ 94.173 + max_value = with_opinion, 94.174 + width = 100, 94.175 + bars = { 94.176 + { color = "#0a0", value = suggestion.plus2_fulfilled_count }, 94.177 + { color = "#8a8", value = suggestion.plus1_fulfilled_count }, 94.178 + { color = "#eee", value = neutral2 }, 94.179 + { color = "#a88", value = suggestion.minus1_fulfilled_count }, 94.180 + { color = "#a00", value = suggestion.minus2_fulfilled_count }, 94.181 + } 94.182 + } 94.183 + end } 94.184 + end 94.185 + 94.186 + end 94.187 + } 94.188 + 94.189 + ui.container { 94.190 + attr = { class = "mdl-card__content mdl-card--border suggestion-text draft" }, 94.191 + content = function () 94.192 + slot.put ( suggestion:get_content( "html" ) ) 94.193 + 94.194 + end 94.195 + } 94.196 + 94.197 + if direct_supporter then 94.198 + ui.container{ attr = { class = "mdl-card__content rating" }, content = function () 94.199 + 94.200 + if not opinion then 94.201 + opinion = {} 94.202 + end 94.203 + ui.form { 94.204 + module = "opinion", action = "update", params = { 94.205 + suggestion_id = suggestion.id 94.206 + }, 94.207 + routing = { default = { 94.208 + mode = "redirect", 94.209 + module = "initiative", view = "show", id = suggestion.initiative_id, 94.210 + params = { suggestion_id = suggestion.id }, 94.211 + anchor = "s" .. suggestion.id -- TODO webmcp 94.212 + } }, 94.213 + content = function () 94.214 + 94.215 + ui.container{ attr = { class = "opinon-question" }, content = _"Should the initiator implement this suggestion?" } 94.216 + ui.container { content = function () 94.217 + 94.218 + local options = { 94.219 + { degree = 2, label = _"must" }, 94.220 + { degree = 1, label = _"should" }, 94.221 + { degree = 0, label = _"neutral" }, 94.222 + { degree = -1, label = _"should not" }, 94.223 + { degree = -2, label = _"must not" }, 94.224 + } 94.225 + 94.226 + for i, option in ipairs(options) do 94.227 + local active = opinion.degree == option.degree 94.228 + ui.tag{ 94.229 + tag = "label", 94.230 + attr = { class = "mdl-radio mdl-js-radio mdl-js-ripple-effect" }, 94.231 + ["for"] = "s" .. suggestion.id .. "_degree" .. option.degree, 94.232 + content = function() 94.233 + ui.tag{ 94.234 + tag = "input", 94.235 + attr = { 94.236 + class = "mdl-radio__button", 94.237 + type = "radio", 94.238 + name = "degree", 94.239 + value = option.degree, 94.240 + id = "s" .. suggestion.id .. "_degree" .. option.degree, 94.241 + checked = active and "checked" or nil 94.242 + } 94.243 + } 94.244 + ui.tag{ 94.245 + attr = { class = "mdl-radio__label" }, 94.246 + content = option.label 94.247 + } 94.248 + end 94.249 + } 94.250 + slot.put(" ") 94.251 + end 94.252 + end } 94.253 + 94.254 + slot.put("<br />") 94.255 + 94.256 + ui.container{ attr = { class = "opinon-question" }, content = _"Did the initiator implement this suggestion?" } 94.257 + ui.container { content = function () 94.258 + 94.259 + local options = { 94.260 + { degree = "false", id = "notfulfilled", label = _"No (not yet)" }, 94.261 + { degree = "true", id = "fulfilled", label = _"Yes, it's implemented" }, 94.262 + } 94.263 + 94.264 + for i, option in ipairs(options) do 94.265 + local active = opinion.fulfilled == (option.degree == "true" and true or false) 94.266 + ui.tag{ 94.267 + tag = "label", 94.268 + attr = { class = "mdl-radio mdl-js-radio mdl-js-ripple-effect" }, 94.269 + ["for"] = "s" .. suggestion.id .. "_" .. option.id, 94.270 + content = function() 94.271 + ui.tag{ 94.272 + tag = "input", 94.273 + attr = { 94.274 + class = "mdl-radio__button", 94.275 + type = "radio", 94.276 + name = "fulfilled", 94.277 + value = option.degree, 94.278 + id = "s" .. suggestion.id .. "_" .. option.id, 94.279 + checked = active and "checked" or nil 94.280 + } 94.281 + } 94.282 + ui.tag{ 94.283 + attr = { class = "mdl-radio__label" }, 94.284 + content = option.label 94.285 + } 94.286 + end 94.287 + } 94.288 + slot.put(" ") 94.289 + end 94.290 + end } 94.291 + 94.292 + slot.put("<br />") 94.293 + 94.294 + ui.tag{ 94.295 + tag = "input", 94.296 + attr = { 94.297 + type = "submit", 94.298 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 94.299 + value = _"publish my rating" 94.300 + }, 94.301 + content = "" 94.302 + } 94.303 + 94.304 + end 94.305 + } 94.306 + 94.307 + end } 94.308 + end 94.309 + 94.310 + end } 94.311 + 94.312 + ui.container { attr = { class = "rating-button" }, content = function() 94.313 + 94.314 + local text = _"Read more" 94.315 + 94.316 + if direct_supporter then 94.317 + text = text .. " / " .. _"Rate suggestion" 94.318 + end 94.319 + 94.320 + ui.link { 94.321 + attr = { 94.322 + class = "mdl-button mdl-js-button suggestion-more", 94.323 + onclick = "document.querySelector('#s" .. suggestion.id .. "').classList.remove('folded');document.querySelector('#s" .. suggestion.id .. "').classList.add('unfolded'); return false;" 94.324 + }, 94.325 + text = text 94.326 + } 94.327 + 94.328 + ui.link { 94.329 + attr = { 94.330 + class = "mdl-button suggestion-less", 94.331 + onclick = "document.querySelector('#s" .. suggestion.id .. "').classList.add('folded');document.querySelector('#s" .. suggestion.id .. "').classList.remove('unfolded'); return false;" 94.332 + }, 94.333 + text = _"Show less" 94.334 + } 94.335 + --[[ 94.336 + ui.link{ 94.337 + attr = { class = "mdl-button" }, 94.338 + content = _"Details", 94.339 + module = "suggestion", view = "show", id = suggestion.id 94.340 + } 94.341 + --]] 94.342 + end } 94.343 + ui.script{ script = [[ 94.344 + window.addEventListener("load", function() { 94.345 + var textEl = document.querySelector('#s]] .. suggestion.id .. [[ .suggestion-content'); 94.346 + var height = textEl.clientHeight; 94.347 + if (height > 180) { 94.348 + document.querySelector('#s]] .. suggestion.id .. [[').classList.add('folded'); 94.349 + document.querySelector('#s]] .. suggestion.id .. [[ .rating-button').classList.add('mdl-card__actions'); 94.350 + document.querySelector('#s]] .. suggestion.id .. [[ .rating-button').classList.add('mdl-card--border'); 94.351 + } 94.352 + }); 94.353 + ]] } 94.354 + 94.355 + end } 94.356 + 94.357 + end -- for i, suggestion 94.358 + 94.359 + end -- if #initiative.suggestions > 0 94.360 + end 94.361 +}
95.1 --- a/app/main/initiative/history.lua Thu Jun 23 03:30:57 2016 +0200 95.2 +++ b/app/main/initiative/history.lua Sun Jul 15 14:07:29 2018 +0200 95.3 @@ -4,125 +4,127 @@ 95.4 initiative.issue:load_everything_for_member_id(app.session.member_id) 95.5 95.6 95.7 -execute.view{ module = "issue", view = "_sidebar_state", params = { 95.8 - initiative = initiative 95.9 -} } 95.10 +ui.grid{ content = function() 95.11 95.12 -execute.view { 95.13 - module = "issue", view = "_sidebar_issue", 95.14 - params = { 95.15 - issue = initiative.issue, 95.16 - highlight_initiative_id = initiative.id 95.17 - } 95.18 -} 95.19 - 95.20 -execute.view { 95.21 - module = "issue", view = "_sidebar_whatcanido", 95.22 - params = { initiative = initiative } 95.23 -} 95.24 - 95.25 -execute.view { 95.26 - module = "issue", view = "_sidebar_members", params = { 95.27 - issue = initiative.issue, initiative = initiative 95.28 - } 95.29 -} 95.30 + ui.cell_main{ content = function() 95.31 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 95.32 95.33 - 95.34 - 95.35 -execute.view { 95.36 - module = "issue", view = "_head", params = { 95.37 - issue = initiative.issue 95.38 - } 95.39 -} 95.40 - 95.41 -ui.form{ 95.42 - method = "get", 95.43 - module = "draft", 95.44 - view = "diff", 95.45 - attr = { class = "section" }, 95.46 - content = function() 95.47 - ui.field.hidden{ name = "initiative_id", value = initiative.id } 95.48 - 95.49 - ui.sectionHead( function() 95.50 - ui.link{ 95.51 - module = "initiative", view = "show", id = initiative.id, 95.52 - content = function () 95.53 - ui.heading { 95.54 - level = 1, 95.55 - content = initiative.display_name 95.56 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function () 95.57 + ui.heading { 95.58 + attr = { class = "mdl-card__title-text" }, 95.59 + content = function() 95.60 + ui.link{ 95.61 + module = "initiative", view = "show", id = initiative.id, 95.62 + content = initiative.display_name 95.63 + } 95.64 + end 95.65 + } 95.66 + ui.container { content = _"Draft history" } 95.67 + end } 95.68 + 95.69 + ui.container { 95.70 + attr = { class = "mdl-card__content" }, 95.71 + content = function() 95.72 + ui.form{ 95.73 + method = "get", 95.74 + module = "draft", 95.75 + view = "diff", 95.76 + attr = { class = "section" }, 95.77 + content = function() 95.78 + ui.field.hidden{ name = "initiative_id", value = initiative.id } 95.79 + 95.80 + ui.sectionRow( function() 95.81 + 95.82 + local columns = { 95.83 + { 95.84 + label = _"draft ID", 95.85 + content = function(record) 95.86 + ui.tag { content = record.id } 95.87 + end 95.88 + }, 95.89 + { 95.90 + label = _"published at", 95.91 + content = function(record) 95.92 + ui.link{ 95.93 + attr = { class = "action" }, 95.94 + module = "draft", view = "show", id = record.id, 95.95 + text = format.timestamp(record.created) 95.96 + } 95.97 + end 95.98 + }, 95.99 + { 95.100 + label = _"compare", 95.101 + content = function(record) 95.102 + slot.put('<label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="old_draft_id_' .. record.id .. '"><input type="radio" class="mdl-radio__button" id="old_draft_id_' .. record.id .. '" name="old_draft_id" value="' .. tostring(record.id) .. '"></label>') 95.103 + slot.put('<label class="mdl-radio mdl-js-radio mdl-js-ripple-effect" for="new_draft_id_' .. record.id .. '"><input type="radio" class="mdl-radio__button" id="new_draft_id_' .. record.id .. '" name="new_draft_id" value="' .. tostring(record.id) .. '"></label>') 95.104 + --lot.put('<input type="radio" name="new_draft_id" value="' .. tostring(record.id) .. '">') 95.105 + end 95.106 + } 95.107 + } 95.108 + 95.109 + if app.session:has_access("authors_pseudonymous") then 95.110 + columns[#columns+1] = { 95.111 + label = _"author", 95.112 + content = function(record) 95.113 + if record.author then 95.114 + return util.micro_avatar ( record.author ) 95.115 + end 95.116 + end 95.117 + } 95.118 + end 95.119 + 95.120 + if config.render_external_reference and config.render_external_reference.draft then 95.121 + columns[#columns+1] = { 95.122 + label = _"external reference", 95.123 + content = function(draft) 95.124 + config.render_external_reference.draft(draft, function (callback) 95.125 + callback() 95.126 + end) 95.127 + end 95.128 + } 95.129 + end 95.130 + 95.131 + ui.list{ 95.132 + records = initiative.drafts, 95.133 + columns = columns 95.134 + } 95.135 + 95.136 + slot.put("<br />") 95.137 + ui.container { attr = { class = "actions" }, content = function() 95.138 + ui.tag{ 95.139 + tag = "input", 95.140 + attr = { 95.141 + type = "submit", 95.142 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 95.143 + value = _"compare revisions" 95.144 + }, 95.145 + content = "" 95.146 + } 95.147 + end } 95.148 + end ) 95.149 + end 95.150 } 95.151 end 95.152 } 95.153 - ui.heading { level = 2, content = _"Draft history" } 95.154 - end) 95.155 - 95.156 - ui.sectionRow( function() 95.157 - 95.158 - local columns = { 95.159 - { 95.160 - label = _"draft ID", 95.161 - content = function(record) 95.162 - ui.tag { content = record.id } 95.163 - end 95.164 - }, 95.165 - { 95.166 - label = _"published at", 95.167 - content = function(record) 95.168 - ui.link{ 95.169 - attr = { class = "action" }, 95.170 - module = "draft", view = "show", id = record.id, 95.171 - text = format.timestamp(record.created) 95.172 - } 95.173 - end 95.174 - }, 95.175 - { 95.176 - label = _"compare", 95.177 - content = function(record) 95.178 - slot.put('<input type="radio" name="old_draft_id" value="' .. tostring(record.id) .. '">') 95.179 - slot.put('<input type="radio" name="new_draft_id" value="' .. tostring(record.id) .. '">') 95.180 - end 95.181 - } 95.182 + end } 95.183 + end } 95.184 + 95.185 + ui.cell_sidebar{ content = function() 95.186 + execute.view{ module = "issue", view = "_sidebar", params = { 95.187 + initiative = initiative, 95.188 + issue = initiative.issue 95.189 + } } 95.190 + 95.191 + execute.view { 95.192 + module = "issue", view = "_sidebar_whatcanido", 95.193 + params = { initiative = initiative } 95.194 + } 95.195 + 95.196 + execute.view { 95.197 + module = "issue", view = "_sidebar_members", params = { 95.198 + issue = initiative.issue, initiative = initiative 95.199 } 95.200 - 95.201 - if app.session:has_access("authors_pseudonymous") then 95.202 - columns[#columns+1] = { 95.203 - label = _"author", 95.204 - content = function(record) 95.205 - if record.author then 95.206 - return util.micro_avatar ( record.author ) 95.207 - end 95.208 - end 95.209 - } 95.210 - end 95.211 - 95.212 - if config.render_external_reference and config.render_external_reference.draft then 95.213 - columns[#columns+1] = { 95.214 - label = _"external reference", 95.215 - content = function(draft) 95.216 - config.render_external_reference.draft(draft, function (callback) 95.217 - callback() 95.218 - end) 95.219 - end 95.220 - } 95.221 - end 95.222 - 95.223 - ui.list{ 95.224 - records = initiative.drafts, 95.225 - columns = columns 95.226 - } 95.227 - 95.228 - slot.put("<br />") 95.229 - ui.container { attr = { class = "actions" }, content = function() 95.230 - ui.tag{ 95.231 - tag = "input", 95.232 - attr = { 95.233 - type = "submit", 95.234 - class = "btn btn-default", 95.235 - value = _"compare revisions" 95.236 - }, 95.237 - content = "" 95.238 - } 95.239 - end } 95.240 - end ) 95.241 - end 95.242 -} 95.243 + } 95.244 + end } 95.245 + 95.246 +end }
96.1 --- a/app/main/initiative/new.lua Thu Jun 23 03:30:57 2016 +0200 96.2 +++ b/app/main/initiative/new.lua Sun Jul 15 14:07:29 2018 +0200 96.3 @@ -33,29 +33,21 @@ 96.4 module = "issue", view = "_head", 96.5 params = { issue = issue, member = app.session.member } 96.6 } 96.7 - execute.view { 96.8 - module = "issue", view = "_sidebar_state", 96.9 - params = { 96.10 - issue = issue 96.11 - } 96.12 - } 96.13 - execute.view { 96.14 - module = "issue", view = "_sidebar_issue", 96.15 - params = { 96.16 - issue = issue 96.17 - } 96.18 - } 96.19 else 96.20 + --[[ 96.21 execute.view { 96.22 module = "area", view = "_head", 96.23 params = { area = area, member = app.session.member } 96.24 } 96.25 + --]] 96.26 + --[[ 96.27 execute.view { 96.28 module = "initiative", view = "_sidebar_policies", 96.29 params = { 96.30 area = area, 96.31 } 96.32 } 96.33 + --]] 96.34 end 96.35 96.36 ui.form{ 96.37 @@ -67,216 +59,221 @@ 96.38 }, 96.39 attr = { class = "vertical" }, 96.40 content = function() 96.41 - 96.42 - if preview then 96.43 - ui.section( function() 96.44 - ui.sectionHead( function() 96.45 - ui.heading{ level = 1, content = encode.html(param.get("name")) } 96.46 - if not issue then 96.47 - ui.container { content = policy.name } 96.48 - end 96.49 - if param.get("free_timing") then 96.50 - ui.container { content = param.get("free_timing") } 96.51 - end 96.52 - slot.put("<br />") 96.53 - 96.54 - ui.field.hidden{ name = "formatting_engine", value = param.get("formatting_engine") } 96.55 - ui.field.hidden{ name = "policy_id", value = param.get("policy_id") } 96.56 - ui.field.hidden{ name = "name", value = param.get("name") } 96.57 - ui.field.hidden{ name = "draft", value = param.get("draft") } 96.58 - ui.field.hidden{ name = "free_timing", value = param.get("free_timing") } 96.59 - ui.field.hidden{ name = "polling", value = param.get("polling", atom.boolean) } 96.60 - local formatting_engine 96.61 - if config.enforce_formatting_engine then 96.62 - formatting_engine = config.enforce_formatting_engine 96.63 - else 96.64 - formatting_engine = param.get("formatting_engine") 96.65 - end 96.66 - ui.container{ 96.67 - attr = { class = "draft" }, 96.68 - content = function() 96.69 - slot.put(format.wiki_text(param.get("draft"), formatting_engine)) 96.70 - end 96.71 - } 96.72 - slot.put("<br />") 96.73 - 96.74 - ui.tag{ 96.75 - tag = "input", 96.76 - attr = { 96.77 - type = "submit", 96.78 - class = "btn btn-default", 96.79 - value = _'Publish now' 96.80 - }, 96.81 - content = "" 96.82 - } 96.83 - slot.put("<br />") 96.84 - slot.put("<br />") 96.85 - ui.tag{ 96.86 - tag = "input", 96.87 - attr = { 96.88 - type = "submit", 96.89 - name = "edit", 96.90 - class = "btn-link", 96.91 - value = _'Edit again' 96.92 - }, 96.93 - content = "" 96.94 - } 96.95 - slot.put(" | ") 96.96 - if issue then 96.97 - ui.link{ content = _"Cancel", module = "issue", view = "show", id = issue.id } 96.98 - else 96.99 - ui.link{ content = _"Cancel", module = "area", view = "show", id = area.id } 96.100 - end 96.101 - end ) 96.102 - end ) 96.103 - else 96.104 - 96.105 - 96.106 - execute.view{ module = "initiative", view = "_sidebar_wikisyntax" } 96.107 - 96.108 - ui.section( function() 96.109 - if preview then 96.110 - ui.sectionHead( function() 96.111 - ui.heading { level = 1, content = _"Edit again" } 96.112 - end ) 96.113 - elseif issue_id then 96.114 - ui.sectionHead( function() 96.115 - ui.heading { level = 1, content = _"Add a new competing initiative to issue" } 96.116 - end ) 96.117 - else 96.118 - ui.sectionHead( function() 96.119 - ui.heading { level = 1, content = _"Create a new issue" } 96.120 - end ) 96.121 - end 96.122 - 96.123 - ui.sectionRow( function() 96.124 - if not preview and not issue_id then 96.125 - ui.container { attr = { class = "section" }, content = _"Before creating a new issue, please check any existant issues before, if the topic is already in discussion." } 96.126 - slot.put("<br />") 96.127 - end 96.128 - if not issue_id then 96.129 - local tmp = { { id = -1, name = "" } } 96.130 - for i, allowed_policy in ipairs(area.allowed_policies) do 96.131 - if not allowed_policy.polling or app.session.member:has_polling_right_for_unit_id(area.unit_id) then 96.132 - tmp[#tmp+1] = allowed_policy 96.133 - end 96.134 + ui.grid{ content = function() 96.135 + ui.cell_main{ content = function() 96.136 + ui.container{ attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 96.137 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 96.138 + if preview then 96.139 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Preview" } 96.140 + elseif issue_id then 96.141 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"New competing initiative" } 96.142 + else 96.143 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Create a new issue" } 96.144 end 96.145 - ui.heading{ level = 2, content = _"Please choose a policy for the new issue:" } 96.146 - ui.field.select{ 96.147 - name = "policy_id", 96.148 - foreign_records = tmp, 96.149 - foreign_id = "id", 96.150 - foreign_name = "name", 96.151 - value = param.get("policy_id", atom.integer) or area.default_policy and area.default_policy.id 96.152 - } 96.153 - if policy and policy.free_timeable then 96.154 - local available_timings 96.155 - if config.free_timing and config.free_timing.available_func then 96.156 - available_timings = config.free_timing.available_func(policy) 96.157 - if available_timings == false then 96.158 - error("error in free timing config") 96.159 - end 96.160 - end 96.161 - ui.heading{ level = 4, content = _"Free timing:" } 96.162 - if available_timings then 96.163 - ui.field.select{ 96.164 - name = "free_timing", 96.165 - foreign_records = available_timings, 96.166 - foreign_id = "id", 96.167 - foreign_name = "name", 96.168 - value = param.get("free_timing") 96.169 - } 96.170 - else 96.171 - ui.field.text{ 96.172 - name = "free_timing", 96.173 - value = param.get("free_timing") 96.174 - } 96.175 - end 96.176 - end 96.177 - end 96.178 + end } 96.179 + 96.180 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 96.181 96.182 - if issue and issue.policy.polling and app.session.member:has_polling_right_for_unit_id(area.unit_id) then 96.183 - slot.put("<br />") 96.184 - ui.field.boolean{ name = "polling", label = _"No admission needed", value = polling } 96.185 - end 96.186 - 96.187 - slot.put("<br />") 96.188 - ui.heading { level = 2, content = _"Enter a title for your initiative (max. 140 chars):" } 96.189 - ui.field.text{ 96.190 - attr = { style = "width: 100%;" }, 96.191 - name = "name", 96.192 - value = param.get("name") 96.193 - } 96.194 - ui.container { content = _"The title is the figurehead of your iniative. It should be short but meaningful! As others identifies your initiative by this title, you cannot change it later!" } 96.195 96.196 - if not config.enforce_formatting_engine then 96.197 - slot.put("<br />") 96.198 - ui.heading { level = 4, content = _"Choose a formatting engine:" } 96.199 - ui.field.select{ 96.200 - name = "formatting_engine", 96.201 - foreign_records = config.formatting_engines, 96.202 - attr = {id = "formatting_engine"}, 96.203 - foreign_id = "id", 96.204 - foreign_name = "name", 96.205 - value = param.get("formatting_engine") 96.206 - } 96.207 - end 96.208 - slot.put("<br />") 96.209 + if preview then 96.210 + 96.211 + ui.section( function() 96.212 + ui.sectionHead( function() 96.213 + ui.heading{ level = 1, content = encode.html(param.get("name")) } 96.214 + if not issue then 96.215 + ui.container { content = policy.name } 96.216 + end 96.217 + if param.get("free_timing") then 96.218 + ui.container { content = param.get("free_timing") } 96.219 + end 96.220 + slot.put("<br />") 96.221 + 96.222 + local draft_text = param.get("draft") 96.223 + local draft_text = util.wysihtml_preproc(draft_text) 96.224 96.225 - ui.heading { level = 2, content = _"Enter your proposal and/or reasons:" } 96.226 - ui.field.text{ 96.227 - name = "draft", 96.228 - multiline = true, 96.229 - attr = { style = "height: 50ex; width: 100%;" }, 96.230 - value = param.get("draft") or config.draft_template or [[ 96.231 -Proposal 96.232 -====== 96.233 - 96.234 -Replace me with your proposal. 96.235 - 96.236 - 96.237 -Reasons 96.238 -====== 96.239 - 96.240 -Argument 1 96.241 ------- 96.242 - 96.243 -Replace me with your first argument 96.244 - 96.245 + ui.field.hidden{ name = "policy_id", value = param.get("policy_id") } 96.246 + ui.field.hidden{ name = "name", value = param.get("name") } 96.247 + if config.initiative_abstract then 96.248 + ui.field.hidden{ name = "abstract", value = param.get("abstract") } 96.249 + ui.container{ 96.250 + attr = { class = "abstract" }, 96.251 + content = param.get("abstract") 96.252 + } 96.253 + slot.put("<br />") 96.254 + end 96.255 + ui.field.hidden{ name = "draft", value = draft_text } 96.256 + ui.field.hidden{ name = "free_timing", value = param.get("free_timing") } 96.257 + ui.field.hidden{ name = "polling", value = param.get("polling", atom.boolean) } 96.258 + ui.field.hidden{ name = "location", value = param.get("location") } 96.259 + local formatting_engine 96.260 + if config.enforce_formatting_engine then 96.261 + formatting_engine = config.enforce_formatting_engine 96.262 + else 96.263 + formatting_engine = param.get("formatting_engine") 96.264 + end 96.265 + ui.container{ 96.266 + attr = { class = "draft" }, 96.267 + content = function() 96.268 + slot.put(draft_text) 96.269 + end 96.270 + } 96.271 + slot.put("<br />") 96.272 96.273 -Argument 2 96.274 ------- 96.275 - 96.276 -Replace me with your second argument 96.277 + ui.tag{ 96.278 + tag = "input", 96.279 + attr = { 96.280 + type = "submit", 96.281 + class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored", 96.282 + value = _'Publish now' 96.283 + }, 96.284 + content = "" 96.285 + } 96.286 + slot.put(" ") 96.287 + ui.tag{ 96.288 + tag = "input", 96.289 + attr = { 96.290 + type = "submit", 96.291 + name = "edit", 96.292 + class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect", 96.293 + value = _'Edit again' 96.294 + }, 96.295 + content = "" 96.296 + } 96.297 + slot.put(" ") 96.298 + local class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" 96.299 + if issue then 96.300 + ui.link{ content = _"Cancel", module = "issue", view = "show", id = issue.id, attr = { class = class } } 96.301 + else 96.302 + ui.link{ content = _"Cancel", module = "index", view = "index", params = { unit = area.unit_id, area = area.id }, attr = { class = class } } 96.303 + end 96.304 + end ) 96.305 + end ) 96.306 + else 96.307 + 96.308 + ui.sectionRow( function() 96.309 + --[[ 96.310 + if not preview and not issue_id then 96.311 + ui.container { attr = { class = "section" }, content = _"Before creating a new issue, please check any existant issues before, if the topic is already in discussion." } 96.312 + slot.put("<br />") 96.313 + end 96.314 + --]] 96.315 + if not issue_id then 96.316 + local tmp = { { id = -1, name = "" } } 96.317 + for i, allowed_policy in ipairs(area.allowed_policies) do 96.318 + if not allowed_policy.polling or app.session.member:has_polling_right_for_unit_id(area.unit_id) then 96.319 + tmp[#tmp+1] = allowed_policy 96.320 + end 96.321 + end 96.322 + ui.container{ content = _"Please choose a policy for the new issue:" } 96.323 + ui.field.select{ 96.324 + name = "policy_id", 96.325 + foreign_records = tmp, 96.326 + foreign_id = "id", 96.327 + foreign_name = "name", 96.328 + value = param.get("policy_id", atom.integer) or area.default_policy and area.default_policy.id 96.329 + } 96.330 + if policy and policy.free_timeable then 96.331 + local available_timings 96.332 + if config.free_timing and config.free_timing.available_func then 96.333 + available_timings = config.free_timing.available_func(policy) 96.334 + if available_timings == false then 96.335 + slot.put_into("error", "error in free timing config") 96.336 + return false 96.337 + end 96.338 + end 96.339 + ui.heading{ level = 4, content = _"Free timing:" } 96.340 + if available_timings then 96.341 + ui.field.select{ 96.342 + name = "free_timing", 96.343 + foreign_records = available_timings, 96.344 + foreign_id = "id", 96.345 + foreign_name = "name", 96.346 + value = param.get("free_timing") 96.347 + } 96.348 + else 96.349 + ui.field.text{ 96.350 + name = "free_timing", 96.351 + value = param.get("free_timing") 96.352 + } 96.353 + end 96.354 + end 96.355 + end 96.356 96.357 -]] 96.358 - } 96.359 - if not issue or issue.state == "admission" or issue.state == "discussion" then 96.360 - ui.container { content = _"You can change your text again anytime during admission and discussion phase" } 96.361 - else 96.362 - ui.container { content = _"You cannot change your text again later, because this issue is already in verfication phase!" } 96.363 - end 96.364 - slot.put("<br />") 96.365 - ui.tag{ 96.366 - tag = "input", 96.367 - attr = { 96.368 - type = "submit", 96.369 - name = "preview", 96.370 - class = "btn btn-default", 96.371 - value = _'Preview' 96.372 - }, 96.373 - content = "" 96.374 - } 96.375 - slot.put("<br />") 96.376 - slot.put("<br />") 96.377 - 96.378 - if issue then 96.379 - ui.link{ content = _"Cancel", module = "issue", view = "show", id = issue.id } 96.380 - else 96.381 - ui.link{ content = _"Cancel", module = "area", view = "show", id = area.id } 96.382 - end 96.383 - end ) 96.384 - end ) 96.385 - end 96.386 + if issue and issue.policy.polling and app.session.member:has_polling_right_for_unit_id(area.unit_id) then 96.387 + slot.put("<br />") 96.388 + ui.field.boolean{ name = "polling", label = _"No admission needed", value = polling } 96.389 + end 96.390 + 96.391 + ui.container{ attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label mdl-card__fullwidth" }, content = function () 96.392 + ui.field.text{ 96.393 + attr = { id = "lf-initiative__name", class = "mdl-textfield__input" }, 96.394 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-initiative__name" }, 96.395 + label = _"Title", 96.396 + name = "name", 96.397 + value = param.get("name") 96.398 + } 96.399 + end } 96.400 + 96.401 + if config.initiative_abstract then 96.402 + ui.container { content = _"Enter abstract:" } 96.403 + ui.container{ attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--expandable mdl-textfield__fullwidth" }, content = function() 96.404 + ui.field.text{ 96.405 + name = "abstract", 96.406 + multiline = true, 96.407 + attr = { id = "abstract", style = "height: 20ex; width: 100%;" }, 96.408 + value = param.get("abstract") 96.409 + } 96.410 + end } 96.411 + end 96.412 + 96.413 + ui.container { content = _"Enter your proposal and/or reasons:" } 96.414 + ui.container{ attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--expandable mdl-textfield__fullwidth" }, content = function() 96.415 + ui.field.wysihtml{ 96.416 + name = "draft", 96.417 + multiline = true, 96.418 + attr = { id = "draft", style = "height: 50ex; width: 100%;" }, 96.419 + value = param.get("draft") or config.draft_template 96.420 + } 96.421 + end } 96.422 + if not issue or issue.state == "admission" or issue.state == "discussion" then 96.423 + ui.container { content = _"You can change your text again anytime during admission and discussion phase" } 96.424 + else 96.425 + ui.container { content = _"You cannot change your text again later, because this issue is already in verfication phase!" } 96.426 + end 96.427 + slot.put("<br />") 96.428 + 96.429 + slot.put("<br />") 96.430 + ui.tag{ 96.431 + tag = "input", 96.432 + attr = { 96.433 + type = "submit", 96.434 + name = "preview", 96.435 + class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored", 96.436 + value = _'Preview' 96.437 + }, 96.438 + content = "" 96.439 + } 96.440 + slot.put(" ") 96.441 + 96.442 + local class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect" 96.443 + if issue then 96.444 + 96.445 + ui.link{ content = _"Cancel", module = "issue", view = "show", id = issue.id, attr = { class = class } } 96.446 + else 96.447 + ui.link{ content = _"Cancel", module = "index", view = "index", params = { unit = area.unit_id, area = area.id }, attr = { class = class } } 96.448 + end 96.449 + end ) 96.450 + end 96.451 + end } 96.452 + end } 96.453 + end } 96.454 + if config.map or config.firstlife then 96.455 + ui.cell_sidebar{ content = function() 96.456 + ui.container{ attr = { class = "mdl-special-card map mdl-shadow--2dp" }, content = function() 96.457 + ui.field.location{ name = "location", value = param.get("location") } 96.458 + end } 96.459 + end } 96.460 + end 96.461 + end } 96.462 end 96.463 }
97.1 --- a/app/main/initiative/remove_initiator.lua Thu Jun 23 03:30:57 2016 +0200 97.2 +++ b/app/main/initiative/remove_initiator.lua Sun Jul 15 14:07:29 2018 +0200 97.3 @@ -8,7 +8,7 @@ 97.4 97.5 local initiator = Initiator:by_pk(initiative.id, app.session.member.id) 97.6 if not initiator or initiator.accepted ~= true then 97.7 - error("access denied") 97.8 + return execute.view { module = "index", view = "403" } 97.9 end 97.10 97.11 execute.view { 97.12 @@ -106,4 +106,4 @@ 97.13 } 97.14 end ) 97.15 end 97.16 -} 97.17 \ No newline at end of file 97.18 +}
98.1 --- a/app/main/initiative/show.lua Thu Jun 23 03:30:57 2016 +0200 98.2 +++ b/app/main/initiative/show.lua Sun Jul 15 14:07:29 2018 +0200 98.3 @@ -7,6 +7,8 @@ 98.4 return 98.5 end 98.6 98.7 +app.current_initiative = initiative 98.8 + 98.9 local issue_info 98.10 98.11 if member then 98.12 @@ -15,541 +17,173 @@ 98.13 issue_info = initiative.issue.member_info 98.14 end 98.15 98.16 -execute.view { 98.17 - module = "issue", view = "_head", 98.18 - params = { 98.19 - issue = initiative.issue, 98.20 - initiative = initiative, 98.21 - member = app.session.member 98.22 - } 98.23 -} 98.24 - 98.25 local direct_supporter 98.26 98.27 if app.session.member_id then 98.28 direct_supporter = initiative.issue.member_info.own_participation and initiative.member_info.supported 98.29 end 98.30 98.31 -ui.script { script = [[ 98.32 - function showTab(tabId) { 98.33 - $('.tab').hide(); 98.34 - $('.main').hide(); 98.35 - $('.main, .slot_extra .section').hide(); 98.36 - $('.' + tabId).show(); 98.37 - if (tabId == "main") $('.slot_extra .section').show(); 98.38 - }; 98.39 - showTab('main'); 98.40 -]]} 98.41 - 98.42 -execute.view{ module = "issue", view = "_sidebar_state", params = { 98.43 - initiative = initiative 98.44 -} } 98.45 - 98.46 -execute.view { 98.47 - module = "issue", view = "_sidebar_issue", 98.48 - params = { 98.49 - issue = initiative.issue, 98.50 - highlight_initiative_id = initiative.id 98.51 - } 98.52 -} 98.53 +slot.put_into("header", initiative.display_name) 98.54 98.55 -execute.view { 98.56 - module = "issue", view = "_sidebar_whatcanido", 98.57 - params = { initiative = initiative } 98.58 -} 98.59 - 98.60 -execute.view { 98.61 - module = "issue", view = "_sidebar_members", params = { 98.62 - issue = initiative.issue, initiative = initiative 98.63 - } 98.64 -} 98.65 +execute.view{ module = "issue", view = "_head", params = { issue = initiative.issue, link_issue = true } } 98.66 98.67 -ui.section( function () 98.68 - execute.view{ 98.69 - module = "initiative", view = "_head", params = { 98.70 - initiative = initiative 98.71 - } 98.72 - } 98.73 - 98.74 - if direct_supporter and not initiative.issue.closed then 98.75 - local supporter = app.session.member:get_reference_selector("supporters") 98.76 - :add_where{ "initiative_id = ?", initiative.id } 98.77 - :optional_object_mode() 98.78 - :exec() 98.79 - 98.80 - if supporter then 98.81 +ui.grid{ content = function() 98.82 98.83 - local old_draft_id = supporter.draft_id 98.84 - local new_draft_id = initiative.current_draft.id 98.85 - 98.86 - if old_draft_id ~= new_draft_id then 98.87 - ui.sectionRow( "draft_updated_info", function () 98.88 - ui.container{ 98.89 - attr = { class = "info" }, 98.90 - content = _"The draft of this initiative has been updated!" 98.91 - } 98.92 - slot.put(" ") 98.93 - ui.link{ 98.94 - content = _"show differences", 98.95 - module = "draft", 98.96 - view = "diff", 98.97 - params = { 98.98 - old_draft_id = old_draft_id, 98.99 - new_draft_id = new_draft_id 98.100 - } 98.101 - } 98.102 - if not initiative.revoked then 98.103 - slot.put(" | ") 98.104 - ui.link{ 98.105 - text = _"refresh my support", 98.106 - module = "initiative", 98.107 - action = "add_support", 98.108 - id = initiative.id, 98.109 - params = { draft_id = initiative.current_draft.id }, 98.110 - routing = { 98.111 - default = { 98.112 - mode = "redirect", 98.113 - module = "initiative", 98.114 - view = "show", 98.115 - id = initiative.id 98.116 - } 98.117 - } 98.118 - } 98.119 - slot.put(" | ") 98.120 - end 98.121 - 98.122 - ui.link{ 98.123 - text = _"remove my support", 98.124 - module = "initiative", 98.125 - action = "remove_support", 98.126 - id = initiative.id, 98.127 - routing = { 98.128 - default = { 98.129 - mode = "redirect", 98.130 - module = "initiative", 98.131 - view = "show", 98.132 - id = initiative.id 98.133 - } 98.134 - } 98.135 - } 98.136 - 98.137 - end ) 98.138 - end 98.139 - end 98.140 - end 98.141 - 98.142 - if config.render_external_reference and config.render_external_reference.initiative then 98.143 - config.render_external_reference.initiative(initiative, function (callback) 98.144 - ui.sectionRow(callback) 98.145 - end) 98.146 - end 98.147 - 98.148 - ui.sectionRow( function () 98.149 - ui.container { 98.150 - attr = { class = "draft" }, 98.151 - content = function () 98.152 - slot.put ( initiative.current_draft:get_content ( "html" ) ) 98.153 - end 98.154 - } 98.155 - end ) 98.156 - 98.157 -end) 98.158 - 98.159 -ui.link { attr = { name = "suggestions" }, text = "" } 98.160 - 98.161 - 98.162 -ui.container { 98.163 - attr = { class = "section suggestions" }, 98.164 - content = function () 98.165 - 98.166 - if # ( initiative.suggestions ) > 0 then 98.167 - 98.168 - ui.sectionHead( function () 98.169 - ui.heading { 98.170 - level = 1, 98.171 - content = _("Suggestions for improvement (#{count})", { count = # ( initiative.suggestions ) } ) 98.172 + ui.cell_main{ content = function() 98.173 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 98.174 + execute.view{ 98.175 + module = "initiative", view = "_head", params = { 98.176 + initiative = initiative 98.177 } 98.178 - ui.container { content = _"written and rated by the supportes of this initiative to improve the proposal and its reasons" } 98.179 - end ) 98.180 - 98.181 - for i, suggestion in ipairs(initiative.suggestions) do 98.182 - 98.183 - local opinion = Opinion:by_pk(app.session.member_id, suggestion.id) 98.184 + } 98.185 98.186 - local class = "sectionRow suggestion" 98.187 - if suggestion.id == param.get("suggestion_id", atom.number) then 98.188 - class = class .. " highlighted" 98.189 - end 98.190 - if member and not initiative.issue.fully_frozen and not initiative.issue.closed and initiative.member_info.supported then 98.191 - class = class .. " rateable" 98.192 - end 98.193 - 98.194 - 98.195 - ui.tag { tag = "div", attr = { class = class, id = "s" .. suggestion.id }, content = function () 98.196 + if direct_supporter and not initiative.issue.closed then 98.197 + local supporter = app.session.member:get_reference_selector("supporters") 98.198 + :add_where{ "initiative_id = ?", initiative.id } 98.199 + :optional_object_mode() 98.200 + :exec() 98.201 + 98.202 + if supporter then 98.203 98.204 - if opinion then 98.205 - 98.206 - ui.container { attr = { class = "opinion"}, content = function() 98.207 - local class = "" 98.208 - local text = "" 98.209 - 98.210 - if opinion.degree == 2 then 98.211 - class = "must" 98.212 - text = _"must" 98.213 - elseif opinion.degree == 1 then 98.214 - class = "should" 98.215 - text = _"should" 98.216 - elseif opinion.degree == 0 then 98.217 - class = "neutral" 98.218 - text = _"neutral" 98.219 - elseif opinion.degree == -1 then 98.220 - class = "shouldnot" 98.221 - text = _"should not" 98.222 - elseif opinion.degree == -2 then 98.223 - class = "mustnot" 98.224 - text = _"must not" 98.225 - end 98.226 - 98.227 - ui.tag { 98.228 - attr = { class = class }, 98.229 - content = text 98.230 - } 98.231 - 98.232 - slot.put ( " " ) 98.233 - 98.234 - if 98.235 - (opinion.degree > 0 and not opinion.fulfilled) 98.236 - or (opinion.degree < 0 and opinion.fulfilled) 98.237 - then 98.238 - ui.tag{ content = _"but" } 98.239 - else 98.240 - ui.tag{ content = _"and" } 98.241 - end 98.242 - 98.243 - slot.put ( " " ) 98.244 - 98.245 - local class = "" 98.246 - local text = "" 98.247 - 98.248 - if opinion.fulfilled then 98.249 - class = "implemented" 98.250 - text = _"is implemented" 98.251 - else 98.252 - class = "notimplemented" 98.253 - text = _"is not implemented" 98.254 - end 98.255 - 98.256 - ui.tag { 98.257 - attr = { class = class }, 98.258 - content = text 98.259 - } 98.260 - 98.261 - if 98.262 - (opinion.degree > 0 and not opinion.fulfilled) 98.263 - or (opinion.degree < 0 and opinion.fulfilled) 98.264 - then 98.265 - if math.abs(opinion.degree) > 1 then 98.266 - slot.put(" !!") 98.267 - else 98.268 - slot.put(" !") 98.269 + local old_draft_id = supporter.draft_id 98.270 + local new_draft_id = initiative.current_draft.id 98.271 + 98.272 + if old_draft_id ~= new_draft_id then 98.273 + ui.container { 98.274 + attr = { class = "mdl-card__content mdl-card--no-bottom-pad mdl-card--notice" }, 98.275 + content = _"The draft of this initiative has been updated!" 98.276 + } 98.277 + ui.container { 98.278 + attr = { class = "mdl-card__actions mdl-card--action-border mdl-card--notice" }, 98.279 + content = function () 98.280 + if not initiative.revoked then 98.281 + ui.link{ 98.282 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 98.283 + text = _"refresh my support", 98.284 + module = "initiative", 98.285 + action = "add_support", 98.286 + id = initiative.id, 98.287 + params = { draft_id = initiative.current_draft.id }, 98.288 + routing = { 98.289 + default = { 98.290 + mode = "redirect", 98.291 + module = "initiative", 98.292 + view = "show", 98.293 + id = initiative.id 98.294 + } 98.295 + } 98.296 + } 98.297 + slot.put(" ") 98.298 + ui.link{ 98.299 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 98.300 + content = _"show differences", 98.301 + module = "draft", 98.302 + view = "diff", 98.303 + params = { 98.304 + old_draft_id = old_draft_id, 98.305 + new_draft_id = new_draft_id 98.306 + } 98.307 + } 98.308 + slot.put(" ") 98.309 end 98.310 - else 98.311 - slot.put(" ✓") 98.312 - end 98.313 - 98.314 - end } 98.315 - 98.316 - end 98.317 - 98.318 - 98.319 - ui.link { attr = { name = "s" .. suggestion.id }, text = "" } 98.320 - ui.heading { level = 2, 98.321 - attr = { class = "suggestionHead" }, 98.322 - content = format.string(suggestion.name, { 98.323 - truncate_at = 160, truncate_suffix = true 98.324 - }) } 98.325 - 98.326 - 98.327 - local plus2 = (suggestion.plus2_unfulfilled_count or 0) 98.328 - + (suggestion.plus2_fulfilled_count or 0) 98.329 - local plus1 = (suggestion.plus1_unfulfilled_count or 0) 98.330 - + (suggestion.plus1_fulfilled_count or 0) 98.331 - local minus1 = (suggestion.minus1_unfulfilled_count or 0) 98.332 - + (suggestion.minus1_fulfilled_count or 0) 98.333 - local minus2 = (suggestion.minus2_unfulfilled_count or 0) 98.334 - + (suggestion.minus2_fulfilled_count or 0) 98.335 - 98.336 - local with_opinion = plus2 + plus1 + minus1 + minus2 98.337 - 98.338 - local neutral = (suggestion.initiative.supporter_count or 0) 98.339 - - with_opinion 98.340 - 98.341 - local neutral2 = with_opinion 98.342 - - (suggestion.plus2_fulfilled_count or 0) 98.343 - - (suggestion.plus1_fulfilled_count or 0) 98.344 - - (suggestion.minus1_fulfilled_count or 0) 98.345 - - (suggestion.minus2_fulfilled_count or 0) 98.346 - 98.347 - ui.container { 98.348 - attr = { class = "suggestionInfo" }, 98.349 - content = function () 98.350 - 98.351 - if with_opinion > 0 then 98.352 - ui.container { attr = { class = "suggestion-rating" }, content = function () 98.353 - ui.tag { content = _"collective rating:" } 98.354 - slot.put(" ") 98.355 - ui.bargraph{ 98.356 - max_value = suggestion.initiative.supporter_count, 98.357 - width = 100, 98.358 - bars = { 98.359 - { color = "#0a0", value = plus2 }, 98.360 - { color = "#8a8", value = plus1 }, 98.361 - { color = "#eee", value = neutral }, 98.362 - { color = "#a88", value = minus1 }, 98.363 - { color = "#a00", value = minus2 }, 98.364 + ui.link{ 98.365 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 98.366 + text = _"remove my support", 98.367 + module = "initiative", 98.368 + action = "remove_support", 98.369 + id = initiative.id, 98.370 + routing = { 98.371 + default = { 98.372 + mode = "redirect", 98.373 + module = "initiative", 98.374 + view = "show", 98.375 + id = initiative.id 98.376 } 98.377 } 98.378 - slot.put(" | ") 98.379 - ui.tag { content = _"implemented:" } 98.380 - slot.put ( " " ) 98.381 - ui.bargraph{ 98.382 - max_value = with_opinion, 98.383 - width = 100, 98.384 - bars = { 98.385 - { color = "#0a0", value = suggestion.plus2_fulfilled_count }, 98.386 - { color = "#8a8", value = suggestion.plus1_fulfilled_count }, 98.387 - { color = "#eee", value = neutral2 }, 98.388 - { color = "#a88", value = suggestion.minus1_fulfilled_count }, 98.389 - { color = "#a00", value = suggestion.minus2_fulfilled_count }, 98.390 - } 98.391 - } 98.392 - end } 98.393 - end 98.394 - 98.395 - if app.session:has_access("authors_pseudonymous") then 98.396 - util.micro_avatar ( suggestion.author ) 98.397 - else 98.398 - slot.put("<br />") 98.399 - end 98.400 - 98.401 - ui.container { 98.402 - attr = { class = "suggestion-text" }, 98.403 - content = function () 98.404 - slot.put ( suggestion:get_content( "html" ) ) 98.405 - 98.406 - 98.407 - if direct_supporter then 98.408 - 98.409 - ui.container { 98.410 - attr = { class = "rating" }, 98.411 - content = function () 98.412 - 98.413 - if not opinion then 98.414 - opinion = {} 98.415 - end 98.416 - ui.form { 98.417 - module = "opinion", action = "update", params = { 98.418 - suggestion_id = suggestion.id 98.419 - }, 98.420 - routing = { default = { 98.421 - mode = "redirect", 98.422 - module = "initiative", view = "show", id = suggestion.initiative_id, 98.423 - params = { suggestion_id = suggestion.id }, 98.424 - anchor = "s" .. suggestion.id -- TODO webmcp 98.425 - } }, 98.426 - content = function () 98.427 - 98.428 - 98.429 - ui.heading { level = 3, content = _"Should the initiator implement this suggestion?" } 98.430 - ui.container { content = function () 98.431 - 98.432 - local active = opinion.degree == 2 98.433 - ui.tag { tag = "input", attr = { 98.434 - type = "radio", name = "degree", value = 2, 98.435 - id = "s" .. suggestion.id .. "_degree2", 98.436 - checked = active and "checked" or nil 98.437 - } } 98.438 - ui.tag { 98.439 - tag = "label", 98.440 - attr = { 98.441 - ["for"] = "s" .. suggestion.id .. "_degree2", 98.442 - class = active and "active-plus2" or nil, 98.443 - }, 98.444 - content = _"must" 98.445 - } 98.446 - 98.447 - local active = opinion.degree == 1 98.448 - ui.tag { tag = "input", attr = { 98.449 - type = "radio", name = "degree", value = 1, 98.450 - id = "s" .. suggestion.id .. "_degree1", 98.451 - checked = active and "checked" or nil 98.452 - } } 98.453 - ui.tag { 98.454 - tag = "label", 98.455 - attr = { 98.456 - ["for"] = "s" .. suggestion.id .. "_degree1", 98.457 - class = active and "active-plus1" or nil, 98.458 - }, 98.459 - content = _"should" 98.460 - } 98.461 - 98.462 - local active = not opinion.member_id 98.463 - ui.tag { tag = "input", attr = { 98.464 - type = "radio", name = "degree", value = 0, 98.465 - id = "s" .. suggestion.id .. "_degree0", 98.466 - checked = active and "checked" or nil 98.467 - } } 98.468 - ui.tag { 98.469 - tag = "label", 98.470 - attr = { 98.471 - ["for"] = "s" .. suggestion.id .. "_degree0", 98.472 - class = active and "active-neutral" or nil, 98.473 - }, 98.474 - content = _"neutral" 98.475 - } 98.476 - 98.477 - local active = opinion.degree == -1 98.478 - ui.tag { tag = "input", attr = { 98.479 - type = "radio", name = "degree", value = -1, 98.480 - id = "s" .. suggestion.id .. "_degree-1", 98.481 - checked = active and "checked" or nil 98.482 - } } 98.483 - ui.tag { 98.484 - tag = "label", 98.485 - attr = { 98.486 - ["for"] = "s" .. suggestion.id .. "_degree-1", 98.487 - class = active and "active-minus1" or nil, 98.488 - }, 98.489 - content = _"should not" 98.490 - } 98.491 - 98.492 - local active = opinion.degree == -2 98.493 - ui.tag { tag = "input", attr = { 98.494 - type = "radio", name = "degree", value = -2, 98.495 - id = "s" .. suggestion.id .. "_degree-2", 98.496 - checked = active and "checked" or nil 98.497 - } } 98.498 - ui.tag { 98.499 - tag = "label", 98.500 - attr = { 98.501 - ["for"] = "s" .. suggestion.id .. "_degree-2", 98.502 - class = active and "active-minus2" or nil, 98.503 - }, 98.504 - content = _"must not" 98.505 - } 98.506 - end } 98.507 - 98.508 - slot.put("<br />") 98.509 - 98.510 - ui.heading { level = 3, content = _"Did the initiator implement this suggestion?" } 98.511 - ui.container { content = function () 98.512 - local active = opinion.fulfilled == false 98.513 - ui.tag { tag = "input", attr = { 98.514 - type = "radio", name = "fulfilled", value = "false", 98.515 - id = "s" .. suggestion.id .. "_notfulfilled", 98.516 - checked = active and "checked" or nil 98.517 - } } 98.518 - ui.tag { 98.519 - tag = "label", 98.520 - attr = { 98.521 - ["for"] = "s" .. suggestion.id .. "_notfulfilled", 98.522 - class = active and "active-notfulfilled" or nil, 98.523 - }, 98.524 - content = _"No (not yet)" 98.525 - } 98.526 - 98.527 - local active = opinion.fulfilled 98.528 - ui.tag { tag = "input", attr = { 98.529 - type = "radio", name = "fulfilled", value = "true", 98.530 - id = "s" .. suggestion.id .. "_fulfilled", 98.531 - checked = active and "checked" or nil 98.532 - } } 98.533 - ui.tag { 98.534 - tag = "label", 98.535 - attr = { 98.536 - ["for"] = "s" .. suggestion.id .. "_fulfilled", 98.537 - class = active and "active-fulfilled" or nil, 98.538 - }, 98.539 - content = _"Yes, it's implemented" 98.540 - } 98.541 - end } 98.542 - slot.put("<br />") 98.543 - 98.544 - ui.tag{ 98.545 - tag = "input", 98.546 - attr = { 98.547 - type = "submit", 98.548 - class = "btn btn-default", 98.549 - value = _"publish my rating" 98.550 - }, 98.551 - content = "" 98.552 - } 98.553 - 98.554 - end 98.555 - } 98.556 - 98.557 - end -- if not issue,fully_frozen or closed 98.558 - } 98.559 - end 98.560 - 98.561 - local text = _"Read more" 98.562 - 98.563 - if direct_supporter then 98.564 - text = _"Show more and rate this" 98.565 - end 98.566 - 98.567 - ui.link{ 98.568 - attr = { class = "suggestion-details" }, 98.569 - content = _"Details", 98.570 - module = "suggestion", view = "show", id = suggestion.id 98.571 } 98.572 98.573 - ui.link { 98.574 - attr = { 98.575 - class = "suggestion-more", 98.576 - onclick = "$('#s" .. suggestion.id .. "').removeClass('folded').addClass('unfolded'); return false;" 98.577 - }, 98.578 - text = text 98.579 - } 98.580 - 98.581 - ui.link { 98.582 - attr = { 98.583 - class = "suggestion-less", 98.584 - onclick = "$('#s" .. suggestion.id .. "').addClass('folded').removeClass('unfolded'); return false;" 98.585 - }, 98.586 - text = _"Show less" 98.587 - } 98.588 - end 98.589 - } 98.590 - 98.591 - ui.script{ script = [[ 98.592 - var textEl = $('#s]] .. suggestion.id .. [[ .suggestion-text'); 98.593 - var height = textEl.height(); 98.594 - if (height > 150) $('#s]] .. suggestion.id .. [[').addClass('folded'); 98.595 - ]] } 98.596 - 98.597 + end 98.598 + } 98.599 + end 98.600 + end 98.601 + end 98.602 + 98.603 + if config.render_external_reference and config.render_external_reference.initiative then 98.604 + config.render_external_reference.initiative(initiative, function (callback) 98.605 + ui.sectionRow(callback) 98.606 + end) 98.607 + end 98.608 + local draft_content = initiative.current_draft.content 98.609 + if config.initiative_abstract then 98.610 + local abstract = string.match(draft_content, "(.+)<!%--END_OF_ABSTRACT%-->") 98.611 + if abstract then 98.612 + draft_content = string.match(draft_content, "<!%--END_OF_ABSTRACT%-->(.*)") 98.613 + end 98.614 + end 98.615 + ui.container { 98.616 + attr = { class = "draft mdl-card__content mdl-card--border" }, 98.617 + content = function () 98.618 + if initiative.current_draft.formatting_engine == "html" or not initiative.current_draft.formatting_engine then 98.619 + if config.draft_filter then 98.620 + slot.put(config.draft_filter(draft_content)) 98.621 + else 98.622 + slot.put(draft_content) 98.623 end 98.624 - } -- ui.paragraph 98.625 - 98.626 - 98.627 - 98.628 - end } -- ui.tag "li" 98.629 - 98.630 - end -- for i, suggestion 98.631 + else 98.632 + slot.put ( initiative.current_draft:get_content ( "html" ) ) 98.633 + end 98.634 + end 98.635 + } 98.636 98.637 - else -- if #initiative.suggestions > 0 98.638 + local drafts_count = initiative:get_reference_selector("drafts"):count() 98.639 98.640 - local text 98.641 - if initiative.issue.closed then 98.642 - text = _"No suggestions" 98.643 - else 98.644 - text = _"No suggestions yet" 98.645 + if not config.voting_only then 98.646 + ui.container { 98.647 + attr = { class = "mdl-card__actions" }, 98.648 + content = function() 98.649 + ui.link{ 98.650 + attr = { class = "mdl-button mdl-js-button" }, 98.651 + module = "initiative", view = "history", id = initiative.id, 98.652 + content = _("draft history (#{count})", { count = drafts_count }) 98.653 + } 98.654 + end 98.655 + } 98.656 end 98.657 - ui.sectionHead( function() 98.658 - ui.heading { level = 1, content = text } 98.659 - end) 98.660 - 98.661 - end -- if #initiative.suggestions > 0 98.662 + 98.663 + end } 98.664 + 98.665 + execute.view{ module = "initiative", view = "_suggestions", params = { initiative = initiative } } 98.666 98.667 - end 98.668 -} 98.669 + end } 98.670 + 98.671 + ui.cell_sidebar{ content = function() 98.672 + if config.logo then 98.673 + config.logo() 98.674 + end 98.675 + execute.view { 98.676 + module = "issue", view = "_sidebar", 98.677 + params = { 98.678 + issue = initiative.issue, 98.679 + initiative = initiative, 98.680 + member = app.session.member 98.681 + } 98.682 + } 98.683 + 98.684 + execute.view { 98.685 + module = "issue", view = "_sidebar_whatcanido", 98.686 + params = { 98.687 + issue = initiative.issue, 98.688 + initiative = initiative, 98.689 + member = app.session.member 98.690 + } 98.691 + } 98.692 + 98.693 + execute.view { 98.694 + module = "issue", view = "_sidebar_members", params = { 98.695 + issue = initiative.issue, initiative = initiative 98.696 + } 98.697 + } 98.698 + 98.699 + end } 98.700 + 98.701 +end }
99.1 --- a/app/main/interest/_action/update.lua Thu Jun 23 03:30:57 2016 +0200 99.2 +++ b/app/main/interest/_action/update.lua Sun Jul 15 14:07:29 2018 +0200 99.3 @@ -5,7 +5,7 @@ 99.4 local issue = Issue:new_selector():add_where{ "id = ?", issue_id }:for_share():single_object_mode():exec() 99.5 99.6 if not app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then 99.7 - error("access denied") 99.8 + return execute.view { module = "index", view = "403" } 99.9 end 99.10 99.11 if issue.closed then 99.12 @@ -25,9 +25,9 @@ 99.13 if param.get("delete", atom.boolean) then 99.14 if interest then 99.15 interest:destroy() 99.16 - slot.put_into("notice", _"Interest removed") 99.17 +-- slot.put_into("notice", _"Interest removed") 99.18 else 99.19 - slot.put_into("notice", _"Interest already removed") 99.20 +-- slot.put_into("notice", _"Interest already removed") 99.21 end 99.22 return 99.23 end 99.24 @@ -37,5 +37,5 @@ 99.25 interest.issue_id = issue_id 99.26 interest.member_id = app.session.member_id 99.27 interest:save() 99.28 - slot.put_into("notice", _"Interest updated") 99.29 +-- slot.put_into("notice", _"Interest updated") 99.30 end
100.1 --- a/app/main/interest/show_incoming.lua Thu Jun 23 03:30:57 2016 +0200 100.2 +++ b/app/main/interest/show_incoming.lua Sun Jul 15 14:07:29 2018 +0200 100.3 @@ -1,34 +1,63 @@ 100.4 local issue = Issue:by_id(param.get("issue_id", atom.integer)) 100.5 local member = Member:by_id(param.get("member_id", atom.integer)) 100.6 100.7 +if not issue or not member then 100.8 + return execute.view { module = "index", view = "404" } 100.9 +end 100.10 + 100.11 +if app.session.member_id then 100.12 + issue:load_everything_for_member_id ( app.session.member_id ) 100.13 +end 100.14 + 100.15 + 100.16 local members_selector = Member:new_selector() 100.17 :join("delegating_interest_snapshot", nil, "delegating_interest_snapshot.member_id = member.id") 100.18 :join("issue", nil, "issue.id = delegating_interest_snapshot.issue_id") 100.19 :add_where{ "delegating_interest_snapshot.issue_id = ?", issue.id } 100.20 - :add_where{ "delegating_interest_snapshot.event = ?", issue.latest_snapshot_event } 100.21 + :add_where{ "delegating_interest_snapshot.snapshot_id = ?", issue.latest_snapshot_id } 100.22 :add_where{ "delegating_interest_snapshot.delegate_member_ids[1] = ?", member.id } 100.23 :add_field{ "delegating_interest_snapshot.weight" } 100.24 100.25 -execute.view{ 100.26 - module = "issue", view = "_head", params = { 100.27 - issue = issue 100.28 - } 100.29 -} 100.30 +execute.view{ module = "issue", view = "_head", params = { issue = issue, link_issue = true } } 100.31 100.32 -ui.section( function() 100.33 - 100.34 - ui.sectionHead( function() 100.35 - ui.heading{ level = 1, content = _("Incoming delegations for '#{member}'", { member = member.name }) } 100.36 - end) 100.37 + 100.38 +ui.grid{ content = function() 100.39 + 100.40 + ui.cell_main{ content = function() 100.41 100.42 - execute.view{ 100.43 - module = "member", 100.44 - view = "_list", 100.45 - params = { 100.46 - members_selector = members_selector, 100.47 - issue = issue, 100.48 - trustee = member 100.49 + ui.container { attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 100.50 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 100.51 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _("Incoming delegations for '#{member}'", { member = member.name }) } 100.52 + end } 100.53 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 100.54 + execute.view{ 100.55 + module = "member", 100.56 + view = "_list", 100.57 + params = { 100.58 + members_selector = members_selector, 100.59 + issue = issue, 100.60 + trustee = member 100.61 + } 100.62 + } 100.63 + end } 100.64 + end } 100.65 + end } 100.66 + 100.67 + ui.cell_sidebar{ content = function() 100.68 + execute.view { 100.69 + module = "issue", view = "_sidebar", 100.70 + params = { 100.71 + issue = issue, 100.72 + member = app.session.member 100.73 + } 100.74 } 100.75 - } 100.76 100.77 -end ) 100.78 \ No newline at end of file 100.79 + execute.view { 100.80 + module = "issue", view = "_sidebar_members", params = { 100.81 + issue = issue 100.82 + } 100.83 + } 100.84 + 100.85 + end } 100.86 + 100.87 +end }
101.1 --- a/app/main/issue/_filters.lua Thu Jun 23 03:30:57 2016 +0200 101.2 +++ b/app/main/issue/_filters.lua Sun Jul 15 14:07:29 2018 +0200 101.3 @@ -12,82 +12,195 @@ 101.4 admission_order_field = "filter_issue_order.order_in_area" 101.5 end 101.6 101.7 +local filter = { class = "filter_mode", name = "mode", label = _"mode" } 101.8 + 101.9 +filter[#filter+1] = { 101.10 + name = "issue", 101.11 + label = _"issue list", 101.12 + selector_modifier = function() end 101.13 +} 101.14 + 101.15 +filter[#filter+1] = { 101.16 + name = "timeline", 101.17 + label = _"timeline", 101.18 + selector_modifier = function() end 101.19 +} 101.20 + 101.21 +--filters[#filters+1] = filter 101.22 + 101.23 if not for_issue and not for_member then 101.24 - 101.25 - -- mode 101.26 101.27 - local filter = { class = "filter_mode", name = "mode" } 101.28 + -- units 101.29 101.30 - filter[#filter+1] = { 101.31 - name = "issue", 101.32 - label = _"issue view", 101.33 - selector_modifier = function () end 101.34 - } 101.35 - 101.36 - filter[#filter+1] = { 101.37 - name = "timeline", 101.38 - label = _"timeline", 101.39 - selector_modifier = function ( selector ) 101.40 - selector:add_order_by ( "event.occurrence DESC" ) 101.41 - selector:add_order_by ( "id DESC" ) 101.42 + if not config.single_unit_id then 101.43 + 101.44 + local units 101.45 + if app.session.member then 101.46 + units = app.session.member:get_reference_selector("units"):add_order_by("name"):add_where("active"):exec() 101.47 + else 101.48 + units = Unit:new_selector():add_where("active"):add_order_by("name"):exec() 101.49 end 101.50 - } 101.51 101.52 - filters[#filters+1] = filter 101.53 + units:load_delegation_info_once_for_member_id(app.session.member_id) 101.54 101.55 - -- context 101.56 + 101.57 + local filter = { class = "filter_unit", name = "unit", label = _"unit" } 101.58 101.59 - local filter = { class = "filter_filter", name = "filter" } 101.60 - 101.61 - if member and not for_unit and not for_area then 101.62 filter[#filter+1] = { 101.63 - name = "my_units", 101.64 - label = _"in my units", 101.65 - selector_modifier = function ( selector ) 101.66 - selector:join ( "area", "filter_area", "filter_area.id = issue.area_id" ) 101.67 - selector:join ( "privilege", "filter_privilege", { 101.68 - "filter_privilege.unit_id = filter_area.unit_id AND filter_privilege.member_id = ?", member.id 101.69 - }) 101.70 - end 101.71 + name = "all", 101.72 + label = _"all units", 101.73 + selector_modifier = function() end 101.74 } 101.75 + 101.76 + for i, unit in ipairs(units) do 101.77 + filter[#filter+1] = { 101.78 + name = tostring(unit.id), 101.79 + label = unit.name, 101.80 + selector_modifier = function(selector) 101.81 + selector:join("area", "__filter_area", "__filter_area.id = issue.area_id") 101.82 + selector:add_where{ "__filter_area.unit_id = ?", unit.id } 101.83 + end 101.84 + } 101.85 + 101.86 + end 101.87 + 101.88 + filters[#filters+1] = filter 101.89 + 101.90 end 101.91 101.92 - if member and not for_area then 101.93 + -- areas 101.94 + local selected_unit_id = config.single_unit_id or request.get_param{ name = "unit" } 101.95 + if selected_unit_id == "all" then 101.96 + selected_unit_id = nil 101.97 + end 101.98 + local selected_unit = Unit:by_id(selected_unit_id) 101.99 + 101.100 + if not config.single_area_id and selected_unit then 101.101 + 101.102 + local filter = { class = "filter_unit", name = "area", label = _"area" } 101.103 + 101.104 + filter[#filter+1] = { 101.105 + name = "all", 101.106 + label = _"all subject areas", 101.107 + selector_modifier = function() end 101.108 + } 101.109 101.110 + local areas = selected_unit.areas 101.111 + if config.area_reverse_order then 101.112 + areas = {} 101.113 + for i, area in ipairs(selected_unit.areas) do 101.114 + table.insert(areas, 1, area) 101.115 + end 101.116 + end 101.117 + 101.118 + for i, area in ipairs(areas) do 101.119 + if area.active then 101.120 + filter[#filter+1] = { 101.121 + name = tostring(area.id), 101.122 + label = area.name, 101.123 + selector_modifier = function(selector) 101.124 + if area.unit_id == selected_unit.id then 101.125 + selector:add_where{ "issue.area_id = ?", area.id } 101.126 + end 101.127 + end 101.128 + } 101.129 + end 101.130 + end 101.131 + 101.132 + filters[#filters+1] = filter 101.133 + 101.134 + end 101.135 + 101.136 + if app.session.member_id then 101.137 + 101.138 + -- interest 101.139 + 101.140 + local filter = { class = "filter_filter", name = "filter", label = _"interest" } 101.141 + 101.142 filter[#filter+1] = { 101.143 - name = "my_areas", 101.144 - label = _"in my areas", 101.145 - selector_modifier = function ( selector ) 101.146 - selector:join ( "membership", "filter_membership", { 101.147 - "filter_membership.area_id = issue.area_id AND filter_membership.member_id = ?", member.id 101.148 - }) 101.149 - end 101.150 + name = "all", 101.151 + label = _"all issues", 101.152 + selector_modifier = function() end 101.153 } 101.154 + 101.155 + if member and not for_unit and not for_area and not config.single_unit_id then 101.156 + filter[#filter+1] = { 101.157 + name = "my_units", 101.158 + label = _"in my units", 101.159 + selector_modifier = function ( selector ) 101.160 + selector:join ( "area", "filter_area", "filter_area.id = issue.area_id" ) 101.161 + selector:join ( "privilege", "filter_privilege", { 101.162 + "filter_privilege.unit_id = filter_area.unit_id AND filter_privilege.member_id = ?", member.id 101.163 + }) 101.164 + end 101.165 + } 101.166 + end 101.167 + 101.168 + if member then 101.169 + filter[#filter+1] = { 101.170 + name = "my_issues", 101.171 + label = _"my issues", 101.172 + selector_modifier = function ( selector ) 101.173 + selector:left_join("interest", "filter_interest", { "filter_interest.issue_id = issue.id AND filter_interest.member_id = ? ", member.id }) 101.174 + selector:left_join("direct_interest_snapshot", "filter_interest_s", { "filter_interest_s.issue_id = issue.id AND filter_interest_s.member_id = ? AND filter_interest_s.snapshot_id = issue.latest_snapshot_id", member.id }) 101.175 + selector:left_join("delegating_interest_snapshot", "filter_d_interest_s", { "filter_d_interest_s.issue_id = issue.id AND filter_d_interest_s.member_id = ? AND filter_d_interest_s.snapshot_id = issue.latest_snapshot_id", member.id }) 101.176 + end 101.177 + } 101.178 + end 101.179 + 101.180 + if not config.voting_only then 101.181 + filters[#filters+1] = filter 101.182 + end 101.183 + 101.184 + -- my issues 101.185 + 101.186 + if request.get_param{ name = "filter" } == "my_issues" then 101.187 + 101.188 + local delegation = request.get_param{ name = "delegation" } 101.189 + 101.190 + local filter = { class = "filter_interest subfilter", name = "interest", label = _"delegation" } 101.191 + 101.192 + filter[#filter+1] = { 101.193 + name = "all", 101.194 + label = _"interested directly or via delegation", 101.195 + selector_modifier = function ( selector ) 101.196 + selector:add_where ( "filter_interest.issue_id NOTNULL OR filter_d_interest_s.issue_id NOTNULL" ) 101.197 + end 101.198 + } 101.199 + 101.200 + filter[#filter+1] = { 101.201 + name = "direct", 101.202 + label = _"direct interest", 101.203 + selector_modifier = function ( selector ) 101.204 + selector:add_where ( "filter_interest.issue_id NOTNULL" ) 101.205 + end 101.206 + } 101.207 + 101.208 + filter[#filter+1] = { 101.209 + name = "via_delegation", 101.210 + label = _"interest via delegation", 101.211 + selector_modifier = function ( selector ) 101.212 + selector:add_where ( "filter_d_interest_s.issue_id NOTNULL" ) 101.213 + end 101.214 + } 101.215 + 101.216 + filter[#filter+1] = { 101.217 + name = "initiated", 101.218 + label = _"initiated by me", 101.219 + selector_modifier = function ( selector ) 101.220 + selector:add_where ( "filter_interest.issue_id NOTNULL" ) 101.221 + end 101.222 + } 101.223 + 101.224 + filters[#filters+1] = filter 101.225 + 101.226 + end 101.227 + 101.228 end 101.229 101.230 - if member then 101.231 - filter[#filter+1] = { 101.232 - name = "my_issues", 101.233 - label = _"my issues", 101.234 - selector_modifier = function ( selector ) 101.235 - selector:left_join("interest", "filter_interest", { "filter_interest.issue_id = issue.id AND filter_interest.member_id = ? ", member.id }) 101.236 - --selector:left_join("direct_interest_snapshot", "filter_interest_s", { "filter_interest_s.issue_id = issue.id AND filter_interest_s.member_id = ? AND filter_interest_s.event = issue.latest_snapshot_event", member.id }) 101.237 - selector:left_join("delegating_interest_snapshot", "filter_d_interest_s", { "filter_d_interest_s.issue_id = issue.id AND filter_d_interest_s.member_id = ? AND filter_d_interest_s.event = issue.latest_snapshot_event", member.id }) 101.238 - end 101.239 - } 101.240 - end 101.241 - 101.242 - filter[#filter+1] = { 101.243 - name = "all", 101.244 - label = _"all issues", 101.245 - selector_modifier = function() end 101.246 - } 101.247 - 101.248 - filters[#filters+1] = filter 101.249 - 101.250 -- phase 101.251 101.252 - local filter = { name = "phase" } 101.253 + local filter = { name = "phase", label = _"phase" } 101.254 101.255 filter[#filter+1] = { 101.256 name = "all", 101.257 @@ -106,7 +219,7 @@ 101.258 101.259 filter[#filter+1] = { 101.260 name = "admission", 101.261 - label = _"(1) Admission", 101.262 + label = _"Admission", 101.263 selector_modifier = function ( selector ) 101.264 selector:add_where { "issue.state = ?", "admission" } 101.265 if not for_events then 101.266 @@ -119,7 +232,7 @@ 101.267 101.268 filter[#filter+1] = { 101.269 name = "discussion", 101.270 - label = _"(2) Discussion", 101.271 + label = _"Discussion", 101.272 selector_modifier = function ( selector ) 101.273 selector:add_where { "issue.state = ?", "discussion" } 101.274 if not for_events then 101.275 @@ -131,7 +244,7 @@ 101.276 101.277 filter[#filter+1] = { 101.278 name = "verification", 101.279 - label = _"(3) Verification", 101.280 + label = _"Verification", 101.281 selector_modifier = function ( selector ) 101.282 selector:add_where { "issue.state = ?", "verification" } 101.283 if not for_events then 101.284 @@ -143,7 +256,7 @@ 101.285 101.286 filter[#filter+1] = { 101.287 name = "voting", 101.288 - label = _"(4) Voting", 101.289 + label = _"Voting", 101.290 selector_modifier = function ( selector ) 101.291 selector:add_where { "issue.state = ?", "voting" } 101.292 if not for_events then 101.293 @@ -155,7 +268,7 @@ 101.294 101.295 filter[#filter+1] = { 101.296 name = "closed", 101.297 - label = _"(5) Result", 101.298 + label = _"Results", 101.299 selector_modifier = function ( selector ) 101.300 if not for_events then 101.301 selector:add_where ( "issue.closed NOTNULL" ) 101.302 @@ -165,57 +278,16 @@ 101.303 end 101.304 } 101.305 101.306 - filters[#filters+1] = filter 101.307 - 101.308 - -- my issues 101.309 - 101.310 - if request.get_param{ name = "filter" } == "my_issues" then 101.311 - 101.312 - local delegation = request.get_param{ name = "delegation" } 101.313 - 101.314 - local filter = { class = "filter_interest subfilter", name = "interest" } 101.315 - 101.316 - filter[#filter+1] = { 101.317 - name = "all", 101.318 - label = _"interested directly or via delegation", 101.319 - selector_modifier = function ( selector ) 101.320 - selector:add_where ( "filter_interest.issue_id NOTNULL OR filter_d_interest_s.issue_id NOTNULL" ) 101.321 - end 101.322 - } 101.323 - 101.324 - filter[#filter+1] = { 101.325 - name = "direct", 101.326 - label = _"direct interest", 101.327 - selector_modifier = function ( selector ) 101.328 - selector:add_where ( "filter_interest.issue_id NOTNULL" ) 101.329 - end 101.330 - } 101.331 - 101.332 - filter[#filter+1] = { 101.333 - name = "via_delegation", 101.334 - label = _"interest via delegation", 101.335 - selector_modifier = function ( selector ) 101.336 - selector:add_where ( "filter_d_interest_s.issue_id NOTNULL" ) 101.337 - end 101.338 - } 101.339 - 101.340 - filter[#filter+1] = { 101.341 - name = "initiated", 101.342 - label = _"initiated by me", 101.343 - selector_modifier = function ( selector ) 101.344 - selector:add_where ( "filter_interest.issue_id NOTNULL" ) 101.345 - end 101.346 - } 101.347 - 101.348 + -- TODO 101.349 + if not config.voting_only then 101.350 filters[#filters+1] = filter 101.351 - 101.352 end 101.353 101.354 -- voting 101.355 101.356 if phase == "voting" and member then 101.357 101.358 - local filter = { class = "subfilter", name = "voted" } 101.359 + local filter = { class = "subfilter", name = "voted", label = _"voted" } 101.360 101.361 filter[#filter+1] = { 101.362 name = "all", 101.363 @@ -248,9 +320,15 @@ 101.364 101.365 if phase == "closed" then 101.366 101.367 - local filter = { class = "subfilter", name = "closed" } 101.368 + local filter = { class = "subfilter", name = "closed", label = _"closed" } 101.369 101.370 filter[#filter+1] = { 101.371 + name = "all", 101.372 + label = _"all results", 101.373 + selector_modifier = function ( selector ) end 101.374 + } 101.375 + 101.376 + filter[#filter+1] = { 101.377 name = "finished", 101.378 label = _"finished", 101.379 selector_modifier = function ( selector )
102.1 --- a/app/main/issue/_head.lua Thu Jun 23 03:30:57 2016 +0200 102.2 +++ b/app/main/issue/_head.lua Sun Jul 15 14:07:29 2018 +0200 102.3 @@ -1,74 +1,52 @@ 102.4 local issue = param.get("issue", "table") 102.5 -local initiative = param.get("initiative", "table") 102.6 +local link_issue = param.get("link_issue", atom.boolean) 102.7 102.8 -local member = param.get ( "member", "table" ) 102.9 - 102.10 +slot.put_into("header", issue.name) 102.11 102.12 ui.title ( function () 102.13 + 102.14 + if not config.single_unit_id then 102.15 + ui.link { 102.16 + attr = { class = "unit" }, 102.17 + content = function() 102.18 + ui.tag{ attr = { class = "name" }, content = issue.area.unit.name } 102.19 + end, 102.20 + module = "index", view = "index", 102.21 + params = { unit = issue.area.unit.id } 102.22 + } 102.23 102.24 - ui.tag { 102.25 - attr = { class = "unit" }, 102.26 - content = function() 102.27 - ui.link { 102.28 - content = function() 102.29 - ui.tag{ attr = { class = "name" }, content = issue.area.unit.name } 102.30 - end, 102.31 - module = "unit", view = "show", 102.32 - id = issue.area.unit.id 102.33 - } 102.34 - end 102.35 - } 102.36 - ui.tag { attr = { class = "spacer" }, content = function() 102.37 - slot.put ( " » " ) 102.38 - end } 102.39 - 102.40 - ui.tag { 102.41 - attr = { class = "area" }, 102.42 - content = function() 102.43 + ui.tag { attr = { class = "spacer" }, content = function() 102.44 + slot.put ( " » " ) 102.45 + end } 102.46 + end 102.47 + 102.48 + if not config.single_area_id then 102.49 + ui.tag { attr = { class = "area" }, content = function() 102.50 + -- area link 102.51 ui.link { 102.52 content = function() 102.53 ui.tag{ attr = { class = "name" }, content = issue.area.name } 102.54 end, 102.55 - module = "area", view = "show", 102.56 - id = issue.area.id 102.57 + module = "index", view = "index", 102.58 + params = { unit = issue.area.unit_id, area = issue.area.id } 102.59 } 102.60 - end 102.61 - } 102.62 - 102.63 - ui.tag { attr = { class = "spacer" }, content = function() 102.64 - slot.put ( " » " ) 102.65 - end } 102.66 + end } 102.67 102.68 - ui.tag { 102.69 - attr = { class = "issue" }, 102.70 - content = function() 102.71 - -- issue link 102.72 - ui.link { 102.73 - text = _("#{policy_name} ##{issue_id}", { 102.74 - policy_name = issue.policy.name, 102.75 - issue_id = issue.id 102.76 - } ), 102.77 - module = "issue", view = "show", 102.78 - id = issue.id 102.79 - } 102.80 - 102.81 - slot.put ( " " ) 102.82 - 102.83 - if member then 102.84 - execute.view { 102.85 - module = "delegation", view = "_info", params = { 102.86 - issue = issue, member = member, for_title = true 102.87 - } 102.88 - } 102.89 - end 102.90 - end 102.91 - } 102.92 - 102.93 - if initiative then 102.94 - ui.tag{ 102.95 - attr = { class = "initiative" }, 102.96 - content = initiative.display_name 102.97 - } 102.98 + ui.tag { attr = { class = "spacer" }, content = function() 102.99 + slot.put ( " » " ) 102.100 + end } 102.101 end 102.102 102.103 -end ) -- ui.title 102.104 + if link_issue then 102.105 + ui.link { 102.106 + content = function() 102.107 + ui.tag { attr = { class = "issue" }, content = issue.name } 102.108 + end, 102.109 + module = "issue", view = "show", id = issue.id 102.110 + } 102.111 + else 102.112 + ui.tag { attr = { class = "issue" }, content = issue.name } 102.113 + end 102.114 + 102.115 +end ) 102.116 +
103.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 103.2 +++ b/app/main/issue/_list.lua Sun Jul 15 14:07:29 2018 +0200 103.3 @@ -0,0 +1,425 @@ 103.4 +local for_member = param.get ( "for_member", "table" ) 103.5 +local for_unit = param.get ( "for_unit", "table" ) 103.6 +local for_area = param.get ( "for_area", "table" ) 103.7 +local for_issue = param.get ( "for_issue", "table" ) 103.8 +local for_initiative = param.get ( "for_initiative", "table" ) 103.9 +local for_sidebar = param.get("for_sidebar", atom.boolean) 103.10 +local no_filter = param.get ( "no_filter", atom.boolean ) 103.11 +local search = param.get ( "search" ) 103.12 + 103.13 +local limit = 25 103.14 + 103.15 +local mode = request.get_param{ name = "mode" } or "issue" 103.16 + 103.17 +if for_initiative or for_issue or for_member then 103.18 + mode = "timeline" 103.19 +end 103.20 + 103.21 +local selector 103.22 + 103.23 +if search then 103.24 + 103.25 + selector = Issue:get_search_selector(search) 103.26 + 103.27 + 103.28 +elseif mode == "timeline" then 103.29 + 103.30 + local event_max_id = request.get_param_strings()["event_max_id"] 103.31 + 103.32 + selector = Event:new_selector() 103.33 + :add_order_by("event.id DESC") 103.34 + :join("issue", nil, "issue.id = event.issue_id") 103.35 + :add_field("now() - event.occurrence", "time_ago") 103.36 + :limit(limit + 1) 103.37 + 103.38 + if event_max_id then 103.39 + selector:add_where{ "event.id < ?", event_max_id } 103.40 + end 103.41 + 103.42 + if for_member then 103.43 + selector:add_where{ "event.member_id = ?", for_member.id } 103.44 + end 103.45 + 103.46 + if for_initiative then 103.47 + selector:add_where{ "event.initiative_id = ?", for_initiative.id } 103.48 + end 103.49 + 103.50 + 103.51 +elseif mode == "issue" then 103.52 + 103.53 + selector = Issue:new_selector() 103.54 + 103.55 +end 103.56 + 103.57 +if for_unit then 103.58 + selector:join("area", nil, "area.id = issue.area_id") 103.59 + selector:add_where{ "area.unit_id = ?", for_unit.id } 103.60 +elseif for_area then 103.61 + selector:add_where{ "issue.area_id = ?", for_area.id } 103.62 +elseif for_issue then 103.63 + selector:add_where{ "issue.id = ?", for_issue.id } 103.64 +end 103.65 + 103.66 +if not search and app.session.member_id then 103.67 + selector 103.68 + :left_join("interest", "_interest", { 103.69 + "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id 103.70 + } ) 103.71 + :add_field("(_interest.member_id NOTNULL)", "is_interested") 103.72 + :left_join("delegating_interest_snapshot", "_delegating_interest", { [[ 103.73 + _delegating_interest.issue_id = issue.id AND 103.74 + _delegating_interest.member_id = ? AND 103.75 + _delegating_interest.snapshot_id = issue.latest_snapshot_id 103.76 + ]], app.session.member.id } ) 103.77 + :add_field("_delegating_interest.delegate_member_ids[1]", "is_interested_by_delegation_to_member_id") 103.78 + :add_field("_delegating_interest.delegate_member_ids[array_upper(_delegating_interest.delegate_member_ids, 1)]", "is_interested_via_member_id") 103.79 + :add_field("array_length(_delegating_interest.delegate_member_ids, 1)", "delegation_chain_length") 103.80 +end 103.81 + 103.82 +local function doit() 103.83 + 103.84 + local last_event_id 103.85 + 103.86 + local items = selector:exec() 103.87 + 103.88 + local row_class = "sectionRow" 103.89 + if for_sidebar then 103.90 + row_class = "sidebarRow" 103.91 + end 103.92 + 103.93 + if mode == "timeline" then 103.94 + local issues = items:load ( "issue" ) 103.95 + local initiative = items:load ( "initiative" ) 103.96 + items:load ( "suggestion" ) 103.97 + items:load ( "member" ) 103.98 + issues:load_everything_for_member_id ( app.session.member_id ) 103.99 + initiative:load_everything_for_member_id ( app.session.member_id ) 103.100 + elseif mode == "issue" then 103.101 + items:load_everything_for_member_id ( app.session.member_id ) 103.102 + end 103.103 + 103.104 + local last_event_date 103.105 + for i, item in ipairs(items) do 103.106 + local event 103.107 + local issue 103.108 + if mode == "timeline" then 103.109 + event = item 103.110 + issue = item.issue 103.111 + elseif mode == "issue" then 103.112 + event = {} 103.113 + issue = item 103.114 + end 103.115 + 103.116 + last_event_id = event.id 103.117 + 103.118 + local class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth event " .. row_class 103.119 + if event.suggestion_id then 103.120 + class = class .. " suggestion" 103.121 + end 103.122 + 103.123 + ui.container{ attr = { class = class, }, content = function() 103.124 + local event_name 103.125 + local event_icon 103.126 + local negative_event = false 103.127 + 103.128 + local days_ago_text 103.129 + 103.130 + if mode == "timeline" then 103.131 + event_name = event.event_name 103.132 + if event.event == "issue_state_changed" then 103.133 + if event.state == "discussion" then 103.134 + event_name = _"Discussion started" 103.135 + elseif event.state == "verification" then 103.136 + event_name = _"Verification started" 103.137 + elseif event.state == "voting" then 103.138 + event_name = _"Voting started" 103.139 + elseif event.state == "finished_with_winner" then 103.140 + event_name = event.state_name 103.141 + elseif event.state == "finished_without_winner" then 103.142 + event_name = event.state_name 103.143 + negative_event = true 103.144 + else 103.145 + event_name = event.state_name 103.146 + negative_event = true 103.147 + end 103.148 + elseif event.event == "initiative_revoked" then 103.149 + negative_event = true 103.150 + end 103.151 + 103.152 + if event.time_ago == 0 then 103.153 + days_ago_text = _("today at #{time}", { time = format.time(event.occurrence) }) 103.154 + elseif event.time_ago == 1 then 103.155 + days_ago_text = _("yesterday at #{time}", { time = format.time(event.occurrence) }) 103.156 + else 103.157 + days_ago_text = _("#{interval} ago", { interval = format.interval_text ( event.time_ago ) } ) 103.158 + end 103.159 + 103.160 + elseif mode == "issue" then 103.161 + local event_icons_map = { 103.162 + admission = "bubble_chart", 103.163 + discussion = "question_answer", 103.164 + verification = "find_in_page", 103.165 + voting = "mail", 103.166 + finished_with_winner = "gavel", 103.167 + finished_without_winner = "gavel", 103.168 + canceled = "clear" 103.169 + } 103.170 + event_icon = event_icons_map[issue.state] or event_icons_map["canceled"] 103.171 + event_name = issue.state_name 103.172 + if issue.state_time_left:sub(1,1) ~= "-" then 103.173 + days_ago_text = _( "#{interval_text} left", { 103.174 + interval_text = format.interval_text ( issue.state_time_left ) 103.175 + }) 103.176 + elseif issue.closed then 103.177 + days_ago_text = _( "#{interval_text} ago", { 103.178 + interval_text = format.interval_text ( issue.closed_ago ) 103.179 + }) 103.180 + else 103.181 + days_ago_text = _"phase ends soon" 103.182 + end 103.183 + if issue.closed and not issue.fully_frozen then 103.184 + negative_event = true 103.185 + end 103.186 + if issue.state == "finished_without_winner" 103.187 + or issue.state == "canceled_no_initiative_admitted" 103.188 + or issue.state == "canceled_by_admin" 103.189 + then 103.190 + negative_event = true 103.191 + end 103.192 + end 103.193 + 103.194 + local class= "event_info" 103.195 + 103.196 + if negative_event then 103.197 + class = class .. " negative" 103.198 + end 103.199 + 103.200 + if not for_issue and not for_initiative then 103.201 + ui.container{ attr = { class = "mdl-card__title mdl-card--has-fab mdl-card--border" }, content = function() 103.202 + if not (config.single_unit_id and config.single_area_id) then 103.203 + if not config.single_unit_id then 103.204 + slot.put ( " " ) 103.205 + ui.link{ 103.206 + module = "index", view = "index", params = { unit = issue.area.unit_id }, 103.207 + attr = { class = "mdl-chip unit" }, content = function() 103.208 + ui.tag{ attr = { class = "mdl-chip__text" }, content = function() 103.209 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "flag" } 103.210 + slot.put(" ") 103.211 + ui.tag{ content = issue.area.unit.name } 103.212 + end } 103.213 + end 103.214 + } 103.215 + end 103.216 + if not config.single_area_id then 103.217 + slot.put(" ") 103.218 + ui.link{ 103.219 + module = "index", view = "index", params = { unit = issue.area.unit_id, area = issue.area_id }, 103.220 + attr = { class = "mdl-chip area" }, content = function() 103.221 + ui.tag{ attr = { class = "mdl-chip__text" }, content = function() 103.222 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "folder" } 103.223 + slot.put(" ") 103.224 + ui.tag{ content = issue.area.name } 103.225 + end} 103.226 + end 103.227 + } 103.228 + end 103.229 + end 103.230 + --ui.heading{ level = 2, attr = { class = "mdl-card__title-text", style = "display: block;" }, content = function() 103.231 + slot.put(" ") 103.232 + ui.link{ 103.233 + module = "issue", view = "show", id = issue.id, 103.234 + attr = { class = "mdl-chip issue" }, content = function() 103.235 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "label" } 103.236 + slot.put(" ") 103.237 + ui.tag{ attr = { class = "mdl-chip__text" }, content = issue.name } 103.238 + end } 103.239 + --end } 103.240 + ui.container{ attr = { class = "mdl-card__subtitle-text .mdl-cell--hide-phone" }, content = function() 103.241 + ui.container{ attr = { class = class }, content = function () 103.242 + if event_icon then 103.243 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = event_icon } 103.244 + end 103.245 + slot.put(" ") 103.246 + ui.tag { content = event_name } 103.247 + slot.put(" ") 103.248 + ui.tag{ content = "(" .. days_ago_text .. ")" } 103.249 + end } 103.250 + end } 103.251 + if app.session.member and issue.fully_frozen and not issue.closed and not issue.member_info.direct_voted and app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then 103.252 + ui.link { 103.253 + attr = { class = "mdl-button mdl-js-button mdl-button--fab mdl-button--colored" , 103.254 + style = "position: absolute; right: 20px; bottom: -27px;" 103.255 + }, 103.256 + module = "vote", view = "list", 103.257 + params = { issue_id = issue.id }, 103.258 + content = function() 103.259 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = config.voting_icon or "mail_outline" } 103.260 + end 103.261 + } 103.262 + end 103.263 + end } 103.264 + end 103.265 + 103.266 + if event.suggestion_id then 103.267 + ui.container{ attr = { class = "suggestion" }, content = function() 103.268 + ui.link{ 103.269 + text = format.string(event.suggestion.name, { 103.270 + truncate_at = 160, truncate_suffix = true 103.271 + }), 103.272 + module = "initiative", view = "show", id = event.initiative.id, 103.273 + params = { suggestion_id = event.suggestion_id }, 103.274 + anchor = "s" .. event.suggestion_id 103.275 + } 103.276 + end } 103.277 + end 103.278 + 103.279 + if not for_initiative and (not for_issue or event.initiative_id) then 103.280 + 103.281 + ui.container{ attr = { class = "initiative_list" }, content = function() 103.282 + if event.initiative_id then 103.283 + local initiative = event.initiative 103.284 + 103.285 + execute.view{ module = "initiative", view = "_list", params = { 103.286 + issue = issue, 103.287 + initiative = initiative, 103.288 + for_event = mode == "timeline" and not (event.state == issue.state) 103.289 + 103.290 + } } 103.291 + else 103.292 + local initiatives = issue.initiatives 103.293 + execute.view{ module = "initiative", view = "_list", params = { 103.294 + issue = issue, 103.295 + initiatives = initiatives, 103.296 + for_event = mode == "timeline" and not (event.state == issue.state) 103.297 + } } 103.298 + end 103.299 + end } 103.300 + end 103.301 + if app.session.member_id then 103.302 + ui.container{ attr = { class = "mdl-card__actions mdl-card--border" }, content = function() 103.303 + execute.view{ 103.304 + module = "delegation", view = "_info", params = { 103.305 + issue = issue, member = for_member 103.306 + } 103.307 + } 103.308 + end } 103.309 + end 103.310 + end } 103.311 + 103.312 + end 103.313 + 103.314 + if mode == "timeline" then 103.315 + if for_sidebar then 103.316 + ui.container { attr = { class = row_class }, content = function () 103.317 + ui.link{ 103.318 + attr = { class = "moreLink" }, 103.319 + text = _"Show full history", 103.320 + module = "initiative", view = "history", id = for_initiative.id 103.321 + } 103.322 + end } 103.323 + elseif #items > limit then 103.324 + ui.container { attr = { class = row_class }, content = function () 103.325 + local params = request.get_param_strings() 103.326 + ui.link{ 103.327 + attr = { class = "moreLink" }, 103.328 + text = _"Show older events", 103.329 + module = request.get_module(), 103.330 + view = request.get_view(), 103.331 + id = for_unit and for_unit.id or for_area and for_area.id or for_issue and for_issue.id or for_member and for_member.id, 103.332 + params = { 103.333 + mode = "timeline", 103.334 + event_max_id = last_event_id, 103.335 + tab = params["tab"], 103.336 + phase = params["phase"], 103.337 + closed = params["closed"] 103.338 + } 103.339 + } 103.340 + end } 103.341 + elseif #items < 1 then 103.342 + ui.container { attr = { class = row_class }, content = _"No more events available" } 103.343 + end 103.344 + end 103.345 + 103.346 + if #items < 1 then 103.347 + ui.section( function() 103.348 + ui.sectionRow( function() 103.349 + ui.container{ content = _"No results for this selection" } 103.350 + end ) 103.351 + end ) 103.352 + end 103.353 + 103.354 + 103.355 +end 103.356 + 103.357 + 103.358 +local filters = {} 103.359 + 103.360 +if not for_initiative and not for_issue and not no_filter then 103.361 + 103.362 + filters = execute.chunk{ 103.363 + module = "issue", chunk = "_filters", params = { 103.364 + for_events = mode == "timeline" and true or false, 103.365 + member = app.session.member, 103.366 + for_member = for_member, 103.367 + state = for_state, 103.368 + for_unit = for_unit and true or false, 103.369 + for_area = for_area and true or false 103.370 + }} 103.371 +end 103.372 + 103.373 +filters.opened = true 103.374 +filters.selector = selector 103.375 + 103.376 + 103.377 +local function dotabs() 103.378 + slot.select("filter", function() 103.379 + ui.container{ attr = { class = "mdl-tabs mdl-js-tabs mdl-js-ripple-effect float-left" }, content = function() 103.380 + ui.container{ attr = { class = "mdl-tabs__tab-bar" }, content = function() 103.381 + local mode = request.get_param{ name = "mode" } 103.382 + local css_active = (not mode or mode == "issue") and " is-active" or "" 103.383 + ui.link{ module = request.get_module(), view = request.get_view(), id = request.get_id_string(), content = "Issues", attr = { class = "mdl-tabs__tab" .. css_active } } 103.384 + local css_active = mode and " is-active" or " " 103.385 + ui.link{ module = request.get_module(), view = request.get_view(), id = request.get_id_string(), params = { mode = "timeline" }, content = "Timeline", attr = { class = "mdl-tabs__tab" .. css_active } } 103.386 + ui.link{ module = "member", view = "list", content = "Member", attr = { class = "mdl-tabs__tab" } } 103.387 + end } 103.388 + end } 103.389 + end) 103.390 +end 103.391 + 103.392 + 103.393 +if mode == "timeline" then 103.394 + --dotabs() 103.395 + filters.content = function() 103.396 + execute.view{ module = "index", view = "_head" } 103.397 + doit() 103.398 + end 103.399 +else 103.400 + -- dotabs() 103.401 + filters.content = function() 103.402 + if config.voting_only then 103.403 + local admission_order_field = "filter_issue_order.order_in_unit" 103.404 + if for_area then 103.405 + admission_order_field = "filter_issue_order.order_in_area" 103.406 + end 103.407 + selector:left_join ( "issue_order_in_admission_state", "filter_issue_order", "filter_issue_order.id = issue.id" ) 103.408 + selector:add_order_by ( "issue.closed DESC NULLS FIRST" ) 103.409 + selector:add_order_by ( "issue.accepted ISNULL" ) 103.410 + selector:add_order_by ( "CASE WHEN issue.accepted ISNULL THEN NULL ELSE justify_interval(coalesce(issue.fully_frozen + issue.voting_time, issue.half_frozen + issue.verification_time, issue.accepted + issue.discussion_time, issue.created + issue.max_admission_time) - now()) END" ) 103.411 + selector:add_order_by ( "CASE WHEN issue.accepted ISNULL THEN " .. admission_order_field .. " ELSE NULL END" ) 103.412 + selector:add_order_by ( "id" ) 103.413 + end 103.414 + execute.view{ module = "index", view = "_head" } 103.415 + ui.paginate{ 103.416 + selector = selector, 103.417 + per_page = 25, 103.418 + content = doit 103.419 + } 103.420 + end 103.421 +end 103.422 + 103.423 +filters.class = "mdl-special-card mdl-card__fullwidth mdl-shadow--2dp" 103.424 + 103.425 +ui.filters(filters) 103.426 + 103.427 + 103.428 +
104.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 104.2 +++ b/app/main/issue/_sidebar.lua Sun Jul 15 14:07:29 2018 +0200 104.3 @@ -0,0 +1,133 @@ 104.4 +local issue = param.get("issue", "table") 104.5 +local initiative = param.get("initiative", "table") 104.6 + 104.7 +local member = param.get ( "member", "table" ) 104.8 + 104.9 +ui.container{ attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 104.10 + 104.11 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 104.12 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = issue.name } 104.13 + end } 104.14 + 104.15 + if issue.admin_notice then 104.16 + ui.container{ attr = { class = "mdl-card__content mdl-card--border phases" }, content = function() 104.17 + slot.put(encode.html_newlines(issue.admin_notice)) 104.18 + end } 104.19 + end 104.20 + 104.21 + ui.container{ attr = { class = "mdl-card__content mdl-card--border phases" }, content = function() 104.22 + execute.view{ module = "issue", view = "_sidebar_state", params = { 104.23 + issue = issue 104.24 + } } 104.25 + end } 104.26 + 104.27 + if app.session.member then 104.28 + if issue.fully_frozen then 104.29 + if issue.member_info.direct_voted then 104.30 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 104.31 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "mail_outline" } 104.32 + slot.put(" ") 104.33 + ui.tag { content = _"You have voted" } 104.34 + end } 104.35 + elseif active_trustee_id then 104.36 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 104.37 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "mail_outline" } 104.38 + slot.put(" ") 104.39 + ui.tag { content = _"You have voted via delegation" } 104.40 + end } 104.41 + end 104.42 + elseif not issue.closed then 104.43 + if issue.member_info.own_participation then 104.44 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 104.45 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "remove_red_eye" } 104.46 + slot.put(" ") 104.47 + ui.tag{ content = _"You are interested in this issue" } 104.48 + end } 104.49 + end 104.50 + end 104.51 + 104.52 + if not issue.closed then 104.53 + ui.container{ attr = { class = "mdl-card__actions" }, content = function() 104.54 + if issue.fully_frozen then 104.55 + if issue.member_info.direct_voted then 104.56 + if not issue.closed then 104.57 + ui.link { 104.58 + attr = { class = "mdl-button mdl-js-button" }, 104.59 + module = "vote", view = "list", 104.60 + params = { issue_id = issue.id }, 104.61 + text = _"change vote" 104.62 + } 104.63 + else 104.64 + ui.link { 104.65 + attr = { class = "mdl-button mdl-js-button" }, 104.66 + module = "vote", view = "list", 104.67 + params = { issue_id = issue.id }, 104.68 + text = _"show vote" 104.69 + } 104.70 + end 104.71 + elseif active_trustee_id then 104.72 + ui.link { 104.73 + attr = { class = "mdl-button mdl-js-button" }, 104.74 + content = _"Show voting ballot", 104.75 + module = "vote", view = "list", params = { 104.76 + issue_id = issue.id, member_id = active_trustee_id 104.77 + } 104.78 + } 104.79 + elseif not issue.closed then 104.80 + ui.link { 104.81 + attr = { class = "mdl-button mdl-js-button" }, 104.82 + module = "vote", view = "list", 104.83 + params = { issue_id = issue.id }, 104.84 + text = _"vote now" 104.85 + } 104.86 + end 104.87 + elseif not issue.closed then 104.88 + if issue.member_info.own_participation then 104.89 + ui.link { 104.90 + attr = { class = "mdl-button mdl-js-button" }, 104.91 + module = "interest", action = "update", 104.92 + params = { issue_id = issue.id, delete = true }, 104.93 + routing = { default = { 104.94 + mode = "redirect", module = initiative and "initiative" or "issue", view = "show", id = initiative and initiative.id or issue.id 104.95 + } }, 104.96 + text = _"remove my interest" 104.97 + } 104.98 + else 104.99 + ui.link { 104.100 + attr = { class = "mdl-button mdl-js-button" }, 104.101 + module = "interest", action = "update", 104.102 + params = { issue_id = issue.id }, 104.103 + routing = { default = { 104.104 + mode = "redirect", module = initiative and "initiative" or "issue", view = "show", id = initiative and initiative.id or issue.id 104.105 + } }, 104.106 + content = function() 104.107 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "remove_red_eye" } 104.108 + slot.put(" ") 104.109 + ui.tag{ content = _"add my interest" } 104.110 + end 104.111 + } 104.112 + end 104.113 + end 104.114 + end } 104.115 + end 104.116 + end 104.117 + 104.118 +end } 104.119 + 104.120 +if initiative then 104.121 + 104.122 + ui.container{ attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 104.123 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 104.124 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Competing initiatives" } 104.125 + end } 104.126 + 104.127 + execute.view { 104.128 + module = "issue", view = "_sidebar_issue", 104.129 + params = { 104.130 + issue = issue, 104.131 + ommit_initiative_id = initiative.id 104.132 + } 104.133 + } 104.134 + 104.135 + end } 104.136 +end
105.1 --- a/app/main/issue/_sidebar_issue.lua Thu Jun 23 03:30:57 2016 +0200 105.2 +++ b/app/main/issue/_sidebar_issue.lua Sun Jul 15 14:07:29 2018 +0200 105.3 @@ -1,39 +1,44 @@ 105.4 local issue = param.get("issue", "table") 105.5 -local hide_initiatives = param.get("hide_initiatives", atom.boolean) 105.6 -local highlight_initiative_id = param.get ( "highlight_initiative_id", "number" ) 105.7 - 105.8 -ui.sidebar ( "tab-whatcanido", function () 105.9 +local ommit_initiative_id = param.get ( "ommit_initiative_id", "number" ) 105.10 105.11 - ui.sidebarHead( function() 105.12 - ui.heading { 105.13 - level = 2, 105.14 - content = _"Competing initiatives" 105.15 - } 105.16 - end ) 105.17 - 105.18 +--[[ 105.19 +ui.heading { 105.20 + level = 2, 105.21 + content = _"Competing initiatives" 105.22 +} 105.23 +--]] 105.24 + 105.25 +if #(issue.initiatives) > (ommit_initiative_id and 1 or 0) then 105.26 execute.view { 105.27 module = "initiative", view = "_list", 105.28 params = { 105.29 issue = issue, 105.30 initiatives = issue.initiatives, 105.31 - highlight_initiative_id = highlight_initiative_id 105.32 + ommit_initiative_id = ommit_initiative_id 105.33 } 105.34 } 105.35 - if #issue.initiatives == 1 then 105.36 - ui.sidebarSection( function () 105.37 - 105.38 - if not issue.closed and not (issue.state == "voting") then 105.39 - ui.container { content = function() 105.40 - ui.tag { content = _"Currently this is the only initiative in this issue, because nobody started a competing initiative (yet)." } 105.41 - if app.session.member and app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then 105.42 - slot.put(" ") 105.43 - ui.tag { content = _"To create a competing initiative see below." } 105.44 - end 105.45 - end } 105.46 - else 105.47 - ui.container { content = _"This is the only initiative in this issue, because nobody started a competing initiative." } 105.48 - end 105.49 - end ) 105.50 - end 105.51 +end 105.52 + 105.53 +if #issue.initiatives == 1 then 105.54 + ui.container { attr = { class = "mdl-card__content" }, content = function() 105.55 + if not issue.closed and not (issue.state == "voting") then 105.56 + ui.tag { content = _"Currently this is the only initiative in this issue, because nobody started a competing initiative (yet)." } 105.57 + else 105.58 + ui.container { content = _"This is the only initiative in this issue, because nobody started a competing initiative." } 105.59 + end 105.60 + end } 105.61 +end 105.62 105.63 -end ) -- ui.sidebar 105.64 +if app.session.member 105.65 + and app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) 105.66 + and not issue.closed and not issue.fully_frozen 105.67 +then 105.68 + ui.container{ attr = { class = "mdl-card__actions mdl-card--border" }, content = function() 105.69 + ui.link { 105.70 + attr = { class = "mdl-button mdl-js-button" }, 105.71 + module = "initiative", view = "new", 105.72 + params = { issue_id = issue.id }, 105.73 + content = _"start a new competing initiative" 105.74 + } 105.75 + end } 105.76 +end
106.1 --- a/app/main/issue/_sidebar_members.lua Thu Jun 23 03:30:57 2016 +0200 106.2 +++ b/app/main/issue/_sidebar_members.lua Sun Jul 15 14:07:29 2018 +0200 106.3 @@ -1,21 +1,25 @@ 106.4 local issue = param.get("issue", "table") 106.5 local initiative = param.get("initiative", "table") 106.6 106.7 +if config.voting_only and not issue.closed then 106.8 + return 106.9 +end 106.10 + 106.11 + 106.12 if app.session:has_access("all_pseudonymous") then 106.13 - ui.sidebar ( "tab-members", function () 106.14 106.15 + ui.container{ attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 106.16 + 106.17 ui.tag { tag = "a", attr = { name = "members" }, content = "" } 106.18 - local text = _"Interested members" 106.19 - if issue.state == "finished_with_winner" or issue.state == "finished_without_winner" then 106.20 - text = _"Voters" 106.21 - end 106.22 - 106.23 - ui.sidebarHead( function() 106.24 - ui.heading{ 106.25 - level = 2, content = text 106.26 - } 106.27 - end ) 106.28 - 106.29 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 106.30 + local text = _"Interested members" 106.31 + if issue.state == "finished_with_winner" or issue.state == "finished_without_winner" then 106.32 + text = _"Voters" 106.33 + end 106.34 + 106.35 + ui.container{ attr = { class = "mdl-card__title-text" }, content = text } 106.36 + end } 106.37 + 106.38 local interested_members_selector 106.39 106.40 if issue.state == "finished_with_winner" or issue.state == "finished_without_winner" then 106.41 @@ -38,28 +42,29 @@ 106.42 interested_members_selector = issue:get_reference_selector("interested_members_snapshot") 106.43 :join("issue", nil, "issue.id = direct_interest_snapshot.issue_id") 106.44 :add_field("direct_interest_snapshot.weight") 106.45 - :add_where("direct_interest_snapshot.event = issue.latest_snapshot_event") 106.46 + :add_where("direct_interest_snapshot.snapshot_id = issue.latest_snapshot_id") 106.47 106.48 if initiative then 106.49 - interested_members_selector:left_join("direct_supporter_snapshot", nil, { "direct_supporter_snapshot.initiative_id = ? AND direct_interest_snapshot.issue_id = direct_supporter_snapshot.issue_id AND direct_supporter_snapshot.member_id = direct_interest_snapshot.member_id AND direct_supporter_snapshot.event = issue.latest_snapshot_event", initiative.id }) 106.50 + interested_members_selector:left_join("direct_supporter_snapshot", nil, { "direct_supporter_snapshot.initiative_id = ? AND direct_interest_snapshot.issue_id = direct_supporter_snapshot.issue_id AND direct_supporter_snapshot.member_id = direct_interest_snapshot.member_id AND direct_supporter_snapshot.snapshot_id = issue.latest_snapshot_id", initiative.id }) 106.51 interested_members_selector:add_field("direct_supporter_snapshot.member_id NOTNULL", "supporter") 106.52 interested_members_selector:add_field("satisfied", "supporter_satisfied") 106.53 end 106.54 end 106.55 106.56 - execute.view{ 106.57 - module = "member", 106.58 - view = "_list", 106.59 - params = { 106.60 - issue = issue, 106.61 - initiative = initiative, 106.62 - members_selector = interested_members_selector, 106.63 - paginator_name = "members", 106.64 - member_class = "sidebarRow sidebarRowNarrow", 106.65 - for_votes = issue.state == "finished_with_winner" or issue.state == "finished_without_winner" 106.66 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 106.67 + execute.view{ 106.68 + module = "member", 106.69 + view = "_list", 106.70 + params = { 106.71 + issue = issue, 106.72 + initiative = initiative, 106.73 + members_selector = interested_members_selector, 106.74 + paginator_name = "members", 106.75 + member_class = "sidebarRow sidebarRowNarrow", 106.76 + for_votes = issue.state == "finished_with_winner" or issue.state == "finished_without_winner", 106.77 + no_filter = true 106.78 + } 106.79 } 106.80 - } 106.81 - 106.82 - end ) 106.83 - 106.84 + end } 106.85 + end } 106.86 end
107.1 --- a/app/main/issue/_sidebar_state.lua Thu Jun 23 03:30:57 2016 +0200 107.2 +++ b/app/main/issue/_sidebar_state.lua Sun Jul 15 14:07:29 2018 +0200 107.3 @@ -13,165 +13,164 @@ 107.4 view_id = issue.id 107.5 end 107.6 107.7 -ui.sidebar( "tab-whatcanido", function() 107.8 +local current_occured = false 107.9 +local failed = false 107.10 + 107.11 +ui.tag{ tag = "table", content = function() 107.12 107.13 - ui.sidebarHead( function() 107.14 - ui.heading{ level = 2, content = function() 107.15 - ui.link{ 107.16 - content = issue.name, 107.17 - module = "issue", view = "show", id = issue.id 107.18 - } 107.19 - end } 107.20 - end ) 107.21 +local function quorum_text(policy, quorum) 107.22 + local num 107.23 + local den 107.24 + 107.25 + if quorum == 1 then 107.26 + return math.max(policy.issue_quorum or 0, issue.issue_quorum or 0) 107.27 + elseif quorum == 2 then 107.28 + num = policy.initiative_quorum_num 107.29 + den = policy.initiative_quorum_den 107.30 + end 107.31 + 107.32 + if num == nil or den == nil then 107.33 + return 0 107.34 + end 107.35 + 107.36 + if den == 100 or den == 10 then 107.37 + return _("#{percentage}%", { percentage = num * 100 / den }) 107.38 + else 107.39 + return num .. "/" .. den 107.40 + end 107.41 + 107.42 +end 107.43 107.44 - local current_occured = false 107.45 - local failed = false 107.46 +local phases = { "admission", "discussion", "verification", "voting" } 107.47 + 107.48 +for i, state in ipairs(phases) do 107.49 + local current = state == issue.state 107.50 + 107.51 + if current then 107.52 + current_occured = true 107.53 + end 107.54 107.55 - for i, state in ipairs{ "admission", "discussion", "verification", "voting" } do 107.56 - local current = state == issue.state 107.57 + local phase_success = ( 107.58 + (state == "admission" and issue.accepted) 107.59 + or (state == "discussion" and issue.half_frozen) 107.60 + or (state == "verification" and issue.fully_frozen and issue.state ~= "canceled_no_initiative_admitted") 107.61 + or (state == "voting" and issue.closed and issue.state ~= "canceled_no_initiative_admitted" and issue.state ~= "canceled_by_admin") 107.62 + ) 107.63 + 107.64 + if not failed then 107.65 + ui.container{ tag = "div", attr = { id = "phase-" .. state, class = current and "phase-current" or nil }, content = function() 107.66 107.67 - if current then 107.68 - current_occured = true 107.69 - end 107.70 - 107.71 - local phase_success = ( 107.72 - (state == "admission" and issue.accepted) 107.73 - or (state == "discussion" and issue.half_frozen) 107.74 - or (state == "verification" and issue.fully_frozen and issue.state ~= "canceled_no_initiative_admitted") 107.75 - or (state == "voting" and issue.closed and issue.state ~= "canceled_no_initiative_admitted" and issue.state ~= "canceled_by_admin") 107.76 - ) 107.77 - 107.78 - if not failed then 107.79 - ui.link{ attr = { 107.80 - onclick = "$('#phase-help-" .. state .. "').toggle();return false;", 107.81 - class = "sidebarRow sidebarRowNarrow", 107.82 - }, content = function() 107.83 + local state_names = { 107.84 + admission = _"Admission", 107.85 + discussion = _"Discussion", 107.86 + verification = _"Verification", 107.87 + voting = _"Voting" 107.88 + } 107.89 107.90 - local state_names = { 107.91 - admission = _"Admission", 107.92 - discussion = _"Discussion", 107.93 - verification = _"Verification", 107.94 - voting = _"Voting" 107.95 - } 107.96 - 107.97 - local state_name = "(" .. i .. ") " .. state_names[state] or state 107.98 + local state_name = state_names[state] or state 107.99 + 107.100 + local quorum 107.101 + if state == "admission" then 107.102 + quorum = quorum_text(issue.policy, 1) 107.103 + elseif state == "verification" then 107.104 + quorum = quorum_text(issue.policy, 2) 107.105 + end 107.106 + 107.107 + local time_text 107.108 + if current then 107.109 + local time_left 107.110 + if issue.state_time_left:sub(1,1) ~= "-" then 107.111 + time_text = format.interval_text(issue.state_time_left, { mode = "time_left" }) 107.112 + else 107.113 + time_text = "phase ends soon" 107.114 + end 107.115 + elseif current_occured then 107.116 + local phase_duration = issue[state .. "_time_text"] 107.117 + time_text = _("#{duration}", { duration = format.interval_text(phase_duration) } ) 107.118 + else 107.119 + local text = "failed" 107.120 + if quorum then 107.121 + text = _("failed #{quorum}", { quorum = quorum }) 107.122 + end 107.123 + if phase_success then 107.124 + if quorum == 0 then 107.125 + text = _"without quorum" 107.126 + elseif quorum then 107.127 + text = _("reached #{quorum}", { quorum = quorum }) 107.128 + else 107.129 + text = _"finished" 107.130 + end 107.131 + elseif issue.state == "canceled_revoked_before_accepted" or 107.132 + issue.state == "canceled_after_revocation_during_discussion" or 107.133 + issue.state == "canceled_after_revocation_during_verification" 107.134 + then 107.135 + text = _"revoked" 107.136 + elseif issue.state == "canceled_by_admin" then 107.137 + text = _"canceled" 107.138 + end 107.139 + time_text = text 107.140 + end 107.141 107.142 - local function quorum_text(policy, quorum) 107.143 - local num 107.144 - local den 107.145 - 107.146 - if quorum == 1 then 107.147 - num = policy.issue_quorum_num 107.148 - den = policy.issue_quorum_den 107.149 - elseif quorum == 2 then 107.150 - num = policy.initiative_quorum_num 107.151 - den = policy.initiative_quorum_den 107.152 - end 107.153 - 107.154 - if num == nil or den == nil then 107.155 - return 0 107.156 - end 107.157 - 107.158 - if den == 100 or den == 10 then 107.159 - return _("#{percentage}%", { percentage = num * 100 / den }) 107.160 - else 107.161 - return num .. "/" .. den 107.162 - end 107.163 - 107.164 - end 107.165 - 107.166 - local quorum 107.167 - if state == "admission" then 107.168 - quorum = quorum_text(issue.policy, 1) 107.169 - elseif state == "verification" then 107.170 - quorum = quorum_text(issue.policy, 2) 107.171 + if not config.voting_only or state == "voting" then 107.172 + if current then 107.173 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "play_arrow" } 107.174 + elseif not current_occured and not phase_success then 107.175 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "clear" } 107.176 + elseif current_occured and issue.accepted then 107.177 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "schedule" } 107.178 + elseif current_occured then 107.179 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "lock" } 107.180 + else 107.181 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "check" } 107.182 end 107.183 107.184 - if current then 107.185 - local time_left 107.186 - if issue.state_time_left:sub(1,1) ~= "-" then 107.187 - time_left = format.interval_text(issue.state_time_left, { mode = "time_left" }) 107.188 - else 107.189 - time_left = "phase ends soon" 107.190 - end 107.191 - 107.192 - ui.tag{ attr = { class = "right" }, 107.193 - content = time_left 107.194 - } 107.195 - elseif current_occured then 107.196 - local phase_duration = issue[state .. "_time_text"] 107.197 - ui.tag{ attr = { class = "right" }, 107.198 - content = _("#{duration}", { 107.199 - duration = format.interval_text(phase_duration) 107.200 - } ) 107.201 - } 107.202 - else 107.203 - local text = "failed" 107.204 - if quorum then 107.205 - text = _("failed #{quorum}", { quorum = quorum }) 107.206 - end 107.207 - if phase_success then 107.208 - if quorum == 0 then 107.209 - text = _"without quorum" 107.210 - elseif quorum then 107.211 - text = _("reached #{quorum}", { quorum = quorum }) 107.212 - else 107.213 - text = _"finished" 107.214 - end 107.215 - elseif issue.state == "canceled_revoked_before_accepted" or 107.216 - issue.state == "canceled_after_revocation_during_discussion" or 107.217 - issue.state == "canceled_after_revocation_during_verification" 107.218 - then 107.219 - text = _"revoked" 107.220 - elseif issue.state == "canceled_by_admin" then 107.221 - text = _"canceled" 107.222 - end 107.223 - 107.224 - ui.tag{ attr = { class = "right" }, 107.225 - content = text 107.226 - } 107.227 + if not config.voting_only then 107.228 + slot.put(" ") 107.229 end 107.230 107.231 - ui.heading{ level = 3, content = function() 107.232 - if current then 107.233 - ui.image{ attr = { class = "icon16" }, static = "icons/32/phase_current.png" } 107.234 - elseif not current_occured and not phase_success then 107.235 - ui.image{ attr = { class = "icon16" }, static = "icons/32/phase_failed.png" } 107.236 - elseif current_occured then 107.237 - ui.image{ attr = { class = "icon16" }, static = "icons/32/empty.png" } 107.238 - else 107.239 - ui.image{ attr = { class = "icon16" }, static = "icons/32/phase_finished.png" } 107.240 + ui.tag{ attr = { class = "mdl-data-table__cell--non-numeric" }, content = function() 107.241 + if not config.voting_only then 107.242 + ui.tag{ content = i .. "." } 107.243 end 107.244 slot.put(" ") 107.245 - ui.tag{ content = state_name } 107.246 - end } 107.247 + ui.tag{ content = state_name } 107.248 + end} 107.249 + ui.tag{ content = " (" .. time_text .. ")"} 107.250 + 107.251 + slot.put(" ") 107.252 + 107.253 + if not current then 107.254 + ui.tag{ tag = "i", attr = { onclick = "document.getElementById('phase-info-" .. i .. "').classList.toggle('hidden');", class = "material-icons material-icons-small clickable" }, content = "info_outline" } 107.255 + end 107.256 + end 107.257 + 107.258 + end } 107.259 + 107.260 + local help_texts = { 107.261 + admission = _("As soon as one initiative of this issue reaches the 1st quorum of #{quorum} support, the issue will proceed to discussion phase.", { quorum = quorum_text(issue.policy, 1) }), 107.262 + discussion = _"During the discussion phase, the issue is debated on while the initiators improve the proposals and reasons in their initiatives. Supporters of initiatives can write and rate suggestions for improvement.", 107.263 + verification = _("During the verification phase, existing initiatives cannot be changed anymore. Initiatives need to pass the 2nd quorum of #{quorum} at end of verification phase to become admitted for voting.", { quorum = quorum_text(issue.policy, 2) }), 107.264 + voting = _"During the voting phase, votes for all admitted initiatives in this issue can be cast. The final result will be calculated as soon as this phase ends." 107.265 + } 107.266 + 107.267 + local class = "phase-info" 107.268 + if not current then 107.269 + class = class .. " hidden" 107.270 + end 107.271 107.272 - local help_texts = { 107.273 - admission = _("As soon as one initiative of this issue reaches the 1st quorum of #{quorum} support, the issue will proceed to discussion phase.", { quorum = quorum_text(issue.policy, 1) }), 107.274 - discussion = _"During the discussion phase, the issue is debated on while the initiators improve the proposals and reasons in their initiatives. Supporters of initiatives can write and rate suggestions for improvement.", 107.275 - verification = _("During the verification phase, existing initiatives cannot be changed anymore. Initiatives need to pass the 2nd quorum of #{quorum} at end of verification phase to become admitted for voting.", { quorum = quorum_text(issue.policy, 2) }), 107.276 - voting = _"During the voting phase, votes for all admitted initiatives in this issue can be cast. The final result will be calculated as soon as this phase ends." 107.277 - } 107.278 - ui.container { attr = { id = "phase-help-" .. state, style = "display: none;" }, content = help_texts[state] } 107.279 - 107.280 - end } 107.281 + if not config.voting_only then 107.282 + ui.container { attr = { id = "phase-info-" .. i, class = class }, content = help_texts[state] } 107.283 end 107.284 107.285 - if not phase_success and not current and not current_occured then 107.286 - failed = true 107.287 - end 107.288 + end 107.289 + 107.290 + if not phase_success and not current and not current_occured then 107.291 + failed = true 107.292 end 107.293 - 107.294 - if issue.closed then 107.295 - ui.sidebarSection( function() 107.296 - ui.heading { level = 1, content = issue.state_name } 107.297 - end ) 107.298 - end 107.299 - 107.300 - if issue.admin_notice then 107.301 - ui.sidebarSection( function() 107.302 - ui.heading { level = 3, content = _"Administrative notice:" } 107.303 - slot.put(encode.html_newlines(issue.admin_notice)) 107.304 - end ) 107.305 - end 107.306 -end ) 107.307 +end 107.308 + 107.309 +end } 107.310 + 107.311 +if issue.closed then 107.312 + ui.tag{ content = issue.state_name } 107.313 +end
108.1 --- a/app/main/issue/_sidebar_whatcanido.lua Thu Jun 23 03:30:57 2016 +0200 108.2 +++ b/app/main/issue/_sidebar_whatcanido.lua Sun Jul 15 14:07:29 2018 +0200 108.3 @@ -19,488 +19,557 @@ 108.4 end 108.5 end 108.6 108.7 -ui.sidebar ( "tab-whatcanido", function () 108.8 +local supporter 108.9 108.10 - ui.sidebarHeadWhatCanIDo() 108.11 - 108.12 - local supporter 108.13 +if initiative and app.session.member_id then 108.14 + supporter = app.session.member:get_reference_selector("supporters") 108.15 + :add_where{ "initiative_id = ?", initiative.id } 108.16 + :optional_object_mode() 108.17 + :exec() 108.18 +end 108.19 + 108.20 +local view_module 108.21 +local view_id 108.22 108.23 - if initiative and app.session.member_id then 108.24 - supporter = app.session.member:get_reference_selector("supporters") 108.25 - :add_where{ "initiative_id = ?", initiative.id } 108.26 - :optional_object_mode() 108.27 - :exec() 108.28 - end 108.29 +if initiative then 108.30 + issue = issue 108.31 + view_module = "initiative" 108.32 + view_id = initiative.id 108.33 +else 108.34 + view_module = "issue" 108.35 + view_id = issue.id 108.36 +end 108.37 108.38 - local view_module 108.39 - local view_id 108.40 +local initiator 108.41 +if initiative and app.session.member_id then 108.42 + initiator = Initiator:by_pk(initiative.id, app.session.member.id) 108.43 +end 108.44 + 108.45 +local initiators 108.46 108.47 - if initiative then 108.48 - issue = issue 108.49 - view_module = "initiative" 108.50 - view_id = initiative.id 108.51 +if initiative then 108.52 + local initiators_members_selector = initiative:get_reference_selector("initiating_members") 108.53 + :add_field("initiator.accepted", "accepted") 108.54 + :add_order_by("member.name") 108.55 + if initiator and initiator.accepted then 108.56 + initiators_members_selector:add_where("initiator.accepted ISNULL OR initiator.accepted") 108.57 else 108.58 - view_module = "issue" 108.59 - view_id = issue.id 108.60 + initiators_members_selector:add_where("initiator.accepted") 108.61 end 108.62 108.63 - local initiator 108.64 - if initiative and app.session.member_id then 108.65 - initiator = Initiator:by_pk(initiative.id, app.session.member.id) 108.66 - end 108.67 - 108.68 - local initiators 108.69 - 108.70 - if initiative then 108.71 - local initiators_members_selector = initiative:get_reference_selector("initiating_members") 108.72 - :add_field("initiator.accepted", "accepted") 108.73 - :add_order_by("member.name") 108.74 - if initiator and initiator.accepted then 108.75 - initiators_members_selector:add_where("initiator.accepted ISNULL OR initiator.accepted") 108.76 - else 108.77 - initiators_members_selector:add_where("initiator.accepted") 108.78 - end 108.79 - 108.80 - initiators = initiators_members_selector:exec() 108.81 - end 108.82 + initiators = initiators_members_selector:exec() 108.83 +end 108.84 108.85 - if initiator and 108.86 - initiator.accepted and 108.87 - not issue.fully_frozen and 108.88 - not issue.closed and 108.89 - not initiative.revoked 108.90 - then 108.91 +ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 108.92 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 108.93 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"What can I do here?" } 108.94 + end } 108.95 + ui.container{ attr = { class = "what-can-i-do-here" }, content = function() 108.96 + 108.97 + if initiator and 108.98 + initiator.accepted and 108.99 + not issue.fully_frozen and 108.100 + not issue.closed and 108.101 + not initiative.revoked 108.102 + then 108.103 108.104 - ui.container { attr = { class = "sidebarRow" }, content = function () 108.105 - ui.heading { level = 3, content = function() 108.106 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.107 ui.tag { content = _"You are initiator of this initiative" } 108.108 - end } 108.109 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.110 - if issue.half_frozen then 108.111 - ui.tag { tag = "li", content = _"this issue is in verification phase, therefore the initiative text cannot be updated anymore" } 108.112 - else 108.113 - ui.tag { tag = "li", content = function () 108.114 - ui.link{ 108.115 - module = "draft", view = "new", 108.116 - params = { initiative_id = initiative.id }, 108.117 - content = _"edit proposal and/or reasons" 108.118 - } 108.119 - end } 108.120 - ui.tag { tag = "li", content = function () 108.121 - ui.link{ 108.122 - attr = { class = "action" }, 108.123 - module = "initiative", view = "add_initiator", 108.124 - params = { initiative_id = initiative.id }, 108.125 - content = _"invite another initiator" 108.126 - } 108.127 - end } 108.128 - if #initiative.initiators > 1 then 108.129 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.130 + if issue.half_frozen then 108.131 + ui.tag { tag = "li", content = _"this issue is in verification phase, therefore the initiative text cannot be updated anymore" } 108.132 + else 108.133 ui.tag { tag = "li", content = function () 108.134 ui.link{ 108.135 - module = "initiative", view = "remove_initiator", 108.136 + module = "draft", view = "new", 108.137 + params = { initiative_id = initiative.id }, 108.138 + content = _"edit proposal and/or reasons" 108.139 + } 108.140 + end } 108.141 + ui.tag { tag = "li", content = function () 108.142 + ui.link{ 108.143 + attr = { class = "action" }, 108.144 + module = "initiative", view = "add_initiator", 108.145 params = { initiative_id = initiative.id }, 108.146 - content = _"remove an initiator" 108.147 + content = _"invite another initiator" 108.148 + } 108.149 + end } 108.150 + if #initiative.initiators > 1 then 108.151 + ui.tag { tag = "li", content = function () 108.152 + ui.link{ 108.153 + module = "initiative", view = "remove_initiator", 108.154 + params = { initiative_id = initiative.id }, 108.155 + content = _"remove an initiator" 108.156 + } 108.157 + end } 108.158 + end 108.159 + ui.tag { tag = "li", content = function () 108.160 + ui.link{ 108.161 + module = "initiative", view = "revoke", id = initiative.id, 108.162 + content = _"revoke initiative" 108.163 } 108.164 end } 108.165 end 108.166 - ui.tag { tag = "li", content = function () 108.167 + end } 108.168 + end } 108.169 + end 108.170 + 108.171 + -- invited as initiator 108.172 + if initiator and initiator.accepted == nil and not initiative.issue.half_frozen and not initiative.issue.closed then 108.173 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.174 + ui.tag { content = _"You are invited to become initiator of this initiative" } 108.175 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.176 + ui.tag{ tag = "li", content = function () 108.177 ui.link{ 108.178 - module = "initiative", view = "revoke", id = initiative.id, 108.179 - content = _"revoke initiative" 108.180 + content = _"accept invitation", 108.181 + module = "initiative", 108.182 + action = "accept_invitation", 108.183 + id = initiative.id, 108.184 + routing = { 108.185 + default = { 108.186 + mode = "redirect", 108.187 + module = request.get_module(), 108.188 + view = request.get_view(), 108.189 + id = request.get_id_string(), 108.190 + params = request.get_param_strings() 108.191 + } 108.192 + } 108.193 } 108.194 end } 108.195 - end 108.196 - end } 108.197 - end } 108.198 - end 108.199 - 108.200 - -- invited as initiator 108.201 - if initiator and initiator.accepted == nil and not initiative.issue.half_frozen and not initiative.issue.closed then 108.202 - ui.container { attr = { class = "sidebarRow highlighted" }, content = function () 108.203 - ui.heading { level = 3, content = function() 108.204 - ui.tag { content = _"You are invited to become initiator of this initiative" } 108.205 - end } 108.206 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.207 - ui.tag{ tag = "li", content = function () 108.208 - ui.link{ 108.209 - content = _"accept invitation", 108.210 - module = "initiative", 108.211 - action = "accept_invitation", 108.212 - id = initiative.id, 108.213 - routing = { 108.214 - default = { 108.215 - mode = "redirect", 108.216 - module = request.get_module(), 108.217 - view = request.get_view(), 108.218 - id = request.get_id_string(), 108.219 - params = request.get_param_strings() 108.220 + 108.221 + ui.tag{ tag = "li", content = function () 108.222 + ui.link{ 108.223 + content = _"refuse invitation", 108.224 + module = "initiative", 108.225 + action = "reject_initiator_invitation", 108.226 + params = { 108.227 + initiative_id = initiative.id, 108.228 + member_id = app.session.member.id 108.229 + }, 108.230 + routing = { 108.231 + default = { 108.232 + mode = "redirect", 108.233 + module = request.get_module(), 108.234 + view = request.get_view(), 108.235 + id = request.get_id_string(), 108.236 + params = request.get_param_strings() 108.237 + } 108.238 } 108.239 } 108.240 - } 108.241 + end } 108.242 end } 108.243 - 108.244 - ui.tag{ tag = "li", content = function () 108.245 - ui.link{ 108.246 - content = _"refuse invitation", 108.247 - module = "initiative", 108.248 - action = "reject_initiator_invitation", 108.249 + end } 108.250 + end 108.251 + 108.252 + 108.253 + if privileged_to_vote and issue.member_info.first_trustee_id then 108.254 + local member = Member:by_id(issue.member_info.first_trustee_id) 108.255 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.256 + --[[ 108.257 + ui.container { attr = { class = "right" }, content = function() 108.258 + execute.view{ 108.259 + module = "member_image", 108.260 + view = "_show", 108.261 params = { 108.262 - initiative_id = initiative.id, 108.263 - member_id = app.session.member.id 108.264 - }, 108.265 - routing = { 108.266 - default = { 108.267 - mode = "redirect", 108.268 - module = request.get_module(), 108.269 - view = request.get_view(), 108.270 - id = request.get_id_string(), 108.271 - params = request.get_param_strings() 108.272 - } 108.273 + member = member, 108.274 + image_type = "avatar", 108.275 + show_dummy = true 108.276 } 108.277 } 108.278 end } 108.279 - end } 108.280 - end } 108.281 - end 108.282 - 108.283 - 108.284 - if privileged_to_vote and issue.member_info.first_trustee_id then 108.285 - local member = Member:by_id(issue.member_info.first_trustee_id) 108.286 - ui.sidebarSection( function () 108.287 - ui.container { attr = { class = "right" }, content = function() 108.288 - execute.view{ 108.289 - module = "member_image", 108.290 - view = "_show", 108.291 - params = { 108.292 - member = member, 108.293 - image_type = "avatar", 108.294 - show_dummy = true 108.295 - } 108.296 - } 108.297 - end } 108.298 - if issue.member_info.own_delegation_scope == "unit" then 108.299 - ui.heading{ level = 3, content = _"You delegated this organizational unit" } 108.300 - elseif issue.member_info.own_delegation_scope == "area" then 108.301 - ui.heading{ level = 3, content = _"You delegated this subject area" } 108.302 - elseif issue.member_info.own_delegation_scope == "issue" then 108.303 - ui.heading{ level = 3, content = _"You delegated this issue" } 108.304 - end 108.305 - 108.306 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.307 - if issue.member_info.own_delegation_scope == "area" or 108.308 - issue.member_info.own_delegation_scope == "unit" then 108.309 - ui.tag { tag = "li", content = function () 108.310 - ui.link { 108.311 - module = "delegation", view = "show", params = { 108.312 - issue_id = issue.id, 108.313 - initiative_id = initiative and initiative.id or nil 108.314 - }, 108.315 - content = _"change/revoke delegation only for this issue" 108.316 - } 108.317 - end } 108.318 - end 108.319 + --]] 108.320 if issue.member_info.own_delegation_scope == "unit" then 108.321 - ui.tag { tag = "li", content = function () 108.322 - ui.link { 108.323 - module = "delegation", view = "show", params = { 108.324 - unit_id = issue.area.unit_id, 108.325 - }, 108.326 - content = _("change/revoke delegation of organizational unit", { 108.327 - unit_name = issue.area.unit.name 108.328 - }) 108.329 - } 108.330 - end } 108.331 + ui.tag{ content = _"You delegated this organizational unit" } 108.332 elseif issue.member_info.own_delegation_scope == "area" then 108.333 - ui.tag { tag = "li", content = function () 108.334 - ui.link { 108.335 - module = "delegation", view = "show", params = { 108.336 - area_id = issue.area_id, 108.337 - }, 108.338 - content = _"change/revoke delegation of subject area" 108.339 - } 108.340 - end } 108.341 - end 108.342 - if issue.member_info.own_delegation_scope == nil then 108.343 - ui.tag { tag = "li", content = function () 108.344 - ui.link { 108.345 - module = "delegation", view = "show", params = { 108.346 - issue_id = issue.id, 108.347 - initiative_id = initiative and initiative.id or nil 108.348 - }, 108.349 - content = _"choose issue delegatee" 108.350 - } 108.351 - end } 108.352 + ui.tag{ content = _"You delegated this subject area" } 108.353 elseif issue.member_info.own_delegation_scope == "issue" then 108.354 - ui.tag { tag = "li", content = function () 108.355 - ui.link { 108.356 - module = "delegation", view = "show", params = { 108.357 - issue_id = issue.id, 108.358 - initiative_id = initiative and initiative.id or nil 108.359 - }, 108.360 - content = _"change/revoke issue delegation" 108.361 - } 108.362 - end } 108.363 - end 108.364 - end } 108.365 - 108.366 - if issue.member_info.first_trustee_id and issue.member_info.own_participation then 108.367 - local text = _"As long as you are interested in this issue yourself, the delegation is suspended for this issue, but it will be applied again in the voting phase unless you vote yourself." 108.368 - if issue.state == "voting" then 108.369 - text = _"This delegation is suspended, because you voted yourself." 108.370 + ui.tag{ content = _"You delegated this issue" } 108.371 end 108.372 - ui.container { content = text } 108.373 - end 108.374 - end ) 108.375 - end 108.376 - 108.377 - if privileged_to_vote and not issue.closed and not issue.fully_frozen then 108.378 - if issue.member_info.own_participation then 108.379 - ui.sidebarSection( function () 108.380 - ui.container{ attr = { class = "right" }, content = function() 108.381 - ui.image{ attr = { class = "right" }, static = "icons/48/eye.png" } 108.382 - if issue.member_info.weight and issue.member_info.weight > 1 then 108.383 - slot.put("<br />") 108.384 - ui.tag{ 108.385 - attr = { class = "right" }, 108.386 - content = "+" .. issue.member_info.weight - 1 108.387 - } 108.388 - end 108.389 - end } 108.390 - ui.heading{ level = 3, content = _("You are interested in this issue", { id = issue.id }) } 108.391 + 108.392 ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.393 - if issue.member_info.weight and issue.member_info.weight > 1 then 108.394 + if issue.member_info.own_delegation_scope == "area" or 108.395 + issue.member_info.own_delegation_scope == "unit" then 108.396 ui.tag { tag = "li", content = function () 108.397 ui.link { 108.398 - module = "delegation", view = "show_incoming", 108.399 - params = { issue_id = issue.id, member_id = app.session.member_id }, 108.400 - content = _("you have #{count} incoming delegations", { 108.401 - count = issue.member_info.weight - 1 108.402 + module = "delegation", view = "show", params = { 108.403 + issue_id = issue.id, 108.404 + initiative_id = initiative and initiative.id or nil 108.405 + }, 108.406 + content = _"change/revoke delegation only for this issue" 108.407 + } 108.408 + end } 108.409 + end 108.410 + if issue.member_info.own_delegation_scope == "unit" then 108.411 + ui.tag { tag = "li", content = function () 108.412 + ui.link { 108.413 + module = "delegation", view = "show", params = { 108.414 + unit_id = issue.area.unit_id, 108.415 + }, 108.416 + content = _("change/revoke delegation of organizational unit", { 108.417 + unit_name = issue.area.unit.name 108.418 }) 108.419 } 108.420 end } 108.421 + elseif issue.member_info.own_delegation_scope == "area" then 108.422 + ui.tag { tag = "li", content = function () 108.423 + ui.link { 108.424 + module = "delegation", view = "show", params = { 108.425 + area_id = issue.area_id, 108.426 + }, 108.427 + content = _"change/revoke delegation of subject area" 108.428 + } 108.429 + end } 108.430 end 108.431 - ui.tag { tag = "li", content = function () 108.432 - ui.link { 108.433 - module = "interest", action = "update", 108.434 - routing = { default = { 108.435 - mode = "redirect", module = view_module, view = "show", id = view_id 108.436 - } }, 108.437 - params = { issue_id = issue.id, delete = true }, 108.438 - text = _"remove my interest" 108.439 - } 108.440 - end } 108.441 - end } 108.442 - end ) 108.443 - else 108.444 - ui.sidebarSection( function () 108.445 - ui.heading{ level = 3, content = _("I want to participate in this issue", { id = issue.id }) } 108.446 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.447 - ui.tag { tag = "li", content = function () 108.448 - ui.link { 108.449 - module = "interest", action = "update", 108.450 - params = { issue_id = issue.id }, 108.451 - routing = { default = { 108.452 - mode = "redirect", module = view_module, view = "show", id = view_id 108.453 - } }, 108.454 - text = _"add my interest" 108.455 - } 108.456 - end } 108.457 - ui.tag { tag = "li", content = _"browse through the competing initiatives" } 108.458 - end } 108.459 - end ) 108.460 - end 108.461 - 108.462 - if initiative then 108.463 - 108.464 - if not initiative.member_info.supported or active_trustee_id then 108.465 - ui.container { attr = { class = "sidebarRow" }, content = function () 108.466 - ui.heading { level = 3, content = function() 108.467 - ui.tag { content = _"I like this initiative and I want to support it" } 108.468 - end } 108.469 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.470 + if issue.member_info.own_delegation_scope == nil then 108.471 + ui.tag { tag = "li", content = function () 108.472 + ui.link { 108.473 + module = "delegation", view = "show", params = { 108.474 + issue_id = issue.id, 108.475 + initiative_id = initiative and initiative.id or nil 108.476 + }, 108.477 + content = _"choose issue delegatee" 108.478 + } 108.479 + end } 108.480 + elseif issue.member_info.own_delegation_scope == "issue" then 108.481 ui.tag { tag = "li", content = function () 108.482 ui.link { 108.483 - module = "initiative", action = "add_support", 108.484 + module = "delegation", view = "show", params = { 108.485 + issue_id = issue.id, 108.486 + initiative_id = initiative and initiative.id or nil 108.487 + }, 108.488 + content = _"change/revoke issue delegation" 108.489 + } 108.490 + end } 108.491 + end 108.492 + if issue.member_info.first_trustee_id and issue.member_info.own_participation then 108.493 + local text = _"As long as you are interested in this issue yourself, the delegation is suspended for this issue, but it will be applied again in the voting phase unless you vote yourself." 108.494 + if issue.state == "voting" then 108.495 + text = _"This delegation is suspended, because you voted yourself." 108.496 + end 108.497 + ui.tag { tag = "li", content = function () 108.498 + ui.container { content = text } 108.499 + end } 108.500 + end 108.501 + 108.502 + end } 108.503 + 108.504 + end } 108.505 + end 108.506 + 108.507 + if not config.disable_delegations and privileged_to_vote and not issue.closed and not issue.fully_frozen then 108.508 + if issue.member_info.own_participation then 108.509 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.510 + --[[ 108.511 + ui.container{ attr = { class = "right" }, content = function() 108.512 + ui.image{ attr = { class = "right" }, static = "icons/48/eye.png" } 108.513 + if issue.member_info.weight and issue.member_info.weight > 1 then 108.514 + slot.put("<br />") 108.515 + ui.tag{ 108.516 + attr = { class = "right" }, 108.517 + content = "+" .. issue.member_info.weight - 1 108.518 + } 108.519 + end 108.520 + end } 108.521 + --]] 108.522 + ui.tag{ content = _("You are interested in this issue", { id = issue.id }) } 108.523 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.524 + if issue.member_info.weight and issue.member_info.weight > 1 then 108.525 + ui.tag { tag = "li", content = function () 108.526 + ui.link { 108.527 + module = "delegation", view = "show_incoming", 108.528 + params = { issue_id = issue.id, member_id = app.session.member_id }, 108.529 + content = _("you have #{count} incoming delegations", { 108.530 + count = issue.member_info.weight - 1 108.531 + }) 108.532 + } 108.533 + end } 108.534 + end 108.535 + ui.tag { tag = "li", content = function () 108.536 + ui.link { 108.537 + module = "interest", action = "update", 108.538 routing = { default = { 108.539 - mode = "redirect", module = "initiative", view = "show", id = initiative.id 108.540 + mode = "redirect", module = view_module, view = "show", id = view_id 108.541 } }, 108.542 - id = initiative.id, 108.543 - text = _"add my support" 108.544 + params = { issue_id = issue.id, delete = true }, 108.545 + text = _"remove my interest" 108.546 } 108.547 end } 108.548 end } 108.549 end } 108.550 - 108.551 - else -- if not supported 108.552 - ui.container { attr = { class = "sidebarRow" }, content = function () 108.553 - if initiative.member_info.satisfied then 108.554 - ui.image{ attr = { class = "right icon48" }, static = "icons/32/support_satisfied.png" } 108.555 - else 108.556 - ui.image{ attr = { class = "right icon48" }, static = "icons/32/support_unsatisfied.png" } 108.557 - end 108.558 - ui.heading { level = 3, content = _"You are supporting this initiative" } 108.559 + else 108.560 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.561 + ui.tag{ content = _("I want to participate in this issue", { id = issue.id }) } 108.562 ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.563 - if not initiative.member_info.satisfied then 108.564 + ui.tag { tag = "li", content = function () 108.565 + ui.link { 108.566 + module = "interest", action = "update", 108.567 + params = { issue_id = issue.id }, 108.568 + routing = { default = { 108.569 + mode = "redirect", module = view_module, view = "show", id = view_id 108.570 + } }, 108.571 + text = _"add my interest" 108.572 + } 108.573 + end } 108.574 + ui.tag { tag = "li", content = _"browse through the competing initiatives" } 108.575 + end } 108.576 + end } 108.577 + end 108.578 + 108.579 + if initiative then 108.580 + 108.581 + if not initiative.member_info.supported or active_trustee_id then 108.582 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.583 + ui.tag { content = _"I like this initiative and I want to support it" } 108.584 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.585 ui.tag { tag = "li", content = function () 108.586 - ui.tag { content = function () 108.587 - ui.link { 108.588 - external = "#suggestions", 108.589 - content = _"you restricted your support by rating suggestions as must or must not" 108.590 - } 108.591 - end } 108.592 - end } 108.593 - end 108.594 - ui.tag { tag = "li", content = function () 108.595 - ui.tag { content = function () 108.596 ui.link { 108.597 - xattr = { class = "btn btn-remove" }, 108.598 - module = "initiative", action = "remove_support", 108.599 + module = "initiative", action = "add_support", 108.600 routing = { default = { 108.601 mode = "redirect", module = "initiative", view = "show", id = initiative.id 108.602 } }, 108.603 id = initiative.id, 108.604 - text = _"remove my support" 108.605 + text = _"add my support" 108.606 } 108.607 end } 108.608 end } 108.609 end } 108.610 - end } 108.611 - 108.612 - end -- not supported 108.613 - 108.614 - ui.container { attr = { class = "sidebarRow" }, content = function () 108.615 - ui.heading { level = 3, content = _"I want to improve this initiative" } 108.616 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.617 - if issue.state == "verification" then 108.618 - ui.tag { tag = "li", content = _"this issue is in verification phase, therefore the initiative text cannot be updated anymore" } 108.619 - elseif issue.state == "voting" then 108.620 - ui.tag { tag = "li", content = _"this issue is in voting phase, therefore the initiative text cannot be updated anymore" } 108.621 - else 108.622 108.623 - if initiative.member_info.initiated then 108.624 - ui.tag { tag = "li", content =_"take a look at the suggestions of your supporters" } 108.625 - ui.tag { tag = "li", content =_"if you like to implement a suggestion in your proposal and/or reasons, update your initiative draft" } 108.626 - ui.tag { tag = "li", content =_"to argue about suggestions, just add your arguments to your reasons in the initiative draft, so your supporters can learn about your opinion" } 108.627 + else -- if not supported 108.628 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.629 + --[[ 108.630 + if initiative.member_info.satisfied then 108.631 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "thumb_up" } 108.632 + else 108.633 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "thumb_up" } 108.634 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "warning" } 108.635 end 108.636 - 108.637 - if not initiative.member_info.supported or active_trustee_id then 108.638 - ui.tag { tag = "li", content =_"add your support (see above) and rate or write new suggestions (and thereby restrict your support to certain conditions if necessary)" } 108.639 - else 108.640 - ui.tag { tag = "li", content = _"take a look at the suggestions (see left) and rate them" } 108.641 + --]] 108.642 + ui.tag{ content = _"You are supporting this initiative" } 108.643 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.644 + if not initiative.member_info.satisfied then 108.645 + ui.tag { tag = "li", content = function () 108.646 + ui.tag { content = function () 108.647 + ui.link { 108.648 + external = "#suggestions", 108.649 + content = _"you restricted your support by rating suggestions as must or must not" 108.650 + } 108.651 + end } 108.652 + end } 108.653 + end 108.654 ui.tag { tag = "li", content = function () 108.655 - ui.link { 108.656 - module = "suggestion", view = "new", params = { 108.657 - initiative_id = initiative.id 108.658 - }, 108.659 - content = _"write a new suggestion" 108.660 - } 108.661 + ui.tag { content = function () 108.662 + ui.link { 108.663 + xattr = { class = "btn btn-remove" }, 108.664 + module = "initiative", action = "remove_support", 108.665 + routing = { default = { 108.666 + mode = "redirect", module = "initiative", view = "show", id = initiative.id 108.667 + } }, 108.668 + id = initiative.id, 108.669 + text = _"remove my support" 108.670 + } 108.671 + end } 108.672 end } 108.673 + end } 108.674 + end } 108.675 + 108.676 + end -- not supported 108.677 + 108.678 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.679 + ui.tag{ content = _"I want to improve this initiative" } 108.680 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.681 + if issue.state == "verification" then 108.682 + ui.tag { tag = "li", content = _"this issue is in verification phase, therefore the initiative text cannot be updated anymore" } 108.683 + elseif issue.state == "voting" then 108.684 + ui.tag { tag = "li", content = _"this issue is in voting phase, therefore the initiative text cannot be updated anymore" } 108.685 + else 108.686 + 108.687 + if initiative.member_info.initiated then 108.688 + ui.tag { tag = "li", content =_"take a look at the suggestions of your supporters" } 108.689 + ui.tag { tag = "li", content =_"if you like to implement a suggestion in your proposal and/or reasons, update your initiative draft" } 108.690 + ui.tag { tag = "li", content =_"to argue about suggestions, just add your arguments to your reasons in the initiative draft, so your supporters can learn about your opinion" } 108.691 + end 108.692 + 108.693 + if not initiative.member_info.supported or active_trustee_id then 108.694 + ui.tag { tag = "li", content =_"add your support (see above) and rate or write new suggestions (and thereby restrict your support to certain conditions if necessary)" } 108.695 + else 108.696 + ui.tag { tag = "li", content = _"take a look at the suggestions (see right) and rate them" } 108.697 + ui.tag { tag = "li", content = function () 108.698 + ui.link { 108.699 + module = "suggestion", view = "new", params = { 108.700 + initiative_id = initiative.id 108.701 + }, 108.702 + content = _"write a new suggestion" 108.703 + } 108.704 + end } 108.705 + end 108.706 end 108.707 + end } 108.708 + end } 108.709 + 108.710 + end 108.711 + 108.712 + if 108.713 + (issue.state == "admission" or 108.714 + issue.state == "discussion" or 108.715 + issue.state == "verification") 108.716 + then 108.717 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.718 + if initiative then 108.719 + ui.tag{ content = _"I don't like this initiative and I want to add my opinion or counter proposal" } 108.720 + else 108.721 + ui.tag{ content = _"I don't like any of the initiative in this issue and I want to add my opinion or counter proposal" } 108.722 end 108.723 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.724 + ui.tag { tag = "li", content = function () 108.725 + ui.link { 108.726 + module = "issue", view = "show", id = issue.id, 108.727 + content = _"take a look at the competing initiatives" 108.728 + } 108.729 + end } 108.730 + ui.tag { tag = "li", content = function () 108.731 + ui.link { 108.732 + module = "initiative", view = "new", 108.733 + params = { issue_id = issue.id }, 108.734 + content = _"start a new competing initiative" 108.735 + } 108.736 + end } 108.737 + end } 108.738 end } 108.739 - end } 108.740 + end 108.741 108.742 end 108.743 108.744 - if 108.745 - (issue.state == "admission" or 108.746 - issue.state == "discussion" or 108.747 - issue.state == "verification") 108.748 - then 108.749 - ui.sidebarSection( function () 108.750 - if initiative then 108.751 - ui.heading{ level = 3, content = _"I don't like this initiative and I want to add my opinion or counter proposal" } 108.752 - else 108.753 - ui.heading{ level = 3, content = _"I don't like any of the initiative in this issue and I want to add my opinion or counter proposal" } 108.754 - end 108.755 + if not config.disable_delegations and privileged_to_vote and not issue.closed then 108.756 + 108.757 + if not issue.member_info.first_trustee_id then 108.758 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.759 + ui.tag{ content = _"I want to delegate this issue" } 108.760 + 108.761 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.762 + ui.tag { tag = "li", content = function () 108.763 + ui.link { 108.764 + module = "delegation", view = "show", params = { 108.765 + issue_id = issue.id, 108.766 + initiative_id = initiative and initiative.id or nil 108.767 + }, 108.768 + content = _"choose issue delegatee" 108.769 + } 108.770 + end } 108.771 + end } 108.772 + end } 108.773 + end 108.774 + 108.775 + end 108.776 + 108.777 + if initiator and initiator.accepted == false then 108.778 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.779 + ui.tag { content = _"You refused to become initiator of this initiative" } 108.780 ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.781 - ui.tag { tag = "li", content = function () 108.782 - ui.link { 108.783 - module = "issue", view = "show", id = issue.id, 108.784 - content = _"take a look at the competing initiatives" 108.785 - } 108.786 - end } 108.787 - ui.tag { tag = "li", content = function () 108.788 - ui.link { 108.789 - module = "initiative", view = "new", 108.790 - params = { issue_id = issue.id }, 108.791 - content = _"start a new competing initiative" 108.792 + ui.tag{ tag = "li", content = function () 108.793 + ui.link{ 108.794 + text = _"allow invitation again", 108.795 + module = "initiative", 108.796 + action = "remove_initiator", 108.797 + params = { 108.798 + initiative_id = initiative.id, 108.799 + member_id = app.session.member.id 108.800 + }, 108.801 + routing = { 108.802 + ok = { 108.803 + mode = "redirect", 108.804 + module = "initiative", 108.805 + view = "show", 108.806 + id = initiative.id 108.807 + } 108.808 + } 108.809 } 108.810 end } 108.811 end } 108.812 - end ) 108.813 - end 108.814 - 108.815 - end 108.816 - 108.817 - if privileged_to_vote and not issue.closed then 108.818 - 108.819 - if not issue.member_info.first_trustee_id then 108.820 - ui.sidebarSection( function () 108.821 - ui.heading{ level = 3, content = _"I want to delegate this issue" } 108.822 + end } 108.823 + end 108.824 108.825 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.826 - ui.tag { tag = "li", content = function () 108.827 - ui.link { 108.828 - module = "delegation", view = "show", params = { 108.829 - issue_id = issue.id, 108.830 - initiative_id = initiative and initiative.id or nil 108.831 - }, 108.832 - content = _"choose issue delegatee" 108.833 - } 108.834 - end } 108.835 - end } 108.836 - end ) 108.837 - end 108.838 + 108.839 108.840 - end 108.841 - 108.842 - if initiator and initiator.accepted == false then 108.843 - ui.container { attr = { class = "sidebarRow" }, content = function () 108.844 - ui.heading { level = 3, content = function() 108.845 - ui.tag { content = _"You refused to become initiator of this initiative" } 108.846 - end } 108.847 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.848 - ui.tag{ tag = "li", content = function () 108.849 - ui.link{ 108.850 - text = _"allow invitation again", 108.851 - module = "initiative", 108.852 - action = "remove_initiator", 108.853 - params = { 108.854 - initiative_id = initiative.id, 108.855 - member_id = app.session.member.id 108.856 - }, 108.857 - routing = { 108.858 - ok = { 108.859 - mode = "redirect", 108.860 - module = "initiative", 108.861 - view = "show", 108.862 - id = initiative.id 108.863 - } 108.864 - } 108.865 - } 108.866 - end } 108.867 - end } 108.868 - end } 108.869 - end 108.870 - 108.871 - 108.872 - 108.873 - if privileged_to_vote then 108.874 - 108.875 - if initiative and 108.876 - (issue.state == "admission" or 108.877 - issue.state == "discussion" or 108.878 - issue.state == "verification") 108.879 - then 108.880 + if privileged_to_vote then 108.881 108.882 - elseif issue.state == "verification" then 108.883 - 108.884 - elseif issue.state == "voting" then 108.885 - if not issue.member_info.direct_voted then 108.886 - if not issue.member_info.non_voter then 108.887 - ui.container { attr = { class = "sidebarRow" }, content = function () 108.888 - ui.heading { level = 3, content = _"I like to vote on this issue:" } 108.889 + if initiative and 108.890 + (issue.state == "admission" or 108.891 + issue.state == "discussion" or 108.892 + issue.state == "verification") 108.893 + then 108.894 + 108.895 + elseif issue.state == "verification" then 108.896 + 108.897 + elseif issue.state == "voting" then 108.898 + if not issue.member_info.direct_voted then 108.899 + if not issue.member_info.non_voter then 108.900 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.901 + ui.tag{ content = _"I like to vote on this issue:" } 108.902 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.903 + ui.tag { tag = "li", content = function () 108.904 + ui.tag { content = function () 108.905 + if not issue.closed then 108.906 + ui.link { 108.907 + xattr = { class = "btn btn-vote" }, 108.908 + module = "vote", view = "list", 108.909 + params = { issue_id = issue.id }, 108.910 + text = _"vote now" 108.911 + } 108.912 + end 108.913 + end } 108.914 + end } 108.915 + end } 108.916 + end } 108.917 + end 108.918 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.919 + if not issue.member_info.non_voter then 108.920 + ui.tag{ content = _"I don't like to vote this issue (myself):" } 108.921 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.922 + ui.tag { tag = "li", content = function () 108.923 + ui.link{ 108.924 + content = _"do not notify me about this voting anymore", 108.925 + module = "vote", 108.926 + action = "non_voter", 108.927 + params = { issue_id = issue.id }, 108.928 + routing = { 108.929 + default = { 108.930 + mode = "redirect", 108.931 + module = request.get_module(), 108.932 + view = request.get_view(), 108.933 + id = request.get_id_string(), 108.934 + params = request.get_param_strings() 108.935 + } 108.936 + } 108.937 + } 108.938 + end } 108.939 + end } 108.940 + else 108.941 + ui.tag{ content = _"You do not like to vote this issue (yourself)" } 108.942 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.943 + ui.tag { tag = "li", content = function () 108.944 + ui.link{ 108.945 + in_brackets = true, 108.946 + content = _"discard", 108.947 + module = "vote", 108.948 + action = "non_voter", 108.949 + params = { issue_id = issue.id, delete = true }, 108.950 + routing = { 108.951 + default = { 108.952 + mode = "redirect", 108.953 + module = request.get_module(), 108.954 + view = request.get_view(), 108.955 + id = request.get_id_string(), 108.956 + params = request.get_param_strings() 108.957 + } 108.958 + } 108.959 + } 108.960 + end } 108.961 + end } 108.962 + end 108.963 + end } 108.964 + else 108.965 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.966 + ui.tag{ content = _"I like to change/revoke my vote:" } 108.967 ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.968 ui.tag { tag = "li", content = function () 108.969 ui.tag { content = function () 108.970 @@ -509,131 +578,73 @@ 108.971 xattr = { class = "btn btn-vote" }, 108.972 module = "vote", view = "list", 108.973 params = { issue_id = issue.id }, 108.974 - text = _"vote now" 108.975 + text = _"change my vote" 108.976 + } 108.977 + end 108.978 + end } 108.979 + end } 108.980 + ui.tag { tag = "li", content = function () 108.981 + ui.tag { content = function () 108.982 + if not issue.closed then 108.983 + ui.link { 108.984 + module = "vote", action = "update", 108.985 + params = { 108.986 + issue_id = issue.id, 108.987 + discard = true 108.988 + }, 108.989 + routing = { 108.990 + default = { 108.991 + mode = "redirect", 108.992 + module = "issue", 108.993 + view = "show", 108.994 + id = issue.id 108.995 + } 108.996 + }, 108.997 + text = _"discard my vote" 108.998 } 108.999 end 108.1000 end } 108.1001 end } 108.1002 - end } 108.1003 - end } 108.1004 + end } 108.1005 + 108.1006 + end } 108.1007 + 108.1008 end 108.1009 - ui.container { attr = { class = "sidebarRow" }, content = function () 108.1010 - if not issue.member_info.non_voter then 108.1011 - ui.heading { level = 3, content = _"I don't like to vote this issue (myself):" } 108.1012 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.1013 - ui.tag { tag = "li", content = function () 108.1014 - ui.link{ 108.1015 - content = _"do not notify me about this voting anymore", 108.1016 - module = "vote", 108.1017 - action = "non_voter", 108.1018 - params = { issue_id = issue.id }, 108.1019 - routing = { 108.1020 - default = { 108.1021 - mode = "redirect", 108.1022 - module = request.get_module(), 108.1023 - view = request.get_view(), 108.1024 - id = request.get_id_string(), 108.1025 - params = request.get_param_strings() 108.1026 - } 108.1027 - } 108.1028 - } 108.1029 - end } 108.1030 - end } 108.1031 - else 108.1032 - ui.heading { level = 3, content = _"You do not like to vote this issue (yourself)" } 108.1033 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.1034 - ui.tag { tag = "li", content = function () 108.1035 - ui.link{ 108.1036 - in_brackets = true, 108.1037 - content = _"discard", 108.1038 - module = "vote", 108.1039 - action = "non_voter", 108.1040 - params = { issue_id = issue.id, delete = true }, 108.1041 - routing = { 108.1042 - default = { 108.1043 - mode = "redirect", 108.1044 - module = request.get_module(), 108.1045 - view = request.get_view(), 108.1046 - id = request.get_id_string(), 108.1047 - params = request.get_param_strings() 108.1048 - } 108.1049 - } 108.1050 - } 108.1051 - end } 108.1052 + end 108.1053 + end 108.1054 + 108.1055 + if not app.session.member or not privileged_to_vote then 108.1056 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.1057 + ui.tag{ content = _"You are not entitled to vote in this unit" } 108.1058 + ui.tag{ tag = "ul", content = function() 108.1059 + ui.tag{ tag = "li", content = function() 108.1060 + ui.link{ module = "index", view = "login", content = _"Login" } 108.1061 + end } 108.1062 + end } 108.1063 + end } 108.1064 + end 108.1065 + 108.1066 + if issue.closed then 108.1067 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.1068 + ui.tag { content = _"This issue is closed" } 108.1069 + end } 108.1070 + end 108.1071 + 108.1072 + if initiative and config.tell_others and config.tell_others.initiative then 108.1073 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 108.1074 + 108.1075 + ui.heading { level = 3, content = _"Tell others about this initiative:" } 108.1076 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.1077 + 108.1078 + for i, link in ipairs (config.tell_others.initiative(initiative)) do 108.1079 + ui.tag { tag = "li", content = function () 108.1080 + ui.link ( link ) 108.1081 end } 108.1082 end 108.1083 + 108.1084 end } 108.1085 - else 108.1086 - ui.container { attr = { class = "sidebarRow" }, content = function () 108.1087 - ui.heading { level = 3, content = _"I like to change/revoke my vote:" } 108.1088 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.1089 - ui.tag { tag = "li", content = function () 108.1090 - ui.tag { content = function () 108.1091 - if not issue.closed then 108.1092 - ui.link { 108.1093 - xattr = { class = "btn btn-vote" }, 108.1094 - module = "vote", view = "list", 108.1095 - params = { issue_id = issue.id }, 108.1096 - text = _"change my vote" 108.1097 - } 108.1098 - end 108.1099 - end } 108.1100 - end } 108.1101 - ui.tag { tag = "li", content = function () 108.1102 - ui.tag { content = function () 108.1103 - if not issue.closed then 108.1104 - ui.link { 108.1105 - module = "vote", action = "update", 108.1106 - params = { 108.1107 - issue_id = issue.id, 108.1108 - discard = true 108.1109 - }, 108.1110 - routing = { 108.1111 - default = { 108.1112 - mode = "redirect", 108.1113 - module = "issue", 108.1114 - view = "show", 108.1115 - id = issue.id 108.1116 - } 108.1117 - }, 108.1118 - text = _"discard my vote" 108.1119 - } 108.1120 - end 108.1121 - end } 108.1122 - end } 108.1123 - end } 108.1124 - 108.1125 - end } 108.1126 - 108.1127 - end 108.1128 + end } 108.1129 end 108.1130 - end 108.1131 - 108.1132 - if app.session.member and not privileged_to_vote then 108.1133 - ui.sidebarSection( _"You are not entitled to vote in this unit" ) 108.1134 - end 108.1135 - 108.1136 - if issue.closed then 108.1137 - ui.container { attr = { class = "sidebarRow" }, content = function () 108.1138 - ui.heading { level = 3, content = _"This issue is closed" } 108.1139 - end } 108.1140 - end 108.1141 + end } 108.1142 108.1143 - if initiative and config.tell_others and config.tell_others.initiative then 108.1144 - ui.container { attr = { class = "sidebarRow" }, content = function () 108.1145 - 108.1146 - ui.heading { level = 3, content = _"Tell others about this initiative:" } 108.1147 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 108.1148 - 108.1149 - for i, link in ipairs (config.tell_others.initiative(initiative)) do 108.1150 - ui.tag { tag = "li", content = function () 108.1151 - ui.link ( link ) 108.1152 - end } 108.1153 - end 108.1154 - 108.1155 - end } 108.1156 - end } 108.1157 - end 108.1158 - 108.1159 - 108.1160 -end ) 108.1161 +end }
109.1 --- a/app/main/issue/history.lua Thu Jun 23 03:30:57 2016 +0200 109.2 +++ b/app/main/issue/history.lua Sun Jul 15 14:07:29 2018 +0200 109.3 @@ -1,9 +1,14 @@ 109.4 local issue = Issue:by_id(param.get_id()) 109.5 issue:load_everything_for_member_id ( app.session.member_id ) 109.6 109.7 +execute.view{ 109.8 + module = "issue", view = "_head", params = { 109.9 + issue = issue, for_history = true 109.10 + } 109.11 +} 109.12 + 109.13 execute.view { 109.14 - module = "issue", view = "_head", 109.15 - params = { issue = issue, member = app.session.member } 109.16 + module = "issue", view = "_list", params = { for_issue = issue } 109.17 } 109.18 109.19 execute.view { 109.20 @@ -13,16 +18,6 @@ 109.21 } 109.22 } 109.23 109.24 -execute.view{ module = "issue", view = "_sidebar_state", params = { 109.25 - issue = issue 109.26 -} } 109.27 - 109.28 -execute.view { 109.29 - module = "issue", view = "_sidebar_whatcanido", params = { 109.30 - issue = issue 109.31 - } 109.32 -} 109.33 - 109.34 execute.view { 109.35 module = "issue", view = "_sidebar_members", params = { 109.36 issue = issue 109.37 @@ -31,17 +26,4 @@ 109.38 109.39 109.40 109.41 -ui.section( function() 109.42 - 109.43 - execute.view{ 109.44 - module = "issue", view = "_head2", params = { 109.45 - issue = issue, for_history = true 109.46 - } 109.47 - } 109.48 109.49 - execute.view { 109.50 - module = "issue", view = "_list2", params = { for_issue = issue } 109.51 - } 109.52 - 109.53 -end ) 109.54 -
110.1 --- a/app/main/issue/show.lua Thu Jun 23 03:30:57 2016 +0200 110.2 +++ b/app/main/issue/show.lua Sun Jul 15 14:07:29 2018 +0200 110.3 @@ -1,11 +1,15 @@ 110.4 local issue = Issue:by_id ( param.get_id () ) 110.5 110.6 if not issue then 110.7 - execute.view { module = "index", view = "404" } 110.8 - request.set_status("404 Not Found") 110.9 - return 110.10 + return execute.view { module = "index", view = "404" } 110.11 end 110.12 110.13 +app.current_issue = issue 110.14 + 110.15 +issue.area:load_delegation_info_once_for_member_id(app.session.member_id) 110.16 + 110.17 +execute.view{ module = "issue", view = "_head", params = { issue = issue } } 110.18 + 110.19 local initiatives = issue.initiatives 110.20 110.21 if app.session.member_id then 110.22 @@ -17,111 +21,109 @@ 110.23 app.html_title.title = _("Issue ##{id}", { id = issue.id }) 110.24 end 110.25 110.26 -execute.view { 110.27 - module = "issue", view = "_head", 110.28 - params = { issue = issue, member = app.session.member } 110.29 -} 110.30 - 110.31 -execute.view{ module = "issue", view = "_sidebar_state", params = { 110.32 - issue = issue 110.33 -} } 110.34 +ui.grid{ content = function() 110.35 + 110.36 + ui.cell_main{ content = function() 110.37 110.38 -execute.view { 110.39 - module = "issue", view = "_sidebar_whatcanido", params = { 110.40 - issue = issue 110.41 - } 110.42 -} 110.43 + ui.container { attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 110.44 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 110.45 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Competing initiatives" } 110.46 + end } 110.47 + execute.view { 110.48 + module = "initiative", view = "_list", 110.49 + params = { 110.50 + issue = issue, 110.51 + initiatives = initiatives 110.52 + } 110.53 + } 110.54 + end } 110.55 110.56 -execute.view { 110.57 - module = "issue", view = "_sidebar_members", params = { 110.58 - issue = issue 110.59 - } 110.60 -} 110.61 + ui.container { attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 110.62 + 110.63 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 110.64 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Details" } 110.65 + end } 110.66 110.67 -ui.section( function () 110.68 - 110.69 - execute.view{ 110.70 - module = "issue", view = "_head2", params = { 110.71 - issue = issue 110.72 - } 110.73 - } 110.74 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 110.75 + local policy = issue.policy 110.76 + ui.form{ 110.77 + record = issue, 110.78 + readonly = true, 110.79 + attr = { class = "sectionRow form" }, 110.80 + content = function() 110.81 + if issue.snapshot then 110.82 + ui.field.timestamp{ label = _"Last counting:", value = issue.snapshot } 110.83 + end 110.84 + ui.field.text{ label = _"Population", name = "population" } 110.85 + ui.field.timestamp{ label = _"Created at", name = "created" } 110.86 + if policy.polling then 110.87 + ui.field.text{ label = _"Admission time", value = _"Implicitly admitted" } 110.88 + else 110.89 + ui.field.text{ label = _"Minimum admission time", value = format.interval_text(issue.min_admission_time_text) } 110.90 + ui.field.text{ label = _"Maximum admission time", value = format.interval_text(issue.max_admission_time_text) } 110.91 + ui.field.text{ label = _"Issue quorum", value = issue.issue_quorum } 110.92 + end 110.93 + if issue.accepted then 110.94 + ui.field.timestamp{ label = _"Accepted at", name = "accepted" } 110.95 + end 110.96 + ui.field.text{ label = _"Discussion time", value = format.interval_text(issue.discussion_time_text) } 110.97 + if issue.half_frozen then 110.98 + ui.field.timestamp{ label = _"Half frozen at", name = "half_frozen" } 110.99 + end 110.100 + ui.field.text{ label = _"Verification time", value = format.interval_text(issue.verification_time_text) } 110.101 + ui.field.text{ 110.102 + label = _"Initiative quorum", 110.103 + value = format.percentage(policy.initiative_quorum_num / policy.initiative_quorum_den) 110.104 + } 110.105 + if issue.fully_frozen then 110.106 + ui.field.timestamp{ label = _"Fully frozen at", name = "fully_frozen" } 110.107 + end 110.108 + ui.field.text{ label = _"Voting time", value = format.interval_text(issue.voting_time_text) } 110.109 + if issue.closed then 110.110 + ui.field.timestamp{ label = _"Closed", name = "closed" } 110.111 + end 110.112 + end 110.113 + } 110.114 110.115 - if issue.initiatives[1].rank == 1 then 110.116 - execute.view{ module = "initiative", view = "_sidebar_state", params = { 110.117 - initiative = issue.initiatives[1] 110.118 - } } 110.119 - end 110.120 - 110.121 - ui.sectionRow( function () 110.122 + if issue.initiatives[1].rank == 1 then 110.123 + execute.view{ module = "initiative", view = "_sidebar_state", params = { 110.124 + initiative = issue.initiatives[1] 110.125 + } } 110.126 + end 110.127 + 110.128 + end } 110.129 + 110.130 + end } 110.131 + 110.132 + end } 110.133 + 110.134 + ui.cell_sidebar{ content = function() 110.135 + if config.logo then 110.136 + config.logo() 110.137 + end 110.138 execute.view { 110.139 - module = "initiative", view = "_list", 110.140 - params = { 110.141 + module = "issue", view = "_sidebar", 110.142 + params = { 110.143 issue = issue, 110.144 - initiatives = initiatives 110.145 + member = app.session.member 110.146 } 110.147 } 110.148 - end ) 110.149 - 110.150 -end ) 110.151 110.152 -ui.section(function() 110.153 - ui.sectionHead( function() 110.154 - ui.heading { level = 1, content = _"Details" } 110.155 - end ) 110.156 - local policy = issue.policy 110.157 - ui.form{ 110.158 - record = issue, 110.159 - readonly = true, 110.160 - attr = { class = "sectionRow form" }, 110.161 - content = function() 110.162 - if issue.snapshot then 110.163 - ui.field.timestamp{ label = _"Last counting:", value = issue.snapshot } 110.164 - end 110.165 - ui.field.text{ label = _"Population", name = "population" } 110.166 - ui.field.timestamp{ label = _"Created at", name = "created" } 110.167 - if policy.polling then 110.168 - ui.field.text{ label = _"Admission time", value = _"Implicitly admitted" } 110.169 - else 110.170 - ui.field.text{ label = _"Minimum admission time", value = format.interval_text(issue.min_admission_time_text) } 110.171 - ui.field.text{ label = _"Maximum admission time", value = format.interval_text(issue.max_admission_time_text) } 110.172 - ui.field.text{ 110.173 - label = _"Issue quorum", 110.174 - value = format.percentage(policy.issue_quorum_num / policy.issue_quorum_den) 110.175 + execute.view { 110.176 + module = "issue", view = "_sidebar_whatcanido", 110.177 + params = { 110.178 + issue = issue, 110.179 + member = app.session.member 110.180 + } 110.181 + } 110.182 + 110.183 + if not config.voting_only or issue.state ~= "voting" then 110.184 + execute.view { 110.185 + module = "issue", view = "_sidebar_members", params = { 110.186 + issue = issue 110.187 } 110.188 - if issue.population then 110.189 - ui.field.text{ 110.190 - label = _"Currently required", 110.191 - value = math.ceil(issue.population * policy.issue_quorum_num / policy.issue_quorum_den) 110.192 - } 110.193 - end 110.194 - end 110.195 - if issue.accepted then 110.196 - ui.field.timestamp{ label = _"Accepted at", name = "accepted" } 110.197 - end 110.198 - ui.field.text{ label = _"Discussion time", value = format.interval_text(issue.discussion_time_text) } 110.199 - if issue.half_frozen then 110.200 - ui.field.timestamp{ label = _"Half frozen at", name = "half_frozen" } 110.201 - end 110.202 - ui.field.text{ label = _"Verification time", value = format.interval_text(issue.verification_time_text) } 110.203 - ui.field.text{ 110.204 - label = _"Initiative quorum", 110.205 - value = format.percentage(policy.initiative_quorum_num / policy.initiative_quorum_den) 110.206 } 110.207 - if issue.population then 110.208 - ui.field.text{ 110.209 - label = _"Currently required", 110.210 - value = math.ceil(issue.population * (issue.policy.initiative_quorum_num / issue.policy.initiative_quorum_den)), 110.211 - } 110.212 - end 110.213 - if issue.fully_frozen then 110.214 - ui.field.timestamp{ label = _"Fully frozen at", name = "fully_frozen" } 110.215 - end 110.216 - ui.field.text{ label = _"Voting time", value = format.interval_text(issue.voting_time_text) } 110.217 - if issue.closed then 110.218 - ui.field.timestamp{ label = _"Closed", name = "closed" } 110.219 - end 110.220 end 110.221 - } 110.222 + end } 110.223 110.224 -end ) 110.225 - 110.226 +end }
111.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 111.2 +++ b/app/main/member/_action/remove_application.lua Sun Jul 15 14:07:29 2018 +0200 111.3 @@ -0,0 +1,10 @@ 111.4 +local id = param.get_id() 111.5 + 111.6 +local application = MemberApplication:by_id(id) 111.7 + 111.8 +if application.member_id ~= app.session.member_id then 111.9 + return 111.10 +end 111.11 + 111.12 +application:destroy() 111.13 +
112.1 --- a/app/main/member/_action/update.lua Thu Jun 23 03:30:57 2016 +0200 112.2 +++ b/app/main/member/_action/update.lua Sun Jul 15 14:07:29 2018 +0200 112.3 @@ -1,28 +1,16 @@ 112.4 -local fields = { 112.5 - "organizational_unit", 112.6 - "internal_posts", 112.7 - "realname", 112.8 - "birthday", 112.9 - "address", 112.10 - "email", 112.11 - "xmpp_address", 112.12 - "website", 112.13 - "phone", 112.14 - "mobile_phone", 112.15 - "profession", 112.16 - "external_memberships", 112.17 - "external_posts" 112.18 -} 112.19 +local profile = app.session.member.profile 112.20 112.21 -local update_args = { app.session.member } 112.22 - 112.23 -for i, field in ipairs(fields) do 112.24 - if not util.is_profile_field_locked(app.session.member, field) then 112.25 - param.update(app.session.member, field) 112.26 +for i, field in ipairs(config.member_profile_fields) do 112.27 + if not util.is_profile_field_locked(app.session.member, field.id) then 112.28 + local value = param.get(field.id) 112.29 + if value == "" then 112.30 + value = null 112.31 + end 112.32 + profile.profile[field.id] = value 112.33 end 112.34 end 112.35 112.36 -if not util.is_profile_field_locked(app.session.member, "statement") then 112.37 +if not util.is_profile_field_locked(profile, "statement") then 112.38 local formatting_engine = param.get("formatting_engine") or config.enforce_formatting_engine 112.39 112.40 local formatting_engine_valid = false 112.41 @@ -38,24 +26,37 @@ 112.42 112.43 local statement = param.get("statement") 112.44 112.45 - if statement ~= app.session.member.statement or 112.46 - formatting_engine ~= app.session.member.formatting_engine then 112.47 - app.session.member.formatting_engine = formatting_engine 112.48 - app.session.member.statement = statement 112.49 - app.session.member:render_content(true) 112.50 + if statement ~= profile.statement or 112.51 + formatting_engine ~= profile.formatting_engine then 112.52 + profile.formatting_engine = formatting_engine 112.53 + profile.statement = statement 112.54 + profile:render_content(true) 112.55 end 112.56 112.57 end 112.58 112.59 -if not util.is_profile_field_locked(app.session.member, "birthday") then 112.60 - if tostring(app.session.member.birthday) == "invalid_date" then 112.61 - app.session.member.birthday = nil 112.62 +if not util.is_profile_field_locked(profile, "birthday") then 112.63 + if tostring(profile.birthday) == "invalid_date" then 112.64 + profile.birthday = nil 112.65 slot.put_into("error", _"Date format is not valid. Please use following format: YYYY-MM-DD") 112.66 return false 112.67 end 112.68 end 112.69 112.70 -app.session.member:save() 112.71 +local search_strings = {} 112.72 +for i, field in ipairs(config.member_profile_fields) do 112.73 + if field.index and profile.profile[field.id] and #(profile.profile[field.id]) > 0 then 112.74 + search_strings[#search_strings+1] = profile.profile[field.id] 112.75 + end 112.76 +end 112.77 + 112.78 +if profile.statement and #(profile.statement) > 0 then 112.79 + search_strings[#search_strings+1] = profile.statement 112.80 +end 112.81 + 112.82 +profile.profile_text_data = table.concat(search_strings, " ") 112.83 + 112.84 +profile:save() 112.85 112.86 112.87 slot.put_into("notice", _"Your page has been updated")
113.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 113.2 +++ b/app/main/member/_action/update_agent.lua Sun Jul 15 14:07:29 2018 +0200 113.3 @@ -0,0 +1,29 @@ 113.4 +if not config.role_registration then 113.5 + return 113.6 +end 113.7 +if not app.session.member.role then 113.8 + return 113.9 +end 113.10 + 113.11 +local controller_id = param.get("controller_id") 113.12 + 113.13 +local member = Member:by_id(controller_id) 113.14 + 113.15 +if member.role then 113.16 + return 113.17 +end 113.18 + 113.19 +local agent = Agent:by_pk(app.session.member_id, controller_id) 113.20 + 113.21 +if param.get("delete") then 113.22 + agent:destroy() 113.23 + return 113.24 +end 113.25 + 113.26 +if not agent then 113.27 + agent = Agent:new() 113.28 + agent.controlled_id = app.session.member_id 113.29 + agent.controller_id = controller_id 113.30 + agent:save() 113.31 +end 113.32 +
114.1 --- a/app/main/member/_action/update_email.lua Thu Jun 23 03:30:57 2016 +0200 114.2 +++ b/app/main/member/_action/update_email.lua Sun Jul 15 14:07:29 2018 +0200 114.3 @@ -1,7 +1,7 @@ 114.4 local resend = param.get("resend", atom.boolean) 114.5 114.6 if not resend and util.is_profile_field_locked(app.session.member, "notify_email") then 114.7 - error("access denied") 114.8 + return execute.view { module = "index", view = "403" } 114.9 end 114.10 114.11 if app.session.member.notify_email_locked then
115.1 --- a/app/main/member/_action/update_login.lua Thu Jun 23 03:30:57 2016 +0200 115.2 +++ b/app/main/member/_action/update_login.lua Sun Jul 15 14:07:29 2018 +0200 115.3 @@ -1,5 +1,5 @@ 115.4 -if util.is_profile_field_locked(app.session.member, "login") then 115.5 - error("access denied") 115.6 +if util.is_profile_field_locked(app.session.member, "login") or app.session.member.role then 115.7 + return execute.view { module = "index", view = "403" } 115.8 end 115.9 115.10 local login = param.get("login")
116.1 --- a/app/main/member/_action/update_name.lua Thu Jun 23 03:30:57 2016 +0200 116.2 +++ b/app/main/member/_action/update_name.lua Sun Jul 15 14:07:29 2018 +0200 116.3 @@ -1,5 +1,5 @@ 116.4 if util.is_profile_field_locked(app.session.member, "name") then 116.5 - error("access denied") 116.6 + return execute.view { module = "index", view = "403" } 116.7 end 116.8 116.9 local name = param.get("name")
117.1 --- a/app/main/member/_action/update_password.lua Thu Jun 23 03:30:57 2016 +0200 117.2 +++ b/app/main/member/_action/update_password.lua Sun Jul 15 14:07:29 2018 +0200 117.3 @@ -1,3 +1,7 @@ 117.4 +if app.session.member.role then 117.5 + return execute.view { module = "index", view = "403" } 117.6 +end 117.7 + 117.8 local old_password = param.get("old_password") 117.9 local new_password1 = param.get("new_password1") 117.10 local new_password2 = param.get("new_password2")
118.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 118.2 +++ b/app/main/member/_agent_menu.lua Sun Jul 15 14:07:29 2018 +0200 118.3 @@ -0,0 +1,35 @@ 118.4 +if app.session.real_member_id then 118.5 + local member = Member:by_id(app.session.real_member_id) 118.6 + ui.tag{ tag = "li", attr = { class = item_class }, content = function() 118.7 + ui.link{ 118.8 + content = _("switch to: #{member_name}", { member_name = member.display_name }), 118.9 + attr = { class = link_class }, 118.10 + module = "role", 118.11 + action = "switch" 118.12 + } 118.13 + end } 118.14 +end 118.15 + 118.16 +local member_id = app.session.real_member_id or app.session.member_id 118.17 + 118.18 +local controlled_members_count = Member:new_selector() 118.19 + :join("agent", nil, "agent.controlled_id = member.id") 118.20 + :add_where("agent.accepted") 118.21 + :add_where("NOT member.locked") 118.22 + :add_where{ "agent.controller_id = ?", member_id } 118.23 + :exec() 118.24 + 118.25 +for i, member in ipairs(controlled_members_count) do 118.26 + if member.id ~= app.session.member_id then 118.27 + ui.tag{ tag = "li", attr = { class = item_class }, content = function() 118.28 + ui.link{ 118.29 + content = _("switch to: #{member_name}", { member_name = member.identification }), 118.30 + attr = { class = link_class }, 118.31 + module = "role", 118.32 + action = "switch", 118.33 + id = member.id 118.34 + } 118.35 + end } 118.36 + end 118.37 +end 118.38 +
119.1 --- a/app/main/member/_list.lua Thu Jun 23 03:30:57 2016 +0200 119.2 +++ b/app/main/member/_list.lua Sun Jul 15 14:07:29 2018 +0200 119.3 @@ -18,7 +18,7 @@ 119.4 members_selector:left_join("delegating_voter", "_member_list__delegating_voter", { "_member_list__delegating_voter.issue_id = issue.id AND _member_list__delegating_voter.member_id = ?", app.session.member_id }) 119.5 members_selector:add_field("member.id = ANY(_member_list__delegating_voter.delegate_member_ids)", "in_delegation_chain") 119.6 else 119.7 - members_selector:left_join("delegating_interest_snapshot", "_member_list__delegating_interest", { "_member_list__delegating_interest.event = issue.latest_snapshot_event AND _member_list__delegating_interest.issue_id = issue.id AND _member_list__delegating_interest.member_id = ?", app.session.member_id }) 119.8 + members_selector:left_join("delegating_interest_snapshot", "_member_list__delegating_interest", { "_member_list__delegating_interest.snapshot_id = issue.latest_snapshot_id AND _member_list__delegating_interest.issue_id = issue.id AND _member_list__delegating_interest.member_id = ?", app.session.member_id }) 119.9 members_selector:add_field("member.id = ANY(_member_list__delegating_interest.delegate_member_ids)", "in_delegation_chain") 119.10 end 119.11 end 119.12 @@ -80,7 +80,7 @@ 119.13 name = paginator_name, 119.14 anchor = paginator_name, 119.15 selector = members_selector, 119.16 - per_page = 25, 119.17 + per_page = 100, 119.18 content = function() 119.19 ui.container{ 119.20 attr = { class = "member_list" }, 119.21 @@ -88,20 +88,18 @@ 119.22 local members = members_selector:exec() 119.23 119.24 for i, member in ipairs(members) do 119.25 - ui.sectionRow( function() 119.26 - execute.view{ 119.27 - module = "member", 119.28 - view = "_show_thumb", 119.29 - params = { 119.30 - class = member_class, 119.31 - member = member, 119.32 - initiative = initiative, 119.33 - issue = issue, 119.34 - trustee = trustee, 119.35 - initiator = initiator 119.36 - } 119.37 + execute.view{ 119.38 + module = "member", 119.39 + view = "_show_thumb", 119.40 + params = { 119.41 + class = member_class, 119.42 + member = member, 119.43 + initiative = initiative, 119.44 + issue = issue, 119.45 + trustee = trustee, 119.46 + initiator = initiator 119.47 } 119.48 - end ) 119.49 + } 119.50 end 119.51 119.52 119.53 @@ -121,4 +119,4 @@ 119.54 content = list_members, 119.55 filter 119.56 } 119.57 -end 119.58 \ No newline at end of file 119.59 +end
120.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 120.2 +++ b/app/main/member/_menu.lua Sun Jul 15 14:07:29 2018 +0200 120.3 @@ -0,0 +1,31 @@ 120.4 +local item_class = param.get("item_class") 120.5 +local link_class = param.get("link_class") 120.6 + 120.7 + 120.8 +ui.tag{ tag = "li", attr = { class = item_class }, content = function() 120.9 + ui.link{ 120.10 + content = _"profile and settings", 120.11 + attr = { class = link_class }, 120.12 + module = "member", 120.13 + view = "show", 120.14 + id = app.session.member_id 120.15 + } 120.16 +end } 120.17 + 120.18 +execute.view{ module = "member", view = "_agent_menu" } 120.19 + 120.20 +ui.tag{ tag = "li", attr = { class = item_class }, content = function() 120.21 + ui.link{ 120.22 + text = _"logout", 120.23 + attr = { class = link_class }, 120.24 + module = 'index', 120.25 + action = 'logout', 120.26 + routing = { 120.27 + default = { 120.28 + mode = "redirect", 120.29 + module = "index", 120.30 + view = "index" 120.31 + } 120.32 + } 120.33 + } 120.34 +end }
121.1 --- a/app/main/member/_profile.lua Thu Jun 23 03:30:57 2016 +0200 121.2 +++ b/app/main/member/_profile.lua Sun Jul 15 14:07:29 2018 +0200 121.3 @@ -2,20 +2,13 @@ 121.4 121.5 local for_registration = param.get("for_registration", atom.boolean) 121.6 121.7 -if not member then 121.8 - local member_id = param.get("member_id", atom.integer) 121.9 - if member_id then 121.10 - member = Member:by_id(member_id) 121.11 - end 121.12 -end 121.13 - 121.14 ui.form{ 121.15 attr = { class = "form" }, 121.16 record = member, 121.17 readonly = true, 121.18 content = function() 121.19 121.20 - if not for_registration then 121.21 + if not for_registration and MemberImage:by_pk(member.id, "photo", true) then 121.22 ui.container { attr = { class = "member_photo" }, content = function() 121.23 execute.view{ 121.24 module = "member_image", 121.25 @@ -42,60 +35,15 @@ 121.26 if for_registration and member.notify_email then 121.27 ui.field.text{ label = _"Notification email", name = "notify_email" } 121.28 end 121.29 - 121.30 - if member.realname and #member.realname > 0 then 121.31 - ui.field.text{ label = _"Real name", name = "realname" } 121.32 - end 121.33 - if member.email and #member.email > 0 then 121.34 - ui.field.text{ label = _"email", name = "email" } 121.35 - end 121.36 - if member.xmpp_address and #member.xmpp_address > 0 then 121.37 - ui.field.text{ label = _"xmpp", name = "xmpp_address" } 121.38 - end 121.39 - if member.website and #member.website > 0 then 121.40 - ui.field.text{ label = _"Website", name = "website" } 121.41 - end 121.42 - if member.phone and #member.phone > 0 then 121.43 - ui.field.text{ label = _"Phone", name = "phone" } 121.44 - end 121.45 - if member.mobile_phone and #member.mobile_phone > 0 then 121.46 - ui.field.text{ label = _"Mobile phone", name = "mobile_phone" } 121.47 + if member.profile then 121.48 + local profile = member.profile.profile or {} 121.49 + for i, field in ipairs(config.member_profile_fields) do 121.50 + if profile[field.id] and #(profile[field.id]) > 0 then 121.51 + ui.field.text{ label = field.name, name = field.id, value = profile[field.id] } 121.52 + end 121.53 + end 121.54 end 121.55 - if member.address and #member.address > 0 then 121.56 - ui.container{ 121.57 - content = function() 121.58 - ui.tag{ 121.59 - tag = "label", 121.60 - attr = { class = "ui_field_label" }, 121.61 - content = _"Address" 121.62 - } 121.63 - ui.tag{ 121.64 - tag = "span", 121.65 - content = function() 121.66 - slot.put(encode.html_newlines(encode.html(member.address))) 121.67 - end 121.68 - } 121.69 - end 121.70 - } 121.71 - end 121.72 - if member.profession and #member.profession > 0 then 121.73 - ui.field.text{ label = _"Profession", name = "profession" } 121.74 - end 121.75 - if member.birthday and #member.birthday > 0 then 121.76 - ui.field.text{ label = _"Birthday", name = "birthday" } 121.77 - end 121.78 - if member.organizational_unit and #member.organizational_unit > 0 then 121.79 - ui.field.text{ label = _"Organizational unit", name = "organizational_unit" } 121.80 - end 121.81 - if member.internal_posts and #member.internal_posts > 0 then 121.82 - ui.field.text{ label = _"Internal posts", name = "internal_posts" } 121.83 - end 121.84 - if member.external_memberships and #member.external_memberships > 0 then 121.85 - ui.field.text{ label = _"Memberships", name = "external_memberships", multiline = true } 121.86 - end 121.87 - if member.external_posts and #member.external_posts > 0 then 121.88 - ui.field.text{ label = _"Posts", name = "external_posts", multiline = true } 121.89 - end 121.90 + 121.91 if member.admin then 121.92 ui.field.boolean{ label = _"Admin?", name = "admin" } 121.93 end 121.94 @@ -105,13 +53,13 @@ 121.95 if member.last_activity then 121.96 ui.field.text{ label = _"Last activity (updated daily)", value = format.date(member.last_activity) or _"not yet" } 121.97 end 121.98 - if member.id and member.statement and #member.statement > 0 then 121.99 + if member.profile and member.profile.statement and #member.profile.statement > 0 then 121.100 slot.put("<br />") 121.101 slot.put("<br />") 121.102 ui.container{ 121.103 attr = { class = " wiki" }, 121.104 content = function() 121.105 - slot.put(member:get_content("html")) 121.106 + slot.put(member.profile:get_content("html")) 121.107 end 121.108 } 121.109 end
122.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 122.2 +++ b/app/main/member/_settings_list.lua Sun Jul 15 14:07:29 2018 +0200 122.3 @@ -0,0 +1,155 @@ 122.4 + 122.5 + 122.6 +ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 122.7 + ui.container{ content = _"I want to show or edit my profile" } 122.8 + ui.tag { tag = "ul", content = function() 122.9 + 122.10 + ui.tag{ tag = "li", content = function() 122.11 + ui.link{ 122.12 + content = _"show my profile", 122.13 + module = "member", 122.14 + view = "show", 122.15 + id = app.session.member_id 122.16 + } 122.17 + end } 122.18 + ui.tag{ tag = "li", content = function() 122.19 + ui.link{ 122.20 + content = _"edit my profile", 122.21 + module = "member", 122.22 + view = "edit", 122.23 + id = app.session.member_id 122.24 + } 122.25 + end } 122.26 + 122.27 + end } 122.28 + 122.29 +end } 122.30 + 122.31 +ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 122.32 + 122.33 + ui.container{ content = _"I want to change account settings" } 122.34 + 122.35 + ui.tag { tag = "ul", content = function() 122.36 + 122.37 + if not util.is_profile_field_locked(app.session.member, "login") and not app.session.member.role then 122.38 + ui.tag{ tag = "li", content = function() 122.39 + ui.link{ 122.40 + content = _"change my login", 122.41 + module = "member", 122.42 + view = "settings_login", 122.43 + } 122.44 + end } 122.45 + end 122.46 + 122.47 + if not util.is_profile_field_locked(app.session.member, "password") and not app.session.member.role then 122.48 + ui.tag{ tag = "li", content = function() 122.49 + ui.link{ 122.50 + content = _"change my password", 122.51 + module = "member", 122.52 + view = "settings_password", 122.53 + } 122.54 + end } 122.55 + end 122.56 + 122.57 + if not util.is_profile_field_locked(app.session.member, "name") then 122.58 + ui.tag{ tag = "li", content = function() 122.59 + ui.link{ 122.60 + content = _"change my screen name", 122.61 + module = "member", 122.62 + view = "settings_name" 122.63 + } 122.64 + end } 122.65 + end 122.66 + 122.67 + ui.tag{ tag = "li", content = function() 122.68 + ui.link{ 122.69 + content = _"change avatar/photo", 122.70 + module = "member", 122.71 + view = "edit_images", 122.72 + } 122.73 + end } 122.74 + 122.75 + ui.tag{ tag = "li", content = function() 122.76 + ui.link{ 122.77 + content = _"notification settings", 122.78 + module = "member", 122.79 + view = "settings_notification", 122.80 + } 122.81 + end } 122.82 + if not util.is_profile_field_locked(app.session.member, "notify_email") then 122.83 + ui.tag{ tag = "li", content = function() 122.84 + ui.link{ 122.85 + content = _"notification email address", 122.86 + module = "member", 122.87 + view = "settings_email", 122.88 + } 122.89 + end } 122.90 + end 122.91 + 122.92 + if app.session.member.role then 122.93 + ui.tag{ tag = "li", content = function() 122.94 + ui.link{ 122.95 + content = _"agents", 122.96 + module = "member", 122.97 + view = "settings_agent", 122.98 + } 122.99 + end } 122.100 + end 122.101 + 122.102 + if config.role_registration and not app.session.member.role then 122.103 + ui.tag{ tag = "li", content = function() 122.104 + ui.link{ 122.105 + content = _"request role account", 122.106 + module = "role", 122.107 + view = "request", 122.108 + } 122.109 + end } 122.110 + end 122.111 + 122.112 + if config.oauth2 then 122.113 + ui.tag{ tag = "li", content = function() 122.114 + ui.link{ 122.115 + content = _"connected applications", 122.116 + module = "member", 122.117 + view = "settings_applications", 122.118 + } 122.119 + end } 122.120 + end 122.121 + 122.122 + if config.download_dir then 122.123 + ui.tag{ tag = "li", content = function() 122.124 + ui.link{ 122.125 + content = _"database download", 122.126 + module = "index", 122.127 + view = "download", 122.128 + } 122.129 + end } 122.130 + end 122.131 + 122.132 + end } 122.133 + 122.134 +end } 122.135 + 122.136 +ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 122.137 + 122.138 + ui.container{ content = _"Logout" } 122.139 + 122.140 + ui.tag { tag = "ul", content = function() 122.141 + ui.tag{ tag = "li", content = function() 122.142 + ui.link{ 122.143 + text = _"logout", 122.144 + module = 'index', 122.145 + action = 'logout', 122.146 + routing = { 122.147 + default = { 122.148 + mode = "redirect", 122.149 + module = "index", 122.150 + view = "index" 122.151 + } 122.152 + } 122.153 + } 122.154 + end } 122.155 + 122.156 + end } 122.157 + 122.158 +end }
123.1 --- a/app/main/member/_show_thumb.lua Thu Jun 23 03:30:57 2016 +0200 123.2 +++ b/app/main/member/_show_thumb.lua Sun Jul 15 14:07:29 2018 +0200 123.3 @@ -14,7 +14,7 @@ 123.4 name_html = encode.html(member.name) 123.5 end 123.6 123.7 -local container_class = "member_thumb" 123.8 +local container_class = "mdl-chip mdl-chip--contact clickable mdl-badge mdl-badge--overlap" 123.9 if initiator and member.accepted ~= true then 123.10 container_class = container_class .. " not_accepted" 123.11 end 123.12 @@ -40,141 +40,69 @@ 123.13 container_class = container_class .. " in_delegation_chain" 123.14 end 123.15 123.16 +local el_id = multirand.string(32, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") 123.17 +local weight = 0 123.18 +if member.weight then 123.19 + weight = member.weight 123.20 +end 123.21 +if member.voter_weight then 123.22 + weight = member.voter_weight 123.23 +end 123.24 + 123.25 ui.container{ 123.26 - attr = { class = container_class }, 123.27 + attr = { id = el_id, class = container_class, ["data-badge"] = weight > 1 and "+" .. weight - 1 or nil }, 123.28 content = function() 123.29 123.30 - local function doit() 123.31 - execute.view{ 123.32 - module = "member_image", 123.33 - view = "_show", 123.34 - params = { 123.35 - member = member, 123.36 - image_type = "avatar", 123.37 - show_dummy = true 123.38 - } 123.39 - } 123.40 - ui.tag{ 123.41 - attr = { class = "member_name" }, 123.42 - content = function() slot.put(name_html) end 123.43 + execute.view{ 123.44 + module = "member_image", 123.45 + view = "_show", 123.46 + params = { 123.47 + member = member, 123.48 + image_type = "avatar", 123.49 + show_dummy = true 123.50 } 123.51 - end 123.52 + } 123.53 + ui.tag{ 123.54 + attr = { class = "mdl-chip__text" }, 123.55 + content = function() slot.put(name_html) end 123.56 + } 123.57 123.58 - if app.session:has_access("everything") then 123.59 - ui.link{ 123.60 - attr = { title = _"Show member" }, 123.61 - module = "member", 123.62 - view = "show", 123.63 - id = member.id, 123.64 - content = doit 123.65 - } 123.66 - else 123.67 - ui.tag{ content = doit } 123.68 - end 123.69 - 123.70 if member.grade then 123.71 slot.put ( " " ) 123.72 - ui.link{ 123.73 - module = "vote", 123.74 - view = "list", 123.75 - params = { 123.76 - issue_id = initiative.issue.id, 123.77 - member_id = member.id, 123.78 + if member.grade > 0 then 123.79 + ui.tag{ tag = "i", attr = { class = "material-icons icon-green" }, content = "thumb_up" } 123.80 + elseif member.grade < 0 then 123.81 + ui.tag{ tag = "i", attr = { class = "material-icons icon-red" }, content = "thumb_down" } 123.82 + else 123.83 + ui.tag{ tag = "i", attr = { class = "material-icons icon-yellow" }, content = "brightness_1" } 123.84 + end 123.85 + end 123.86 + 123.87 + if (member.voter_comment) then 123.88 + ui.image{ 123.89 + attr = { 123.90 + alt = _"Voting comment available", 123.91 + title = _"Voting comment available", 123.92 + class = "icon24 right" 123.93 }, 123.94 - content = function() 123.95 - if member.grade > 0 then 123.96 - ui.image{ 123.97 - attr = { 123.98 - alt = _"Voted yes", 123.99 - title = _"Voted yes", 123.100 - class = "icon24 right" 123.101 - }, 123.102 - static = "icons/32/support_satisfied.png" 123.103 - } 123.104 - elseif member.grade < 0 then 123.105 - ui.image{ 123.106 - attr = { 123.107 - alt = _"Voted no", 123.108 - title = _"Voted no", 123.109 - class = "icon24 right" 123.110 - }, 123.111 - static = "icons/32/voted_no.png" 123.112 - } 123.113 - else 123.114 - ui.image{ 123.115 - attr = { 123.116 - alt = _"Abstention", 123.117 - title = _"Abstention", 123.118 - class = "icon24 right" 123.119 - }, 123.120 - static = "icons/16/bullet_yellow.png" 123.121 - } 123.122 - end 123.123 - end 123.124 + static = "icons/16/comment.png" 123.125 } 123.126 end 123.127 123.128 - if (member.voter_comment) then 123.129 - ui.link{ 123.130 - module = "vote", 123.131 - view = "list", 123.132 - params = { 123.133 - issue_id = issue.id, 123.134 - member_id = member.id, 123.135 - }, 123.136 - content = function() 123.137 - ui.image{ 123.138 - attr = { 123.139 - alt = _"Voting comment available", 123.140 - title = _"Voting comment available", 123.141 - class = "icon24 right" 123.142 - }, 123.143 - static = "icons/16/comment.png" 123.144 - } 123.145 - end 123.146 - } 123.147 - end 123.148 123.149 - local weight = 0 123.150 - if member.weight then 123.151 - weight = member.weight 123.152 - end 123.153 - if member.voter_weight then 123.154 - weight = member.voter_weight 123.155 - end 123.156 - 123.157 - if (issue or initiative) and weight > 1 then 123.158 - local module = "interest" 123.159 - if member.voter_weight then 123.160 - module = "vote" 123.161 - end 123.162 - 123.163 - slot.put ( " " ) 123.164 - ui.link{ 123.165 - attr = { 123.166 - class = in_delegation_chain and "in_delegation_chain" or nil, 123.167 - title = _"Number of incoming delegations, follow link to see more details" 123.168 - }, 123.169 - content = _("+ #{weight}", { weight = weight - 1 }), 123.170 - module = module, 123.171 - view = "show_incoming", 123.172 - params = { 123.173 - member_id = member.id, 123.174 - initiative_id = initiative and initiative.id or nil, 123.175 - issue_id = issue and issue.id or nil 123.176 - } 123.177 - } 123.178 + if (issue or initiative) and weight > 0 then 123.179 end 123.180 123.181 if member.supporter then 123.182 - slot.put ( " " ) 123.183 - if member.supporter_satisfied then 123.184 - local text = _"supporter" 123.185 - ui.image{ attr = { class = "icon24 right", alt = text, title = text }, static = "icons/32/support_satisfied.png" } 123.186 - else 123.187 - local text = _"supporter with restricting suggestions" 123.188 - ui.image{ attr = { class = "icon24 right", alt = text, title = text }, static = "icons/32/support_unsatisfied.png" } 123.189 - end 123.190 + ui.tag { attr = { class = "mdl-chip__action" }, content = function() 123.191 + if member.supporter_satisfied then 123.192 + local text = _"supporter" 123.193 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "thumb_up" } 123.194 + else 123.195 + local text = _"supporter with restricting suggestions" 123.196 + ui.tag{ tag = "i", attr = { class = "material-icons mdl-color-text--orange-900" }, content = "thumb_up" } 123.197 + end 123.198 + end } 123.199 end 123.200 123.201 if not member.active then 123.202 @@ -205,3 +133,60 @@ 123.203 123.204 end 123.205 } 123.206 + 123.207 +if member.grade or (issue and weight > 1) or app.session.member_id or app.session:has_access("everything") then 123.208 + ui.tag { tag = "ul", attr = { class = "mdl-menu mdl-menu--bottom-left mdl-js-menu mdl-js-ripple-effect", ["for"] = el_id }, content = function() 123.209 + if (member.grade) then 123.210 + ui.tag{ tag = "li", attr = { class = "mdl-menu__item" }, content = function() 123.211 + ui.link{ 123.212 + attr = { class = "mdl-menu__link" }, 123.213 + module = "vote", 123.214 + view = "list", 123.215 + params = { 123.216 + issue_id = issue.id, 123.217 + member_id = member.id, 123.218 + }, 123.219 + content = _"show ballot" 123.220 + } 123.221 + end } 123.222 + end 123.223 + if issue and weight > 1 then 123.224 + ui.tag{ tag = "li", attr = { class = "mdl-menu__item" }, content = function() 123.225 + local module = "interest" 123.226 + if member.voter_weight then 123.227 + module = "vote" 123.228 + end 123.229 + ui.link{ attr = { class = "mdl-menu__link" }, content = _"show incoming delegations", module = module, view = "show_incoming", params = { 123.230 + member_id = member.id, 123.231 + initiative_id = initiative and initiative.id or nil, 123.232 + issue_id = issue and issue.id or nil 123.233 + } } 123.234 + end } 123.235 + end 123.236 + if app.session:has_access("everything") then 123.237 + ui.tag{ tag = "li", attr = { class = "mdl-menu__item" }, content = function() 123.238 + ui.link{ attr = { class = "mdl-menu__link" }, content = _"show profile", module = "member", view = "show", id = member.id } 123.239 + end } 123.240 + end 123.241 + if app.session.member_id then 123.242 + ui.tag{ tag = "li", attr = { class = "mdl-menu__item" }, content = function() 123.243 + ui.link{ 123.244 + attr = { class = "mdl-menu__link" }, 123.245 + text = _"add to my list of private contacts", 123.246 + module = "contact", 123.247 + action = "add_member", 123.248 + id = member.id, 123.249 + routing = { 123.250 + default = { 123.251 + mode = "redirect", 123.252 + module = request.get_module(), 123.253 + view = request.get_view(), 123.254 + id = request.get_id_string(), 123.255 + params = request.get_param_strings() 123.256 + } 123.257 + } 123.258 + } 123.259 + end } 123.260 + end 123.261 + end } 123.262 +end
124.1 --- a/app/main/member/_sidebar_contacts.lua Thu Jun 23 03:30:57 2016 +0200 124.2 +++ b/app/main/member/_sidebar_contacts.lua Sun Jul 15 14:07:29 2018 +0200 124.3 @@ -12,18 +12,15 @@ 124.4 order = "name" 124.5 } 124.6 124.7 -ui.sidebar( "tab-members", function() 124.8 +ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 124.9 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 124.10 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Published contacts" } 124.11 + end } 124.12 124.13 - ui.sidebarHead( function() 124.14 - ui.heading { level = 2, content = _"Published contacts" } 124.15 - end ) 124.16 - 124.17 - --ui.sidebarSection( function() 124.18 - 124.19 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function() 124.20 + ui.container{ content = _"Published contacts" } 124.21 if public_contacts_selector:count() == 0 then 124.22 - ui.sidebarSection( function() 124.23 - ui.field.text{ value = _"No published contacts" } 124.24 - end ) 124.25 + ui.container{ content = _"No published contacts" } 124.26 else 124.27 ui.paginate{ 124.28 selector = public_contacts_selector, 124.29 @@ -31,7 +28,7 @@ 124.30 content = function() 124.31 local contacts = public_contacts_selector:exec() 124.32 for i, contact in ipairs(contacts) do 124.33 - ui.sidebarSection( "sidebarRowNarrow", function() 124.34 + ui.container{ content = function() 124.35 execute.view{ module = "member_image", view = "_show", params = { 124.36 member_id = contact.other_member.id, class = "micro_avatar", 124.37 popup_text = contact.other_member.name, 124.38 @@ -44,45 +41,118 @@ 124.39 view = "show", 124.40 id = contact.other_member.id 124.41 } 124.42 - end ) 124.43 + if app.session.member_id == member.id then 124.44 + ui.link{ 124.45 + content = function() 124.46 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "get_app" } 124.47 + end, 124.48 + module = "contact", 124.49 + action = "add_member", 124.50 + id = contact.other_member_id, 124.51 + params = { public = false }, 124.52 + routing = { 124.53 + default = { 124.54 + mode = "redirect", 124.55 + module = request.get_module(), 124.56 + view = request.get_view(), 124.57 + id = request.get_id_string(), 124.58 + params = request.get_param_strings() 124.59 + } 124.60 + } 124.61 + } 124.62 + ui.link{ 124.63 + content = function() 124.64 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "delete" } 124.65 + end, 124.66 + module = "contact", 124.67 + action = "remove_member", 124.68 + id = contact.other_member_id, 124.69 + routing = { 124.70 + default = { 124.71 + mode = "redirect", 124.72 + module = request.get_module(), 124.73 + view = request.get_view(), 124.74 + id = request.get_id_string(), 124.75 + params = request.get_param_strings() 124.76 + } 124.77 + } 124.78 + } 124.79 + end 124.80 + end } 124.81 end 124.82 end 124.83 } 124.84 end 124.85 - --end ) 124.86 - 124.87 - 124.88 - if app.session.member_id and app.session.member_id == member.id 124.89 - and private_contacts_selector:count() > 0 124.90 - then 124.91 + end } 124.92 + 124.93 + if member.id == app.session.member_id then 124.94 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function() 124.95 + ui.tag{ content = _"Private contacts" } 124.96 + 124.97 + if app.session.member_id and app.session.member_id == member.id 124.98 + and private_contacts_selector:count() > 0 124.99 + then 124.100 124.101 - ui.sidebarHead( function() 124.102 - ui.heading { level = 2, content = _"Private contacts" } 124.103 - end ) 124.104 - 124.105 - ui.paginate{ 124.106 - selector = private_contacts_selector, 124.107 - name = "contacts", 124.108 - content = function() 124.109 - local contacts = private_contacts_selector:exec() 124.110 - for i, contact in ipairs(contacts) do 124.111 - ui.sidebarSection( "sidebarRowNarrow", function() 124.112 - execute.view{ module = "member_image", view = "_show", params = { 124.113 - member_id = contact.other_member.id, class = "micro_avatar", 124.114 - popup_text = contact.other_member.name, 124.115 - image_type = "avatar", show_dummy = true, 124.116 - } } 124.117 - slot.put(" ") 124.118 - ui.link{ 124.119 - content = contact.other_member.name, 124.120 - module = "member", 124.121 - view = "show", 124.122 - id = contact.other_member.id 124.123 - } 124.124 - end ) 124.125 - end 124.126 + ui.paginate{ 124.127 + selector = private_contacts_selector, 124.128 + name = "contacts", 124.129 + content = function() 124.130 + local contacts = private_contacts_selector:exec() 124.131 + for i, contact in ipairs(contacts) do 124.132 + ui.container{ content = function() 124.133 + execute.view{ module = "member_image", view = "_show", params = { 124.134 + member_id = contact.other_member.id, class = "micro_avatar", 124.135 + popup_text = contact.other_member.name, 124.136 + image_type = "avatar", show_dummy = true, 124.137 + } } 124.138 + slot.put(" ") 124.139 + ui.link{ 124.140 + content = contact.other_member.name, 124.141 + module = "member", 124.142 + view = "show", 124.143 + id = contact.other_member.id 124.144 + } 124.145 + slot.put(" ") 124.146 + ui.link{ 124.147 + content = function() 124.148 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "publish" } 124.149 + end, 124.150 + module = "contact", 124.151 + action = "add_member", 124.152 + id = contact.other_member_id, 124.153 + params = { public = true }, 124.154 + routing = { 124.155 + default = { 124.156 + mode = "redirect", 124.157 + module = request.get_module(), 124.158 + view = request.get_view(), 124.159 + id = request.get_id_string(), 124.160 + params = request.get_param_strings() 124.161 + } 124.162 + } 124.163 + } 124.164 + ui.link{ 124.165 + content = function() 124.166 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "delete" } 124.167 + end, 124.168 + module = "contact", 124.169 + action = "remove_member", 124.170 + id = contact.other_member_id, 124.171 + routing = { 124.172 + default = { 124.173 + mode = "redirect", 124.174 + module = request.get_module(), 124.175 + view = request.get_view(), 124.176 + id = request.get_id_string(), 124.177 + params = request.get_param_strings() 124.178 + } 124.179 + } 124.180 + } 124.181 + end } 124.182 + end 124.183 + end 124.184 + } 124.185 end 124.186 - } 124.187 - 124.188 + end } 124.189 end 124.190 -end ) 124.191 \ No newline at end of file 124.192 +end }
125.1 --- a/app/main/member/_sidebar_whatcanido.lua Thu Jun 23 03:30:57 2016 +0200 125.2 +++ b/app/main/member/_sidebar_whatcanido.lua Sun Jul 15 14:07:29 2018 +0200 125.3 @@ -1,327 +1,210 @@ 125.4 local member = param.get("member", "table") 125.5 125.6 -ui.sidebar( "tab-whatcanido", function() 125.7 - 125.8 - if not member.active then 125.9 - ui.container{ attr = { class = "sidebarSection" }, content = function() 125.10 - slot.put(" · ") 125.11 - ui.tag{ 125.12 - attr = { class = "interest deactivated_member_info" }, 125.13 - content = _"This member is inactive" 125.14 - } 125.15 - end } 125.16 - end 125.17 +ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 125.18 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 125.19 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"What can I do here?" } 125.20 + end } 125.21 + ui.container{ attr = { class = "what-can-i-do-here" }, content = function() 125.22 125.23 - if member.locked then 125.24 - ui.container{ attr = { class = "sidebarSection" }, content = function() 125.25 - slot.put(" · ") 125.26 - ui.tag{ 125.27 - attr = { class = "interest deactivated_member_info" }, 125.28 - content = _"This member is locked" 125.29 - } 125.30 - end } 125.31 - end 125.32 - 125.33 - 125.34 - ui.sidebarHeadWhatCanIDo() 125.35 125.36 - if member.id == app.session.member_id and not app.session.needs_delegation_check then 125.37 - ui.sidebarSection( function() 125.38 - ui.heading { level = 3, content = _"I want to customize my profile" } 125.39 - ui.tag{ tag = "ul", attr = { class = "ul" }, content = function() 125.40 - ui.tag{ tag = "li", content = function() 125.41 - ui.link{ 125.42 - content = _"edit profile data", 125.43 - module = "member", 125.44 - view = "edit" 125.45 - } 125.46 - end } 125.47 - ui.tag{ tag = "li", content = function() 125.48 - ui.link{ 125.49 - content = _"change avatar/photo", 125.50 - module = "member", 125.51 - view = "edit_images" 125.52 - } 125.53 - end } 125.54 - end } 125.55 - end ) 125.56 - --[[ 125.57 - ui.sidebarSection( function() 125.58 - ui.heading { level = 3, content = _"I want to manage my saved contacts" } 125.59 - ui.tag{ tag = "ul", attr = { class = "ul" }, content = function() 125.60 - ui.tag{ tag = "li", content = function() 125.61 + if not member.active then 125.62 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 125.63 + slot.put(" · ") 125.64 + ui.tag{ 125.65 + attr = { class = "interest deactivated_member_info" }, 125.66 + content = _"This member is inactive" 125.67 + } 125.68 + end } 125.69 + end 125.70 + 125.71 + if member.locked then 125.72 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 125.73 + slot.put(" · ") 125.74 + ui.tag{ 125.75 + attr = { class = "interest deactivated_member_info" }, 125.76 + content = _"This member is locked" 125.77 + } 125.78 + end } 125.79 + end 125.80 125.81 - ui.link{ 125.82 - content = _"show saved contacts", 125.83 - module = 'contact', 125.84 - view = 'list' 125.85 - } 125.86 - 125.87 - end } 125.88 - end } 125.89 - end ) 125.90 - --]] 125.91 - 125.92 - ui.sidebarSection( function() 125.93 - 125.94 - ui.heading { level = 3, content = _"I want to change account settings" } 125.95 - 125.96 - local pages = {} 125.97 + if app.session.member_id == member.id then 125.98 + execute.view{ module = "member", view = "_settings_list" } 125.99 + end 125.100 + 125.101 + if app.session.member_id and not (member.id == app.session.member.id) then 125.102 + 125.103 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 125.104 125.105 - pages[#pages+1] = { view = "settings_notification", text = _"notification settings" } 125.106 - if not util.is_profile_field_locked(app.session.member, "notify_email") then 125.107 - pages[#pages+1] = { view = "settings_email", text = _"change your notification email address" } 125.108 - end 125.109 - if not util.is_profile_field_locked(app.session.member, "name") then 125.110 - pages[#pages+1] = { view = "settings_name", text = _"change your screen name" } 125.111 - end 125.112 - if not util.is_profile_field_locked(app.session.member, "login") then 125.113 - pages[#pages+1] = { view = "settings_login", text = _"change your login" } 125.114 - end 125.115 - if not util.is_profile_field_locked(app.session.member, "password") then 125.116 - pages[#pages+1] = { view = "settings_password", text = _"change your password" } 125.117 - end 125.118 - 125.119 - if config.download_dir then 125.120 - pages[#pages+1] = { module = "index", view = "download", text = _"database download" } 125.121 - end 125.122 - 125.123 - ui.tag{ tag = "ul", attr = { class = "ul" }, content = function() 125.124 - for i, page in ipairs(pages) do 125.125 - ui.tag{ tag = "li", content = function() 125.126 - ui.link{ 125.127 - module = page.module or "member", 125.128 - view = page.view, 125.129 - text = page.text 125.130 - } 125.131 - end } 125.132 - end 125.133 - end } 125.134 - end ) 125.135 - 125.136 - ui.sidebarSection( function() 125.137 - ui.heading { level = 3, content = _"I want to logout" } 125.138 - ui.tag{ tag = "ul", attr = { class = "ul" }, content = function() 125.139 - ui.tag{ tag = "li", content = function() 125.140 - ui.link{ 125.141 - text = _"logout", 125.142 - module = 'index', 125.143 - action = 'logout', 125.144 - routing = { 125.145 - default = { 125.146 - mode = "redirect", 125.147 - module = "index", 125.148 - view = "index" 125.149 + local contact = Contact:by_pk(app.session.member.id, member.id) 125.150 + if not contact then 125.151 + ui.tag{ content = _"I want to save this member as contact (i.e. to use as delegatee)" } 125.152 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 125.153 + ui.tag { tag = "li", content = function () 125.154 + ui.link{ 125.155 + text = _"add to my list of public contacts", 125.156 + module = "contact", 125.157 + action = "add_member", 125.158 + id = member.id, 125.159 + params = { public = true }, 125.160 + routing = { 125.161 + default = { 125.162 + mode = "redirect", 125.163 + module = request.get_module(), 125.164 + view = request.get_view(), 125.165 + id = request.get_id_string(), 125.166 + params = request.get_param_strings() 125.167 + } 125.168 + } 125.169 } 125.170 - } 125.171 - } 125.172 - end } 125.173 - end } 125.174 - end ) 125.175 - 125.176 - ui.sidebarSection( function() 125.177 - ui.heading { level = 3, content = _"I want to change the interface language" } 125.178 - ui.tag{ tag = "ul", attr = { class = "ul" }, content = function() 125.179 - for i, lang in ipairs(config.enabled_languages) do 125.180 - 125.181 - local langcode 125.182 - 125.183 - locale.do_with({ lang = lang }, function() 125.184 - langcode = _("[Name of Language]") 125.185 - end) 125.186 - 125.187 - ui.tag{ tag = "li", content = function() 125.188 - ui.link{ 125.189 - content = _('Select language "#{langcode}"', { langcode = langcode }), 125.190 - module = "index", 125.191 - action = "set_lang", 125.192 - params = { lang = lang }, 125.193 - routing = { 125.194 - default = { 125.195 - mode = "redirect", 125.196 - module = request.get_module(), 125.197 - view = request.get_view(), 125.198 - id = request.get_id_string(), 125.199 - params = request.get_param_strings() 125.200 + end } 125.201 + ui.tag { tag = "li", content = function () 125.202 + ui.link{ 125.203 + text = _"add to my list of private contacts", 125.204 + module = "contact", 125.205 + action = "add_member", 125.206 + id = member.id, 125.207 + routing = { 125.208 + default = { 125.209 + mode = "redirect", 125.210 + module = request.get_module(), 125.211 + view = request.get_view(), 125.212 + id = request.get_id_string(), 125.213 + params = request.get_param_strings() 125.214 + } 125.215 } 125.216 } 125.217 - } 125.218 + end } 125.219 + end } 125.220 + elseif contact.public then 125.221 + ui.tag{ content = _"You saved this member as contact (i.e. to use as delegatee) and others can see it" } 125.222 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 125.223 + ui.tag { tag = "li", content = function () 125.224 + ui.link{ 125.225 + text = _"make this contact private", 125.226 + module = "contact", 125.227 + action = "add_member", 125.228 + id = contact.other_member_id, 125.229 + params = { public = false }, 125.230 + routing = { 125.231 + default = { 125.232 + mode = "redirect", 125.233 + module = request.get_module(), 125.234 + view = request.get_view(), 125.235 + id = request.get_id_string(), 125.236 + params = request.get_param_strings() 125.237 + } 125.238 + } 125.239 + } 125.240 + end } 125.241 + ui.tag { tag = "li", content = function () 125.242 + ui.link{ 125.243 + text = _"remove from my contact list", 125.244 + module = "contact", 125.245 + action = "remove_member", 125.246 + id = contact.other_member_id, 125.247 + routing = { 125.248 + default = { 125.249 + mode = "redirect", 125.250 + module = request.get_module(), 125.251 + view = request.get_view(), 125.252 + id = request.get_id_string(), 125.253 + params = request.get_param_strings() 125.254 + } 125.255 + } 125.256 + } 125.257 + end } 125.258 + end } 125.259 + else 125.260 + ui.tag{ content = _"You saved this member as contact (i.e. to use as delegatee)" } 125.261 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 125.262 + ui.tag { tag = "li", content = function () 125.263 + ui.link{ 125.264 + text = _"make this contact public", 125.265 + module = "contact", 125.266 + action = "add_member", 125.267 + id = contact.other_member_id, 125.268 + params = { public = true }, 125.269 + routing = { 125.270 + default = { 125.271 + mode = "redirect", 125.272 + module = request.get_module(), 125.273 + view = request.get_view(), 125.274 + id = request.get_id_string(), 125.275 + params = request.get_param_strings() 125.276 + } 125.277 + } 125.278 + } 125.279 + end } 125.280 + ui.tag { tag = "li", content = function () 125.281 + ui.link{ 125.282 + text = _"remove from my contact list", 125.283 + module = "contact", 125.284 + action = "remove_member", 125.285 + id = contact.other_member_id, 125.286 + routing = { 125.287 + default = { 125.288 + mode = "redirect", 125.289 + module = request.get_module(), 125.290 + view = request.get_view(), 125.291 + id = request.get_id_string(), 125.292 + params = request.get_param_strings() 125.293 + } 125.294 + } 125.295 + } 125.296 + end } 125.297 end } 125.298 end 125.299 end } 125.300 - end ) 125.301 - elseif app.session.member_id and not (member.id == app.session.member.id) then 125.302 - 125.303 - ui.sidebarSection( function () 125.304 - 125.305 - local contact = Contact:by_pk(app.session.member.id, member.id) 125.306 - if not contact then 125.307 - ui.heading { level = 3, content = _"I want to save this member as contact (i.e. to use as delegatee)" } 125.308 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 125.309 - ui.tag { tag = "li", content = function () 125.310 - ui.link{ 125.311 - text = _"add to my list of public contacts", 125.312 - module = "contact", 125.313 - action = "add_member", 125.314 - id = member.id, 125.315 - params = { public = true }, 125.316 - routing = { 125.317 - default = { 125.318 - mode = "redirect", 125.319 - module = request.get_module(), 125.320 - view = request.get_view(), 125.321 - id = request.get_id_string(), 125.322 - params = request.get_param_strings() 125.323 - } 125.324 - } 125.325 - } 125.326 - end } 125.327 - ui.tag { tag = "li", content = function () 125.328 - ui.link{ 125.329 - text = _"add to my list of private contacts", 125.330 - module = "contact", 125.331 - action = "add_member", 125.332 - id = member.id, 125.333 - routing = { 125.334 - default = { 125.335 - mode = "redirect", 125.336 - module = request.get_module(), 125.337 - view = request.get_view(), 125.338 - id = request.get_id_string(), 125.339 - params = request.get_param_strings() 125.340 - } 125.341 - } 125.342 - } 125.343 - end } 125.344 - end } 125.345 - elseif contact.public then 125.346 - ui.heading { level = 3, content = _"You saved this member as contact (i.e. to use as delegatee) and others can see it" } 125.347 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 125.348 - ui.tag { tag = "li", content = function () 125.349 - ui.link{ 125.350 - text = _"make this contact private", 125.351 - module = "contact", 125.352 - action = "add_member", 125.353 - id = contact.other_member_id, 125.354 - params = { public = false }, 125.355 - routing = { 125.356 - default = { 125.357 - mode = "redirect", 125.358 - module = request.get_module(), 125.359 - view = request.get_view(), 125.360 - id = request.get_id_string(), 125.361 - params = request.get_param_strings() 125.362 - } 125.363 - } 125.364 - } 125.365 - end } 125.366 - ui.tag { tag = "li", content = function () 125.367 - ui.link{ 125.368 - text = _"remove from my contact list", 125.369 - module = "contact", 125.370 - action = "remove_member", 125.371 - id = contact.other_member_id, 125.372 - routing = { 125.373 - default = { 125.374 - mode = "redirect", 125.375 - module = request.get_module(), 125.376 - view = request.get_view(), 125.377 - id = request.get_id_string(), 125.378 - params = request.get_param_strings() 125.379 + 125.380 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 125.381 + local ignored_member = IgnoredMember:by_pk(app.session.member.id, member.id) 125.382 + if not ignored_member then 125.383 + ui.tag{ content = _"I do not like to hear from this member" } 125.384 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 125.385 + ui.tag { tag = "li", content = function () 125.386 + ui.link{ 125.387 + attr = { class = "interest" }, 125.388 + text = _"block this member", 125.389 + module = "member", 125.390 + action = "update_ignore_member", 125.391 + id = member.id, 125.392 + routing = { 125.393 + default = { 125.394 + mode = "redirect", 125.395 + module = request.get_module(), 125.396 + view = request.get_view(), 125.397 + id = request.get_id_string(), 125.398 + params = request.get_param_strings() 125.399 + } 125.400 } 125.401 } 125.402 - } 125.403 + end } 125.404 end } 125.405 - end } 125.406 - else 125.407 - ui.heading { level = 3, content = _"You saved this member as contact (i.e. to use as delegatee)" } 125.408 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 125.409 - ui.tag { tag = "li", content = function () 125.410 - ui.link{ 125.411 - text = _"make this contact public", 125.412 - module = "contact", 125.413 - action = "add_member", 125.414 - id = contact.other_member_id, 125.415 - params = { public = true }, 125.416 - routing = { 125.417 - default = { 125.418 - mode = "redirect", 125.419 - module = request.get_module(), 125.420 - view = request.get_view(), 125.421 - id = request.get_id_string(), 125.422 - params = request.get_param_strings() 125.423 - } 125.424 - } 125.425 - } 125.426 - end } 125.427 - ui.tag { tag = "li", content = function () 125.428 - ui.link{ 125.429 - text = _"remove from my contact list", 125.430 - module = "contact", 125.431 - action = "remove_member", 125.432 - id = contact.other_member_id, 125.433 - routing = { 125.434 - default = { 125.435 - mode = "redirect", 125.436 - module = request.get_module(), 125.437 - view = request.get_view(), 125.438 - id = request.get_id_string(), 125.439 - params = request.get_param_strings() 125.440 + else 125.441 + ui.tag{ content = _"You blocked this member (i.e. you will not be notified about this members actions)" } 125.442 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 125.443 + ui.tag { tag = "li", content = function () 125.444 + ui.link{ 125.445 + text = _"unblock member", 125.446 + module = "member", 125.447 + action = "update_ignore_member", 125.448 + id = member.id, 125.449 + params = { delete = true }, 125.450 + routing = { 125.451 + default = { 125.452 + mode = "redirect", 125.453 + module = request.get_module(), 125.454 + view = request.get_view(), 125.455 + id = request.get_id_string(), 125.456 + params = request.get_param_strings() 125.457 + } 125.458 } 125.459 } 125.460 - } 125.461 + end } 125.462 end } 125.463 - end } 125.464 - end 125.465 - end ) 125.466 - 125.467 - ui.sidebarSection( function() 125.468 - local ignored_member = IgnoredMember:by_pk(app.session.member.id, member.id) 125.469 - if not ignored_member then 125.470 - ui.heading { level = 3, content = _"I do not like to hear from this member" } 125.471 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 125.472 - ui.tag { tag = "li", content = function () 125.473 - ui.link{ 125.474 - attr = { class = "interest" }, 125.475 - text = _"block this member", 125.476 - module = "member", 125.477 - action = "update_ignore_member", 125.478 - id = member.id, 125.479 - routing = { 125.480 - default = { 125.481 - mode = "redirect", 125.482 - module = request.get_module(), 125.483 - view = request.get_view(), 125.484 - id = request.get_id_string(), 125.485 - params = request.get_param_strings() 125.486 - } 125.487 - } 125.488 - } 125.489 - end } 125.490 - end } 125.491 - else 125.492 - ui.heading { level = 3, content = _"You blocked this member (i.e. you will not be notified about this members actions)" } 125.493 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 125.494 - ui.tag { tag = "li", content = function () 125.495 - ui.link{ 125.496 - text = _"unblock member", 125.497 - module = "member", 125.498 - action = "update_ignore_member", 125.499 - id = member.id, 125.500 - params = { delete = true }, 125.501 - routing = { 125.502 - default = { 125.503 - mode = "redirect", 125.504 - module = request.get_module(), 125.505 - view = request.get_view(), 125.506 - id = request.get_id_string(), 125.507 - params = request.get_param_strings() 125.508 - } 125.509 - } 125.510 - } 125.511 - end } 125.512 - end } 125.513 - end 125.514 - end ) 125.515 - end 125.516 -end ) 125.517 \ No newline at end of file 125.518 + end 125.519 + end } 125.520 + 125.521 + end 125.522 + end } 125.523 +end }
126.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 126.2 +++ b/app/main/member/_timeline.lua Sun Jul 15 14:07:29 2018 +0200 126.3 @@ -0,0 +1,51 @@ 126.4 +local member = param.get("member", "table") 126.5 + 126.6 +local events = Event:by_member_id(member.id) 126.7 + 126.8 +local last_date 126.9 + 126.10 +for i, event in ipairs(events) do 126.11 + 126.12 + ui.container{ content = function() 126.13 + 126.14 + local date = atom.date(event.occurrence) 126.15 + if date ~= last_date then 126.16 + last_date = date 126.17 + ui.heading{ level = 3, content = format.date(date) } 126.18 + end 126.19 + 126.20 + local date_dumped = atom.dump(event.occurrence) 126.21 + local time = atom.time:load(string.sub(date_dumped, 12, #date_dumped)) 126.22 + 126.23 + ui.tag{ content = format.time(time) } 126.24 + 126.25 + slot.put(" ") 126.26 + 126.27 + if event.event == "member_active" then 126.28 + ui.tag{ content = _"account activated" } 126.29 + end 126.30 + 126.31 + if event.event == "initiative_created_in_new_issue" then 126.32 + ui.tag{ content = _("created #{initiative} (as new issue)", { initiative = event.initiative.display_name }) } 126.33 + end 126.34 + 126.35 + if event.event == "interest" then 126.36 + if event.value == 1 then 126.37 + ui.tag{ content = _("added interest to #{issue}", { issue = event.issue.name }) } 126.38 + else 126.39 + ui.tag{ content = _"removed interest" } 126.40 + end 126.41 + end 126.42 + 126.43 + if event.event == "support" then 126.44 + if event.value == 1 then 126.45 + ui.tag{ content = _("added support to #{initiative}", { initiative = event.initiative.display_name }) } 126.46 + else 126.47 + ui.tag{ content = _"removed support" } 126.48 + end 126.49 + end 126.50 + 126.51 + end } 126.52 + 126.53 + 126.54 +end
127.1 --- a/app/main/member/edit.lua Thu Jun 23 03:30:57 2016 +0200 127.2 +++ b/app/main/member/edit.lua Sun Jul 15 14:07:29 2018 +0200 127.3 @@ -1,117 +1,122 @@ 127.4 ui.titleMember(_"Edit your profile data") 127.5 127.6 -execute.view { 127.7 - module = "member", view = "_sidebar_whatcanido", params = { 127.8 - member = app.session.member 127.9 - } 127.10 -} 127.11 +local profile = app.session.member.profile 127.12 + 127.13 +ui.grid{ content = function() 127.14 + ui.cell_main{ content = function() 127.15 127.16 -ui.form{ 127.17 - record = app.session.member, 127.18 - attr = { class = "vertical" }, 127.19 - module = "member", 127.20 - action = "update", 127.21 - routing = { 127.22 - ok = { 127.23 - mode = "redirect", 127.24 - module = "member", 127.25 - view = "show", 127.26 - id = app.session.member_id 127.27 - } 127.28 - }, 127.29 - content = function() 127.30 - 127.31 - ui.section( function() 127.32 - 127.33 - ui.sectionHead( function() 127.34 - ui.heading{ level = 1, content = _"Edit your profile data" } 127.35 - end ) 127.36 - 127.37 - ui.sectionRow( _"All fields are optional. Please enter only data which should be published." ) 127.38 - 127.39 - ui.sectionRow( function() 127.40 - 127.41 - ui.field.text{ label = _"Organizational unit", name = "organizational_unit", readonly = config.locked_profile_fields.organizational_unit } 127.42 - ui.field.text{ label = _"Internal posts", name = "internal_posts", readonly = config.locked_profile_fields.internal_posts } 127.43 - ui.field.text{ label = _"Real name", name = "realname", readonly = config.locked_profile_fields.realname } 127.44 - ui.field.text{ label = _"Birthday" .. " YYYY-MM-DD ", name = "birthday", attr = { id = "profile_birthday" }, readonly = config.locked_profile_fields.birthday } 127.45 - ui.script{ static = "gregor.js/gregor.js" } 127.46 - util.gregor("profile_birthday", "document.getElementById('timeline_search_date').form.submit();") 127.47 - ui.field.text{ label = _"Address", name = "address", multiline = true, readonly = config.locked_profile_fields.address } 127.48 - ui.field.text{ label = _"email", name = "email", readonly = config.locked_profile_fields.email } 127.49 - ui.field.text{ label = _"xmpp", name = "xmpp_address", readonly = config.locked_profile_fields.xmpp_address } 127.50 - ui.field.text{ label = _"Website", name = "website", readonly = config.locked_profile_fields.website } 127.51 - ui.field.text{ label = _"Phone", name = "phone", readonly = config.locked_profile_fields.phone } 127.52 - ui.field.text{ label = _"Mobile phone", name = "mobile_phone", readonly = config.locked_profile_fields.mobile_phone } 127.53 - ui.field.text{ label = _"Profession", name = "profession", readonly = config.locked_profile_fields.profession } 127.54 - ui.field.text{ label = _"External memberships", name = "external_memberships", multiline = true, readonly = config.locked_profile_fields.external_memberships } 127.55 - ui.field.text{ label = _"External posts", name = "external_posts", multiline = true, readonly = config.locked_profile_fields.external_posts } 127.56 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 127.57 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 127.58 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Edit your profile data" } 127.59 + end } 127.60 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 127.61 + ui.form{ 127.62 + record = profile, 127.63 + attr = { class = "vertical" }, 127.64 + module = "member", 127.65 + action = "update", 127.66 + routing = { 127.67 + ok = { 127.68 + mode = "redirect", 127.69 + module = "member", 127.70 + view = "show", 127.71 + id = app.session.member_id 127.72 + } 127.73 + }, 127.74 + content = function() 127.75 + 127.76 + ui.container{ content = _"All fields are optional. Please enter only data which should be published." } 127.77 + 127.78 + for i, field in ipairs(config.member_profile_fields) do 127.79 + ui.container{ 127.80 + attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 127.81 + content = function() 127.82 + ui.tag{ tag = "input", attr = { class = "mdl-textfield__input", name = field.id, id = "input_" .. field.id, readonly = config.locked_profile_fields[field.id], value = profile and profile.profile and profile.profile[field.id] or nil } } 127.83 + ui.tag{ tag = "label", attr = { class = "mdl-textfield__label", ["for"] = "input_" .. field.id }, content = field.name } 127.84 + end } 127.85 + slot.put("<br />") 127.86 + end 127.87 127.88 - if not config.enforce_formatting_engine then 127.89 - ui.field.select{ 127.90 - label = _"Wiki engine for statement", 127.91 - name = "formatting_engine", 127.92 - foreign_records = config.formatting_engines, 127.93 - attr = {id = "formatting_engine"}, 127.94 - foreign_id = "id", 127.95 - foreign_name = "name", 127.96 - value = param.get("formatting_engine") 127.97 - } 127.98 - ui.tag{ 127.99 - tag = "div", 127.100 - content = function() 127.101 - ui.tag{ 127.102 - tag = "label", 127.103 - attr = { class = "ui_field_label" }, 127.104 - content = function() slot.put(" ") end, 127.105 + if not config.enforce_formatting_engine then 127.106 + ui.field.select{ 127.107 + label = _"Wiki engine for statement", 127.108 + name = "formatting_engine", 127.109 + foreign_records = config.formatting_engines, 127.110 + attr = {id = "formatting_engine"}, 127.111 + foreign_id = "id", 127.112 + foreign_name = "name", 127.113 + value = param.get("formatting_engine") 127.114 } 127.115 ui.tag{ 127.116 + tag = "div", 127.117 content = function() 127.118 - ui.link{ 127.119 - text = _"Syntax help", 127.120 - module = "help", 127.121 - view = "show", 127.122 - id = "wikisyntax", 127.123 - attr = {onClick="this.href=this.href.replace(/wikisyntax[^.]*/g, 'wikisyntax_'+getElementById('formatting_engine').value)"} 127.124 + ui.tag{ 127.125 + tag = "label", 127.126 + attr = { class = "ui_field_label" }, 127.127 + content = function() slot.put(" ") end, 127.128 } 127.129 - slot.put(" ") 127.130 - ui.link{ 127.131 - text = _"(new window)", 127.132 - module = "help", 127.133 - view = "show", 127.134 - id = "wikisyntax", 127.135 - attr = {target = "_blank", onClick="this.href=this.href.replace(/wikisyntax[^.]*/g, 'wikisyntax_'+getElementById('formatting_engine').value)"} 127.136 + ui.tag{ 127.137 + content = function() 127.138 + ui.link{ 127.139 + text = _"Syntax help", 127.140 + module = "help", 127.141 + view = "show", 127.142 + id = "wikisyntax", 127.143 + attr = {onClick="this.href=this.href.replace(/wikisyntax[^.]*/g, 'wikisyntax_'+getElementById('formatting_engine').value)"} 127.144 + } 127.145 + slot.put(" ") 127.146 + ui.link{ 127.147 + text = _"(new window)", 127.148 + module = "help", 127.149 + view = "show", 127.150 + id = "wikisyntax", 127.151 + attr = {target = "_blank", onClick="this.href=this.href.replace(/wikisyntax[^.]*/g, 'wikisyntax_'+getElementById('formatting_engine').value)"} 127.152 + } 127.153 + end 127.154 } 127.155 end 127.156 } 127.157 end 127.158 - } 127.159 - end 127.160 - ui.field.text{ 127.161 - label = _"Statement", 127.162 - name = "statement", 127.163 - multiline = true, 127.164 - attr = { style = "height: 50ex;" }, 127.165 - value = param.get("statement") 127.166 + ui.field.text{ 127.167 + label = _"Statement", 127.168 + name = "statement", 127.169 + multiline = true, 127.170 + attr = { style = "height: 50ex;" }, 127.171 + value = param.get("statement") 127.172 + } 127.173 + slot.put("<br />") 127.174 + ui.container{ attr = { class = "actions" }, content = function() 127.175 + ui.tag{ 127.176 + tag = "input", 127.177 + attr = { 127.178 + type = "submit", 127.179 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored mdl-js-ripple-effect", 127.180 + value = _"publish profile data" 127.181 + }, 127.182 + content = "" 127.183 + } 127.184 + slot.put(" ") 127.185 + ui.link{ 127.186 + attr = { 127.187 + class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect", 127.188 + }, 127.189 + content = _"cancel", 127.190 + module = "member", view = "show", id = app.session.member.id 127.191 + } 127.192 + end } 127.193 + end 127.194 } 127.195 - slot.put("<br />") 127.196 - ui.container{ attr = { class = "actions" }, content = function() 127.197 - ui.tag{ 127.198 - tag = "input", 127.199 - attr = { 127.200 - type = "submit", 127.201 - class = "btn btn-default", 127.202 - value = _"publish profile data" 127.203 - }, 127.204 - content = "" 127.205 - } 127.206 - slot.put("<br /><br /><br />") 127.207 - ui.link{ 127.208 - content = _"cancel", 127.209 - module = "member", view = "show", id = app.session.member.id 127.210 - } 127.211 - end } 127.212 - end ) 127.213 - end ) 127.214 - end 127.215 -} 127.216 \ No newline at end of file 127.217 + 127.218 + end } 127.219 + end } 127.220 + end } 127.221 + 127.222 + ui.cell_sidebar{ content = function() 127.223 + execute.view { 127.224 + module = "member", view = "_sidebar_whatcanido", params = { 127.225 + member = app.session.member 127.226 + } 127.227 + } 127.228 + end } 127.229 + 127.230 +end }
128.1 --- a/app/main/member/edit_images.lua Thu Jun 23 03:30:57 2016 +0200 128.2 +++ b/app/main/member/edit_images.lua Sun Jul 15 14:07:29 2018 +0200 128.3 @@ -1,76 +1,88 @@ 128.4 ui.titleMember(_"avatar/photo") 128.5 128.6 -execute.view { 128.7 - module = "member", view = "_sidebar_whatcanido", params = { 128.8 - member = app.session.member 128.9 - } 128.10 -} 128.11 +ui.grid{ content = function() 128.12 + ui.cell_main{ content = function() 128.13 + 128.14 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 128.15 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 128.16 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Upload avatar/photo" } 128.17 + end } 128.18 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 128.19 + 128.20 + ui.form{ 128.21 + record = app.session.member, 128.22 + attr = { 128.23 + class = "vertical section", 128.24 + enctype = 'multipart/form-data' 128.25 + }, 128.26 + module = "member", 128.27 + action = "update_images", 128.28 + routing = { 128.29 + ok = { 128.30 + mode = "redirect", 128.31 + module = "member", 128.32 + view = "settings" 128.33 + } 128.34 + }, 128.35 + content = function() 128.36 + execute.view{ 128.37 + module = "member_image", 128.38 + view = "_show", 128.39 + params = { 128.40 + class = "float-right", 128.41 + member = app.session.member, 128.42 + image_type = "avatar", 128.43 + force_update = true 128.44 + } 128.45 + } 128.46 + ui.heading { level = 4, content = _"Avatar"} 128.47 + ui.container { content = _"Your avatar is a small photo, which will be shown always next to your name." } 128.48 + slot.put("<br />") 128.49 + ui.field.image{ field_name = "avatar" } 128.50 128.51 -ui.form{ 128.52 - record = app.session.member, 128.53 - attr = { 128.54 - class = "vertical section", 128.55 - enctype = 'multipart/form-data' 128.56 - }, 128.57 - module = "member", 128.58 - action = "update_images", 128.59 - routing = { 128.60 - ok = { 128.61 - mode = "redirect", 128.62 - module = "member", 128.63 - view = "show", 128.64 - id = app.session.member_id 128.65 - } 128.66 - }, 128.67 - content = function() 128.68 - ui.sectionHead( function() 128.69 - ui.heading { level = 1, content = _"Upload avatar/photo" } 128.70 - end ) 128.71 - ui.sectionRow( function() 128.72 - execute.view{ 128.73 - module = "member_image", 128.74 - view = "_show", 128.75 - params = { 128.76 - class = "right", 128.77 - member = app.session.member, 128.78 - image_type = "avatar", 128.79 - force_update = true 128.80 + execute.view{ 128.81 + module = "member_image", 128.82 + view = "_show", 128.83 + params = { 128.84 + class = "float-right", 128.85 + member = app.session.member, 128.86 + image_type = "photo", 128.87 + force_update = true 128.88 + } 128.89 + } 128.90 + ui.heading { level = 4, content = _"Photo"} 128.91 + ui.container { content = _"Your photo will be shown in your profile." } 128.92 + slot.put("<br />") 128.93 + ui.field.image{ field_name = "photo" } 128.94 + slot.put("<br style='clear: right;' />") 128.95 + ui.tag{ 128.96 + tag = "input", 128.97 + attr = { 128.98 + type = "submit", 128.99 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 128.100 + value = _"publish avatar/photo" 128.101 + }, 128.102 + content = "" 128.103 + } 128.104 + slot.put(" ") 128.105 + ui.link{ 128.106 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 128.107 + content = _"cancel", 128.108 + module = "member", view = "settings" 128.109 + } 128.110 + end 128.111 } 128.112 + 128.113 + end } 128.114 + end } 128.115 + end } 128.116 + 128.117 + ui.cell_sidebar{ content = function() 128.118 + execute.view { 128.119 + module = "member", view = "_sidebar_whatcanido", params = { 128.120 + member = app.session.member 128.121 } 128.122 - ui.heading { level = 2, content = _"Avatar"} 128.123 - ui.container { content = _"Your avatar is a small photo, which will be shown always next to your name." } 128.124 - slot.put("<br />") 128.125 - ui.field.image{ field_name = "avatar" } 128.126 - slot.put("<br /><br />") 128.127 - execute.view{ 128.128 - module = "member_image", 128.129 - view = "_show", 128.130 - params = { 128.131 - class = "right", 128.132 - member = app.session.member, 128.133 - image_type = "photo", 128.134 - force_update = true 128.135 - } 128.136 - } 128.137 - ui.heading { level = 2, content = _"Photo"} 128.138 - ui.container { content = _"Your photo will be shown in your profile." } 128.139 - slot.put("<br />") 128.140 - ui.field.image{ field_name = "photo" } 128.141 - slot.put("<br style='clear: right;' />") 128.142 - ui.tag{ 128.143 - tag = "input", 128.144 - attr = { 128.145 - type = "submit", 128.146 - class = "btn btn-default", 128.147 - value = _"publish avatar/photo" 128.148 - }, 128.149 - content = "" 128.150 - } 128.151 - slot.put("<br /><br /><br />") 128.152 - ui.link{ 128.153 - content = _"cancel", 128.154 - module = "member", view = "show", id = app.session.member.id 128.155 - } 128.156 - end ) 128.157 - end 128.158 -} 128.159 \ No newline at end of file 128.160 + } 128.161 + end } 128.162 + 128.163 +end }
129.1 --- a/app/main/member/history.lua Thu Jun 23 03:30:57 2016 +0200 129.2 +++ b/app/main/member/history.lua Sun Jul 15 14:07:29 2018 +0200 129.3 @@ -1,72 +1,93 @@ 129.4 local member = Member:by_id(param.get_id()) 129.5 129.6 +if not member then 129.7 + execute.view { module = "index", view = "404" } 129.8 + request.set_status("404 Not Found") 129.9 + return 129.10 +end 129.11 + 129.12 + 129.13 ui.titleMember(member) 129.14 129.15 -ui.section( function() 129.16 - 129.17 - ui.sectionHead( function() 129.18 - ui.heading{ level = 1, content = _"Account history" } 129.19 - end) 129.20 +ui.grid{ content = function() 129.21 + ui.cell_main{ content = function() 129.22 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 129.23 + 129.24 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 129.25 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Account history" } 129.26 + end } 129.27 129.28 - ui.sectionRow( function() 129.29 - ui.form{ 129.30 - attr = { class = "vertical" }, 129.31 - content = function() 129.32 - ui.field.text{ label = _"Current name", value = member.name } 129.33 - ui.field.text{ label = _"Current status", value = member.active and _'activated' or _'deactivated' } 129.34 - end 129.35 - } 129.36 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 129.37 + 129.38 + 129.39 + ui.form{ 129.40 + attr = { class = "vertical" }, 129.41 + content = function() 129.42 + ui.field.text{ label = _"Current name", value = member.name } 129.43 + ui.field.text{ label = _"Current status", value = member.active and _'activated' or _'deactivated' } 129.44 + end 129.45 + } 129.46 129.47 129.48 - local entries = member:get_reference_selector("history_entries"):add_order_by("id DESC"):exec() 129.49 - 129.50 - if #entries > 0 then 129.51 - ui.tag{ 129.52 - tag = "table", 129.53 - content = function() 129.54 + local entries = member:get_reference_selector("history_entries"):add_order_by("id DESC"):exec() 129.55 + 129.56 + if #entries > 0 then 129.57 ui.tag{ 129.58 - tag = "tr", 129.59 + tag = "table", 129.60 content = function() 129.61 ui.tag{ 129.62 - tag = "th", 129.63 - content = _("Name") 129.64 + tag = "tr", 129.65 + content = function() 129.66 + ui.tag{ 129.67 + tag = "th", 129.68 + content = _("Name") 129.69 + } 129.70 + ui.tag{ 129.71 + tag = "th", 129.72 + content = _("Status") 129.73 + } 129.74 + ui.tag{ 129.75 + tag = "th", 129.76 + content = _("until") 129.77 + } 129.78 + end 129.79 } 129.80 - ui.tag{ 129.81 - tag = "th", 129.82 - content = _("Status") 129.83 - } 129.84 - ui.tag{ 129.85 - tag = "th", 129.86 - content = _("until") 129.87 - } 129.88 + for i, entry in ipairs(entries) do 129.89 + ui.tag{ 129.90 + tag = "tr", 129.91 + content = function() 129.92 + ui.tag{ 129.93 + tag = "td", 129.94 + content = entry.name 129.95 + } 129.96 + ui.tag{ 129.97 + tag = "td", 129.98 + content = entry.active and _'activated' or _'deactivated', 129.99 + } 129.100 + ui.tag{ 129.101 + tag = "td", 129.102 + content = format.timestamp(entry["until"]) 129.103 + } 129.104 + end 129.105 + } 129.106 + end 129.107 end 129.108 } 129.109 - for i, entry in ipairs(entries) do 129.110 - ui.tag{ 129.111 - tag = "tr", 129.112 - content = function() 129.113 - ui.tag{ 129.114 - tag = "td", 129.115 - content = entry.name 129.116 - } 129.117 - ui.tag{ 129.118 - tag = "td", 129.119 - content = entry.active and _'activated' or _'deactivated', 129.120 - } 129.121 - ui.tag{ 129.122 - tag = "td", 129.123 - content = format.timestamp(entry["until"]) 129.124 - } 129.125 - end 129.126 - } 129.127 - end 129.128 end 129.129 + slot.put("<br />") 129.130 + ui.container{ 129.131 + content = _("This member account has been created at #{created}", { created = format.timestamp(member.activated)}) 129.132 + } 129.133 + 129.134 + end} 129.135 + end} 129.136 + end } 129.137 + ui.cell_sidebar{ content = function() 129.138 + execute.view { 129.139 + module = "member", view = "_sidebar_whatcanido", params = { 129.140 + member = member 129.141 } 129.142 - end 129.143 - slot.put("<br />") 129.144 - ui.container{ 129.145 - content = _("This member account has been created at #{created}", { created = format.timestamp(member.activated)}) 129.146 } 129.147 - end) 129.148 + end } 129.149 129.150 -end) 129.151 \ No newline at end of file 129.152 +end }
130.1 --- a/app/main/member/list.lua Thu Jun 23 03:30:57 2016 +0200 130.2 +++ b/app/main/member/list.lua Sun Jul 15 14:07:29 2018 +0200 130.3 @@ -3,17 +3,21 @@ 130.4 local members_selector = Member:new_selector() 130.5 :add_where("activated NOTNULL") 130.6 130.7 -ui.section( function() 130.8 +ui.grid{ content = function() 130.9 + ui.cell_full{ content = function() 130.10 130.11 - ui.sectionHead( function() 130.12 - ui.heading { level = 1, content = _"Member list" } 130.13 - end ) 130.14 + ui.container { attr = { class = "mdl-card mdl-shadow--2dp mdl-card__fullwidth" }, content = function() 130.15 130.16 - ui.sectionRow( function() 130.17 - execute.view{ 130.18 - module = "member", 130.19 - view = "_list", 130.20 - params = { members_selector = members_selector } 130.21 - } 130.22 - end ) 130.23 -end ) 130.24 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 130.25 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Member list" } 130.26 + end } 130.27 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 130.28 + execute.view{ 130.29 + module = "member", 130.30 + view = "_list", 130.31 + params = { members_selector = members_selector } 130.32 + } 130.33 + end } 130.34 + end } 130.35 + end } 130.36 +end }
131.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 131.2 +++ b/app/main/member/settings.lua Sun Jul 15 14:07:29 2018 +0200 131.3 @@ -0,0 +1,55 @@ 131.4 +execute.view{ module = "index", view = "_lang_chooser" } 131.5 + 131.6 +ui.grid{ content = function() 131.7 + ui.cell_main{ content = function() 131.8 + 131.9 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 131.10 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 131.11 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Settings" } 131.12 + end } 131.13 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 131.14 + 131.15 + local agents = Agent:new_selector() 131.16 + :add_where{ "controller_id = ?", app.session.member_id } 131.17 + :add_where{ "accepted ISNULL" } 131.18 + :exec() 131.19 + 131.20 + if #agents > 0 then 131.21 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 131.22 + ui.container{ content = _"You have been granted access to the following account(s):" } 131.23 + for i, agent in ipairs(agents) do 131.24 + local member = Member:by_id(agent.controlled_id) 131.25 + ui.tag { tag = "ul", content = function() 131.26 + ui.tag{ tag = "li", content = function() 131.27 + ui.link{ 131.28 + module = "agent", view = "show", params = { controlled_id = agent.controlled_id }, 131.29 + content= _("Account access invitation from '#{member_name}'", { member_name = member.name }) 131.30 + } 131.31 + end } 131.32 + end } 131.33 + end 131.34 + end } 131.35 + end 131.36 + 131.37 + 131.38 + local controlled_members_count = Member:new_selector() 131.39 + :join("agent", nil, "agent.controlled_id = member.id") 131.40 + :add_where("agent.accepted") 131.41 + :add_where("NOT member.locked") 131.42 + :add_where{ "agent.controller_id = ?", app.session.member_id } 131.43 + :count() 131.44 + if controlled_members_count > 0 or app.session.real_member_id then 131.45 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 131.46 + ui.container{ content = _"I want to switch to another account" } 131.47 + ui.tag { tag = "ul", content = function() 131.48 + execute.view{ module = "member", view = "_agent_menu" } 131.49 + end } 131.50 + end } 131.51 + end 131.52 + 131.53 + execute.view{ module = "member", view = "_settings_list" } 131.54 + 131.55 + end } 131.56 + end } 131.57 + end } 131.58 +end }
132.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 132.2 +++ b/app/main/member/settings_agent.lua Sun Jul 15 14:07:29 2018 +0200 132.3 @@ -0,0 +1,118 @@ 132.4 +if not app.session.member.role then 132.5 + return 132.6 +end 132.7 + 132.8 +ui.titleMember(_"Account access") 132.9 + 132.10 +ui.grid{ content = function() 132.11 + 132.12 + ui.cell_main{ content = function() 132.13 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 132.14 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 132.15 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Agents" } 132.16 + end } 132.17 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 132.18 + 132.19 + local agents = Agent:new_selector() 132.20 + :add_where{ "controlled_id = ?", app.session.member_id } 132.21 + :exec() 132.22 + 132.23 + if #(agents) > 0 then 132.24 + ui.list{ 132.25 + records = agents, 132.26 + columns = { 132.27 + { 132.28 + label = _"Account access by member", 132.29 + content = function(record) 132.30 + ui.tag{ content = record.controller.name } 132.31 + end 132.32 + }, 132.33 + { 132.34 + label = _"Status", 132.35 + content = function(record) 132.36 + local text 132.37 + if record.accepted then 132.38 + text = _"accepted" 132.39 + elseif record.accepted == false then 132.40 + text = _"rejected" 132.41 + else 132.42 + text = _"not decided yet" 132.43 + end 132.44 + ui.tag{ content = text } 132.45 + end 132.46 + }, 132.47 + { 132.48 + content = function(record) 132.49 + ui.link{ content = _"Revoke", module = "member", action = "update_agent", params = { delete = true, controller_id = record.controller_id } } 132.50 + end 132.51 + }, 132.52 + } 132.53 + } 132.54 + else 132.55 + ui.container{ content = _"No other members are allowed to use this account." } 132.56 + end 132.57 + 132.58 + end } 132.59 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 132.60 + 132.61 + ui.form{ 132.62 + attr = { class = "wide" }, 132.63 + module = "member", 132.64 + action = "update_agent", 132.65 + routing = { 132.66 + ok = { 132.67 + mode = "redirect", 132.68 + module = "member", 132.69 + view = "settings_agent" 132.70 + } 132.71 + }, 132.72 + content = function() 132.73 + 132.74 + ui.container{ content = _"Add new account access privilege" } 132.75 + 132.76 + local contact_members = Member:build_selector{ 132.77 + is_contact_of_member_id = app.session.member_id, 132.78 + active = true, 132.79 + order = "name" 132.80 + }:add_where("NOT member.role"):exec() 132.81 + 132.82 + ui.field.select{ 132.83 + name = "controller_id", 132.84 + foreign_records = contact_members, 132.85 + foreign_id = "id", 132.86 + foreign_name = "name" 132.87 + } 132.88 + slot.put("<br />") 132.89 + 132.90 + ui.tag{ 132.91 + tag = "input", 132.92 + attr = { 132.93 + type = "submit", 132.94 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 132.95 + value = _"Grant account access" 132.96 + }, 132.97 + content = "" 132.98 + } 132.99 + slot.put(" ") 132.100 + ui.link { 132.101 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 132.102 + module = "member", view = "show", id = app.session.member_id, 132.103 + content = _"Cancel" 132.104 + } 132.105 + end 132.106 + } 132.107 + 132.108 + end } 132.109 + end } 132.110 + end } 132.111 + 132.112 + ui.cell_sidebar{ content = function() 132.113 + execute.view { 132.114 + module = "member", view = "_sidebar_whatcanido", params = { 132.115 + member = app.session.member 132.116 + } 132.117 + } 132.118 + end } 132.119 + 132.120 +end } 132.121 +
133.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 133.2 +++ b/app/main/member/settings_applications.lua Sun Jul 15 14:07:29 2018 +0200 133.3 @@ -0,0 +1,41 @@ 133.4 +ui.titleMember(_"Connected applications") 133.5 + 133.6 +ui.grid{ content = function() 133.7 + ui.cell_main{ content = function() 133.8 + 133.9 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 133.10 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 133.11 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Connected applications" } 133.12 + end } 133.13 + 133.14 + local applications = MemberApplication:by_member_id(app.session.member_id) 133.15 + 133.16 + for i, application in ipairs(applications) do 133.17 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 133.18 + if application.system_application_id then 133.19 + ui.heading{ level = 2, content = application.system_application.name } 133.20 + else 133.21 + ui.heading{ level = 2, content = application.domain } 133.22 + end 133.23 + local scopes = {} 133.24 + for scope in string.gmatch(application.scope, "[^ ]+") do 133.25 + scopes[#scopes+1] = util.scope_name(scope) 133.26 + end 133.27 + local scopes_string = table.concat(scopes, ", ") 133.28 + ui.container{ content = scopes_string } 133.29 + ui.link{ content = _"Remove application", module = "member", action = "remove_application", id = application.id } 133.30 + end } 133.31 + end 133.32 + 133.33 + end } 133.34 + end } 133.35 + 133.36 + ui.cell_sidebar{ content = function() 133.37 + execute.view { 133.38 + module = "member", view = "_sidebar_whatcanido", params = { 133.39 + member = app.session.member 133.40 + } 133.41 + } 133.42 + end } 133.43 + 133.44 +end }
134.1 --- a/app/main/member/settings_email.lua Thu Jun 23 03:30:57 2016 +0200 134.2 +++ b/app/main/member/settings_email.lua Sun Jul 15 14:07:29 2018 +0200 134.3 @@ -1,59 +1,73 @@ 134.4 ui.titleMember(_"Email address") 134.5 134.6 -execute.view { 134.7 - module = "member", view = "_sidebar_whatcanido", params = { 134.8 - member = app.session.member 134.9 - } 134.10 -} 134.11 +ui.grid{ content = function() 134.12 + ui.cell_main{ content = function() 134.13 134.14 -ui.form{ 134.15 - attr = { class = "vertical" }, 134.16 - module = "member", 134.17 - action = "update_email", 134.18 - routing = { 134.19 - ok = { 134.20 - mode = "redirect", 134.21 - module = "member", 134.22 - view = "show", 134.23 - id = app.session.member_id 134.24 - } 134.25 - }, 134.26 - content = function() 134.27 - ui.section( function() 134.28 - 134.29 - ui.sectionHead( function() 134.30 - ui.heading { level = 1, content = _"Email address for notifications" } 134.31 - end ) 134.32 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 134.33 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 134.34 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Email address for notifications" } 134.35 + end } 134.36 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 134.37 134.38 - ui.sectionRow( function() 134.39 - if app.session.member.notify_email then 134.40 - ui.field.text{ label = _"confirmed address", value = app.session.member.notify_email, readonly = true } 134.41 - end 134.42 - if app.session.member.notify_email_unconfirmed then 134.43 - ui.field.text{ label = _"unconfirmed address", value = app.session.member.notify_email_unconfirmed, readonly = true } 134.44 - end 134.45 - if app.session.member.notify_email or app.session.member.notify_email_unconfirmed then 134.46 - slot.put("<br />") 134.47 - end 134.48 - ui.heading { level = 2, content = _"Enter a new email address:" } 134.49 - ui.field.text{ name = "email" } 134.50 - slot.put("<br />") 134.51 - ui.tag{ 134.52 - tag = "input", 134.53 - attr = { 134.54 - type = "submit", 134.55 - class = "btn btn-default", 134.56 - value = _"Save" 134.57 + ui.form{ 134.58 + attr = { class = "vertical" }, 134.59 + module = "member", 134.60 + action = "update_email", 134.61 + routing = { 134.62 + ok = { 134.63 + mode = "redirect", 134.64 + module = "member", 134.65 + view = "settings" 134.66 + } 134.67 }, 134.68 - content = "" 134.69 + content = function() 134.70 + if app.session.member.notify_email then 134.71 + ui.field.text{ label = _"confirmed address", value = app.session.member.notify_email, readonly = true } 134.72 + end 134.73 + if app.session.member.notify_email_unconfirmed then 134.74 + ui.field.text{ label = _"unconfirmed address", value = app.session.member.notify_email_unconfirmed, readonly = true } 134.75 + end 134.76 + if app.session.member.notify_email or app.session.member.notify_email_unconfirmed then 134.77 + slot.put("<br />") 134.78 + end 134.79 + ui.heading { level = 4, content = _"Enter a new email address:" } 134.80 + ui.field.text{ 134.81 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 134.82 + attr = { id = "lf-member__notify_email", class = "mdl-textfield__input" }, 134.83 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-member__notify_email" }, 134.84 + label = _'email address', 134.85 + name = 'email', 134.86 + value = '' 134.87 + } 134.88 + slot.put("<br />") 134.89 + ui.tag{ 134.90 + tag = "input", 134.91 + attr = { 134.92 + type = "submit", 134.93 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 134.94 + value = _"Save" 134.95 + }, 134.96 + content = "" 134.97 + } 134.98 + slot.put(" ") 134.99 + ui.link{ 134.100 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 134.101 + content = _"Cancel", 134.102 + module = "member", view = "settings" 134.103 + } 134.104 + end 134.105 } 134.106 - slot.put("<br /><br /><br />") 134.107 - ui.link{ 134.108 - content = _"Cancel", 134.109 - module = "member", view = "show", id = app.session.member.id 134.110 - } 134.111 - end ) 134.112 - end ) 134.113 - end 134.114 -} 134.115 + 134.116 + end } 134.117 + end } 134.118 + end } 134.119 134.120 + ui.cell_sidebar{ content = function() 134.121 + execute.view { 134.122 + module = "member", view = "_sidebar_whatcanido", params = { 134.123 + member = app.session.member 134.124 + } 134.125 + } 134.126 + end } 134.127 + 134.128 +end }
135.1 --- a/app/main/member/settings_login.lua Thu Jun 23 03:30:57 2016 +0200 135.2 +++ b/app/main/member/settings_login.lua Sun Jul 15 14:07:29 2018 +0200 135.3 @@ -1,52 +1,69 @@ 135.4 ui.titleMember(_"login name") 135.5 135.6 -execute.view { 135.7 - module = "member", view = "_sidebar_whatcanido", params = { 135.8 - member = app.session.member 135.9 - } 135.10 -} 135.11 +ui.grid{ content = function() 135.12 + ui.cell_main{ content = function() 135.13 + 135.14 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 135.15 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 135.16 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Login name" } 135.17 + end } 135.18 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 135.19 135.20 -ui.form{ 135.21 - attr = { class = "wide" }, 135.22 - module = "member", 135.23 - action = "update_login", 135.24 - routing = { 135.25 - ok = { 135.26 - mode = "redirect", 135.27 - module = "member", 135.28 - view = "show", 135.29 - id = app.session.member_id 135.30 - } 135.31 - }, 135.32 - content = function() 135.33 - ui.section( function() 135.34 - ui.sectionHead( function() 135.35 - ui.heading { level = 1, content = _"Login name" } 135.36 - end ) 135.37 + ui.form{ 135.38 + attr = { class = "wide" }, 135.39 + module = "member", 135.40 + action = "update_login", 135.41 + routing = { 135.42 + ok = { 135.43 + mode = "redirect", 135.44 + module = "member", 135.45 + view = "settings" 135.46 + } 135.47 + }, 135.48 + content = function() 135.49 135.50 - ui.sectionRow( function() 135.51 - ui.heading { level = 2, content = _"Enter a new login name" } 135.52 - ui.field.text{ name = "login", value = app.session.member.login } 135.53 - 135.54 - slot.put("<br />") 135.55 - 135.56 - ui.tag{ 135.57 - tag = "input", 135.58 - attr = { 135.59 - type = "submit", 135.60 - class = "btn btn-default", 135.61 - value = _"Save" 135.62 - }, 135.63 - content = "" 135.64 + ui.field.text{ 135.65 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 135.66 + attr = { id = "lf-member__login", class = "mdl-textfield__input" }, 135.67 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-member__login" }, 135.68 + label= _'Login', 135.69 + name = 'login', 135.70 + value = app.session.member.login 135.71 + } 135.72 + 135.73 + slot.put("<br />") 135.74 + 135.75 + ui.tag{ 135.76 + tag = "input", 135.77 + attr = { 135.78 + type = "submit", 135.79 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 135.80 + value = _"Save" 135.81 + }, 135.82 + content = "" 135.83 + } 135.84 + slot.put(" ") 135.85 + 135.86 + ui.link { 135.87 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 135.88 + module = "member", view = "settings", 135.89 + content = _"Cancel" 135.90 + } 135.91 + end 135.92 } 135.93 - slot.put("<br /><br /><br />") 135.94 + 135.95 + 135.96 + end } 135.97 + end } 135.98 + end } 135.99 135.100 - ui.link { 135.101 - module = "member", view = "show", id = app.session.member_id, 135.102 - content = _"Cancel" 135.103 - } 135.104 - end ) 135.105 - end ) 135.106 - end 135.107 -} 135.108 + ui.cell_sidebar{ content = function() 135.109 + execute.view { 135.110 + module = "member", view = "_sidebar_whatcanido", params = { 135.111 + member = app.session.member 135.112 + } 135.113 + } 135.114 + end } 135.115 135.116 +end } 135.117 +
136.1 --- a/app/main/member/settings_name.lua Thu Jun 23 03:30:57 2016 +0200 136.2 +++ b/app/main/member/settings_name.lua Sun Jul 15 14:07:29 2018 +0200 136.3 @@ -1,50 +1,66 @@ 136.4 ui.titleMember(_"Screen name") 136.5 136.6 -execute.view { 136.7 - module = "member", view = "_sidebar_whatcanido", params = { 136.8 - member = app.session.member 136.9 - } 136.10 -} 136.11 +ui.grid{ content = function() 136.12 136.13 -ui.form{ 136.14 - attr = { class = "wide" }, 136.15 - module = "member", 136.16 - action = "update_name", 136.17 - routing = { 136.18 - ok = { 136.19 - mode = "redirect", 136.20 - module = "member", 136.21 - view = "show", 136.22 - id = app.session.member_id 136.23 - } 136.24 - }, 136.25 - content = function() 136.26 - ui.section( function() 136.27 - ui.sectionHead( function() 136.28 - ui.heading { level = 1, content = _"Screen name" } 136.29 - end ) 136.30 + ui.cell_main{ content = function() 136.31 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 136.32 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 136.33 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Screen name" } 136.34 + end } 136.35 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 136.36 136.37 - ui.sectionRow( function() 136.38 - ui.heading { level = 2, content = _"Enter a new screen name:" } 136.39 - ui.field.text{ name = "name", value = app.session.member.name } 136.40 - 136.41 - slot.put("<br />") 136.42 - 136.43 - ui.tag{ 136.44 - tag = "input", 136.45 - attr = { 136.46 - type = "submit", 136.47 - class = "btn btn-default", 136.48 - value = _"Save" 136.49 + ui.form{ 136.50 + attr = { class = "wide" }, 136.51 + module = "member", 136.52 + action = "update_name", 136.53 + routing = { 136.54 + ok = { 136.55 + mode = "redirect", 136.56 + module = "member", 136.57 + view = "settings" 136.58 + } 136.59 }, 136.60 - content = "" 136.61 + content = function() 136.62 + ui.field.text{ 136.63 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 136.64 + attr = { id = "lf-member__name", class = "mdl-textfield__input" }, 136.65 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-member__name" }, 136.66 + label= _'Screen name', 136.67 + name = 'name', 136.68 + value = app.session.member.name 136.69 + } 136.70 + 136.71 + slot.put("<br />") 136.72 + 136.73 + ui.tag{ 136.74 + tag = "input", 136.75 + attr = { 136.76 + type = "submit", 136.77 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 136.78 + value = _"Save" 136.79 + }, 136.80 + content = "" 136.81 + } 136.82 + slot.put(" ") 136.83 + ui.link { 136.84 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 136.85 + module = "member", view = "show", 136.86 + content = _"Cancel" 136.87 + } 136.88 + end 136.89 } 136.90 - slot.put("<br /><br /><br />") 136.91 - ui.link { 136.92 - module = "member", view = "show", id = app.session.member_id, 136.93 - content = _"Cancel" 136.94 - } 136.95 - end ) 136.96 - end ) 136.97 - end 136.98 -} 136.99 + 136.100 + end } 136.101 + end } 136.102 + end } 136.103 + 136.104 + ui.cell_sidebar{ content = function() 136.105 + execute.view { 136.106 + module = "member", view = "_sidebar_whatcanido", params = { 136.107 + member = app.session.member 136.108 + } 136.109 + } 136.110 + end } 136.111 + 136.112 +end } 136.113 +
137.1 --- a/app/main/member/settings_notification.lua Thu Jun 23 03:30:57 2016 +0200 137.2 +++ b/app/main/member/settings_notification.lua Sun Jul 15 14:07:29 2018 +0200 137.3 @@ -1,194 +1,224 @@ 137.4 local return_to = param.get("return_to") 137.5 -local return_to_area_id param.get("return_to_area_id", atom.integer) 137.6 +local return_to_area_id = param.get("return_to_area_id", atom.integer) 137.7 +local return_to_area = Area:by_id(return_to_area_id) 137.8 137.9 ui.titleMember(_"notification settings") 137.10 137.11 -execute.view { 137.12 - module = "member", view = "_sidebar_whatcanido", params = { 137.13 - member = app.session.member 137.14 - } 137.15 -} 137.16 +ui.grid{ content = function() 137.17 + ui.cell_main{ content = function() 137.18 + 137.19 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 137.20 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 137.21 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"notification settings" } 137.22 + end } 137.23 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 137.24 137.25 -ui.form{ 137.26 - attr = { class = "vertical" }, 137.27 - module = "member", 137.28 - action = "update_notify_level", 137.29 - routing = { 137.30 - ok = { 137.31 - mode = "redirect", 137.32 - module = return_to == "area" and "area" or return_to == "home" and "index" or "member", 137.33 - view = return_to == "area" and "show" or return_to == "home" and "index" or "show", 137.34 - id = return_to == "area" and return_to_area_id or return_to ~= "home" and app.session.member_id or nil 137.35 - } 137.36 - }, 137.37 - content = function() 137.38 - 137.39 - ui.section( function() 137.40 - 137.41 - ui.sectionHead( function() 137.42 - ui.heading { level = 1, content = _"Do you like to receive updates by email?" } 137.43 - end ) 137.44 - 137.45 - ui.sectionRow( function() 137.46 - 137.47 - ui.container{ content = function() 137.48 - ui.tag{ 137.49 - tag = "input", 137.50 - attr = { 137.51 - id = "notify_level_all", 137.52 - type = "radio", name = "disable_notifications", value = "false", 137.53 - checked = not app.session.member.disable_notifications and "checked" or nil, 137.54 - onchange = [[ $(".view_on_notify_level_all_false")[this.checked ? "show" : "hide"](400) ]] 137.55 + ui.form{ 137.56 + attr = { class = "vertical" }, 137.57 + module = "member", 137.58 + action = "update_notify_level", 137.59 + routing = { 137.60 + ok = { 137.61 + mode = "redirect", 137.62 + module = return_to == "area" and "index" or return_to == "home" and "index" or "member", 137.63 + view = return_to == "area" and "index" or return_to == "home" and "index" or "settings", 137.64 + params = return_to_area and { unit = return_to_area.unit_id, area = return_to_area.id } or nil 137.65 } 137.66 - } 137.67 - ui.tag{ 137.68 - tag = "label", attr = { ['for'] = "notify_level_all" }, 137.69 - content = _"I like to receive notifications" 137.70 - } 137.71 - end } 137.72 - 137.73 - 137.74 - ui.container{ attr = { class = "view_on_notify_level_all_false", style = "margin-left: 3em;" }, content = function() 137.75 - slot.put("<br />") 137.76 - 137.77 - ui.container{ content = _"You will receive status update notification on issue phase changes. Additionally you can subscribe for a regular digest including updates on initiative drafts and new suggestions." } 137.78 - slot.put("<br />") 137.79 - ui.container{ content = function() 137.80 - ui.tag{ 137.81 - tag = "input", 137.82 - attr = { 137.83 - id = "digest_on", 137.84 - type = "radio", name = "digest", value = "true", 137.85 - checked = (app.session.member.disable_notifications or app.session.member.notification_hour ~= nil) and "checked" or nil, 137.86 - onchange = [[ $(".view_on_digest_true")[this.checked ? "show" : "hide"](400) ]] 137.87 + }, 137.88 + content = function() 137.89 + 137.90 + 137.91 + ui.container{ content = function() 137.92 + ui.tag{ tag = "label", attr = { 137.93 + class = "mdl-radio mdl-js-radio mdl-js-ripple-effect", 137.94 + ["for"] = "notify_level_all" 137.95 + }, 137.96 + content = function() 137.97 + ui.tag{ 137.98 + tag = "input", 137.99 + attr = { 137.100 + id = "notify_level_all", 137.101 + class = "mdl-radio__button", 137.102 + type = "radio", name = "disable_notifications", value = "false", 137.103 + checked = not app.session.member.disable_notifications and "checked" or nil, 137.104 + } 137.105 + } 137.106 + ui.tag{ 137.107 + attr = { class = "mdl-radio__label", ['for'] = "notify_level_all" }, 137.108 + content = _"I like to receive notifications" 137.109 + } 137.110 + end 137.111 } 137.112 - } 137.113 + end } 137.114 + 137.115 + 137.116 + ui.container{ attr = { class = "view_on_notify_level_all_false", style = "margin-left: 3em;" }, content = function() 137.117 + slot.put("<br />") 137.118 + 137.119 + ui.container{ content = _"You will receive status update notification on issue phase changes. Additionally you can subscribe for a regular digest including updates on initiative drafts and new suggestions." } 137.120 + slot.put("<br />") 137.121 + ui.container{ content = function() 137.122 + ui.tag{ tag = "label", attr = { 137.123 + class = "mdl-radio mdl-js-radio mdl-js-ripple-effect", 137.124 + ["for"] = "digest_on" 137.125 + }, 137.126 + content = function() 137.127 + ui.tag{ 137.128 + tag = "input", 137.129 + attr = { 137.130 + id = "digest_on", 137.131 + class = "mdl-radio__button", 137.132 + type = "radio", name = "digest", value = "true", 137.133 + checked = (app.session.member.disable_notifications or app.session.member.notification_hour ~= nil) and "checked" or nil, 137.134 + } 137.135 + } 137.136 + ui.tag{ 137.137 + attr = { class = "mdl-radio__label", ['for'] = "digest_on" }, 137.138 + content = _"I want to receive a regular digest" 137.139 + } 137.140 + end 137.141 + } 137.142 + end } 137.143 + 137.144 + ui.container{ attr = { class = "view_on_digest_true", style = "margin-left: 4em;" }, content = function() 137.145 + 137.146 + ui.tag{ content = _"every" } 137.147 + slot.put(" ") 137.148 + ui.field.select{ 137.149 + container_attr = { style = "display: inline-block; vertical-align: middle;" }, 137.150 + attr = { style = "width: 10em;" }, 137.151 + name = "notification_dow", 137.152 + foreign_records = { 137.153 + { id = "daily", name = _"day" }, 137.154 + { id = 0, name = _"Sunday" }, 137.155 + { id = 1, name = _"Monday" }, 137.156 + { id = 2, name = _"Tuesday" }, 137.157 + { id = 3, name = _"Wednesday" }, 137.158 + { id = 4, name = _"Thursday" }, 137.159 + { id = 5, name = _"Friday" }, 137.160 + { id = 6, name = _"Saturday" } 137.161 + }, 137.162 + foreign_id = "id", 137.163 + foreign_name = "name", 137.164 + value = app.session.member.notification_dow 137.165 + } 137.166 + 137.167 + slot.put(" ") 137.168 + 137.169 + ui.tag{ content = _"between" } 137.170 + slot.put(" ") 137.171 + local foreign_records = {} 137.172 + for i = 0, 23 do 137.173 + foreign_records[#foreign_records+1] = { 137.174 + id = i, 137.175 + name = string.format("%02d:00 - %02d:59", i, i), 137.176 + } 137.177 + end 137.178 + local random_hour 137.179 + if app.session.member.disable_notifications or app.session.member.notification_hour == nil then 137.180 + random_hour = multirand.integer(0,23) 137.181 + end 137.182 + ui.field.select{ 137.183 + container_attr = { style = "display: inline-block; vertical-align: middle;" }, 137.184 + attr = { style = "width: 10em;" }, 137.185 + name = "notification_hour", 137.186 + foreign_records = foreign_records, 137.187 + foreign_id = "id", 137.188 + foreign_name = "name", 137.189 + value = random_hour or app.session.member.notification_hour 137.190 + } 137.191 + end } 137.192 + 137.193 + ui.container{ content = function() 137.194 + ui.tag{ tag = "label", attr = { 137.195 + class = "mdl-radio mdl-js-radio mdl-js-ripple-effect", 137.196 + ["for"] = "digest_off" 137.197 + }, 137.198 + content = function() 137.199 + ui.tag{ 137.200 + tag = "input", 137.201 + attr = { 137.202 + id = "digest_off", 137.203 + class = "mdl-radio__button", 137.204 + type = "radio", name = "digest", value = "false", 137.205 + checked = not app.session.member.disable_notifications and app.session.member.notification_dow == nil and app.session.member.notification_hour == nil and "checked" or nil, 137.206 + } 137.207 + } 137.208 + ui.tag{ 137.209 + tag = "label", attr = { class = "mdl-radio__label", ['for'] = "digest_off" }, 137.210 + content = _"don't send me a digest" 137.211 + } 137.212 + end 137.213 + } 137.214 + end } 137.215 + end } 137.216 + 137.217 + slot.put("<br />") 137.218 + 137.219 + ui.container{ content = function() 137.220 + ui.tag{ tag = "label", attr = { 137.221 + class = "mdl-radio mdl-js-radio mdl-js-ripple-effect", 137.222 + ["for"] = "notify_level_none" 137.223 + }, 137.224 + content = function() 137.225 + ui.tag{ 137.226 + tag = "input", 137.227 + attr = { 137.228 + id = "notify_level_none", 137.229 + class = "mdl-radio__button", 137.230 + type = "radio", name = "disable_notifications", value = "true", 137.231 + checked = app.session.member.disable_notifications and "checked" or nil 137.232 + } 137.233 + } 137.234 + ui.tag{ 137.235 + attr = { class = "mdl-radio__label", ['for'] = "notify_level_none" }, 137.236 + content = _"don't send me notifications by email" 137.237 + } 137.238 + end 137.239 + } 137.240 + end } 137.241 + 137.242 + slot.put("<br />") 137.243 + 137.244 ui.tag{ 137.245 - tag = "label", attr = { ['for'] = "digest_on" }, 137.246 - content = _"I want to receive a regular digest" 137.247 + tag = "input", 137.248 + attr = { 137.249 + type = "submit", 137.250 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 137.251 + value = _"Save" 137.252 + }, 137.253 + content = "" 137.254 } 137.255 - end } 137.256 - 137.257 - ui.container{ attr = { class = "view_on_digest_true", style = "margin-left: 4em;" }, content = function() 137.258 - 137.259 - ui.tag{ content = _"every" } 137.260 - slot.put(" ") 137.261 - ui.field.select{ 137.262 - container_attr = { style = "display: inline-block; vertical-align: middle;" }, 137.263 - attr = { style = "width: 10em;" }, 137.264 - name = "notification_dow", 137.265 - foreign_records = { 137.266 - { id = "daily", name = _"day" }, 137.267 - { id = 0, name = _"Sunday" }, 137.268 - { id = 1, name = _"Monday" }, 137.269 - { id = 2, name = _"Tuesday" }, 137.270 - { id = 3, name = _"Wednesday" }, 137.271 - { id = 4, name = _"Thursday" }, 137.272 - { id = 5, name = _"Friday" }, 137.273 - { id = 6, name = _"Saturday" } 137.274 - }, 137.275 - foreign_id = "id", 137.276 - foreign_name = "name", 137.277 - value = app.session.member.notification_dow 137.278 - } 137.279 + slot.put(" ") 137.280 137.281 slot.put(" ") 137.282 - 137.283 - ui.tag{ content = _"between" } 137.284 - slot.put(" ") 137.285 - local foreign_records = {} 137.286 - for i = 0, 23 do 137.287 - foreign_records[#foreign_records+1] = { 137.288 - id = i, 137.289 - name = string.format("%02d:00 - %02d:59", i, i), 137.290 + if return_to == "home" then 137.291 + ui.link { 137.292 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 137.293 + module = "index", view = "index", 137.294 + content = _"cancel" 137.295 + } 137.296 + else 137.297 + ui.link { 137.298 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 137.299 + module = "member", view = "settings", 137.300 + content = _"cancel" 137.301 } 137.302 end 137.303 - local random_hour 137.304 - if app.session.member.disable_notifications or app.session.member.notification_hour == nil then 137.305 - random_hour = multirand.integer(0,23) 137.306 - end 137.307 - ui.field.select{ 137.308 - container_attr = { style = "display: inline-block; vertical-align: middle;" }, 137.309 - attr = { style = "width: 10em;" }, 137.310 - name = "notification_hour", 137.311 - foreign_records = foreign_records, 137.312 - foreign_id = "id", 137.313 - foreign_name = "name", 137.314 - value = random_hour or app.session.member.notification_hour 137.315 - } 137.316 - end } 137.317 - 137.318 - ui.container{ content = function() 137.319 - ui.tag{ 137.320 - tag = "input", 137.321 - attr = { 137.322 - id = "digest_off", 137.323 - type = "radio", name = "digest", value = "false", 137.324 - checked = not app.session.member.disable_notifications and app.session.member.notification_dow == nil and app.session.member.notification_hour == nil and "checked" or nil, 137.325 - onchange = [[ $(".view_on_digest_true")[this.checked ? "hide" : "show"](400) ]] 137.326 - } 137.327 - } 137.328 - ui.tag{ 137.329 - tag = "label", attr = { ['for'] = "digest_off" }, 137.330 - content = _"don't send me a digest" 137.331 - } 137.332 - end } 137.333 - end } 137.334 - 137.335 - slot.put("<br />") 137.336 + 137.337 + end 137.338 + } 137.339 137.340 - ui.container{ content = function() 137.341 - ui.tag{ 137.342 - tag = "input", 137.343 - attr = { 137.344 - id = "notify_level_none", 137.345 - type = "radio", name = "disable_notifications", value = "true", 137.346 - checked = app.session.member.disable_notifications and "checked" or nil, 137.347 - onchange = [[ $(".view_on_notify_level_all_false")[this.checked ? "hide" : "show"](400) ]] 137.348 - } 137.349 - } 137.350 - ui.tag{ 137.351 - tag = "label", attr = { ['for'] = "notify_level_none" }, 137.352 - content = _"don't send me notifications by email" 137.353 - } 137.354 - end } 137.355 - 137.356 - if app.session.member.disable_notifications then 137.357 - ui.script{ script = [[ $(".view_on_notify_level_all_false").hide() ]] } 137.358 - end 137.359 - 137.360 - if not app.session.member.disable_notifications and app.session.member.notification_hour == nil then 137.361 - ui.script{ script = [[ $(".view_on_digest_true").hide() ]] } 137.362 - end 137.363 + end } 137.364 + end } 137.365 + end } 137.366 137.367 - slot.put("<br />") 137.368 - 137.369 - ui.tag{ 137.370 - tag = "input", 137.371 - attr = { 137.372 - type = "submit", 137.373 - class = "btn btn-default", 137.374 - value = _"Save" 137.375 - }, 137.376 - content = "" 137.377 - } 137.378 - slot.put("<br /><br /><br />") 137.379 - 137.380 - slot.put(" ") 137.381 - if return_to == "home" then 137.382 - ui.link { 137.383 - module = "index", view = "index", 137.384 - content = _"cancel" 137.385 - } 137.386 - else 137.387 - ui.link { 137.388 - module = "member", view = "show", id = app.session.member_id, 137.389 - content = _"cancel" 137.390 - } 137.391 - end 137.392 - end ) 137.393 - end ) 137.394 - 137.395 - end 137.396 -} 137.397 - 137.398 + ui.cell_sidebar{ content = function() 137.399 + execute.view { 137.400 + module = "member", view = "_sidebar_whatcanido", params = { 137.401 + member = app.session.member 137.402 + } 137.403 + } 137.404 + end } 137.405 + 137.406 +end }
138.1 --- a/app/main/member/settings_password.lua Thu Jun 23 03:30:57 2016 +0200 138.2 +++ b/app/main/member/settings_password.lua Sun Jul 15 14:07:29 2018 +0200 138.3 @@ -1,58 +1,88 @@ 138.4 ui.titleMember(_"Password") 138.5 138.6 -execute.view { 138.7 - module = "member", view = "_sidebar_whatcanido", params = { 138.8 - member = app.session.member 138.9 - } 138.10 -} 138.11 +ui.grid{ content = function() 138.12 + ui.cell_main{ content = function() 138.13 + 138.14 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 138.15 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 138.16 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Password" } 138.17 + end } 138.18 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 138.19 138.20 -ui.form{ 138.21 - attr = { class = "wide" }, 138.22 - module = "member", 138.23 - action = "update_password", 138.24 - routing = { 138.25 - ok = { 138.26 - mode = "redirect", 138.27 - module = "member", 138.28 - view = "show", 138.29 - id = app.session.member_id 138.30 - } 138.31 - }, 138.32 - content = function() 138.33 - ui.section( function() 138.34 - ui.sectionHead( function() 138.35 - ui.heading { level = 1, content = _"Password" } 138.36 - end ) 138.37 + ui.form{ 138.38 + attr = { class = "wide" }, 138.39 + module = "member", 138.40 + action = "update_password", 138.41 + routing = { 138.42 + ok = { 138.43 + mode = "redirect", 138.44 + module = "member", 138.45 + view = "settings" 138.46 + } 138.47 + }, 138.48 + content = function() 138.49 138.50 - ui.sectionRow( function() 138.51 - ui.heading { level = 2, content = _"Enter your current password:" } 138.52 - ui.field.password{ name = "old_password" } 138.53 + ui.field.password{ 138.54 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 138.55 + attr = { id = "lf-member__old_password", class = "mdl-textfield__input" }, 138.56 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-member__old_password" }, 138.57 + label= _'Curent password', 138.58 + name = 'old_password', 138.59 + value = "" 138.60 + } 138.61 + 138.62 + slot.put("<br />") 138.63 + 138.64 + ui.field.password{ 138.65 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 138.66 + attr = { id = "lf-member__new_password1", class = "mdl-textfield__input" }, 138.67 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-member__new_password1" }, 138.68 + label= _'New password', 138.69 + name = 'new_password1', 138.70 + value = "" 138.71 + } 138.72 138.73 - slot.put("<br />") 138.74 - 138.75 - ui.heading { level = 2, content = _"Enter a new password:" } 138.76 - ui.field.password{ name = "new_password1" } 138.77 + slot.put("<br />") 138.78 + 138.79 + ui.field.password{ 138.80 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 138.81 + attr = { id = "lf-member__new_password2", class = "mdl-textfield__input" }, 138.82 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-member__new_password2" }, 138.83 + label= _'Repeat new password', 138.84 + name = 'new_password2', 138.85 + value = "" 138.86 + } 138.87 138.88 - ui.heading { level = 2, content = _"Enter your new password again please:" } 138.89 - ui.field.password{ name = "new_password2" } 138.90 - 138.91 - slot.put("<br />") 138.92 + slot.put("<br />") 138.93 + 138.94 + ui.tag{ 138.95 + tag = "input", 138.96 + attr = { 138.97 + type = "submit", 138.98 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 138.99 + value = _"Save" 138.100 + }, 138.101 + content = "" 138.102 + } 138.103 + slot.put(" ") 138.104 + ui.link { 138.105 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 138.106 + module = "member", view = "show", id = app.session.member_id, 138.107 + content = _"Cancel" 138.108 + } 138.109 + end 138.110 + } 138.111 138.112 - ui.tag{ 138.113 - tag = "input", 138.114 - attr = { 138.115 - type = "submit", 138.116 - class = "btn btn-default", 138.117 - value = _"Save" 138.118 - }, 138.119 - content = "" 138.120 - } 138.121 - slot.put("<br /><br /><br />") 138.122 - ui.link { 138.123 - module = "member", view = "show", id = app.session.member_id, 138.124 - content = _"Cancel" 138.125 - } 138.126 - end ) 138.127 - end ) 138.128 - end 138.129 -} 138.130 \ No newline at end of file 138.131 + end } 138.132 + end } 138.133 + end } 138.134 + 138.135 + ui.cell_sidebar{ content = function() 138.136 + execute.view { 138.137 + module = "member", view = "_sidebar_whatcanido", params = { 138.138 + member = app.session.member 138.139 + } 138.140 + } 138.141 + end } 138.142 + 138.143 +end }
139.1 --- a/app/main/member/show.lua Thu Jun 23 03:30:57 2016 +0200 139.2 +++ b/app/main/member/show.lua Sun Jul 15 14:07:29 2018 +0200 139.3 @@ -61,129 +61,141 @@ 139.4 139.5 ui.titleMember(member) 139.6 139.7 -execute.view { 139.8 - module = "member", view = "_sidebar_whatcanido", params = { 139.9 - member = member 139.10 - } 139.11 -} 139.12 +ui.grid{ content = function() 139.13 + ui.cell_main{ content = function() 139.14 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 139.15 + 139.16 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 139.17 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 139.18 + execute.view{ 139.19 + module = "member_image", 139.20 + view = "_show", 139.21 + params = { 139.22 + member = member, 139.23 + image_type = "avatar", 139.24 + show_dummy = true, 139.25 + class = "left", 139.26 + force_update = app.session.member_id == member.id 139.27 + } 139.28 + } 139.29 + slot.put(" ") 139.30 + ui.tag{ content = member.name } 139.31 + end } 139.32 + ui.container { 139.33 + attr = { class = "float-right" }, 139.34 + content = function() 139.35 + ui.link{ 139.36 + content = _"Account history", 139.37 + module = "member", view = "history", id = member.id 139.38 + } 139.39 + end 139.40 + } 139.41 + end } 139.42 + 139.43 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 139.44 139.45 -execute.view { 139.46 - module = "member", view = "_sidebar_contacts", params = { 139.47 - member = member 139.48 - } 139.49 -} 139.50 + if member.identification then 139.51 + ui.container{ content = member.identification } 139.52 + end 139.53 139.54 + execute.view{ 139.55 + module = "member", 139.56 + view = "_profile", 139.57 + params = { member = member } 139.58 + } 139.59 139.60 -ui.section( function() 139.61 - ui.sectionHead( function() 139.62 - execute.view{ 139.63 - module = "member_image", 139.64 - view = "_show", 139.65 - params = { 139.66 - member = member, 139.67 - image_type = "avatar", 139.68 - show_dummy = true, 139.69 - class = "left", 139.70 - force_update = app.session.member_id == member.id 139.71 - } 139.72 - } 139.73 - ui.heading{ level = 1, content = member.name } 139.74 - slot.put("<br />") 139.75 - ui.container { 139.76 - attr = { class = "right" }, 139.77 - content = function() 139.78 - ui.link{ 139.79 - content = _"Account history", 139.80 - module = "member", view = "history", id = member.id 139.81 + --[[ 139.82 + execute.view { 139.83 + module = "member", view = "_timeline", 139.84 + params = { member = member } 139.85 } 139.86 - end 139.87 - } 139.88 - if member.identification then 139.89 - ui.container{ content = member.identification } 139.90 + --]] 139.91 + end } 139.92 + end } 139.93 + 139.94 + if #initiated_initiatives > 0 then 139.95 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 139.96 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 139.97 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Initiatives created by this member" } 139.98 + end } 139.99 + ui.container{ attr = { class = "initiative_list" }, content = function() 139.100 + execute.view { 139.101 + module = "initiative", view = "_list", 139.102 + params = { initiatives = initiated_initiatives, for_member = member }, 139.103 + } 139.104 + end } 139.105 + end } 139.106 + end 139.107 + 139.108 + if #supported_initiatives > 0 then 139.109 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 139.110 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 139.111 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"What this member is currently supporting" } 139.112 + end } 139.113 + ui.container{ attr = { class = "initiative_list" }, content = function() 139.114 + execute.view { 139.115 + module = "initiative", view = "_list", 139.116 + params = { initiatives = supported_initiatives, for_member = member }, 139.117 + } 139.118 + end } 139.119 + end } 139.120 end 139.121 - end ) 139.122 - ui.sectionRow( function() 139.123 - execute.view{ 139.124 - module = "member", 139.125 - view = "_profile", 139.126 - params = { member = member } 139.127 - } 139.128 - end ) 139.129 -end ) 139.130 + 139.131 + if #voted_initiatives > 0 then 139.132 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 139.133 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 139.134 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"How this member voted" } 139.135 + end } 139.136 + ui.container{ attr = { class = "initiative_list" }, content = function() 139.137 + execute.view { 139.138 + module = "initiative", view = "_list", 139.139 + params = { initiatives = voted_initiatives, for_member = member }, 139.140 + } 139.141 + end } 139.142 + end } 139.143 + end 139.144 + --[[ 139.145 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 139.146 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 139.147 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Outgoing delegations" } 139.148 + end } 139.149 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 139.150 + execute.view { 139.151 + module = "delegation", view = "_list", 139.152 + params = { delegations_selector = outgoing_delegations_selector, outgoing = true }, 139.153 + } 139.154 + end } 139.155 + end } 139.156 139.157 - 139.158 -ui.section( function() 139.159 - ui.sectionHead( function() 139.160 - ui.heading { level = 2, content = _"Initiatives created by this member" } 139.161 - end ) 139.162 - ui.sectionRow( function() 139.163 - for i, initiative in ipairs(initiated_initiatives) do 139.164 - execute.view { 139.165 - module = "initiative", view = "_list", 139.166 - params = { initiative = initiative }, 139.167 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 139.168 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 139.169 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Incoming delegations" } 139.170 + end } 139.171 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 139.172 + execute.view { 139.173 + module = "delegation", view = "_list", 139.174 + params = { delegations_selector = incoming_delegations_selector, incoming = true }, 139.175 + } 139.176 + end } 139.177 + end } 139.178 + --]] 139.179 + end } 139.180 + 139.181 + ui.cell_sidebar{ content = function() 139.182 + execute.view { 139.183 + module = "member", view = "_sidebar_whatcanido", params = { 139.184 member = member 139.185 } 139.186 - end 139.187 - end ) 139.188 -end ) 139.189 + } 139.190 139.191 -ui.section( function() 139.192 - ui.sectionHead( function() 139.193 - ui.heading { level = 2, content = _"What this member is currently supporting" } 139.194 - end ) 139.195 - ui.sectionRow( function() 139.196 - for i, initiative in ipairs(supported_initiatives) do 139.197 - execute.view { 139.198 - module = "initiative", view = "_list", 139.199 - params = { initiative = initiative }, 139.200 + execute.view { 139.201 + module = "member", view = "_sidebar_contacts", params = { 139.202 member = member 139.203 } 139.204 - end 139.205 - end ) 139.206 -end ) 139.207 - 139.208 -ui.section( function() 139.209 - ui.sectionHead( function() 139.210 - ui.heading { level = 2, content = _"How this member voted" } 139.211 - end ) 139.212 - ui.sectionRow( function() 139.213 - for i, initiative in ipairs(voted_initiatives) do 139.214 - execute.view { 139.215 - module = "initiative", view = "_list", 139.216 - params = { initiative = initiative } 139.217 - } 139.218 - end 139.219 - end ) 139.220 -end ) 139.221 - 139.222 + } 139.223 + end } 139.224 139.225 -ui.section( function() 139.226 - ui.sectionHead( function() 139.227 - ui.heading { level = 2, content = _"Outgoing delegations" } 139.228 - end ) 139.229 - ui.sectionRow( function() 139.230 - execute.view { 139.231 - module = "delegation", view = "_list", 139.232 - params = { delegations_selector = outgoing_delegations_selector, outgoing = true }, 139.233 - } 139.234 - end ) 139.235 -end ) 139.236 - 139.237 - 139.238 -ui.section( function() 139.239 - 139.240 - ui.sectionHead( function() 139.241 - ui.heading { level = 2, content = _"Incoming delegations" } 139.242 - end ) 139.243 - ui.sectionRow( function() 139.244 - execute.view { 139.245 - module = "delegation", view = "_list", 139.246 - params = { delegations_selector = incoming_delegations_selector, incoming = true }, 139.247 - } 139.248 - end ) 139.249 - 139.250 -end ) 139.251 - 139.252 +end } 139.253 139.254 if app.session.member_id == member.id then 139.255 ui.script{ script = [[
140.1 --- a/app/main/member_image/_show.lua Thu Jun 23 03:30:57 2016 +0200 140.2 +++ b/app/main/member_image/_show.lua Sun Jul 15 14:07:29 2018 +0200 140.3 @@ -13,15 +13,18 @@ 140.4 class = "" 140.5 end 140.6 140.7 - 140.8 if config.fastpath_url_func then 140.9 ui.image{ 140.10 - attr = { title = popup_text, class = "member_image member_image_" .. image_type .. class }, 140.11 + attr = { title = popup_text, class = "mdl-chip__contact member_image member_image_" .. image_type .. class }, 140.12 external = config.fastpath_url_func(member_id, image_type) 140.13 } 140.14 else 140.15 + local c = "mdl-chip__contact " 140.16 + if image_type == "photo" then 140.17 + c = "" 140.18 + end 140.19 ui.image{ 140.20 - attr = { title = popup_text, class = "member_image member_image_" .. image_type .. class }, 140.21 + attr = { title = popup_text, class = c .. "member_image member_image_" .. image_type .. class }, 140.22 module = "member_image", 140.23 view = "show", 140.24 extension = "jpg",
141.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 141.2 +++ b/app/main/oauth2/_action/accept_scope.lua Sun Jul 15 14:07:29 2018 +0200 141.3 @@ -0,0 +1,76 @@ 141.4 +local system_application_id = param.get("system_application_id", atom.integer) 141.5 +local domain = param.get("domain") 141.6 +local response_type = param.get("response_type") 141.7 + 141.8 +if domain then 141.9 + domain = string.lower(domain) 141.10 +end 141.11 +local scopes = {} 141.12 +for i = 0, math.huge do 141.13 + scopes[i] = param.get("scope" .. i) 141.14 + if not scopes[i] then 141.15 + break 141.16 + end 141.17 +end 141.18 + 141.19 +local redirect_uri = param.get("redirect_uri") 141.20 +local redirect_uri_explicit = param.get("redirect_uri_explicit", atom.boolean) 141.21 +local state = param.get("state") 141.22 + 141.23 +local selector 141.24 + 141.25 +if system_application_id then 141.26 + selector = MemberApplication:get_selector_by_member_id_and_system_application_id(app.session.member_id, system_application_id) 141.27 +else 141.28 + selector = MemberApplication:get_selector_by_member_id_and_domain(app.session.member_id, domain) 141.29 +end 141.30 +selector:for_update() 141.31 + 141.32 +local member_application = selector:exec() 141.33 + 141.34 +if not member_application then 141.35 + member_application = MemberApplication:new() 141.36 + member_application.member_id = app.session.member_id 141.37 + member_application.system_application_id = system_application_id 141.38 + member_application.domain = domain 141.39 +end 141.40 + 141.41 +local new_scopes = {} 141.42 + 141.43 +for i = 0, #scopes do 141.44 + if scopes[i] then 141.45 + for scope in string.gmatch(scopes[i], "[^ ]+") do 141.46 + new_scopes[scope] = true 141.47 + end 141.48 + end 141.49 +end 141.50 + 141.51 +if member_application.scopes then 141.52 + for scope in string.gmatch(member_application.scopes, "[^ ]+") do 141.53 + new_scopes[scope] = true 141.54 + end 141.55 +end 141.56 + 141.57 +local new_scopes_list = {} 141.58 + 141.59 +for k, v in pairs(new_scopes) do 141.60 + new_scopes_list[#new_scopes_list+1] = k 141.61 +end 141.62 + 141.63 +local new_scopes_string = table.concat(new_scopes_list, " ") 141.64 + 141.65 +member_application.scope = new_scopes_string 141.66 + 141.67 +member_application:save() 141.68 + 141.69 +execute.chunk{ module = "oauth2", chunk = "_authorization", params = { 141.70 + member_id = app.session.member_id, 141.71 + system_application_id = system_application_id, 141.72 + domain = domain, 141.73 + session_id = app.session.id, 141.74 + redirect_uri = redirect_uri, 141.75 + redirect_uri_explicit = redirect_uri_explicit, 141.76 + scopes = scopes, 141.77 + state = state, 141.78 + response_type = response_type 141.79 +} }
142.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 142.2 +++ b/app/main/oauth2/_authorization.lua Sun Jul 15 14:07:29 2018 +0200 142.3 @@ -0,0 +1,70 @@ 142.4 +local member_id = param.get("member_id", atom.integer) 142.5 +local system_application_id = param.get("system_application_id", atom.integer) 142.6 +local domain = param.get("domain") 142.7 +local session_id = param.get("session_id", atom.integer) 142.8 +local redirect_uri = param.get("redirect_uri") 142.9 +local redirect_uri_explicit = param.get("redirect_uri_explicit", atom.boolean) 142.10 +local scopes = param.get("scopes", "table") 142.11 +local state = param.get("state") 142.12 +local response_type = param.get("response_type") 142.13 + 142.14 +if response_type == "code" then 142.15 + 142.16 + local token = Token:create_authorization( 142.17 + member_id, 142.18 + system_application_id, 142.19 + domain, 142.20 + session_id, 142.21 + redirect_uri, 142.22 + redirect_uri_explicit, 142.23 + scopes, 142.24 + state 142.25 + ) 142.26 + 142.27 + request.redirect{ 142.28 + external = redirect_uri, 142.29 + params = { code = token.token, state = state } 142.30 + } 142.31 + 142.32 + 142.33 +elseif response_type == "token" then 142.34 + 142.35 + local expiry = db:query({ "SELECT now() + (? || 'sec')::interval AS access", config.oauth2.access_token_lifetime }, "object").access 142.36 + 142.37 + local anchor_params = { 142.38 + state = state, 142.39 + expires_in = config.oauth2.access_token_lifetime, 142.40 + token_type = "bearer" 142.41 + } 142.42 + 142.43 + for i = 0, #scopes do 142.44 + if scopes[i] then 142.45 + local access_token = Token:new() 142.46 + access_token.token_type = "access" 142.47 + access_token.member_id = member_id 142.48 + access_token.system_application_id = system_application_id 142.49 + access_token.domain = domain 142.50 + access_token.session_id = session_id 142.51 + access_token.expiry = expiry 142.52 + access_token.scope = scopes[i] 142.53 + access_token:save() 142.54 + local index = i == 0 and "" or i 142.55 + anchor_params["access_token" .. index] = access_token.token 142.56 + end 142.57 + end 142.58 + 142.59 + local anchor_params_list = {} 142.60 + for k, v in pairs(anchor_params) do 142.61 + anchor_params_list[#anchor_params_list+1] = k .. "=" .. encode.url_part(v) 142.62 + end 142.63 + local anchor = table.concat(anchor_params_list, "&") 142.64 + 142.65 + request.redirect{ 142.66 + external = redirect_uri .. "#" .. anchor 142.67 + } 142.68 + 142.69 +else 142.70 + 142.71 + error("Internal error, should not happen") 142.72 + 142.73 +end
143.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 143.2 +++ b/app/main/oauth2/_filter_view/10_transaction.lua Sun Jul 15 14:07:29 2018 +0200 143.3 @@ -0,0 +1,5 @@ 143.4 +db:query("BEGIN;") 143.5 + 143.6 +execute.inner() 143.7 + 143.8 +db:query("COMMIT;")
144.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 144.2 +++ b/app/main/oauth2/authorization.lua Sun Jul 15 14:07:29 2018 +0200 144.3 @@ -0,0 +1,267 @@ 144.4 +local function show_error(text) 144.5 + ui.title("Authorization") 144.6 + ui.section(function() 144.7 + ui.sectionHead(function() 144.8 + ui.heading{ content = _"Error during authorization" } 144.9 + end) 144.10 + ui.sectionRow(function() 144.11 + ui.container{ content = text } 144.12 + end ) 144.13 + end ) 144.14 +end 144.15 + 144.16 +local client_id = param.get("client_id") 144.17 +local redirect_uri = param.get("redirect_uri") 144.18 +local redirect_uri_explicit = redirect_uri and true or false 144.19 +local response_type = param.get("response_type") 144.20 +local state = param.get("state") 144.21 + 144.22 +local no_scope_requested = true 144.23 + 144.24 +local scopes = { 144.25 + [0] = param.get("scope") 144.26 +} 144.27 + 144.28 +for i = 1, math.huge do 144.29 + scopes[i] = param.get("scope" .. i) 144.30 + if not scopes[i] then 144.31 + break 144.32 + end 144.33 +end 144.34 + 144.35 +if #scopes == 0 and not scopes[0] then 144.36 + scopes[0] = "identification" 144.37 +end 144.38 + 144.39 +local requested_scopes = {} 144.40 + 144.41 +for i = 0, #scopes do 144.42 + if scopes[i] then 144.43 + for scope in string.gmatch(scopes[i], "[^ ]+") do 144.44 + requested_scopes[scope] = true 144.45 + end 144.46 + end 144.47 +end 144.48 + 144.49 +local system_application 144.50 +local member_application 144.51 +local client_name 144.52 +local scopes_to_accept = table.new(requested_scopes) 144.53 +local accepted_scopes = {} 144.54 + 144.55 +local domain 144.56 + 144.57 +if client_id then 144.58 + domain = string.match(client_id, "^dynamic:([a-z0-9.-]+)$") 144.59 +end 144.60 + 144.61 +local dynamic_application_check 144.62 +if domain then 144.63 + if #domain > 255 then 144.64 + return show_error(_"Domain too long") 144.65 + end 144.66 + if string.find(domain, "^%.") or string.find(domain, "%.$") or string.find(domain, "%.%.") then 144.67 + return show_error(_"Invalid domain format") 144.68 + end 144.69 + if redirect_uri then 144.70 + local redirect_uri_domain, magic = string.match(redirect_uri, "^[Hh][Tt][Tt][Pp][Ss]://([A-Za-z0-9_.-]+)/(.*)$") 144.71 + if not redirect_uri_domain or string.lower(redirect_uri_domain) ~= domain or magic ~= config.oauth2.endpoint_magic then 144.72 + return show_error(_"Redirect URI forbidden") 144.73 + end 144.74 + else 144.75 + redirect_uri = "https://" .. domain .. "/" .. config.oauth2.endpoint_magic 144.76 + end 144.77 + dynamic_application_check = DynamicApplicationScope:check_scopes(domain, redirect_uri, response_type, requested_scopes) 144.78 + if dynamic_application_check == "not_registered" then 144.79 + return show_error(_"Redirect URI or response type not registered") 144.80 + end 144.81 + client_name = domain 144.82 + member_application = MemberApplication:by_member_id_and_domain(app.session.member_id, domain) 144.83 + if member_application then 144.84 + for scope in string.gmatch(member_application.scope, "[^ ]+") do 144.85 + accepted_scopes[scope] = true 144.86 + scopes_to_accept[scope] = nil 144.87 + end 144.88 + end 144.89 +else 144.90 + system_application = SystemApplication:by_client_id(client_id) 144.91 + if system_application then 144.92 + if redirect_uri_explicit then 144.93 + if 144.94 + redirect_uri ~= system_application.default_redirect_uri 144.95 + and not SystemApplicationRedirectUri:by_pk(system_application.id, redirect_uri) 144.96 + then 144.97 + return show_error(_"Redirect URI invalid") 144.98 + end 144.99 + else 144.100 + redirect_uri = system_application.default_redirect_uri 144.101 + end 144.102 + if system_application.flow ~= response_type then 144.103 + return show_error(_"Response type not allowed for given client") 144.104 + end 144.105 + client_name = system_application.name 144.106 + member_application = MemberApplication:by_member_id_and_system_application_id(app.session.member_id, system_application.id) 144.107 + end 144.108 +end 144.109 + 144.110 +if not client_name then 144.111 + return show_error(_"Client ID invalid") 144.112 +end 144.113 + 144.114 +local function error_redirect(error_code, description) 144.115 + local params = { 144.116 + state = state, 144.117 + error = error_code, 144.118 + error_description = description 144.119 + } 144.120 + if response_type == "token" then 144.121 + local anchor_params_list = {} 144.122 + for k, v in pairs(params) do 144.123 + anchor_params_list[#anchor_params_list+1] = k .. "=" .. encode.url_part(v) 144.124 + end 144.125 + local anchor = table.concat(anchor_params_list, "&") 144.126 + request.redirect{ 144.127 + external = redirect_uri .. "#" .. anchor 144.128 + } 144.129 + else 144.130 + request.redirect{ 144.131 + external = redirect_uri, 144.132 + params = params 144.133 + } 144.134 + end 144.135 +end 144.136 + 144.137 +if response_type ~= "code" and response_type ~= "token" then 144.138 + return error_redirect("unsupported_response_type", "Invalid response type") 144.139 +end 144.140 + 144.141 +for i = 0, #scopes do 144.142 + if scopes[i] == "" then 144.143 + return error_redirect("invalid_scope", "Empty scope requested") 144.144 + end 144.145 +end 144.146 + 144.147 +for scope in pairs(requested_scopes) do 144.148 + local scope_valid = false 144.149 + for i, entry in ipairs(config.oauth2.available_scopes) do 144.150 + if scope == entry.scope or scope == entry.scope .. "_detached" then 144.151 + scope_valid = true 144.152 + break 144.153 + end 144.154 + end 144.155 + if not scope_valid then 144.156 + return error_redirect("invalid_scope", "Requested scope not available") 144.157 + end 144.158 +end 144.159 + 144.160 +if system_application then 144.161 + if system_application.permitted_scope then 144.162 + local permitted_scopes = {} 144.163 + for scope in string.gmatch(system_application.permitted_scope, "[^ ]+") do 144.164 + permitted_scopes[scope] = true 144.165 + end 144.166 + for scope in pairs(requested_scopes) do 144.167 + if not permitted_scopes[scope] then 144.168 + return error_redirect("invalid_scope", "Scope not permitted") 144.169 + end 144.170 + end 144.171 + end 144.172 + if system_application.forbidden_scope then 144.173 + for scope in string.gmatch(system_application.forbidden_scope, "[^ ]+") do 144.174 + if requested_scopes[scope] then 144.175 + return error_redirect("invalid_scope", "Scope forbidden") 144.176 + end 144.177 + end 144.178 + end 144.179 + if system_application.automatic_scope then 144.180 + for scope in string.gmatch(system_application.automatic_scope, "[^ ]+") do 144.181 + scopes_to_accept[scope] = nil 144.182 + accepted_scopes[scope] = true 144.183 + end 144.184 + end 144.185 + if member_application then 144.186 + for scope in string.gmatch(member_application.scope, "[^ ]+") do 144.187 + scopes_to_accept[scope] = nil 144.188 + accepted_scopes[scope] = true 144.189 + end 144.190 + end 144.191 +else 144.192 + if dynamic_application_check == "missing_scope" then 144.193 + return error_redirect("invalid_scope", "Scope not permitted") 144.194 + end 144.195 +end 144.196 + 144.197 +if next(scopes_to_accept) then 144.198 + ui.title("Application authorization") 144.199 + ui.section(function() 144.200 + ui.sectionHead(function() 144.201 + ui.heading{ content = client_name } 144.202 + ui.heading{ content = "wants to access your account" } 144.203 + end) 144.204 + if not system_application and not member_application then 144.205 + ui.sectionRow(function() 144.206 + ui.container{ content = _"Warning: Untrusted third party application." } 144.207 + end) 144.208 + end 144.209 + ui.sectionRow(function() 144.210 + ui.heading{ level = 3, content = _"Requested privileges:" } 144.211 + ui.tag{ tag = "ul", attr = { class = "ul" }, content = function() 144.212 + for i, entry in ipairs(config.oauth2.available_scopes) do 144.213 + local name = entry.name[locale.get("lang")] or entry.scope 144.214 + if accepted_scopes[entry.scope] or requested_scopes[entry.scope] or accepted_scopes[entry.scope .. "_detached"] or requested_scopes[entry.scope .. "_detached"] then 144.215 + ui.tag{ tag = "li", content = function() 144.216 + ui.tag{ content = name } 144.217 + if accepted_scopes[entry.scope .. "_detached"] or requested_scopes[entry.scope .. "_detached"] then 144.218 + slot.put(" ") 144.219 + ui.tag{ content = _"(detached)" } 144.220 + end 144.221 + if scopes_to_accept[entry.scope] or scopes_to_accept[entry.scope .. "_detached"] then 144.222 + slot.put(" ") 144.223 + ui.tag{ content = _"(new)" } 144.224 + end 144.225 + -- TODO display changes 144.226 + end } 144.227 + end 144.228 + end 144.229 + end } 144.230 + end ) 144.231 + local params = { 144.232 + system_application_id = system_application and system_application.id or nil, 144.233 + domain = domain, 144.234 + redirect_uri = redirect_uri, 144.235 + redirect_uri_explicit = redirect_uri_explicit, 144.236 + state = state, 144.237 + response_type = response_type 144.238 + } 144.239 + for i = 0, #scopes do 144.240 + params["scope" .. i] = scopes[i] 144.241 + end 144.242 + ui.form{ 144.243 + module = "oauth2", action = "accept_scope", params = params, 144.244 + routing = { default = { mode = "redirect", module = "oauth2", view = "authorization", params = request.get_param_strings() } }, 144.245 + content = function() 144.246 + ui.sectionRow(function() 144.247 + ui.submit{ text = _"Grant authorization", attr = { class = "mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect mdl-button--colored " } } 144.248 + slot.put(" ") 144.249 + ui.link{ content = _"Decline authorization", attr = { class = "mdl-button mdl-js-button" }, external = redirect_uri, params = { error = "access_denied", error_description = "User declined to authorize client" } } 144.250 + end ) 144.251 + end 144.252 + } 144.253 + end ) 144.254 +else 144.255 + 144.256 + execute.chunk{ module = "oauth2", chunk = "_authorization", params = { 144.257 + member_id = app.session.member_id, 144.258 + system_application_id = system_application and system_application.id or nil, 144.259 + domain = domain, 144.260 + session_id = app.session.id, 144.261 + redirect_uri = redirect_uri, 144.262 + redirect_uri_explicit = redirect_uri_explicit, 144.263 + scopes = scopes, 144.264 + state = state, 144.265 + response_type = response_type 144.266 + } } 144.267 + 144.268 + 144.269 +end 144.270 +
145.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 145.2 +++ b/app/main/oauth2/register.lua Sun Jul 15 14:07:29 2018 +0200 145.3 @@ -0,0 +1,77 @@ 145.4 +if not request.is_post() then 145.5 + return execute.view { module = "index", view = "405" } 145.6 +end 145.7 + 145.8 +slot.set_layout(nil, "application/json;charset=UTF-8") 145.9 + 145.10 +local r = json.object() 145.11 + 145.12 +local function error_result(error_code, error_description) 145.13 + -- TODO special HTTP status codes for some errors? 145.14 + request.set_status("400 Bad Request") 145.15 + slot.put_into("data", json.export{ 145.16 + error = error_code, 145.17 + error_description = error_description 145.18 + }) 145.19 +end 145.20 + 145.21 +local client_id = param.get("client_id") 145.22 +local flow = param.get("flow") 145.23 +local scope = param.get("scope") 145.24 + 145.25 +if flow ~= "code" and flow ~= "token" then 145.26 + return error_result("invalid_request", "invalid flow") 145.27 +end 145.28 + 145.29 +local domain 145.30 + 145.31 +if client_id then 145.32 + domain = string.match(client_id, "^dynamic:([a-z0-9.-]+)$") 145.33 + if not domain then 145.34 + return error_result("invalid_client", "invalid client_id (use lower case host name prefixed with 'dynamic:')") 145.35 + end 145.36 +end 145.37 + 145.38 +local cert_ca = request.get_header("X-LiquidFeedback-CA") 145.39 +local cert_distinguished_name = request.get_header("X-SSL-DN") 145.40 +local cert_common_name 145.41 + 145.42 +if cert_distinguished_name then 145.43 + cert_common_name = string.match(cert_distinguished_name, "%f[^/\0]CN=([A-Za-z0-9_.-]+)%f[/\0]") 145.44 + if not cert_common_name then 145.45 + return error_result("invalid_client", "CN in X.509 certificate invalid") 145.46 + end 145.47 +else 145.48 + return error_result("invalid_client", "X.509 client authorization missing") 145.49 +end 145.50 + 145.51 +if cert_ca ~= "public" then 145.52 + return error_result("invalid_client", "X.509 certificate not signed by publicly trusted certificate authority or wrong endpoint used") 145.53 +end 145.54 + 145.55 +if domain then 145.56 + if domain ~= cert_common_name then 145.57 + return error_result("invalid_grant", "CN in X.509 certificate incorrect") 145.58 + end 145.59 +else 145.60 + domain = cert_common_name 145.61 +end 145.62 + 145.63 +local redirect_uri = "https://" .. domain .. "/" .. config.oauth2.endpoint_magic 145.64 + 145.65 +local expiry = db:query({ "SELECT now() + (? || 'sec')::interval AS expiry", config.oauth2.dynamic_registration_lifetime }, "object").expiry 145.66 + 145.67 +for s in string.gmatch(scope, "[^ ]+") do 145.68 + local dynamic_application_scope = DynamicApplicationScope:new() 145.69 + dynamic_application_scope.redirect_uri = redirect_uri 145.70 + dynamic_application_scope.flow = flow 145.71 + dynamic_application_scope.scope = s 145.72 + dynamic_application_scope.expiry = expiry 145.73 + dynamic_application_scope:upsert_mode() 145.74 + dynamic_application_scope:save() 145.75 +end 145.76 + 145.77 +r.client_id = "dynamic:" .. domain 145.78 +r.expires_in = config.oauth2.dynamic_registration_lifetime 145.79 + 145.80 +slot.put_into("data", json.export(r))
146.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 146.2 +++ b/app/main/oauth2/session.lua Sun Jul 15 14:07:29 2018 +0200 146.3 @@ -0,0 +1,32 @@ 146.4 +if not request.is_post() then 146.5 + return execute.view { module = "index", view = "405" } 146.6 +end 146.7 + 146.8 +slot.set_layout(nil, "application/json") 146.9 + 146.10 +local r = json.object{ 146.11 + member_id = json.null 146.12 +} 146.13 + 146.14 +if app.session.member_id then 146.15 + local origin = request.get_header("Origin") 146.16 + if origin then 146.17 + local system_applications = SystemApplication:by_origin(origin) 146.18 + if #system_applications > 0 then 146.19 + r.member_id = app.session.member_id 146.20 + r.real_member_id = app.session.real_member_id 146.21 + if app.session.member.role then 146.22 + r.member_is_role = true 146.23 + end 146.24 + else 146.25 + local member_application = MemberApplication:by_member_id_and_origin(app.session.member_id, origin) 146.26 + if member_application then 146.27 + r.member_id = app.session.member_id 146.28 + r.real_member_id = app.session.real_member_id 146.29 + end 146.30 + end 146.31 + end 146.32 +end 146.33 + 146.34 +slot.put_into("data", json.export(r)) 146.35 +
147.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 147.2 +++ b/app/main/oauth2/token.lua Sun Jul 15 14:07:29 2018 +0200 147.3 @@ -0,0 +1,274 @@ 147.4 +if not request.is_post() then 147.5 + return execute.view { module = "index", view = "405" } 147.6 +end 147.7 + 147.8 +slot.set_layout(nil, "application/json;charset=UTF-8") 147.9 + 147.10 +request.add_header("Cache-Control", "no-store") 147.11 +request.add_header("Pragma", "no-cache") 147.12 + 147.13 +local function error_result(error_code, error_description) 147.14 + -- TODO special HTTP status codes for some errors? 147.15 + request.set_status("400 Bad Request") 147.16 + slot.put_into("data", json.export{ 147.17 + error = error_code, 147.18 + error_description = error_description 147.19 + }) 147.20 +end 147.21 + 147.22 +local token 147.23 +local grant_type = param.get("grant_type") 147.24 +if grant_type == "authorization_code" then 147.25 + local code = param.get("code") 147.26 + token = Token:by_token_type_and_token("authorization", code) 147.27 +elseif grant_type == "refresh_token" then 147.28 + local refresh_token = param.get("refresh_token") 147.29 + token = Token:by_token_type_and_token("refresh", refresh_token) 147.30 +elseif grant_type == "access_token" then 147.31 + local access_token, access_token_err = util.get_access_token() 147.32 + if access_token_err then 147.33 + if access_token_err == "header_and_param" then 147.34 + return error_result("invalid_request", "Access token passed both via header and param") 147.35 + end 147.36 + error("Error in util.get_access_token") 147.37 + end 147.38 + token = Token:by_token_type_and_token("access", access_token) 147.39 +else 147.40 + return error_result("unsupported_grant_type", "Grant type not supported") 147.41 +end 147.42 + 147.43 +if not token then 147.44 + return error_result("invalid_grant", "Token invalid or expired") 147.45 +end 147.46 + 147.47 +if grant_type == "authorization_code" then 147.48 + if not token.used then 147.49 + local expiry = db:query({"SELECT now() + (? || 'sec')::interval AS expiry", config.oauth2.authorization_code_lifetime }, "object").expiry 147.50 + token.used = true 147.51 + token.expiry = expiry 147.52 + token:save() 147.53 + else 147.54 + token:destroy() 147.55 + return error_result("invalid_grant", "Token invalid or expired") 147.56 + end 147.57 +end 147.58 + 147.59 +if grant_type ~= "access_token" then 147.60 + local cert_ca = request.get_header("X-LiquidFeedback-CA") 147.61 + local cert_distinguished_name = request.get_header("X-SSL-DN") 147.62 + local cert_common_name 147.63 + if cert_distinguished_name then 147.64 + cert_common_name = string.match(cert_distinguished_name, "%f[^/\0]CN=([A-Za-z0-9_.-]+)%f[/\0]") 147.65 + if not cert_common_name then 147.66 + return error_result("invalid_client", "CN in X.509 certificate invalid") 147.67 + end 147.68 + else 147.69 + return error_result("invalid_client", "X.509 client authorization missing") 147.70 + end 147.71 + if token.system_application then 147.72 + if cert_ca ~= "private" then 147.73 + return error_result("invalid_client", "X.509 certificate not signed by private certificate authority or wrong endpoint used") 147.74 + end 147.75 + if cert_common_name ~= token.system_application.cert_common_name then 147.76 + return error_result("invalid_grant", "CN in X.509 certificate incorrect") 147.77 + end 147.78 + else 147.79 + if cert_ca ~= "public" then 147.80 + return error_result("invalid_client", "X.509 certificate not signed by publicly trusted certificate authority or wrong endpoint used") 147.81 + end 147.82 + if cert_common_name ~= token.domain then 147.83 + return error_result("invalid_grant", "CN in X.509 certificate incorrect") 147.84 + end 147.85 + end 147.86 + local client_id = param.get("client_id") 147.87 + if client_id then 147.88 + if token.system_application then 147.89 + if client_id ~= token.system_application.client_id then 147.90 + return error_result("invalid_grant", "Token was issued to another client") 147.91 + end 147.92 + else 147.93 + if client_id ~= "dynamic:" .. token.domain then 147.94 + return error_result ("invalid_grant", "Token was issued to another client") 147.95 + end 147.96 + end 147.97 + elseif grant_type == "authorization_code" and not cert_common_name then 147.98 + return error_result("invalid_request", "No client_id supplied for authorization_code request") 147.99 + end 147.100 +end 147.101 + 147.102 +if grant_type == "authorization_code" then 147.103 + local redirect_uri = param.get("redirect_uri") 147.104 + if (token.redirect_uri_explicit or redirect_uri) and token.redirect_uri ~= redirect_uri then 147.105 + return error_result("invalid_request", "Redirect URI missing or invalid") 147.106 + end 147.107 +end 147.108 + 147.109 +local scopes = { 147.110 + [0] = param.get("scope") 147.111 +} 147.112 +for i = 1, math.huge do 147.113 + scopes[i] = param.get("scope" .. i) 147.114 + if not scopes[i] then 147.115 + break 147.116 + end 147.117 +end 147.118 + 147.119 +if not scopes[0] and #scopes == 0 then 147.120 + for dummy, token_scope in ipairs(token.token_scopes) do 147.121 + scopes[token_scope.index] = token_scope.scope 147.122 + end 147.123 +end 147.124 + 147.125 +local allowed_scopes = {} 147.126 +local requested_detached_scopes = {} 147.127 +for scope in string.gmatch(token.scope, "[^ ]+") do 147.128 + allowed_scopes[scope] = true 147.129 +end 147.130 +for i = 0, #scopes do 147.131 + if scopes[i] then 147.132 + for scope in string.gmatch(scopes[i], "[^ ]+") do 147.133 + if string.match(scope, "_detached$") then 147.134 + requested_detached_scopes[scope] = true 147.135 + end 147.136 + if not allowed_scopes[scope] then 147.137 + return error_result("invalid_scope", "Scope exceeds limits") 147.138 + end 147.139 + end 147.140 + end 147.141 +end 147.142 + 147.143 +local expiry 147.144 + 147.145 +if grant_type == "access_token" then 147.146 + expiry = db:query({ "SELECT FLOOR(EXTRACT(EPOCH FROM ? - now())) AS access_time_left", token.expiry }, "object") 147.147 +else 147.148 + expiry = db:query({ 147.149 + "SELECT now() + (? || 'sec')::interval AS refresh, now() + (? || 'sec')::interval AS access", 147.150 + config.oauth2.refresh_token_lifetime, 147.151 + config.oauth2.access_token_lifetime 147.152 + }, "object") 147.153 +end 147.154 + 147.155 +if grant_type == "refresh_token" then 147.156 + local requested_detached_scopes_list = {} 147.157 + for scope in pairs(requested_detached_scopes) do 147.158 + requested_detached_scopes_list[#requested_detached_scopes_list+1] = scope 147.159 + end 147.160 + local tokens_to_reduce = Token:old_refresh_token_by_token(token, requested_detached_scopes_list) 147.161 + for dummy, t in ipairs(tokens_to_reduce) do 147.162 + local t_scopes = {} 147.163 + for t_scope in string.gmatch(t.scope, "[^ ]+") do 147.164 + t_scopes[t_scope] = true 147.165 + end 147.166 + for scope in pairs(requested_detached_scopes) do 147.167 + local scope_without_detached = string.gmatch(scope, "(.+)_detached") 147.168 + if t_scope[scope] then 147.169 + t_scope[scope] = nil 147.170 + t_scope[scope_without_detached] = true 147.171 + end 147.172 + end 147.173 + local t_scope_list = {} 147.174 + for scope in pairs(t_scopes) do 147.175 + t_scope_list[#t_scope_list+1] = scope 147.176 + end 147.177 + t.scope = table.concat(t_scope_list, " ") 147.178 + t:save() 147.179 + end 147.180 +end 147.181 + 147.182 +local r = json.object() 147.183 + 147.184 +local refresh_token 147.185 +if 147.186 + grant_type ~= "access_token" 147.187 + and (grant_type == "authorization_code" or #(Token:fresh_refresh_token_by_token(token)) == 0) 147.188 +then 147.189 + refresh_token = Token:new() 147.190 + refresh_token.token_type = "refresh" 147.191 + if grant_type == "authorization_code" then 147.192 + refresh_token.authorization_token_id = token.id 147.193 + else 147.194 + refresh_token.authorization_token_id = token.authorization_token_id 147.195 + end 147.196 + refresh_token.member_id = token.member_id 147.197 + refresh_token.system_application_id = token.system_application_id 147.198 + refresh_token.domain = token.domain 147.199 + refresh_token.session_id = token.session_id 147.200 + refresh_token.expiry = expiry.refresh 147.201 + refresh_token.scope = token.scope 147.202 + refresh_token:save() 147.203 + r.refresh_token = refresh_token.token 147.204 +end 147.205 + 147.206 + 147.207 +r.token_type = "bearer" 147.208 +if grant_type == "access_token" then 147.209 + r.expires_in = expiry.access_time_left 147.210 +else 147.211 + r.expires_in = config.oauth2.access_token_lifetime 147.212 +end 147.213 + 147.214 +for i = 0, #scopes do 147.215 + if scopes[i] then 147.216 + local scope = scopes[i] 147.217 + local access_token = Token:new() 147.218 + access_token.token_type = "access" 147.219 + if grant_type == "authorization_code" then 147.220 + access_token.authorization_token_id = token.id 147.221 + else 147.222 + access_token.authorization_token_id = token.authorization_token_id 147.223 + end 147.224 + access_token.member_id = token.member_id 147.225 + access_token.system_application_id = token.system_application_id 147.226 + access_token.domain = token.domain 147.227 + access_token.session_id = token.session_id 147.228 + if grant_type == "access_token" then 147.229 + access_token.expiry = token.expiry 147.230 + else 147.231 + access_token.expiry = expiry.access 147.232 + end 147.233 + access_token.scope = scope 147.234 + access_token:save() 147.235 + if refresh_token then 147.236 + local refresh_token_scope = TokenScope:new() 147.237 + refresh_token_scope.token_id = refresh_token.id 147.238 + refresh_token_scope.index = i 147.239 + refresh_token_scope.scope = scope 147.240 + refresh_token_scope:save() 147.241 + end 147.242 + local index = i == 0 and "" or i 147.243 + r["access_token" .. index] = access_token.token 147.244 + end 147.245 +end 147.246 + 147.247 +r.member_id = token.member_id 147.248 +if token.member.role then 147.249 + r.member_is_role = true 147.250 +end 147.251 +if token.session then 147.252 + r.real_member_id = token.real_member_id 147.253 +end 147.254 + 147.255 +if param.get("include_member", atom.boolean) then 147.256 + if allowed_scopes.identification or allowed_scopes.authentication then 147.257 + local member = token.member 147.258 + r.member = json.object{ 147.259 + id = member.id, 147.260 + name = member.name, 147.261 + } 147.262 + if token.session and token.session.real_member then 147.263 + r.real_member = json.object{ 147.264 + id = token.session.real_member.id, 147.265 + name = token.session.real_member.name, 147.266 + } 147.267 + end 147.268 + if allowed_scopes.identification then 147.269 + r.member.identification = member.identification 147.270 + if token.session and token.session.real_member then 147.271 + r.real_member.identification = token.session.real_member.identification 147.272 + end 147.273 + end 147.274 + end 147.275 +end 147.276 + 147.277 +slot.put_into("data", json.export(r))
148.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 148.2 +++ b/app/main/oauth2/validate.lua Sun Jul 15 14:07:29 2018 +0200 148.3 @@ -0,0 +1,85 @@ 148.4 +if not request.is_post() then 148.5 + return execute.view { module = "index", view = "405" } 148.6 +end 148.7 + 148.8 +slot.set_layout(nil, "application/json") 148.9 + 148.10 +local function error_result(error_code, description) 148.11 + local r = json.object() 148.12 + r.error = error_code 148.13 + r.error_description = description 148.14 + slot.put_into("data", json.export(r)) 148.15 + request.set_status("400 Bad Request") 148.16 +end 148.17 + 148.18 +local access_token, access_token_err = util.get_access_token() 148.19 + 148.20 +if access_token_err then 148.21 + if access_token_err == "header_and_param" then 148.22 + return error_result("invalid_request", "Access token passed both via header and param") 148.23 + end 148.24 + error("Error in util.get_access_token") 148.25 +end 148.26 + 148.27 +if not access_token then 148.28 + return error_result("invalid_token", "No access token supplied") 148.29 +end 148.30 + 148.31 +local token = Token:by_token_type_and_token("access", access_token) 148.32 + 148.33 +if not token then 148.34 + return error_result("invalid_token", "Access token invalid") 148.35 +end 148.36 + 148.37 +local scopes = {} 148.38 +for scope in string.gmatch(token.scope, "[^ ]+") do 148.39 + local match = string.match(scope, "(.+)_detached$") 148.40 + scopes[match or scope] = true 148.41 +end 148.42 +local scope_list = {} 148.43 +for scope in pairs(scopes) do 148.44 + scope_list[#scope_list+1] = scope 148.45 +end 148.46 +table.sort(scope_list) 148.47 +local scope = table.concat(scope_list, " ") 148.48 + 148.49 +local r = json.object() 148.50 +r.scope = scope 148.51 +r.member_id = token.member_id 148.52 +if token.member.role then 148.53 + r.member_is_role = true 148.54 +end 148.55 +if token.session then 148.56 + r.real_member_id = token.session.real_member_id 148.57 +end 148.58 + 148.59 +if param.get("include_member", atom.boolean) then 148.60 + if scopes.identification or scopes.authentication then 148.61 + local member = token.member 148.62 + r.member = json.object{ 148.63 + id = member.id, 148.64 + name = member.name, 148.65 + } 148.66 + if token.session and token.session.real_member then 148.67 + r.real_member = json.object{ 148.68 + id = token.session.real_member.id, 148.69 + name = token.session.real_member.name, 148.70 + } 148.71 + end 148.72 + if scopes.identification then 148.73 + r.member.identification = member.identification 148.74 + if token.session and token.session.real_member then 148.75 + r.real_member.identification = token.session.real_member.identification 148.76 + end 148.77 + end 148.78 + if param.get("include_member_notify_email", atom.boolean) then 148.79 + r.member.notify_email = member.notify_email 148.80 + end 148.81 + end 148.82 +end 148.83 + 148.84 +r.logged_in = token.session_id and true or false 148.85 +slot.put_into("data", json.export(r)) 148.86 + 148.87 + 148.88 +
149.1 --- a/app/main/opinion/_action/update.lua Thu Jun 23 03:30:57 2016 +0200 149.2 +++ b/app/main/opinion/_action/update.lua Sun Jul 15 14:07:29 2018 +0200 149.3 @@ -41,7 +41,7 @@ 149.4 end 149.5 149.6 if degree ~= 0 and not app.session.member:has_voting_right_for_unit_id(suggestion.initiative.issue.area.unit_id) then 149.7 - error("access denied") 149.8 + return execute.view { module = "index", view = "403" } 149.9 end 149.10 149.11 if not opinion then
150.1 --- a/app/main/policy/_list.lua Thu Jun 23 03:30:57 2016 +0200 150.2 +++ b/app/main/policy/_list.lua Sun Jul 15 14:07:29 2018 +0200 150.3 @@ -20,11 +20,12 @@ 150.4 150.5 ui.heading { level = 3, content = policy.name } 150.6 150.7 - ui.tag{ 150.8 - content = policy.description 150.9 - } 150.10 - 150.11 - slot.put ( "<br />" ) 150.12 + if policy.description and #(policy.description) > 0 then 150.13 + ui.tag{ 150.14 + content = policy.description 150.15 + } 150.16 + slot.put ( "<br />" ) 150.17 + end 150.18 150.19 ui.link { 150.20 attr = { 150.21 @@ -55,8 +56,8 @@ 150.22 if policy.polling then 150.23 ui.field.text{ label = _"New" .. ":", value = _"without" } 150.24 else 150.25 - ui.field.text{ label = _"New" .. ":", value = "≤ min " .. format.interval_text(policy.max_admission_time) } 150.26 - ui.field.text{ label = _"New" .. ":", value = "≤ max " .. format.interval_text(policy.max_admission_time) } 150.27 + ui.field.text{ label = _"New" .. ":", value = "≥ " .. format.interval_text(policy.min_admission_time) } 150.28 + ui.field.text{ label = _"New" .. ":", value = "≤ " .. format.interval_text(policy.max_admission_time) } 150.29 end 150.30 ui.field.text{ label = _"Discussion" .. ":", value = format.interval_text(policy.discussion_time) or _"variable" } 150.31 ui.field.text{ label = _"Frozen" .. ":", value = format.interval_text(policy.verification_time) or _"variable" } 150.32 @@ -69,7 +70,7 @@ 150.33 else 150.34 ui.field.text{ 150.35 label = _"Issue quorum" .. ":", 150.36 - value = "≥ " .. tostring(policy.issue_quorum_num) .. "/" .. tostring(policy.issue_quorum_den) 150.37 + value = "≥ " .. tostring(policy.issue_quorum) 150.38 } 150.39 end 150.40 ui.field.text{ 150.41 @@ -88,4 +89,4 @@ 150.42 } 150.43 end 150.44 } 150.45 -end 150.46 \ No newline at end of file 150.47 +end
151.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 151.2 +++ b/app/main/registration/_action/register.lua Sun Jul 15 14:07:29 2018 +0200 151.3 @@ -0,0 +1,296 @@ 151.4 +local function check_italian_mobile_phone_number(value) 151.5 + 151.6 + if not value then 151.7 + return false 151.8 + end 151.9 + 151.10 + value = string.gsub(value, "[^0-9]*", "") 151.11 + 151.12 + if #(value) < 9 or #(value) > 10 then 151.13 + return false 151.14 + end 151.15 + 151.16 + local mobile_phone_prefixes = { 151.17 + { min = 320, max = 329, comment = "Wind Tre" }, 151.18 + { min = 330, max = 339, comment = "Telecom Italia (TIM)" }, 151.19 + { min = 340, max = 349, comment = "Vodafone Omnitel" }, 151.20 + { min = 350, max = 359, comment = "" }, 151.21 + { min = 360, max = 369, comment = "Telecom Italia (TIM)" }, 151.22 + { min = 370, max = 379, comment = "" }, 151.23 + { min = 380, max = 389, comment = "Wind Tre" }, 151.24 + { min = 390, max = 393, comment = "Wind Tre" }, 151.25 + { min = 394, max = 399, comment = "Wind Tre" } 151.26 + } 151.27 + 151.28 + local value_prefix = tonumber(string.match(value, "^(...)")) 151.29 + 151.30 + local valid_prefix = false 151.31 + 151.32 + for i, prefix in ipairs(mobile_phone_prefixes) do 151.33 + trace.debug(value_prefix, prefix.min) 151.34 + if value_prefix >= prefix.min and value_prefix <= prefix.max then 151.35 + valid_prefix = true 151.36 + end 151.37 + end 151.38 + 151.39 + if valid_prefix then 151.40 + return true 151.41 + else 151.42 + return false 151.43 + end 151.44 +end 151.45 + 151.46 +local function check_uk_mobile_phone_number(value) 151.47 + 151.48 + if not value then 151.49 + return false 151.50 + end 151.51 + 151.52 + value = string.gsub(value, "[^0-9]*", "") 151.53 + 151.54 + if #(value) < 11 or #(value) > 11 then 151.55 + return false 151.56 + end 151.57 + 151.58 + local mobile_phone_prefixes = { 151.59 + { min = 071, max = 079, comment = "UK phone" }, 151.60 + } 151.61 + 151.62 + local value_prefix = tonumber(string.match(value, "^(...)")) 151.63 + 151.64 + local valid_prefix = false 151.65 + 151.66 + for i, prefix in ipairs(mobile_phone_prefixes) do 151.67 + trace.debug(value_prefix, prefix.min) 151.68 + if value_prefix >= prefix.min and value_prefix <= prefix.max then 151.69 + valid_prefix = true 151.70 + end 151.71 + end 151.72 + 151.73 + if valid_prefix then 151.74 + return true 151.75 + else 151.76 + return false 151.77 + end 151.78 +end 151.79 + 151.80 +local errors = 0 151.81 + 151.82 +local manual_verification 151.83 + 151.84 +if config.self_registration.allow_bypass_checks and param.get("manual_verification") then 151.85 + manual_verification = true 151.86 +end 151.87 + 151.88 +for i, checkbox in ipairs(config.use_terms_checkboxes) do 151.89 + local accepted = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) 151.90 + if not accepted then 151.91 + slot.put_into("error", checkbox.not_accepted_error) 151.92 + errors = errors + 1 151.93 + end 151.94 +end 151.95 + 151.96 +local email = param.get("email") 151.97 + 151.98 +local members = Member:new_selector() 151.99 + :add_where{ "notify_email = ? OR notify_email_unconfirmed = ?", email } 151.100 + :exec() 151.101 + 151.102 +if #members > 0 then 151.103 + slot.select("error", function() 151.104 + slot.put_into("registration_register_email_invalid", "already_used") 151.105 + ui.tag{ content = _"This email address already been used. Please check your inbox for an invitation or contact us." } 151.106 + end) 151.107 + errors = errors + 1 151.108 +end 151.109 + 151.110 +local verification = Verification:new() 151.111 +verification.requested = "now" 151.112 +verification.request_origin = json.object{ 151.113 + ip = request.get_header("X-Forwarded-For"), 151.114 + hostname = request.get_header("X-Forwarded-Host") 151.115 +} 151.116 +verification.request_data = json.object() 151.117 + 151.118 +for i, field in ipairs(config.self_registration.fields) do 151.119 + if field.name == "date_of_birth" then 151.120 + local day = tonumber(param.get("verification_data_" .. field.name .. "_day")) 151.121 + local month = tonumber(param.get("verification_data_" .. field.name .. "_month")) 151.122 + local year = tonumber(param.get("verification_data_" .. field.name .. "_year")) 151.123 + local date = atom.date:new{ year = year, month = month, day = day } 151.124 + if date.invalid then 151.125 + slot.select("error", function() 151.126 + ui.container{ content = _"Please check date of birth" } 151.127 + slot.put_into("self_registration__invalid_" .. field.name, "invalid") 151.128 + end) 151.129 + errors = errors + 1 151.130 + end 151.131 + local today = atom.date:get_current() 151.132 + local date_16y_ago = atom.date:new{ year = today.year - 16, month = today.month, day = today.day } 151.133 + if date_16y_ago.invalid and today.month == 2 and today.day == 29 then 151.134 + date_16y_ago = atom.date:new{ year = today.year - 16, month = 2, day = 28 } 151.135 + end 151.136 + if date > date_16y_ago then 151.137 + request.redirect{ external = encode.url { module = "registration", view = "register_rejected_age" } } 151.138 + return 151.139 + end 151.140 + verification.request_data[field.name] = string.format("%04i-%02i-%02i", year, month, day) 151.141 + 151.142 + else 151.143 + local value = param.get("verification_data_" .. field.name) 151.144 + if not value or (#value < 1 and (not manual_verification or field.name ~= "mobile_phone")) then 151.145 + slot.put_into("self_registration__invalid_" .. field.name, "to_short") 151.146 + slot.select("error", function() 151.147 + ui.container{ content = _("Please enter: #{field_name}", { field_name = field.label }) } 151.148 + end) 151.149 + errors = errors + 1 151.150 + end 151.151 + if field.name == "fiscal_code" then 151.152 + value = string.upper(value) 151.153 + value = string.gsub(value, "[^A-Z0-9]", "") 151.154 + elseif field.name == "mobile_phone" then 151.155 + value = string.gsub(value, "[^0-9]", "") 151.156 + else 151.157 + value = string.gsub(value, "^%s+", "") 151.158 + value = string.gsub(value, "%s+$", "") 151.159 + value = string.gsub(value, "%s+", " ") 151.160 + end 151.161 + verification.request_data[field.name] = value 151.162 + end 151.163 +end 151.164 + 151.165 +local automatic_verification_possible = true 151.166 + 151.167 +local mobile_phone = verification.request_data.mobile_phone 151.168 + 151.169 +if not manual_verification then 151.170 + if config.self_registration.check_for_italien_mobile_phone then 151.171 + if not check_italian_mobile_phone_number(mobile_phone) then 151.172 + slot.select("error", function() 151.173 + ui.container{ content = _"Please check the mobile phone number (invalid format)" } 151.174 + end) 151.175 + errors = errors + 1 151.176 + end 151.177 + end 151.178 + 151.179 + if config.self_registration.check_for_uk_mobile_phone then 151.180 + if not check_uk_mobile_phone_number(mobile_phone) then 151.181 + slot.select("error", function() 151.182 + ui.container{ content = _"Please check the mobile phone number (invalid format)" } 151.183 + end) 151.184 + errors = errors + 1 151.185 + end 151.186 + end 151.187 +end 151.188 + 151.189 +if config.self_registration.check_for_italian_fiscal_code then 151.190 + local check_fiscal_code = execute.chunk{ module = "registration", chunk = "_check_fiscal_code" } 151.191 + 151.192 + local fiscal_code_valid, fiscal_code_error = check_fiscal_code( 151.193 + verification.request_data.fiscal_code, 151.194 + { 151.195 + first_name = verification.request_data.first_name, 151.196 + last_name = verification.request_data.name, 151.197 + year = tonumber(string.match(verification.request_data.date_of_birth, "^(....)-..-..$")), 151.198 + month = tonumber(string.match(verification.request_data.date_of_birth, "^....-(..)-..$")), 151.199 + day = tonumber(string.match(verification.request_data.date_of_birth, "^....-..-(..)$")), 151.200 + } 151.201 + ) 151.202 + 151.203 + if fiscal_code_valid then 151.204 + verification.comment = (verification.comment or "").. " /// Fiscal code matched" 151.205 + else 151.206 + slot.select("error", function() 151.207 + ui.container{ content = _"Please check the fiscal code (invalid format or does not match name, first name and/or date of birth)" } 151.208 + end) 151.209 + errors = errors + 1 151.210 + --table.insert(manual_check_reasons, "fiscal code does not match (" .. fiscal_code_error .. ")") 151.211 + end 151.212 +end 151.213 + 151.214 +if errors > 0 then 151.215 + return false 151.216 +end 151.217 + 151.218 +local member = Member:new() 151.219 +member.notify_email = email 151.220 +member:save() 151.221 + 151.222 +for i, checkbox in ipairs(config.use_terms_checkboxes) do 151.223 + local accepted = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) 151.224 + local member_useterms = MemberUseterms:new() 151.225 + member_useterms.member_id = member.id 151.226 + member_useterms.contract_identifier = checkbox.name 151.227 + member_useterms:save() 151.228 +end 151.229 + 151.230 +verification.requesting_member_id = member.id 151.231 + 151.232 +local manual_check_reasons = {} 151.233 + 151.234 +if manual_verification then 151.235 + table.insert(manual_check_reasons, "User requested manual verification (during step 1)") 151.236 +end 151.237 + 151.238 +local existing_verifications = Verification:new_selector() 151.239 + :add_where{ "request_data->>'mobile_phone' = ?", mobile_phone } 151.240 + :add_where("comment ilike '%SMS code%'") 151.241 + :exec() 151.242 + 151.243 +if #existing_verifications > 0 then 151.244 + table.insert(manual_check_reasons, "mobile phone number already used before") 151.245 +end 151.246 + 151.247 +if #manual_check_reasons > 0 then 151.248 + local reasons = table.concat(manual_check_reasons, ", ") 151.249 + verification.comment = (verification.comment or "").. " /// Manual verification needed: " .. reasons 151.250 + verification:save() 151.251 + request.redirect{ external = encode.url { module = "registration", view = "register_manual_check_needed" } } 151.252 + 151.253 +else 151.254 + local pin = multirand.string(6, "0123456789") 151.255 + verification.request_data.sms_code = pin 151.256 + verification.request_data.sms_code_tries = 3 151.257 + local sms_text = config.self_registration.sms_text 151.258 + local sms_text = string.gsub(sms_text, "{PIN}", pin) 151.259 + print("SMS Code: " .. sms_text) 151.260 + local phone_number 151.261 + if config.self_registration.sms_strip_leading_zero then 151.262 + phone_number = string.match(verification.request_data.mobile_phone, "0(.+)") 151.263 + else 151.264 + phone_number = verification.request_data.mobile_phone 151.265 + end 151.266 + phone_number = config.self_registration.sms_prefix .. phone_number 151.267 + local params = { 151.268 + id = config.self_registration.sms_id, 151.269 + pass = config.self_registration.sms_pass, 151.270 + gateway = config.self_registration.sms_gateway, 151.271 + absender = config.self_registration.sms_from, 151.272 + text = sms_text, 151.273 + nummer = phone_number, 151.274 + test = config.self_registration.test and "1" or nil 151.275 + } 151.276 + local params_list = {} 151.277 + for k, v in pairs(params) do 151.278 + table.insert(params_list, encode.url_part(k) .. "=" .. encode.url_part(v)) 151.279 + end 151.280 + 151.281 + local params_string = table.concat(params_list, "&") 151.282 + local url = "http://gateway.any-sms.biz/send_sms.php?" .. params_string 151.283 + print("curl " .. url) 151.284 + local output, err, status = extos.pfilter(nil, "curl", url) 151.285 + print(output) 151.286 + verification.request_data.sms_code_sent_status = output 151.287 + if not string.match(output, "^err:0") then 151.288 + verification.comment = (verification.comment or "").. " /// Manual verification needed: sending SMS failed (" .. output .. ")" 151.289 + verification:save() 151.290 + request.redirect{ external = encode.url { module = "registration", view = "register_manual_check_needed" } } 151.291 + return 151.292 + end 151.293 + verification.comment = (verification.comment or "") .. " /// SMS code " .. pin .. " sent" 151.294 + verification:save() 151.295 + request.redirect{ external = encode.url { module = "registration", view = "register_enter_pin", id = verification.id } } 151.296 +end 151.297 + 151.298 + 151.299 +
152.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 152.2 +++ b/app/main/registration/_action/register_pin.lua Sun Jul 15 14:07:29 2018 +0200 152.3 @@ -0,0 +1,71 @@ 152.4 +local id = param.get_id() 152.5 +local verification = Verification:by_id(id) 152.6 + 152.7 +if not verification then 152.8 + return false 152.9 +end 152.10 + 152.11 +local pin = param.get("pin") 152.12 + 152.13 +if param.get("manual_verification") then 152.14 + verification.comment = (verification.comment or "") .. " /// User requested manual verification (during step 2)" 152.15 + verification:save() 152.16 + request.redirect{ external = encode.url { module = "registration", view = "register_manual_check_needed" } } 152.17 + return false 152.18 +elseif verification.request_data.sms_code ~= pin then 152.19 + verification.request_data.sms_code_tries = verification.request_data.sms_code_tries - 1 152.20 + verification.comment = (verification.comment or "") .. " /// User entered wrong PIN " .. pin 152.21 + if verification.request_data.sms_code_tries > 0 then 152.22 + verification:save() 152.23 + request.redirect{ external = encode.url { module = "registration", view = "register_enter_pin", id = verification.id, params = { invalid_pin = true } } } 152.24 + return false 152.25 + else 152.26 + verification.comment = (verification.comment or "") .. " /// Manual verification needed: user entered invalid PIN three times" 152.27 + verification:save() 152.28 + request.redirect{ external = encode.url { module = "registration", view = "register_manual_check_needed" } } 152.29 + return false 152.30 + end 152.31 +end 152.32 + 152.33 +verification.comment = (verification.comment or "").. " /// User entered correct PIN code" 152.34 + 152.35 +verification.verified = "now" 152.36 +verification.verification_data = verification.request_data 152.37 + 152.38 +local identification = config.self_registration.identification_func(verification.request_data) 152.39 + 152.40 +local members_with_same_identification = Member:new_selector() 152.41 + :add_where{ "identification = ?", identification } 152.42 + :exec() 152.43 + 152.44 +if #members_with_same_identification > 0 then 152.45 + verification.comment = (verification.comment or "").. " /// Manual verification needed: user with same name already exists" 152.46 + verification:save() 152.47 + request.redirect{ external = encode.url { module = "registration", view = "register_manual_check_needed" } } 152.48 + return false 152.49 +end 152.50 + 152.51 +local member = Member:by_id(verification.requesting_member_id) 152.52 + 152.53 +member.identification = identification 152.54 +member.notify_email = verification.request_data.email 152.55 + 152.56 +member:send_invitation() 152.57 + 152.58 +for i, unit_id in ipairs(config.self_registration.grant_privileges_for_unit_ids) do 152.59 + local privilege = Privilege:new() 152.60 + privilege.member_id = member.id 152.61 + privilege.unit_id = unit_id 152.62 + privilege.initiative_right = true 152.63 + privilege.voting_right = true 152.64 + privilege:save() 152.65 +end 152.66 + 152.67 +verification.verified_member_id = member.id 152.68 + 152.69 +verification.comment = (verification.comment or "").. " /// Account created" 152.70 + 152.71 +verification:save() 152.72 + 152.73 + 152.74 +request.redirect{ external = encode.url { module = "registration", view = "register_completed" } }
153.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 153.2 +++ b/app/main/registration/_action/update_vote.lua Sun Jul 15 14:07:29 2018 +0200 153.3 @@ -0,0 +1,81 @@ 153.4 +if not app.session.member then 153.5 + return 153.6 +end 153.7 + 153.8 +local cancel = param.get("cancel") and true or false 153.9 +if cancel then return true end 153.10 + 153.11 +local issue = Issue:new_selector():add_where{ "id = ?", param.get("issue_id", atom.integer) }:for_share():single_object_mode():exec() 153.12 + 153.13 + 153.14 +if not app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then 153.15 + return execute.view { module = "index", view = "403" } 153.16 +end 153.17 + 153.18 +if issue.state ~= "voting" and not issue.closed then 153.19 + slot.put_into("error", _"Voting has not started yet.") 153.20 + return false 153.21 +end 153.22 + 153.23 +if issue.phase_finished or issue.closed and not update_comment then 153.24 + slot.put_into("error", _"This issue is already closed.") 153.25 + return false 153.26 +end 153.27 + 153.28 +local direct_voter = DirectVoter:by_pk(issue.id, app.session.member_id) 153.29 + 153.30 +if param.get("discard") then 153.31 + if direct_voter then 153.32 + direct_voter:destroy() 153.33 + end 153.34 + slot.put_into("notice", _"Your vote has been discarded. Delegation rules apply if set.") 153.35 + return 153.36 +end 153.37 + 153.38 +local initiatives = issue:get_reference_selector("initiatives") 153.39 + :add_where("initiative.admitted") 153.40 + :add_order_by("initiative.satisfied_supporter_count DESC") 153.41 + :exec() 153.42 + 153.43 +local vote_for_initiative_id = tonumber(param.get("vote_for_initiative_id")) 153.44 + 153.45 +local voted = 0 153.46 + 153.47 +for i, initiative in ipairs(initiatives) do 153.48 + if initiative.id == vote_for_initiative_id then 153.49 + voted = voted + 1 153.50 + end 153.51 +end 153.52 + 153.53 +if voted ~= 1 then 153.54 + slot.put_into("error", _"Please choose one project to vote for.") 153.55 + return false 153.56 +end 153.57 + 153.58 +if not direct_voter then 153.59 + direct_voter = DirectVoter:new() 153.60 + direct_voter.issue_id = issue.id 153.61 + direct_voter.member_id = app.session.member_id 153.62 + direct_voter:save() 153.63 +else 153.64 + local votes = Vote:new_selector() 153.65 + :add_where{ "vote.issue_id = ?", issue.id } 153.66 + :add_where{ "vote.member_id = ?", app.session.member_id } 153.67 + :exec() 153.68 + for i, vote in ipairs(votes) do 153.69 + vote:destroy() 153.70 + end 153.71 +end 153.72 + 153.73 +for i, initiative in ipairs(initiatives) do 153.74 + local vote = Vote:new() 153.75 + vote.issue_id = issue.id 153.76 + vote.initiative_id = initiative.id 153.77 + vote.member_id = app.session.member_id 153.78 + if initiative.id == vote_for_initiative_id then 153.79 + vote.grade = 1 153.80 + else 153.81 + vote.grade = 0 153.82 + end 153.83 + vote:save() 153.84 +end
154.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 154.2 +++ b/app/main/registration/_check_fiscal_code.lua Sun Jul 15 14:07:29 2018 +0200 154.3 @@ -0,0 +1,198 @@ 154.4 +local oddmap = { 154.5 + [0] = 1, 0, 5, 7, 9, 13, 15, 17, 19, 21, 154.6 + 2, 4, 18, 20, 11, 3, 6, 8, 12, 14, 16, 154.7 + 10, 22, 25, 24, 23 154.8 +} 154.9 + 154.10 +local monthtable = { 154.11 + "A", "B", "C", "D", "E", "H", "L", "M", "P", "R", "S", "T" 154.12 +} 154.13 + 154.14 +local function removeaccent(str) 154.15 + local gsub = string.gsub 154.16 + str = gsub(str, "\195\129", "A") 154.17 + str = gsub(str, "\195\128", "A") 154.18 + str = gsub(str, "\195\161", "a") 154.19 + str = gsub(str, "\195\160", "a") 154.20 + str = gsub(str, "\195\137", "E") 154.21 + str = gsub(str, "\195\136", "E") 154.22 + str = gsub(str, "\195\169", "e") 154.23 + str = gsub(str, "\195\168", "e") 154.24 + str = gsub(str, "\195\141", "I") 154.25 + str = gsub(str, "\195\140", "I") 154.26 + str = gsub(str, "\195\173", "i") 154.27 + str = gsub(str, "\195\172", "i") 154.28 + str = gsub(str, "\195\147", "O") 154.29 + str = gsub(str, "\195\146", "O") 154.30 + str = gsub(str, "\195\179", "o") 154.31 + str = gsub(str, "\195\178", "o") 154.32 + str = gsub(str, "\195\154", "U") 154.33 + str = gsub(str, "\195\153", "U") 154.34 + str = gsub(str, "\195\186", "u") 154.35 + str = gsub(str, "\195\185", "u") 154.36 + return str 154.37 +end 154.38 + 154.39 +local function normalize_name(str) 154.40 + local gsub = string.gsub 154.41 + str = removeaccent(str) 154.42 + str = gsub(str, " ", "") 154.43 + str = gsub(str, "-", "") 154.44 + str = gsub(str, "'", "") 154.45 + str = gsub(str, "\226\128\146", "") 154.46 + str = gsub(str, "\226\128\147", "") 154.47 + str = gsub(str, "\226\128\148", "") 154.48 + if string.find(str, "^[A-Za-z]+$") then 154.49 + return string.upper(str) 154.50 + else 154.51 + return nil 154.52 + end 154.53 +end 154.54 + 154.55 +local function remove_consonants(str) 154.56 + return (string.gsub(str, "[BCDFGHJKLMNPQRSTVWXYZ]", "")) 154.57 +end 154.58 + 154.59 +local function remove_vowels(str) 154.60 + return (string.gsub(str, "[AEIOU]", "")) 154.61 +end 154.62 + 154.63 +local function numberize(str) 154.64 + local gsub = string.gsub 154.65 + str = gsub(str, "L", "0") 154.66 + str = gsub(str, "M", "1") 154.67 + str = gsub(str, "N", "2") 154.68 + str = gsub(str, "P", "3") 154.69 + str = gsub(str, "Q", "4") 154.70 + str = gsub(str, "R", "5") 154.71 + str = gsub(str, "S", "6") 154.72 + str = gsub(str, "T", "7") 154.73 + str = gsub(str, "U", "8") 154.74 + str = gsub(str, "V", "9") 154.75 + return str 154.76 +end 154.77 + 154.78 +return function(code, data) 154.79 + local sub = string.sub 154.80 + local byte = string.byte 154.81 + local byte0 = byte("0") 154.82 + local byteA = byte("A") 154.83 + local function byteat(str, pos) 154.84 + return (byte(sub(str, pos, pos))) 154.85 + end 154.86 + if #code ~= 16 then 154.87 + return false, "Invalid length" 154.88 + end 154.89 + local sum = 0 154.90 + for i = 1, 15, 2 do 154.91 + local b = byteat(code, i) 154.92 + local b0 = b - byte0 154.93 + if b0 >= 0 and b0 <= 9 then 154.94 + sum = sum + oddmap[b0] 154.95 + else 154.96 + local bA = b - byteA 154.97 + if bA >= 0 and bA <= 25 then 154.98 + sum = sum + oddmap[bA] 154.99 + else 154.100 + return false, "Invalid character" 154.101 + end 154.102 + end 154.103 + end 154.104 + for i = 2, 14, 2 do 154.105 + local b = byteat(code, i) 154.106 + local b0 = b - byte0 154.107 + if b0 >= 0 and b0 <= 9 then 154.108 + sum = sum + b0 154.109 + else 154.110 + local bA = b - byteA 154.111 + if bA >= 0 and bA <= 25 then 154.112 + sum = sum + bA 154.113 + else 154.114 + return false, "Invalid character" 154.115 + end 154.116 + end 154.117 + end 154.118 + local check = byteat(code, 16) 154.119 + local checkA = check - byteA 154.120 + if checkA >= 0 and checkA <= 25 then 154.121 + if checkA ~= sum % 26 then 154.122 + return false, "Invalid checksum" 154.123 + end 154.124 + else 154.125 + local check0 = check - byte0 154.126 + if check0 >= 0 and check0 <= 9 then 154.127 + return false, "Checksum must not be numeric" 154.128 + else 154.129 + return false, "Invalid character" 154.130 + end 154.131 + end 154.132 + if data then 154.133 + if data.last_name then 154.134 + local name = normalize_name(data.last_name) 154.135 + if not name then 154.136 + return false, "Invalid last name" 154.137 + end 154.138 + local consonants = remove_vowels(name) 154.139 + local short = sub(consonants, 1, 3) 154.140 + if #short < 3 then 154.141 + local vowels = remove_consonants(name) 154.142 + short = short .. sub(vowels, 1, 3 - #short) 154.143 + while #short < 3 do 154.144 + short = short .. "X" 154.145 + end 154.146 + end 154.147 + if short ~= sub(code, 1, 3) then 154.148 + return false, "Last name not matching" 154.149 + end 154.150 + end 154.151 + if data.first_name then 154.152 + local name = normalize_name(data.first_name) 154.153 + if not name then 154.154 + return false, "Invalid first name" 154.155 + end 154.156 + local consonants = remove_vowels(name) 154.157 + local short 154.158 + if #consonants >= 4 then 154.159 + short = sub(consonants, 1, 1) .. sub(consonants, 3, 4) 154.160 + else 154.161 + short = consonants 154.162 + if #short < 3 then 154.163 + local vowels = remove_consonants(name) 154.164 + short = short .. sub(vowels, 1, 3 - #short) 154.165 + while #short < 3 do 154.166 + short = short .. "X" 154.167 + end 154.168 + end 154.169 + end 154.170 + if short ~= sub(code, 4, 6) then 154.171 + return false, "First name not matching" 154.172 + end 154.173 + end 154.174 + if data.year then 154.175 + local year = tostring(data.year % 100) 154.176 + if #year < 2 then 154.177 + year = "0" .. year 154.178 + end 154.179 + if year ~= numberize(sub(code, 7, 8)) then 154.180 + return false, "Year of birth not matching" 154.181 + end 154.182 + end 154.183 + if data.month then 154.184 + local monthchar = monthtable[data.month] 154.185 + if monthchar ~= sub(code, 9, 9) then 154.186 + return false, "Month of birth not matching" 154.187 + end 154.188 + end 154.189 + if data.day then 154.190 + local day = tostring(data.day) 154.191 + if #day < 2 then 154.192 + day = "0" .. day 154.193 + end 154.194 + local daycode = numberize(sub(code, 10, 11)) 154.195 + if day ~= daycode and tostring(day + 40) ~= daycode then 154.196 + return false, "Day of birth not matching" 154.197 + end 154.198 + end 154.199 + end 154.200 + return true 154.201 +end
155.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 155.2 +++ b/app/main/registration/_register_form.lua Sun Jul 15 14:07:29 2018 +0200 155.3 @@ -0,0 +1,102 @@ 155.4 +for i, field in ipairs(config.self_registration.fields) do 155.5 + local class = "" 155.6 + local field_error = slot.get_content("self_registration__invalid_" .. field.name) 155.7 + if field_error == "" then 155.8 + field_error = nil 155.9 + end 155.10 + if field_error then 155.11 + class = " is-invalid" 155.12 + end 155.13 + if field.name == "date_of_birth" then 155.14 + slot.put("<br />") 155.15 + ui.tag{ tag = "label", attr = { style = "vertical-align: bottom; border-bottom: 1px solid rgba(0,0,0, 0.12); color: #777; font-size: 16px;" }, content = field.label .. ":" } 155.16 + slot.put(" ") 155.17 + local days = { { id = 0, name = _"day" } } 155.18 + for i = 1, 31 do 155.19 + table.insert(days, { id = i, name = i }) 155.20 + end 155.21 + local months = { 155.22 + { id = 0, name = _"month" }, 155.23 + { id = 1, name = "gennaio" }, 155.24 + { id = 2, name = "febbraio" }, 155.25 + { id = 3, name = "marzo" }, 155.26 + { id = 4, name = "aprile" }, 155.27 + { id = 5, name = "maggio" }, 155.28 + { id = 6, name = "giugno" }, 155.29 + { id = 7, name = "luglio" }, 155.30 + { id = 8, name = "agosto" }, 155.31 + { id = 9, name = "settembre" }, 155.32 + { id = 10, name = "ottobre" }, 155.33 + { id = 11, name = "novembre" }, 155.34 + { id = 12, name = "dicembre" }, 155.35 + } 155.36 + if config.self_registration.lang == "en" then 155.37 + months = { 155.38 + { id = 0, name = _"month" }, 155.39 + { id = 1, name = "January" }, 155.40 + { id = 2, name = "February" }, 155.41 + { id = 3, name = "March" }, 155.42 + { id = 4, name = "April" }, 155.43 + { id = 5, name = "May" }, 155.44 + { id = 6, name = "June" }, 155.45 + { id = 7, name = "July" }, 155.46 + { id = 8, name = "August" }, 155.47 + { id = 9, name = "September" }, 155.48 + { id = 10, name = "October" }, 155.49 + { id = 11, name = "November" }, 155.50 + { id = 12, name = "December" }, 155.51 + } 155.52 + end 155.53 + local years = { { id = 0, name = _"year" } } 155.54 + for i = 2002, 1900, -1 do 155.55 + table.insert(years, { id = i, name = i }) 155.56 + end 155.57 + ui.field.select{ 155.58 + container_attr = { style = "display: inline-block; " }, 155.59 + attr = { class = class }, 155.60 + foreign_records = days, 155.61 + foreign_id = "id", 155.62 + foreign_name = "name", 155.63 + name = "verification_data_" .. field.name .. "_day", 155.64 + value = tonumber(request.get_param{ name = "verification_data_" .. field.name .. "_day" }) 155.65 + } 155.66 + slot.put(" ") 155.67 + ui.field.select{ 155.68 + container_attr = { style = "display: inline-block; " }, 155.69 + attr = { class = class }, 155.70 + foreign_records = months, 155.71 + foreign_id = "id", 155.72 + foreign_name = "name", 155.73 + name = "verification_data_" .. field.name .. "_month", 155.74 + value = tonumber(request.get_param{ name = "verification_data_" .. field.name .. "_month" }) 155.75 + } 155.76 + slot.put(" ") 155.77 + ui.field.select{ 155.78 + container_attr = { style = "display: inline-block; " }, 155.79 + attr = { class = class }, 155.80 + foreign_records = years, 155.81 + foreign_id = "id", 155.82 + foreign_name = "name", 155.83 + name = "verification_data_" .. field.name .. "_year", 155.84 + value = tonumber(request.get_param{ name = "verification_data_" .. field.name .. "_year" }) 155.85 + } 155.86 + slot.put("<br />") 155.87 + 155.88 + else 155.89 + if field.name == "mobile_phone" then 155.90 + if config.self_registration.lang ~= "en" then 155.91 + ui.tag{ content = "+39" } 155.92 + slot.put(" ") 155.93 + end 155.94 + end 155.95 + ui.field.text{ 155.96 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" .. class }, 155.97 + attr = { id = "lf-register__data_" .. field.name, class = "mdl-textfield__input" }, 155.98 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__data" .. field.name }, 155.99 + label = field.label, 155.100 + name = "verification_data_" .. field.name, 155.101 + value = request.get_param{ name = "verification_data_" .. field.name } 155.102 + } 155.103 + end 155.104 + slot.put("<br />") 155.105 +end
156.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 156.2 +++ b/app/main/registration/register.lua Sun Jul 15 14:07:29 2018 +0200 156.3 @@ -0,0 +1,95 @@ 156.4 +ui.title(_"Self registration") 156.5 +app.html_title.title = _"Self registration" 156.6 + 156.7 +slot.put("<style>select.is-invalid { border-color: #c00; }</style>") 156.8 + 156.9 +ui.container{ attr = { class = "mdl-grid" }, content = function() 156.10 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 156.11 + 156.12 + ui.form{ 156.13 + attr = { onsubmit = "document.getElementById('register_button').disabled = true;" }, 156.14 + module = "registration", action = "register", 156.15 + routing = { 156.16 + error = { mode = "forward", module = "registration", view = "register" } 156.17 + }, 156.18 + content = function() 156.19 + 156.20 + ui.container{ content = config.self_registration.info_top } 156.21 + 156.22 + execute.view{ module = "registration", view = "_register_form" } 156.23 + 156.24 + ui.container{ 156.25 + attr = { class = "use_terms" }, 156.26 + content = function() 156.27 + slot.put(config.use_terms) 156.28 + end 156.29 + } 156.30 + 156.31 + for i, checkbox in ipairs(config.use_terms_checkboxes) do 156.32 + ui.tag{ tag = "label", attr = { 156.33 + class = "mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect", 156.34 + ["for"] = "use_terms_checkbox_" .. checkbox.name 156.35 + }, 156.36 + content = function() 156.37 + ui.tag{ 156.38 + tag = "input", 156.39 + attr = { 156.40 + type = "checkbox", 156.41 + class = "mdl-checkbox__input", 156.42 + id = "use_terms_checkbox_" .. checkbox.name, 156.43 + name = "use_terms_checkbox_" .. checkbox.name, 156.44 + value = "1", 156.45 + style = "float: left;", 156.46 + checked = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) and "checked" or nil 156.47 + } 156.48 + } 156.49 + ui.tag{ 156.50 + attr = { class = "mdl-checkbox__label" }, 156.51 + content = function() slot.put(checkbox.html) end 156.52 + } 156.53 + end 156.54 + } 156.55 + slot.put("<br /><br />") 156.56 + end 156.57 + 156.58 + ui.container{ content = function() 156.59 + slot.put(config.self_registration.info_bottom) 156.60 + end } 156.61 + 156.62 + slot.put("<br />") 156.63 + 156.64 + ui.tag{ 156.65 + tag = "input", 156.66 + attr = { 156.67 + id = "register_button", 156.68 + type = "submit", 156.69 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 156.70 + value = _"Proceed with registration" 156.71 + } 156.72 + } 156.73 + slot.put(" ") 156.74 + ui.tag{ 156.75 + tag = "input", 156.76 + attr = { 156.77 + name = "manual_verification", 156.78 + id = "manual_verification_button", 156.79 + type = "submit", 156.80 + class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined", 156.81 + value = _"Manual verification (w/o mobile)" 156.82 + } 156.83 + } 156.84 + slot.put(" ") 156.85 + ui.link{ 156.86 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 156.87 + module = "index", view = "login", text = _"Cancel", params = { 156.88 + redirect_module = param.get("redirect_module"), 156.89 + redirect_view = param.get("redirect_view"), 156.90 + redirect_id = param.get("redirect_id"), 156.91 + redirect_params = param.get("redirect_params") 156.92 + } 156.93 + } 156.94 + end 156.95 + } 156.96 + 156.97 + end } 156.98 +end }
157.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 157.2 +++ b/app/main/registration/register_completed.lua Sun Jul 15 14:07:29 2018 +0200 157.3 @@ -0,0 +1,16 @@ 157.4 +ui.title(_"Self registration") 157.5 +app.html_title.title = _"Self registration" 157.6 + 157.7 +ui.container{ attr = { class = "mdl-grid" }, content = function() 157.8 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 157.9 + 157.10 + ui.heading{ content = _"Self registration completed" } 157.11 + slot.put("<br />") 157.12 + ui.container { content = _"We have sent you an invitation email to finish the account setup." } 157.13 + slot.put("<br />") 157.14 + ui.container { content = _"Please also check your SPAM folder." } 157.15 + slot.put("<br />") 157.16 + 157.17 + 157.18 + end } 157.19 +end }
158.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 158.2 +++ b/app/main/registration/register_enter_pin.lua Sun Jul 15 14:07:29 2018 +0200 158.3 @@ -0,0 +1,66 @@ 158.4 +local id = param.get_id() 158.5 +local verification = Verification:by_id(id) 158.6 +local invalid_pin = param.get("invalid_pin", atom.boolean) 158.7 + 158.8 +ui.title(_"Self registration") 158.9 +app.html_title.title = _"Self registration" 158.10 + 158.11 +ui.container{ attr = { class = "mdl-grid" }, content = function() 158.12 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 158.13 + 158.14 + ui.heading{ content = _"PIN page" } 158.15 + slot.put("<br />") 158.16 + ui.container { content = _"You should receive a PIN code via SMS shortly. Please enter the PIN." } 158.17 + 158.18 + if invalid_pin then 158.19 + slot.put("<br />") 158.20 + ui.container { attr = { class = "warning" }, content = _"Invalid PIN, please try again!" } 158.21 + slot.put("<br />") 158.22 + end 158.23 + 158.24 + ui.form{ 158.25 + module = "registration", action = "register_pin", id = verification.id, 158.26 + content = function() 158.27 + ui.field.text{ 158.28 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 158.29 + attr = { id = "pin", class = "mdl-textfield__input", autofocus = "autofocus" }, 158.30 + label_attr = { class = "mdl-textfield__label", ["for"] = "pin" }, 158.31 + label = "PIN code", 158.32 + name = "pin" 158.33 + } 158.34 + 158.35 + slot.put("<br />") 158.36 + 158.37 + ui.tag{ 158.38 + tag = "input", 158.39 + attr = { 158.40 + type = "submit", 158.41 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 158.42 + value = _"Proceed with registration" 158.43 + } 158.44 + } 158.45 + 158.46 + slot.put("<br /><br />") 158.47 + 158.48 + ui.heading{ content = _"No PIN code received?" } 158.49 + slot.put("<br />") 158.50 + ui.container { content = _"If you have not received a PIN code, our team will need to check your registration manually. We will be in touch within two working days. Please accept our apologies for the inconvenience." } 158.51 + 158.52 + slot.put("<br />") 158.53 + 158.54 + ui.tag{ 158.55 + tag = "input", 158.56 + attr = { 158.57 + name = "manual_verification", 158.58 + type = "submit", 158.59 + class = "mdl-button mdl-js-button mdl-button--raised", 158.60 + value = _"Start manual verification" 158.61 + } 158.62 + } 158.63 + 158.64 + end 158.65 + } 158.66 + 158.67 + 158.68 + end } 158.69 +end }
159.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 159.2 +++ b/app/main/registration/register_manual_check_needed.lua Sun Jul 15 14:07:29 2018 +0200 159.3 @@ -0,0 +1,22 @@ 159.4 +ui.title(_"Self registration") 159.5 +app.html_title.title = _"Self registration" 159.6 + 159.7 +ui.container{ attr = { class = "mdl-grid" }, content = function() 159.8 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 159.9 + 159.10 + ui.heading{ content = _"Manual verification needed" } 159.11 + slot.put("<br />") 159.12 + ui.container { content = function() 159.13 + ui.tag{ content = "We are sorry but the automatic verification of personal data has not been successful. We will need to verify your information manually. We apologise for the wait, and thank you for your cooperation. Until your information is verified, you can continue to " } 159.14 + ui.link{ content = _"browse the portal as an unregistered user", module = "index", view = "index" } 159.15 + ui.tag{ content = "." } 159.16 + slot.put("<br /><br />") 159.17 + ui.tag{ content = "For problems related to registration and use of the platform, please email " } 159.18 + ui.link{ external = "mailto:" .. config.self_registration.contact_email, content = config.self_registration.contact_email } 159.19 + ui.tag{ content = "." } 159.20 + end } 159.21 + slot.put("<br />") 159.22 + 159.23 + end } 159.24 +end } 159.25 +
160.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 160.2 +++ b/app/main/registration/register_rejected_age.lua Sun Jul 15 14:07:29 2018 +0200 160.3 @@ -0,0 +1,18 @@ 160.4 +ui.title(_"Self registration") 160.5 +app.html_title.title = _"Self registration" 160.6 + 160.7 +ui.container{ attr = { class = "mdl-grid" }, content = function() 160.8 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 160.9 + 160.10 + ui.heading{ content = _"Registration rejected" } 160.11 + slot.put("<br />") 160.12 + ui.container { content = function() 160.13 + ui.tag { content = _"Sorry, but you need to be at least 16 years old to participate. You can " } 160.14 + ui.link{ content = _"browse the platform as a guest", module = "index", view = "index" } 160.15 + ui.tag{ content = "." } 160.16 + end } 160.17 + slot.put("<br />") 160.18 + 160.19 + 160.20 + end } 160.21 +end }
161.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 161.2 +++ b/app/main/registration_admin/_action/update_role_verification.lua Sun Jul 15 14:07:29 2018 +0200 161.3 @@ -0,0 +1,74 @@ 161.4 +local verification = RoleVerification:by_id(param.get_id()) 161.5 + 161.6 +local function update_data() 161.7 + verification.verification_data = json.object() 161.8 + 161.9 + for i, field in ipairs(config.role_registration.fields) do 161.10 + local value = param.get(field.name) 161.11 + value = string.gsub(value, "^%s+", "") 161.12 + value = string.gsub(value, "%s+$", "") 161.13 + value = string.gsub(value, "%s+", " ") 161.14 + verification.verification_data[field.name] = value 161.15 + end 161.16 +end 161.17 + 161.18 +if verification.verified then 161.19 + 161.20 + local member = Member:by_id(verification.verified_member_id) 161.21 + 161.22 + if param.get("cancel") then 161.23 + db:query({ "SELECT delete_member(?)", member.id }) 161.24 + return 161.25 + end 161.26 + 161.27 + member.identification = param.get("identification") 161.28 + member.name = param.get("screen_name") 161.29 + member.notify_email = param.get("email") 161.30 + member:save() 161.31 + 161.32 + update_data() 161.33 + 161.34 + verification:save() 161.35 + 161.36 + if param.get("invite") then 161.37 + member:send_invitation() 161.38 + end 161.39 + 161.40 +elseif param.get("drop") then 161.41 + 161.42 + verification.denied = "now" 161.43 + verification:save() 161.44 + return 161.45 + 161.46 +elseif param.get("accredit") then 161.47 + 161.48 + local member = Member:new() 161.49 + member.role = true 161.50 + member.identification = param.get("identification") 161.51 + member.name = param.get("screen_name") 161.52 + member.notify_email = param.get("email") 161.53 + member:save() 161.54 + 161.55 + for i, unit_id in ipairs(config.role_registration.grant_privileges_for_unit_ids) do 161.56 + local privilege = Privilege:new() 161.57 + privilege.member_id = member.id 161.58 + privilege.unit_id = unit_id 161.59 + privilege.initiative_right = false -- TODO 161.60 + privilege.voting_right = true 161.61 + privilege:save() 161.62 + end 161.63 + 161.64 + local agent = Agent:new() 161.65 + agent.controlled_id = member.id 161.66 + agent.controller_id = verification.requesting_member_id 161.67 + agent:save() 161.68 + 161.69 + update_data() 161.70 + 161.71 + verification.verified_member_id = member.id 161.72 + verification.verifying_member_id = app.session.member_id 161.73 + verification.verified = "now" 161.74 + 161.75 + verification:save() 161.76 + 161.77 +end
162.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 162.2 +++ b/app/main/registration_admin/_action/update_verification.lua Sun Jul 15 14:07:29 2018 +0200 162.3 @@ -0,0 +1,79 @@ 162.4 +local verification = Verification:by_id(param.get_id()) 162.5 + 162.6 +local function update_data() 162.7 + verification.verification_data = json.object() 162.8 + 162.9 + for i, field in ipairs(config.self_registration.fields) do 162.10 + local value = param.get(field.name) 162.11 + if field.name == "fiscal_code" then 162.12 + value = string.gsub(value, "[^A-Z0-9]", "") 162.13 + elseif field.name == "mobile_phone" then 162.14 + value = string.gsub(value, "[^0-9]", "") 162.15 + else 162.16 + value = string.gsub(value, "^%s+", "") 162.17 + value = string.gsub(value, "%s+$", "") 162.18 + value = string.gsub(value, "%s+", " ") 162.19 + end 162.20 + verification.verification_data[field.name] = value 162.21 + end 162.22 +end 162.23 + 162.24 +if verification.verified_member_id then 162.25 + 162.26 + local member = Member:by_id(verification.verified_member_id) 162.27 + 162.28 + if param.get("cancel") then 162.29 + db:query({ "SELECT delete_member(?)", member.id }) 162.30 + return 162.31 + end 162.32 + 162.33 + member.identification = param.get("identification") 162.34 + member.notify_email = param.get("email") 162.35 + member:save() 162.36 + 162.37 + update_data() 162.38 + 162.39 + verification:save() 162.40 + 162.41 + if param.get("invite") then 162.42 + member:send_invitation() 162.43 + end 162.44 + 162.45 +elseif param.get("drop") then 162.46 + 162.47 + verification.denied = "now" 162.48 + verification:save() 162.49 + return 162.50 + 162.51 +elseif param.get("accredit") then 162.52 + 162.53 + local member = Member:by_id(verification.requesting_member_id) 162.54 + member.identification = param.get("identification") 162.55 + member.notify_email = param.get("email") 162.56 + member:save() 162.57 + member:send_invitation() 162.58 + 162.59 + for i, unit_id in ipairs(config.self_registration.grant_privileges_for_unit_ids) do 162.60 + local privilege = Privilege:new() 162.61 + privilege.member_id = member.id 162.62 + privilege.unit_id = unit_id 162.63 + privilege.initiative_right = true 162.64 + privilege.voting_right = true 162.65 + privilege:save() 162.66 + end 162.67 + 162.68 + update_data() 162.69 + 162.70 + verification.verified_member_id = verification.requesting_member_id 162.71 + verification.verifying_member_id = app.session.member_id 162.72 + verification.verified = "now" 162.73 + 162.74 + verification:save() 162.75 + 162.76 + 162.77 +else 162.78 + 162.79 + update_data() 162.80 + verification:save() 162.81 + 162.82 +end
163.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 163.2 +++ b/app/main/registration_admin/_filter/90_admin.lua Sun Jul 15 14:07:29 2018 +0200 163.3 @@ -0,0 +1,9 @@ 163.4 +if not app.session.member.admin then 163.5 + return execute.view { module = "index", view = "403" } 163.6 +end 163.7 + 163.8 +if config.admin_logger then 163.9 + config.admin_logger(request.get_param_strings()) 163.10 +end 163.11 + 163.12 +execute.inner()
164.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 164.2 +++ b/app/main/registration_admin/_role_verification_list.lua Sun Jul 15 14:07:29 2018 +0200 164.3 @@ -0,0 +1,38 @@ 164.4 +local verifications = param.get("verifications", "table") 164.5 + 164.6 +local columns = { 164.7 + { 164.8 + label = _"Requested at", 164.9 + content = function(record) 164.10 + ui.link{ module = "registration_admin", view = "role_verification", id = record.id, content = function() 164.11 + ui.container{ content = format.date(record.requested) } 164.12 + ui.container{ attr = { class = "light" }, content = format.time(record.requested) } 164.13 + end } 164.14 + end 164.15 + } 164.16 +} 164.17 + 164.18 +for i, field in ipairs(config.role_registration.fields) do 164.19 + table.insert(columns, { 164.20 + label = field.label, 164.21 + content = function(record) 164.22 + ui.tag{ content = record.request_data[field.name] } 164.23 + end 164.24 + }) 164.25 +end 164.26 + 164.27 +ui.list{ 164.28 + records = verifications, 164.29 + columns = columns 164.30 +} 164.31 + 164.32 +slot.put([[<style> 164.33 + 164.34 + td { 164.35 + vertical-align: top; 164.36 + } 164.37 + td div.light { 164.38 + color: #777; 164.39 + } 164.40 + 164.41 +</style>]])
165.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 165.2 +++ b/app/main/registration_admin/_verification_list.lua Sun Jul 15 14:07:29 2018 +0200 165.3 @@ -0,0 +1,76 @@ 165.4 +local verifications = param.get("verifications", "table") 165.5 + 165.6 +ui.list{ 165.7 + records = verifications, 165.8 + columns = { 165.9 + { 165.10 + label = _"Requested at", 165.11 + content = function(record) 165.12 + ui.link{ module = "registration_admin", view = "verification", id = record.id, content = function() 165.13 + ui.container{ content = format.date(record.requested) } 165.14 + ui.container{ attr = { class = "light" }, content = format.time(record.requested) } 165.15 + end } 165.16 + end 165.17 + }, 165.18 + { 165.19 + label = _"Name", 165.20 + content = function(record) 165.21 + ui.container{ content = function() 165.22 + ui.tag{ content = (record.verification_data or record.request_data).name } 165.23 + ui.tag{ content = ", " } 165.24 + ui.tag{ content = (record.verification_data or record.request_data).first_name } 165.25 + end } 165.26 + end 165.27 + }, 165.28 + --[[ 165.29 + { 165.30 + label = _"City", 165.31 + content = function(record) 165.32 + ui.container{ content = (record.verification_data or record.request_data).zip_code } 165.33 + ui.tag{ content = " " } 165.34 + ui.tag{ content = (record.verification_data or record.request_data).city } 165.35 + end 165.36 + }, 165.37 + --]] 165.38 + { 165.39 + label = _"Date/place of birth", 165.40 + content = function(record) 165.41 + ui.container{ content = (record.verification_data or record.request_data).date_of_birth } 165.42 + ui.container{ content = (record.verification_data or record.request_data).place_of_birth } 165.43 + end 165.44 + }, 165.45 + { 165.46 + label = _"Fiscal code", 165.47 + content = function(record) 165.48 + ui.tag{ content = (record.verification_data or record.request_data).fiscal_code } 165.49 + end 165.50 + }, 165.51 + { 165.52 + label = _"Contact", 165.53 + content = function(record) 165.54 + ui.container{ content = function() 165.55 + ui.tag{ content = (record.verification_data or record.request_data).email } 165.56 + end } 165.57 + ui.container{ content = function() 165.58 + ui.tag{ content = config.self_registration.sms_prefix } 165.59 + local phone_number = (record.verification_data or record.request_data).mobile_phone 165.60 + if config.self_registration.sms_strip_leading_zero then 165.61 + phone_number = string.match(phone_number, "0(.+)") 165.62 + end 165.63 + ui.tag{ content = phone_number } 165.64 + end } 165.65 + end 165.66 + } 165.67 + } 165.68 +} 165.69 + 165.70 +slot.put([[<style> 165.71 + 165.72 + td { 165.73 + vertical-align: top; 165.74 + } 165.75 + td div.light { 165.76 + color: #777; 165.77 + } 165.78 + 165.79 +</style>]])
166.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 166.2 +++ b/app/main/registration_admin/index.lua Sun Jul 15 14:07:29 2018 +0200 166.3 @@ -0,0 +1,212 @@ 166.4 +ui.title(_"Usermanagement") 166.5 +app.html_title.title = _"Usermanagement" 166.6 + 166.7 +ui.container{ attr = { class = "mdl-grid" }, content = function() 166.8 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 166.9 + 166.10 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 166.11 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 166.12 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 166.13 + ui.tag{ content = _"Usermanagement" } 166.14 + end } 166.15 + end } 166.16 + 166.17 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 166.18 + 166.19 + ui.container{ content = _"User accounts" } 166.20 + 166.21 + ui.tag{ tag = "ul", content = function() 166.22 + 166.23 + local count = Verification:new_selector() 166.24 + :add_where("verified_member_id ISNULL") 166.25 + :add_where("denied ISNULL") 166.26 + :count() 166.27 + ui.tag{ tag = "li", content = function() 166.28 + ui.link{ module = "registration_admin", view = "verification_requests", content = _("Open requests (#{count})", { count = count }) } 166.29 + end } 166.30 + 166.31 + ui.tag{ tag = "ul", content = function() 166.32 + 166.33 + local count = Verification:new_selector() 166.34 + :add_where("verified_member_id ISNULL") 166.35 + :add_where("denied ISNULL") 166.36 + :add_where("comment ilike '%User requested manual verification (during step 1)'") 166.37 + :count() 166.38 + ui.tag{ tag = "li", content = function() 166.39 + ui.link{ module = "registration_admin", view = "verification_requests", params = { mode = "manual_requested", step = 1 }, content = _("Manual verification requested during step 1 (#{count})", { count = count }) } 166.40 + end } 166.41 + 166.42 + local count = Verification:new_selector() 166.43 + :add_where("verified_member_id ISNULL") 166.44 + :add_where("denied ISNULL") 166.45 + :add_where("comment ilike '%User requested manual verification (during step 2)'") 166.46 + :count() 166.47 + ui.tag{ tag = "li", content = function() 166.48 + ui.link{ module = "registration_admin", view = "verification_requests", params = { mode = "manual_requested", step = 2 }, content = _("Manual verification requested during step 2 (#{count})", { count = count }) } 166.49 + end } 166.50 + 166.51 + local count = Verification:new_selector() 166.52 + :add_where("verified_member_id ISNULL") 166.53 + :add_where("denied ISNULL") 166.54 + :add_where("comment ilike '% sent'") 166.55 + :count() 166.56 + ui.tag{ tag = "li", content = function() 166.57 + ui.link{ module = "registration_admin", view = "verification_requests", params = { mode = "pin_sent" }, content = _("PIN code not entered (yet) (#{count})", { count = count }) } 166.58 + end } 166.59 + 166.60 + local count = Verification:new_selector() 166.61 + :add_where("verified_member_id ISNULL") 166.62 + :add_where("denied ISNULL") 166.63 + :add_where("comment similar to '%fiscal code does not match[^/]*'") 166.64 + :count() 166.65 + ui.tag{ tag = "li", content = function() 166.66 + ui.link{ module = "registration_admin", view = "verification_requests", params = { mode = "fiscal_code" }, content = _("Fiscal code does not match (#{count})", { count = count }) } 166.67 + end } 166.68 + 166.69 + local count = Verification:new_selector() 166.70 + :add_where("verified_member_id ISNULL") 166.71 + :add_where("denied ISNULL") 166.72 + :add_where("comment ilike '%mobile phone number already used before'") 166.73 + :count() 166.74 + ui.tag{ tag = "li", content = function() 166.75 + ui.link{ module = "registration_admin", view = "verification_requests", params = { mode = "mobile_phone" }, content = _("Phone number used before (#{count})", { count = count }) } 166.76 + end } 166.77 + 166.78 + local count = Verification:new_selector() 166.79 + :add_where("verified_member_id ISNULL") 166.80 + :add_where("denied ISNULL") 166.81 + :add_where("comment ilike '%user with same name already exist'") 166.82 + :count() 166.83 + ui.tag{ tag = "li", content = function() 166.84 + ui.link{ module = "registration_admin", view = "verification_requests", params = { mode = "identification" }, content = _("Identification used before (#{count})", { count = count }) } 166.85 + end } 166.86 + 166.87 + local count = Verification:new_selector() 166.88 + :add_where("verified_member_id ISNULL") 166.89 + :add_where("denied ISNULL") 166.90 + :add_where("comment ilike '%user entered invalid PIN three times'") 166.91 + :count() 166.92 + ui.tag{ tag = "li", content = function() 166.93 + ui.link{ module = "registration_admin", view = "verification_requests", params = { mode = "invalid_pin" }, content = _("Invalid PIN entered (#{count})", { count = count }) } 166.94 + end } 166.95 + 166.96 + local count = Verification:new_selector() 166.97 + :add_where("verified_member_id ISNULL") 166.98 + :add_where("denied ISNULL") 166.99 + :add_where("comment ilike '%user with same name already exists'") 166.100 + :count() 166.101 + ui.tag{ tag = "li", content = function() 166.102 + ui.link{ module = "registration_admin", view = "verification_requests", params = { mode = "same_name_already_exists" }, content = _("User with same name already exists (#{count})", { count = count }) } 166.103 + end } 166.104 + 166.105 + local count = Verification:new_selector() 166.106 + :add_where("verified_member_id ISNULL") 166.107 + :add_where("denied ISNULL") 166.108 + :add_where("not comment ilike '%User requested manual verification'") 166.109 + :add_where("not comment ilike '% sent'") 166.110 + :add_where("not comment similar to '%fiscal code does not match[^/]*'") 166.111 + :add_where("not comment ilike '%mobile phone number already used before'") 166.112 + :add_where("not comment ilike '%user with same name already exist'") 166.113 + :add_where("not comment ilike '%user entered invalid PIN three times'") 166.114 + :add_where("not comment ilike '%user with same name already exists'") 166.115 + :count() 166.116 + ui.tag{ tag = "li", content = function() 166.117 + ui.link{ module = "registration_admin", view = "verification_requests", params = { mode = "other" }, content = _("other reasons (#{count})", { count = count }) } 166.118 + end } 166.119 + end } 166.120 + 166.121 + local count = Verification:new_selector() 166.122 + :join("member", nil, "member.id = verification.verified_member_id") 166.123 + :count() 166.124 + ui.tag{ tag = "li", content = function() 166.125 + ui.link{ module = "registration_admin", view = "verification_accredited", content = _("Accredited (#{count})", { count = count }) } 166.126 + ui.tag{ tag = "ul", content = function() 166.127 + 166.128 + local count = Verification:new_selector() 166.129 + :join("member", nil, "member.id = verification.verified_member_id") 166.130 + :add_where("member.activated ISNULL") 166.131 + :add_where("member.deleted ISNULL") 166.132 + :count() 166.133 + ui.tag{ tag = "li", content = function() 166.134 + ui.link{ module = "registration_admin", view = "verification_accredited", params = { mode = "not_activated" }, content = _("Account not activated (yet) (#{count})", { count = count }) } 166.135 + end } 166.136 + 166.137 + local count = Verification:new_selector() 166.138 + :join("member", nil, "member.id = verification.verified_member_id") 166.139 + :add_where("member.activated NOTNULL") 166.140 + :add_where("member.deleted ISNULL") 166.141 + :count() 166.142 + ui.tag{ tag = "li", content = function() 166.143 + ui.link{ module = "registration_admin", view = "verification_accredited", params = { mode = "activated" }, content = _("Activated accounts (#{count})", { count = count }) } 166.144 + end } 166.145 + 166.146 + local count = Verification:new_selector() 166.147 + :join("member", nil, "member.id = verification.verified_member_id") 166.148 + :add_where("member.deleted NOTNULL") 166.149 + :count() 166.150 + ui.tag{ tag = "li", content = function() 166.151 + ui.link{ module = "registration_admin", view = "verification_cancelled", content = _("Cancelled accounts (#{count})", { count = count }) } 166.152 + end } 166.153 + end } 166.154 + end } 166.155 + 166.156 + local count = Verification:new_selector() 166.157 + :add_where("denied NOTNULL") 166.158 + :count() 166.159 + ui.tag{ tag = "li", content = function() 166.160 + ui.link{ module = "registration_admin", view = "verification_rejected", content = _("Rejected requests (#{count})", { count = count }) } 166.161 + end } 166.162 + 166.163 + end } 166.164 + 166.165 + ui.container{ content = _"Role accounts" } 166.166 + 166.167 + ui.tag{ tag = "ul", content = function() 166.168 + 166.169 + local count = RoleVerification:new_selector() 166.170 + :add_where("verified ISNULL") 166.171 + :add_where("denied ISNULL") 166.172 + :count() 166.173 + ui.tag{ tag = "li", content = function() 166.174 + ui.link{ module = "registration_admin", view = "role_verification_requests", content = _("Open requests (#{count})", { count = count }) } 166.175 + end } 166.176 + 166.177 + local count = RoleVerification:new_selector() 166.178 + :add_where("verified NOTNULL") 166.179 + :add_where("denied ISNULL") 166.180 + :join("member", nil, "member.id = role_verification.verified_member_id") 166.181 + :add_where("member.deleted ISNULL") 166.182 + :count() 166.183 + ui.tag{ tag = "li", content = function() 166.184 + ui.link{ module = "registration_admin", view = "role_verification_accredited", content = _("Accredited (#{count})", { count = count }) } 166.185 + end } 166.186 + 166.187 + local count = RoleVerification:new_selector() 166.188 + :add_where("verified NOTNULL") 166.189 + :add_where("denied ISNULL") 166.190 + :join("member", nil, "member.id = role_verification.verified_member_id") 166.191 + :add_where("member.deleted NOTNULL") 166.192 + :count() 166.193 + ui.tag{ tag = "li", content = function() 166.194 + ui.link{ module = "registration_admin", view = "role_verification_cancelled", content = _("Cancelled (#{count})", { count = count }) } 166.195 + end } 166.196 + 166.197 + local count = RoleVerification:new_selector() 166.198 + :add_where("verified ISNULL") 166.199 + :add_where("denied NOTNULL") 166.200 + :count() 166.201 + ui.tag{ tag = "li", content = function() 166.202 + ui.link{ module = "registration_admin", view = "role_verification_rejected", content = _("Rejected (#{count})", { count = count }) } 166.203 + end } 166.204 + 166.205 + 166.206 + end } 166.207 + 166.208 + end } 166.209 + end } 166.210 + 166.211 + 166.212 + 166.213 + end } 166.214 +end } 166.215 +
167.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 167.2 +++ b/app/main/registration_admin/role_verification.lua Sun Jul 15 14:07:29 2018 +0200 167.3 @@ -0,0 +1,168 @@ 167.4 +local verification = RoleVerification:by_id(param.get_id()) 167.5 +local data = verification.verification_data or verification.request_data 167.6 + 167.7 +local identification = config.role_registration.identification_func(data) 167.8 +local member 167.9 +if verification.verified_member_id then 167.10 + member = Member:by_id(verification.verified_member_id) 167.11 + identification = member.identification 167.12 +end 167.13 + 167.14 +local group, title, view 167.15 +if verification.verified then 167.16 + if member.deleted then 167.17 + group = _"Cancelled accounts" 167.18 + title = _"Cancelled account" 167.19 + view = "role_verification_cancelled" 167.20 + else 167.21 + group = _"Accredited users" 167.22 + title = member.identification 167.23 + view = "role_verification_accredited" 167.24 + end 167.25 +elseif verification.denied then 167.26 + group = "Rejected requests" 167.27 + title = _"Rejected request" 167.28 + view = "role_verification_rejected" 167.29 +else 167.30 + group = "Open requests" 167.31 + title = _"Open request" 167.32 + view = "role_verification_requests" 167.33 +end 167.34 + 167.35 + 167.36 +ui.title(function() 167.37 + ui.link { module = "registration_admin", view = "index", content = _"Rolemanagement" } 167.38 + slot.put ( " » " ) 167.39 + ui.link { module = "registration_admin", view = view, content = group } 167.40 + slot.put ( " » " ) 167.41 + ui.tag { tag = "span", content = title } 167.42 +end) 167.43 +app.html_title.title = _"Rolemanagement" 167.44 + 167.45 +ui.container{ attr = { class = "mdl-grid" }, content = function() 167.46 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 167.47 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 167.48 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 167.49 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = title } 167.50 + end } 167.51 + 167.52 + 167.53 + ui.form{ 167.54 + module = "registration_admin", action = "update_role_verification", id = verification.id, 167.55 + routing = { ok = { mode = "redirect", module = "registration_admin", view = view } }, 167.56 + record = data, 167.57 + content = function() 167.58 + 167.59 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 167.60 + 167.61 + for i, field in ipairs(config.role_registration.fields) do 167.62 + ui.container{ content = function() 167.63 + ui.field.text{ 167.64 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 167.65 + attr = { id = "lf-register__data_" .. field.name, class = "mdl-textfield__input" }, 167.66 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__data" .. field.name }, 167.67 + label = field.label, 167.68 + name = field.name 167.69 + } 167.70 + 167.71 + ui.tag{ content = verification.request_data[field.name] } 167.72 + end } 167.73 + end 167.74 + 167.75 + ui.field.text{ 167.76 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label", style = "width: 30em;" }, 167.77 + attr = { id = "lf-register__data_identification", class = "mdl-textfield__input" }, 167.78 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__data_identification" }, 167.79 + label = "Identification", 167.80 + name = "identification", 167.81 + value = identification 167.82 + } 167.83 + 167.84 + end } 167.85 + 167.86 + local member = Member:by_id(verification.requesting_member_id) 167.87 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 167.88 + ui.tag{ content = _"Requested by:" } 167.89 + slot.put(" ") 167.90 + ui.link{ content = member.name, module = "member", view = "show", id = member.id } 167.91 + end } 167.92 + 167.93 + ui.container{ attr = { class = "mdl-card__actions mdl-card--border" }, content = function() 167.94 + 167.95 + if verification.denied then 167.96 + ui.link{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, module = "registration_admin", view = view, content = "Back" } 167.97 + elseif verification.verified then 167.98 + ui.submit{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, value = "Save role data" } 167.99 + slot.put(" ") 167.100 + if not member.activated then 167.101 + ui.submit{ name = "invite", attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, value = "Send email invitation again" } 167.102 + slot.put(" ") 167.103 + end 167.104 + ui.link{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, module = "registration_admin", view = view, content = "Back" } 167.105 + slot.put(" ") 167.106 + ui.submit{ name = "cancel", attr = { class = "mdl-button mdl-js-button" }, value = _"Delete account" } 167.107 + else 167.108 + ui.submit{ name = "accredit", attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, value = "Accredit role" } 167.109 + slot.put(" ") 167.110 + ui.submit{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, value = "Save role data" } 167.111 + slot.put(" ") 167.112 + ui.link{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, module = "registration_admin", view = view, content = "Back" } 167.113 + slot.put(" ") 167.114 + ui.submit{ name = "drop", attr = { class = "mdl-button mdl-js-button" }, value = "Reject request" } 167.115 + end 167.116 + end } 167.117 + 167.118 + end 167.119 + } 167.120 + end } 167.121 + 167.122 + local verifications = RoleVerification:new_selector() 167.123 + :join("member", nil, "member.id = role_verification.verified_member_id") 167.124 + :add_where{ "member.identification = ?", identification } 167.125 + :add_where{ "role_verification.id <> ?", verification.id } 167.126 + :exec() 167.127 + 167.128 + if #verifications > 0 then 167.129 + 167.130 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 167.131 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 167.132 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 167.133 + ui.tag{ content = _"Same identification" } 167.134 + end } 167.135 + end } 167.136 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 167.137 + execute.view{ module = "registration_admin", view = "_role_verification_list", params = { verifications = verifications } } 167.138 + end } 167.139 + end } 167.140 + end 167.141 + 167.142 + for i, field_name in ipairs(config.role_registration.match_fields) do 167.143 + local field 167.144 + for j, f in ipairs(config.role_registration.fields) do 167.145 + if f.name == field_name then 167.146 + field = f 167.147 + end 167.148 + end 167.149 + local verifications = Verification:new_selector() 167.150 + :add_where("verified NOTNULL") 167.151 + :add_where{ "lower(request_data->>'" .. field.name .. "') = lower(?)", data[field.name] } 167.152 + :add_where{ "verification.id <> ?", verification.id } 167.153 + :exec() 167.154 + 167.155 + if #verifications > 0 then 167.156 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 167.157 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 167.158 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 167.159 + ui.tag{ content = _"Same " .. field.label } 167.160 + end } 167.161 + end } 167.162 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 167.163 + execute.view{ module = "registration_admin", view = "_verification_list", params = { verifications = verifications } } 167.164 + end } 167.165 + end } 167.166 + end 167.167 + end 167.168 + 167.169 + end } 167.170 +end } 167.171 +
168.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 168.2 +++ b/app/main/registration_admin/role_verification_accredited.lua Sun Jul 15 14:07:29 2018 +0200 168.3 @@ -0,0 +1,86 @@ 168.4 +local mode = param.get("mode") 168.5 + 168.6 +ui.title(function() 168.7 + ui.link { module = "registration_admin", view = "index", content = _"Rolemanagement" } 168.8 + slot.put ( " » " ) 168.9 + ui.tag { tag = "span", content = "Accredited role accounts"} 168.10 +end) 168.11 +app.html_title.title = _"Usermanagement" 168.12 + 168.13 +ui.container{ attr = { class = "mdl-grid" }, content = function() 168.14 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 168.15 + 168.16 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 168.17 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 168.18 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 168.19 + ui.tag{ content = _"Accredited role accounts" } 168.20 + end } 168.21 + end } 168.22 + 168.23 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 168.24 + 168.25 + local verifications_selector = RoleVerification:new_selector() 168.26 + :join("member", nil, "member.id = role_verification.verified_member_id") 168.27 + :add_where("member.deleted ISNULL") 168.28 + :add_order_by("member.identification") 168.29 + 168.30 + if mode == "activated" then 168.31 + verifications_selector:add_where("member.activated NOTNULL") 168.32 + elseif mode == "not_activated" then 168.33 + verifications_selector:add_where("member.activated ISNULL") 168.34 + end 168.35 + 168.36 + local verifications = verifications_selector:exec() 168.37 + 168.38 + if #verifications > 0 then 168.39 + ui.list{ 168.40 + records = verifications, 168.41 + columns = { 168.42 + { 168.43 + label = _"Identification", 168.44 + content = function(record) 168.45 + ui.container{ content = function() 168.46 + local member = Member:by_id(record.verified_member_id) 168.47 + if member then 168.48 + ui.link{ module = "registration_admin", view = "role_verification", id = record.id, content = member.identification } 168.49 + end 168.50 + end } 168.51 + end 168.52 + }, 168.53 + { 168.54 + label = _"Account", 168.55 + content = function(record) 168.56 + local member = Member:by_id(record.verified_member_id) 168.57 + if member and member.activated then 168.58 + ui.link{ module = "member", view = "show", id = record.verified_member_id, content = "ID " .. record.verified_member_id } 168.59 + else 168.60 + ui.tag{ content = "ID " } 168.61 + ui.tag{ content = record.verified_member_id } 168.62 + ui.tag{ content = ", " } 168.63 + ui.tag{ content = _"not activated (yet)" } 168.64 + 168.65 + end 168.66 + end 168.67 + }, 168.68 + { 168.69 + label = _"Verified at", 168.70 + content = function(record) 168.71 + ui.tag{ content = format.timestamp(record.verified) } 168.72 + end 168.73 + }, 168.74 + { 168.75 + label = _"Verified by", 168.76 + content = function(record) 168.77 + local member = Member:by_id(record.verifying_member_id) 168.78 + ui.link{ module = "member", view = "show", id = member.id, content = member.identification or (member.id .. " " .. member.name) } 168.79 + end 168.80 + }, 168.81 + } 168.82 + } 168.83 + end 168.84 + end } 168.85 + end } 168.86 + 168.87 + end } 168.88 +end } 168.89 +
169.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 169.2 +++ b/app/main/registration_admin/role_verification_cancelled.lua Sun Jul 15 14:07:29 2018 +0200 169.3 @@ -0,0 +1,35 @@ 169.4 +ui.title(function() 169.5 + ui.link { module = "registration_admin", view = "index", content = _"Rolemanagement" } 169.6 + slot.put ( " » " ) 169.7 + ui.tag { tag = "span", content = "Cancelled accounts"} 169.8 +end) 169.9 +app.html_title.title = _"Rolemanagement" 169.10 + 169.11 +ui.container{ attr = { class = "mdl-grid" }, content = function() 169.12 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 169.13 + 169.14 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 169.15 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 169.16 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 169.17 + ui.tag{ content = _"Cancelled accounts" } 169.18 + end } 169.19 + end } 169.20 + 169.21 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 169.22 + 169.23 + local verifications = RoleVerification:new_selector() 169.24 + :join("member", nil, "member.id = role_verification.verified_member_id") 169.25 + :add_where("member.deleted NOTNULL") 169.26 + :add_order_by("requested DESC") 169.27 + :exec() 169.28 + 169.29 + if #verifications > 0 then 169.30 + execute.view{ module = "registration_admin", view = "_role_verification_list", params = { verifications = verifications } } 169.31 + end 169.32 + 169.33 + end } 169.34 + end } 169.35 + 169.36 + end } 169.37 +end } 169.38 +
170.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 170.2 +++ b/app/main/registration_admin/role_verification_rejected.lua Sun Jul 15 14:07:29 2018 +0200 170.3 @@ -0,0 +1,35 @@ 170.4 +ui.title(function() 170.5 + ui.link { module = "registration_admin", view = "index", content = _"Rolemanagement" } 170.6 + slot.put ( " » " ) 170.7 + ui.tag { tag = "span", content = "Rejected requests"} 170.8 +end) 170.9 +app.html_title.title = _"Rolemanagement" 170.10 + 170.11 +ui.container{ attr = { class = "mdl-grid" }, content = function() 170.12 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 170.13 + 170.14 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 170.15 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 170.16 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 170.17 + ui.tag{ content = _"Rejected role accreditation requests" } 170.18 + end } 170.19 + end } 170.20 + 170.21 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 170.22 + 170.23 + local verifications = RoleVerification:new_selector() 170.24 + :add_where("denied NOTNULL") 170.25 + :add_order_by("requested DESC") 170.26 + :exec() 170.27 + 170.28 + if #verifications > 0 then 170.29 + execute.view{ module = "registration_admin", view = "_role_verification_list", params = { verifications = verifications } } 170.30 + end 170.31 + 170.32 + end } 170.33 + end } 170.34 + 170.35 + 170.36 + end } 170.37 +end } 170.38 +
171.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 171.2 +++ b/app/main/registration_admin/role_verification_requests.lua Sun Jul 15 14:07:29 2018 +0200 171.3 @@ -0,0 +1,42 @@ 171.4 +local mode = param.get("mode") 171.5 + 171.6 +local verifications_selector = RoleVerification:new_selector() 171.7 + :add_where("verified ISNULL") 171.8 + :add_where("denied ISNULL") 171.9 + :add_order_by("requested DESC") 171.10 + 171.11 +local title = _"Open role requests" 171.12 + 171.13 +local verifications = verifications_selector:exec() 171.14 + 171.15 + 171.16 +ui.title(function() 171.17 + ui.link { module = "registration_admin", view = "index", content = _"Rolemanagement" } 171.18 + slot.put ( " » " ) 171.19 + ui.tag { tag = "span", content = _"Open requests" } 171.20 +end) 171.21 + 171.22 +app.html_title.title = _"Rolemanagement" 171.23 + 171.24 +ui.container{ attr = { class = "mdl-grid" }, content = function() 171.25 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 171.26 + 171.27 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 171.28 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 171.29 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 171.30 + ui.tag{ content = title } 171.31 + end } 171.32 + end } 171.33 + 171.34 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 171.35 + 171.36 + if #verifications > 0 then 171.37 + execute.view{ module = "registration_admin", view = "_role_verification_list", params = { verifications = verifications } } 171.38 + end 171.39 + 171.40 + end } 171.41 + end } 171.42 + 171.43 + end } 171.44 +end } 171.45 +
172.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 172.2 +++ b/app/main/registration_admin/verification.lua Sun Jul 15 14:07:29 2018 +0200 172.3 @@ -0,0 +1,167 @@ 172.4 +local verification = Verification:by_id(param.get_id()) 172.5 +local data = verification.verification_data or verification.request_data 172.6 + 172.7 +local identification = config.self_registration.identification_func(data) 172.8 +local member 172.9 +if verification.verified_member_id then 172.10 + member = Member:by_id(verification.verified_member_id) 172.11 + identification = member.identification 172.12 +end 172.13 + 172.14 +local group, title, view 172.15 +if verification.verified_member_id then 172.16 + if member.deleted then 172.17 + group = _"Cancelled accounts" 172.18 + title = _"Cancelled account" 172.19 + view = "verification_cancelled" 172.20 + else 172.21 + group = _"Accredited users" 172.22 + title = member.identification 172.23 + view = "verification_accredited" 172.24 + end 172.25 +elseif verification.denied then 172.26 + group = "Rejected requests" 172.27 + title = _"Rejected request" 172.28 + view = "verification_rejected" 172.29 +else 172.30 + group = "Open requests" 172.31 + title = _"Open request" 172.32 + view = "verification_requests" 172.33 +end 172.34 + 172.35 + 172.36 +ui.title(function() 172.37 + ui.link { module = "registration_admin", view = "index", content = _"Usermanagement" } 172.38 + slot.put ( " » " ) 172.39 + ui.link { module = "registration_admin", view = view, content = group } 172.40 + slot.put ( " » " ) 172.41 + ui.tag { tag = "span", content = title } 172.42 +end) 172.43 +app.html_title.title = _"Usermanagement" 172.44 + 172.45 +ui.container{ attr = { class = "mdl-grid" }, content = function() 172.46 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 172.47 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 172.48 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 172.49 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = title } 172.50 + end } 172.51 + 172.52 + 172.53 + ui.form{ 172.54 + module = "registration_admin", action = "update_verification", id = verification.id, 172.55 + routing = { ok = { mode = "redirect", module = "registration_admin", view = view } }, 172.56 + record = data, 172.57 + content = function() 172.58 + 172.59 + if not verification.verified_member_id then 172.60 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 172.61 + ui.container{ content = verification.comment and verification.comment:gsub("///", " / ") or ""} 172.62 + end } 172.63 + end 172.64 + 172.65 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 172.66 + 172.67 + for i, field in ipairs(config.self_registration.fields) do 172.68 + ui.container{ content = function() 172.69 + ui.field.text{ 172.70 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" }, 172.71 + attr = { id = "lf-register__data_" .. field.name, class = "mdl-textfield__input" }, 172.72 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__data" .. field.name }, 172.73 + label = field.label, 172.74 + name = field.name 172.75 + } 172.76 + 172.77 + ui.tag{ content = verification.request_data[field.name] } 172.78 + end } 172.79 + end 172.80 + 172.81 + ui.field.text{ 172.82 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label", style = "width: 30em;" }, 172.83 + attr = { id = "lf-register__data_identification", class = "mdl-textfield__input" }, 172.84 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__data_identification" }, 172.85 + label = "Identification", 172.86 + name = "identification", 172.87 + value = identification 172.88 + } 172.89 + 172.90 + end } 172.91 + 172.92 + ui.container{ attr = { class = "mdl-card__actions mdl-card--border" }, content = function() 172.93 + 172.94 + if verification.denied then 172.95 + ui.link{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, module = "registration_admin", view = view, content = "Back" } 172.96 + elseif verification.verified_member_id then 172.97 + ui.submit{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, value = "Save personal data" } 172.98 + slot.put(" ") 172.99 + if not member.activated then 172.100 + ui.submit{ name = "invite", attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, value = "Send email invitation again" } 172.101 + slot.put(" ") 172.102 + end 172.103 + ui.link{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, module = "registration_admin", view = view, content = "Back" } 172.104 + slot.put(" ") 172.105 + ui.submit{ name = "cancel", attr = { class = "mdl-button mdl-js-button" }, value = _"Delete account" } 172.106 + else 172.107 + ui.submit{ name = "accredit", attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, value = "Accredit user" } 172.108 + slot.put(" ") 172.109 + ui.submit{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, value = "Save personal data" } 172.110 + slot.put(" ") 172.111 + ui.link{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, module = "registration_admin", view = view, content = "Back" } 172.112 + slot.put(" ") 172.113 + ui.submit{ name = "drop", attr = { class = "mdl-button mdl-js-button" }, value = "Reject request" } 172.114 + end 172.115 + end } 172.116 + 172.117 + end 172.118 + } 172.119 + end } 172.120 + 172.121 + local verifications = Verification:new_selector() 172.122 + :join("member", nil, "member.id = verification.verified_member_id") 172.123 + :add_where{ "member.identification = ?", identification } 172.124 + :add_where{ "verification.id <> ?", verification.id } 172.125 + :exec() 172.126 + 172.127 + if #verifications > 0 then 172.128 + 172.129 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 172.130 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 172.131 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 172.132 + ui.tag{ content = _"Same identification" } 172.133 + end } 172.134 + end } 172.135 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 172.136 + execute.view{ module = "registration_admin", view = "_verification_list", params = { verifications = verifications } } 172.137 + end } 172.138 + end } 172.139 + end 172.140 + 172.141 + for i, field_name in ipairs(config.self_registration.match_fields) do 172.142 + local field 172.143 + for j, f in ipairs(config.self_registration.fields) do 172.144 + if f.name == field_name then 172.145 + field = f 172.146 + end 172.147 + end 172.148 + local verifications = Verification:new_selector() 172.149 + :add_where("verified_member_id NOTNULL") 172.150 + :add_where{ "lower(request_data->>'" .. field.name .. "') = lower(?)", data[field.name] } 172.151 + :add_where{ "verification.id <> ?", verification.id } 172.152 + :exec() 172.153 + 172.154 + if #verifications > 0 then 172.155 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 172.156 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 172.157 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 172.158 + ui.tag{ content = _"Same " .. field.label } 172.159 + end } 172.160 + end } 172.161 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 172.162 + execute.view{ module = "registration_admin", view = "_verification_list", params = { verifications = verifications } } 172.163 + end } 172.164 + end } 172.165 + end 172.166 + end 172.167 + 172.168 + end } 172.169 +end } 172.170 +
173.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 173.2 +++ b/app/main/registration_admin/verification_accredited.lua Sun Jul 15 14:07:29 2018 +0200 173.3 @@ -0,0 +1,110 @@ 173.4 +local mode = param.get("mode") 173.5 + 173.6 +ui.title(function() 173.7 + ui.link { module = "registration_admin", view = "index", content = _"Usermanagement" } 173.8 + slot.put ( " » " ) 173.9 + ui.tag { tag = "span", content = "Accredited users"} 173.10 +end) 173.11 +app.html_title.title = _"Usermanagement" 173.12 + 173.13 +ui.container{ attr = { class = "mdl-grid" }, content = function() 173.14 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 173.15 + 173.16 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 173.17 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 173.18 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 173.19 + ui.tag{ content = _"Accredited users" } 173.20 + end } 173.21 + end } 173.22 + 173.23 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 173.24 + 173.25 + local verifications_selector = Verification:new_selector() 173.26 + :join("member", nil, "member.id = verification.verified_member_id") 173.27 + :add_where("member.deleted ISNULL") 173.28 + 173.29 + if mode == "activated" then 173.30 + verifications_selector:add_where("member.activated NOTNULL") 173.31 + end 173.32 + 173.33 + if mode ~= "not_activated" then 173.34 + verifications_selector:add_order_by("member.identification") 173.35 + local verifications = verifications_selector:exec() 173.36 + 173.37 + if #verifications > 0 then 173.38 + ui.list{ 173.39 + records = verifications, 173.40 + columns = { 173.41 + { 173.42 + label = _"Name", 173.43 + content = function(record) 173.44 + ui.container{ content = function() 173.45 + local member = Member:by_id(record.verified_member_id) 173.46 + if member then 173.47 + ui.link{ module = "registration_admin", view = "verification", id = record.id, content = member.identification } 173.48 + end 173.49 + end } 173.50 + end 173.51 + }, 173.52 + --[[ 173.53 + { 173.54 + label = _"City", 173.55 + content = function(record) 173.56 + ui.container{ content = (record.verification_data or record.request_data).zip_code } 173.57 + ui.tag{ content = " " } 173.58 + ui.tag{ content = (record.verification_data or record.request_data).city } 173.59 + end 173.60 + }, 173.61 + --]] 173.62 + { 173.63 + label = _"Account", 173.64 + content = function(record) 173.65 + local member = Member:by_id(record.verified_member_id) 173.66 + if member and member.activated then 173.67 + ui.link{ module = "member", view = "show", id = record.verified_member_id, content = "ID " .. record.verified_member_id } 173.68 + else 173.69 + ui.tag{ content = "ID " } 173.70 + ui.tag{ content = record.verified_member_id } 173.71 + ui.tag{ content = ", " } 173.72 + ui.tag{ content = _"not activated (yet)" } 173.73 + 173.74 + end 173.75 + end 173.76 + }, 173.77 + { 173.78 + label = _"Verified at", 173.79 + content = function(record) 173.80 + ui.tag{ content = format.timestamp(record.verified) } 173.81 + end 173.82 + }, 173.83 + { 173.84 + label = _"Verified by", 173.85 + content = function(record) 173.86 + if record.verifying_member_id then 173.87 + local member = Member:by_id(record.verifying_member_id) 173.88 + ui.link{ module = "member", view = "show", id = member.id, content = member.identification or (member.id .. " " .. member.name) } 173.89 + local state = string.match(record.comment, "[^/]+$") 173.90 + slot.put(" ") 173.91 + ui.tag{ content = state } 173.92 + elseif record.verified then 173.93 + ui.tag{ content = _"SMS" } 173.94 + end 173.95 + 173.96 + end 173.97 + }, 173.98 + } 173.99 + } 173.100 + end 173.101 + elseif mode == "not_activated" then 173.102 + verifications_selector:add_where("member.activated ISNULL") 173.103 + :add_order_by("verification.id DESC") 173.104 + local verifications = verifications_selector:exec() 173.105 + execute.view{ module = "registration_admin", view = "_verification_list", params = { verifications = verifications } } 173.106 + end 173.107 + 173.108 + end } 173.109 + end } 173.110 + 173.111 + end } 173.112 +end } 173.113 +
174.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 174.2 +++ b/app/main/registration_admin/verification_cancelled.lua Sun Jul 15 14:07:29 2018 +0200 174.3 @@ -0,0 +1,35 @@ 174.4 +ui.title(function() 174.5 + ui.link { module = "registration_admin", view = "index", content = _"Usermanagement" } 174.6 + slot.put ( " » " ) 174.7 + ui.tag { tag = "span", content = "Cancelled accounts"} 174.8 +end) 174.9 +app.html_title.title = _"Usermanagement" 174.10 + 174.11 +ui.container{ attr = { class = "mdl-grid" }, content = function() 174.12 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 174.13 + 174.14 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 174.15 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 174.16 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 174.17 + ui.tag{ content = _"Cancelled accounts" } 174.18 + end } 174.19 + end } 174.20 + 174.21 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 174.22 + 174.23 + local verifications = Verification:new_selector() 174.24 + :join("member", nil, "member.id = verification.verified_member_id") 174.25 + :add_where("member.deleted NOTNULL") 174.26 + :add_order_by("requested DESC") 174.27 + :exec() 174.28 + 174.29 + if #verifications > 0 then 174.30 + execute.view{ module = "registration_admin", view = "_verification_list", params = { verifications = verifications } } 174.31 + end 174.32 + 174.33 + end } 174.34 + end } 174.35 + 174.36 + end } 174.37 +end } 174.38 +
175.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 175.2 +++ b/app/main/registration_admin/verification_rejected.lua Sun Jul 15 14:07:29 2018 +0200 175.3 @@ -0,0 +1,35 @@ 175.4 +ui.title(function() 175.5 + ui.link { module = "registration_admin", view = "index", content = _"Usermanagement" } 175.6 + slot.put ( " » " ) 175.7 + ui.tag { tag = "span", content = "Rejected requests"} 175.8 +end) 175.9 +app.html_title.title = _"Usermanagement" 175.10 + 175.11 +ui.container{ attr = { class = "mdl-grid" }, content = function() 175.12 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 175.13 + 175.14 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 175.15 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 175.16 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 175.17 + ui.tag{ content = _"Rejected accreditation requests" } 175.18 + end } 175.19 + end } 175.20 + 175.21 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 175.22 + 175.23 + local verifications = Verification:new_selector() 175.24 + :add_where("denied NOTNULL") 175.25 + :add_order_by("request_data->>'name', request_data->>'first_name', requested DESC") 175.26 + :exec() 175.27 + 175.28 + if #verifications > 0 then 175.29 + execute.view{ module = "registration_admin", view = "_verification_list", params = { verifications = verifications } } 175.30 + end 175.31 + 175.32 + end } 175.33 + end } 175.34 + 175.35 + 175.36 + end } 175.37 +end } 175.38 +
176.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 176.2 +++ b/app/main/registration_admin/verification_requests.lua Sun Jul 15 14:07:29 2018 +0200 176.3 @@ -0,0 +1,78 @@ 176.4 +local mode = param.get("mode") 176.5 +local step = param.get("step", atom.integer) 176.6 + 176.7 +local verifications_selector = Verification:new_selector() 176.8 + :add_where("verified_member_id ISNULL") 176.9 + :add_where("denied ISNULL") 176.10 + :add_order_by("requested DESC") 176.11 + 176.12 +local title = _"Open requests" 176.13 + 176.14 +if mode == "manual_requested" and step == 1 then 176.15 + title = _"Manual verification requested during step 1" 176.16 + verifications_selector:add_where("comment ilike '%User requested manual verification (during step 1)'") 176.17 +elseif mode == "manual_requested" and step == 2 then 176.18 + title = _"Manual verification requested during step 2" 176.19 + verifications_selector:add_where("comment ilike '%User requested manual verification (during step 2)'") 176.20 +elseif mode == "pin_sent" then 176.21 + title = _"PIN code not entered" 176.22 + verifications_selector:add_where("comment ilike '% sent'") 176.23 +elseif mode == "fiscal_code" then 176.24 + title = _"Fiscal code does not match" 176.25 + verifications_selector:add_where("comment similar to '%fiscal code does not match[^/]*'") 176.26 +elseif mode == "mobile_phone" then 176.27 + title = _"Phone number used before" 176.28 + verifications_selector:add_where("comment ilike '%mobile phone number already used before'") 176.29 +elseif mode == "identification" then 176.30 + title = _"Identification used before" 176.31 + verifications_selector:add_where("comment ilike '%user with same name already exist'") 176.32 +elseif mode == "invalid_pin" then 176.33 + title = _"Invalid PIN entered" 176.34 + verifications_selector:add_where("comment ilike '%user entered invalid PIN three times'") 176.35 +elseif mode == "same_name_already_exists" then 176.36 + title = _"Same name already exists" 176.37 + verifications_selector:add_where("comment ilike '%user with same name already exists'") 176.38 +elseif mode == "other" then 176.39 + title = _"Other reasons" 176.40 + verifications_selector:add_where("not comment ilike '%User requested manual verification'") 176.41 + verifications_selector:add_where("not comment ilike '% sent'") 176.42 + verifications_selector:add_where("not comment similar to '%fiscal code does not match[^/]*'") 176.43 + verifications_selector:add_where("not comment ilike '%mobile phone number already used before'") 176.44 + verifications_selector:add_where("not comment ilike '%user with same name already exist'") 176.45 + verifications_selector:add_where("not comment ilike '%user entered invalid PIN three times'") 176.46 + verifications_selector:add_where("not comment ilike '%user with same name already exists'") 176.47 +end 176.48 + 176.49 +local verifications = verifications_selector:exec() 176.50 + 176.51 + 176.52 +ui.title(function() 176.53 + ui.link { module = "registration_admin", view = "index", content = _"Usermanagement" } 176.54 + slot.put ( " » " ) 176.55 + ui.tag { tag = "span", content = _"Open requests" } 176.56 +end) 176.57 + 176.58 +app.html_title.title = _"Usermanagement" 176.59 + 176.60 +ui.container{ attr = { class = "mdl-grid" }, content = function() 176.61 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 176.62 + 176.63 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 176.64 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 176.65 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 176.66 + ui.tag{ content = title } 176.67 + end } 176.68 + end } 176.69 + 176.70 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 176.71 + 176.72 + if #verifications > 0 then 176.73 + execute.view{ module = "registration_admin", view = "_verification_list", params = { verifications = verifications } } 176.74 + end 176.75 + 176.76 + end } 176.77 + end } 176.78 + 176.79 + end } 176.80 +end } 176.81 +
177.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 177.2 +++ b/app/main/role/_action/request.lua Sun Jul 15 14:07:29 2018 +0200 177.3 @@ -0,0 +1,57 @@ 177.4 +if not app.session.member or app.session.member.role then 177.5 + return 177.6 +end 177.7 + 177.8 +local errors = 0 177.9 + 177.10 +if config.use_terms_checkboxes_role then 177.11 + for i, checkbox in ipairs(config.use_terms_checkboxes_role) do 177.12 + local accepted = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) 177.13 + if not accepted then 177.14 + slot.put_into("error", checkbox.not_accepted_error) 177.15 + errors = errors + 1 177.16 + end 177.17 + end 177.18 +end 177.19 + 177.20 +local email = param.get("email") 177.21 + 177.22 +local members = Member:new_selector() 177.23 + :add_where{ "notify_email = ? OR notify_email_unconfirmed = ?", email } 177.24 + :exec() 177.25 + 177.26 +if #members > 0 then 177.27 + slot.select("error", function() 177.28 + slot.put_into("registration_register_email_invalid", "already_used") 177.29 + ui.tag{ content = _"This email address already been used. Please check your inbox for an invitation or contact us." } 177.30 + end) 177.31 + errors = errors + 1 177.32 +end 177.33 + 177.34 +local verification = RoleVerification:new() 177.35 +verification.requesting_member_id = app.session.member_id 177.36 +verification.requested = "now" 177.37 +verification.request_origin = json.object{ 177.38 + ip = request.get_header("X-Forwarded-For"), 177.39 + hostname = request.get_header("X-Forwarded-Host") 177.40 +} 177.41 +verification.request_data = json.object() 177.42 + 177.43 +for i, field in ipairs(config.role_registration.fields) do 177.44 + local value = param.get("verification_data_" .. field.name) 177.45 + if not value or #value < 1 then 177.46 + slot.put_into("self_registration__invalid_" .. field.name, "to_short") 177.47 + slot.select("error", function() 177.48 + ui.container{ content = _("Please enter: #{field_name}", { field_name = field.label }) } 177.49 + end) 177.50 + errors = errors + 1 177.51 + end 177.52 + value = string.gsub(value, "^%s+", "") 177.53 + value = string.gsub(value, "%s+$", "") 177.54 + value = string.gsub(value, "%s+", " ") 177.55 + verification.request_data[field.name] = value 177.56 +end 177.57 + 177.58 +verification:save() 177.59 + 177.60 +request.redirect{ external = encode.url { module = "member", view = "show", id = app.session.member_id } }
178.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 178.2 +++ b/app/main/role/_action/switch.lua Sun Jul 15 14:07:29 2018 +0200 178.3 @@ -0,0 +1,55 @@ 178.4 +local id = param.get_id() 178.5 + 178.6 +local member_id = app.session.real_member_id or app.session.member_id 178.7 + 178.8 +if id then 178.9 + local member = Member:by_id(id) 178.10 + 178.11 + if member.locked then 178.12 + return 178.13 + end 178.14 + 178.15 + local agent = Agent:by_pk(member.id, member_id) 178.16 + 178.17 + if not agent then 178.18 + return 178.19 + end 178.20 + 178.21 + local session = Session:new() 178.22 + session.member_id = member.id 178.23 + session.real_member_id = member_id 178.24 + session:save() 178.25 + 178.26 + if not member.activated then 178.27 + member.activated = "now" 178.28 + end 178.29 + 178.30 + member.last_login = "now" 178.31 + member.last_activity = "now" 178.32 + member.active = true 178.33 + member:save() 178.34 + 178.35 + app.session:destroy() 178.36 + 178.37 + request.set_cookie{ 178.38 + name = config.cookie_name or "liquid_feedback_session", 178.39 + value = session.ident 178.40 + } 178.41 +elseif app.session.real_member_id then 178.42 + local session = Session:new() 178.43 + session.member_id = app.session.real_member_id 178.44 + session:save() 178.45 + 178.46 + app.session:destroy() 178.47 + 178.48 + request.set_cookie{ 178.49 + name = config.cookie_name or "liquid_feedback_session", 178.50 + value = session.ident 178.51 + } 178.52 +end 178.53 + 178.54 +if config.meta_navigation_home_url then 178.55 + request.redirect{ external = config.meta_navigation_home_url } 178.56 +else 178.57 + request.redirect{ module = "index", view = "index" } 178.58 +end
179.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 179.2 +++ b/app/main/role/_request_form.lua Sun Jul 15 14:07:29 2018 +0200 179.3 @@ -0,0 +1,19 @@ 179.4 +for i, field in ipairs(config.role_registration.fields) do 179.5 + local class = "" 179.6 + local field_error = slot.get_content("role_registration__invalid_" .. field.name) 179.7 + if field_error == "" then 179.8 + field_error = nil 179.9 + end 179.10 + if field_error then 179.11 + class = " is-invalid" 179.12 + end 179.13 + ui.field.text{ 179.14 + container_attr = { class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label" .. class }, 179.15 + attr = { id = "lf-register__data_" .. field.name, class = "mdl-textfield__input" }, 179.16 + label_attr = { class = "mdl-textfield__label", ["for"] = "lf-register__data" .. field.name }, 179.17 + label = field.label, 179.18 + name = "verification_data_" .. field.name, 179.19 + value = request.get_param{ name = "verification_data_" .. field.name } 179.20 + } 179.21 + slot.put("<br />") 179.22 +end
180.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 180.2 +++ b/app/main/role/request.lua Sun Jul 15 14:07:29 2018 +0200 180.3 @@ -0,0 +1,96 @@ 180.4 +ui.titleMember(_"Request role account") 180.5 + 180.6 +ui.grid{ content = function() 180.7 + 180.8 + ui.cell_main{ content = function() 180.9 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 180.10 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 180.11 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"Request role accounts" } 180.12 + end } 180.13 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 180.14 + 180.15 + 180.16 + ui.form{ 180.17 + attr = { onsubmit = "document.getElementById('register_button').disabled = true;" }, 180.18 + module = "role", action = "request", 180.19 + routing = { 180.20 + error = { mode = "forward", module = "role", view = "request" } 180.21 + }, 180.22 + content = function() 180.23 + 180.24 + ui.container{ content = config.role_registration.info_top } 180.25 + 180.26 + execute.view{ module = "role", view = "_request_form" } 180.27 + 180.28 + ui.container{ 180.29 + attr = { class = "use_terms" }, 180.30 + content = function() 180.31 + slot.put(config.use_terms_role) 180.32 + end 180.33 + } 180.34 + 180.35 + if config.use_terms_checkboxes_role then 180.36 + for i, checkbox in ipairs(config.use_terms_checkboxes_role) do 180.37 + ui.tag{ tag = "label", attr = { 180.38 + class = "mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect", 180.39 + ["for"] = "use_terms_checkbox_" .. checkbox.name 180.40 + }, 180.41 + content = function() 180.42 + ui.tag{ 180.43 + tag = "input", 180.44 + attr = { 180.45 + type = "checkbox", 180.46 + class = "mdl-checkbox__input", 180.47 + id = "use_terms_checkbox_" .. checkbox.name, 180.48 + name = "use_terms_checkbox_" .. checkbox.name, 180.49 + value = "1", 180.50 + style = "float: left;", 180.51 + checked = param.get("use_terms_checkbox_" .. checkbox.name, atom.boolean) and "checked" or nil 180.52 + } 180.53 + } 180.54 + ui.tag{ 180.55 + attr = { class = "mdl-checkbox__label" }, 180.56 + content = function() slot.put(checkbox.html) end 180.57 + } 180.58 + end 180.59 + } 180.60 + slot.put("<br /><br />") 180.61 + end 180.62 + end 180.63 + 180.64 + ui.container{ content = function() 180.65 + slot.put(config.role_registration.info_bottom) 180.66 + end } 180.67 + 180.68 + slot.put("<br />") 180.69 + 180.70 + ui.tag{ 180.71 + tag = "input", 180.72 + attr = { 180.73 + id = "register_button", 180.74 + type = "submit", 180.75 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 180.76 + value = _"Proceed with registration" 180.77 + } 180.78 + } 180.79 + slot.put(" ") 180.80 + ui.link{ 180.81 + attr = { class = "mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--underlined" }, 180.82 + module = "member", view = "show", id = app.session.member_id, text = _"Cancel", 180.83 + } 180.84 + 180.85 + end } 180.86 + end } 180.87 + end } 180.88 + end } 180.89 + 180.90 + ui.cell_sidebar{ content = function() 180.91 + execute.view { 180.92 + module = "member", view = "_sidebar_whatcanido", params = { 180.93 + member = app.session.member 180.94 + } 180.95 + } 180.96 + end } 180.97 + 180.98 +end } 180.99 +
181.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 181.2 +++ b/app/main/style/_style.lua Sun Jul 15 14:07:29 2018 +0200 181.3 @@ -0,0 +1,91 @@ 181.4 +local style = param.get("style", "table") 181.5 + 181.6 +local md_colors = { 181.7 + ["500"] = { 181.8 + red = "244,67,54", 181.9 + pink = "233,30,99", 181.10 + purple = "156,39,176", 181.11 + ["deep-purple"] = "103,58,183", 181.12 + indigo = "63,81,181", 181.13 + blue = "33,150,243", 181.14 + ["light-blue"] = "3,169,244", 181.15 + cyan = "0,188,212", 181.16 + teal = "0,150,136", 181.17 + green = "76,175,80", 181.18 + ["light-green"] = "139,195,74", 181.19 + lime = "205,220,57", 181.20 + yellow = "255,235,59", 181.21 + amber = "255,193,7", 181.22 + orange = "255,152,0", 181.23 + ["deep-orange"] = "255,87,34", 181.24 + brown = "121,85,72", 181.25 + grey = "158,158,158", 181.26 + ["blue-grey"] = "96,125,139", 181.27 + }, 181.28 + ["A200"] = { 181.29 + red = "255,82,82", 181.30 + pink = "255,64,129", 181.31 + purple = "224,64,251", 181.32 + ["deep-purple"] = "124,77,255", 181.33 + indigo = "83,109,254", 181.34 + blue = "68,138,255", 181.35 + ["light-blue"] = "64,196,255", 181.36 + cyan = "24,255,255", 181.37 + teal = "100,255,218", 181.38 + green = "105,240,174", 181.39 + ["light-green"] = "178,255,89", 181.40 + lime = "238,255,65", 181.41 + yellow = "255,255,0", 181.42 + amber = "255,215,64", 181.43 + orange = "255,171,64", 181.44 + ["deep-orange"] = "255,110,64", 181.45 + brown ="62,39,35", 181.46 + grey = "33,33,33", 181.47 + ["blue-grey"] = "38,50,56" 181.48 + } 181.49 +} 181.50 + 181.51 +local r = {} 181.52 + 181.53 +if style.color then 181.54 + r.color = { 181.55 + primary = style.color.primary, 181.56 + primary_dark = style.color.primary_dark, 181.57 + accent = style.color.accent, 181.58 + primary_contrast = style.color.primary_contrast, 181.59 + accent_contrast = style.color.accent_contrast 181.60 + } 181.61 + r.color_rgb = { 181.62 + primary = style.color.primary, 181.63 + accent = style.color.accent 181.64 + } 181.65 +elseif style.color_md then 181.66 + r.color_md = { 181.67 + primary = style.color_md.primary, 181.68 + primary_contrast = style.color_md.primary_contrast, 181.69 + accent = style.color_md.accent, 181.70 + accent_contrast = style.color_md.accent_contrast 181.71 + } 181.72 +else 181.73 + r.color_md = { 181.74 + primary = "grey", 181.75 + primary_contrast = "dark", 181.76 + accent = "red", 181.77 + accent_contrast = "dark" 181.78 + } 181.79 +end 181.80 +if not r.color then 181.81 + r.color = { 181.82 + primary = "$palette-" .. r.color_md.primary .. "-500", 181.83 + primary_dark = "$palette-" .. r.color_md.primary .. "-700", 181.84 + accent = "$palette-" .. r.color_md.accent .. "-A200", 181.85 + primary_contrast = "$color-" .. r.color_md.primary_contrast.. "-contrast", 181.86 + accent_contrast = "$color-" .. r.color_md.accent_contrast .. "-contrast" 181.87 + } 181.88 + r.color_rgb = { 181.89 + primary = md_colors["500"][r.color_md.primary], 181.90 + accent = md_colors["A200"][r.color_md.accent] 181.91 + } 181.92 +end 181.93 + 181.94 +return r
182.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 182.2 +++ b/app/main/style/style.css.lua Sun Jul 15 14:07:29 2018 +0200 182.3 @@ -0,0 +1,47 @@ 182.4 +slot.set_layout(nil, "text/css") 182.5 + 182.6 +local style = execute.chunk{ module = "style", chunk = "_style", params = { style = config.style } } 182.7 + 182.8 +local scss = [[ 182.9 +@import "../style/mdl/color-definitions"; 182.10 +$color-primary: ]] .. style.color.primary .. [[; 182.11 +$color-primary-dark: ]] .. style.color.primary .. [[; 182.12 +$color-primary-contrast: ]] .. style.color.primary_contrast .. [[; 182.13 +$color-accent: ]] .. style.color.accent .. [[; 182.14 +$color-accent-contrast: ]] .. style.color.accent_contrast .. [[; 182.15 +$checkbox-image-path: "]] .. request.get_absolute_baseurl() .. "static/mdl" .. [["; 182.16 +@import "../style/mdl/material-design-lite" 182.17 +]] 182.18 + 182.19 +local key = extos.crypt(json.export(style.color), "$1$12345678") -- TODO hash function 182.20 +local filename_scss = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "style-" .. key .. ".scss") 182.21 +local filename_css = encode.file_path(WEBMCP_BASE_PATH, 'tmp', "style-" .. key .. ".css") 182.22 + 182.23 +local css_file = io.open(filename_css, "r") 182.24 + 182.25 +if not config.css then 182.26 + config.css = {} 182.27 +end 182.28 + 182.29 +if not config.css[key] then 182.30 + if css_file then 182.31 + config.css[key] = css_file:read("*a") 182.32 + else 182.33 + local scss_file = assert(io.open(filename_scss, "w")) 182.34 + scss_file:write(scss) 182.35 + scss_file:write("\n") 182.36 + scss_file:close() 182.37 + 182.38 + local output, err, status = extos.pfilter(nil, "sassc", filename_scss) 182.39 + if status ~= 0 then 182.40 + error(err) 182.41 + end 182.42 + config.css[key] = output 182.43 + local css_file = assert(io.open(filename_css, "w")) 182.44 + css_file:write(config.css[key]) 182.45 + css_file:close() 182.46 + end 182.47 +end 182.48 + 182.49 +slot.put_into("data", config.css[key]) 182.50 +
183.1 --- a/app/main/suggestion/_action/add.lua Thu Jun 23 03:30:57 2016 +0200 183.2 +++ b/app/main/suggestion/_action/add.lua Sun Jul 15 14:07:29 2018 +0200 183.3 @@ -6,7 +6,7 @@ 183.4 183.5 local initiative = Initiative:by_id(param.get("initiative_id", atom.integer)) 183.6 if not app.session.member:has_voting_right_for_unit_id(initiative.issue.area.unit_id) then 183.7 - error("access denied") 183.8 + return execute.view { module = "index", view = "403" } 183.9 end 183.10 183.11 183.12 @@ -27,7 +27,8 @@ 183.13 end 183.14 end 183.15 if not formatting_engine_valid then 183.16 - error("invalid formatting engine!") 183.17 + slot.put_into("error", "invalid formatting engine!") 183.18 + return false 183.19 end 183.20 183.21 if param.get("preview") then 183.22 @@ -68,4 +69,4 @@ 183.23 183.24 opinion:save() 183.25 183.26 -slot.put_into("notice", _"Your suggestion has been added") 183.27 \ No newline at end of file 183.28 +slot.put_into("notice", _"Your suggestion has been added")
184.1 --- a/app/main/unit/_head.lua Thu Jun 23 03:30:57 2016 +0200 184.2 +++ b/app/main/unit/_head.lua Sun Jul 15 14:07:29 2018 +0200 184.3 @@ -29,4 +29,4 @@ 184.4 end 184.5 end } 184.6 184.7 -end ) 184.8 \ No newline at end of file 184.9 +end )
185.1 --- a/app/main/unit/_sidebar.lua Thu Jun 23 03:30:57 2016 +0200 185.2 +++ b/app/main/unit/_sidebar.lua Sun Jul 15 14:07:29 2018 +0200 185.3 @@ -11,15 +11,6 @@ 185.4 :add_where{ "area.active" } 185.5 :add_order_by("area.name") 185.6 185.7 -if member then 185.8 - areas_selector:left_join ( 185.9 - "membership", nil, 185.10 - { "membership.area_id = area.id AND membership.member_id = ?", member.id } 185.11 - ) 185.12 - areas_selector:add_field("membership.member_id NOTNULL", "subscribed", { "grouped" }) 185.13 -end 185.14 - 185.15 - 185.16 local areas = areas_selector:exec() 185.17 if member then 185.18 unit:load_delegation_info_once_for_member_id(member.id) 185.19 @@ -77,4 +68,4 @@ 185.20 185.21 end -- if #areas > 0 185.22 185.23 -end ) -- ui.sidebar 185.24 \ No newline at end of file 185.25 +end ) -- ui.sidebar
186.1 --- a/app/main/unit/_sidebar_whatcanido.lua Thu Jun 23 03:30:57 2016 +0200 186.2 +++ b/app/main/unit/_sidebar_whatcanido.lua Sun Jul 15 14:07:29 2018 +0200 186.3 @@ -1,69 +1,108 @@ 186.4 local unit = param.get ( "unit", "table" ) 186.5 +unit:load_delegation_info_once_for_member_id(app.session.member_id) 186.6 186.7 -ui.sidebar ( "tab-whatcanido", function () 186.8 +ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 186.9 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 186.10 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = _"What can I do here?" } 186.11 + end } 186.12 + ui.container{ attr = { class = "what-can-i-do-here" }, content = function() 186.13 186.14 - ui.sidebarHeadWhatCanIDo() 186.15 - 186.16 - if app.session.member then 186.17 - 186.18 - if app.session.member:has_voting_right_for_unit_id ( unit.id ) then 186.19 - ui.sidebarSection( function () 186.20 - 186.21 - if not unit.delegation_info.first_trustee_id then 186.22 - ui.heading{ level = 3, content = _"I want to delegate this organizational unit" } 186.23 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 186.24 + if app.session.member and app.session.member:has_voting_right_for_unit_id ( unit.id ) then 186.25 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 186.26 + ui.tag{ content = _"I want to stay informed" } 186.27 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 186.28 + ui.tag { tag = "li", content = function () 186.29 + ui.tag{ content = _"check your " } 186.30 + ui.link{ 186.31 + module = "member", view = "settings_notification", 186.32 + params = { return_to = "home" }, 186.33 + text = _"notifications settings" 186.34 + } 186.35 + end } 186.36 + if not config.voting_only then 186.37 ui.tag { tag = "li", content = function () 186.38 - ui.link { 186.39 - module = "delegation", view = "show", params = { 186.40 - unit_id = unit.id, 186.41 - }, 186.42 - content = _("choose delegatee", { 186.43 - unit_name = unit.name 186.44 - }) 186.45 + ui.tag{ content = _"subscribe subject areas or add your interested to issues and you will be notified about changes (follow the instruction on the area or issue page)" } 186.46 + end } 186.47 + end 186.48 + end } 186.49 + end } 186.50 + 186.51 + if not config.disable_delegations then 186.52 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 186.53 + if not unit.delegation_info.first_trustee_id then 186.54 + ui.tag{ content = _"I want to delegate this organizational unit" } 186.55 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 186.56 + ui.tag { tag = "li", content = function () 186.57 + ui.link { 186.58 + module = "delegation", view = "show", params = { 186.59 + unit_id = unit.id, 186.60 + }, 186.61 + content = _("choose delegatee", { 186.62 + unit_name = unit.name 186.63 + }) 186.64 + } 186.65 + end } 186.66 + end } 186.67 + else 186.68 + ui.container { attr = { class = "right" }, content = function() 186.69 + local member = Member:by_id(unit.delegation_info.first_trustee_id) 186.70 + execute.view{ 186.71 + module = "member_image", 186.72 + view = "_show", 186.73 + params = { 186.74 + member = member, 186.75 + image_type = "avatar", 186.76 + show_dummy = true 186.77 + } 186.78 } 186.79 end } 186.80 - end } 186.81 - else 186.82 - ui.container { attr = { class = "right" }, content = function() 186.83 - local member = Member:by_id(unit.delegation_info.first_trustee_id) 186.84 - execute.view{ 186.85 - module = "member_image", 186.86 - view = "_show", 186.87 - params = { 186.88 - member = member, 186.89 - image_type = "avatar", 186.90 - show_dummy = true 186.91 - } 186.92 - } 186.93 - end } 186.94 - ui.heading{ level = 3, content = _"You delegated this unit" } 186.95 + ui.tag{ content = _"You delegated this unit" } 186.96 186.97 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 186.98 - ui.tag { tag = "li", content = function () 186.99 - ui.link { 186.100 - module = "delegation", view = "show", params = { 186.101 - unit_id = unit.id, 186.102 - }, 186.103 - content = _("change/revoke delegation", { 186.104 - unit_name = unit.name 186.105 - }) 186.106 - } 186.107 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 186.108 + ui.tag { tag = "li", content = function () 186.109 + ui.link { 186.110 + module = "delegation", view = "show", params = { 186.111 + unit_id = unit.id, 186.112 + }, 186.113 + content = _("change/revoke delegation", { 186.114 + unit_name = unit.name 186.115 + }) 186.116 + } 186.117 + end } 186.118 end } 186.119 - end } 186.120 - end 186.121 - end ) 186.122 + end 186.123 + end } 186.124 + end 186.125 + 186.126 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 186.127 + ui.tag{ 186.128 + content = _"I want to vote" 186.129 + } 186.130 + ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 186.131 + ui.tag { tag = "li", content = _"check the issues on the right, and click on 'Vote now' to vote on an issue which is in voting phase." } 186.132 + end } 186.133 + end } 186.134 186.135 - ui.sidebarSection( function() 186.136 - ui.heading { level = 3, content = _"I want to start a new initiative" } 186.137 - ui.tag { tag = "ul", attr = { class = "ul" }, content = function () 186.138 - ui.tag { tag = "li", content = _"Open the appropriate subject area where your issue fits in and follow the instruction on that page." } 186.139 - end } 186.140 - end ) 186.141 + else 186.142 + ui.container { attr = { class = "mdl-card__content mdl-card--border" }, content = function () 186.143 + ui.tag{ content = _"You are not entitled to vote in this unit" } 186.144 + ui.tag{ tag = "ul", content = function() 186.145 + ui.tag{ tag = "li", content = function() 186.146 + ui.link{ module = "index", view = "login", content = _"Login" } 186.147 + end } 186.148 + end } 186.149 + end } 186.150 + end 186.151 186.152 - else 186.153 - ui.sidebarSection( _"You are not entitled to vote in this unit" ) 186.154 + if not config.voting_only and app.session.member_id and app.session.member:has_initiative_right_for_unit_id ( unit.id ) then 186.155 + ui.container{ attr = { class = "mdl-card__content mdl-card--border" }, content = function() 186.156 + ui.tag{ content = _"I want to start a new initiative" } 186.157 + ui.tag{ tag = "ul", attr = { class = "ul" }, content = function () 186.158 + ui.tag { tag = "li", content = _"open the appropriate subject area for your issue and follow the instruction on that page." } 186.159 + end } 186.160 + end } 186.161 end 186.162 - 186.163 - end 186.164 + 186.165 + end } 186.166 186.167 -end ) 186.168 \ No newline at end of file 186.169 +end }
187.1 --- a/app/main/unit/show.lua Thu Jun 23 03:30:57 2016 +0200 187.2 +++ b/app/main/unit/show.lua Sun Jul 15 14:07:29 2018 +0200 187.3 @@ -8,6 +8,7 @@ 187.4 return 187.5 end 187.6 187.7 +app.current_unit = unit 187.8 187.9 unit:load_delegation_info_once_for_member_id(app.session.member_id) 187.10 187.11 @@ -38,36 +39,40 @@ 187.12 :add_where("issue.closed NOTNULL") 187.13 :add_order_by("issue.closed DESC") 187.14 187.15 - 187.16 - 187.17 -execute.view { module = "unit", view = "_head", params = { unit = unit } } 187.18 - 187.19 + ui.container{ attr = { class = "mdl-grid" }, content = function() 187.20 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 187.21 + 187.22 + ui.heading{ content = unit.name } 187.23 187.24 -execute.view { 187.25 - module = "unit", view = "_sidebar", params = { 187.26 - unit = unit 187.27 - } 187.28 -} 187.29 + execute.view { module = "unit", view = "_head", params = { unit = unit } } 187.30 + 187.31 + execute.view { 187.32 + module = "unit", view = "_sidebar", params = { 187.33 + unit = unit 187.34 + } 187.35 + } 187.36 187.37 -execute.view { 187.38 - module = "unit", view = "_sidebar_whatcanido", params = { 187.39 - unit = unit 187.40 - } 187.41 -} 187.42 + execute.view { 187.43 + module = "unit", view = "_sidebar_whatcanido", params = { 187.44 + unit = unit 187.45 + } 187.46 + } 187.47 187.48 -execute.view { 187.49 - module = "unit", view = "_sidebar_members", params = { 187.50 - unit = unit 187.51 - } 187.52 -} 187.53 + execute.view { 187.54 + module = "unit", view = "_sidebar_members", params = { 187.55 + unit = unit 187.56 + } 187.57 + } 187.58 187.59 -execute.view { 187.60 - module = "issue", 187.61 - view = "_list2", 187.62 - params = { for_unit = unit, head = function () 187.63 - ui.heading { attr = { class = "left" }, level = 1, content = unit.name } 187.64 + execute.view { 187.65 + module = "issue", 187.66 + view = "_list", 187.67 + params = { for_unit = unit, head = function () 187.68 + ui.heading { attr = { class = "left" }, level = 1, content = unit.name } 187.69 + end } 187.70 + } 187.71 + end } 187.72 end } 187.73 -} 187.74 187.75 --[[ 187.76 if app.session:has_access("all_pseudonymous") then 187.77 @@ -90,4 +95,4 @@ 187.78 187.79 ui.tabs(tabs) 187.80 187.81 ---]] 187.82 \ No newline at end of file 187.83 +--]]
188.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 188.2 +++ b/app/main/util/_unit_area_filter.lua Sun Jul 15 14:07:29 2018 +0200 188.3 @@ -0,0 +1,44 @@ 188.4 +local member = app.session.member 188.5 +local units 188.6 +if member then 188.7 + units = member:get_reference_selector("units"):add_order_by("name"):exec() 188.8 + units:load_delegation_info_once_for_member_id(member.id) 188.9 +else 188.10 + units = Unit:new_selector():add_where("active"):add_order_by("name"):exec() 188.11 +end 188.12 + 188.13 +ui.tag{ tag = "button", attr = { id = "unit-menu", class = "mdl-button mdl-js-button" }, content = function() 188.14 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "filter_list" } 188.15 + ui.tag{ content = "All units" } 188.16 +end } 188.17 +ui.tag{ tag = "ul", attr = { class = "mdl-menu mdl-menu--top-left mdl-js-menu mdl-js-ripple-effect", ["data-mdl-for"]="unit-menu" }, content = function() 188.18 + if #units > 0 then 188.19 + for i, unit in ipairs(units) do 188.20 + local class = "mdl-navigation__link mdl-navigation__head" 188.21 + if i == #units then 188.22 + class = class .. " mdl-menu__item--full-bleed-divider" 188.23 + end 188.24 + ui.tag{ tag = "li", attr = { class = "mdl-menu__item" }, content = function() 188.25 + ui.link{ attr = { class = classx }, content = unit.name, module = "unit", view = "show", id = unit.id } 188.26 + end } 188.27 + end 188.28 + end 188.29 +end } 188.30 + 188.31 +if app.current_unit then 188.32 + ui.tag{ tag = "button", attr = { id = "area-menu", class = "mdl-button mdl-js-button mdl-button--icon" }, content = function() 188.33 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "more_vert" } 188.34 + end } 188.35 + ui.tag{ content = "All units" } 188.36 + ui.tag{ tag = "ul", attr = { class = "mdl-menu mdl-menu--top-left mdl-js-menu mdl-js-ripple-effect", ["data-mdl-for"]="area-menu" }, content = function() 188.37 + for i, area in ipairs({}) do 188.38 + local class = "mdl-navigation__link mdl-menu__item--small" 188.39 + if i == #areas then 188.40 + class = class .. " mdl-menu__item--full-bleed-divider" 188.41 + end 188.42 + ui.tag{ tag = "li", attr = { class = "mdl-menu__item" }, content = function() 188.43 + ui.link{ attr = { class = classx }, module = "area", view = "show", id = area.id, content = area.name } 188.44 + end } 188.45 + end 188.46 + end } 188.47 +end
189.1 --- a/app/main/vote/_action/update.lua Thu Jun 23 03:30:57 2016 +0200 189.2 +++ b/app/main/vote/_action/update.lua Sun Jul 15 14:07:29 2018 +0200 189.3 @@ -3,10 +3,16 @@ 189.4 189.5 local issue = Issue:new_selector():add_where{ "id = ?", param.get("issue_id", atom.integer) }:for_share():single_object_mode():exec() 189.6 189.7 +-- TODO patch for project voting 189.8 +if config.alternative_voting and config.alternative_voting[tostring(issue.policy.id)] then 189.9 + return false 189.10 +end 189.11 + 189.12 + 189.13 local preview = (param.get("preview") or param.get("edit")) and true or false 189.14 189.15 if not app.session.member:has_voting_right_for_unit_id(issue.area.unit_id) then 189.16 - error("access denied") 189.17 + return execute.view { module = "index", view = "403" } 189.18 end 189.19 189.20 local update_comment = param.get("update_comment") and true or false 189.21 @@ -82,7 +88,8 @@ 189.22 end 189.23 end 189.24 if not formatting_engine_valid then 189.25 - error("invalid formatting engine!") 189.26 + slot.put_into("error", "invalid formatting engine!") 189.27 + return false 189.28 end 189.29 end 189.30 189.31 @@ -116,7 +123,7 @@ 189.32 local grade = tonumber(grade) 189.33 local initiative = Initiative:by_id(initiative_id) 189.34 if initiative.issue.id ~= issue.id then 189.35 - error("initiative from wrong issue") 189.36 + return execute.view { module = "index", view = "403" } 189.37 end 189.38 if not preview and not issue.closed then 189.39 local vote = Vote:by_pk(initiative_id, app.session.member.id)
190.1 --- a/app/main/vote/list.lua Thu Jun 23 03:30:57 2016 +0200 190.2 +++ b/app/main/vote/list.lua Sun Jul 15 14:07:29 2018 +0200 190.3 @@ -1,5 +1,23 @@ 190.4 local issue = Issue:by_id(param.get("issue_id"), atom.integer) 190.5 190.6 +-- TODO patch for project voting 190.7 +if not issue.closed and config.alternative_voting and config.alternative_voting[tostring(issue.policy.id)] then 190.8 + local voting_config = config.alternative_voting[tostring(issue.policy.id)] 190.9 + 190.10 + local url = encode.url { 190.11 + module = voting_config.module, 190.12 + view = voting_config.view, 190.13 + params = { issue_id = issue.id } 190.14 + } 190.15 + 190.16 + return request.redirect{ external = url } 190.17 +end 190.18 + 190.19 +if not issue then 190.20 + execute.view { module = "index", view = "404" } 190.21 + return 190.22 +end 190.23 + 190.24 local member_id = param.get("member_id", atom.integer) 190.25 local member 190.26 local readonly = false 190.27 @@ -7,8 +25,9 @@ 190.28 local preview = param.get("preview") and true or false 190.29 190.30 if member_id then 190.31 - if not issue.closed then 190.32 - error("access denied") 190.33 + if not issue.closed then 190.34 + execute.view{ module = "index", view = "403" } 190.35 + return 190.36 end 190.37 member = Member:by_id(member_id) 190.38 readonly = true 190.39 @@ -149,7 +168,7 @@ 190.40 190.41 ui.sidebar( "tab-members", function() 190.42 ui.sidebarHead(function() 190.43 - ui.heading{ level = 2, content = _"Incoming delegations" } 190.44 + ui.heading{ level = 4, content = _"Incoming delegations" } 190.45 end) 190.46 execute.view{ 190.47 module = "member", 190.48 @@ -166,407 +185,410 @@ 190.49 end) 190.50 end 190.51 190.52 - 190.53 -ui.section( function() 190.54 - 190.55 - ui.sectionHead( function() 190.56 - if preview then 190.57 - ui.heading { level = 1, content = _"Preview of voting ballot" } 190.58 - elseif readonly then 190.59 - local str = _("Ballot of '#{member_name}'", 190.60 - {member_name = string.format('<a href="%s">%s</a>', 190.61 - encode.url{ 190.62 - module = "member", 190.63 - view = "show", 190.64 - id = member.id, 190.65 - }, 190.66 - encode.html(member.name)) 190.67 - } 190.68 - ) 190.69 - ui.heading { level = 1, content = function () slot.put ( str ) end } 190.70 - else 190.71 - ui.heading { level = 1, content = _"Voting" } 190.72 - end 190.73 - end ) 190.74 - 190.75 - ui.sectionRow( function() 190.76 +ui.container{ attr = { class = "mdl-grid" }, content = function() 190.77 + ui.container{ attr = { class = "mdl-cell mdl-cell--12-col" }, content = function() 190.78 190.79 - ui.form{ 190.80 - record = direct_voter, 190.81 - attr = { 190.82 - id = "voting_form", 190.83 - class = readonly and "voting_form_readonly" or "voting_form_active" 190.84 - }, 190.85 - module = "vote", 190.86 - action = "update", 190.87 - params = { issue_id = issue.id }, 190.88 - content = function() 190.89 - if not readonly or preview then 190.90 - local scoring = param.get("scoring") 190.91 - if not scoring then 190.92 - for i, initiative in ipairs(initiatives) do 190.93 - local vote = initiative.vote 190.94 - if vote then 190.95 - tempvotings[initiative.id] = vote.grade 190.96 - else 190.97 - tempvotings[initiative.id] = 0 190.98 - end 190.99 - end 190.100 - local tempvotings_list = {} 190.101 - for key, val in pairs(tempvotings) do 190.102 - tempvotings_list[#tempvotings_list+1] = tostring(key) .. ":" .. tostring(val) 190.103 - end 190.104 - if #tempvotings_list > 0 then 190.105 - scoring = table.concat(tempvotings_list, ";") 190.106 - else 190.107 - scoring = "" 190.108 - end 190.109 + ui.container{ attr = { class = "mdl-card mdl-card__fullwidth mdl-shadow--2dp" }, content = function() 190.110 + ui.container{ attr = { class = "mdl-card__title mdl-card--border" }, content = function() 190.111 + ui.heading { attr = { class = "mdl-card__title-text" }, level = 2, content = function() 190.112 + if preview then 190.113 + ui.tag{ content = _"Preview of voting ballot" } 190.114 + elseif readonly then 190.115 + local str = _("Ballot of '#{member_name}'", { member_name = string.format( 190.116 + '<a href="%s">%s</a>', 190.117 + encode.url{ module = "member", view = "show", id = member.id }, 190.118 + encode.html(member.name) 190.119 + ) }) 190.120 + ui.tag{ content = function () slot.put ( str ) end } 190.121 + else 190.122 + ui.tag{ content = _"Voting" } 190.123 end 190.124 - slot.put('<input type="hidden" name="scoring" value="' .. scoring .. '"/>') 190.125 - end 190.126 - if preview then 190.127 - ui.heading{ level = 2, content = _"Your choice" } 190.128 - elseif not readonly then 190.129 - ui.heading{ level = 2, content = _"Make your choice by placing the initiatives" } 190.130 - end 190.131 - 190.132 - ui.container{ 190.133 - attr = { id = "voting" }, 190.134 + end } 190.135 + end } 190.136 + 190.137 + ui.container{ attr = { class = "mdl-card__content" }, content = function() 190.138 + 190.139 + ui.form{ 190.140 + record = direct_voter, 190.141 + attr = { 190.142 + id = "voting_form", 190.143 + class = readonly and "voting_form_readonly" or "voting_form_active" 190.144 + }, 190.145 + module = "vote", 190.146 + action = "update", 190.147 + params = { issue_id = issue.id }, 190.148 content = function() 190.149 - local approval_index, disapproval_index = 0, 0 190.150 - local approval_used, disapproval_used 190.151 - for grade = max_grade, min_grade, -1 do 190.152 - local entries = sections[grade] 190.153 - local class 190.154 - if grade > 0 then 190.155 - class = "approval" 190.156 - elseif grade < 0 then 190.157 - class = "disapproval" 190.158 - else 190.159 - class = "abstention" 190.160 + if not readonly or preview then 190.161 + local scoring = param.get("scoring") 190.162 + if not scoring then 190.163 + for i, initiative in ipairs(initiatives) do 190.164 + local vote = initiative.vote 190.165 + if vote then 190.166 + tempvotings[initiative.id] = vote.grade 190.167 + else 190.168 + tempvotings[initiative.id] = 0 190.169 + end 190.170 + end 190.171 + local tempvotings_list = {} 190.172 + for key, val in pairs(tempvotings) do 190.173 + tempvotings_list[#tempvotings_list+1] = tostring(key) .. ":" .. tostring(val) 190.174 + end 190.175 + if #tempvotings_list > 0 then 190.176 + scoring = table.concat(tempvotings_list, ";") 190.177 + else 190.178 + scoring = "" 190.179 + end 190.180 end 190.181 - if 190.182 - #entries > 0 or 190.183 - (grade == 1 and not approval_used) or 190.184 - (grade == -1 and not disapproval_used) or 190.185 - grade == 0 190.186 - then 190.187 - ui.container{ 190.188 - attr = { class = class }, 190.189 - content = function() 190.190 - local heading 190.191 - if class == "approval" then 190.192 - approval_used = true 190.193 - approval_index = approval_index + 1 190.194 - if approval_count > 1 then 190.195 - if approval_index == 1 then 190.196 - if #entries == 1 then 190.197 - heading = _"Approval (first preference) [single entry]" 190.198 + slot.put('<input type="hidden" name="scoring" value="' .. scoring .. '"/>') 190.199 + end 190.200 + if preview then 190.201 + ui.container{ content = _"Your choice" } 190.202 + elseif not readonly then 190.203 + ui.container{ content = _"Make your choice by placing the initiatives" } 190.204 + end 190.205 + 190.206 + ui.container{ 190.207 + attr = { id = "voting" }, 190.208 + content = function() 190.209 + local approval_index, disapproval_index = 0, 0 190.210 + local approval_used, disapproval_used 190.211 + for grade = max_grade, min_grade, -1 do 190.212 + local entries = sections[grade] 190.213 + local class 190.214 + if grade > 0 then 190.215 + class = "approval" 190.216 + elseif grade < 0 then 190.217 + class = "disapproval" 190.218 + else 190.219 + class = "abstention" 190.220 + end 190.221 + if 190.222 + #entries > 0 or 190.223 + (grade == 1 and not approval_used) or 190.224 + (grade == -1 and not disapproval_used) or 190.225 + grade == 0 190.226 + then 190.227 + ui.container{ 190.228 + attr = { class = class }, 190.229 + content = function() 190.230 + local heading 190.231 + if class == "approval" then 190.232 + approval_used = true 190.233 + approval_index = approval_index + 1 190.234 + if approval_count > 1 then 190.235 + if approval_index == 1 then 190.236 + if #entries == 1 then 190.237 + heading = _"Approval (first preference) [single entry]" 190.238 + else 190.239 + heading = _"Approval (first preference) [many entries]" 190.240 + end 190.241 + elseif approval_index == 2 then 190.242 + if #entries == 1 then 190.243 + heading = _"Approval (second preference) [single entry]" 190.244 + else 190.245 + heading = _"Approval (second preference) [many entries]" 190.246 + end 190.247 + elseif approval_index == 3 then 190.248 + if #entries == 1 then 190.249 + heading = _"Approval (third preference) [single entry]" 190.250 + else 190.251 + heading = _"Approval (third preference) [many entries]" 190.252 + end 190.253 + else 190.254 + if #entries == 1 then 190.255 + heading = _"Approval (#th preference) [single entry]" 190.256 + else 190.257 + heading = _"Approval (#th preference) [many entries]" 190.258 + end 190.259 + end 190.260 else 190.261 - heading = _"Approval (first preference) [many entries]" 190.262 - end 190.263 - elseif approval_index == 2 then 190.264 - if #entries == 1 then 190.265 - heading = _"Approval (second preference) [single entry]" 190.266 - else 190.267 - heading = _"Approval (second preference) [many entries]" 190.268 + if #entries == 1 then 190.269 + heading = _"Approval [single entry]" 190.270 + else 190.271 + heading = _"Approval [many entries]" 190.272 + end 190.273 end 190.274 - elseif approval_index == 3 then 190.275 - if #entries == 1 then 190.276 - heading = _"Approval (third preference) [single entry]" 190.277 + elseif class == "abstention" then 190.278 + if #entries == 1 then 190.279 + heading = _"Abstention [single entry]" 190.280 + else 190.281 + heading = _"Abstention [many entries]" 190.282 + end 190.283 + elseif class == "disapproval" then 190.284 + disapproval_used = true 190.285 + disapproval_index = disapproval_index + 1 190.286 + if disapproval_count > disapproval_index + 1 then 190.287 + if #entries == 1 then 190.288 + heading = _"Disapproval (prefer to lower blocks) [single entry]" 190.289 + else 190.290 + heading = _"Disapproval (prefer to lower blocks) [many entries]" 190.291 + end 190.292 + elseif disapproval_count == 2 and disapproval_index == 1 then 190.293 + if #entries == 1 then 190.294 + heading = _"Disapproval (prefer to lower block) [single entry]" 190.295 + else 190.296 + heading = _"Disapproval (prefer to lower block) [many entries]" 190.297 + end 190.298 + elseif disapproval_index == disapproval_count - 1 then 190.299 + if #entries == 1 then 190.300 + heading = _"Disapproval (prefer to last block) [single entry]" 190.301 + else 190.302 + heading = _"Disapproval (prefer to last block) [many entries]" 190.303 + end 190.304 else 190.305 - heading = _"Approval (third preference) [many entries]" 190.306 - end 190.307 - else 190.308 - if #entries == 1 then 190.309 - heading = _"Approval (#th preference) [single entry]" 190.310 - else 190.311 - heading = _"Approval (#th preference) [many entries]" 190.312 + if #entries == 1 then 190.313 + heading = _"Disapproval [single entry]" 190.314 + else 190.315 + heading = _"Disapproval [many entries]" 190.316 + end 190.317 end 190.318 end 190.319 - else 190.320 - if #entries == 1 then 190.321 - heading = _"Approval [single entry]" 190.322 - else 190.323 - heading = _"Approval [many entries]" 190.324 - end 190.325 - end 190.326 - elseif class == "abstention" then 190.327 - if #entries == 1 then 190.328 - heading = _"Abstention [single entry]" 190.329 - else 190.330 - heading = _"Abstention [many entries]" 190.331 - end 190.332 - elseif class == "disapproval" then 190.333 - disapproval_used = true 190.334 - disapproval_index = disapproval_index + 1 190.335 - if disapproval_count > disapproval_index + 1 then 190.336 - if #entries == 1 then 190.337 - heading = _"Disapproval (prefer to lower blocks) [single entry]" 190.338 - else 190.339 - heading = _"Disapproval (prefer to lower blocks) [many entries]" 190.340 - end 190.341 - elseif disapproval_count == 2 and disapproval_index == 1 then 190.342 - if #entries == 1 then 190.343 - heading = _"Disapproval (prefer to lower block) [single entry]" 190.344 - else 190.345 - heading = _"Disapproval (prefer to lower block) [many entries]" 190.346 - end 190.347 - elseif disapproval_index == disapproval_count - 1 then 190.348 - if #entries == 1 then 190.349 - heading = _"Disapproval (prefer to last block) [single entry]" 190.350 - else 190.351 - heading = _"Disapproval (prefer to last block) [many entries]" 190.352 - end 190.353 - else 190.354 - if #entries == 1 then 190.355 - heading = _"Disapproval [single entry]" 190.356 - else 190.357 - heading = _"Disapproval [many entries]" 190.358 - end 190.359 - end 190.360 - end 190.361 - ui.tag { 190.362 - tag = "div", 190.363 - attr = { class = "cathead" }, 190.364 - content = heading 190.365 - } 190.366 - for i, initiative in ipairs(entries) do 190.367 - ui.container{ 190.368 - attr = { 190.369 - class = "movable", 190.370 - id = "entry_" .. tostring(initiative.id) 190.371 - }, 190.372 - content = function() 190.373 - local initiators_selector = initiative:get_reference_selector("initiating_members") 190.374 - :add_where("accepted") 190.375 - local initiators = initiators_selector:exec() 190.376 - local initiator_names = {} 190.377 - for i, initiator in ipairs(initiators) do 190.378 - initiator_names[#initiator_names+1] = initiator.name 190.379 - end 190.380 - local initiator_names_string = table.concat(initiator_names, ", ") 190.381 + ui.tag { 190.382 + tag = "div", 190.383 + attr = { class = "cathead " }, 190.384 + content = heading 190.385 + } 190.386 + for i, initiative in ipairs(entries) do 190.387 ui.container{ 190.388 - attr = { style = "float: right; position: relative;" }, 190.389 + attr = { 190.390 + class = "movable", 190.391 + id = "entry_" .. tostring(initiative.id) 190.392 + }, 190.393 content = function() 190.394 - ui.link{ 190.395 - attr = { class = "clickable" }, 190.396 - content = _"Show", 190.397 - module = "initiative", 190.398 - view = "show", 190.399 - id = initiative.id 190.400 - } 190.401 - slot.put(" ") 190.402 - ui.link{ 190.403 - attr = { class = "clickable", target = "_blank" }, 190.404 - content = _"(new window)", 190.405 - module = "initiative", 190.406 - view = "show", 190.407 - id = initiative.id 190.408 + local initiators_selector = initiative:get_reference_selector("initiating_members") 190.409 + :add_where("accepted") 190.410 + local initiators = initiators_selector:exec() 190.411 + local initiator_names = {} 190.412 + for i, initiator in ipairs(initiators) do 190.413 + initiator_names[#initiator_names+1] = initiator.name 190.414 + end 190.415 + local initiator_names_string = table.concat(initiator_names, ", ") 190.416 + ui.container{ 190.417 + attr = { style = "float: right; position: relative;" }, 190.418 + content = function() 190.419 + ui.link{ 190.420 + attr = { class = "clickable" }, 190.421 + content = _"Show", 190.422 + module = "initiative", 190.423 + view = "show", 190.424 + id = initiative.id 190.425 + } 190.426 + slot.put(" ") 190.427 + ui.link{ 190.428 + attr = { class = "clickable", target = "_blank" }, 190.429 + content = _"(new window)", 190.430 + module = "initiative", 190.431 + view = "show", 190.432 + id = initiative.id 190.433 + } 190.434 + if not readonly then 190.435 + slot.put(" ") 190.436 + ui.image{ attr = { class = "grabber" }, static = "icons/grabber.png" } 190.437 + end 190.438 + end 190.439 } 190.440 if not readonly then 190.441 - slot.put(" ") 190.442 - ui.image{ attr = { class = "grabber" }, static = "icons/grabber.png" } 190.443 - end 190.444 - end 190.445 - } 190.446 - if not readonly then 190.447 - ui.container{ 190.448 - attr = { style = "float: left; position: relative;" }, 190.449 - content = function() 190.450 - ui.tag{ 190.451 - tag = "input", 190.452 - attr = { 190.453 - onclick = "if (jsFail) return true; voting_moveUp(this.parentNode.parentNode); return(false);", 190.454 - name = "move_up_" .. tostring(initiative.id), 190.455 - class = not disabled and "clickable" or nil, 190.456 - type = "image", 190.457 - src = encode.url{ static = "icons/move_up.png" }, 190.458 - alt = _"Move up" 190.459 - } 190.460 - } 190.461 - slot.put(" ") 190.462 - ui.tag{ 190.463 - tag = "input", 190.464 - attr = { 190.465 - onclick = "if (jsFail) return true; voting_moveDown(this.parentNode.parentNode); return(false);", 190.466 - name = "move_down_" .. tostring(initiative.id), 190.467 - class = not disabled and "clickable" or nil, 190.468 - type = "image", 190.469 - src = encode.url{ static = "icons/move_down.png" }, 190.470 - alt = _"Move down" 190.471 - } 190.472 + ui.container{ 190.473 + attr = { style = "float: left; position: relative;" }, 190.474 + content = function() 190.475 + ui.tag{ 190.476 + tag = "button", 190.477 + attr = { 190.478 + onclick = "if (jsFail) return true; voting_moveUp(this.parentNode.parentNode); return(false);", 190.479 + name = "move_up_" .. tostring(initiative.id), 190.480 + class = "clickable mdl-button mdl-js-button mdl-button--icon", 190.481 + alt = _"Move up", 190.482 + }, 190.483 + content = function() 190.484 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "arrow_upward" } 190.485 + end 190.486 + } 190.487 + ui.tag{ 190.488 + tag = "button", 190.489 + attr = { 190.490 + onclick = "if (jsFail) return true; voting_moveDown(this.parentNode.parentNode); return(false);", 190.491 + name = "move_down_" .. tostring(initiative.id), 190.492 + class = "clickable mdl-button mdl-js-button mdl-button--icon", 190.493 + alt = _"Move down" 190.494 + }, 190.495 + content = function() 190.496 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "arrow_downward" } 190.497 + end 190.498 + } 190.499 + slot.put(" ") 190.500 + end 190.501 } 190.502 - slot.put(" ") 190.503 end 190.504 - } 190.505 - end 190.506 - ui.container{ 190.507 - content = function() 190.508 - ui.tag{ content = "i" .. initiative.id .. ": " } 190.509 - ui.tag{ content = initiative.shortened_name } 190.510 - slot.put("<br />") 190.511 - for i, initiator in ipairs(initiators) do 190.512 - ui.link{ 190.513 - attr = { class = "clickable" }, 190.514 - content = function () 190.515 - execute.view{ 190.516 - module = "member_image", 190.517 - view = "_show", 190.518 - params = { 190.519 - member = initiator, 190.520 - image_type = "avatar", 190.521 - show_dummy = true, 190.522 - class = "micro_avatar", 190.523 - popup_text = text 190.524 - } 190.525 + ui.container{ 190.526 + content = function() 190.527 + ui.tag{ attr = { class = "initiative_name" }, content = function() 190.528 + ui.tag{ content = "i" .. initiative.id .. ": " } 190.529 + ui.tag{ content = initiative.shortened_name } 190.530 + end } 190.531 + slot.put("<br />") 190.532 + for i, initiator in ipairs(initiators) do 190.533 + ui.link{ 190.534 + attr = { class = "clickable" }, 190.535 + content = function () 190.536 + execute.view{ 190.537 + module = "member_image", 190.538 + view = "_show", 190.539 + params = { 190.540 + member = initiator, 190.541 + image_type = "avatar", 190.542 + show_dummy = true, 190.543 + class = "micro_avatar", 190.544 + popup_text = text 190.545 + } 190.546 + } 190.547 + end, 190.548 + module = "member", view = "show", id = initiator.id 190.549 } 190.550 - end, 190.551 - module = "member", view = "show", id = initiator.id 190.552 - } 190.553 - slot.put(" ") 190.554 - ui.tag{ content = initiator.name } 190.555 - slot.put(" ") 190.556 - end 190.557 + slot.put(" ") 190.558 + ui.tag{ content = initiator.name } 190.559 + slot.put(" ") 190.560 + end 190.561 + end 190.562 + } 190.563 end 190.564 } 190.565 end 190.566 - } 190.567 - end 190.568 + end 190.569 + } 190.570 + end 190.571 + end 190.572 + end 190.573 + } 190.574 + if app.session.member_id and preview then 190.575 + local formatting_engine = param.get("formatting_engine") or config.enforce_formatting_engine 190.576 + local comment = param.get("comment") 190.577 + if comment and #comment > 0 then 190.578 + local rendered_comment = format.wiki_text(comment, formatting_engine) 190.579 + ui.container{ content = _"Voting comment" } 190.580 + ui.container { attr = { class = "member_statement" }, content = function() 190.581 + slot.put(rendered_comment) 190.582 + end } 190.583 + slot.put("<br />") 190.584 + end 190.585 + end 190.586 + if (readonly or direct_voter and direct_voter.comment) and not preview and not (app.session.member_id == member.id) then 190.587 + local text 190.588 + if direct_voter and direct_voter.comment_changed then 190.589 + text = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) }) 190.590 + elseif direct_voter and direct_voter.comment then 190.591 + text = _"Voting comment" 190.592 + end 190.593 + if text then 190.594 + ui.container{ content = text } 190.595 + end 190.596 + if direct_voter and direct_voter.comment then 190.597 + local rendered_comment = direct_voter:get_content('html') 190.598 + ui.container { attr = { class = "member_statement" }, content = function() 190.599 + slot.put(rendered_comment) 190.600 + end } 190.601 + slot.put("<br />") 190.602 + end 190.603 + end 190.604 + if app.session.member_id and app.session.member_id == member.id then 190.605 + if (not readonly or direct_voter) and not preview then 190.606 + ui.container{ content = function() 190.607 + if not config.enforce_formatting_engine then 190.608 + ui.field.select{ 190.609 + label = _"Wiki engine for statement", 190.610 + name = "formatting_engine", 190.611 + foreign_records = config.formatting_engines, 190.612 + attr = {id = "formatting_engine"}, 190.613 + foreign_id = "id", 190.614 + foreign_name = "name", 190.615 + value = param.get("formatting_engine") or direct_voter and direct_voter.formatting_engine 190.616 + } 190.617 end 190.618 - } 190.619 + ui.container{ content = _"Voting comment (optional)" } 190.620 + ui.field.text{ 190.621 + name = "comment", 190.622 + multiline = true, 190.623 + value = param.get("comment") or direct_voter and direct_voter.comment, 190.624 + attr = { style = "height: 10ex; width: 100%;" }, 190.625 + } 190.626 + end } 190.627 + end 190.628 + 190.629 + if preview then 190.630 + if not config.enforce_formatting_engine then 190.631 + ui.field.hidden{ name = "formatting_engine", value = param.get("formatting_engine") } 190.632 + end 190.633 + ui.field.hidden{ name = "comment", value = param.get("comment") or direct_voter and direct_voter.comment } 190.634 + end 190.635 + 190.636 + if not readonly or direct_voter or preview then 190.637 + if preview then 190.638 + slot.put(" ") 190.639 + ui.tag{ 190.640 + tag = "input", 190.641 + attr = { 190.642 + type = "submit", 190.643 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 190.644 + name = issue.closed and "update_comment" or nil, 190.645 + value = submit_button_text -- finish voting / update comment 190.646 + } 190.647 + } 190.648 + end 190.649 + if not preview then 190.650 + ui.tag{ 190.651 + tag = "input", 190.652 + attr = { 190.653 + type = "submit", 190.654 + name = "preview", 190.655 + class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored", 190.656 + value = _"Preview", 190.657 + } 190.658 + } 190.659 + else 190.660 + slot.put(" ") 190.661 + ui.tag{ 190.662 + tag = "input", 190.663 + attr = { 190.664 + type = "submit", 190.665 + name = "edit", 190.666 + class = "mdl-button mdl-js-button mdl-button--raised", 190.667 + value = edit_button_text, 190.668 + } 190.669 + } 190.670 + end 190.671 end 190.672 end 190.673 end 190.674 } 190.675 - if app.session.member_id and preview then 190.676 - local formatting_engine = param.get("formatting_engine") or config.enforce_formatting_engine 190.677 - local comment = param.get("comment") 190.678 - if comment and #comment > 0 then 190.679 - local rendered_comment = format.wiki_text(comment, formatting_engine) 190.680 - ui.heading{ level = "2", content = _"Voting comment" } 190.681 - ui.container { attr = { class = "member_statement" }, content = function() 190.682 - slot.put(rendered_comment) 190.683 - end } 190.684 - slot.put("<br />") 190.685 - end 190.686 - end 190.687 - if (readonly or direct_voter and direct_voter.comment) and not preview and not (app.session.member_id == member.id) then 190.688 - local text 190.689 - if direct_voter and direct_voter.comment_changed then 190.690 - text = _("Voting comment (last updated: #{timestamp})", { timestamp = format.timestamp(direct_voter.comment_changed) }) 190.691 - elseif direct_voter and direct_voter.comment then 190.692 - text = _"Voting comment" 190.693 - end 190.694 - if text then 190.695 - ui.heading{ level = "2", content = text } 190.696 - end 190.697 - if direct_voter and direct_voter.comment then 190.698 - local rendered_comment = direct_voter:get_content('html') 190.699 - ui.container { attr = { class = "member_statement" }, content = function() 190.700 - slot.put(rendered_comment) 190.701 - end } 190.702 - slot.put("<br />") 190.703 - end 190.704 - end 190.705 - if app.session.member_id and app.session.member_id == member.id then 190.706 - if (not readonly or direct_voter) and not preview then 190.707 - ui.container{ content = function() 190.708 - if not config.enforce_formatting_engine then 190.709 - ui.field.select{ 190.710 - label = _"Wiki engine for statement", 190.711 - name = "formatting_engine", 190.712 - foreign_records = config.formatting_engines, 190.713 - attr = {id = "formatting_engine"}, 190.714 - foreign_id = "id", 190.715 - foreign_name = "name", 190.716 - value = param.get("formatting_engine") or direct_voter and direct_voter.formatting_engine 190.717 - } 190.718 - end 190.719 - ui.heading { level = 2, content = _"Voting comment (optional)" } 190.720 - ui.field.text{ 190.721 - name = "comment", 190.722 - multiline = true, 190.723 - value = param.get("comment") or direct_voter and direct_voter.comment, 190.724 - attr = { style = "height: 10ex; width: 100%;" }, 190.725 + slot.put("<br />") 190.726 + ui.link{ 190.727 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 190.728 + text = _"Cancel", 190.729 + module = "issue", 190.730 + view = "show", 190.731 + id = issue.id 190.732 + } 190.733 + if direct_voter then 190.734 + slot.put(" ") 190.735 + ui.link { 190.736 + attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, 190.737 + module = "vote", action = "update", 190.738 + params = { 190.739 + issue_id = issue.id, 190.740 + discard = true 190.741 + }, 190.742 + routing = { 190.743 + default = { 190.744 + mode = "redirect", 190.745 + module = "issue", 190.746 + view = "show", 190.747 + id = issue.id 190.748 } 190.749 - end } 190.750 - end 190.751 - 190.752 - if preview then 190.753 - if not config.enforce_formatting_engine then 190.754 - ui.field.hidden{ name = "formatting_engine", value = param.get("formatting_engine") } 190.755 - end 190.756 - ui.field.hidden{ name = "comment", value = param.get("comment") or direct_voter and direct_voter.comment } 190.757 - end 190.758 - 190.759 - if not readonly or direct_voter or preview then 190.760 - ui.container{ content = function() 190.761 - if preview then 190.762 - slot.put(" ") 190.763 - ui.tag{ 190.764 - tag = "input", 190.765 - attr = { 190.766 - type = "submit", 190.767 - class = "btn btn-default", 190.768 - name = issue.closed and "update_comment" or nil, 190.769 - value = submit_button_text -- finish voting / update comment 190.770 - } 190.771 - } 190.772 - end 190.773 - if not preview then 190.774 - ui.tag{ 190.775 - tag = "input", 190.776 - attr = { 190.777 - type = "submit", 190.778 - name = "preview", 190.779 - class = "btn btn-default", 190.780 - value = _"Preview", 190.781 - } 190.782 - } 190.783 - else 190.784 - slot.put(" ") 190.785 - ui.tag{ 190.786 - tag = "input", 190.787 - attr = { 190.788 - type = "submit", 190.789 - name = "edit", 190.790 - class = "btn-link", 190.791 - value = edit_button_text, 190.792 - } 190.793 - } 190.794 - end 190.795 - end } 190.796 - end 190.797 + }, 190.798 + text = _"Discard my vote" 190.799 + } 190.800 end 190.801 - end 190.802 - } 190.803 - slot.put("<br />") 190.804 - ui.link{ 190.805 - text = _"Cancel", 190.806 - module = "issue", 190.807 - view = "show", 190.808 - id = issue.id 190.809 - } 190.810 - if direct_voter then 190.811 - slot.put(" | ") 190.812 - ui.link { 190.813 - module = "vote", action = "update", 190.814 - params = { 190.815 - issue_id = issue.id, 190.816 - discard = true 190.817 - }, 190.818 - routing = { 190.819 - default = { 190.820 - mode = "redirect", 190.821 - module = "issue", 190.822 - view = "show", 190.823 - id = issue.id 190.824 - } 190.825 - }, 190.826 - text = _"Discard my vote" 190.827 - } 190.828 - end 190.829 - 190.830 - end ) 190.831 -end ) 190.832 \ No newline at end of file 190.833 + 190.834 + end } 190.835 + end } 190.836 + end } 190.837 +end }
191.1 --- a/app/main/vote/show_incoming.lua Thu Jun 23 03:30:57 2016 +0200 191.2 +++ b/app/main/vote/show_incoming.lua Sun Jul 15 14:07:29 2018 +0200 191.3 @@ -8,6 +8,13 @@ 191.4 issue = Issue:by_id(param.get("issue_id")) 191.5 end 191.6 191.7 +if not issue then 191.8 + execute.view { module = "index", view = "404" } 191.9 + request.set_status("404 Not Found") 191.10 + return 191.11 +end 191.12 + 191.13 + 191.14 if app.session.member_id then 191.15 if initiative then 191.16 initiative:load_everything_for_member_id(app.session.member.id)
192.1 --- a/config/example.lua Thu Jun 23 03:30:57 2016 +0200 192.2 +++ b/config/example.lua Sun Jul 15 14:07:29 2018 +0200 192.3 @@ -88,7 +88,7 @@ 192.4 -- "everything" 192.5 -- -> Show everything a member can see, including profile pages 192.6 -- ------------------------------------------------------------------------ 192.7 -config.public_access = "none" 192.8 +config.public_access = "authors_pseudonymous" 192.9 192.10 192.11 192.12 @@ -483,5 +483,28 @@ 192.13 -- ------------------------------------------------------------------------ 192.14 -- uncomment the following line to enable debug trace 192.15 -- ------------------------------------------------------------------------ 192.16 --- config.enable_debug_trace = true 192.17 +config.enable_debug_trace = true 192.18 + 192.19 + 192.20 +config.fork = { 192.21 + pre =1, min = 1, max = 1, max_requests = 1, min_requests = 1 192.22 +} 192.23 + 192.24 +config.localhost = true 192.25 192.26 +config.oauth2 = { 192.27 + available_scopes = { 192.28 + { scope = "read", name = { de = "Lesen", en = "Read data" } }, 192.29 + { scope = "write", name = { de = "Schreiben", en = "Write data" } }, 192.30 + { scope = "privA", name = { de = "Beispielprivileg A", en = "Example privilege A" } }, 192.31 + { scope = "privB", name = { de = "Beispielprivileg B", en = "Example privilege B" } } 192.32 + }, 192.33 + authorization_code_lifetime = 5 * 60, 192.34 + refresh_token_lifetime = 60 * 60 * 24 * 30 * 3, 192.35 + refresh_pause = 60, 192.36 + refresh_grace_period = 60, 192.37 + access_token_lifetime = 60 * 60, 192.38 + -- NOTE for init.lua : check for refresh_pause >= refresh_grace_period 192.39 + endpoint_magic = "liquidfeedback_client_redirection_endpoint" 192.40 +} 192.41 +
193.1 --- a/env/format/interval_text.lua Thu Jun 23 03:30:57 2016 +0200 193.2 +++ b/env/format/interval_text.lua Sun Jul 15 14:07:29 2018 +0200 193.3 @@ -5,6 +5,8 @@ 193.4 193.5 local options = options or {} 193.6 193.7 + value = value:match("^([^ ]* *[^ ]* *[^ ]* *[^ ]*)") 193.8 + 193.9 value = value:gsub("%..*", "") 193.10 :gsub("days", "{DAYS}") 193.11 :gsub("day", "{DAY}")
194.1 --- a/env/lf4rcs/commit.lua Thu Jun 23 03:30:57 2016 +0200 194.2 +++ b/env/lf4rcs/commit.lua Sun Jul 15 14:07:29 2018 +0200 194.3 @@ -42,7 +42,7 @@ 194.4 local target_node_id = initiative.current_draft.external_reference 194.5 if target_node_id then 194.6 local branch = "i" .. initiative.id 194.7 - lf4rcs[repository].commit(path, exec, branch, target_node_id, close_message, merge_message) 194.8 + config.lf4rcs[repository].commit(path, exec, branch, target_node_id, close_message, merge_message) 194.9 end 194.10 end 194.11 end
195.1 --- a/env/lf4rcs/init.lua Thu Jun 23 03:30:57 2016 +0200 195.2 +++ b/env/lf4rcs/init.lua Sun Jul 15 14:07:29 2018 +0200 195.3 @@ -1,9 +1,7 @@ 195.4 function lf4rcs.init() 195.5 - local super_handler = config.notification_handler_func 195.6 - config.notification_handler_func = function(event) 195.7 - if super_handler then super_handler(event) end 195.8 + Event.add_handler(function(event) 195.9 lf4rcs.notification_handler(event) 195.10 - end 195.11 + end) 195.12 config.render_external_reference = { 195.13 draft = lf4rcs.render_draft_reference, 195.14 initiative = lf4rcs.render_initiative_reference
196.1 --- a/env/model/has_rendered_content.lua Thu Jun 23 03:30:57 2016 +0200 196.2 +++ b/env/model/has_rendered_content.lua Sun Jul 15 14:07:29 2018 +0200 196.3 @@ -1,4 +1,4 @@ 196.4 -function model.has_rendered_content(class, rendered_class, content_field_name) 196.5 +function model.has_rendered_content(class, rendered_class, content_field_name, primary_key) 196.6 196.7 local content_field_name = content_field_name or 'content' 196.8 196.9 @@ -12,6 +12,8 @@ 196.10 for i, key in ipairs(class.primary_key) do 196.11 selector:add_where{ "$ = ?", { key }, self[key] } 196.12 end 196.13 + elseif class.primary_key then 196.14 + selector:add_where{ "$ = ?", { class.primary_key }, self[class.primary_key] } 196.15 else 196.16 selector:add_where{ "id = ?", self.id } 196.17 end 196.18 @@ -24,6 +26,8 @@ 196.19 for i, key in ipairs(class.primary_key) do 196.20 selector:add_where{ "$.$ = ?", { rendered_class.table }, { key }, self[key] } 196.21 end 196.22 + elseif class.primary_key then 196.23 + selector:add_where{ "$." .. primary_key .. " = ?", { rendered_class.table }, self[class.primary_key] } 196.24 else 196.25 selector:add_where{ "$." .. class.table .. "_id = ?", { rendered_class.table }, self.id } 196.26 end 196.27 @@ -43,6 +47,8 @@ 196.28 for i, key in ipairs(class.primary_key) do 196.29 rendered[key] = self[key] 196.30 end 196.31 + elseif class.primary_key then 196.32 + rendered[primary_key] = self[class.primary_key] 196.33 else 196.34 rendered[class.table .. "_id"] = self.id 196.35 end 196.36 @@ -65,6 +71,8 @@ 196.37 for i, key in ipairs(class.primary_key) do 196.38 selector:add_where{ "$.$ = ?", { rendered_class.table }, { key }, self.id } 196.39 end 196.40 + elseif class.primary_key then 196.41 + selector:add_where{ primary_key .. " = ?", self[class.primary_key] } 196.42 else 196.43 selector:add_where{ class.table .. "_id = ?", self.id } 196.44 end 196.45 @@ -79,4 +87,4 @@ 196.46 return rendered.content 196.47 end 196.48 196.49 -end 196.50 \ No newline at end of file 196.51 +end
197.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 197.2 +++ b/env/request/router.lua Sun Jul 15 14:07:29 2018 +0200 197.3 @@ -0,0 +1,41 @@ 197.4 +local api_endpoints = { 197.5 + instance = true, 197.6 + navigation = true, 197.7 + style = true, 197.8 + application = true, 197.9 + info = true, 197.10 + member = true, 197.11 + notify_email = true, 197.12 + profile_info = true, 197.13 + profile = true, 197.14 + settings_info = true, 197.15 + settings = true, 197.16 + event = true 197.17 +} 197.18 + 197.19 +function request.router() 197.20 + 197.21 + local api_prefix = "api/1/" 197.22 + 197.23 + local path = request.get_path() 197.24 + 197.25 + if path == api_prefix .. "register" then 197.26 + return { module = "oauth2", view = "register" } 197.27 + elseif path == api_prefix .. "authorization" then 197.28 + return { module = "oauth2", view = "authorization" } 197.29 + elseif path == api_prefix .. "token" then 197.30 + return { module = "oauth2", view = "token" } 197.31 + elseif path == api_prefix .. "validate" then 197.32 + return { module = "oauth2", view = "validate" } 197.33 + elseif path == api_prefix .. "session" then 197.34 + return { module = "oauth2", view = "session" } 197.35 + else 197.36 + local endpoint = string.match(path, "^" .. api_prefix .. "(.*)$") 197.37 + if api_endpoints[endpoint] then 197.38 + return { module = "api", view = endpoint } 197.39 + end 197.40 + end 197.41 + 197.42 + return request.default_router(path) 197.43 + 197.44 +end
198.1 --- a/env/ui/bargraph.lua Thu Jun 23 03:30:57 2016 +0200 198.2 +++ b/env/ui/bargraph.lua Sun Jul 15 14:07:29 2018 +0200 198.3 @@ -26,7 +26,7 @@ 198.4 if bar.value > 0 then 198.5 at_least_one_bar = true 198.6 local value = bar.value * args.width / args.max_value 198.7 - if quorum and quorum < length + value then 198.8 + if quorum and quorum <= length + value then 198.9 local dlength = math.max(quorum - length - 1, 0) 198.10 local dlength_abs = math.floor(dlength) 198.11 local rest = rest + dlength - dlength_abs
199.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 199.2 +++ b/env/ui/cell_full.lua Sun Jul 15 14:07:29 2018 +0200 199.3 @@ -0,0 +1,6 @@ 199.4 +function ui.cell_full(args) 199.5 + args.attr = args.attr or {} 199.6 + args.attr.class = args.attr.class and args.attr.class .. " " or "" 199.7 + args.attr.class = args.attr.class .. "mdl-cell mdl-cell--12-col" 199.8 + ui.container(args) 199.9 +end
200.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 200.2 +++ b/env/ui/cell_main.lua Sun Jul 15 14:07:29 2018 +0200 200.3 @@ -0,0 +1,6 @@ 200.4 +function ui.cell_main(args) 200.5 + args.attr = args.attr or {} 200.6 + args.attr.class = args.attr.class and args.attr.class .. " " or "" 200.7 + args.attr.class = args.attr.class .. "mdl-cell mdl-cell--12-col mdl-cell--5-col-tablet mdl-cell--8-col-desktop mdl-cell--order-2-tablet mdl-cell--order-2-desktop" 200.8 + ui.container(args) 200.9 +end
201.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 201.2 +++ b/env/ui/cell_sidebar.lua Sun Jul 15 14:07:29 2018 +0200 201.3 @@ -0,0 +1,6 @@ 201.4 +function ui.cell_sidebar(args) 201.5 + args.attr = args.attr or {} 201.6 + args.attr.class = args.attr.class and args.attr.class .. " " or "" 201.7 + args.attr.class = args.attr.class .. "mdl-cell mdl-cell--12-col mdl-cell--3-col-tablet mdl-cell--4-col-desktop mdl-cell--order-1-tablet mdl-cell--order-1-desktop" 201.8 + ui.container(args) 201.9 +end
202.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 202.2 +++ b/env/ui/field/location.lua Sun Jul 15 14:07:29 2018 +0200 202.3 @@ -0,0 +1,33 @@ 202.4 +function ui.field.location(args) 202.5 + if config.map then 202.6 + ui.form_element(args, {fetch_value = true}, function(args) 202.7 + ui.tag{ 202.8 + tag = "input", 202.9 + attr = { type = "hidden", name = args.name, value = args.value, id = "ui_field_location_value" } 202.10 + } 202.11 + end) 202.12 + ui.map({}, "ui_field_location_value") 202.13 + elseif config.firstlife then 202.14 + ui.form_element(args, {fetch_value = true}, function(args) 202.15 + ui.tag{ 202.16 + tag = "input", 202.17 + attr = { type = "hidden", name = args.name, value = args.value, id = "ui_field_location_value" } 202.18 + } 202.19 + ui.tag{ tag = "iframe", attr = { src = config.firstlife.inputmap_url .. "/src/index.html?domain=" .. request.get_absolute_baseurl() .. "&" .. config.firstlife.coordinates .. "&lightArea=false&contrast=false&mode=lite", id = "ui_field_location", class = "ui_field_location" }, content = "" } 202.20 + 202.21 + ui.script{ script = [[ 202.22 + 202.23 + window.addEventListener("message", function (e) { 202.24 + if (e.origin !== "]] .. config.firstlife.inputmap_url .. [[") return; 202.25 + var data = e.data; 202.26 + if (data.src == "InputMap") { 202.27 + var el = document.getElementById("ui_field_location_value"); 202.28 + el.value = JSON.stringify({ "type": "Point", "coordinates": [data.lng, data.lat], "zoom_level": data.zoom_level }); 202.29 + console.log(el.value); 202.30 + } 202.31 + }); 202.32 + 202.33 + ]] } 202.34 + end) 202.35 + end 202.36 +end
203.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 203.2 +++ b/env/ui/field/wysihtml.lua Sun Jul 15 14:07:29 2018 +0200 203.3 @@ -0,0 +1,83 @@ 203.4 +function ui.field.wysihtml(args) 203.5 + 203.6 + local toolbar = { 203.7 + { command = "bold", title ="CTRL+B", icon = "format_bold" }, 203.8 + { command = "italic", title ="CTRL+I", icon = "format_italic" }, 203.9 + { command = "createLink", icon = "insert_link" }, 203.10 + { command = "removeLink", icon = "insert_link", crossed = "\\" }, 203.11 + { command = "formatBlock", command_value = "h1", icon = "title", head_level = "1" }, 203.12 + { command = "formatBlock", command_value = "h2", icon = "title", head_level = "2" }, 203.13 + { command = "formatBlock", command_value = "h3", icon = "title", head_level = "3" }, 203.14 + { command = "formatBlock", command_blank = "true", icon = "format_clear" }, 203.15 + { command = "insertBlockQuote", icon = "format_quote" }, 203.16 + { command = "insertUnorderedList", icon = "format_list_bulleted" }, 203.17 + { command = "insertOrderedList", icon = "format_list_numbered" }, 203.18 + { command = "outdentList", icon = "format_indent_decrease" }, 203.19 + { command = "indentList", icon = "format_indent_increase" }, 203.20 +-- { command = "alignLeftStyle", icon = "format_align_left" }, 203.21 +-- { command = "alignRightStyle", icon = "format_align_right" }, 203.22 +-- { command = "alignCenterStyle", icon = "format_align_center" }, 203.23 + { command = "undo", icon = "undo" }, 203.24 + { command = "redo", icon = "redo" } 203.25 + } 203.26 + 203.27 + slot.put([[ 203.28 + <style> 203.29 + #wysihtml-html-button { 203.30 + padding: 2px; 203.31 + vertical-align: bottom; 203.32 + } 203.33 + #wysihtml-html-button.wysihtml-action-active { 203.34 + color: #fff; 203.35 + background: #000; 203.36 + } 203.37 + </style> 203.38 + ]]) 203.39 + 203.40 + ui.container{ attr = { id = "toolbar", class = "toolbar", style = "display: none;" }, content = function() 203.41 + for i, t in ipairs(toolbar) do 203.42 + ui.tag{ tag = "a", attr = { ["data-wysihtml-command"] = t.command, ["data-wysihtml-command-value"] = t.command_value, ["data-wysihtml-command-blank-value"] = t.command_blank, title = t.shortcut }, content = function() 203.43 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = t.icon } 203.44 + if t.crossed then 203.45 + ui.tag{ attr = { class = "crossed" }, content = t.crossed } 203.46 + end 203.47 + if t.head_level then 203.48 + ui.tag{ attr = { class = "head_level" }, content = t.head_level } 203.49 + end 203.50 + end } 203.51 + end 203.52 + slot.put([[ 203.53 + <div data-wysihtml-dialog="createLink" style="display: none;"> 203.54 + <label> 203.55 + Link: 203.56 + <input data-wysihtml-dialog-field="href" value="http://"> 203.57 + </label> 203.58 + <a data-wysihtml-dialog-action="save">OK</a> <a data-wysihtml-dialog-action="cancel">Cancel</a> 203.59 + </div> 203.60 + ]]) 203.61 + slot.put([[ <a id="wysihtml-html-button" data-wysihtml-action="change_view">]] .. _"expert editor (HTML)" .. [[</a> ]]) 203.62 + end } 203.63 + 203.64 + ui.field.text(args) 203.65 + 203.66 + ui.tag{ tag = "script", attr = { src = request.get_absolute_baseurl() .. "static/wysihtml/wysihtml.js" }, content = "" } 203.67 + ui.tag{ tag = "script", attr = { src = request.get_absolute_baseurl() .. "static/wysihtml/wysihtml.all-commands.js" }, content = "" } 203.68 + ui.tag{ tag = "script", attr = { src = request.get_absolute_baseurl() .. "static/wysihtml/wysihtml.toolbar.js" }, content = "" } 203.69 + ui.tag{ tag = "script", attr = { src = request.get_absolute_baseurl() .. "static/wysihtml/wysihtml_liquidfeedback_rules.js" }, content = "" } 203.70 + ui.script{ script = [[ 203.71 + function initEditor() { 203.72 + var editor = new wysihtml.Editor("]] .. args.attr.id .. [[", { 203.73 + toolbar: "toolbar", 203.74 + parserRules: wysihtmlParserRules, 203.75 + useLineBreaks: true 203.76 + }); 203.77 + } 203.78 + if(window.addEventListener){ 203.79 + window.addEventListener('load', initEditor, false); 203.80 + } else { 203.81 + window.attachEvent('onload', initEditor); 203.82 + } 203.83 + ]] } 203.84 + 203.85 +end 203.86 +
204.1 --- a/env/ui/filters.lua Thu Jun 23 03:30:57 2016 +0200 204.2 +++ b/env/ui/filters.lua Sun Jul 15 14:07:29 2018 +0200 204.3 @@ -1,55 +1,72 @@ 204.4 -function ui.filters(args) 204.5 +local function filters(args) 204.6 + 204.7 local el_id = ui.create_unique_id() 204.8 + local class = "lf-filter" 204.9 + if args.class then 204.10 + class = class .. " " .. args.class 204.11 + end 204.12 ui.container{ 204.13 - attr = { class = "ui_filter" }, 204.14 + attr = { class = { class } }, 204.15 content = function() 204.16 for idx, filter in ipairs(args) do 204.17 local filter_name = filter.name or "filter" 204.18 - local current_option = atom.string:load(request.get_param{ name = filter_name }) 204.19 - if not current_option then 204.20 - current_option = param.get(filter_name) 204.21 + local current_option_name = atom.string:load(request.get_param{ name = filter_name }) 204.22 + if not current_option_name then 204.23 + current_option_name = param.get(filter_name) 204.24 end 204.25 - local current_option_valid = false 204.26 + local current_option = filter[1] 204.27 for idx, option in ipairs(filter) do 204.28 - if current_option == option.name then 204.29 - current_option_valid = true 204.30 + if current_option_name == option.name then 204.31 + current_option = option 204.32 end 204.33 end 204.34 - if not current_option or #current_option == 0 or not current_option_valid then 204.35 - current_option = filter[1].name 204.36 + if not current_option_name or #current_option_name == 0 or not current_option then 204.37 + current_option_name = filter[1].name 204.38 end 204.39 + ui.tag{ tag = "button", attr = { id = "filter-" .. filter_name .. "-menu", class = "mdl-button mdl-js-button" }, content = function() 204.40 + ui.tag{ content = current_option.label } 204.41 + slot.put(" ") 204.42 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "arrow_drop_down" } 204.43 + end } 204.44 local id = request.get_id_string() 204.45 local params = request.get_param_strings() 204.46 - local class = "ui_filter_head" 204.47 + local class = "mdl-menu mdl-js-menu mdl-js-ripple-effect" 204.48 if filter.class then 204.49 class = class .. " " .. filter.class 204.50 end 204.51 - ui.container{ 204.52 - attr = { class = class }, 204.53 + ui.tag{ 204.54 + tag = "ul", 204.55 + attr = { class = class, ["data-mdl-for"] = "filter-" .. filter_name .. "-menu" }, 204.56 content = function() 204.57 - slot.put(filter.label) 204.58 for idx, option in ipairs(filter) do 204.59 params[filter_name] = option.name 204.60 local attr = {} 204.61 - if current_option == option.name then 204.62 - attr.class = "active" 204.63 + attr.class = "mdl-menu__link" 204.64 + if current_option_name == option.name then 204.65 + attr.class = attr.class .. " active" 204.66 option.selector_modifier(args.selector) 204.67 end 204.68 if idx > 1 then 204.69 slot.put(" ") 204.70 end 204.71 - ui.link{ 204.72 - attr = attr, 204.73 - module = request.get_module(), 204.74 - view = request.get_view(), 204.75 - id = id, 204.76 - params = params, 204.77 - text = option.label, 204.78 - partial = { 204.79 - params = { 204.80 - [filter_name] = option.name 204.81 + ui.tag{ 204.82 + tag = "li", 204.83 + attr = { class = "mdl-menu__item" }, 204.84 + content = function() 204.85 + ui.link{ 204.86 + attr = attr, 204.87 + module = request.get_module(), 204.88 + view = request.get_view(), 204.89 + id = id, 204.90 + params = params, 204.91 + content = option.label, 204.92 + partial = { 204.93 + params = { 204.94 + [filter_name] = idx > 1 and option.name or nil 204.95 + } 204.96 + } 204.97 } 204.98 - } 204.99 + end 204.100 } 204.101 end 204.102 end 204.103 @@ -57,6 +74,16 @@ 204.104 end 204.105 end 204.106 } 204.107 +end 204.108 + 204.109 +function ui.filters(args) 204.110 + if args.slot then 204.111 + slot.select(args.slot, function() 204.112 + filters(args) 204.113 + end) 204.114 + else 204.115 + filters(args) 204.116 + end 204.117 ui.container{ 204.118 attr = { class = "ui_filter_content" }, 204.119 content = function()
205.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 205.2 +++ b/env/ui/grid.lua Sun Jul 15 14:07:29 2018 +0200 205.3 @@ -0,0 +1,6 @@ 205.4 +function ui.grid(args) 205.5 + args.attr = args.attr or {} 205.6 + args.attr.class = args.attr.class and args.attr.class .. " " or "" 205.7 + args.attr.class = args.attr.class .. "mdl-grid" 205.8 + ui.container(args) 205.9 +end
206.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 206.2 +++ b/env/ui/map.lua Sun Jul 15 14:07:29 2018 +0200 206.3 @@ -0,0 +1,16 @@ 206.4 +function ui.map(geo_objects, input_element_id) 206.5 + local header = config.map.header 206.6 + if type(header) == "function" then 206.7 + header = header() 206.8 + end 206.9 + slot.put_into("html_head", header) 206.10 + ui.container{ attr = { id = "map" }, content = "" } 206.11 + config.map.func( 206.12 + "map", 206.13 + config.map.default_viewport.lon, 206.14 + config.map.default_viewport.lat, 206.15 + config.map.default_viewport.zoom, 206.16 + geo_objects, 206.17 + input_element_id 206.18 + ) 206.19 +end
207.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 207.2 +++ b/env/ui/supporter_count.lua Sun Jul 15 14:07:29 2018 +0200 207.3 @@ -0,0 +1,22 @@ 207.4 +function ui.supporter_count(initiative) 207.5 + if initiative.supporter_count == nil then 207.6 + ui.tag { 207.7 + attr = { class = "supporterCount" }, 207.8 + content = _"[calculating]" 207.9 + } 207.10 + elseif initiative.issue.closed == nil then 207.11 + ui.tag { 207.12 + attr = { class = "satisfiedSupporterCount" }, 207.13 + content = _("#{count} supporter", { count = initiative.satisfied_supporter_count }) 207.14 + } 207.15 + if initiative.potential_supporter_count and 207.16 + initiative.potential_supporter_count > 0 207.17 + then 207.18 + slot.put ( " " ) 207.19 + ui.tag { 207.20 + attr = { class = "potentialSupporterCount" }, 207.21 + content = _("(+ #{count} potential)", { count = initiative.potential_supporter_count }) 207.22 + } 207.23 + end 207.24 + end 207.25 +end
208.1 --- a/env/ui/title.lua Thu Jun 23 03:30:57 2016 +0200 208.2 +++ b/env/ui/title.lua Sun Jul 15 14:07:29 2018 +0200 208.3 @@ -7,10 +7,7 @@ 208.4 module = "index", view = "index", 208.5 attr = { class = "home", title = _"Home" }, 208.6 content = function () 208.7 - ui.image { 208.8 - attr = { class = "icon24", alt = title }, 208.9 - static = "icons/48/home.png" 208.10 - } 208.11 + ui.tag{ tag = "i", attr = { class = "material-icons" }, content = "home" } 208.12 end 208.13 } 208.14 208.15 @@ -18,12 +15,12 @@ 208.16 ui.tag { attr = { class = "spacer" }, content = function() 208.17 slot.put ( " » " ) 208.18 end } 208.19 - ui.tag { tag = "span", content = content } 208.20 + ui.tag { content = content } 208.21 else 208.22 ui.tag { attr = { class = "spacer" }, content = function() 208.23 slot.put ( " " ) 208.24 end } 208.25 - ui.tag { tag = "span", content = _"Home" } 208.26 + ui.tag { content = _"Home" } 208.27 end 208.28 208.29 end )
209.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 209.2 +++ b/env/util/api_error.lua Sun Jul 15 14:07:29 2018 +0200 209.3 @@ -0,0 +1,12 @@ 209.4 +function util.api_error(status_code, status_text, error_code, error_description) 209.5 + slot.set_layout(nil, "application/json") 209.6 + request.set_status(status_code, status_text) 209.7 + if status_code == 401 then 209.8 + request.add_header("WWW-Authenticate", "Bearer error=\"" .. error_code .. "\", error_description=\"" .. error_description .. "\"") 209.9 + end 209.10 + slot.put_into("data", json.export(json.object{ 209.11 + error = error_code, 209.12 + error_description = error_description 209.13 + })) 209.14 + return false 209.15 +end
210.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 210.2 +++ b/env/util/get_access_token.lua Sun Jul 15 14:07:29 2018 +0200 210.3 @@ -0,0 +1,16 @@ 210.4 +function util.get_access_token() 210.5 + 210.6 + local access_token_header = request.get_header("Authorization") 210.7 + if access_token_header then 210.8 + access_token_header = string.match(access_token_header, "^Bearer ([^ ,]*)$") 210.9 + end 210.10 + 210.11 + local access_token_param = param.get("access_token") 210.12 + 210.13 + if access_token_header and access_token_param then 210.14 + return nil, "header_and_param" 210.15 + end 210.16 + 210.17 + return(access_token_header or access_token_param) 210.18 + 210.19 +end
211.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 211.2 +++ b/env/util/html_is_safe.lua Sun Jul 15 14:07:29 2018 +0200 211.3 @@ -0,0 +1,191 @@ 211.4 +function util.html_is_safe(str) 211.5 + 211.6 + -- All (ASCII) control characters except \t\n\f\r are forbidden: 211.7 + if string.find(str, "[\0-\8\11\14-\31\127]") then 211.8 + return false, "Invalid ASCII control character" 211.9 + end 211.10 + 211.11 + -- Memorize expected closing tags: 211.12 + local stack = {} 211.13 + 211.14 + -- State during parsing: 211.15 + local para = false -- <p> tag open 211.16 + local bold = false -- <b> tag open 211.17 + local italic = false -- <i> tag open 211.18 + local supsub = false -- <sup> or <sub> tag open 211.19 + local link = false -- <a href="..."> tag open 211.20 + local heading = false -- <h1-6> tag open 211.21 + local list = false -- <ol> or <ul> (but no corresponding <li>) tag open 211.22 + local listelm = false -- <li> tag (but no further <ol> or <ul> tag) open 211.23 + 211.24 + -- Function looped with tail-calls: 211.25 + local function loop(str) 211.26 + 211.27 + -- NOTE: We do not allow non-escaped "<" or ">" in attributes, 211.28 + -- even if HTML5 allows it. 211.29 + 211.30 + -- Find any "<" or ">" character and determine context, i.e. 211.31 + -- pre = text before character, tag = text until closing ">", and rest: 211.32 + local pre, tag, rest = string.match(str, "^(.-)([<>][^<>]*>?)(.*)") 211.33 + 211.34 + -- Disallow text content (except inter-element white-space) in <ol> or <ul> 211.35 + -- when outside <li>: 211.36 + if list and string.find(pre, "[^\t\n\f\r ]") then 211.37 + return false, "Text content in list but outside list element" 211.38 + end 211.39 + 211.40 + -- If no more "<" or ">" characters are found, 211.41 + -- then return true if all tags have been closed: 211.42 + if not tag then 211.43 + if #stack == 0 then 211.44 + return true 211.45 + else 211.46 + return false, "Not all tags have been closed" 211.47 + end 211.48 + end 211.49 + 211.50 + -- Handle (expected) closing tags: 211.51 + local closed_tagname = string.match(tag, "^</(.-)[\t\n\f\r ]*>$") 211.52 + if closed_tagname then 211.53 + closed_tagname = string.lower(closed_tagname) 211.54 + if closed_tagname ~= stack[#stack] then 211.55 + return false, "Wrong closing tag" 211.56 + end 211.57 + if closed_tagname == "p" then 211.58 + para = false 211.59 + elseif closed_tagname == "b" then 211.60 + bold = false 211.61 + elseif closed_tagname == "i" then 211.62 + italic = false 211.63 + elseif closed_tagname == "sup" or closed_tagname == "sub" then 211.64 + supsub = false 211.65 + elseif closed_tagname == "a" then 211.66 + link = false 211.67 + elseif string.find(closed_tagname, "^h[1-6]$") then 211.68 + heading = false 211.69 + elseif closed_tagname == "ul" or closed_tagname == "ol" then 211.70 + list = false 211.71 + elseif closed_tagname == "li" then 211.72 + listelm = false 211.73 + list = true 211.74 + end 211.75 + stack[#stack] = nil 211.76 + return loop(rest) 211.77 + end 211.78 + 211.79 + -- Allow <br> tag as void tag: 211.80 + if string.find(tag, "^<[Bb][Rr][\t\n\f\r ]*/?>$") then 211.81 + return loop(rest) 211.82 + end 211.83 + 211.84 + -- Parse opening tag: 211.85 + local tagname, attrs = string.match( 211.86 + tag, 211.87 + "^<([^<>\0-\32]+)[\t\n\f\r ]*([^<>]-)[\t\n\f\r ]*>$" 211.88 + ) 211.89 + 211.90 + -- Return false if tag could not be parsed: 211.91 + if not tagname then 211.92 + return false, "Malformed tag" 211.93 + end 211.94 + 211.95 + -- Make tagname lowercase: 211.96 + tagname = string.lower(tagname) 211.97 + 211.98 + -- Append closing tag to list of expected closing tags: 211.99 + stack[#stack+1] = tagname 211.100 + 211.101 + -- Allow <li> tag in proper context: 211.102 + if tagname == "li" and attrs == "" then 211.103 + if not list then 211.104 + return false, "List element outside list" 211.105 + end 211.106 + list = false 211.107 + listelm = true 211.108 + return loop(rest) 211.109 + end 211.110 + 211.111 + -- If there was no valid <li> tag but <ol> or <ul> is open, 211.112 + -- then return false: 211.113 + if list then 211.114 + return false 211.115 + end 211.116 + 211.117 + -- Allow <b>, <i>, <sup>, <sub> unless already open: 211.118 + if tagname == "b" and attrs == "" then 211.119 + if bold then 211.120 + return false, "Bold inside bold tag" 211.121 + end 211.122 + bold = true 211.123 + return loop(rest) 211.124 + end 211.125 + if tagname == "i" and attrs == "" then 211.126 + if italic then 211.127 + return false, "Italic inside italic tag" 211.128 + end 211.129 + italic = true 211.130 + return loop(rest) 211.131 + end 211.132 + if (tagname == "sup" or tagname == "sub") and attrs == "" then 211.133 + if supsub then 211.134 + return false, "Super/subscript inside super/subscript tag" 211.135 + end 211.136 + supsub = true 211.137 + return loop(rest) 211.138 + end 211.139 + 211.140 + -- Allow <a href="..."> tag unless already open or malformed: 211.141 + if tagname == "a" then 211.142 + if link then 211.143 + return false, "Link inside link" 211.144 + end 211.145 + local url = string.match(attrs, '^[Hh][Rr][Ee][Ff][\t\n\f\r ]*=[\t\n\f\r ]*"([^"]*)"$') 211.146 + if not url then 211.147 + url = string.match(attrs, "^[Hh][Rr][Ee][Ff][\t\n\f\r ]*=[\t\n\f\r ]*'([^']*)'$") 211.148 + end 211.149 + if not url then 211.150 + url = string.match(attrs, "^[Hh][Rr][Ee][Ff][\t\n\f\r ]*=[\t\n\f\r ]*([^\0-\32\"'=<>`]+)$") 211.151 + end 211.152 + if not url then 211.153 + return false, "Forbidden, missing, or malformed attributes in link tag" 211.154 + end 211.155 + if not string.find(url, "^[Hh][Tt][Tt][Pp][Ss]?://") then 211.156 + return false, "Invalid link URL" 211.157 + end 211.158 + link = true 211.159 + return loop(rest) 211.160 + end 211.161 + 211.162 + -- Remaining tags require no open <p>, <b>, <i>, <sup>, <sub>, 211.163 + -- <a href="...">, or <h1>..</h6> tag: 211.164 + if para or bold or italic or supsub or link or heading then 211.165 + return false, "Forbidden child tag within paragraph, bold, italic, super/subscript, link, or heading tag" 211.166 + end 211.167 + 211.168 + -- Allow <p>: 211.169 + if tagname == "p" and attrs == "" then 211.170 + para = true 211.171 + return loop(rest) 211.172 + end 211.173 + 211.174 + -- Allow <h1>..<h6>: 211.175 + if string.find(tagname, "^h[1-6]$") and attrs == "" then 211.176 + heading = true 211.177 + return loop(rest) 211.178 + end 211.179 + 211.180 + -- Allow <ul> and <ol>: 211.181 + if (tagname == "ul" or tagname == "ol") and attrs == "" then 211.182 + list = true 211.183 + return loop(rest) 211.184 + end 211.185 + 211.186 + -- Disallow all others (including unexpected closing tags): 211.187 + return false, "Forbidden tag or forbidden attributes" 211.188 + 211.189 + end 211.190 + 211.191 + -- Invoke tail-call loop: 211.192 + return loop(str) 211.193 + 211.194 +end
212.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 212.2 +++ b/env/util/html_to_text.lua Sun Jul 15 14:07:29 2018 +0200 212.3 @@ -0,0 +1,86 @@ 212.4 +function util.html_to_text(str) 212.5 + str = string.gsub(str, "[\0-\32]", " ") 212.6 + str = string.gsub(str, "<[Bb][Rr] */?>", "\n") 212.7 + str = string.gsub(str, "</?[Pp] *>", "\n\n") 212.8 + str = string.gsub(str, "</?[Bb] *>", "**") 212.9 + str = string.gsub(str, "</?[Ii] *>", "//") 212.10 + str = string.gsub(str, "</?[Ss][Uu][Bb] *>", "__") 212.11 + str = string.gsub(str, "</?[Ss][Uu][Pp] *>", "^^") 212.12 + str = string.gsub(str, '<[Aa] *[Hh][Rr][Ee][Ff] *= *"([^"]*)" *>', "[[%1 ") 212.13 + str = string.gsub(str, "<[Aa] *[Hh][Rr][Ee][Ff] *= *'([^']*)' *>", "[[%1 ") 212.14 + str = string.gsub(str, "<[Aa] *[Hh][Rr][Ee][Ff] *= *([^ <>\"']*) *>", "[[%1 ") 212.15 + str = string.gsub(str, "</[Aa] *>", "]]") 212.16 + str = string.gsub(str, "<[Hh]1 *>", "\n\n###### ") 212.17 + str = string.gsub(str, "<[Hh]2 *>", "\n\n##### ") 212.18 + str = string.gsub(str, "<[Hh]3 *>", "\n\n#### ") 212.19 + str = string.gsub(str, "<[Hh]4 *>", "\n\n### ") 212.20 + str = string.gsub(str, "<[Hh]5 *>", "\n\n## ") 212.21 + str = string.gsub(str, "<[Hh]6 *>", "\n\n# ") 212.22 + str = string.gsub(str, "</[Hh]1 *>", " ######\n\n") 212.23 + str = string.gsub(str, "</[Hh]2 *>", " #####\n\n") 212.24 + str = string.gsub(str, "</[Hh]3 *>", " ####\n\n") 212.25 + str = string.gsub(str, "</[Hh]4 *>", " ###\n\n") 212.26 + str = string.gsub(str, "</[Hh]5 *>", " ##\n\n") 212.27 + str = string.gsub(str, "</[Hh]6 *>", " #\n\n") 212.28 + local li_info = {} 212.29 + local pos = 1 212.30 + local counters = {} 212.31 + while true do 212.32 + local list_start, list_stop, list_tagname = string.find(str, "<(/?[OoUu]l) *>", pos) 212.33 + if list_tagname then 212.34 + list_tagname = string.lower(list_tagname) 212.35 + end 212.36 + local elem_start, elem_stop = string.find(str, "<[Ll][Ii] *>", pos) 212.37 + if list_start and not elem_start then 212.38 + pos = list_stop 212.39 + elseif elem_start and not list_start then 212.40 + pos = elem_stop 212.41 + elseif list_start and elem_start then 212.42 + if list_start < elem_start then 212.43 + pos = list_stop 212.44 + else 212.45 + pos = elem_stop 212.46 + list_tagname = nil 212.47 + end 212.48 + else 212.49 + break 212.50 + end 212.51 + if list_tagname == "ol" then 212.52 + counters[#counters+1] = 0 212.53 + elseif list_tagname == "ul" then 212.54 + counters[#counters+1] = false 212.55 + elseif list_tagname then 212.56 + counters[#counters] = nil 212.57 + else 212.58 + if counters[#counters] then 212.59 + counters[#counters] = counters[#counters] + 1 212.60 + end 212.61 + local string_parts = {} 212.62 + for idx, counter in ipairs(counters) do 212.63 + if counter then 212.64 + string_parts[idx] = tostring(counter) .. ". " 212.65 + else 212.66 + string_parts[idx] = "* " 212.67 + end 212.68 + end 212.69 + li_info[#li_info+1] = table.concat(string_parts) 212.70 + end 212.71 + end 212.72 + str = string.gsub(str, "</?[OoUu]l *>", "\n\n") 212.73 + local li_index = 0 212.74 + str = string.gsub(str, "<[Ll][Ii] *>", function() 212.75 + li_index = li_index + 1 212.76 + return li_info[li_index] 212.77 + end) 212.78 + str = string.gsub(str, "</[Ll][Ii] *>", "\n") 212.79 + str = string.gsub(str, "<[^<>]*>", "") 212.80 + str = string.gsub(str, "<", "<") 212.81 + str = string.gsub(str, ">", ">") 212.82 + str = string.gsub(str, " +", " ") 212.83 + str = string.gsub(str, "%f[^\0\n] ", "") 212.84 + str = string.gsub(str, " %f[\0\n]", "") 212.85 + str = string.gsub(str, "\n\n\n+", "\n\n") 212.86 + str = string.gsub(str, "^\n+", "") 212.87 + str = string.gsub(str, "\n*$", "\n") 212.88 + return str 212.89 +end
213.1 --- a/env/util/initiative_pie.lua Thu Jun 23 03:30:57 2016 +0200 213.2 +++ b/env/util/initiative_pie.lua Sun Jul 15 14:07:29 2018 +0200 213.3 @@ -10,7 +10,7 @@ 213.4 local first_preference_votes = initiative.first_preference_votes 213.5 213.6 local d = d or 100 213.7 - local gap = gap or d / 20 213.8 + local gap = 2 213.9 213.10 local r = d/2 213.11 local r_circle = r - gap 213.12 @@ -131,4 +131,4 @@ 213.13 offset = - offset 213.14 } 213.15 213.16 -end 213.17 \ No newline at end of file 213.18 +end
214.1 --- a/env/util/is_profile_field_locked.lua Thu Jun 23 03:30:57 2016 +0200 214.2 +++ b/env/util/is_profile_field_locked.lua Sun Jul 15 14:07:29 2018 +0200 214.3 @@ -7,10 +7,16 @@ 214.4 return true 214.5 end 214.6 end 214.7 - 214.8 + 214.9 + if member.authority and string.sub(member.authority, 1, 7) == "oauth2_" then 214.10 + if field_name == "login" or field_name == "password" or field_name == "notify_email" then 214.11 + return true 214.12 + end 214.13 + end 214.14 + 214.15 if config.locked_profile_fields[field_name] then 214.16 return true 214.17 end 214.18 214.19 return false 214.20 -end 214.21 \ No newline at end of file 214.22 +end
215.1 --- a/env/util/micro_avatar.lua Thu Jun 23 03:30:57 2016 +0200 215.2 +++ b/env/util/micro_avatar.lua Sun Jul 15 14:07:29 2018 +0200 215.3 @@ -11,7 +11,7 @@ 215.4 ui.image{ 215.5 attr = { 215.6 title = member.name, 215.7 - class = "microAvatar" 215.8 + class = "mdl-chip__contact" 215.9 }, 215.10 external = config.fastpath_url_func(member.id, "avatar") 215.11 } 215.12 @@ -19,7 +19,7 @@ 215.13 ui.image { 215.14 attr = { 215.15 title = member.name, 215.16 - class = "microAvatar" 215.17 + class = "mdl-chip__contact" 215.18 }, 215.19 module = "member_image", 215.20 view = "show", 215.21 @@ -30,7 +30,7 @@ 215.22 } 215.23 } 215.24 end 215.25 - ui.tag { tag = "span", content = member.name } 215.26 + ui.tag { attr = { class = "mdl-chip__text" }, content = member.name } 215.27 end 215.28 215.29 ui.tag { 215.30 @@ -38,11 +38,15 @@ 215.31 content = function () 215.32 if app.session:has_access("everything") then 215.33 ui.link { 215.34 + attr = { class = "mdl-chip mdl-chip--contact" }, 215.35 module = "member", view = "show", id = member.id, 215.36 content = doit 215.37 } 215.38 else 215.39 - ui.tag{ content = doit } 215.40 + ui.tag{ 215.41 + attr = { class = "mdl-chip mdl-chip--contact" }, 215.42 + content = doit 215.43 + } 215.44 end 215.45 end 215.46 }
216.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 216.2 +++ b/env/util/scope_name.lua Sun Jul 15 14:07:29 2018 +0200 216.3 @@ -0,0 +1,9 @@ 216.4 +function util.scope_name(scope) 216.5 + local name 216.6 + for i, entry in ipairs(config.oauth2.available_scopes) do 216.7 + if entry.scope == scope then 216.8 + name = entry.name[locale.get("lang")] or entry.scope 216.9 + end 216.10 + end 216.11 + return name 216.12 +end
217.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 217.2 +++ b/env/util/wysihtml_preproc.lua Sun Jul 15 14:07:29 2018 +0200 217.3 @@ -0,0 +1,12 @@ 217.4 +local function normalize_whitespace(str) 217.5 + str = string.gsub(str, "\194\160", " ") 217.6 + str = string.gsub(str, " ", " ") 217.7 + return str 217.8 +end 217.9 + 217.10 +function util.wysihtml_preproc(str) 217.11 + str = string.gsub(str, "<a>(.-)</a>", "%1") 217.12 + str = string.gsub(str, "<[ou]l>[^<>]*", normalize_whitespace) 217.13 + str = string.gsub(str, "</li>[^<>]*", normalize_whitespace) 217.14 + return str 217.15 +end
218.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 218.2 +++ b/lib/ontomap/ontomap.lua Sun Jul 15 14:07:29 2018 +0200 218.3 @@ -0,0 +1,382 @@ 218.4 +function _G.ontomap_get_instances(event) 218.5 + if true then return {} end 218.6 + local url = config.ontomap.base_url .. "instances/Issue?geometries=true&descriptions=true" 218.7 + print("OnToMap>>") 218.8 + 218.9 + local output, err, status = extos.pfilter(doc, "curl", "-X", "GET", "-H", "Content-Type: application/json", "--cert", config.ontomap.client_cert_file, "-d", "@-", url) 218.10 + print(output) 218.11 + 218.12 + local data = json.import(output) 218.13 + 218.14 + if not data then 218.15 + return {} 218.16 + end 218.17 + 218.18 + if data.type ~= "FeatureCollection" or not data.features then 218.19 + return {} 218.20 + end 218.21 + 218.22 + local instances = {} 218.23 + for i, feature in ipairs(data.features) do 218.24 + if feature.geometry then 218.25 + table.insert(instances, { 218.26 + application = feature.applicationName, 218.27 + title = feature.hasName, 218.28 + description = slot.use_temporary(function() 218.29 + ui.link{ external = feature.properties.external_url, text = feature.properties.hasName or "" } 218.30 + ui.container{ content = feature.hasDescription } 218.31 + end), 218.32 + lon = feature.geometry.coordinates[1], 218.33 + lat = feature.geometry.coordinates[2], 218.34 + label = "IMC" .. feature.properties.hasID, 218.35 + type = "Improve My City" 218.36 + }) 218.37 + print(feature.applicationName, feature.properties.hasName, feature.properties.hasDescription) 218.38 + end 218.39 + end 218.40 + return instances 218.41 + 218.42 +end 218.43 + 218.44 + 218.45 +local function new_log_event(event, actor, activity_type) 218.46 + local e = json.object{ 218.47 + activity_type = activity_type, 218.48 + activity_objects = json.array(), 218.49 + references = json.array(), 218.50 + visibility_details = json.array(), 218.51 + details = json.object() 218.52 + } 218.53 + e.actor = actor 218.54 + e.timestamp = math.floor(event.occurrence_epoch * 1000) 218.55 + return e 218.56 +end 218.57 + 218.58 +local function new_activity_object(attr) 218.59 + return json.object{ 218.60 + type = "Feature", 218.61 + geometry = attr.geometry or json.null, 218.62 + properties = json.object{ 218.63 + hasType = attr.type, 218.64 + external_url = attr.url 218.65 + } 218.66 + } 218.67 +end 218.68 + 218.69 +local function new_reference_object(url, application) 218.70 + return json.object{ 218.71 + application = application or config.ontomap.application_ident, 218.72 + external_url = url 218.73 + } 218.74 +end 218.75 + 218.76 +local function log_to_ontomap(log_events) 218.77 + if json.type(log_events) == "object" then 218.78 + log_events = json.array{ log_events } 218.79 + end 218.80 + for i, log_event in ipairs(log_events) do 218.81 + if #(log_event.activity_objects) == 0 then 218.82 + log_event.activity_objects = nil 218.83 + end 218.84 + if #(log_event.references) == 0 then 218.85 + log_event.references = nil 218.86 + end 218.87 + if #(log_event.visibility_details) == 0 then 218.88 + log_event.visibility_details = nil 218.89 + end 218.90 + if not (next(log_event.details)) then -- TODO 218.91 + log_event.details = nil 218.92 + end 218.93 + end 218.94 + 218.95 + local doc = json.export(json.object{ 218.96 + event_list = log_events 218.97 + }, " ") 218.98 + 218.99 + local url = config.ontomap.base_url .. "logger/events" 218.100 + print("OnToMap<<") 218.101 + print(doc) 218.102 + 218.103 + local output, err, status = extos.pfilter(doc, "curl", "-X", "POST", "-H", "Content-Type: application/json", "--cert", config.ontomap.client_cert_file, "-d", "@-", url) 218.104 + 218.105 + print("---------") 218.106 + print(output) 218.107 + print("---------") 218.108 + 218.109 + if err then 218.110 + -- TODO log error 218.111 + end 218.112 +end 218.113 + 218.114 +local function url_for(relation, id) 218.115 + return config.absolute_base_url .. relation .. "/show/" .. id .. ".html" 218.116 +end 218.117 + 218.118 + 218.119 +local function unit_updated(event, event_type) 218.120 + local log_event = new_log_event(event, 0, event_type == "created" and "object_created" or "object_updated") 218.121 + table.insert(log_event.activity_objects, new_activity_object{ 218.122 + type = "unit", 218.123 + url = url_for("unit", event.unit_id) 218.124 + }) 218.125 + log_event.details.active = event.unit.active 218.126 + log_event.details.name = event.unit.name 218.127 + log_event.details.description = event.unit.description 218.128 + log_event.details.external_reference = event.unit.external_reference 218.129 + log_event.details.region = event.unit.region 218.130 + log_to_ontomap(log_event) 218.131 +end 218.132 + 218.133 +local function area_updated(event, event_type) 218.134 + local log_event = new_log_event(event, 0, event_type == "created" and "object_created" or "object_updated") 218.135 + table.insert(log_event.activity_objects, new_activity_object{ 218.136 + type = "area", 218.137 + url = url_for("area", event.area_id) 218.138 + }) 218.139 + table.insert(log_event.references, new_reference_object( 218.140 + url_for("unit", event.area.unit_id) 218.141 + )) 218.142 + log_event.details.active = event.area.active 218.143 + log_event.details.name = event.area.name 218.144 + log_event.details.description = event.area.description 218.145 + log_event.details.external_reference = event.area.external_reference 218.146 + log_event.details.region = event.area.region 218.147 + log_to_ontomap(log_event) 218.148 +end 218.149 + 218.150 +local function policy_updated(event, event_type) 218.151 + local log_event = new_log_event(event, 0, event_type == "created" and "object_created" or "object_updated") 218.152 + table.insert(log_event.activity_objects, new_activity_object{ 218.153 + type = "policy", 218.154 + url = url_for("policy", event.policy_id) 218.155 + }) 218.156 + log_event.details.active = event.policy.active 218.157 + log_event.details.name = event.policy.name 218.158 + log_event.details.description = event.policy.description 218.159 + log_to_ontomap(log_event) 218.160 +end 218.161 + 218.162 +local mapper = { 218.163 + 218.164 + unit_created = function(event) 218.165 + unit_updated(event, "created") 218.166 + end, 218.167 + 218.168 + unit_updated = function(event) 218.169 + unit_updated(event, "updated") 218.170 + end, 218.171 + 218.172 + area_created = function(event) 218.173 + area_updated(event, "created") 218.174 + end, 218.175 + 218.176 + area_updated = function(event) 218.177 + area_updated(event, "updated") 218.178 + end, 218.179 + 218.180 + policy_created = function(event) 218.181 + policy_updated(event, "created") 218.182 + end, 218.183 + 218.184 + policy_updated = function(event) 218.185 + policy_updated(event, "updated") 218.186 + end, 218.187 + 218.188 + issue_state_changed = function(event) 218.189 + local log_event = new_log_event(event, 0, "issue_status_updated") 218.190 + table.insert(log_event.references, new_reference_object( 218.191 + url_for("issue", event.issue_id) 218.192 + )) 218.193 + log_event.details.new_issue_state = event.state 218.194 + log_to_ontomap(log_event) 218.195 + end, 218.196 + 218.197 + initiative_created_in_new_issue = function(event) 218.198 + local log_events = json.array() 218.199 + 218.200 + local log_event = new_log_event(event, 0, "object_created") 218.201 + table.insert(log_event.activity_objects, new_activity_object{ 218.202 + type = "issue", 218.203 + url = url_for("issue", event.issue_id) 218.204 + }) 218.205 + table.insert(log_event.references, new_reference_object( 218.206 + url_for("policy", event.issue.policy_id) 218.207 + )) 218.208 + log_event.details.new_issue_state = event.state 218.209 + table.insert(log_events, log_event) 218.210 + 218.211 + local log_event = new_log_event(event, event.member_id, "object_created") 218.212 + table.insert(log_event.activity_objects, new_activity_object{ 218.213 + type = "initiative", 218.214 + url = url_for("initiative", event.initiative_id), 218.215 + geometry = event.initiative.location 218.216 + }) 218.217 + table.insert(log_event.references, new_reference_object( 218.218 + url_for("issue", event.issue_id) 218.219 + )) 218.220 + log_event.details.name = event.initiative.name 218.221 + table.insert(log_events, log_event) 218.222 + 218.223 + log_to_ontomap(log_events) 218.224 + end, 218.225 + 218.226 + initiative_created_in_existing_issue = function(event) 218.227 + local log_event = new_log_event(event, event.member_id, "object_created") 218.228 + table.insert(log_event.activity_objects, new_activity_object{ 218.229 + type = "initiative", 218.230 + url = url_for("initiative", event.initiative_id), 218.231 + geometry = event.initiative.location 218.232 + }) 218.233 + table.insert(log_event.references, new_reference_object( 218.234 + url_for("issue", event.issue_id) 218.235 + )) 218.236 + log_event.details.name = event.initiative.name 218.237 + log_to_ontomap(log_event) 218.238 + end, 218.239 + 218.240 + initiative_revoked = function(event) 218.241 + -- TODO -> which activity? 218.242 + end, 218.243 + 218.244 + new_draft_created = function(event) 218.245 + local log_event = new_log_event(event, event.member_id, "object_updated") 218.246 + table.insert(log_event.activity_objects, new_activity_object{ 218.247 + type = "initiative", 218.248 + url = url_for("initiative", event.issue_id) 218.249 + }) 218.250 + table.insert(log_event.references, new_reference_object( 218.251 + url_for("issue", event.issue_id) 218.252 + )) 218.253 + log_event.details.name = event.initiative.name 218.254 + log_event.details.location = event.initiative.current_draft.location 218.255 + log_to_ontomap(log_event) 218.256 + end, 218.257 + 218.258 + interest = function(event) 218.259 + local activity_type = event.boolean_value and "interest_added" or "interest_removed" 218.260 + local log_event = new_log_event(event, event.member_id, activity_type) 218.261 + table.insert(log_event.references, new_reference_object( 218.262 + url_for("issue", event.issue_id) 218.263 + )) 218.264 + log_to_ontomap(log_event) 218.265 + end, 218.266 + 218.267 + initiator = function(event) 218.268 + local activity_type = event.boolean_value and "initiator_added" or "initiator_removed" 218.269 + local log_event = new_log_event(event, event.member_id, activity_type) 218.270 + table.insert(log_event.references, new_reference_object( 218.271 + url_for("initiative", event.initiative_id) 218.272 + )) 218.273 + log_to_ontomap(log_event) 218.274 + end, 218.275 + 218.276 + support = function(event) 218.277 + local activity_type = event.boolean_value and "support_added" or "support_removed" 218.278 + local log_event = new_log_event(event, event.member_id, activity_type) 218.279 + table.insert(log_event.references, new_reference_object( 218.280 + url_for("initiative", event.initiative_id) 218.281 + )) 218.282 + log_event.details.draft_id = event.draft_id 218.283 + log_to_ontomap(log_event) 218.284 + end, 218.285 + 218.286 + support_updated = function(event) 218.287 + local log_event = new_log_event(event, event.member_id, "support_updated") 218.288 + table.insert(log_event.references, new_reference_object( 218.289 + url_for("initiative", event.initiative_id) 218.290 + )) 218.291 + log_event.details.draft_id = event.draft_id 218.292 + log_to_ontomap(log_event) 218.293 + end, 218.294 + 218.295 + suggestion_created = function(event) 218.296 + local log_event = new_log_event(event, event.member_id, "object_created") 218.297 + table.insert(log_event.activity_objects, new_activity_object{ 218.298 + type = "suggestion", 218.299 + url = url_for("suggestion", event.suggestion_id) 218.300 + }) 218.301 + table.insert(log_event.references, new_reference_object( 218.302 + url_for("initiative", event.initiative_id) 218.303 + )) 218.304 + log_to_ontomap(log_event) 218.305 + end, 218.306 + 218.307 + suggestion_removed = function(event) 218.308 + local log_event = new_log_event(event, 0, "object_removed") 218.309 + table.insert(log_event.activity_objects, new_activity_object{ 218.310 + type = "suggestion", 218.311 + url = url_for("suggestion", event.suggestion_id) 218.312 + }) 218.313 + table.insert(log_event.references, new_reference_object( 218.314 + url_for("initiative", event.initiative_id) 218.315 + )) 218.316 + log_to_ontomap(log_event) 218.317 + end, 218.318 + 218.319 + suggestion_rated = function(event) 218.320 + local log_event = new_log_event(event, event.member_id, "suggestion_rated") 218.321 + table.insert(log_event.references, new_reference_object( 218.322 + url_for("suggestion", event.suggestion_id) 218.323 + )) 218.324 + log_event.details.degree = event.numeric_value 218.325 + log_event.details.fulfilled = event.boolean_value or json.null 218.326 + log_to_ontomap(log_event) 218.327 + end, 218.328 + 218.329 + delegation = function(event) 218.330 + -- TODO 218.331 + end, 218.332 + 218.333 + member_activated = function(event) 218.334 + local log_event = new_log_event(event, event.member_id, "account_registered") 218.335 + log_to_ontomap(log_event) 218.336 + end, 218.337 + 218.338 + member_removed = function(event) 218.339 + -- TODO -> which activity to log? 218.340 + end, 218.341 + 218.342 + member_active = function(event) 218.343 + -- TODO -> which activity to log? 218.344 + end, 218.345 + 218.346 + member_name_updated = function(event) 218.347 + local log_event = new_log_event(event, event.member_id, "screen_name_changed") 218.348 + log_event.details.screen_name = event.text_value 218.349 + log_to_ontomap(log_event) 218.350 + end, 218.351 + 218.352 + member_profile_updated = function(event) 218.353 + local log_event = new_log_event(event, event.member_id, "profile_updated") 218.354 + log_to_ontomap(log_event) 218.355 + end, 218.356 + 218.357 + member_image_updated = function(event) 218.358 + local log_event = new_log_event(event, event.member_id, "avatar_changed") 218.359 + log_to_ontomap(log_event) 218.360 + end, 218.361 + 218.362 + contact = function(event) 218.363 + local activity_type = event.boolean_value and "contact_published" or "contact_unpublished" 218.364 + local log_event = new_log_event(event, event.member_id, activity_type) 218.365 + log_event.details.other_member_id = event.other_member_id 218.366 + log_to_ontomap(log_event) 218.367 + end 218.368 + 218.369 +} 218.370 + 218.371 +function _G.ontomap_log_event(event) 218.372 + 218.373 + if mapper[event.event] then 218.374 + local e = Event:new_selector() 218.375 + :add_where{ "id = ?", event.id } 218.376 + :add_field("extract(epoch from occurrence)", "occurrence_epoch") 218.377 + :optional_object_mode() 218.378 + :exec() 218.379 + if e then 218.380 + mapper[event.event](e) 218.381 + end 218.382 + end 218.383 + 218.384 + 218.385 +end
219.1 --- a/locale/translations.de.lua Thu Jun 23 03:30:57 2016 +0200 219.2 +++ b/locale/translations.de.lua Sun Jul 15 14:07:29 2018 +0200 219.3 @@ -1,5 +1,6 @@ 219.4 #!/usr/bin/env lua 219.5 return { 219.6 +["all subject areas"] = "Alle Themenbereiche"; 219.7 [" to receive updates by email"] = " bearbeiten um Aktualisierungen per E-Mail zu erhalten"; 219.8 ["#{closed_ago} ago"] = "vor #{closed_ago}"; 219.9 ["#{count} Neutral"] = "#{count} Enthaltung"; 219.10 @@ -33,7 +34,7 @@ 219.11 ["#{policy_name} ##{issue_id}"] = false; 219.12 ["#{policy} ##{id}"] = false; 219.13 ["#{result}: #{yes_count} Yes (#{yes_percent}), #{no_count} No (#{no_percent}), #{neutral_count} Abstention (#{neutral_percent})"] = "#{result}: #{yes_count} Ja (#{yes_percent}), #{no_count} Nein (#{no_percent}), #{neutral_count} Enthaltung (#{neutral_percent})"; 219.14 -["#{result}: No votes (0)"] = "#{result}: Keine Stimmen (0)"; 219.15 +["No votes (0)"] = "Keine Stimmen (0)"; 219.16 ["(+ #{count} potential)"] = "(+ #{count} potentielle)"; 219.17 ["(1) Admission"] = "(1) Zulassung"; 219.18 ["(1) Admission phase"] = "(1) Zulassungsphase"; 219.19 @@ -425,7 +426,7 @@ 219.20 ["No matching members found"] = "Keine passenden Mitglieder gefunden"; 219.21 ["No more events available"] = "Keine weiteren Ereignisse verfügbar"; 219.22 ["No multistage majority"] = "Keine mehrstufigen Mehrheiten"; 219.23 -["No published contacts"] = "Keiner Kontakte veröffentlicht"; 219.24 +["No published contacts"] = "Keine Kontakte veröffentlicht"; 219.25 ["No results for this selection"] = "Keine Ergebnisse für diese Auswahl"; 219.26 ["No reverse beat path"] = "Kein rückwärtsgerichteter Schlagpfad"; 219.27 ["No suggestions"] = "Keine Verbesserungsvorschläge"; 219.28 @@ -958,9 +959,9 @@ 219.29 ["supporter"] = "Unterstützer"; 219.30 ["supporter with restricting suggestions"] = "Unterstützer mit beschränkenden Verbesserungsvorschlägen"; 219.31 ["take a look at the competing initiatives"] = "schau dir die konkurrierenden Initiativen an"; 219.32 -["take a look at the suggestions (see left) and rate them"] = "schau dir die Verbesserungsvorschläge (links) an und bewerte sie"; 219.33 +["take a look at the suggestions (see right) and rate them"] = "schau dir die Verbesserungsvorschläge (rechts) an und bewerte sie"; 219.34 ["take a look at the suggestions of your supporters"] = "schau dir die Verbesserungsvorschläge deiner Unterstützer an"; 219.35 -["take a look on the issues (see left)"] = "schau dir die Themen an (siehe links)"; 219.36 +["take a look on the issues (see right)"] = "schau dir die Themen an (siehe rechts)"; 219.37 ["the following login is connected to this email address:\n\n"] = "der folgende Anmeldename ist mit dieser E-Mail-Adresse verknüpft:\n\n"; 219.38 ["this issue is in verification phase, therefore the initiative text cannot be updated anymore"] = "dieses Thema ist in der Überprüfungsphase, daher kann der Text nicht mehr geändert werden"; 219.39 ["this issue is in voting phase, therefore the initiative text cannot be updated anymore"] = "dieses Thema ist in Abstimmung, daher kann der Text nicht mehr geändert werden"; 219.40 @@ -1007,4 +1008,8 @@ 219.41 ["you have #{count} incoming delegations"] = "#{count} eingehende Delegationen"; 219.42 ["you restricted your support by rating suggestions as must or must not"] = "deine Untersützung ist aufgrund von 'muss'- oder 'darf nicht'-Verbesserungsvorschlägen beschränkt"; 219.43 ["you voted"] = "du hast abgestimmt"; 219.44 +["new issue"] = "Neues Thema starten"; 219.45 +["expert editor (HTML)"] = "Expertenmodus (HTML)"; 219.46 +["show profile"] = "Profil aufrufen"; 219.47 +["show ballot"] = "Stimmzettel aufrufen"; 219.48 }
220.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 220.2 +++ b/model/agent.lua Sun Jul 15 14:07:29 2018 +0200 220.3 @@ -0,0 +1,27 @@ 220.4 +Agent= mondelefant.new_class() 220.5 +Agent.table = 'agent' 220.6 +Agent.primary_key = { "controlled_id", "controller_id" } 220.7 + 220.8 +Agent:add_reference{ 220.9 + mode = 'm1', 220.10 + to = "Member", 220.11 + this_key = 'controller_id', 220.12 + that_key = 'id', 220.13 + ref = 'controller', 220.14 +} 220.15 + 220.16 +Agent:add_reference{ 220.17 + mode = 'm1', 220.18 + to = "Member", 220.19 + this_key = 'controlled_id', 220.20 + that_key = 'id', 220.21 + ref = 'controllee', 220.22 +} 220.23 + 220.24 +function Agent:by_pk(controlled_id, controller_id) 220.25 + return self:new_selector() 220.26 + :add_where{ "controlled_id = ?", controlled_id } 220.27 + :add_where{ "controller_id = ?", controller_id } 220.28 + :optional_object_mode() 220.29 + :exec() 220.30 +end
221.1 --- a/model/direct_voter.lua Thu Jun 23 03:30:57 2016 +0200 221.2 +++ b/model/direct_voter.lua Sun Jul 15 14:07:29 2018 +0200 221.3 @@ -18,11 +18,11 @@ 221.4 ref = 'member', 221.5 } 221.6 221.7 -model.has_rendered_content(DirectVoter, RenderedVoterComment, "comment") 221.8 +model.has_rendered_content(DirectVoter, RenderedVoterComment, "comment", { "issue_id", "member_id" }) 221.9 221.10 function DirectVoter:by_pk(issue_id, member_id) 221.11 return self:new_selector() 221.12 :add_where{ "issue_id = ? AND member_id = ?", issue_id, member_id } 221.13 :optional_object_mode() 221.14 :exec() 221.15 -end 221.16 \ No newline at end of file 221.17 +end
222.1 --- a/model/draft.lua Thu Jun 23 03:30:57 2016 +0200 222.2 +++ b/model/draft.lua Sun Jul 15 14:07:29 2018 +0200 222.3 @@ -23,7 +23,7 @@ 222.4 return self.author and self.author.name or _"Unknown author" 222.5 end 222.6 222.7 -model.has_rendered_content(Draft, RenderedDraft) 222.8 +model.has_rendered_content(Draft, RenderedDraft, "content", "draft_id") 222.9 222.10 function Draft:update_content(member_id, initiative_id, p_formatting_engine, content, external_reference, preview) 222.11 local initiative = Initiative:by_id(initiative_id) 222.12 @@ -44,7 +44,7 @@ 222.13 222.14 local initiator = Initiator:by_pk(initiative.id, member_id) 222.15 if not initiator or not initiator.accepted then 222.16 - error("access denied") 222.17 + return false 222.18 end 222.19 222.20 local tmp = db:query({ "SELECT text_entries_left FROM member_contingent_left WHERE member_id = ? AND polling = ?", member_id, initiative.polling }, "opt_object")
223.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 223.2 +++ b/model/dynamic_application_scope.lua Sun Jul 15 14:07:29 2018 +0200 223.3 @@ -0,0 +1,73 @@ 223.4 +DynamicApplicationScope = mondelefant.new_class() 223.5 +DynamicApplicationScope.table = 'dynamic_application_scope' 223.6 +DynamicApplicationScope.primary_key = { "redirect_uri", "flow", "scope" } 223.7 + 223.8 +function DynamicApplicationScope:by_redirect_uri_and_flow(redirect_uri, flow) 223.9 + local dynamic_application_scopes = self:new_selector() 223.10 + :add_where{ "redirect_uri = ?", redirect_uri } 223.11 + :add_where{ "flow = ?", flow } 223.12 + :add_where("expiry >= now()") 223.13 + :exec() 223.14 + return dynamic_application_scopes 223.15 +end 223.16 + 223.17 +function DynamicApplicationScope:check_scopes(domain, redirect_uri, requested_flow, requested_scopes) 223.18 + local function check_scopes(permitted_scopes) 223.19 + local missing_scope = false 223.20 + for scope in pairs(requested_scopes) do 223.21 + if not permitted_scopes[scope] then 223.22 + missing_scope = true 223.23 + end 223.24 + end 223.25 + return missing_scope 223.26 + end 223.27 + 223.28 + local registered = false 223.29 + local missing_scope = false 223.30 + 223.31 + local dynamic_application_scopes = DynamicApplicationScope:by_redirect_uri_and_flow(redirect_uri, requested_flow) 223.32 + 223.33 + if #dynamic_application_scopes > 0 then 223.34 + registered = true 223.35 + local permitted_scopes = {} 223.36 + for i, dynamic_application_scope in ipairs(dynamic_application_scopes) do 223.37 + permitted_scopes[dynamic_application_scope.scope] = true 223.38 + end 223.39 + missing_scope = check_scopes(permitted_scopes) 223.40 + end 223.41 + 223.42 + if not registered or missing_scope then 223.43 + local output, err, status = config.oauth2.host_func("_liquidfeedback_client." .. domain) 223.44 + if output == nil then 223.45 + error("Cannot execute host_func command") 223.46 + end 223.47 + if status == 0 then 223.48 + for line in string.gmatch(output, "[^\r\n]+") do 223.49 + local flow, result = string.match(line, '"dynamic client v1" "([^"]+)" (.+)$') 223.50 + if flow == requested_flow then 223.51 + registered = true 223.52 + local permitted_scopes = {} 223.53 + local wildcard = false 223.54 + for entry in string.gmatch(result, '"([^"]+)"') do 223.55 + if entry == "*" then 223.56 + wildcard = true 223.57 + break 223.58 + end 223.59 + permitted_scopes[entry] = true 223.60 + end 223.61 + if not wildcard then 223.62 + missing_scope = check_scopes(permitted_scopes) 223.63 + end 223.64 + end 223.65 + end 223.66 + end 223.67 + end 223.68 + 223.69 + if not registered then 223.70 + return "not_registered" 223.71 + elseif missing_scope then 223.72 + return "missing_scope" 223.73 + else 223.74 + return "ok" 223.75 + end 223.76 +end
224.1 --- a/model/event.lua Thu Jun 23 03:30:57 2016 +0200 224.2 +++ b/model/event.lua Sun Jul 15 14:07:29 2018 +0200 224.3 @@ -3,6 +3,30 @@ 224.4 224.5 Event:add_reference{ 224.6 mode = 'm1', 224.7 + to = "Unit", 224.8 + this_key = 'unit_id', 224.9 + that_key = 'id', 224.10 + ref = 'unit', 224.11 +} 224.12 + 224.13 +Event:add_reference{ 224.14 + mode = 'm1', 224.15 + to = "Area", 224.16 + this_key = 'area_id', 224.17 + that_key = 'id', 224.18 + ref = 'area', 224.19 +} 224.20 + 224.21 +Event:add_reference{ 224.22 + mode = 'm1', 224.23 + to = "Policy", 224.24 + this_key = 'policy_id', 224.25 + that_key = 'id', 224.26 + ref = 'policy', 224.27 +} 224.28 + 224.29 +Event:add_reference{ 224.30 + mode = 'm1', 224.31 to = "Issue", 224.32 this_key = 'issue_id', 224.33 that_key = 'id', 224.34 @@ -33,6 +57,13 @@ 224.35 ref = 'member', 224.36 } 224.37 224.38 +function Event:by_member_id(member_id) 224.39 + return Event:new_selector() 224.40 + :add_where{ "member_id = ?", member_id } 224.41 + :add_order_by("id DESC") 224.42 + :exec() 224.43 +end 224.44 + 224.45 function Event.object_get:event_name() 224.46 return ({ 224.47 issue_state_changed = _"Issue reached next phase", 224.48 @@ -136,40 +167,57 @@ 224.49 224.50 end 224.51 224.52 -function Event:send_next_notification() 224.53 - 224.54 - local notification_event_sent = NotificationEventSent:new_selector() 224.55 - :optional_object_mode() 224.56 - :for_update() 224.57 - :exec() 224.58 - 224.59 - local last_event_id = 0 224.60 - if notification_event_sent then 224.61 - last_event_id = notification_event_sent.event_id 224.62 - end 224.63 +function Event:get_events_after_id(id) 224.64 + return ( 224.65 + Event:new_selector() 224.66 + :add_where{ "event.id > ?", id } 224.67 + :add_order_by("event.id") 224.68 + :exec() 224.69 + ) 224.70 +end 224.71 + 224.72 + 224.73 + 224.74 +Event.handlers = {} 224.75 + 224.76 +function Event:add_handler(func) 224.77 + table.insert(Event.handlers, func) 224.78 +end 224.79 + 224.80 +function Event:process_stream(poll) 224.81 + 224.82 + db:query('LISTEN "event"') 224.83 + 224.84 + local last_event_id = EventProcessed:get_last_id() 224.85 224.86 - local event = Event:new_selector() 224.87 - :add_where{ "event.id > ?", last_event_id } 224.88 - :add_order_by("event.id") 224.89 - :limit(1) 224.90 - :optional_object_mode() 224.91 - :exec() 224.92 + while true do 224.93 + 224.94 + local events = Event:get_events_after_id(last_event_id) 224.95 + 224.96 + for i_event, event in ipairs(events) do 224.97 + last_event_id = event.id 224.98 + EventProcessed:set_last_id(last_event_id) 224.99 + for i_handler, event_handler in ipairs(Event.handlers) do 224.100 + event_handler(event) 224.101 + end 224.102 + event:send_notification() 224.103 + end 224.104 224.105 - if event then 224.106 - if last_event_id == 0 then 224.107 - db:query{ "INSERT INTO notification_event_sent (event_id) VALUES (?)", event.id } 224.108 + if poll then 224.109 + while not db:wait(0) do 224.110 + if not poll({[db.fd]=true}, nil, nil, true) then 224.111 + goto exit 224.112 + end 224.113 + end 224.114 else 224.115 - db:query{ "UPDATE notification_event_sent SET event_id = ?", event.id } 224.116 + db:wait() 224.117 end 224.118 - 224.119 - event:send_notification() 224.120 + while db:wait(0) do end -- discard multiple events 224.121 224.122 - if config.notification_handler_func then 224.123 - config.notification_handler_func(event) 224.124 - end 224.125 - 224.126 - return true 224.127 - 224.128 end 224.129 224.130 + ::exit:: 224.131 + 224.132 + db:query('UNLISTEN "event"') 224.133 + 224.134 end
225.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 225.2 +++ b/model/event_processed.lua Sun Jul 15 14:07:29 2018 +0200 225.3 @@ -0,0 +1,22 @@ 225.4 +EventProcessed = mondelefant.new_class() 225.5 +EventProcessed.table = 'event_processed' 225.6 + 225.7 +function EventProcessed:get_last_id() 225.8 + 225.9 + local event_processed = self:new_selector() 225.10 + :optional_object_mode() 225.11 + :for_update() 225.12 + :exec() 225.13 + 225.14 + local last_event_id = 0 225.15 + if event_processed then 225.16 + last_event_id = event_processed.event_id 225.17 + end 225.18 + 225.19 + return last_event_id 225.20 + 225.21 +end 225.22 + 225.23 +function EventProcessed:set_last_id(id) 225.24 + db:query{ "INSERT INTO event_processed (event_id) VALUES (?) ON CONFLICT ((1)) DO UPDATE SET event_id = EXCLUDED.event_id", id } 225.25 +end
226.1 --- a/model/initiative.lua Thu Jun 23 03:30:57 2016 +0200 226.2 +++ b/model/initiative.lua Sun Jul 15 14:07:29 2018 +0200 226.3 @@ -154,9 +154,9 @@ 226.4 226.5 selector:left_join("supporter", nil, "supporter.initiative_id = initiative.id AND supporter.member_id = delegation_info.participating_member_id") 226.6 226.7 - selector:left_join("delegating_interest_snapshot", "delegating_interest_s", { "delegating_interest_s.event = issue.latest_snapshot_event AND delegating_interest_s.issue_id = issue.id AND delegating_interest_s.member_id = ?", options.member_id }) 226.8 + selector:left_join("delegating_interest_snapshot", "delegating_interest_s", { "delegating_interest_s.snapshot_id = issue.latest_snapshot_id AND delegating_interest_s.issue_id = issue.id AND delegating_interest_s.member_id = ?", options.member_id }) 226.9 226.10 - selector:left_join("direct_supporter_snapshot", "supporter_s", { "supporter_s.event = issue.latest_snapshot_event AND supporter_s.initiative_id = initiative.id AND (supporter_s.member_id = ? OR supporter_s.member_id = delegating_interest_s.delegate_member_ids[array_upper(delegating_interest_s.delegate_member_ids, 1)])", options.member_id }) 226.11 + selector:left_join("direct_supporter_snapshot", "supporter_s", { "supporter_s.snapshot_id = issue.latest_snapshot_id AND supporter_s.initiative_id = initiative.id AND (supporter_s.member_id = ? OR supporter_s.member_id = delegating_interest_s.delegate_member_ids[array_upper(delegating_interest_s.delegate_member_ids, 1)])", options.member_id }) 226.12 226.13 selector:add_field("CASE WHEN issue.fully_frozen ISNULL AND issue.closed ISNULL THEN supporter.member_id NOTNULL ELSE supporter_s.member_id NOTNULL END", "supported") 226.14 selector:add_field({ "CASE WHEN issue.fully_frozen ISNULL AND issue.closed ISNULL THEN delegation_info.own_participation AND supporter.member_id NOTNULL ELSE supporter_s.member_id = ? END", options.member_id }, "directly_supported")
227.1 --- a/model/issue.lua Thu Jun 23 03:30:57 2016 +0200 227.2 +++ b/model/issue.lua Sun Jul 15 14:07:29 2018 +0200 227.3 @@ -162,7 +162,7 @@ 227.4 selector:add_field ( "non_voter.member_id NOTNULL", "non_voter" ) 227.5 selector:left_join ( "direct_interest_snapshot", nil, { [[ 227.6 direct_interest_snapshot.issue_id = issue.id AND 227.7 - direct_interest_snapshot.event = issue.latest_snapshot_event AND 227.8 + direct_interest_snapshot.snapshot_id = issue.latest_snapshot_id AND 227.9 direct_interest_snapshot.member_id = ? 227.10 ]], options.member_id }) 227.11 selector:add_field ( "direct_interest_snapshot.weight", "weight" ) 227.12 @@ -275,3 +275,10 @@ 227.13 return _("ends in #{state_time_left}", { state_time_left = self.state_time_left }) 227.14 end 227.15 end 227.16 + 227.17 +function Issue:by_ids(ids) 227.18 + local selector = self:new_selector() 227.19 + selector:add_where{'"id" IN ($)', { ids } } 227.20 + return selector:exec() 227.21 +end 227.22 +
228.1 --- a/model/member.lua Thu Jun 23 03:30:57 2016 +0200 228.2 +++ b/model/member.lua Sun Jul 15 14:07:29 2018 +0200 228.3 @@ -166,6 +166,24 @@ 228.4 } 228.5 228.6 Member:add_reference{ 228.7 + mode = '11', 228.8 + to = "MemberProfile", 228.9 + this_key = 'id', 228.10 + that_key = 'member_id', 228.11 + ref = 'profile', 228.12 + back_ref = 'member' 228.13 +} 228.14 + 228.15 +Member:add_reference{ 228.16 + mode = '11', 228.17 + to = "MemberSettings", 228.18 + this_key = 'id', 228.19 + that_key = 'member_id', 228.20 + ref = 'settings', 228.21 + back_ref = 'member' 228.22 +} 228.23 + 228.24 +Member:add_reference{ 228.25 mode = 'mm', 228.26 to = "Member", 228.27 this_key = 'id', 228.28 @@ -242,8 +260,6 @@ 228.29 ref = 'supported_initiatives' 228.30 } 228.31 228.32 -model.has_rendered_content(Member, RenderedMemberStatement, "statement") 228.33 - 228.34 function Member:build_selector(args) 228.35 local selector = self:new_selector() 228.36 if args.active ~= nil then 228.37 @@ -531,6 +547,12 @@ 228.38 228.39 end 228.40 228.41 +function Member:by_ids(ids) 228.42 + local selector = self:new_selector() 228.43 + selector:add_where{'"id" IN ($)', { ids } } 228.44 + return selector:exec() 228.45 +end 228.46 + 228.47 function Member:by_login(login) 228.48 local selector = self:new_selector() 228.49 selector:add_where{'"login" = ?', login } 228.50 @@ -589,19 +611,23 @@ 228.51 228.52 local subject = subject 228.53 local content 228.54 - 228.55 + local baseurl = request.get_absolute_baseurl() .. "index/register.html" 228.56 + local url = baseurl .. "?skip=1&code=" .. self.invite_code 228.57 if template_file then 228.58 local fh = io.open(template_file, "r") 228.59 content = fh:read("*a") 228.60 - content = (content:gsub("#{invite_code}", self.invite_code)) 228.61 + content = content:gsub("#{url}", url):gsub("#{baseurl}", baseurl):gsub("#{code}", self.invite_code) 228.62 + elseif config.invitation_mail then 228.63 + subject = config.invitation_mail.subject 228.64 + content = config.invitation_mail.content:gsub("#{url}", url):gsub("#{baseurl}", baseurl):gsub("#{code}", self.invite_code) 228.65 else 228.66 subject = config.mail_subject_prefix .. _"Invitation to LiquidFeedback" 228.67 content = slot.use_temporary(function() 228.68 slot.put(_"Hello\n\n") 228.69 slot.put(_"You are invited to LiquidFeedback. To register please click the following link:\n\n") 228.70 - slot.put(request.get_absolute_baseurl() .. "index/register.html?invite=" .. self.invite_code .. "\n\n") 228.71 + slot.put(url .. "\n\n") 228.72 slot.put(_"If this link is not working, please open following url in your web browser:\n\n") 228.73 - slot.put(request.get_absolute_baseurl() .. "index/register.html\n\n") 228.74 + slot.put(baseurl .. "\n\n") 228.75 slot.put(_"On that page please enter the invite key:\n\n") 228.76 slot.put(self.invite_code .. "\n\n") 228.77 end) 228.78 @@ -738,7 +764,33 @@ 228.79 end 228.80 end 228.81 228.82 -function Member.object:has_voting_right_for_unit_id(unit_id) 228.83 +local function populate_units_with_initiative_right_hash(self) 228.84 + if not self.__units_with_initiative_right_hash then 228.85 + local privileges = Privilege:new_selector() 228.86 + :add_where{ "member_id = ?", self.id } 228.87 + :add_where("initiative_right") 228.88 + :exec() 228.89 + self.__units_with_initiative_right_hash = {} 228.90 + for i, privilege in ipairs(privileges) do 228.91 + self.__units_with_initiative_right_hash[privilege.unit_id] = true 228.92 + end 228.93 + end 228.94 +end 228.95 + 228.96 +function Member.object:has_initiative_right_for_unit_id(unit_id) 228.97 + populate_units_with_initiative_right_hash(self) 228.98 + return self.__units_with_initiative_right_hash[unit_id] and true or false 228.99 +end 228.100 + 228.101 +function Member.object_get:has_initiative_right() 228.102 + populate_units_with_initiative_right_hash(self) 228.103 + for k, v in pairs(self.__units_with_initiative_right_hash) do 228.104 + return true 228.105 + end 228.106 + return false 228.107 +end 228.108 + 228.109 +local function populate_units_with_voting_right_hash(self) 228.110 if not self.__units_with_voting_right_hash then 228.111 local privileges = Privilege:new_selector() 228.112 :add_where{ "member_id = ?", self.id } 228.113 @@ -749,6 +801,18 @@ 228.114 self.__units_with_voting_right_hash[privilege.unit_id] = true 228.115 end 228.116 end 228.117 +end 228.118 + 228.119 +function Member.object_get:has_voting_right() 228.120 + populate_units_with_voting_right_hash(self) 228.121 + for k, v in pairs(self.__units_with_voting_right_hash) do 228.122 + return true 228.123 + end 228.124 + return false 228.125 +end 228.126 + 228.127 +function Member.object:has_voting_right_for_unit_id(unit_id) 228.128 + populate_units_with_voting_right_hash(self) 228.129 return self.__units_with_voting_right_hash[unit_id] and true or false 228.130 end 228.131 228.132 @@ -778,3 +842,13 @@ 228.133 function Member.object:delete() 228.134 db:query{ "SELECT delete_member(?)", self.id } 228.135 end 228.136 + 228.137 +function Member.object_get:display_name() 228.138 + if self.identification then 228.139 + return self.identification 228.140 + elseif self.name then 228.141 + return self.name 228.142 + else 228.143 + return "Member #" .. self.id 228.144 + end 228.145 +end
229.1 --- a/model/member_application.lua Thu Jun 23 03:30:57 2016 +0200 229.2 +++ b/model/member_application.lua Sun Jul 15 14:07:29 2018 +0200 229.3 @@ -1,2 +1,66 @@ 229.4 MemberApplication = mondelefant.new_class() 229.5 MemberApplication.table = 'member_application' 229.6 + 229.7 +MemberApplication:add_reference{ 229.8 + mode = 'm1', 229.9 + to = "SystemApplication", 229.10 + this_key = 'system_application_id', 229.11 + that_key = 'id', 229.12 + ref = 'system_application' 229.13 +} 229.14 + 229.15 +function MemberApplication:get_selector_by_member_id_and_system_application_id(member_id, system_application_id) 229.16 + local selector = self:new_selector() 229.17 + selector:add_where{ "member_id = ?", member_id } 229.18 + selector:add_where{ "system_application_id = ?", system_application_id } 229.19 + selector:optional_object_mode() 229.20 + return selector 229.21 +end 229.22 + 229.23 +function MemberApplication:by_member_id_and_system_application_id(member_id, system_application_id) 229.24 + local member_application = self:get_selector_by_member_id_and_system_application_id(member_id, system_application_id) 229.25 + :optional_object_mode() 229.26 + :exec() 229.27 + return member_application 229.28 +end 229.29 + 229.30 +function MemberApplication:get_selector_by_member_id_and_domain(member_id, domain) 229.31 + local selector = self:new_selector() 229.32 + selector:add_where{ "member_id = ?", member_id } 229.33 + selector:add_where{ "domain = ?", domain } 229.34 + selector:optional_object_mode() 229.35 + return selector 229.36 +end 229.37 + 229.38 +function MemberApplication:by_member_id_and_domain(member_id, domain) 229.39 + local member_application = self:get_selector_by_member_id_and_domain(member_id, domain) 229.40 + :optional_object_mode() 229.41 + :exec() 229.42 + return member_application 229.43 +end 229.44 + 229.45 +function MemberApplication:by_member_id(member_id) 229.46 + local member_applications = self:new_selector() 229.47 + :add_where{ "member_id = ?", member_id } 229.48 + :exec() 229.49 + return member_applications 229.50 +end 229.51 + 229.52 +function MemberApplication:by_member_id_with_domain(member_id) 229.53 + local member_applications = self:new_selector() 229.54 + :add_where{ "member_id = ?", member_id } 229.55 + :add_where( "domain NOTNULL" ) 229.56 + :exec() 229.57 + return member_applications 229.58 +end 229.59 + 229.60 +function MemberApplication:by_member_id_and_origin(member_id, origin) 229.61 + local domain = string.match(string.lower(origin), "^https://(.+)") 229.62 + if not domain then 229.63 + return 229.64 + end 229.65 + local member_application = self:get_selector_by_member_id_and_domain(member_id, domain) 229.66 + :optional_object_mode() 229.67 + :exec() 229.68 + return member_application 229.69 +end
230.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 230.2 +++ b/model/member_profile.lua Sun Jul 15 14:07:29 2018 +0200 230.3 @@ -0,0 +1,6 @@ 230.4 +MemberProfile = mondelefant.new_class() 230.5 +MemberProfile.table = 'member_profile' 230.6 +MemberProfile.primary_key = "member_id" 230.7 + 230.8 +model.has_rendered_content(MemberProfile, RenderedMemberStatement, "statement", "member_id") 230.9 +
231.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 231.2 +++ b/model/member_settings.lua Sun Jul 15 14:07:29 2018 +0200 231.3 @@ -0,0 +1,5 @@ 231.4 +MemberSettings = mondelefant.new_class() 231.5 +MemberSettings.table = 'member_settings' 231.6 +MemberSettings.primary_key = "member_id" 231.7 + 231.8 +
232.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 232.2 +++ b/model/member_useterms.lua Sun Jul 15 14:07:29 2018 +0200 232.3 @@ -0,0 +1,5 @@ 232.4 +MemberUseterms = mondelefant.new_class() 232.5 +MemberUseterms.table = 'member_useterms' 232.6 +MemberUseterms.primary_key = { "member_id", "contract_identifier" } 232.7 + 232.8 +
233.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 233.2 +++ b/model/role_verification.lua Sun Jul 15 14:07:29 2018 +0200 233.3 @@ -0,0 +1,3 @@ 233.4 +RoleVerification = mondelefant.new_class() 233.5 +RoleVerification.table = "role_verification" 233.6 +RoleVerification.primary_key = "id"
234.1 --- a/model/session.lua Thu Jun 23 03:30:57 2016 +0200 234.2 +++ b/model/session.lua Sun Jul 15 14:07:29 2018 +0200 234.3 @@ -10,21 +10,57 @@ 234.4 ref = 'member', 234.5 } 234.6 234.7 -local function random_string() 234.8 +Session:add_reference{ 234.9 + mode = 'm1', 234.10 + to = "Member", 234.11 + this_key = 'real_member_id', 234.12 + that_key = 'id', 234.13 + ref = 'real_member', 234.14 +} 234.15 + 234.16 +local secret_length = 24 234.17 +local secret_alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' 234.18 +local secret_purposes = { "oauth", "csrf", "_other" } 234.19 +for idx, purpose in ipairs(secret_purposes) do 234.20 + secret_purposes[purpose] = idx 234.21 +end 234.22 + 234.23 +local function random_string(length_multiplier) 234.24 return multirand.string( 234.25 - 32, 234.26 - '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' 234.27 + secret_length * (length_multiplier or 1), 234.28 + secret_alphabet 234.29 ) 234.30 end 234.31 234.32 function Session:new() 234.33 local session = self.prototype.new(self) -- super call 234.34 session.ident = random_string() 234.35 - session.additional_secret = random_string() 234.36 - session:save() 234.37 + session.additional_secret = random_string(#secret_purposes) 234.38 + session:save() 234.39 return session 234.40 end 234.41 234.42 +function Session.object:additional_secret_for(purpose) 234.43 + local use_hash = false 234.44 + local idx = secret_purposes[purpose] 234.45 + if not idx then 234.46 + idx = assert(secret_purposes._other, "No other secrets supported") 234.47 + use_hash = true 234.48 + end 234.49 + local from_pos = secret_length * (idx-1) + 1 234.50 + local to_pos = from_pos + secret_length - 1 234.51 + local secret = string.sub(self.additional_secret, from_pos, to_pos) 234.52 + if #secret ~= secret_length then 234.53 + self:destroy() 234.54 + error("Session state invalid") 234.55 + end 234.56 + if use_hash then 234.57 + local moonhash = require "moonhash" -- TODO: auto loader for libraries in WebMCP? 234.58 + secret = moonhash.shake256(secret .. "\0" .. purpose, secret_length, secret_alphabet) 234.59 + end 234.60 + return secret 234.61 +end 234.62 + 234.63 function Session:by_ident(ident) 234.64 local selector = self:new_selector() 234.65 selector:add_where{ 'ident = ?', ident }
235.1 --- a/model/suggestion.lua Thu Jun 23 03:30:57 2016 +0200 235.2 +++ b/model/suggestion.lua Sun Jul 15 14:07:29 2018 +0200 235.3 @@ -27,4 +27,4 @@ 235.4 default_order = '"id"' 235.5 } 235.6 235.7 -model.has_rendered_content(Suggestion, RenderedSuggestion) 235.8 +model.has_rendered_content(Suggestion, RenderedSuggestion, "content", "suggestion_id")
236.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 236.2 +++ b/model/system_application.lua Sun Jul 15 14:07:29 2018 +0200 236.3 @@ -0,0 +1,28 @@ 236.4 +SystemApplication = mondelefant.new_class() 236.5 +SystemApplication.table = 'system_application' 236.6 + 236.7 + 236.8 +function SystemApplication:by_client_id(client_id) 236.9 + local system_application = self:new_selector() 236.10 + :add_where{ "client_id = ?", client_id } 236.11 + :optional_object_mode() 236.12 + :exec() 236.13 + return system_application 236.14 +end 236.15 + 236.16 +function SystemApplication:by_origin(origin) 236.17 + local system_applications = self:new_selector() 236.18 + :set_distinct() 236.19 + :left_join("system_application_redirect_uri", nil, "system_application_redirect_uri.system_application_id = system_application.id") 236.20 + :add_where{ "lower(regexp_replace(system_application.default_redirect_uri, '^([^:]+://[^/]+)/.*', E'\\\\1', 'i')) = lower(?) OR lower(regexp_replace(system_application_redirect_uri.redirect_uri, '^([^:]+://[^/]+)/.*', E'\\\\1', 'i')) = lower(?)", origin, origin } 236.21 + :exec() 236.22 + return system_applications 236.23 +end 236.24 + 236.25 +function SystemApplication:get_all() 236.26 + local system_application = self:new_selector() 236.27 + :exec() 236.28 + return system_application 236.29 +end 236.30 + 236.31 +
237.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 237.2 +++ b/model/system_application_redirect_uri.lua Sun Jul 15 14:07:29 2018 +0200 237.3 @@ -0,0 +1,12 @@ 237.4 +SystemApplicationRedirectUri = mondelefant.new_class() 237.5 +SystemApplicationRedirectUri.table = 'system_application_redirect_uri' 237.6 + 237.7 + 237.8 +function SystemApplicationRedirectUri:by_pk(system_application_id, redirect_uri) 237.9 + local system_application_redirect_uri = self:new_selector() 237.10 + :add_where{ "system_application_id = ?", system_application_id } 237.11 + :add_where{ "redirect_uri = ?", redirect_uri } 237.12 + :optional_object_mode() 237.13 + :exec() 237.14 + return system_application_redirect_uri 237.15 +end
238.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 238.2 +++ b/model/token.lua Sun Jul 15 14:07:29 2018 +0200 238.3 @@ -0,0 +1,147 @@ 238.4 +Token = mondelefant.new_class() 238.5 +Token.table = 'token' 238.6 + 238.7 +Token:add_reference{ 238.8 + mode = '1m', 238.9 + to = "TokenScope", 238.10 + this_key = 'id', 238.11 + that_key = 'token_id', 238.12 + ref = 'token_scopes', 238.13 + back_ref = 'token', 238.14 + default_order = 'token_scope.index' 238.15 +} 238.16 + 238.17 +Token:add_reference{ 238.18 + mode = 'm1', 238.19 + to = "Member", 238.20 + this_key = 'member_id', 238.21 + that_key = 'id', 238.22 + ref = 'member', 238.23 +} 238.24 + 238.25 +Token:add_reference{ 238.26 + mode = 'm1', 238.27 + to = "Session", 238.28 + this_key = 'session_id', 238.29 + that_key = 'id', 238.30 + ref = 'session', 238.31 +} 238.32 + 238.33 +Token:add_reference{ 238.34 + mode = 'm1', 238.35 + to = "SystemApplication", 238.36 + this_key = 'system_application_id', 238.37 + that_key = 'id', 238.38 + ref = 'system_application', 238.39 +} 238.40 + 238.41 +function Token:new() 238.42 + local token = self.prototype.new(self) 238.43 + token.token = multirand.string(16, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") 238.44 + return token 238.45 +end 238.46 + 238.47 +function Token:create_authorization(member_id, system_application_id, domain, session_id, redirect_uri, redirect_uri_explicit, scopes, state) 238.48 + 238.49 + local detached = false 238.50 + for i = 0, #scopes do 238.51 + if scopes[i] then 238.52 + for s in string.gmatch(scopes[i], "[^ ]+") do 238.53 + if s == "detached" then 238.54 + detached = true 238.55 + end 238.56 + end 238.57 + end 238.58 + end 238.59 + 238.60 + local requested_scopes = {} 238.61 + 238.62 + for i = 0, #scopes do 238.63 + if scopes[i] then 238.64 + for scope in string.gmatch(scopes[i], "[^ ]+") do 238.65 + requested_scopes[scope] = true 238.66 + end 238.67 + end 238.68 + end 238.69 + 238.70 + local requested_scopes_list = {} 238.71 + 238.72 + for k, v in pairs(requested_scopes) do 238.73 + requested_scopes_list[#requested_scopes_list+1] = k 238.74 + end 238.75 + 238.76 + local requested_scopes_string = table.concat(requested_scopes_list, " ") 238.77 + 238.78 + local expiry = db:query({"SELECT now() + (? || 'sec')::interval AS expiry", config.oauth2.authorization_code_lifetime }, "object").expiry 238.79 + 238.80 + local token = Token:new() 238.81 + token.token_type = "authorization" 238.82 + token.member_id = member_id 238.83 + token.system_application_id = system_application_id 238.84 + token.domain = domain 238.85 + if not detached then 238.86 + token.session_id = session_id 238.87 + end 238.88 + token.redirect_uri = redirect_uri 238.89 + token.redirect_uri_explicit = redirect_uri_explicit 238.90 + token.expiry = expiry 238.91 + token.scope = requested_scopes_string 238.92 + 238.93 + token:save() 238.94 + 238.95 + for i = 0, #scopes do 238.96 + if scopes[i] then 238.97 + local token_scope = TokenScope:new() 238.98 + token_scope.token_id = token.id 238.99 + token_scope.index = i 238.100 + token_scope.scope = scopes[i] 238.101 + token_scope:save() 238.102 + end 238.103 + end 238.104 + 238.105 + 238.106 + return token, target_uri 238.107 +end 238.108 + 238.109 +function Token:by_token_type_and_token(token_type, token) 238.110 + local selector = Token:new_selector() 238.111 + selector:add_where{ "token_type = ?", token_type } 238.112 + selector:add_where{ "token = ?", token } 238.113 + selector:add_where{ "expiry > now()" } 238.114 + selector:optional_object_mode() 238.115 + if token_type == "authorization_code" then 238.116 + selector:for_update() 238.117 + end 238.118 + if token_type == "access_token" then 238.119 + selector:add_field("FLOOR(EXTRACT(EPOCH FROM expiry - now()))", "expiry_in") 238.120 + end 238.121 + return selector:exec() 238.122 +end 238.123 + 238.124 +function Token:refresh_token_by_token_selector(token) 238.125 + local selector = Token:new_selector() 238.126 + selector:add_where{ "token_type = ?", "refresh" } 238.127 + selector:add_where{ "member_id = ?", token.member_id } 238.128 + if token.system_application_id then 238.129 + selector:add_where{ "system_application_id = ?", token.system_application_id } 238.130 + else 238.131 + selector:add_where{ "domain = ?", token.domain } 238.132 + end 238.133 + return selector 238.134 +end 238.135 + 238.136 +function Token:fresh_refresh_token_by_token(token) 238.137 + local selector = Token:refresh_token_by_token_selector(token) 238.138 + selector:add_where{ "created + ('?' || ' sec')::interval > now()", config.oauth2.refresh_pause } 238.139 + selector:add_where{ "regexp_split_to_array(scope, E'\\\\s+') <@ regexp_split_to_array(?, E'\\\\s+')", token.scope } 238.140 + selector:add_where{ "regexp_split_to_array(scope, E'\\\\s+') @> regexp_split_to_array(?, E'\\\\s+')", token.scope } 238.141 + return selector:exec() 238.142 +end 238.143 + 238.144 +function Token:old_refresh_token_by_token(token, scopes) 238.145 + local selector = Token:refresh_token_by_token_selector(token) 238.146 + selector:add_where{ "id < ?", token.id } 238.147 + selector:add_where{ "created + ('?' || ' sec')::interval <= now()", config.oauth2.refresh_grace_period } 238.148 + selector:add_where{ "regexp_split_to_array(scope, E'\\\\s+') && regexp_split_to_array(?, E'\\\\s+')", scopes } 238.149 + return selector:exec() 238.150 +end
239.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 239.2 +++ b/model/token_scope.lua Sun Jul 15 14:07:29 2018 +0200 239.3 @@ -0,0 +1,3 @@ 239.4 +TokenScope = mondelefant.new_class() 239.5 +TokenScope.table = 'token_scope' 239.6 +TokenScope.primary_key = { "token_id", "index" }
240.1 --- a/model/unit.lua Thu Jun 23 03:30:57 2016 +0200 240.2 +++ b/model/unit.lua Sun Jul 15 14:07:29 2018 +0200 240.3 @@ -7,7 +7,8 @@ 240.4 this_key = 'id', 240.5 that_key = 'unit_id', 240.6 ref = 'areas', 240.7 - back_ref = 'unit' 240.8 + back_ref = 'unit', 240.9 + default_order = "area.name, area.id" 240.10 } 240.11 240.12 Unit:add_reference{
241.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 241.2 +++ b/model/verification.lua Sun Jul 15 14:07:29 2018 +0200 241.3 @@ -0,0 +1,3 @@ 241.4 +Verification = mondelefant.new_class() 241.5 +Verification.table = "verification" 241.6 +Verification.primary_key = "id"
242.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 242.2 +++ b/static/font/Apache_License.txt Sun Jul 15 14:07:29 2018 +0200 242.3 @@ -0,0 +1,203 @@ 242.4 +Font data copyright Google 2013 242.5 + 242.6 + Apache License 242.7 + Version 2.0, January 2004 242.8 + http://www.apache.org/licenses/ 242.9 + 242.10 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 242.11 + 242.12 + 1. Definitions. 242.13 + 242.14 + "License" shall mean the terms and conditions for use, reproduction, 242.15 + and distribution as defined by Sections 1 through 9 of this document. 242.16 + 242.17 + "Licensor" shall mean the copyright owner or entity authorized by 242.18 + the copyright owner that is granting the License. 242.19 + 242.20 + "Legal Entity" shall mean the union of the acting entity and all 242.21 + other entities that control, are controlled by, or are under common 242.22 + control with that entity. For the purposes of this definition, 242.23 + "control" means (i) the power, direct or indirect, to cause the 242.24 + direction or management of such entity, whether by contract or 242.25 + otherwise, or (ii) ownership of fifty percent (50%) or more of the 242.26 + outstanding shares, or (iii) beneficial ownership of such entity. 242.27 + 242.28 + "You" (or "Your") shall mean an individual or Legal Entity 242.29 + exercising permissions granted by this License. 242.30 + 242.31 + "Source" form shall mean the preferred form for making modifications, 242.32 + including but not limited to software source code, documentation 242.33 + source, and configuration files. 242.34 + 242.35 + "Object" form shall mean any form resulting from mechanical 242.36 + transformation or translation of a Source form, including but 242.37 + not limited to compiled object code, generated documentation, 242.38 + and conversions to other media types. 242.39 + 242.40 + "Work" shall mean the work of authorship, whether in Source or 242.41 + Object form, made available under the License, as indicated by a 242.42 + copyright notice that is included in or attached to the work 242.43 + (an example is provided in the Appendix below). 242.44 + 242.45 + "Derivative Works" shall mean any work, whether in Source or Object 242.46 + form, that is based on (or derived from) the Work and for which the 242.47 + editorial revisions, annotations, elaborations, or other modifications 242.48 + represent, as a whole, an original work of authorship. For the purposes 242.49 + of this License, Derivative Works shall not include works that remain 242.50 + separable from, or merely link (or bind by name) to the interfaces of, 242.51 + the Work and Derivative Works thereof. 242.52 + 242.53 + "Contribution" shall mean any work of authorship, including 242.54 + the original version of the Work and any modifications or additions 242.55 + to that Work or Derivative Works thereof, that is intentionally 242.56 + submitted to Licensor for inclusion in the Work by the copyright owner 242.57 + or by an individual or Legal Entity authorized to submit on behalf of 242.58 + the copyright owner. For the purposes of this definition, "submitted" 242.59 + means any form of electronic, verbal, or written communication sent 242.60 + to the Licensor or its representatives, including but not limited to 242.61 + communication on electronic mailing lists, source code control systems, 242.62 + and issue tracking systems that are managed by, or on behalf of, the 242.63 + Licensor for the purpose of discussing and improving the Work, but 242.64 + excluding communication that is conspicuously marked or otherwise 242.65 + designated in writing by the copyright owner as "Not a Contribution." 242.66 + 242.67 + "Contributor" shall mean Licensor and any individual or Legal Entity 242.68 + on behalf of whom a Contribution has been received by Licensor and 242.69 + subsequently incorporated within the Work. 242.70 + 242.71 + 2. Grant of Copyright License. Subject to the terms and conditions of 242.72 + this License, each Contributor hereby grants to You a perpetual, 242.73 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 242.74 + copyright license to reproduce, prepare Derivative Works of, 242.75 + publicly display, publicly perform, sublicense, and distribute the 242.76 + Work and such Derivative Works in Source or Object form. 242.77 + 242.78 + 3. Grant of Patent License. Subject to the terms and conditions of 242.79 + this License, each Contributor hereby grants to You a perpetual, 242.80 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 242.81 + (except as stated in this section) patent license to make, have made, 242.82 + use, offer to sell, sell, import, and otherwise transfer the Work, 242.83 + where such license applies only to those patent claims licensable 242.84 + by such Contributor that are necessarily infringed by their 242.85 + Contribution(s) alone or by combination of their Contribution(s) 242.86 + with the Work to which such Contribution(s) was submitted. If You 242.87 + institute patent litigation against any entity (including a 242.88 + cross-claim or counterclaim in a lawsuit) alleging that the Work 242.89 + or a Contribution incorporated within the Work constitutes direct 242.90 + or contributory patent infringement, then any patent licenses 242.91 + granted to You under this License for that Work shall terminate 242.92 + as of the date such litigation is filed. 242.93 + 242.94 + 4. Redistribution. You may reproduce and distribute copies of the 242.95 + Work or Derivative Works thereof in any medium, with or without 242.96 + modifications, and in Source or Object form, provided that You 242.97 + meet the following conditions: 242.98 + 242.99 + (a) You must give any other recipients of the Work or 242.100 + Derivative Works a copy of this License; and 242.101 + 242.102 + (b) You must cause any modified files to carry prominent notices 242.103 + stating that You changed the files; and 242.104 + 242.105 + (c) You must retain, in the Source form of any Derivative Works 242.106 + that You distribute, all copyright, patent, trademark, and 242.107 + attribution notices from the Source form of the Work, 242.108 + excluding those notices that do not pertain to any part of 242.109 + the Derivative Works; and 242.110 + 242.111 + (d) If the Work includes a "NOTICE" text file as part of its 242.112 + distribution, then any Derivative Works that You distribute must 242.113 + include a readable copy of the attribution notices contained 242.114 + within such NOTICE file, excluding those notices that do not 242.115 + pertain to any part of the Derivative Works, in at least one 242.116 + of the following places: within a NOTICE text file distributed 242.117 + as part of the Derivative Works; within the Source form or 242.118 + documentation, if provided along with the Derivative Works; or, 242.119 + within a display generated by the Derivative Works, if and 242.120 + wherever such third-party notices normally appear. The contents 242.121 + of the NOTICE file are for informational purposes only and 242.122 + do not modify the License. You may add Your own attribution 242.123 + notices within Derivative Works that You distribute, alongside 242.124 + or as an addendum to the NOTICE text from the Work, provided 242.125 + that such additional attribution notices cannot be construed 242.126 + as modifying the License. 242.127 + 242.128 + You may add Your own copyright statement to Your modifications and 242.129 + may provide additional or different license terms and conditions 242.130 + for use, reproduction, or distribution of Your modifications, or 242.131 + for any such Derivative Works as a whole, provided Your use, 242.132 + reproduction, and distribution of the Work otherwise complies with 242.133 + the conditions stated in this License. 242.134 + 242.135 + 5. Submission of Contributions. Unless You explicitly state otherwise, 242.136 + any Contribution intentionally submitted for inclusion in the Work 242.137 + by You to the Licensor shall be under the terms and conditions of 242.138 + this License, without any additional terms or conditions. 242.139 + Notwithstanding the above, nothing herein shall supersede or modify 242.140 + the terms of any separate license agreement you may have executed 242.141 + with Licensor regarding such Contributions. 242.142 + 242.143 + 6. Trademarks. This License does not grant permission to use the trade 242.144 + names, trademarks, service marks, or product names of the Licensor, 242.145 + except as required for reasonable and customary use in describing the 242.146 + origin of the Work and reproducing the content of the NOTICE file. 242.147 + 242.148 + 7. Disclaimer of Warranty. Unless required by applicable law or 242.149 + agreed to in writing, Licensor provides the Work (and each 242.150 + Contributor provides its Contributions) on an "AS IS" BASIS, 242.151 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 242.152 + implied, including, without limitation, any warranties or conditions 242.153 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 242.154 + PARTICULAR PURPOSE. You are solely responsible for determining the 242.155 + appropriateness of using or redistributing the Work and assume any 242.156 + risks associated with Your exercise of permissions under this License. 242.157 + 242.158 + 8. Limitation of Liability. In no event and under no legal theory, 242.159 + whether in tort (including negligence), contract, or otherwise, 242.160 + unless required by applicable law (such as deliberate and grossly 242.161 + negligent acts) or agreed to in writing, shall any Contributor be 242.162 + liable to You for damages, including any direct, indirect, special, 242.163 + incidental, or consequential damages of any character arising as a 242.164 + result of this License or out of the use or inability to use the 242.165 + Work (including but not limited to damages for loss of goodwill, 242.166 + work stoppage, computer failure or malfunction, or any and all 242.167 + other commercial damages or losses), even if such Contributor 242.168 + has been advised of the possibility of such damages. 242.169 + 242.170 + 9. Accepting Warranty or Additional Liability. While redistributing 242.171 + the Work or Derivative Works thereof, You may choose to offer, 242.172 + and charge a fee for, acceptance of support, warranty, indemnity, 242.173 + or other liability obligations and/or rights consistent with this 242.174 + License. However, in accepting such obligations, You may act only 242.175 + on Your own behalf and on Your sole responsibility, not on behalf 242.176 + of any other Contributor, and only if You agree to indemnify, 242.177 + defend, and hold each Contributor harmless for any liability 242.178 + incurred by, or claims asserted against, such Contributor by reason 242.179 + of your accepting any such warranty or additional liability. 242.180 + 242.181 + END OF TERMS AND CONDITIONS 242.182 + 242.183 + APPENDIX: How to apply the Apache License to your work. 242.184 + 242.185 + To apply the Apache License to your work, attach the following 242.186 + boilerplate notice, with the fields enclosed by brackets "[]" 242.187 + replaced with your own identifying information. (Don't include 242.188 + the brackets!) The text should be enclosed in the appropriate 242.189 + comment syntax for the file format. We also recommend that a 242.190 + file or class name and description of purpose be included on the 242.191 + same "printed page" as the copyright notice for easier 242.192 + identification within third-party archives. 242.193 + 242.194 + Copyright [yyyy] [name of copyright owner] 242.195 + 242.196 + Licensed under the Apache License, Version 2.0 (the "License"); 242.197 + you may not use this file except in compliance with the License. 242.198 + You may obtain a copy of the License at 242.199 + 242.200 + http://www.apache.org/licenses/LICENSE-2.0 242.201 + 242.202 + Unless required by applicable law or agreed to in writing, software 242.203 + distributed under the License is distributed on an "AS IS" BASIS, 242.204 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 242.205 + See the License for the specific language governing permissions and 242.206 + limitations under the License. 242.207 \ No newline at end of file
243.1 --- a/static/js/dragdrop.js Thu Jun 23 03:30:57 2016 +0200 243.2 +++ b/static/js/dragdrop.js Sun Jul 15 14:07:29 2018 +0200 243.3 @@ -24,9 +24,10 @@ 243.4 draggedElement.style.backgroundColor = "#eee"; 243.5 draggedElement.style.opacity = 0.8; 243.6 originalElement.offsetParent.appendChild(draggedElement); 243.7 - // workaround for wrong clientWidth and clientHeight information: 243.8 - draggedElement.style.width = 2*originalElement.clientWidth - draggedElement.clientWidth; 243.9 - draggedElement.style.height = 2*originalElement.clientHeight - draggedElement.clientHeight; 243.10 + draggedElement.style.width = originalElement.clientWidth + "px"; 243.11 + draggedElement.style.height = originalElement.clientHeight + "px"; 243.12 + draggedElement.style.left = originalElement.offsetLeft + "px"; 243.13 + draggedElement.style.top = originalElement.offsetTop + "px"; 243.14 mouseOffsetX = mouseX; 243.15 mouseOffsetY = mouseY; 243.16 dropFunc = func; 243.17 @@ -36,8 +37,8 @@ 243.18 mouseX = event.pageX; 243.19 mouseY = event.pageY; 243.20 if (draggedElement) { 243.21 - draggedElement.style.left = elementOffsetX + mouseX - mouseOffsetX; 243.22 - draggedElement.style.top = elementOffsetY + mouseY - mouseOffsetY; 243.23 + draggedElement.style.left = elementOffsetX + mouseX - mouseOffsetX + "px"; 243.24 + draggedElement.style.top = elementOffsetY + mouseY - mouseOffsetY + "px"; 243.25 } 243.26 }); 243.27 }, true); 243.28 @@ -72,7 +73,7 @@ 243.29 event.preventDefault(); 243.30 }); 243.31 }, false); 243.32 - } else if (element.className == "clickable") { 243.33 + } else if (element.classList.contains("clickable")) { 243.34 element.addEventListener("mousedown", function(event) { 243.35 jsProtect(function() { 243.36 event.stopPropagation();
244.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 244.2 +++ b/static/mdl/LICENSE Sun Jul 15 14:07:29 2018 +0200 244.3 @@ -0,0 +1,212 @@ 244.4 + 244.5 + Apache License 244.6 + Version 2.0, January 2004 244.7 + http://www.apache.org/licenses/ 244.8 + 244.9 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 244.10 + 244.11 + 1. Definitions. 244.12 + 244.13 + "License" shall mean the terms and conditions for use, reproduction, 244.14 + and distribution as defined by Sections 1 through 9 of this document. 244.15 + 244.16 + "Licensor" shall mean the copyright owner or entity authorized by 244.17 + the copyright owner that is granting the License. 244.18 + 244.19 + "Legal Entity" shall mean the union of the acting entity and all 244.20 + other entities that control, are controlled by, or are under common 244.21 + control with that entity. For the purposes of this definition, 244.22 + "control" means (i) the power, direct or indirect, to cause the 244.23 + direction or management of such entity, whether by contract or 244.24 + otherwise, or (ii) ownership of fifty percent (50%) or more of the 244.25 + outstanding shares, or (iii) beneficial ownership of such entity. 244.26 + 244.27 + "You" (or "Your") shall mean an individual or Legal Entity 244.28 + exercising permissions granted by this License. 244.29 + 244.30 + "Source" form shall mean the preferred form for making modifications, 244.31 + including but not limited to software source code, documentation 244.32 + source, and configuration files. 244.33 + 244.34 + "Object" form shall mean any form resulting from mechanical 244.35 + transformation or translation of a Source form, including but 244.36 + not limited to compiled object code, generated documentation, 244.37 + and conversions to other media types. 244.38 + 244.39 + "Work" shall mean the work of authorship, whether in Source or 244.40 + Object form, made available under the License, as indicated by a 244.41 + copyright notice that is included in or attached to the work 244.42 + (an example is provided in the Appendix below). 244.43 + 244.44 + "Derivative Works" shall mean any work, whether in Source or Object 244.45 + form, that is based on (or derived from) the Work and for which the 244.46 + editorial revisions, annotations, elaborations, or other modifications 244.47 + represent, as a whole, an original work of authorship. For the purposes 244.48 + of this License, Derivative Works shall not include works that remain 244.49 + separable from, or merely link (or bind by name) to the interfaces of, 244.50 + the Work and Derivative Works thereof. 244.51 + 244.52 + "Contribution" shall mean any work of authorship, including 244.53 + the original version of the Work and any modifications or additions 244.54 + to that Work or Derivative Works thereof, that is intentionally 244.55 + submitted to Licensor for inclusion in the Work by the copyright owner 244.56 + or by an individual or Legal Entity authorized to submit on behalf of 244.57 + the copyright owner. For the purposes of this definition, "submitted" 244.58 + means any form of electronic, verbal, or written communication sent 244.59 + to the Licensor or its representatives, including but not limited to 244.60 + communication on electronic mailing lists, source code control systems, 244.61 + and issue tracking systems that are managed by, or on behalf of, the 244.62 + Licensor for the purpose of discussing and improving the Work, but 244.63 + excluding communication that is conspicuously marked or otherwise 244.64 + designated in writing by the copyright owner as "Not a Contribution." 244.65 + 244.66 + "Contributor" shall mean Licensor and any individual or Legal Entity 244.67 + on behalf of whom a Contribution has been received by Licensor and 244.68 + subsequently incorporated within the Work. 244.69 + 244.70 + 2. Grant of Copyright License. Subject to the terms and conditions of 244.71 + this License, each Contributor hereby grants to You a perpetual, 244.72 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 244.73 + copyright license to reproduce, prepare Derivative Works of, 244.74 + publicly display, publicly perform, sublicense, and distribute the 244.75 + Work and such Derivative Works in Source or Object form. 244.76 + 244.77 + 3. Grant of Patent License. Subject to the terms and conditions of 244.78 + this License, each Contributor hereby grants to You a perpetual, 244.79 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 244.80 + (except as stated in this section) patent license to make, have made, 244.81 + use, offer to sell, sell, import, and otherwise transfer the Work, 244.82 + where such license applies only to those patent claims licensable 244.83 + by such Contributor that are necessarily infringed by their 244.84 + Contribution(s) alone or by combination of their Contribution(s) 244.85 + with the Work to which such Contribution(s) was submitted. If You 244.86 + institute patent litigation against any entity (including a 244.87 + cross-claim or counterclaim in a lawsuit) alleging that the Work 244.88 + or a Contribution incorporated within the Work constitutes direct 244.89 + or contributory patent infringement, then any patent licenses 244.90 + granted to You under this License for that Work shall terminate 244.91 + as of the date such litigation is filed. 244.92 + 244.93 + 4. Redistribution. You may reproduce and distribute copies of the 244.94 + Work or Derivative Works thereof in any medium, with or without 244.95 + modifications, and in Source or Object form, provided that You 244.96 + meet the following conditions: 244.97 + 244.98 + (a) You must give any other recipients of the Work or 244.99 + Derivative Works a copy of this License; and 244.100 + 244.101 + (b) You must cause any modified files to carry prominent notices 244.102 + stating that You changed the files; and 244.103 + 244.104 + (c) You must retain, in the Source form of any Derivative Works 244.105 + that You distribute, all copyright, patent, trademark, and 244.106 + attribution notices from the Source form of the Work, 244.107 + excluding those notices that do not pertain to any part of 244.108 + the Derivative Works; and 244.109 + 244.110 + (d) If the Work includes a "NOTICE" text file as part of its 244.111 + distribution, then any Derivative Works that You distribute must 244.112 + include a readable copy of the attribution notices contained 244.113 + within such NOTICE file, excluding those notices that do not 244.114 + pertain to any part of the Derivative Works, in at least one 244.115 + of the following places: within a NOTICE text file distributed 244.116 + as part of the Derivative Works; within the Source form or 244.117 + documentation, if provided along with the Derivative Works; or, 244.118 + within a display generated by the Derivative Works, if and 244.119 + wherever such third-party notices normally appear. The contents 244.120 + of the NOTICE file are for informational purposes only and 244.121 + do not modify the License. You may add Your own attribution 244.122 + notices within Derivative Works that You distribute, alongside 244.123 + or as an addendum to the NOTICE text from the Work, provided 244.124 + that such additional attribution notices cannot be construed 244.125 + as modifying the License. 244.126 + 244.127 + You may add Your own copyright statement to Your modifications and 244.128 + may provide additional or different license terms and conditions 244.129 + for use, reproduction, or distribution of Your modifications, or 244.130 + for any such Derivative Works as a whole, provided Your use, 244.131 + reproduction, and distribution of the Work otherwise complies with 244.132 + the conditions stated in this License. 244.133 + 244.134 + 5. Submission of Contributions. Unless You explicitly state otherwise, 244.135 + any Contribution intentionally submitted for inclusion in the Work 244.136 + by You to the Licensor shall be under the terms and conditions of 244.137 + this License, without any additional terms or conditions. 244.138 + Notwithstanding the above, nothing herein shall supersede or modify 244.139 + the terms of any separate license agreement you may have executed 244.140 + with Licensor regarding such Contributions. 244.141 + 244.142 + 6. Trademarks. This License does not grant permission to use the trade 244.143 + names, trademarks, service marks, or product names of the Licensor, 244.144 + except as required for reasonable and customary use in describing the 244.145 + origin of the Work and reproducing the content of the NOTICE file. 244.146 + 244.147 + 7. Disclaimer of Warranty. Unless required by applicable law or 244.148 + agreed to in writing, Licensor provides the Work (and each 244.149 + Contributor provides its Contributions) on an "AS IS" BASIS, 244.150 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 244.151 + implied, including, without limitation, any warranties or conditions 244.152 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 244.153 + PARTICULAR PURPOSE. You are solely responsible for determining the 244.154 + appropriateness of using or redistributing the Work and assume any 244.155 + risks associated with Your exercise of permissions under this License. 244.156 + 244.157 + 8. Limitation of Liability. In no event and under no legal theory, 244.158 + whether in tort (including negligence), contract, or otherwise, 244.159 + unless required by applicable law (such as deliberate and grossly 244.160 + negligent acts) or agreed to in writing, shall any Contributor be 244.161 + liable to You for damages, including any direct, indirect, special, 244.162 + incidental, or consequential damages of any character arising as a 244.163 + result of this License or out of the use or inability to use the 244.164 + Work (including but not limited to damages for loss of goodwill, 244.165 + work stoppage, computer failure or malfunction, or any and all 244.166 + other commercial damages or losses), even if such Contributor 244.167 + has been advised of the possibility of such damages. 244.168 + 244.169 + 9. Accepting Warranty or Additional Liability. While redistributing 244.170 + the Work or Derivative Works thereof, You may choose to offer, 244.171 + and charge a fee for, acceptance of support, warranty, indemnity, 244.172 + or other liability obligations and/or rights consistent with this 244.173 + License. However, in accepting such obligations, You may act only 244.174 + on Your own behalf and on Your sole responsibility, not on behalf 244.175 + of any other Contributor, and only if You agree to indemnify, 244.176 + defend, and hold each Contributor harmless for any liability 244.177 + incurred by, or claims asserted against, such Contributor by reason 244.178 + of your accepting any such warranty or additional liability. 244.179 + 244.180 + END OF TERMS AND CONDITIONS 244.181 + 244.182 + APPENDIX: How to apply the Apache License to your work. 244.183 + 244.184 + To apply the Apache License to your work, attach the following 244.185 + boilerplate notice, with the fields enclosed by brackets "[]" 244.186 + replaced with your own identifying information. (Don't include 244.187 + the brackets!) The text should be enclosed in the appropriate 244.188 + comment syntax for the file format. We also recommend that a 244.189 + file or class name and description of purpose be included on the 244.190 + same "printed page" as the copyright notice for easier 244.191 + identification within third-party archives. 244.192 + 244.193 + Copyright 2015 Google Inc 244.194 + 244.195 + Licensed under the Apache License, Version 2.0 (the "License"); 244.196 + you may not use this file except in compliance with the License. 244.197 + You may obtain a copy of the License at 244.198 + 244.199 + http://www.apache.org/licenses/LICENSE-2.0 244.200 + 244.201 + Unless required by applicable law or agreed to in writing, software 244.202 + distributed under the License is distributed on an "AS IS" BASIS, 244.203 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 244.204 + See the License for the specific language governing permissions and 244.205 + limitations under the License. 244.206 + 244.207 + All code in any directories or sub-directories that end with *.html or 244.208 + *.css is licensed under the Creative Commons Attribution International 244.209 + 4.0 License, which full text can be found here: 244.210 + https://creativecommons.org/licenses/by/4.0/legalcode. 244.211 + 244.212 + As an exception to this license, all html or css that is generated by 244.213 + the software at the direction of the user is copyright the user. The 244.214 + user has full ownership and control over such content, including 244.215 + whether and how they wish to license it.
245.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 245.2 +++ b/static/mdl/buffer.svg Sun Jul 15 14:07:29 2018 +0200 245.3 @@ -0,0 +1,9 @@ 245.4 +<?xml version="1.0"?> 245.5 +<svg width="12" height="4" viewPort="0 0 12 4" version="1.1" xmlns="http://www.w3.org/2000/svg"> 245.6 + <ellipse cx="2" cy="2" rx="2" ry="2"> 245.7 + <animate attributeName="cx" from="2" to="-10" dur="0.6s" repeatCount="indefinite" /> 245.8 + </ellipse> 245.9 + <ellipse cx="14" cy="2" rx="2" ry="2" class="loader"> 245.10 + <animate attributeName="cx" from="14" to="2" dur="0.6s" repeatCount="indefinite" /> 245.11 + </ellipse> 245.12 +</svg>
246.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 246.2 +++ b/static/mdl/material.js Sun Jul 15 14:07:29 2018 +0200 246.3 @@ -0,0 +1,3981 @@ 246.4 +;(function() { 246.5 +"use strict"; 246.6 + 246.7 +/** 246.8 + * @license 246.9 + * Copyright 2015 Google Inc. All Rights Reserved. 246.10 + * 246.11 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.12 + * you may not use this file except in compliance with the License. 246.13 + * You may obtain a copy of the License at 246.14 + * 246.15 + * http://www.apache.org/licenses/LICENSE-2.0 246.16 + * 246.17 + * Unless required by applicable law or agreed to in writing, software 246.18 + * distributed under the License is distributed on an "AS IS" BASIS, 246.19 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.20 + * See the License for the specific language governing permissions and 246.21 + * limitations under the License. 246.22 + */ 246.23 + 246.24 +/** 246.25 + * A component handler interface using the revealing module design pattern. 246.26 + * More details on this design pattern here: 246.27 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.28 + * 246.29 + * @author Jason Mayes. 246.30 + */ 246.31 +/* exported componentHandler */ 246.32 + 246.33 +// Pre-defining the componentHandler interface, for closure documentation and 246.34 +// static verification. 246.35 +var componentHandler = { 246.36 + /** 246.37 + * Searches existing DOM for elements of our component type and upgrades them 246.38 + * if they have not already been upgraded. 246.39 + * 246.40 + * @param {string=} optJsClass the programatic name of the element class we 246.41 + * need to create a new instance of. 246.42 + * @param {string=} optCssClass the name of the CSS class elements of this 246.43 + * type will have. 246.44 + */ 246.45 + upgradeDom: function(optJsClass, optCssClass) {}, 246.46 + /** 246.47 + * Upgrades a specific element rather than all in the DOM. 246.48 + * 246.49 + * @param {!Element} element The element we wish to upgrade. 246.50 + * @param {string=} optJsClass Optional name of the class we want to upgrade 246.51 + * the element to. 246.52 + */ 246.53 + upgradeElement: function(element, optJsClass) {}, 246.54 + /** 246.55 + * Upgrades a specific list of elements rather than all in the DOM. 246.56 + * 246.57 + * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements 246.58 + * The elements we wish to upgrade. 246.59 + */ 246.60 + upgradeElements: function(elements) {}, 246.61 + /** 246.62 + * Upgrades all registered components found in the current DOM. This is 246.63 + * automatically called on window load. 246.64 + */ 246.65 + upgradeAllRegistered: function() {}, 246.66 + /** 246.67 + * Allows user to be alerted to any upgrades that are performed for a given 246.68 + * component type 246.69 + * 246.70 + * @param {string} jsClass The class name of the MDL component we wish 246.71 + * to hook into for any upgrades performed. 246.72 + * @param {function(!HTMLElement)} callback The function to call upon an 246.73 + * upgrade. This function should expect 1 parameter - the HTMLElement which 246.74 + * got upgraded. 246.75 + */ 246.76 + registerUpgradedCallback: function(jsClass, callback) {}, 246.77 + /** 246.78 + * Registers a class for future use and attempts to upgrade existing DOM. 246.79 + * 246.80 + * @param {componentHandler.ComponentConfigPublic} config the registration configuration 246.81 + */ 246.82 + register: function(config) {}, 246.83 + /** 246.84 + * Downgrade either a given node, an array of nodes, or a NodeList. 246.85 + * 246.86 + * @param {!Node|!Array<!Node>|!NodeList} nodes 246.87 + */ 246.88 + downgradeElements: function(nodes) {} 246.89 +}; 246.90 + 246.91 +componentHandler = (function() { 246.92 + 'use strict'; 246.93 + 246.94 + /** @type {!Array<componentHandler.ComponentConfig>} */ 246.95 + var registeredComponents_ = []; 246.96 + 246.97 + /** @type {!Array<componentHandler.Component>} */ 246.98 + var createdComponents_ = []; 246.99 + 246.100 + var componentConfigProperty_ = 'mdlComponentConfigInternal_'; 246.101 + 246.102 + /** 246.103 + * Searches registered components for a class we are interested in using. 246.104 + * Optionally replaces a match with passed object if specified. 246.105 + * 246.106 + * @param {string} name The name of a class we want to use. 246.107 + * @param {componentHandler.ComponentConfig=} optReplace Optional object to replace match with. 246.108 + * @return {!Object|boolean} 246.109 + * @private 246.110 + */ 246.111 + function findRegisteredClass_(name, optReplace) { 246.112 + for (var i = 0; i < registeredComponents_.length; i++) { 246.113 + if (registeredComponents_[i].className === name) { 246.114 + if (typeof optReplace !== 'undefined') { 246.115 + registeredComponents_[i] = optReplace; 246.116 + } 246.117 + return registeredComponents_[i]; 246.118 + } 246.119 + } 246.120 + return false; 246.121 + } 246.122 + 246.123 + /** 246.124 + * Returns an array of the classNames of the upgraded classes on the element. 246.125 + * 246.126 + * @param {!Element} element The element to fetch data from. 246.127 + * @return {!Array<string>} 246.128 + * @private 246.129 + */ 246.130 + function getUpgradedListOfElement_(element) { 246.131 + var dataUpgraded = element.getAttribute('data-upgraded'); 246.132 + // Use `['']` as default value to conform the `,name,name...` style. 246.133 + return dataUpgraded === null ? [''] : dataUpgraded.split(','); 246.134 + } 246.135 + 246.136 + /** 246.137 + * Returns true if the given element has already been upgraded for the given 246.138 + * class. 246.139 + * 246.140 + * @param {!Element} element The element we want to check. 246.141 + * @param {string} jsClass The class to check for. 246.142 + * @returns {boolean} 246.143 + * @private 246.144 + */ 246.145 + function isElementUpgraded_(element, jsClass) { 246.146 + var upgradedList = getUpgradedListOfElement_(element); 246.147 + return upgradedList.indexOf(jsClass) !== -1; 246.148 + } 246.149 + 246.150 + /** 246.151 + * Searches existing DOM for elements of our component type and upgrades them 246.152 + * if they have not already been upgraded. 246.153 + * 246.154 + * @param {string=} optJsClass the programatic name of the element class we 246.155 + * need to create a new instance of. 246.156 + * @param {string=} optCssClass the name of the CSS class elements of this 246.157 + * type will have. 246.158 + */ 246.159 + function upgradeDomInternal(optJsClass, optCssClass) { 246.160 + if (typeof optJsClass === 'undefined' && 246.161 + typeof optCssClass === 'undefined') { 246.162 + for (var i = 0; i < registeredComponents_.length; i++) { 246.163 + upgradeDomInternal(registeredComponents_[i].className, 246.164 + registeredComponents_[i].cssClass); 246.165 + } 246.166 + } else { 246.167 + var jsClass = /** @type {string} */ (optJsClass); 246.168 + if (typeof optCssClass === 'undefined') { 246.169 + var registeredClass = findRegisteredClass_(jsClass); 246.170 + if (registeredClass) { 246.171 + optCssClass = registeredClass.cssClass; 246.172 + } 246.173 + } 246.174 + 246.175 + var elements = document.querySelectorAll('.' + optCssClass); 246.176 + for (var n = 0; n < elements.length; n++) { 246.177 + upgradeElementInternal(elements[n], jsClass); 246.178 + } 246.179 + } 246.180 + } 246.181 + 246.182 + /** 246.183 + * Upgrades a specific element rather than all in the DOM. 246.184 + * 246.185 + * @param {!Element} element The element we wish to upgrade. 246.186 + * @param {string=} optJsClass Optional name of the class we want to upgrade 246.187 + * the element to. 246.188 + */ 246.189 + function upgradeElementInternal(element, optJsClass) { 246.190 + // Verify argument type. 246.191 + if (!(typeof element === 'object' && element instanceof Element)) { 246.192 + throw new Error('Invalid argument provided to upgrade MDL element.'); 246.193 + } 246.194 + var upgradedList = getUpgradedListOfElement_(element); 246.195 + var classesToUpgrade = []; 246.196 + // If jsClass is not provided scan the registered components to find the 246.197 + // ones matching the element's CSS classList. 246.198 + if (!optJsClass) { 246.199 + var classList = element.classList; 246.200 + registeredComponents_.forEach(function(component) { 246.201 + // Match CSS & Not to be upgraded & Not upgraded. 246.202 + if (classList.contains(component.cssClass) && 246.203 + classesToUpgrade.indexOf(component) === -1 && 246.204 + !isElementUpgraded_(element, component.className)) { 246.205 + classesToUpgrade.push(component); 246.206 + } 246.207 + }); 246.208 + } else if (!isElementUpgraded_(element, optJsClass)) { 246.209 + classesToUpgrade.push(findRegisteredClass_(optJsClass)); 246.210 + } 246.211 + 246.212 + // Upgrade the element for each classes. 246.213 + for (var i = 0, n = classesToUpgrade.length, registeredClass; i < n; i++) { 246.214 + registeredClass = classesToUpgrade[i]; 246.215 + if (registeredClass) { 246.216 + // Mark element as upgraded. 246.217 + upgradedList.push(registeredClass.className); 246.218 + element.setAttribute('data-upgraded', upgradedList.join(',')); 246.219 + var instance = new registeredClass.classConstructor(element); 246.220 + instance[componentConfigProperty_] = registeredClass; 246.221 + createdComponents_.push(instance); 246.222 + // Call any callbacks the user has registered with this component type. 246.223 + for (var j = 0, m = registeredClass.callbacks.length; j < m; j++) { 246.224 + registeredClass.callbacks[j](element); 246.225 + } 246.226 + 246.227 + if (registeredClass.widget) { 246.228 + // Assign per element instance for control over API 246.229 + element[registeredClass.className] = instance; 246.230 + } 246.231 + } else { 246.232 + throw new Error( 246.233 + 'Unable to find a registered component for the given class.'); 246.234 + } 246.235 + 246.236 + var ev; 246.237 + if ('CustomEvent' in window && typeof window.CustomEvent === 'function') { 246.238 + ev = new CustomEvent('mdl-componentupgraded', { 246.239 + bubbles: true, cancelable: false 246.240 + }); 246.241 + } else { 246.242 + ev = document.createEvent('Events'); 246.243 + ev.initEvent('mdl-componentupgraded', true, true); 246.244 + } 246.245 + element.dispatchEvent(ev); 246.246 + } 246.247 + } 246.248 + 246.249 + /** 246.250 + * Upgrades a specific list of elements rather than all in the DOM. 246.251 + * 246.252 + * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements 246.253 + * The elements we wish to upgrade. 246.254 + */ 246.255 + function upgradeElementsInternal(elements) { 246.256 + if (!Array.isArray(elements)) { 246.257 + if (elements instanceof Element) { 246.258 + elements = [elements]; 246.259 + } else { 246.260 + elements = Array.prototype.slice.call(elements); 246.261 + } 246.262 + } 246.263 + for (var i = 0, n = elements.length, element; i < n; i++) { 246.264 + element = elements[i]; 246.265 + if (element instanceof HTMLElement) { 246.266 + upgradeElementInternal(element); 246.267 + if (element.children.length > 0) { 246.268 + upgradeElementsInternal(element.children); 246.269 + } 246.270 + } 246.271 + } 246.272 + } 246.273 + 246.274 + /** 246.275 + * Registers a class for future use and attempts to upgrade existing DOM. 246.276 + * 246.277 + * @param {componentHandler.ComponentConfigPublic} config 246.278 + */ 246.279 + function registerInternal(config) { 246.280 + // In order to support both Closure-compiled and uncompiled code accessing 246.281 + // this method, we need to allow for both the dot and array syntax for 246.282 + // property access. You'll therefore see the `foo.bar || foo['bar']` 246.283 + // pattern repeated across this method. 246.284 + var widgetMissing = (typeof config.widget === 'undefined' && 246.285 + typeof config['widget'] === 'undefined'); 246.286 + var widget = true; 246.287 + 246.288 + if (!widgetMissing) { 246.289 + widget = config.widget || config['widget']; 246.290 + } 246.291 + 246.292 + var newConfig = /** @type {componentHandler.ComponentConfig} */ ({ 246.293 + classConstructor: config.constructor || config['constructor'], 246.294 + className: config.classAsString || config['classAsString'], 246.295 + cssClass: config.cssClass || config['cssClass'], 246.296 + widget: widget, 246.297 + callbacks: [] 246.298 + }); 246.299 + 246.300 + registeredComponents_.forEach(function(item) { 246.301 + if (item.cssClass === newConfig.cssClass) { 246.302 + throw new Error('The provided cssClass has already been registered: ' + item.cssClass); 246.303 + } 246.304 + if (item.className === newConfig.className) { 246.305 + throw new Error('The provided className has already been registered'); 246.306 + } 246.307 + }); 246.308 + 246.309 + if (config.constructor.prototype 246.310 + .hasOwnProperty(componentConfigProperty_)) { 246.311 + throw new Error( 246.312 + 'MDL component classes must not have ' + componentConfigProperty_ + 246.313 + ' defined as a property.'); 246.314 + } 246.315 + 246.316 + var found = findRegisteredClass_(config.classAsString, newConfig); 246.317 + 246.318 + if (!found) { 246.319 + registeredComponents_.push(newConfig); 246.320 + } 246.321 + } 246.322 + 246.323 + /** 246.324 + * Allows user to be alerted to any upgrades that are performed for a given 246.325 + * component type 246.326 + * 246.327 + * @param {string} jsClass The class name of the MDL component we wish 246.328 + * to hook into for any upgrades performed. 246.329 + * @param {function(!HTMLElement)} callback The function to call upon an 246.330 + * upgrade. This function should expect 1 parameter - the HTMLElement which 246.331 + * got upgraded. 246.332 + */ 246.333 + function registerUpgradedCallbackInternal(jsClass, callback) { 246.334 + var regClass = findRegisteredClass_(jsClass); 246.335 + if (regClass) { 246.336 + regClass.callbacks.push(callback); 246.337 + } 246.338 + } 246.339 + 246.340 + /** 246.341 + * Upgrades all registered components found in the current DOM. This is 246.342 + * automatically called on window load. 246.343 + */ 246.344 + function upgradeAllRegisteredInternal() { 246.345 + for (var n = 0; n < registeredComponents_.length; n++) { 246.346 + upgradeDomInternal(registeredComponents_[n].className); 246.347 + } 246.348 + } 246.349 + 246.350 + /** 246.351 + * Check the component for the downgrade method. 246.352 + * Execute if found. 246.353 + * Remove component from createdComponents list. 246.354 + * 246.355 + * @param {?componentHandler.Component} component 246.356 + */ 246.357 + function deconstructComponentInternal(component) { 246.358 + if (component) { 246.359 + var componentIndex = createdComponents_.indexOf(component); 246.360 + createdComponents_.splice(componentIndex, 1); 246.361 + 246.362 + var upgrades = component.element_.getAttribute('data-upgraded').split(','); 246.363 + var componentPlace = upgrades.indexOf(component[componentConfigProperty_].classAsString); 246.364 + upgrades.splice(componentPlace, 1); 246.365 + component.element_.setAttribute('data-upgraded', upgrades.join(',')); 246.366 + 246.367 + var ev; 246.368 + if ('CustomEvent' in window && typeof window.CustomEvent === 'function') { 246.369 + ev = new CustomEvent('mdl-componentdowngraded', { 246.370 + bubbles: true, cancelable: false 246.371 + }); 246.372 + } else { 246.373 + ev = document.createEvent('Events'); 246.374 + ev.initEvent('mdl-componentdowngraded', true, true); 246.375 + } 246.376 + component.element_.dispatchEvent(ev); 246.377 + } 246.378 + } 246.379 + 246.380 + /** 246.381 + * Downgrade either a given node, an array of nodes, or a NodeList. 246.382 + * 246.383 + * @param {!Node|!Array<!Node>|!NodeList} nodes 246.384 + */ 246.385 + function downgradeNodesInternal(nodes) { 246.386 + /** 246.387 + * Auxiliary function to downgrade a single node. 246.388 + * @param {!Node} node the node to be downgraded 246.389 + */ 246.390 + var downgradeNode = function(node) { 246.391 + createdComponents_.filter(function(item) { 246.392 + return item.element_ === node; 246.393 + }).forEach(deconstructComponentInternal); 246.394 + }; 246.395 + if (nodes instanceof Array || nodes instanceof NodeList) { 246.396 + for (var n = 0; n < nodes.length; n++) { 246.397 + downgradeNode(nodes[n]); 246.398 + } 246.399 + } else if (nodes instanceof Node) { 246.400 + downgradeNode(nodes); 246.401 + } else { 246.402 + throw new Error('Invalid argument provided to downgrade MDL nodes.'); 246.403 + } 246.404 + } 246.405 + 246.406 + // Now return the functions that should be made public with their publicly 246.407 + // facing names... 246.408 + return { 246.409 + upgradeDom: upgradeDomInternal, 246.410 + upgradeElement: upgradeElementInternal, 246.411 + upgradeElements: upgradeElementsInternal, 246.412 + upgradeAllRegistered: upgradeAllRegisteredInternal, 246.413 + registerUpgradedCallback: registerUpgradedCallbackInternal, 246.414 + register: registerInternal, 246.415 + downgradeElements: downgradeNodesInternal 246.416 + }; 246.417 +})(); 246.418 + 246.419 +/** 246.420 + * Describes the type of a registered component type managed by 246.421 + * componentHandler. Provided for benefit of the Closure compiler. 246.422 + * 246.423 + * @typedef {{ 246.424 + * constructor: Function, 246.425 + * classAsString: string, 246.426 + * cssClass: string, 246.427 + * widget: (string|boolean|undefined) 246.428 + * }} 246.429 + */ 246.430 +componentHandler.ComponentConfigPublic; // jshint ignore:line 246.431 + 246.432 +/** 246.433 + * Describes the type of a registered component type managed by 246.434 + * componentHandler. Provided for benefit of the Closure compiler. 246.435 + * 246.436 + * @typedef {{ 246.437 + * constructor: !Function, 246.438 + * className: string, 246.439 + * cssClass: string, 246.440 + * widget: (string|boolean), 246.441 + * callbacks: !Array<function(!HTMLElement)> 246.442 + * }} 246.443 + */ 246.444 +componentHandler.ComponentConfig; // jshint ignore:line 246.445 + 246.446 +/** 246.447 + * Created component (i.e., upgraded element) type as managed by 246.448 + * componentHandler. Provided for benefit of the Closure compiler. 246.449 + * 246.450 + * @typedef {{ 246.451 + * element_: !HTMLElement, 246.452 + * className: string, 246.453 + * classAsString: string, 246.454 + * cssClass: string, 246.455 + * widget: string 246.456 + * }} 246.457 + */ 246.458 +componentHandler.Component; // jshint ignore:line 246.459 + 246.460 +// Export all symbols, for the benefit of Closure compiler. 246.461 +// No effect on uncompiled code. 246.462 +componentHandler['upgradeDom'] = componentHandler.upgradeDom; 246.463 +componentHandler['upgradeElement'] = componentHandler.upgradeElement; 246.464 +componentHandler['upgradeElements'] = componentHandler.upgradeElements; 246.465 +componentHandler['upgradeAllRegistered'] = 246.466 + componentHandler.upgradeAllRegistered; 246.467 +componentHandler['registerUpgradedCallback'] = 246.468 + componentHandler.registerUpgradedCallback; 246.469 +componentHandler['register'] = componentHandler.register; 246.470 +componentHandler['downgradeElements'] = componentHandler.downgradeElements; 246.471 +window.componentHandler = componentHandler; 246.472 +window['componentHandler'] = componentHandler; 246.473 + 246.474 +window.addEventListener('load', function() { 246.475 + 'use strict'; 246.476 + 246.477 + /** 246.478 + * Performs a "Cutting the mustard" test. If the browser supports the features 246.479 + * tested, adds a mdl-js class to the <html> element. It then upgrades all MDL 246.480 + * components requiring JavaScript. 246.481 + */ 246.482 + if ('classList' in document.createElement('div') && 246.483 + 'querySelector' in document && 246.484 + 'addEventListener' in window && Array.prototype.forEach) { 246.485 + document.documentElement.classList.add('mdl-js'); 246.486 + componentHandler.upgradeAllRegistered(); 246.487 + } else { 246.488 + /** 246.489 + * Dummy function to avoid JS errors. 246.490 + */ 246.491 + componentHandler.upgradeElement = function() {}; 246.492 + /** 246.493 + * Dummy function to avoid JS errors. 246.494 + */ 246.495 + componentHandler.register = function() {}; 246.496 + } 246.497 +}); 246.498 + 246.499 +// Source: https://github.com/darius/requestAnimationFrame/blob/master/requestAnimationFrame.js 246.500 +// Adapted from https://gist.github.com/paulirish/1579671 which derived from 246.501 +// http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 246.502 +// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 246.503 +// requestAnimationFrame polyfill by Erik Möller. 246.504 +// Fixes from Paul Irish, Tino Zijdel, Andrew Mao, Klemen Slavič, Darius Bacon 246.505 +// MIT license 246.506 +if (!Date.now) { 246.507 + /** 246.508 + * Date.now polyfill. 246.509 + * @return {number} the current Date 246.510 + */ 246.511 + Date.now = function () { 246.512 + return new Date().getTime(); 246.513 + }; 246.514 + Date['now'] = Date.now; 246.515 +} 246.516 +var vendors = [ 246.517 + 'webkit', 246.518 + 'moz' 246.519 +]; 246.520 +for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { 246.521 + var vp = vendors[i]; 246.522 + window.requestAnimationFrame = window[vp + 'RequestAnimationFrame']; 246.523 + window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame']; 246.524 + window['requestAnimationFrame'] = window.requestAnimationFrame; 246.525 + window['cancelAnimationFrame'] = window.cancelAnimationFrame; 246.526 +} 246.527 +if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) || !window.requestAnimationFrame || !window.cancelAnimationFrame) { 246.528 + var lastTime = 0; 246.529 + /** 246.530 + * requestAnimationFrame polyfill. 246.531 + * @param {!Function} callback the callback function. 246.532 + */ 246.533 + window.requestAnimationFrame = function (callback) { 246.534 + var now = Date.now(); 246.535 + var nextTime = Math.max(lastTime + 16, now); 246.536 + return setTimeout(function () { 246.537 + callback(lastTime = nextTime); 246.538 + }, nextTime - now); 246.539 + }; 246.540 + window.cancelAnimationFrame = clearTimeout; 246.541 + window['requestAnimationFrame'] = window.requestAnimationFrame; 246.542 + window['cancelAnimationFrame'] = window.cancelAnimationFrame; 246.543 +} 246.544 +/** 246.545 + * @license 246.546 + * Copyright 2015 Google Inc. All Rights Reserved. 246.547 + * 246.548 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.549 + * you may not use this file except in compliance with the License. 246.550 + * You may obtain a copy of the License at 246.551 + * 246.552 + * http://www.apache.org/licenses/LICENSE-2.0 246.553 + * 246.554 + * Unless required by applicable law or agreed to in writing, software 246.555 + * distributed under the License is distributed on an "AS IS" BASIS, 246.556 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.557 + * See the License for the specific language governing permissions and 246.558 + * limitations under the License. 246.559 + */ 246.560 +/** 246.561 + * Class constructor for Button MDL component. 246.562 + * Implements MDL component design pattern defined at: 246.563 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.564 + * 246.565 + * @param {HTMLElement} element The element that will be upgraded. 246.566 + */ 246.567 +var MaterialButton = function MaterialButton(element) { 246.568 + this.element_ = element; 246.569 + // Initialize instance. 246.570 + this.init(); 246.571 +}; 246.572 +window['MaterialButton'] = MaterialButton; 246.573 +/** 246.574 + * Store constants in one place so they can be updated easily. 246.575 + * 246.576 + * @enum {string | number} 246.577 + * @private 246.578 + */ 246.579 +MaterialButton.prototype.Constant_ = {}; 246.580 +/** 246.581 + * Store strings for class names defined by this component that are used in 246.582 + * JavaScript. This allows us to simply change it in one place should we 246.583 + * decide to modify at a later date. 246.584 + * 246.585 + * @enum {string} 246.586 + * @private 246.587 + */ 246.588 +MaterialButton.prototype.CssClasses_ = { 246.589 + RIPPLE_EFFECT: 'mdl-js-ripple-effect', 246.590 + RIPPLE_CONTAINER: 'mdl-button__ripple-container', 246.591 + RIPPLE: 'mdl-ripple' 246.592 +}; 246.593 +/** 246.594 + * Handle blur of element. 246.595 + * 246.596 + * @param {Event} event The event that fired. 246.597 + * @private 246.598 + */ 246.599 +MaterialButton.prototype.blurHandler_ = function (event) { 246.600 + if (event) { 246.601 + this.element_.blur(); 246.602 + } 246.603 +}; 246.604 +// Public methods. 246.605 +/** 246.606 + * Disable button. 246.607 + * 246.608 + * @public 246.609 + */ 246.610 +MaterialButton.prototype.disable = function () { 246.611 + this.element_.disabled = true; 246.612 +}; 246.613 +MaterialButton.prototype['disable'] = MaterialButton.prototype.disable; 246.614 +/** 246.615 + * Enable button. 246.616 + * 246.617 + * @public 246.618 + */ 246.619 +MaterialButton.prototype.enable = function () { 246.620 + this.element_.disabled = false; 246.621 +}; 246.622 +MaterialButton.prototype['enable'] = MaterialButton.prototype.enable; 246.623 +/** 246.624 + * Initialize element. 246.625 + */ 246.626 +MaterialButton.prototype.init = function () { 246.627 + if (this.element_) { 246.628 + if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { 246.629 + var rippleContainer = document.createElement('span'); 246.630 + rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER); 246.631 + this.rippleElement_ = document.createElement('span'); 246.632 + this.rippleElement_.classList.add(this.CssClasses_.RIPPLE); 246.633 + rippleContainer.appendChild(this.rippleElement_); 246.634 + this.boundRippleBlurHandler = this.blurHandler_.bind(this); 246.635 + this.rippleElement_.addEventListener('mouseup', this.boundRippleBlurHandler); 246.636 + this.element_.appendChild(rippleContainer); 246.637 + } 246.638 + this.boundButtonBlurHandler = this.blurHandler_.bind(this); 246.639 + this.element_.addEventListener('mouseup', this.boundButtonBlurHandler); 246.640 + this.element_.addEventListener('mouseleave', this.boundButtonBlurHandler); 246.641 + } 246.642 +}; 246.643 +// The component registers itself. It can assume componentHandler is available 246.644 +// in the global scope. 246.645 +componentHandler.register({ 246.646 + constructor: MaterialButton, 246.647 + classAsString: 'MaterialButton', 246.648 + cssClass: 'mdl-js-button', 246.649 + widget: true 246.650 +}); 246.651 +/** 246.652 + * @license 246.653 + * Copyright 2015 Google Inc. All Rights Reserved. 246.654 + * 246.655 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.656 + * you may not use this file except in compliance with the License. 246.657 + * You may obtain a copy of the License at 246.658 + * 246.659 + * http://www.apache.org/licenses/LICENSE-2.0 246.660 + * 246.661 + * Unless required by applicable law or agreed to in writing, software 246.662 + * distributed under the License is distributed on an "AS IS" BASIS, 246.663 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.664 + * See the License for the specific language governing permissions and 246.665 + * limitations under the License. 246.666 + */ 246.667 +/** 246.668 + * Class constructor for Checkbox MDL component. 246.669 + * Implements MDL component design pattern defined at: 246.670 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.671 + * 246.672 + * @constructor 246.673 + * @param {HTMLElement} element The element that will be upgraded. 246.674 + */ 246.675 +var MaterialCheckbox = function MaterialCheckbox(element) { 246.676 + this.element_ = element; 246.677 + // Initialize instance. 246.678 + this.init(); 246.679 +}; 246.680 +window['MaterialCheckbox'] = MaterialCheckbox; 246.681 +/** 246.682 + * Store constants in one place so they can be updated easily. 246.683 + * 246.684 + * @enum {string | number} 246.685 + * @private 246.686 + */ 246.687 +MaterialCheckbox.prototype.Constant_ = { TINY_TIMEOUT: 0.001 }; 246.688 +/** 246.689 + * Store strings for class names defined by this component that are used in 246.690 + * JavaScript. This allows us to simply change it in one place should we 246.691 + * decide to modify at a later date. 246.692 + * 246.693 + * @enum {string} 246.694 + * @private 246.695 + */ 246.696 +MaterialCheckbox.prototype.CssClasses_ = { 246.697 + INPUT: 'mdl-checkbox__input', 246.698 + BOX_OUTLINE: 'mdl-checkbox__box-outline', 246.699 + FOCUS_HELPER: 'mdl-checkbox__focus-helper', 246.700 + TICK_OUTLINE: 'mdl-checkbox__tick-outline', 246.701 + RIPPLE_EFFECT: 'mdl-js-ripple-effect', 246.702 + RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 246.703 + RIPPLE_CONTAINER: 'mdl-checkbox__ripple-container', 246.704 + RIPPLE_CENTER: 'mdl-ripple--center', 246.705 + RIPPLE: 'mdl-ripple', 246.706 + IS_FOCUSED: 'is-focused', 246.707 + IS_DISABLED: 'is-disabled', 246.708 + IS_CHECKED: 'is-checked', 246.709 + IS_UPGRADED: 'is-upgraded' 246.710 +}; 246.711 +/** 246.712 + * Handle change of state. 246.713 + * 246.714 + * @param {Event} event The event that fired. 246.715 + * @private 246.716 + */ 246.717 +MaterialCheckbox.prototype.onChange_ = function (event) { 246.718 + this.updateClasses_(); 246.719 +}; 246.720 +/** 246.721 + * Handle focus of element. 246.722 + * 246.723 + * @param {Event} event The event that fired. 246.724 + * @private 246.725 + */ 246.726 +MaterialCheckbox.prototype.onFocus_ = function (event) { 246.727 + this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 246.728 +}; 246.729 +/** 246.730 + * Handle lost focus of element. 246.731 + * 246.732 + * @param {Event} event The event that fired. 246.733 + * @private 246.734 + */ 246.735 +MaterialCheckbox.prototype.onBlur_ = function (event) { 246.736 + this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 246.737 +}; 246.738 +/** 246.739 + * Handle mouseup. 246.740 + * 246.741 + * @param {Event} event The event that fired. 246.742 + * @private 246.743 + */ 246.744 +MaterialCheckbox.prototype.onMouseUp_ = function (event) { 246.745 + this.blur_(); 246.746 +}; 246.747 +/** 246.748 + * Handle class updates. 246.749 + * 246.750 + * @private 246.751 + */ 246.752 +MaterialCheckbox.prototype.updateClasses_ = function () { 246.753 + this.checkDisabled(); 246.754 + this.checkToggleState(); 246.755 +}; 246.756 +/** 246.757 + * Add blur. 246.758 + * 246.759 + * @private 246.760 + */ 246.761 +MaterialCheckbox.prototype.blur_ = function () { 246.762 + // TODO: figure out why there's a focus event being fired after our blur, 246.763 + // so that we can avoid this hack. 246.764 + window.setTimeout(function () { 246.765 + this.inputElement_.blur(); 246.766 + }.bind(this), this.Constant_.TINY_TIMEOUT); 246.767 +}; 246.768 +// Public methods. 246.769 +/** 246.770 + * Check the inputs toggle state and update display. 246.771 + * 246.772 + * @public 246.773 + */ 246.774 +MaterialCheckbox.prototype.checkToggleState = function () { 246.775 + if (this.inputElement_.checked) { 246.776 + this.element_.classList.add(this.CssClasses_.IS_CHECKED); 246.777 + } else { 246.778 + this.element_.classList.remove(this.CssClasses_.IS_CHECKED); 246.779 + } 246.780 +}; 246.781 +MaterialCheckbox.prototype['checkToggleState'] = MaterialCheckbox.prototype.checkToggleState; 246.782 +/** 246.783 + * Check the inputs disabled state and update display. 246.784 + * 246.785 + * @public 246.786 + */ 246.787 +MaterialCheckbox.prototype.checkDisabled = function () { 246.788 + if (this.inputElement_.disabled) { 246.789 + this.element_.classList.add(this.CssClasses_.IS_DISABLED); 246.790 + } else { 246.791 + this.element_.classList.remove(this.CssClasses_.IS_DISABLED); 246.792 + } 246.793 +}; 246.794 +MaterialCheckbox.prototype['checkDisabled'] = MaterialCheckbox.prototype.checkDisabled; 246.795 +/** 246.796 + * Disable checkbox. 246.797 + * 246.798 + * @public 246.799 + */ 246.800 +MaterialCheckbox.prototype.disable = function () { 246.801 + this.inputElement_.disabled = true; 246.802 + this.updateClasses_(); 246.803 +}; 246.804 +MaterialCheckbox.prototype['disable'] = MaterialCheckbox.prototype.disable; 246.805 +/** 246.806 + * Enable checkbox. 246.807 + * 246.808 + * @public 246.809 + */ 246.810 +MaterialCheckbox.prototype.enable = function () { 246.811 + this.inputElement_.disabled = false; 246.812 + this.updateClasses_(); 246.813 +}; 246.814 +MaterialCheckbox.prototype['enable'] = MaterialCheckbox.prototype.enable; 246.815 +/** 246.816 + * Check checkbox. 246.817 + * 246.818 + * @public 246.819 + */ 246.820 +MaterialCheckbox.prototype.check = function () { 246.821 + this.inputElement_.checked = true; 246.822 + this.updateClasses_(); 246.823 +}; 246.824 +MaterialCheckbox.prototype['check'] = MaterialCheckbox.prototype.check; 246.825 +/** 246.826 + * Uncheck checkbox. 246.827 + * 246.828 + * @public 246.829 + */ 246.830 +MaterialCheckbox.prototype.uncheck = function () { 246.831 + this.inputElement_.checked = false; 246.832 + this.updateClasses_(); 246.833 +}; 246.834 +MaterialCheckbox.prototype['uncheck'] = MaterialCheckbox.prototype.uncheck; 246.835 +/** 246.836 + * Initialize element. 246.837 + */ 246.838 +MaterialCheckbox.prototype.init = function () { 246.839 + if (this.element_) { 246.840 + this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); 246.841 + var boxOutline = document.createElement('span'); 246.842 + boxOutline.classList.add(this.CssClasses_.BOX_OUTLINE); 246.843 + var tickContainer = document.createElement('span'); 246.844 + tickContainer.classList.add(this.CssClasses_.FOCUS_HELPER); 246.845 + var tickOutline = document.createElement('span'); 246.846 + tickOutline.classList.add(this.CssClasses_.TICK_OUTLINE); 246.847 + boxOutline.appendChild(tickOutline); 246.848 + this.element_.appendChild(tickContainer); 246.849 + this.element_.appendChild(boxOutline); 246.850 + if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { 246.851 + this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 246.852 + this.rippleContainerElement_ = document.createElement('span'); 246.853 + this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER); 246.854 + this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT); 246.855 + this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER); 246.856 + this.boundRippleMouseUp = this.onMouseUp_.bind(this); 246.857 + this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp); 246.858 + var ripple = document.createElement('span'); 246.859 + ripple.classList.add(this.CssClasses_.RIPPLE); 246.860 + this.rippleContainerElement_.appendChild(ripple); 246.861 + this.element_.appendChild(this.rippleContainerElement_); 246.862 + } 246.863 + this.boundInputOnChange = this.onChange_.bind(this); 246.864 + this.boundInputOnFocus = this.onFocus_.bind(this); 246.865 + this.boundInputOnBlur = this.onBlur_.bind(this); 246.866 + this.boundElementMouseUp = this.onMouseUp_.bind(this); 246.867 + this.inputElement_.addEventListener('change', this.boundInputOnChange); 246.868 + this.inputElement_.addEventListener('focus', this.boundInputOnFocus); 246.869 + this.inputElement_.addEventListener('blur', this.boundInputOnBlur); 246.870 + this.element_.addEventListener('mouseup', this.boundElementMouseUp); 246.871 + this.updateClasses_(); 246.872 + this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 246.873 + } 246.874 +}; 246.875 +// The component registers itself. It can assume componentHandler is available 246.876 +// in the global scope. 246.877 +componentHandler.register({ 246.878 + constructor: MaterialCheckbox, 246.879 + classAsString: 'MaterialCheckbox', 246.880 + cssClass: 'mdl-js-checkbox', 246.881 + widget: true 246.882 +}); 246.883 +/** 246.884 + * @license 246.885 + * Copyright 2015 Google Inc. All Rights Reserved. 246.886 + * 246.887 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.888 + * you may not use this file except in compliance with the License. 246.889 + * You may obtain a copy of the License at 246.890 + * 246.891 + * http://www.apache.org/licenses/LICENSE-2.0 246.892 + * 246.893 + * Unless required by applicable law or agreed to in writing, software 246.894 + * distributed under the License is distributed on an "AS IS" BASIS, 246.895 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.896 + * See the License for the specific language governing permissions and 246.897 + * limitations under the License. 246.898 + */ 246.899 +/** 246.900 + * Class constructor for icon toggle MDL component. 246.901 + * Implements MDL component design pattern defined at: 246.902 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.903 + * 246.904 + * @constructor 246.905 + * @param {HTMLElement} element The element that will be upgraded. 246.906 + */ 246.907 +var MaterialIconToggle = function MaterialIconToggle(element) { 246.908 + this.element_ = element; 246.909 + // Initialize instance. 246.910 + this.init(); 246.911 +}; 246.912 +window['MaterialIconToggle'] = MaterialIconToggle; 246.913 +/** 246.914 + * Store constants in one place so they can be updated easily. 246.915 + * 246.916 + * @enum {string | number} 246.917 + * @private 246.918 + */ 246.919 +MaterialIconToggle.prototype.Constant_ = { TINY_TIMEOUT: 0.001 }; 246.920 +/** 246.921 + * Store strings for class names defined by this component that are used in 246.922 + * JavaScript. This allows us to simply change it in one place should we 246.923 + * decide to modify at a later date. 246.924 + * 246.925 + * @enum {string} 246.926 + * @private 246.927 + */ 246.928 +MaterialIconToggle.prototype.CssClasses_ = { 246.929 + INPUT: 'mdl-icon-toggle__input', 246.930 + JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect', 246.931 + RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 246.932 + RIPPLE_CONTAINER: 'mdl-icon-toggle__ripple-container', 246.933 + RIPPLE_CENTER: 'mdl-ripple--center', 246.934 + RIPPLE: 'mdl-ripple', 246.935 + IS_FOCUSED: 'is-focused', 246.936 + IS_DISABLED: 'is-disabled', 246.937 + IS_CHECKED: 'is-checked' 246.938 +}; 246.939 +/** 246.940 + * Handle change of state. 246.941 + * 246.942 + * @param {Event} event The event that fired. 246.943 + * @private 246.944 + */ 246.945 +MaterialIconToggle.prototype.onChange_ = function (event) { 246.946 + this.updateClasses_(); 246.947 +}; 246.948 +/** 246.949 + * Handle focus of element. 246.950 + * 246.951 + * @param {Event} event The event that fired. 246.952 + * @private 246.953 + */ 246.954 +MaterialIconToggle.prototype.onFocus_ = function (event) { 246.955 + this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 246.956 +}; 246.957 +/** 246.958 + * Handle lost focus of element. 246.959 + * 246.960 + * @param {Event} event The event that fired. 246.961 + * @private 246.962 + */ 246.963 +MaterialIconToggle.prototype.onBlur_ = function (event) { 246.964 + this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 246.965 +}; 246.966 +/** 246.967 + * Handle mouseup. 246.968 + * 246.969 + * @param {Event} event The event that fired. 246.970 + * @private 246.971 + */ 246.972 +MaterialIconToggle.prototype.onMouseUp_ = function (event) { 246.973 + this.blur_(); 246.974 +}; 246.975 +/** 246.976 + * Handle class updates. 246.977 + * 246.978 + * @private 246.979 + */ 246.980 +MaterialIconToggle.prototype.updateClasses_ = function () { 246.981 + this.checkDisabled(); 246.982 + this.checkToggleState(); 246.983 +}; 246.984 +/** 246.985 + * Add blur. 246.986 + * 246.987 + * @private 246.988 + */ 246.989 +MaterialIconToggle.prototype.blur_ = function () { 246.990 + // TODO: figure out why there's a focus event being fired after our blur, 246.991 + // so that we can avoid this hack. 246.992 + window.setTimeout(function () { 246.993 + this.inputElement_.blur(); 246.994 + }.bind(this), this.Constant_.TINY_TIMEOUT); 246.995 +}; 246.996 +// Public methods. 246.997 +/** 246.998 + * Check the inputs toggle state and update display. 246.999 + * 246.1000 + * @public 246.1001 + */ 246.1002 +MaterialIconToggle.prototype.checkToggleState = function () { 246.1003 + if (this.inputElement_.checked) { 246.1004 + this.element_.classList.add(this.CssClasses_.IS_CHECKED); 246.1005 + } else { 246.1006 + this.element_.classList.remove(this.CssClasses_.IS_CHECKED); 246.1007 + } 246.1008 +}; 246.1009 +MaterialIconToggle.prototype['checkToggleState'] = MaterialIconToggle.prototype.checkToggleState; 246.1010 +/** 246.1011 + * Check the inputs disabled state and update display. 246.1012 + * 246.1013 + * @public 246.1014 + */ 246.1015 +MaterialIconToggle.prototype.checkDisabled = function () { 246.1016 + if (this.inputElement_.disabled) { 246.1017 + this.element_.classList.add(this.CssClasses_.IS_DISABLED); 246.1018 + } else { 246.1019 + this.element_.classList.remove(this.CssClasses_.IS_DISABLED); 246.1020 + } 246.1021 +}; 246.1022 +MaterialIconToggle.prototype['checkDisabled'] = MaterialIconToggle.prototype.checkDisabled; 246.1023 +/** 246.1024 + * Disable icon toggle. 246.1025 + * 246.1026 + * @public 246.1027 + */ 246.1028 +MaterialIconToggle.prototype.disable = function () { 246.1029 + this.inputElement_.disabled = true; 246.1030 + this.updateClasses_(); 246.1031 +}; 246.1032 +MaterialIconToggle.prototype['disable'] = MaterialIconToggle.prototype.disable; 246.1033 +/** 246.1034 + * Enable icon toggle. 246.1035 + * 246.1036 + * @public 246.1037 + */ 246.1038 +MaterialIconToggle.prototype.enable = function () { 246.1039 + this.inputElement_.disabled = false; 246.1040 + this.updateClasses_(); 246.1041 +}; 246.1042 +MaterialIconToggle.prototype['enable'] = MaterialIconToggle.prototype.enable; 246.1043 +/** 246.1044 + * Check icon toggle. 246.1045 + * 246.1046 + * @public 246.1047 + */ 246.1048 +MaterialIconToggle.prototype.check = function () { 246.1049 + this.inputElement_.checked = true; 246.1050 + this.updateClasses_(); 246.1051 +}; 246.1052 +MaterialIconToggle.prototype['check'] = MaterialIconToggle.prototype.check; 246.1053 +/** 246.1054 + * Uncheck icon toggle. 246.1055 + * 246.1056 + * @public 246.1057 + */ 246.1058 +MaterialIconToggle.prototype.uncheck = function () { 246.1059 + this.inputElement_.checked = false; 246.1060 + this.updateClasses_(); 246.1061 +}; 246.1062 +MaterialIconToggle.prototype['uncheck'] = MaterialIconToggle.prototype.uncheck; 246.1063 +/** 246.1064 + * Initialize element. 246.1065 + */ 246.1066 +MaterialIconToggle.prototype.init = function () { 246.1067 + if (this.element_) { 246.1068 + this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); 246.1069 + if (this.element_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) { 246.1070 + this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 246.1071 + this.rippleContainerElement_ = document.createElement('span'); 246.1072 + this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER); 246.1073 + this.rippleContainerElement_.classList.add(this.CssClasses_.JS_RIPPLE_EFFECT); 246.1074 + this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER); 246.1075 + this.boundRippleMouseUp = this.onMouseUp_.bind(this); 246.1076 + this.rippleContainerElement_.addEventListener('mouseup', this.boundRippleMouseUp); 246.1077 + var ripple = document.createElement('span'); 246.1078 + ripple.classList.add(this.CssClasses_.RIPPLE); 246.1079 + this.rippleContainerElement_.appendChild(ripple); 246.1080 + this.element_.appendChild(this.rippleContainerElement_); 246.1081 + } 246.1082 + this.boundInputOnChange = this.onChange_.bind(this); 246.1083 + this.boundInputOnFocus = this.onFocus_.bind(this); 246.1084 + this.boundInputOnBlur = this.onBlur_.bind(this); 246.1085 + this.boundElementOnMouseUp = this.onMouseUp_.bind(this); 246.1086 + this.inputElement_.addEventListener('change', this.boundInputOnChange); 246.1087 + this.inputElement_.addEventListener('focus', this.boundInputOnFocus); 246.1088 + this.inputElement_.addEventListener('blur', this.boundInputOnBlur); 246.1089 + this.element_.addEventListener('mouseup', this.boundElementOnMouseUp); 246.1090 + this.updateClasses_(); 246.1091 + this.element_.classList.add('is-upgraded'); 246.1092 + } 246.1093 +}; 246.1094 +// The component registers itself. It can assume componentHandler is available 246.1095 +// in the global scope. 246.1096 +componentHandler.register({ 246.1097 + constructor: MaterialIconToggle, 246.1098 + classAsString: 'MaterialIconToggle', 246.1099 + cssClass: 'mdl-js-icon-toggle', 246.1100 + widget: true 246.1101 +}); 246.1102 +/** 246.1103 + * @license 246.1104 + * Copyright 2015 Google Inc. All Rights Reserved. 246.1105 + * 246.1106 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.1107 + * you may not use this file except in compliance with the License. 246.1108 + * You may obtain a copy of the License at 246.1109 + * 246.1110 + * http://www.apache.org/licenses/LICENSE-2.0 246.1111 + * 246.1112 + * Unless required by applicable law or agreed to in writing, software 246.1113 + * distributed under the License is distributed on an "AS IS" BASIS, 246.1114 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.1115 + * See the License for the specific language governing permissions and 246.1116 + * limitations under the License. 246.1117 + */ 246.1118 +/** 246.1119 + * Class constructor for dropdown MDL component. 246.1120 + * Implements MDL component design pattern defined at: 246.1121 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.1122 + * 246.1123 + * @constructor 246.1124 + * @param {HTMLElement} element The element that will be upgraded. 246.1125 + */ 246.1126 +var MaterialMenu = function MaterialMenu(element) { 246.1127 + this.element_ = element; 246.1128 + // Initialize instance. 246.1129 + this.init(); 246.1130 +}; 246.1131 +window['MaterialMenu'] = MaterialMenu; 246.1132 +/** 246.1133 + * Store constants in one place so they can be updated easily. 246.1134 + * 246.1135 + * @enum {string | number} 246.1136 + * @private 246.1137 + */ 246.1138 +MaterialMenu.prototype.Constant_ = { 246.1139 + // Total duration of the menu animation. 246.1140 + TRANSITION_DURATION_SECONDS: 0.3, 246.1141 + // The fraction of the total duration we want to use for menu item animations. 246.1142 + TRANSITION_DURATION_FRACTION: 0.8, 246.1143 + // How long the menu stays open after choosing an option (so the user can see 246.1144 + // the ripple). 246.1145 + CLOSE_TIMEOUT: 150 246.1146 +}; 246.1147 +/** 246.1148 + * Keycodes, for code readability. 246.1149 + * 246.1150 + * @enum {number} 246.1151 + * @private 246.1152 + */ 246.1153 +MaterialMenu.prototype.Keycodes_ = { 246.1154 + ENTER: 13, 246.1155 + ESCAPE: 27, 246.1156 + SPACE: 32, 246.1157 + UP_ARROW: 38, 246.1158 + DOWN_ARROW: 40 246.1159 +}; 246.1160 +/** 246.1161 + * Store strings for class names defined by this component that are used in 246.1162 + * JavaScript. This allows us to simply change it in one place should we 246.1163 + * decide to modify at a later date. 246.1164 + * 246.1165 + * @enum {string} 246.1166 + * @private 246.1167 + */ 246.1168 +MaterialMenu.prototype.CssClasses_ = { 246.1169 + CONTAINER: 'mdl-menu__container', 246.1170 + OUTLINE: 'mdl-menu__outline', 246.1171 + ITEM: 'mdl-menu__item', 246.1172 + ITEM_RIPPLE_CONTAINER: 'mdl-menu__item-ripple-container', 246.1173 + RIPPLE_EFFECT: 'mdl-js-ripple-effect', 246.1174 + RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 246.1175 + RIPPLE: 'mdl-ripple', 246.1176 + // Statuses 246.1177 + IS_UPGRADED: 'is-upgraded', 246.1178 + IS_VISIBLE: 'is-visible', 246.1179 + IS_ANIMATING: 'is-animating', 246.1180 + // Alignment options 246.1181 + BOTTOM_LEFT: 'mdl-menu--bottom-left', 246.1182 + // This is the default. 246.1183 + BOTTOM_RIGHT: 'mdl-menu--bottom-right', 246.1184 + TOP_LEFT: 'mdl-menu--top-left', 246.1185 + TOP_RIGHT: 'mdl-menu--top-right', 246.1186 + UNALIGNED: 'mdl-menu--unaligned' 246.1187 +}; 246.1188 +/** 246.1189 + * Initialize element. 246.1190 + */ 246.1191 +MaterialMenu.prototype.init = function () { 246.1192 + if (this.element_) { 246.1193 + // Create container for the menu. 246.1194 + var container = document.createElement('div'); 246.1195 + container.classList.add(this.CssClasses_.CONTAINER); 246.1196 + this.element_.parentElement.insertBefore(container, this.element_); 246.1197 + this.element_.parentElement.removeChild(this.element_); 246.1198 + container.appendChild(this.element_); 246.1199 + this.container_ = container; 246.1200 + // Create outline for the menu (shadow and background). 246.1201 + var outline = document.createElement('div'); 246.1202 + outline.classList.add(this.CssClasses_.OUTLINE); 246.1203 + this.outline_ = outline; 246.1204 + container.insertBefore(outline, this.element_); 246.1205 + // Find the "for" element and bind events to it. 246.1206 + var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for'); 246.1207 + var forEl = null; 246.1208 + if (forElId) { 246.1209 + forEl = document.getElementById(forElId); 246.1210 + if (forEl) { 246.1211 + this.forElement_ = forEl; 246.1212 + forEl.addEventListener('click', this.handleForClick_.bind(this)); 246.1213 + forEl.addEventListener('keydown', this.handleForKeyboardEvent_.bind(this)); 246.1214 + } 246.1215 + } 246.1216 + var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM); 246.1217 + this.boundItemKeydown_ = this.handleItemKeyboardEvent_.bind(this); 246.1218 + this.boundItemClick_ = this.handleItemClick_.bind(this); 246.1219 + for (var i = 0; i < items.length; i++) { 246.1220 + // Add a listener to each menu item. 246.1221 + items[i].addEventListener('click', this.boundItemClick_); 246.1222 + // Add a tab index to each menu item. 246.1223 + items[i].tabIndex = '-1'; 246.1224 + // Add a keyboard listener to each menu item. 246.1225 + items[i].addEventListener('keydown', this.boundItemKeydown_); 246.1226 + } 246.1227 + // Add ripple classes to each item, if the user has enabled ripples. 246.1228 + if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { 246.1229 + this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 246.1230 + for (i = 0; i < items.length; i++) { 246.1231 + var item = items[i]; 246.1232 + var rippleContainer = document.createElement('span'); 246.1233 + rippleContainer.classList.add(this.CssClasses_.ITEM_RIPPLE_CONTAINER); 246.1234 + var ripple = document.createElement('span'); 246.1235 + ripple.classList.add(this.CssClasses_.RIPPLE); 246.1236 + rippleContainer.appendChild(ripple); 246.1237 + item.appendChild(rippleContainer); 246.1238 + item.classList.add(this.CssClasses_.RIPPLE_EFFECT); 246.1239 + } 246.1240 + } 246.1241 + // Copy alignment classes to the container, so the outline can use them. 246.1242 + if (this.element_.classList.contains(this.CssClasses_.BOTTOM_LEFT)) { 246.1243 + this.outline_.classList.add(this.CssClasses_.BOTTOM_LEFT); 246.1244 + } 246.1245 + if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) { 246.1246 + this.outline_.classList.add(this.CssClasses_.BOTTOM_RIGHT); 246.1247 + } 246.1248 + if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) { 246.1249 + this.outline_.classList.add(this.CssClasses_.TOP_LEFT); 246.1250 + } 246.1251 + if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { 246.1252 + this.outline_.classList.add(this.CssClasses_.TOP_RIGHT); 246.1253 + } 246.1254 + if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) { 246.1255 + this.outline_.classList.add(this.CssClasses_.UNALIGNED); 246.1256 + } 246.1257 + container.classList.add(this.CssClasses_.IS_UPGRADED); 246.1258 + } 246.1259 +}; 246.1260 +/** 246.1261 + * Handles a click on the "for" element, by positioning the menu and then 246.1262 + * toggling it. 246.1263 + * 246.1264 + * @param {Event} evt The event that fired. 246.1265 + * @private 246.1266 + */ 246.1267 +MaterialMenu.prototype.handleForClick_ = function (evt) { 246.1268 + if (this.element_ && this.forElement_) { 246.1269 + var rect = this.forElement_.getBoundingClientRect(); 246.1270 + var forRect = this.forElement_.parentElement.getBoundingClientRect(); 246.1271 + if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) { 246.1272 + } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) { 246.1273 + // Position below the "for" element, aligned to its right. 246.1274 + this.container_.style.right = forRect.right - rect.right + 'px'; 246.1275 + this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px'; 246.1276 + } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) { 246.1277 + // Position above the "for" element, aligned to its left. 246.1278 + this.container_.style.left = this.forElement_.offsetLeft + 'px'; 246.1279 + this.container_.style.bottom = forRect.bottom - rect.top + 'px'; 246.1280 + } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { 246.1281 + // Position above the "for" element, aligned to its right. 246.1282 + this.container_.style.right = forRect.right - rect.right + 'px'; 246.1283 + this.container_.style.bottom = forRect.bottom - rect.top + 'px'; 246.1284 + } else { 246.1285 + // Default: position below the "for" element, aligned to its left. 246.1286 + this.container_.style.left = this.forElement_.offsetLeft + 'px'; 246.1287 + this.container_.style.top = this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px'; 246.1288 + } 246.1289 + } 246.1290 + this.toggle(evt); 246.1291 +}; 246.1292 +/** 246.1293 + * Handles a keyboard event on the "for" element. 246.1294 + * 246.1295 + * @param {Event} evt The event that fired. 246.1296 + * @private 246.1297 + */ 246.1298 +MaterialMenu.prototype.handleForKeyboardEvent_ = function (evt) { 246.1299 + if (this.element_ && this.container_ && this.forElement_) { 246.1300 + var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])'); 246.1301 + if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) { 246.1302 + if (evt.keyCode === this.Keycodes_.UP_ARROW) { 246.1303 + evt.preventDefault(); 246.1304 + items[items.length - 1].focus(); 246.1305 + } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) { 246.1306 + evt.preventDefault(); 246.1307 + items[0].focus(); 246.1308 + } 246.1309 + } 246.1310 + } 246.1311 +}; 246.1312 +/** 246.1313 + * Handles a keyboard event on an item. 246.1314 + * 246.1315 + * @param {Event} evt The event that fired. 246.1316 + * @private 246.1317 + */ 246.1318 +MaterialMenu.prototype.handleItemKeyboardEvent_ = function (evt) { 246.1319 + if (this.element_ && this.container_) { 246.1320 + var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM + ':not([disabled])'); 246.1321 + if (items && items.length > 0 && this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) { 246.1322 + var currentIndex = Array.prototype.slice.call(items).indexOf(evt.target); 246.1323 + if (evt.keyCode === this.Keycodes_.UP_ARROW) { 246.1324 + evt.preventDefault(); 246.1325 + if (currentIndex > 0) { 246.1326 + items[currentIndex - 1].focus(); 246.1327 + } else { 246.1328 + items[items.length - 1].focus(); 246.1329 + } 246.1330 + } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) { 246.1331 + evt.preventDefault(); 246.1332 + if (items.length > currentIndex + 1) { 246.1333 + items[currentIndex + 1].focus(); 246.1334 + } else { 246.1335 + items[0].focus(); 246.1336 + } 246.1337 + } else if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) { 246.1338 + evt.preventDefault(); 246.1339 + // Send mousedown and mouseup to trigger ripple. 246.1340 + var e = new MouseEvent('mousedown'); 246.1341 + evt.target.dispatchEvent(e); 246.1342 + e = new MouseEvent('mouseup'); 246.1343 + evt.target.dispatchEvent(e); 246.1344 + // Send click. 246.1345 + evt.target.click(); 246.1346 + } else if (evt.keyCode === this.Keycodes_.ESCAPE) { 246.1347 + evt.preventDefault(); 246.1348 + this.hide(); 246.1349 + } 246.1350 + } 246.1351 + } 246.1352 +}; 246.1353 +/** 246.1354 + * Handles a click event on an item. 246.1355 + * 246.1356 + * @param {Event} evt The event that fired. 246.1357 + * @private 246.1358 + */ 246.1359 +MaterialMenu.prototype.handleItemClick_ = function (evt) { 246.1360 + if (evt.target.hasAttribute('disabled')) { 246.1361 + evt.stopPropagation(); 246.1362 + } else { 246.1363 + // Wait some time before closing menu, so the user can see the ripple. 246.1364 + this.closing_ = true; 246.1365 + window.setTimeout(function (evt) { 246.1366 + this.hide(); 246.1367 + this.closing_ = false; 246.1368 + }.bind(this), this.Constant_.CLOSE_TIMEOUT); 246.1369 + } 246.1370 +}; 246.1371 +/** 246.1372 + * Calculates the initial clip (for opening the menu) or final clip (for closing 246.1373 + * it), and applies it. This allows us to animate from or to the correct point, 246.1374 + * that is, the point it's aligned to in the "for" element. 246.1375 + * 246.1376 + * @param {number} height Height of the clip rectangle 246.1377 + * @param {number} width Width of the clip rectangle 246.1378 + * @private 246.1379 + */ 246.1380 +MaterialMenu.prototype.applyClip_ = function (height, width) { 246.1381 + if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) { 246.1382 + // Do not clip. 246.1383 + this.element_.style.clip = ''; 246.1384 + } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) { 246.1385 + // Clip to the top right corner of the menu. 246.1386 + this.element_.style.clip = 'rect(0 ' + width + 'px ' + '0 ' + width + 'px)'; 246.1387 + } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) { 246.1388 + // Clip to the bottom left corner of the menu. 246.1389 + this.element_.style.clip = 'rect(' + height + 'px 0 ' + height + 'px 0)'; 246.1390 + } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { 246.1391 + // Clip to the bottom right corner of the menu. 246.1392 + this.element_.style.clip = 'rect(' + height + 'px ' + width + 'px ' + height + 'px ' + width + 'px)'; 246.1393 + } else { 246.1394 + // Default: do not clip (same as clipping to the top left corner). 246.1395 + this.element_.style.clip = ''; 246.1396 + } 246.1397 +}; 246.1398 +/** 246.1399 + * Cleanup function to remove animation listeners. 246.1400 + * 246.1401 + * @param {Event} evt 246.1402 + * @private 246.1403 + */ 246.1404 +MaterialMenu.prototype.removeAnimationEndListener_ = function (evt) { 246.1405 + evt.target.classList.remove(MaterialMenu.prototype.CssClasses_.IS_ANIMATING); 246.1406 +}; 246.1407 +/** 246.1408 + * Adds an event listener to clean up after the animation ends. 246.1409 + * 246.1410 + * @private 246.1411 + */ 246.1412 +MaterialMenu.prototype.addAnimationEndListener_ = function () { 246.1413 + this.element_.addEventListener('transitionend', this.removeAnimationEndListener_); 246.1414 + this.element_.addEventListener('webkitTransitionEnd', this.removeAnimationEndListener_); 246.1415 +}; 246.1416 +/** 246.1417 + * Displays the menu. 246.1418 + * 246.1419 + * @public 246.1420 + */ 246.1421 +MaterialMenu.prototype.show = function (evt) { 246.1422 + if (this.element_ && this.container_ && this.outline_) { 246.1423 + // Measure the inner element. 246.1424 + var height = this.element_.getBoundingClientRect().height; 246.1425 + var width = this.element_.getBoundingClientRect().width; 246.1426 + // Apply the inner element's size to the container and outline. 246.1427 + this.container_.style.width = width + 'px'; 246.1428 + this.container_.style.height = height + 'px'; 246.1429 + this.outline_.style.width = width + 'px'; 246.1430 + this.outline_.style.height = height + 'px'; 246.1431 + var transitionDuration = this.Constant_.TRANSITION_DURATION_SECONDS * this.Constant_.TRANSITION_DURATION_FRACTION; 246.1432 + // Calculate transition delays for individual menu items, so that they fade 246.1433 + // in one at a time. 246.1434 + var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM); 246.1435 + for (var i = 0; i < items.length; i++) { 246.1436 + var itemDelay = null; 246.1437 + if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT) || this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) { 246.1438 + itemDelay = (height - items[i].offsetTop - items[i].offsetHeight) / height * transitionDuration + 's'; 246.1439 + } else { 246.1440 + itemDelay = items[i].offsetTop / height * transitionDuration + 's'; 246.1441 + } 246.1442 + items[i].style.transitionDelay = itemDelay; 246.1443 + } 246.1444 + // Apply the initial clip to the text before we start animating. 246.1445 + this.applyClip_(height, width); 246.1446 + // Wait for the next frame, turn on animation, and apply the final clip. 246.1447 + // Also make it visible. This triggers the transitions. 246.1448 + window.requestAnimationFrame(function () { 246.1449 + this.element_.classList.add(this.CssClasses_.IS_ANIMATING); 246.1450 + this.element_.style.clip = 'rect(0 ' + width + 'px ' + height + 'px 0)'; 246.1451 + this.container_.classList.add(this.CssClasses_.IS_VISIBLE); 246.1452 + }.bind(this)); 246.1453 + // Clean up after the animation is complete. 246.1454 + this.addAnimationEndListener_(); 246.1455 + // Add a click listener to the document, to close the menu. 246.1456 + var callback = function (e) { 246.1457 + // Check to see if the document is processing the same event that 246.1458 + // displayed the menu in the first place. If so, do nothing. 246.1459 + // Also check to see if the menu is in the process of closing itself, and 246.1460 + // do nothing in that case. 246.1461 + // Also check if the clicked element is a menu item 246.1462 + // if so, do nothing. 246.1463 + if (e !== evt && !this.closing_ && e.target.parentNode !== this.element_) { 246.1464 + document.removeEventListener('click', callback); 246.1465 + this.hide(); 246.1466 + } 246.1467 + }.bind(this); 246.1468 + document.addEventListener('click', callback); 246.1469 + } 246.1470 +}; 246.1471 +MaterialMenu.prototype['show'] = MaterialMenu.prototype.show; 246.1472 +/** 246.1473 + * Hides the menu. 246.1474 + * 246.1475 + * @public 246.1476 + */ 246.1477 +MaterialMenu.prototype.hide = function () { 246.1478 + if (this.element_ && this.container_ && this.outline_) { 246.1479 + var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM); 246.1480 + // Remove all transition delays; menu items fade out concurrently. 246.1481 + for (var i = 0; i < items.length; i++) { 246.1482 + items[i].style.removeProperty('transition-delay'); 246.1483 + } 246.1484 + // Measure the inner element. 246.1485 + var rect = this.element_.getBoundingClientRect(); 246.1486 + var height = rect.height; 246.1487 + var width = rect.width; 246.1488 + // Turn on animation, and apply the final clip. Also make invisible. 246.1489 + // This triggers the transitions. 246.1490 + this.element_.classList.add(this.CssClasses_.IS_ANIMATING); 246.1491 + this.applyClip_(height, width); 246.1492 + this.container_.classList.remove(this.CssClasses_.IS_VISIBLE); 246.1493 + // Clean up after the animation is complete. 246.1494 + this.addAnimationEndListener_(); 246.1495 + } 246.1496 +}; 246.1497 +MaterialMenu.prototype['hide'] = MaterialMenu.prototype.hide; 246.1498 +/** 246.1499 + * Displays or hides the menu, depending on current state. 246.1500 + * 246.1501 + * @public 246.1502 + */ 246.1503 +MaterialMenu.prototype.toggle = function (evt) { 246.1504 + if (this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) { 246.1505 + this.hide(); 246.1506 + } else { 246.1507 + this.show(evt); 246.1508 + } 246.1509 +}; 246.1510 +MaterialMenu.prototype['toggle'] = MaterialMenu.prototype.toggle; 246.1511 +// The component registers itself. It can assume componentHandler is available 246.1512 +// in the global scope. 246.1513 +componentHandler.register({ 246.1514 + constructor: MaterialMenu, 246.1515 + classAsString: 'MaterialMenu', 246.1516 + cssClass: 'mdl-js-menu', 246.1517 + widget: true 246.1518 +}); 246.1519 +/** 246.1520 + * @license 246.1521 + * Copyright 2015 Google Inc. All Rights Reserved. 246.1522 + * 246.1523 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.1524 + * you may not use this file except in compliance with the License. 246.1525 + * You may obtain a copy of the License at 246.1526 + * 246.1527 + * http://www.apache.org/licenses/LICENSE-2.0 246.1528 + * 246.1529 + * Unless required by applicable law or agreed to in writing, software 246.1530 + * distributed under the License is distributed on an "AS IS" BASIS, 246.1531 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.1532 + * See the License for the specific language governing permissions and 246.1533 + * limitations under the License. 246.1534 + */ 246.1535 +/** 246.1536 + * Class constructor for Progress MDL component. 246.1537 + * Implements MDL component design pattern defined at: 246.1538 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.1539 + * 246.1540 + * @constructor 246.1541 + * @param {HTMLElement} element The element that will be upgraded. 246.1542 + */ 246.1543 +var MaterialProgress = function MaterialProgress(element) { 246.1544 + this.element_ = element; 246.1545 + // Initialize instance. 246.1546 + this.init(); 246.1547 +}; 246.1548 +window['MaterialProgress'] = MaterialProgress; 246.1549 +/** 246.1550 + * Store constants in one place so they can be updated easily. 246.1551 + * 246.1552 + * @enum {string | number} 246.1553 + * @private 246.1554 + */ 246.1555 +MaterialProgress.prototype.Constant_ = {}; 246.1556 +/** 246.1557 + * Store strings for class names defined by this component that are used in 246.1558 + * JavaScript. This allows us to simply change it in one place should we 246.1559 + * decide to modify at a later date. 246.1560 + * 246.1561 + * @enum {string} 246.1562 + * @private 246.1563 + */ 246.1564 +MaterialProgress.prototype.CssClasses_ = { INDETERMINATE_CLASS: 'mdl-progress__indeterminate' }; 246.1565 +/** 246.1566 + * Set the current progress of the progressbar. 246.1567 + * 246.1568 + * @param {number} p Percentage of the progress (0-100) 246.1569 + * @public 246.1570 + */ 246.1571 +MaterialProgress.prototype.setProgress = function (p) { 246.1572 + if (this.element_.classList.contains(this.CssClasses_.INDETERMINATE_CLASS)) { 246.1573 + return; 246.1574 + } 246.1575 + this.progressbar_.style.width = p + '%'; 246.1576 +}; 246.1577 +MaterialProgress.prototype['setProgress'] = MaterialProgress.prototype.setProgress; 246.1578 +/** 246.1579 + * Set the current progress of the buffer. 246.1580 + * 246.1581 + * @param {number} p Percentage of the buffer (0-100) 246.1582 + * @public 246.1583 + */ 246.1584 +MaterialProgress.prototype.setBuffer = function (p) { 246.1585 + this.bufferbar_.style.width = p + '%'; 246.1586 + this.auxbar_.style.width = 100 - p + '%'; 246.1587 +}; 246.1588 +MaterialProgress.prototype['setBuffer'] = MaterialProgress.prototype.setBuffer; 246.1589 +/** 246.1590 + * Initialize element. 246.1591 + */ 246.1592 +MaterialProgress.prototype.init = function () { 246.1593 + if (this.element_) { 246.1594 + var el = document.createElement('div'); 246.1595 + el.className = 'progressbar bar bar1'; 246.1596 + this.element_.appendChild(el); 246.1597 + this.progressbar_ = el; 246.1598 + el = document.createElement('div'); 246.1599 + el.className = 'bufferbar bar bar2'; 246.1600 + this.element_.appendChild(el); 246.1601 + this.bufferbar_ = el; 246.1602 + el = document.createElement('div'); 246.1603 + el.className = 'auxbar bar bar3'; 246.1604 + this.element_.appendChild(el); 246.1605 + this.auxbar_ = el; 246.1606 + this.progressbar_.style.width = '0%'; 246.1607 + this.bufferbar_.style.width = '100%'; 246.1608 + this.auxbar_.style.width = '0%'; 246.1609 + this.element_.classList.add('is-upgraded'); 246.1610 + } 246.1611 +}; 246.1612 +// The component registers itself. It can assume componentHandler is available 246.1613 +// in the global scope. 246.1614 +componentHandler.register({ 246.1615 + constructor: MaterialProgress, 246.1616 + classAsString: 'MaterialProgress', 246.1617 + cssClass: 'mdl-js-progress', 246.1618 + widget: true 246.1619 +}); 246.1620 +/** 246.1621 + * @license 246.1622 + * Copyright 2015 Google Inc. All Rights Reserved. 246.1623 + * 246.1624 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.1625 + * you may not use this file except in compliance with the License. 246.1626 + * You may obtain a copy of the License at 246.1627 + * 246.1628 + * http://www.apache.org/licenses/LICENSE-2.0 246.1629 + * 246.1630 + * Unless required by applicable law or agreed to in writing, software 246.1631 + * distributed under the License is distributed on an "AS IS" BASIS, 246.1632 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.1633 + * See the License for the specific language governing permissions and 246.1634 + * limitations under the License. 246.1635 + */ 246.1636 +/** 246.1637 + * Class constructor for Radio MDL component. 246.1638 + * Implements MDL component design pattern defined at: 246.1639 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.1640 + * 246.1641 + * @constructor 246.1642 + * @param {HTMLElement} element The element that will be upgraded. 246.1643 + */ 246.1644 +var MaterialRadio = function MaterialRadio(element) { 246.1645 + this.element_ = element; 246.1646 + // Initialize instance. 246.1647 + this.init(); 246.1648 +}; 246.1649 +window['MaterialRadio'] = MaterialRadio; 246.1650 +/** 246.1651 + * Store constants in one place so they can be updated easily. 246.1652 + * 246.1653 + * @enum {string | number} 246.1654 + * @private 246.1655 + */ 246.1656 +MaterialRadio.prototype.Constant_ = { TINY_TIMEOUT: 0.001 }; 246.1657 +/** 246.1658 + * Store strings for class names defined by this component that are used in 246.1659 + * JavaScript. This allows us to simply change it in one place should we 246.1660 + * decide to modify at a later date. 246.1661 + * 246.1662 + * @enum {string} 246.1663 + * @private 246.1664 + */ 246.1665 +MaterialRadio.prototype.CssClasses_ = { 246.1666 + IS_FOCUSED: 'is-focused', 246.1667 + IS_DISABLED: 'is-disabled', 246.1668 + IS_CHECKED: 'is-checked', 246.1669 + IS_UPGRADED: 'is-upgraded', 246.1670 + JS_RADIO: 'mdl-js-radio', 246.1671 + RADIO_BTN: 'mdl-radio__button', 246.1672 + RADIO_OUTER_CIRCLE: 'mdl-radio__outer-circle', 246.1673 + RADIO_INNER_CIRCLE: 'mdl-radio__inner-circle', 246.1674 + RIPPLE_EFFECT: 'mdl-js-ripple-effect', 246.1675 + RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 246.1676 + RIPPLE_CONTAINER: 'mdl-radio__ripple-container', 246.1677 + RIPPLE_CENTER: 'mdl-ripple--center', 246.1678 + RIPPLE: 'mdl-ripple' 246.1679 +}; 246.1680 +/** 246.1681 + * Handle change of state. 246.1682 + * 246.1683 + * @param {Event} event The event that fired. 246.1684 + * @private 246.1685 + */ 246.1686 +MaterialRadio.prototype.onChange_ = function (event) { 246.1687 + // Since other radio buttons don't get change events, we need to look for 246.1688 + // them to update their classes. 246.1689 + var radios = document.getElementsByClassName(this.CssClasses_.JS_RADIO); 246.1690 + for (var i = 0; i < radios.length; i++) { 246.1691 + var button = radios[i].querySelector('.' + this.CssClasses_.RADIO_BTN); 246.1692 + // Different name == different group, so no point updating those. 246.1693 + if (button.getAttribute('name') === this.btnElement_.getAttribute('name')) { 246.1694 + radios[i]['MaterialRadio'].updateClasses_(); 246.1695 + } 246.1696 + } 246.1697 +}; 246.1698 +/** 246.1699 + * Handle focus. 246.1700 + * 246.1701 + * @param {Event} event The event that fired. 246.1702 + * @private 246.1703 + */ 246.1704 +MaterialRadio.prototype.onFocus_ = function (event) { 246.1705 + this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 246.1706 +}; 246.1707 +/** 246.1708 + * Handle lost focus. 246.1709 + * 246.1710 + * @param {Event} event The event that fired. 246.1711 + * @private 246.1712 + */ 246.1713 +MaterialRadio.prototype.onBlur_ = function (event) { 246.1714 + this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 246.1715 +}; 246.1716 +/** 246.1717 + * Handle mouseup. 246.1718 + * 246.1719 + * @param {Event} event The event that fired. 246.1720 + * @private 246.1721 + */ 246.1722 +MaterialRadio.prototype.onMouseup_ = function (event) { 246.1723 + this.blur_(); 246.1724 +}; 246.1725 +/** 246.1726 + * Update classes. 246.1727 + * 246.1728 + * @private 246.1729 + */ 246.1730 +MaterialRadio.prototype.updateClasses_ = function () { 246.1731 + this.checkDisabled(); 246.1732 + this.checkToggleState(); 246.1733 +}; 246.1734 +/** 246.1735 + * Add blur. 246.1736 + * 246.1737 + * @private 246.1738 + */ 246.1739 +MaterialRadio.prototype.blur_ = function () { 246.1740 + // TODO: figure out why there's a focus event being fired after our blur, 246.1741 + // so that we can avoid this hack. 246.1742 + window.setTimeout(function () { 246.1743 + this.btnElement_.blur(); 246.1744 + }.bind(this), this.Constant_.TINY_TIMEOUT); 246.1745 +}; 246.1746 +// Public methods. 246.1747 +/** 246.1748 + * Check the components disabled state. 246.1749 + * 246.1750 + * @public 246.1751 + */ 246.1752 +MaterialRadio.prototype.checkDisabled = function () { 246.1753 + if (this.btnElement_.disabled) { 246.1754 + this.element_.classList.add(this.CssClasses_.IS_DISABLED); 246.1755 + } else { 246.1756 + this.element_.classList.remove(this.CssClasses_.IS_DISABLED); 246.1757 + } 246.1758 +}; 246.1759 +MaterialRadio.prototype['checkDisabled'] = MaterialRadio.prototype.checkDisabled; 246.1760 +/** 246.1761 + * Check the components toggled state. 246.1762 + * 246.1763 + * @public 246.1764 + */ 246.1765 +MaterialRadio.prototype.checkToggleState = function () { 246.1766 + if (this.btnElement_.checked) { 246.1767 + this.element_.classList.add(this.CssClasses_.IS_CHECKED); 246.1768 + } else { 246.1769 + this.element_.classList.remove(this.CssClasses_.IS_CHECKED); 246.1770 + } 246.1771 +}; 246.1772 +MaterialRadio.prototype['checkToggleState'] = MaterialRadio.prototype.checkToggleState; 246.1773 +/** 246.1774 + * Disable radio. 246.1775 + * 246.1776 + * @public 246.1777 + */ 246.1778 +MaterialRadio.prototype.disable = function () { 246.1779 + this.btnElement_.disabled = true; 246.1780 + this.updateClasses_(); 246.1781 +}; 246.1782 +MaterialRadio.prototype['disable'] = MaterialRadio.prototype.disable; 246.1783 +/** 246.1784 + * Enable radio. 246.1785 + * 246.1786 + * @public 246.1787 + */ 246.1788 +MaterialRadio.prototype.enable = function () { 246.1789 + this.btnElement_.disabled = false; 246.1790 + this.updateClasses_(); 246.1791 +}; 246.1792 +MaterialRadio.prototype['enable'] = MaterialRadio.prototype.enable; 246.1793 +/** 246.1794 + * Check radio. 246.1795 + * 246.1796 + * @public 246.1797 + */ 246.1798 +MaterialRadio.prototype.check = function () { 246.1799 + this.btnElement_.checked = true; 246.1800 + this.onChange_(null); 246.1801 +}; 246.1802 +MaterialRadio.prototype['check'] = MaterialRadio.prototype.check; 246.1803 +/** 246.1804 + * Uncheck radio. 246.1805 + * 246.1806 + * @public 246.1807 + */ 246.1808 +MaterialRadio.prototype.uncheck = function () { 246.1809 + this.btnElement_.checked = false; 246.1810 + this.onChange_(null); 246.1811 +}; 246.1812 +MaterialRadio.prototype['uncheck'] = MaterialRadio.prototype.uncheck; 246.1813 +/** 246.1814 + * Initialize element. 246.1815 + */ 246.1816 +MaterialRadio.prototype.init = function () { 246.1817 + if (this.element_) { 246.1818 + this.btnElement_ = this.element_.querySelector('.' + this.CssClasses_.RADIO_BTN); 246.1819 + this.boundChangeHandler_ = this.onChange_.bind(this); 246.1820 + this.boundFocusHandler_ = this.onChange_.bind(this); 246.1821 + this.boundBlurHandler_ = this.onBlur_.bind(this); 246.1822 + this.boundMouseUpHandler_ = this.onMouseup_.bind(this); 246.1823 + var outerCircle = document.createElement('span'); 246.1824 + outerCircle.classList.add(this.CssClasses_.RADIO_OUTER_CIRCLE); 246.1825 + var innerCircle = document.createElement('span'); 246.1826 + innerCircle.classList.add(this.CssClasses_.RADIO_INNER_CIRCLE); 246.1827 + this.element_.appendChild(outerCircle); 246.1828 + this.element_.appendChild(innerCircle); 246.1829 + var rippleContainer; 246.1830 + if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { 246.1831 + this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 246.1832 + rippleContainer = document.createElement('span'); 246.1833 + rippleContainer.classList.add(this.CssClasses_.RIPPLE_CONTAINER); 246.1834 + rippleContainer.classList.add(this.CssClasses_.RIPPLE_EFFECT); 246.1835 + rippleContainer.classList.add(this.CssClasses_.RIPPLE_CENTER); 246.1836 + rippleContainer.addEventListener('mouseup', this.boundMouseUpHandler_); 246.1837 + var ripple = document.createElement('span'); 246.1838 + ripple.classList.add(this.CssClasses_.RIPPLE); 246.1839 + rippleContainer.appendChild(ripple); 246.1840 + this.element_.appendChild(rippleContainer); 246.1841 + } 246.1842 + this.btnElement_.addEventListener('change', this.boundChangeHandler_); 246.1843 + this.btnElement_.addEventListener('focus', this.boundFocusHandler_); 246.1844 + this.btnElement_.addEventListener('blur', this.boundBlurHandler_); 246.1845 + this.element_.addEventListener('mouseup', this.boundMouseUpHandler_); 246.1846 + this.updateClasses_(); 246.1847 + this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 246.1848 + } 246.1849 +}; 246.1850 +// The component registers itself. It can assume componentHandler is available 246.1851 +// in the global scope. 246.1852 +componentHandler.register({ 246.1853 + constructor: MaterialRadio, 246.1854 + classAsString: 'MaterialRadio', 246.1855 + cssClass: 'mdl-js-radio', 246.1856 + widget: true 246.1857 +}); 246.1858 +/** 246.1859 + * @license 246.1860 + * Copyright 2015 Google Inc. All Rights Reserved. 246.1861 + * 246.1862 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.1863 + * you may not use this file except in compliance with the License. 246.1864 + * You may obtain a copy of the License at 246.1865 + * 246.1866 + * http://www.apache.org/licenses/LICENSE-2.0 246.1867 + * 246.1868 + * Unless required by applicable law or agreed to in writing, software 246.1869 + * distributed under the License is distributed on an "AS IS" BASIS, 246.1870 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.1871 + * See the License for the specific language governing permissions and 246.1872 + * limitations under the License. 246.1873 + */ 246.1874 +/** 246.1875 + * Class constructor for Slider MDL component. 246.1876 + * Implements MDL component design pattern defined at: 246.1877 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.1878 + * 246.1879 + * @constructor 246.1880 + * @param {HTMLElement} element The element that will be upgraded. 246.1881 + */ 246.1882 +var MaterialSlider = function MaterialSlider(element) { 246.1883 + this.element_ = element; 246.1884 + // Browser feature detection. 246.1885 + this.isIE_ = window.navigator.msPointerEnabled; 246.1886 + // Initialize instance. 246.1887 + this.init(); 246.1888 +}; 246.1889 +window['MaterialSlider'] = MaterialSlider; 246.1890 +/** 246.1891 + * Store constants in one place so they can be updated easily. 246.1892 + * 246.1893 + * @enum {string | number} 246.1894 + * @private 246.1895 + */ 246.1896 +MaterialSlider.prototype.Constant_ = {}; 246.1897 +/** 246.1898 + * Store strings for class names defined by this component that are used in 246.1899 + * JavaScript. This allows us to simply change it in one place should we 246.1900 + * decide to modify at a later date. 246.1901 + * 246.1902 + * @enum {string} 246.1903 + * @private 246.1904 + */ 246.1905 +MaterialSlider.prototype.CssClasses_ = { 246.1906 + IE_CONTAINER: 'mdl-slider__ie-container', 246.1907 + SLIDER_CONTAINER: 'mdl-slider__container', 246.1908 + BACKGROUND_FLEX: 'mdl-slider__background-flex', 246.1909 + BACKGROUND_LOWER: 'mdl-slider__background-lower', 246.1910 + BACKGROUND_UPPER: 'mdl-slider__background-upper', 246.1911 + IS_LOWEST_VALUE: 'is-lowest-value', 246.1912 + IS_UPGRADED: 'is-upgraded' 246.1913 +}; 246.1914 +/** 246.1915 + * Handle input on element. 246.1916 + * 246.1917 + * @param {Event} event The event that fired. 246.1918 + * @private 246.1919 + */ 246.1920 +MaterialSlider.prototype.onInput_ = function (event) { 246.1921 + this.updateValueStyles_(); 246.1922 +}; 246.1923 +/** 246.1924 + * Handle change on element. 246.1925 + * 246.1926 + * @param {Event} event The event that fired. 246.1927 + * @private 246.1928 + */ 246.1929 +MaterialSlider.prototype.onChange_ = function (event) { 246.1930 + this.updateValueStyles_(); 246.1931 +}; 246.1932 +/** 246.1933 + * Handle mouseup on element. 246.1934 + * 246.1935 + * @param {Event} event The event that fired. 246.1936 + * @private 246.1937 + */ 246.1938 +MaterialSlider.prototype.onMouseUp_ = function (event) { 246.1939 + event.target.blur(); 246.1940 +}; 246.1941 +/** 246.1942 + * Handle mousedown on container element. 246.1943 + * This handler is purpose is to not require the use to click 246.1944 + * exactly on the 2px slider element, as FireFox seems to be very 246.1945 + * strict about this. 246.1946 + * 246.1947 + * @param {Event} event The event that fired. 246.1948 + * @private 246.1949 + * @suppress {missingProperties} 246.1950 + */ 246.1951 +MaterialSlider.prototype.onContainerMouseDown_ = function (event) { 246.1952 + // If this click is not on the parent element (but rather some child) 246.1953 + // ignore. It may still bubble up. 246.1954 + if (event.target !== this.element_.parentElement) { 246.1955 + return; 246.1956 + } 246.1957 + // Discard the original event and create a new event that 246.1958 + // is on the slider element. 246.1959 + event.preventDefault(); 246.1960 + var newEvent = new MouseEvent('mousedown', { 246.1961 + target: event.target, 246.1962 + buttons: event.buttons, 246.1963 + clientX: event.clientX, 246.1964 + clientY: this.element_.getBoundingClientRect().y 246.1965 + }); 246.1966 + this.element_.dispatchEvent(newEvent); 246.1967 +}; 246.1968 +/** 246.1969 + * Handle updating of values. 246.1970 + * 246.1971 + * @private 246.1972 + */ 246.1973 +MaterialSlider.prototype.updateValueStyles_ = function () { 246.1974 + // Calculate and apply percentages to div structure behind slider. 246.1975 + var fraction = (this.element_.value - this.element_.min) / (this.element_.max - this.element_.min); 246.1976 + if (fraction === 0) { 246.1977 + this.element_.classList.add(this.CssClasses_.IS_LOWEST_VALUE); 246.1978 + } else { 246.1979 + this.element_.classList.remove(this.CssClasses_.IS_LOWEST_VALUE); 246.1980 + } 246.1981 + if (!this.isIE_) { 246.1982 + this.backgroundLower_.style.flex = fraction; 246.1983 + this.backgroundLower_.style.webkitFlex = fraction; 246.1984 + this.backgroundUpper_.style.flex = 1 - fraction; 246.1985 + this.backgroundUpper_.style.webkitFlex = 1 - fraction; 246.1986 + } 246.1987 +}; 246.1988 +// Public methods. 246.1989 +/** 246.1990 + * Disable slider. 246.1991 + * 246.1992 + * @public 246.1993 + */ 246.1994 +MaterialSlider.prototype.disable = function () { 246.1995 + this.element_.disabled = true; 246.1996 +}; 246.1997 +MaterialSlider.prototype['disable'] = MaterialSlider.prototype.disable; 246.1998 +/** 246.1999 + * Enable slider. 246.2000 + * 246.2001 + * @public 246.2002 + */ 246.2003 +MaterialSlider.prototype.enable = function () { 246.2004 + this.element_.disabled = false; 246.2005 +}; 246.2006 +MaterialSlider.prototype['enable'] = MaterialSlider.prototype.enable; 246.2007 +/** 246.2008 + * Update slider value. 246.2009 + * 246.2010 + * @param {number} value The value to which to set the control (optional). 246.2011 + * @public 246.2012 + */ 246.2013 +MaterialSlider.prototype.change = function (value) { 246.2014 + if (typeof value !== 'undefined') { 246.2015 + this.element_.value = value; 246.2016 + } 246.2017 + this.updateValueStyles_(); 246.2018 +}; 246.2019 +MaterialSlider.prototype['change'] = MaterialSlider.prototype.change; 246.2020 +/** 246.2021 + * Initialize element. 246.2022 + */ 246.2023 +MaterialSlider.prototype.init = function () { 246.2024 + if (this.element_) { 246.2025 + if (this.isIE_) { 246.2026 + // Since we need to specify a very large height in IE due to 246.2027 + // implementation limitations, we add a parent here that trims it down to 246.2028 + // a reasonable size. 246.2029 + var containerIE = document.createElement('div'); 246.2030 + containerIE.classList.add(this.CssClasses_.IE_CONTAINER); 246.2031 + this.element_.parentElement.insertBefore(containerIE, this.element_); 246.2032 + this.element_.parentElement.removeChild(this.element_); 246.2033 + containerIE.appendChild(this.element_); 246.2034 + } else { 246.2035 + // For non-IE browsers, we need a div structure that sits behind the 246.2036 + // slider and allows us to style the left and right sides of it with 246.2037 + // different colors. 246.2038 + var container = document.createElement('div'); 246.2039 + container.classList.add(this.CssClasses_.SLIDER_CONTAINER); 246.2040 + this.element_.parentElement.insertBefore(container, this.element_); 246.2041 + this.element_.parentElement.removeChild(this.element_); 246.2042 + container.appendChild(this.element_); 246.2043 + var backgroundFlex = document.createElement('div'); 246.2044 + backgroundFlex.classList.add(this.CssClasses_.BACKGROUND_FLEX); 246.2045 + container.appendChild(backgroundFlex); 246.2046 + this.backgroundLower_ = document.createElement('div'); 246.2047 + this.backgroundLower_.classList.add(this.CssClasses_.BACKGROUND_LOWER); 246.2048 + backgroundFlex.appendChild(this.backgroundLower_); 246.2049 + this.backgroundUpper_ = document.createElement('div'); 246.2050 + this.backgroundUpper_.classList.add(this.CssClasses_.BACKGROUND_UPPER); 246.2051 + backgroundFlex.appendChild(this.backgroundUpper_); 246.2052 + } 246.2053 + this.boundInputHandler = this.onInput_.bind(this); 246.2054 + this.boundChangeHandler = this.onChange_.bind(this); 246.2055 + this.boundMouseUpHandler = this.onMouseUp_.bind(this); 246.2056 + this.boundContainerMouseDownHandler = this.onContainerMouseDown_.bind(this); 246.2057 + this.element_.addEventListener('input', this.boundInputHandler); 246.2058 + this.element_.addEventListener('change', this.boundChangeHandler); 246.2059 + this.element_.addEventListener('mouseup', this.boundMouseUpHandler); 246.2060 + this.element_.parentElement.addEventListener('mousedown', this.boundContainerMouseDownHandler); 246.2061 + this.updateValueStyles_(); 246.2062 + this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 246.2063 + } 246.2064 +}; 246.2065 +// The component registers itself. It can assume componentHandler is available 246.2066 +// in the global scope. 246.2067 +componentHandler.register({ 246.2068 + constructor: MaterialSlider, 246.2069 + classAsString: 'MaterialSlider', 246.2070 + cssClass: 'mdl-js-slider', 246.2071 + widget: true 246.2072 +}); 246.2073 +/** 246.2074 + * Copyright 2015 Google Inc. All Rights Reserved. 246.2075 + * 246.2076 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.2077 + * you may not use this file except in compliance with the License. 246.2078 + * You may obtain a copy of the License at 246.2079 + * 246.2080 + * http://www.apache.org/licenses/LICENSE-2.0 246.2081 + * 246.2082 + * Unless required by applicable law or agreed to in writing, software 246.2083 + * distributed under the License is distributed on an "AS IS" BASIS, 246.2084 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.2085 + * See the License for the specific language governing permissions and 246.2086 + * limitations under the License. 246.2087 + */ 246.2088 +/** 246.2089 + * Class constructor for Snackbar MDL component. 246.2090 + * Implements MDL component design pattern defined at: 246.2091 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.2092 + * 246.2093 + * @constructor 246.2094 + * @param {HTMLElement} element The element that will be upgraded. 246.2095 + */ 246.2096 +var MaterialSnackbar = function MaterialSnackbar(element) { 246.2097 + this.element_ = element; 246.2098 + this.textElement_ = this.element_.querySelector('.' + this.cssClasses_.MESSAGE); 246.2099 + this.actionElement_ = this.element_.querySelector('.' + this.cssClasses_.ACTION); 246.2100 + if (!this.textElement_) { 246.2101 + throw new Error('There must be a message element for a snackbar.'); 246.2102 + } 246.2103 + if (!this.actionElement_) { 246.2104 + throw new Error('There must be an action element for a snackbar.'); 246.2105 + } 246.2106 + this.active = false; 246.2107 + this.actionHandler_ = undefined; 246.2108 + this.message_ = undefined; 246.2109 + this.actionText_ = undefined; 246.2110 + this.queuedNotifications_ = []; 246.2111 + this.setActionHidden_(true); 246.2112 +}; 246.2113 +window['MaterialSnackbar'] = MaterialSnackbar; 246.2114 +/** 246.2115 + * Store constants in one place so they can be updated easily. 246.2116 + * 246.2117 + * @enum {string | number} 246.2118 + * @private 246.2119 + */ 246.2120 +MaterialSnackbar.prototype.Constant_ = { 246.2121 + // The duration of the snackbar show/hide animation, in ms. 246.2122 + ANIMATION_LENGTH: 250 246.2123 +}; 246.2124 +/** 246.2125 + * Store strings for class names defined by this component that are used in 246.2126 + * JavaScript. This allows us to simply change it in one place should we 246.2127 + * decide to modify at a later date. 246.2128 + * 246.2129 + * @enum {string} 246.2130 + * @private 246.2131 + */ 246.2132 +MaterialSnackbar.prototype.cssClasses_ = { 246.2133 + SNACKBAR: 'mdl-snackbar', 246.2134 + MESSAGE: 'mdl-snackbar__text', 246.2135 + ACTION: 'mdl-snackbar__action', 246.2136 + ACTIVE: 'mdl-snackbar--active' 246.2137 +}; 246.2138 +/** 246.2139 + * Display the snackbar. 246.2140 + * 246.2141 + * @private 246.2142 + */ 246.2143 +MaterialSnackbar.prototype.displaySnackbar_ = function () { 246.2144 + this.element_.setAttribute('aria-hidden', 'true'); 246.2145 + if (this.actionHandler_) { 246.2146 + this.actionElement_.textContent = this.actionText_; 246.2147 + this.actionElement_.addEventListener('click', this.actionHandler_); 246.2148 + this.setActionHidden_(false); 246.2149 + } 246.2150 + this.textElement_.textContent = this.message_; 246.2151 + this.element_.classList.add(this.cssClasses_.ACTIVE); 246.2152 + this.element_.setAttribute('aria-hidden', 'false'); 246.2153 + setTimeout(this.cleanup_.bind(this), this.timeout_); 246.2154 +}; 246.2155 +/** 246.2156 + * Show the snackbar. 246.2157 + * 246.2158 + * @param {Object} data The data for the notification. 246.2159 + * @public 246.2160 + */ 246.2161 +MaterialSnackbar.prototype.showSnackbar = function (data) { 246.2162 + if (data === undefined) { 246.2163 + throw new Error('Please provide a data object with at least a message to display.'); 246.2164 + } 246.2165 + if (data['message'] === undefined) { 246.2166 + throw new Error('Please provide a message to be displayed.'); 246.2167 + } 246.2168 + if (data['actionHandler'] && !data['actionText']) { 246.2169 + throw new Error('Please provide action text with the handler.'); 246.2170 + } 246.2171 + if (this.active) { 246.2172 + this.queuedNotifications_.push(data); 246.2173 + } else { 246.2174 + this.active = true; 246.2175 + this.message_ = data['message']; 246.2176 + if (data['timeout']) { 246.2177 + this.timeout_ = data['timeout']; 246.2178 + } else { 246.2179 + this.timeout_ = 2750; 246.2180 + } 246.2181 + if (data['actionHandler']) { 246.2182 + this.actionHandler_ = data['actionHandler']; 246.2183 + } 246.2184 + if (data['actionText']) { 246.2185 + this.actionText_ = data['actionText']; 246.2186 + } 246.2187 + this.displaySnackbar_(); 246.2188 + } 246.2189 +}; 246.2190 +MaterialSnackbar.prototype['showSnackbar'] = MaterialSnackbar.prototype.showSnackbar; 246.2191 +/** 246.2192 + * Check if the queue has items within it. 246.2193 + * If it does, display the next entry. 246.2194 + * 246.2195 + * @private 246.2196 + */ 246.2197 +MaterialSnackbar.prototype.checkQueue_ = function () { 246.2198 + if (this.queuedNotifications_.length > 0) { 246.2199 + this.showSnackbar(this.queuedNotifications_.shift()); 246.2200 + } 246.2201 +}; 246.2202 +/** 246.2203 + * Cleanup the snackbar event listeners and accessiblity attributes. 246.2204 + * 246.2205 + * @private 246.2206 + */ 246.2207 +MaterialSnackbar.prototype.cleanup_ = function () { 246.2208 + this.element_.classList.remove(this.cssClasses_.ACTIVE); 246.2209 + setTimeout(function () { 246.2210 + this.element_.setAttribute('aria-hidden', 'true'); 246.2211 + this.textElement_.textContent = ''; 246.2212 + if (!Boolean(this.actionElement_.getAttribute('aria-hidden'))) { 246.2213 + this.setActionHidden_(true); 246.2214 + this.actionElement_.textContent = ''; 246.2215 + this.actionElement_.removeEventListener('click', this.actionHandler_); 246.2216 + } 246.2217 + this.actionHandler_ = undefined; 246.2218 + this.message_ = undefined; 246.2219 + this.actionText_ = undefined; 246.2220 + this.active = false; 246.2221 + this.checkQueue_(); 246.2222 + }.bind(this), this.Constant_.ANIMATION_LENGTH); 246.2223 +}; 246.2224 +/** 246.2225 + * Set the action handler hidden state. 246.2226 + * 246.2227 + * @param {boolean} value 246.2228 + * @private 246.2229 + */ 246.2230 +MaterialSnackbar.prototype.setActionHidden_ = function (value) { 246.2231 + if (value) { 246.2232 + this.actionElement_.setAttribute('aria-hidden', 'true'); 246.2233 + } else { 246.2234 + this.actionElement_.removeAttribute('aria-hidden'); 246.2235 + } 246.2236 +}; 246.2237 +// The component registers itself. It can assume componentHandler is available 246.2238 +// in the global scope. 246.2239 +componentHandler.register({ 246.2240 + constructor: MaterialSnackbar, 246.2241 + classAsString: 'MaterialSnackbar', 246.2242 + cssClass: 'mdl-js-snackbar', 246.2243 + widget: true 246.2244 +}); 246.2245 +/** 246.2246 + * @license 246.2247 + * Copyright 2015 Google Inc. All Rights Reserved. 246.2248 + * 246.2249 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.2250 + * you may not use this file except in compliance with the License. 246.2251 + * You may obtain a copy of the License at 246.2252 + * 246.2253 + * http://www.apache.org/licenses/LICENSE-2.0 246.2254 + * 246.2255 + * Unless required by applicable law or agreed to in writing, software 246.2256 + * distributed under the License is distributed on an "AS IS" BASIS, 246.2257 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.2258 + * See the License for the specific language governing permissions and 246.2259 + * limitations under the License. 246.2260 + */ 246.2261 +/** 246.2262 + * Class constructor for Spinner MDL component. 246.2263 + * Implements MDL component design pattern defined at: 246.2264 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.2265 + * 246.2266 + * @param {HTMLElement} element The element that will be upgraded. 246.2267 + * @constructor 246.2268 + */ 246.2269 +var MaterialSpinner = function MaterialSpinner(element) { 246.2270 + this.element_ = element; 246.2271 + // Initialize instance. 246.2272 + this.init(); 246.2273 +}; 246.2274 +window['MaterialSpinner'] = MaterialSpinner; 246.2275 +/** 246.2276 + * Store constants in one place so they can be updated easily. 246.2277 + * 246.2278 + * @enum {string | number} 246.2279 + * @private 246.2280 + */ 246.2281 +MaterialSpinner.prototype.Constant_ = { MDL_SPINNER_LAYER_COUNT: 4 }; 246.2282 +/** 246.2283 + * Store strings for class names defined by this component that are used in 246.2284 + * JavaScript. This allows us to simply change it in one place should we 246.2285 + * decide to modify at a later date. 246.2286 + * 246.2287 + * @enum {string} 246.2288 + * @private 246.2289 + */ 246.2290 +MaterialSpinner.prototype.CssClasses_ = { 246.2291 + MDL_SPINNER_LAYER: 'mdl-spinner__layer', 246.2292 + MDL_SPINNER_CIRCLE_CLIPPER: 'mdl-spinner__circle-clipper', 246.2293 + MDL_SPINNER_CIRCLE: 'mdl-spinner__circle', 246.2294 + MDL_SPINNER_GAP_PATCH: 'mdl-spinner__gap-patch', 246.2295 + MDL_SPINNER_LEFT: 'mdl-spinner__left', 246.2296 + MDL_SPINNER_RIGHT: 'mdl-spinner__right' 246.2297 +}; 246.2298 +/** 246.2299 + * Auxiliary method to create a spinner layer. 246.2300 + * 246.2301 + * @param {number} index Index of the layer to be created. 246.2302 + * @public 246.2303 + */ 246.2304 +MaterialSpinner.prototype.createLayer = function (index) { 246.2305 + var layer = document.createElement('div'); 246.2306 + layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER); 246.2307 + layer.classList.add(this.CssClasses_.MDL_SPINNER_LAYER + '-' + index); 246.2308 + var leftClipper = document.createElement('div'); 246.2309 + leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER); 246.2310 + leftClipper.classList.add(this.CssClasses_.MDL_SPINNER_LEFT); 246.2311 + var gapPatch = document.createElement('div'); 246.2312 + gapPatch.classList.add(this.CssClasses_.MDL_SPINNER_GAP_PATCH); 246.2313 + var rightClipper = document.createElement('div'); 246.2314 + rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER); 246.2315 + rightClipper.classList.add(this.CssClasses_.MDL_SPINNER_RIGHT); 246.2316 + var circleOwners = [ 246.2317 + leftClipper, 246.2318 + gapPatch, 246.2319 + rightClipper 246.2320 + ]; 246.2321 + for (var i = 0; i < circleOwners.length; i++) { 246.2322 + var circle = document.createElement('div'); 246.2323 + circle.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE); 246.2324 + circleOwners[i].appendChild(circle); 246.2325 + } 246.2326 + layer.appendChild(leftClipper); 246.2327 + layer.appendChild(gapPatch); 246.2328 + layer.appendChild(rightClipper); 246.2329 + this.element_.appendChild(layer); 246.2330 +}; 246.2331 +MaterialSpinner.prototype['createLayer'] = MaterialSpinner.prototype.createLayer; 246.2332 +/** 246.2333 + * Stops the spinner animation. 246.2334 + * Public method for users who need to stop the spinner for any reason. 246.2335 + * 246.2336 + * @public 246.2337 + */ 246.2338 +MaterialSpinner.prototype.stop = function () { 246.2339 + this.element_.classList.remove('is-active'); 246.2340 +}; 246.2341 +MaterialSpinner.prototype['stop'] = MaterialSpinner.prototype.stop; 246.2342 +/** 246.2343 + * Starts the spinner animation. 246.2344 + * Public method for users who need to manually start the spinner for any reason 246.2345 + * (instead of just adding the 'is-active' class to their markup). 246.2346 + * 246.2347 + * @public 246.2348 + */ 246.2349 +MaterialSpinner.prototype.start = function () { 246.2350 + this.element_.classList.add('is-active'); 246.2351 +}; 246.2352 +MaterialSpinner.prototype['start'] = MaterialSpinner.prototype.start; 246.2353 +/** 246.2354 + * Initialize element. 246.2355 + */ 246.2356 +MaterialSpinner.prototype.init = function () { 246.2357 + if (this.element_) { 246.2358 + for (var i = 1; i <= this.Constant_.MDL_SPINNER_LAYER_COUNT; i++) { 246.2359 + this.createLayer(i); 246.2360 + } 246.2361 + this.element_.classList.add('is-upgraded'); 246.2362 + } 246.2363 +}; 246.2364 +// The component registers itself. It can assume componentHandler is available 246.2365 +// in the global scope. 246.2366 +componentHandler.register({ 246.2367 + constructor: MaterialSpinner, 246.2368 + classAsString: 'MaterialSpinner', 246.2369 + cssClass: 'mdl-js-spinner', 246.2370 + widget: true 246.2371 +}); 246.2372 +/** 246.2373 + * @license 246.2374 + * Copyright 2015 Google Inc. All Rights Reserved. 246.2375 + * 246.2376 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.2377 + * you may not use this file except in compliance with the License. 246.2378 + * You may obtain a copy of the License at 246.2379 + * 246.2380 + * http://www.apache.org/licenses/LICENSE-2.0 246.2381 + * 246.2382 + * Unless required by applicable law or agreed to in writing, software 246.2383 + * distributed under the License is distributed on an "AS IS" BASIS, 246.2384 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.2385 + * See the License for the specific language governing permissions and 246.2386 + * limitations under the License. 246.2387 + */ 246.2388 +/** 246.2389 + * Class constructor for Checkbox MDL component. 246.2390 + * Implements MDL component design pattern defined at: 246.2391 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.2392 + * 246.2393 + * @constructor 246.2394 + * @param {HTMLElement} element The element that will be upgraded. 246.2395 + */ 246.2396 +var MaterialSwitch = function MaterialSwitch(element) { 246.2397 + this.element_ = element; 246.2398 + // Initialize instance. 246.2399 + this.init(); 246.2400 +}; 246.2401 +window['MaterialSwitch'] = MaterialSwitch; 246.2402 +/** 246.2403 + * Store constants in one place so they can be updated easily. 246.2404 + * 246.2405 + * @enum {string | number} 246.2406 + * @private 246.2407 + */ 246.2408 +MaterialSwitch.prototype.Constant_ = { TINY_TIMEOUT: 0.001 }; 246.2409 +/** 246.2410 + * Store strings for class names defined by this component that are used in 246.2411 + * JavaScript. This allows us to simply change it in one place should we 246.2412 + * decide to modify at a later date. 246.2413 + * 246.2414 + * @enum {string} 246.2415 + * @private 246.2416 + */ 246.2417 +MaterialSwitch.prototype.CssClasses_ = { 246.2418 + INPUT: 'mdl-switch__input', 246.2419 + TRACK: 'mdl-switch__track', 246.2420 + THUMB: 'mdl-switch__thumb', 246.2421 + FOCUS_HELPER: 'mdl-switch__focus-helper', 246.2422 + RIPPLE_EFFECT: 'mdl-js-ripple-effect', 246.2423 + RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 246.2424 + RIPPLE_CONTAINER: 'mdl-switch__ripple-container', 246.2425 + RIPPLE_CENTER: 'mdl-ripple--center', 246.2426 + RIPPLE: 'mdl-ripple', 246.2427 + IS_FOCUSED: 'is-focused', 246.2428 + IS_DISABLED: 'is-disabled', 246.2429 + IS_CHECKED: 'is-checked' 246.2430 +}; 246.2431 +/** 246.2432 + * Handle change of state. 246.2433 + * 246.2434 + * @param {Event} event The event that fired. 246.2435 + * @private 246.2436 + */ 246.2437 +MaterialSwitch.prototype.onChange_ = function (event) { 246.2438 + this.updateClasses_(); 246.2439 +}; 246.2440 +/** 246.2441 + * Handle focus of element. 246.2442 + * 246.2443 + * @param {Event} event The event that fired. 246.2444 + * @private 246.2445 + */ 246.2446 +MaterialSwitch.prototype.onFocus_ = function (event) { 246.2447 + this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 246.2448 +}; 246.2449 +/** 246.2450 + * Handle lost focus of element. 246.2451 + * 246.2452 + * @param {Event} event The event that fired. 246.2453 + * @private 246.2454 + */ 246.2455 +MaterialSwitch.prototype.onBlur_ = function (event) { 246.2456 + this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 246.2457 +}; 246.2458 +/** 246.2459 + * Handle mouseup. 246.2460 + * 246.2461 + * @param {Event} event The event that fired. 246.2462 + * @private 246.2463 + */ 246.2464 +MaterialSwitch.prototype.onMouseUp_ = function (event) { 246.2465 + this.blur_(); 246.2466 +}; 246.2467 +/** 246.2468 + * Handle class updates. 246.2469 + * 246.2470 + * @private 246.2471 + */ 246.2472 +MaterialSwitch.prototype.updateClasses_ = function () { 246.2473 + this.checkDisabled(); 246.2474 + this.checkToggleState(); 246.2475 +}; 246.2476 +/** 246.2477 + * Add blur. 246.2478 + * 246.2479 + * @private 246.2480 + */ 246.2481 +MaterialSwitch.prototype.blur_ = function () { 246.2482 + // TODO: figure out why there's a focus event being fired after our blur, 246.2483 + // so that we can avoid this hack. 246.2484 + window.setTimeout(function () { 246.2485 + this.inputElement_.blur(); 246.2486 + }.bind(this), this.Constant_.TINY_TIMEOUT); 246.2487 +}; 246.2488 +// Public methods. 246.2489 +/** 246.2490 + * Check the components disabled state. 246.2491 + * 246.2492 + * @public 246.2493 + */ 246.2494 +MaterialSwitch.prototype.checkDisabled = function () { 246.2495 + if (this.inputElement_.disabled) { 246.2496 + this.element_.classList.add(this.CssClasses_.IS_DISABLED); 246.2497 + } else { 246.2498 + this.element_.classList.remove(this.CssClasses_.IS_DISABLED); 246.2499 + } 246.2500 +}; 246.2501 +MaterialSwitch.prototype['checkDisabled'] = MaterialSwitch.prototype.checkDisabled; 246.2502 +/** 246.2503 + * Check the components toggled state. 246.2504 + * 246.2505 + * @public 246.2506 + */ 246.2507 +MaterialSwitch.prototype.checkToggleState = function () { 246.2508 + if (this.inputElement_.checked) { 246.2509 + this.element_.classList.add(this.CssClasses_.IS_CHECKED); 246.2510 + } else { 246.2511 + this.element_.classList.remove(this.CssClasses_.IS_CHECKED); 246.2512 + } 246.2513 +}; 246.2514 +MaterialSwitch.prototype['checkToggleState'] = MaterialSwitch.prototype.checkToggleState; 246.2515 +/** 246.2516 + * Disable switch. 246.2517 + * 246.2518 + * @public 246.2519 + */ 246.2520 +MaterialSwitch.prototype.disable = function () { 246.2521 + this.inputElement_.disabled = true; 246.2522 + this.updateClasses_(); 246.2523 +}; 246.2524 +MaterialSwitch.prototype['disable'] = MaterialSwitch.prototype.disable; 246.2525 +/** 246.2526 + * Enable switch. 246.2527 + * 246.2528 + * @public 246.2529 + */ 246.2530 +MaterialSwitch.prototype.enable = function () { 246.2531 + this.inputElement_.disabled = false; 246.2532 + this.updateClasses_(); 246.2533 +}; 246.2534 +MaterialSwitch.prototype['enable'] = MaterialSwitch.prototype.enable; 246.2535 +/** 246.2536 + * Activate switch. 246.2537 + * 246.2538 + * @public 246.2539 + */ 246.2540 +MaterialSwitch.prototype.on = function () { 246.2541 + this.inputElement_.checked = true; 246.2542 + this.updateClasses_(); 246.2543 +}; 246.2544 +MaterialSwitch.prototype['on'] = MaterialSwitch.prototype.on; 246.2545 +/** 246.2546 + * Deactivate switch. 246.2547 + * 246.2548 + * @public 246.2549 + */ 246.2550 +MaterialSwitch.prototype.off = function () { 246.2551 + this.inputElement_.checked = false; 246.2552 + this.updateClasses_(); 246.2553 +}; 246.2554 +MaterialSwitch.prototype['off'] = MaterialSwitch.prototype.off; 246.2555 +/** 246.2556 + * Initialize element. 246.2557 + */ 246.2558 +MaterialSwitch.prototype.init = function () { 246.2559 + if (this.element_) { 246.2560 + this.inputElement_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); 246.2561 + var track = document.createElement('div'); 246.2562 + track.classList.add(this.CssClasses_.TRACK); 246.2563 + var thumb = document.createElement('div'); 246.2564 + thumb.classList.add(this.CssClasses_.THUMB); 246.2565 + var focusHelper = document.createElement('span'); 246.2566 + focusHelper.classList.add(this.CssClasses_.FOCUS_HELPER); 246.2567 + thumb.appendChild(focusHelper); 246.2568 + this.element_.appendChild(track); 246.2569 + this.element_.appendChild(thumb); 246.2570 + this.boundMouseUpHandler = this.onMouseUp_.bind(this); 246.2571 + if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) { 246.2572 + this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 246.2573 + this.rippleContainerElement_ = document.createElement('span'); 246.2574 + this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER); 246.2575 + this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT); 246.2576 + this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER); 246.2577 + this.rippleContainerElement_.addEventListener('mouseup', this.boundMouseUpHandler); 246.2578 + var ripple = document.createElement('span'); 246.2579 + ripple.classList.add(this.CssClasses_.RIPPLE); 246.2580 + this.rippleContainerElement_.appendChild(ripple); 246.2581 + this.element_.appendChild(this.rippleContainerElement_); 246.2582 + } 246.2583 + this.boundChangeHandler = this.onChange_.bind(this); 246.2584 + this.boundFocusHandler = this.onFocus_.bind(this); 246.2585 + this.boundBlurHandler = this.onBlur_.bind(this); 246.2586 + this.inputElement_.addEventListener('change', this.boundChangeHandler); 246.2587 + this.inputElement_.addEventListener('focus', this.boundFocusHandler); 246.2588 + this.inputElement_.addEventListener('blur', this.boundBlurHandler); 246.2589 + this.element_.addEventListener('mouseup', this.boundMouseUpHandler); 246.2590 + this.updateClasses_(); 246.2591 + this.element_.classList.add('is-upgraded'); 246.2592 + } 246.2593 +}; 246.2594 +// The component registers itself. It can assume componentHandler is available 246.2595 +// in the global scope. 246.2596 +componentHandler.register({ 246.2597 + constructor: MaterialSwitch, 246.2598 + classAsString: 'MaterialSwitch', 246.2599 + cssClass: 'mdl-js-switch', 246.2600 + widget: true 246.2601 +}); 246.2602 +/** 246.2603 + * @license 246.2604 + * Copyright 2015 Google Inc. All Rights Reserved. 246.2605 + * 246.2606 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.2607 + * you may not use this file except in compliance with the License. 246.2608 + * You may obtain a copy of the License at 246.2609 + * 246.2610 + * http://www.apache.org/licenses/LICENSE-2.0 246.2611 + * 246.2612 + * Unless required by applicable law or agreed to in writing, software 246.2613 + * distributed under the License is distributed on an "AS IS" BASIS, 246.2614 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.2615 + * See the License for the specific language governing permissions and 246.2616 + * limitations under the License. 246.2617 + */ 246.2618 +/** 246.2619 + * Class constructor for Tabs MDL component. 246.2620 + * Implements MDL component design pattern defined at: 246.2621 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.2622 + * 246.2623 + * @constructor 246.2624 + * @param {Element} element The element that will be upgraded. 246.2625 + */ 246.2626 +var MaterialTabs = function MaterialTabs(element) { 246.2627 + // Stores the HTML element. 246.2628 + this.element_ = element; 246.2629 + // Initialize instance. 246.2630 + this.init(); 246.2631 +}; 246.2632 +window['MaterialTabs'] = MaterialTabs; 246.2633 +/** 246.2634 + * Store constants in one place so they can be updated easily. 246.2635 + * 246.2636 + * @enum {string} 246.2637 + * @private 246.2638 + */ 246.2639 +MaterialTabs.prototype.Constant_ = {}; 246.2640 +/** 246.2641 + * Store strings for class names defined by this component that are used in 246.2642 + * JavaScript. This allows us to simply change it in one place should we 246.2643 + * decide to modify at a later date. 246.2644 + * 246.2645 + * @enum {string} 246.2646 + * @private 246.2647 + */ 246.2648 +MaterialTabs.prototype.CssClasses_ = { 246.2649 + TAB_CLASS: 'mdl-tabs__tab', 246.2650 + PANEL_CLASS: 'mdl-tabs__panel', 246.2651 + ACTIVE_CLASS: 'is-active', 246.2652 + UPGRADED_CLASS: 'is-upgraded', 246.2653 + MDL_JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect', 246.2654 + MDL_RIPPLE_CONTAINER: 'mdl-tabs__ripple-container', 246.2655 + MDL_RIPPLE: 'mdl-ripple', 246.2656 + MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events' 246.2657 +}; 246.2658 +/** 246.2659 + * Handle clicks to a tabs component 246.2660 + * 246.2661 + * @private 246.2662 + */ 246.2663 +MaterialTabs.prototype.initTabs_ = function () { 246.2664 + if (this.element_.classList.contains(this.CssClasses_.MDL_JS_RIPPLE_EFFECT)) { 246.2665 + this.element_.classList.add(this.CssClasses_.MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS); 246.2666 + } 246.2667 + // Select element tabs, document panels 246.2668 + this.tabs_ = this.element_.querySelectorAll('.' + this.CssClasses_.TAB_CLASS); 246.2669 + this.panels_ = this.element_.querySelectorAll('.' + this.CssClasses_.PANEL_CLASS); 246.2670 + // Create new tabs for each tab element 246.2671 + for (var i = 0; i < this.tabs_.length; i++) { 246.2672 + new MaterialTab(this.tabs_[i], this); 246.2673 + } 246.2674 + this.element_.classList.add(this.CssClasses_.UPGRADED_CLASS); 246.2675 +}; 246.2676 +/** 246.2677 + * Reset tab state, dropping active classes 246.2678 + * 246.2679 + * @private 246.2680 + */ 246.2681 +MaterialTabs.prototype.resetTabState_ = function () { 246.2682 + for (var k = 0; k < this.tabs_.length; k++) { 246.2683 + this.tabs_[k].classList.remove(this.CssClasses_.ACTIVE_CLASS); 246.2684 + } 246.2685 +}; 246.2686 +/** 246.2687 + * Reset panel state, droping active classes 246.2688 + * 246.2689 + * @private 246.2690 + */ 246.2691 +MaterialTabs.prototype.resetPanelState_ = function () { 246.2692 + for (var j = 0; j < this.panels_.length; j++) { 246.2693 + this.panels_[j].classList.remove(this.CssClasses_.ACTIVE_CLASS); 246.2694 + } 246.2695 +}; 246.2696 +/** 246.2697 + * Initialize element. 246.2698 + */ 246.2699 +MaterialTabs.prototype.init = function () { 246.2700 + if (this.element_) { 246.2701 + this.initTabs_(); 246.2702 + } 246.2703 +}; 246.2704 +/** 246.2705 + * Constructor for an individual tab. 246.2706 + * 246.2707 + * @constructor 246.2708 + * @param {Element} tab The HTML element for the tab. 246.2709 + * @param {MaterialTabs} ctx The MaterialTabs object that owns the tab. 246.2710 + */ 246.2711 +function MaterialTab(tab, ctx) { 246.2712 + if (tab) { 246.2713 + if (ctx.element_.classList.contains(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT)) { 246.2714 + var rippleContainer = document.createElement('span'); 246.2715 + rippleContainer.classList.add(ctx.CssClasses_.MDL_RIPPLE_CONTAINER); 246.2716 + rippleContainer.classList.add(ctx.CssClasses_.MDL_JS_RIPPLE_EFFECT); 246.2717 + var ripple = document.createElement('span'); 246.2718 + ripple.classList.add(ctx.CssClasses_.MDL_RIPPLE); 246.2719 + rippleContainer.appendChild(ripple); 246.2720 + tab.appendChild(rippleContainer); 246.2721 + } 246.2722 + tab.addEventListener('click', function (e) { 246.2723 + if (tab.getAttribute('href').charAt(0) === '#') { 246.2724 + e.preventDefault(); 246.2725 + } 246.2726 + var href = tab.href.split('#')[1]; 246.2727 + var panel = ctx.element_.querySelector('#' + href); 246.2728 + ctx.resetTabState_(); 246.2729 + ctx.resetPanelState_(); 246.2730 + tab.classList.add(ctx.CssClasses_.ACTIVE_CLASS); 246.2731 + if (panel) { 246.2732 + panel.classList.add(ctx.CssClasses_.ACTIVE_CLASS); 246.2733 + } 246.2734 + }); 246.2735 + } 246.2736 +} 246.2737 +// The component registers itself. It can assume componentHandler is available 246.2738 +// in the global scope. 246.2739 +componentHandler.register({ 246.2740 + constructor: MaterialTabs, 246.2741 + classAsString: 'MaterialTabs', 246.2742 + cssClass: 'mdl-js-tabs' 246.2743 +}); 246.2744 +/** 246.2745 + * @license 246.2746 + * Copyright 2015 Google Inc. All Rights Reserved. 246.2747 + * 246.2748 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.2749 + * you may not use this file except in compliance with the License. 246.2750 + * You may obtain a copy of the License at 246.2751 + * 246.2752 + * http://www.apache.org/licenses/LICENSE-2.0 246.2753 + * 246.2754 + * Unless required by applicable law or agreed to in writing, software 246.2755 + * distributed under the License is distributed on an "AS IS" BASIS, 246.2756 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.2757 + * See the License for the specific language governing permissions and 246.2758 + * limitations under the License. 246.2759 + */ 246.2760 +/** 246.2761 + * Class constructor for Textfield MDL component. 246.2762 + * Implements MDL component design pattern defined at: 246.2763 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.2764 + * 246.2765 + * @constructor 246.2766 + * @param {HTMLElement} element The element that will be upgraded. 246.2767 + */ 246.2768 +var MaterialTextfield = function MaterialTextfield(element) { 246.2769 + this.element_ = element; 246.2770 + this.maxRows = this.Constant_.NO_MAX_ROWS; 246.2771 + // Initialize instance. 246.2772 + this.init(); 246.2773 +}; 246.2774 +window['MaterialTextfield'] = MaterialTextfield; 246.2775 +/** 246.2776 + * Store constants in one place so they can be updated easily. 246.2777 + * 246.2778 + * @enum {string | number} 246.2779 + * @private 246.2780 + */ 246.2781 +MaterialTextfield.prototype.Constant_ = { 246.2782 + NO_MAX_ROWS: -1, 246.2783 + MAX_ROWS_ATTRIBUTE: 'maxrows' 246.2784 +}; 246.2785 +/** 246.2786 + * Store strings for class names defined by this component that are used in 246.2787 + * JavaScript. This allows us to simply change it in one place should we 246.2788 + * decide to modify at a later date. 246.2789 + * 246.2790 + * @enum {string} 246.2791 + * @private 246.2792 + */ 246.2793 +MaterialTextfield.prototype.CssClasses_ = { 246.2794 + LABEL: 'mdl-textfield__label', 246.2795 + INPUT: 'mdl-textfield__input', 246.2796 + IS_DIRTY: 'is-dirty', 246.2797 + IS_FOCUSED: 'is-focused', 246.2798 + IS_DISABLED: 'is-disabled', 246.2799 + IS_INVALID: 'is-invalid', 246.2800 + IS_UPGRADED: 'is-upgraded', 246.2801 + HAS_PLACEHOLDER: 'has-placeholder' 246.2802 +}; 246.2803 +/** 246.2804 + * Handle input being entered. 246.2805 + * 246.2806 + * @param {Event} event The event that fired. 246.2807 + * @private 246.2808 + */ 246.2809 +MaterialTextfield.prototype.onKeyDown_ = function (event) { 246.2810 + var currentRowCount = event.target.value.split('\n').length; 246.2811 + if (event.keyCode === 13) { 246.2812 + if (currentRowCount >= this.maxRows) { 246.2813 + event.preventDefault(); 246.2814 + } 246.2815 + } 246.2816 +}; 246.2817 +/** 246.2818 + * Handle focus. 246.2819 + * 246.2820 + * @param {Event} event The event that fired. 246.2821 + * @private 246.2822 + */ 246.2823 +MaterialTextfield.prototype.onFocus_ = function (event) { 246.2824 + this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 246.2825 +}; 246.2826 +/** 246.2827 + * Handle lost focus. 246.2828 + * 246.2829 + * @param {Event} event The event that fired. 246.2830 + * @private 246.2831 + */ 246.2832 +MaterialTextfield.prototype.onBlur_ = function (event) { 246.2833 + this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 246.2834 +}; 246.2835 +/** 246.2836 + * Handle reset event from out side. 246.2837 + * 246.2838 + * @param {Event} event The event that fired. 246.2839 + * @private 246.2840 + */ 246.2841 +MaterialTextfield.prototype.onReset_ = function (event) { 246.2842 + this.updateClasses_(); 246.2843 +}; 246.2844 +/** 246.2845 + * Handle class updates. 246.2846 + * 246.2847 + * @private 246.2848 + */ 246.2849 +MaterialTextfield.prototype.updateClasses_ = function () { 246.2850 + this.checkDisabled(); 246.2851 + this.checkValidity(); 246.2852 + this.checkDirty(); 246.2853 + this.checkFocus(); 246.2854 +}; 246.2855 +// Public methods. 246.2856 +/** 246.2857 + * Check the disabled state and update field accordingly. 246.2858 + * 246.2859 + * @public 246.2860 + */ 246.2861 +MaterialTextfield.prototype.checkDisabled = function () { 246.2862 + if (this.input_.disabled) { 246.2863 + this.element_.classList.add(this.CssClasses_.IS_DISABLED); 246.2864 + } else { 246.2865 + this.element_.classList.remove(this.CssClasses_.IS_DISABLED); 246.2866 + } 246.2867 +}; 246.2868 +MaterialTextfield.prototype['checkDisabled'] = MaterialTextfield.prototype.checkDisabled; 246.2869 +/** 246.2870 + * Check the focus state and update field accordingly. 246.2871 + * 246.2872 + * @public 246.2873 + */ 246.2874 +MaterialTextfield.prototype.checkFocus = function () { 246.2875 + if (Boolean(this.element_.querySelector(':focus'))) { 246.2876 + this.element_.classList.add(this.CssClasses_.IS_FOCUSED); 246.2877 + } else { 246.2878 + this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 246.2879 + } 246.2880 +}; 246.2881 +MaterialTextfield.prototype['checkFocus'] = MaterialTextfield.prototype.checkFocus; 246.2882 +/** 246.2883 + * Check the validity state and update field accordingly. 246.2884 + * 246.2885 + * @public 246.2886 + */ 246.2887 +MaterialTextfield.prototype.checkValidity = function () { 246.2888 + if (this.input_.validity) { 246.2889 + if (this.input_.validity.valid) { 246.2890 + this.element_.classList.remove(this.CssClasses_.IS_INVALID); 246.2891 + } else { 246.2892 + this.element_.classList.add(this.CssClasses_.IS_INVALID); 246.2893 + } 246.2894 + } 246.2895 +}; 246.2896 +MaterialTextfield.prototype['checkValidity'] = MaterialTextfield.prototype.checkValidity; 246.2897 +/** 246.2898 + * Check the dirty state and update field accordingly. 246.2899 + * 246.2900 + * @public 246.2901 + */ 246.2902 +MaterialTextfield.prototype.checkDirty = function () { 246.2903 + if (this.input_.value && this.input_.value.length > 0) { 246.2904 + this.element_.classList.add(this.CssClasses_.IS_DIRTY); 246.2905 + } else { 246.2906 + this.element_.classList.remove(this.CssClasses_.IS_DIRTY); 246.2907 + } 246.2908 +}; 246.2909 +MaterialTextfield.prototype['checkDirty'] = MaterialTextfield.prototype.checkDirty; 246.2910 +/** 246.2911 + * Disable text field. 246.2912 + * 246.2913 + * @public 246.2914 + */ 246.2915 +MaterialTextfield.prototype.disable = function () { 246.2916 + this.input_.disabled = true; 246.2917 + this.updateClasses_(); 246.2918 +}; 246.2919 +MaterialTextfield.prototype['disable'] = MaterialTextfield.prototype.disable; 246.2920 +/** 246.2921 + * Enable text field. 246.2922 + * 246.2923 + * @public 246.2924 + */ 246.2925 +MaterialTextfield.prototype.enable = function () { 246.2926 + this.input_.disabled = false; 246.2927 + this.updateClasses_(); 246.2928 +}; 246.2929 +MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable; 246.2930 +/** 246.2931 + * Update text field value. 246.2932 + * 246.2933 + * @param {string} value The value to which to set the control (optional). 246.2934 + * @public 246.2935 + */ 246.2936 +MaterialTextfield.prototype.change = function (value) { 246.2937 + this.input_.value = value || ''; 246.2938 + this.updateClasses_(); 246.2939 +}; 246.2940 +MaterialTextfield.prototype['change'] = MaterialTextfield.prototype.change; 246.2941 +/** 246.2942 + * Initialize element. 246.2943 + */ 246.2944 +MaterialTextfield.prototype.init = function () { 246.2945 + if (this.element_) { 246.2946 + this.label_ = this.element_.querySelector('.' + this.CssClasses_.LABEL); 246.2947 + this.input_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); 246.2948 + if (this.input_) { 246.2949 + if (this.input_.hasAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE)) { 246.2950 + this.maxRows = parseInt(this.input_.getAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE), 10); 246.2951 + if (isNaN(this.maxRows)) { 246.2952 + this.maxRows = this.Constant_.NO_MAX_ROWS; 246.2953 + } 246.2954 + } 246.2955 + if (this.input_.hasAttribute('placeholder')) { 246.2956 + this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER); 246.2957 + } 246.2958 + this.boundUpdateClassesHandler = this.updateClasses_.bind(this); 246.2959 + this.boundFocusHandler = this.onFocus_.bind(this); 246.2960 + this.boundBlurHandler = this.onBlur_.bind(this); 246.2961 + this.boundResetHandler = this.onReset_.bind(this); 246.2962 + this.input_.addEventListener('input', this.boundUpdateClassesHandler); 246.2963 + this.input_.addEventListener('focus', this.boundFocusHandler); 246.2964 + this.input_.addEventListener('blur', this.boundBlurHandler); 246.2965 + this.input_.addEventListener('reset', this.boundResetHandler); 246.2966 + if (this.maxRows !== this.Constant_.NO_MAX_ROWS) { 246.2967 + // TODO: This should handle pasting multi line text. 246.2968 + // Currently doesn't. 246.2969 + this.boundKeyDownHandler = this.onKeyDown_.bind(this); 246.2970 + this.input_.addEventListener('keydown', this.boundKeyDownHandler); 246.2971 + } 246.2972 + var invalid = this.element_.classList.contains(this.CssClasses_.IS_INVALID); 246.2973 + this.updateClasses_(); 246.2974 + this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 246.2975 + if (invalid) { 246.2976 + this.element_.classList.add(this.CssClasses_.IS_INVALID); 246.2977 + } 246.2978 + if (this.input_.hasAttribute('autofocus')) { 246.2979 + this.element_.focus(); 246.2980 + this.checkFocus(); 246.2981 + } 246.2982 + } 246.2983 + } 246.2984 +}; 246.2985 +// The component registers itself. It can assume componentHandler is available 246.2986 +// in the global scope. 246.2987 +componentHandler.register({ 246.2988 + constructor: MaterialTextfield, 246.2989 + classAsString: 'MaterialTextfield', 246.2990 + cssClass: 'mdl-js-textfield', 246.2991 + widget: true 246.2992 +}); 246.2993 +/** 246.2994 + * @license 246.2995 + * Copyright 2015 Google Inc. All Rights Reserved. 246.2996 + * 246.2997 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.2998 + * you may not use this file except in compliance with the License. 246.2999 + * You may obtain a copy of the License at 246.3000 + * 246.3001 + * http://www.apache.org/licenses/LICENSE-2.0 246.3002 + * 246.3003 + * Unless required by applicable law or agreed to in writing, software 246.3004 + * distributed under the License is distributed on an "AS IS" BASIS, 246.3005 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.3006 + * See the License for the specific language governing permissions and 246.3007 + * limitations under the License. 246.3008 + */ 246.3009 +/** 246.3010 + * Class constructor for Tooltip MDL component. 246.3011 + * Implements MDL component design pattern defined at: 246.3012 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.3013 + * 246.3014 + * @constructor 246.3015 + * @param {HTMLElement} element The element that will be upgraded. 246.3016 + */ 246.3017 +var MaterialTooltip = function MaterialTooltip(element) { 246.3018 + this.element_ = element; 246.3019 + // Initialize instance. 246.3020 + this.init(); 246.3021 +}; 246.3022 +window['MaterialTooltip'] = MaterialTooltip; 246.3023 +/** 246.3024 + * Store constants in one place so they can be updated easily. 246.3025 + * 246.3026 + * @enum {string | number} 246.3027 + * @private 246.3028 + */ 246.3029 +MaterialTooltip.prototype.Constant_ = {}; 246.3030 +/** 246.3031 + * Store strings for class names defined by this component that are used in 246.3032 + * JavaScript. This allows us to simply change it in one place should we 246.3033 + * decide to modify at a later date. 246.3034 + * 246.3035 + * @enum {string} 246.3036 + * @private 246.3037 + */ 246.3038 +MaterialTooltip.prototype.CssClasses_ = { 246.3039 + IS_ACTIVE: 'is-active', 246.3040 + BOTTOM: 'mdl-tooltip--bottom', 246.3041 + LEFT: 'mdl-tooltip--left', 246.3042 + RIGHT: 'mdl-tooltip--right', 246.3043 + TOP: 'mdl-tooltip--top' 246.3044 +}; 246.3045 +/** 246.3046 + * Handle mouseenter for tooltip. 246.3047 + * 246.3048 + * @param {Event} event The event that fired. 246.3049 + * @private 246.3050 + */ 246.3051 +MaterialTooltip.prototype.handleMouseEnter_ = function (event) { 246.3052 + var props = event.target.getBoundingClientRect(); 246.3053 + var left = props.left + props.width / 2; 246.3054 + var top = props.top + props.height / 2; 246.3055 + var marginLeft = -1 * (this.element_.offsetWidth / 2); 246.3056 + var marginTop = -1 * (this.element_.offsetHeight / 2); 246.3057 + if (this.element_.classList.contains(this.CssClasses_.LEFT) || this.element_.classList.contains(this.CssClasses_.RIGHT)) { 246.3058 + left = props.width / 2; 246.3059 + if (top + marginTop < 0) { 246.3060 + this.element_.style.top = '0'; 246.3061 + this.element_.style.marginTop = '0'; 246.3062 + } else { 246.3063 + this.element_.style.top = top + 'px'; 246.3064 + this.element_.style.marginTop = marginTop + 'px'; 246.3065 + } 246.3066 + } else { 246.3067 + if (left + marginLeft < 0) { 246.3068 + this.element_.style.left = '0'; 246.3069 + this.element_.style.marginLeft = '0'; 246.3070 + } else { 246.3071 + this.element_.style.left = left + 'px'; 246.3072 + this.element_.style.marginLeft = marginLeft + 'px'; 246.3073 + } 246.3074 + } 246.3075 + if (this.element_.classList.contains(this.CssClasses_.TOP)) { 246.3076 + this.element_.style.top = props.top - this.element_.offsetHeight - 10 + 'px'; 246.3077 + } else if (this.element_.classList.contains(this.CssClasses_.RIGHT)) { 246.3078 + this.element_.style.left = props.left + props.width + 10 + 'px'; 246.3079 + } else if (this.element_.classList.contains(this.CssClasses_.LEFT)) { 246.3080 + this.element_.style.left = props.left - this.element_.offsetWidth - 10 + 'px'; 246.3081 + } else { 246.3082 + this.element_.style.top = props.top + props.height + 10 + 'px'; 246.3083 + } 246.3084 + this.element_.classList.add(this.CssClasses_.IS_ACTIVE); 246.3085 +}; 246.3086 +/** 246.3087 + * Hide tooltip on mouseleave or scroll 246.3088 + * 246.3089 + * @private 246.3090 + */ 246.3091 +MaterialTooltip.prototype.hideTooltip_ = function () { 246.3092 + this.element_.classList.remove(this.CssClasses_.IS_ACTIVE); 246.3093 +}; 246.3094 +/** 246.3095 + * Initialize element. 246.3096 + */ 246.3097 +MaterialTooltip.prototype.init = function () { 246.3098 + if (this.element_) { 246.3099 + var forElId = this.element_.getAttribute('for') || this.element_.getAttribute('data-mdl-for'); 246.3100 + if (forElId) { 246.3101 + this.forElement_ = document.getElementById(forElId); 246.3102 + } 246.3103 + if (this.forElement_) { 246.3104 + // It's left here because it prevents accidental text selection on Android 246.3105 + if (!this.forElement_.hasAttribute('tabindex')) { 246.3106 + this.forElement_.setAttribute('tabindex', '0'); 246.3107 + } 246.3108 + this.boundMouseEnterHandler = this.handleMouseEnter_.bind(this); 246.3109 + this.boundMouseLeaveAndScrollHandler = this.hideTooltip_.bind(this); 246.3110 + this.forElement_.addEventListener('mouseenter', this.boundMouseEnterHandler, false); 246.3111 + this.forElement_.addEventListener('touchend', this.boundMouseEnterHandler, false); 246.3112 + this.forElement_.addEventListener('mouseleave', this.boundMouseLeaveAndScrollHandler, false); 246.3113 + window.addEventListener('scroll', this.boundMouseLeaveAndScrollHandler, true); 246.3114 + window.addEventListener('touchstart', this.boundMouseLeaveAndScrollHandler); 246.3115 + } 246.3116 + } 246.3117 +}; 246.3118 +// The component registers itself. It can assume componentHandler is available 246.3119 +// in the global scope. 246.3120 +componentHandler.register({ 246.3121 + constructor: MaterialTooltip, 246.3122 + classAsString: 'MaterialTooltip', 246.3123 + cssClass: 'mdl-tooltip' 246.3124 +}); 246.3125 +/** 246.3126 + * @license 246.3127 + * Copyright 2015 Google Inc. All Rights Reserved. 246.3128 + * 246.3129 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.3130 + * you may not use this file except in compliance with the License. 246.3131 + * You may obtain a copy of the License at 246.3132 + * 246.3133 + * http://www.apache.org/licenses/LICENSE-2.0 246.3134 + * 246.3135 + * Unless required by applicable law or agreed to in writing, software 246.3136 + * distributed under the License is distributed on an "AS IS" BASIS, 246.3137 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.3138 + * See the License for the specific language governing permissions and 246.3139 + * limitations under the License. 246.3140 + */ 246.3141 +/** 246.3142 + * Class constructor for Layout MDL component. 246.3143 + * Implements MDL component design pattern defined at: 246.3144 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.3145 + * 246.3146 + * @constructor 246.3147 + * @param {HTMLElement} element The element that will be upgraded. 246.3148 + */ 246.3149 +var MaterialLayout = function MaterialLayout(element) { 246.3150 + this.element_ = element; 246.3151 + // Initialize instance. 246.3152 + this.init(); 246.3153 +}; 246.3154 +window['MaterialLayout'] = MaterialLayout; 246.3155 +/** 246.3156 + * Store constants in one place so they can be updated easily. 246.3157 + * 246.3158 + * @enum {string | number} 246.3159 + * @private 246.3160 + */ 246.3161 +MaterialLayout.prototype.Constant_ = { 246.3162 + MAX_WIDTH: '(max-width: 1024px)', 246.3163 + TAB_SCROLL_PIXELS: 100, 246.3164 + RESIZE_TIMEOUT: 100, 246.3165 + MENU_ICON: '', 246.3166 + CHEVRON_LEFT: 'chevron_left', 246.3167 + CHEVRON_RIGHT: 'chevron_right' 246.3168 +}; 246.3169 +/** 246.3170 + * Keycodes, for code readability. 246.3171 + * 246.3172 + * @enum {number} 246.3173 + * @private 246.3174 + */ 246.3175 +MaterialLayout.prototype.Keycodes_ = { 246.3176 + ENTER: 13, 246.3177 + ESCAPE: 27, 246.3178 + SPACE: 32 246.3179 +}; 246.3180 +/** 246.3181 + * Modes. 246.3182 + * 246.3183 + * @enum {number} 246.3184 + * @private 246.3185 + */ 246.3186 +MaterialLayout.prototype.Mode_ = { 246.3187 + STANDARD: 0, 246.3188 + SEAMED: 1, 246.3189 + WATERFALL: 2, 246.3190 + SCROLL: 3 246.3191 +}; 246.3192 +/** 246.3193 + * Store strings for class names defined by this component that are used in 246.3194 + * JavaScript. This allows us to simply change it in one place should we 246.3195 + * decide to modify at a later date. 246.3196 + * 246.3197 + * @enum {string} 246.3198 + * @private 246.3199 + */ 246.3200 +MaterialLayout.prototype.CssClasses_ = { 246.3201 + CONTAINER: 'mdl-layout__container', 246.3202 + HEADER: 'mdl-layout__header', 246.3203 + DRAWER: 'mdl-layout__drawer', 246.3204 + CONTENT: 'mdl-layout__content', 246.3205 + DRAWER_BTN: 'mdl-layout__drawer-button', 246.3206 + ICON: 'material-icons', 246.3207 + JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect', 246.3208 + RIPPLE_CONTAINER: 'mdl-layout__tab-ripple-container', 246.3209 + RIPPLE: 'mdl-ripple', 246.3210 + RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 246.3211 + HEADER_SEAMED: 'mdl-layout__header--seamed', 246.3212 + HEADER_WATERFALL: 'mdl-layout__header--waterfall', 246.3213 + HEADER_SCROLL: 'mdl-layout__header--scroll', 246.3214 + FIXED_HEADER: 'mdl-layout--fixed-header', 246.3215 + OBFUSCATOR: 'mdl-layout__obfuscator', 246.3216 + TAB_BAR: 'mdl-layout__tab-bar', 246.3217 + TAB_CONTAINER: 'mdl-layout__tab-bar-container', 246.3218 + TAB: 'mdl-layout__tab', 246.3219 + TAB_BAR_BUTTON: 'mdl-layout__tab-bar-button', 246.3220 + TAB_BAR_LEFT_BUTTON: 'mdl-layout__tab-bar-left-button', 246.3221 + TAB_BAR_RIGHT_BUTTON: 'mdl-layout__tab-bar-right-button', 246.3222 + PANEL: 'mdl-layout__tab-panel', 246.3223 + HAS_DRAWER: 'has-drawer', 246.3224 + HAS_TABS: 'has-tabs', 246.3225 + HAS_SCROLLING_HEADER: 'has-scrolling-header', 246.3226 + CASTING_SHADOW: 'is-casting-shadow', 246.3227 + IS_COMPACT: 'is-compact', 246.3228 + IS_SMALL_SCREEN: 'is-small-screen', 246.3229 + IS_DRAWER_OPEN: 'is-visible', 246.3230 + IS_ACTIVE: 'is-active', 246.3231 + IS_UPGRADED: 'is-upgraded', 246.3232 + IS_ANIMATING: 'is-animating', 246.3233 + ON_LARGE_SCREEN: 'mdl-layout--large-screen-only', 246.3234 + ON_SMALL_SCREEN: 'mdl-layout--small-screen-only' 246.3235 +}; 246.3236 +/** 246.3237 + * Handles scrolling on the content. 246.3238 + * 246.3239 + * @private 246.3240 + */ 246.3241 +MaterialLayout.prototype.contentScrollHandler_ = function () { 246.3242 + if (this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)) { 246.3243 + return; 246.3244 + } 246.3245 + var headerVisible = !this.element_.classList.contains(this.CssClasses_.IS_SMALL_SCREEN) || this.element_.classList.contains(this.CssClasses_.FIXED_HEADER); 246.3246 + if (this.content_.scrollTop > 0 && !this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) { 246.3247 + this.header_.classList.add(this.CssClasses_.CASTING_SHADOW); 246.3248 + this.header_.classList.add(this.CssClasses_.IS_COMPACT); 246.3249 + if (headerVisible) { 246.3250 + this.header_.classList.add(this.CssClasses_.IS_ANIMATING); 246.3251 + } 246.3252 + } else if (this.content_.scrollTop <= 0 && this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) { 246.3253 + this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW); 246.3254 + this.header_.classList.remove(this.CssClasses_.IS_COMPACT); 246.3255 + if (headerVisible) { 246.3256 + this.header_.classList.add(this.CssClasses_.IS_ANIMATING); 246.3257 + } 246.3258 + } 246.3259 +}; 246.3260 +/** 246.3261 + * Handles a keyboard event on the drawer. 246.3262 + * 246.3263 + * @param {Event} evt The event that fired. 246.3264 + * @private 246.3265 + */ 246.3266 +MaterialLayout.prototype.keyboardEventHandler_ = function (evt) { 246.3267 + // Only react when the drawer is open. 246.3268 + if (evt.keyCode === this.Keycodes_.ESCAPE && this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) { 246.3269 + this.toggleDrawer(); 246.3270 + } 246.3271 +}; 246.3272 +/** 246.3273 + * Handles changes in screen size. 246.3274 + * 246.3275 + * @private 246.3276 + */ 246.3277 +MaterialLayout.prototype.screenSizeHandler_ = function () { 246.3278 + if (this.screenSizeMediaQuery_.matches) { 246.3279 + this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN); 246.3280 + } else { 246.3281 + this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN); 246.3282 + // Collapse drawer (if any) when moving to a large screen size. 246.3283 + if (this.drawer_) { 246.3284 + this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN); 246.3285 + this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN); 246.3286 + } 246.3287 + } 246.3288 +}; 246.3289 +/** 246.3290 + * Handles events of drawer button. 246.3291 + * 246.3292 + * @param {Event} evt The event that fired. 246.3293 + * @private 246.3294 + */ 246.3295 +MaterialLayout.prototype.drawerToggleHandler_ = function (evt) { 246.3296 + if (evt && evt.type === 'keydown') { 246.3297 + if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) { 246.3298 + // prevent scrolling in drawer nav 246.3299 + evt.preventDefault(); 246.3300 + } else { 246.3301 + // prevent other keys 246.3302 + return; 246.3303 + } 246.3304 + } 246.3305 + this.toggleDrawer(); 246.3306 +}; 246.3307 +/** 246.3308 + * Handles (un)setting the `is-animating` class 246.3309 + * 246.3310 + * @private 246.3311 + */ 246.3312 +MaterialLayout.prototype.headerTransitionEndHandler_ = function () { 246.3313 + this.header_.classList.remove(this.CssClasses_.IS_ANIMATING); 246.3314 +}; 246.3315 +/** 246.3316 + * Handles expanding the header on click 246.3317 + * 246.3318 + * @private 246.3319 + */ 246.3320 +MaterialLayout.prototype.headerClickHandler_ = function () { 246.3321 + if (this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) { 246.3322 + this.header_.classList.remove(this.CssClasses_.IS_COMPACT); 246.3323 + this.header_.classList.add(this.CssClasses_.IS_ANIMATING); 246.3324 + } 246.3325 +}; 246.3326 +/** 246.3327 + * Reset tab state, dropping active classes 246.3328 + * 246.3329 + * @private 246.3330 + */ 246.3331 +MaterialLayout.prototype.resetTabState_ = function (tabBar) { 246.3332 + for (var k = 0; k < tabBar.length; k++) { 246.3333 + tabBar[k].classList.remove(this.CssClasses_.IS_ACTIVE); 246.3334 + } 246.3335 +}; 246.3336 +/** 246.3337 + * Reset panel state, droping active classes 246.3338 + * 246.3339 + * @private 246.3340 + */ 246.3341 +MaterialLayout.prototype.resetPanelState_ = function (panels) { 246.3342 + for (var j = 0; j < panels.length; j++) { 246.3343 + panels[j].classList.remove(this.CssClasses_.IS_ACTIVE); 246.3344 + } 246.3345 +}; 246.3346 +/** 246.3347 + * Toggle drawer state 246.3348 + * 246.3349 + * @public 246.3350 + */ 246.3351 +MaterialLayout.prototype.toggleDrawer = function () { 246.3352 + var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN); 246.3353 + this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN); 246.3354 + this.obfuscator_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN); 246.3355 + // Set accessibility properties. 246.3356 + if (this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) { 246.3357 + this.drawer_.setAttribute('aria-hidden', 'false'); 246.3358 + drawerButton.setAttribute('aria-expanded', 'true'); 246.3359 + } else { 246.3360 + this.drawer_.setAttribute('aria-hidden', 'true'); 246.3361 + drawerButton.setAttribute('aria-expanded', 'false'); 246.3362 + } 246.3363 +}; 246.3364 +MaterialLayout.prototype['toggleDrawer'] = MaterialLayout.prototype.toggleDrawer; 246.3365 +/** 246.3366 + * Initialize element. 246.3367 + */ 246.3368 +MaterialLayout.prototype.init = function () { 246.3369 + if (this.element_) { 246.3370 + var container = document.createElement('div'); 246.3371 + container.classList.add(this.CssClasses_.CONTAINER); 246.3372 + var focusedElement = this.element_.querySelector(':focus'); 246.3373 + this.element_.parentElement.insertBefore(container, this.element_); 246.3374 + this.element_.parentElement.removeChild(this.element_); 246.3375 + container.appendChild(this.element_); 246.3376 + if (focusedElement) { 246.3377 + focusedElement.focus(); 246.3378 + } 246.3379 + var directChildren = this.element_.childNodes; 246.3380 + var numChildren = directChildren.length; 246.3381 + for (var c = 0; c < numChildren; c++) { 246.3382 + var child = directChildren[c]; 246.3383 + if (child.classList && child.classList.contains(this.CssClasses_.HEADER)) { 246.3384 + this.header_ = child; 246.3385 + } 246.3386 + if (child.classList && child.classList.contains(this.CssClasses_.DRAWER)) { 246.3387 + this.drawer_ = child; 246.3388 + } 246.3389 + if (child.classList && child.classList.contains(this.CssClasses_.CONTENT)) { 246.3390 + this.content_ = child; 246.3391 + } 246.3392 + } 246.3393 + window.addEventListener('pageshow', function (e) { 246.3394 + if (e.persisted) { 246.3395 + // when page is loaded from back/forward cache 246.3396 + // trigger repaint to let layout scroll in safari 246.3397 + this.element_.style.overflowY = 'hidden'; 246.3398 + requestAnimationFrame(function () { 246.3399 + this.element_.style.overflowY = ''; 246.3400 + }.bind(this)); 246.3401 + } 246.3402 + }.bind(this), false); 246.3403 + if (this.header_) { 246.3404 + this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR); 246.3405 + } 246.3406 + var mode = this.Mode_.STANDARD; 246.3407 + if (this.header_) { 246.3408 + if (this.header_.classList.contains(this.CssClasses_.HEADER_SEAMED)) { 246.3409 + mode = this.Mode_.SEAMED; 246.3410 + } else if (this.header_.classList.contains(this.CssClasses_.HEADER_WATERFALL)) { 246.3411 + mode = this.Mode_.WATERFALL; 246.3412 + this.header_.addEventListener('transitionend', this.headerTransitionEndHandler_.bind(this)); 246.3413 + this.header_.addEventListener('click', this.headerClickHandler_.bind(this)); 246.3414 + } else if (this.header_.classList.contains(this.CssClasses_.HEADER_SCROLL)) { 246.3415 + mode = this.Mode_.SCROLL; 246.3416 + container.classList.add(this.CssClasses_.HAS_SCROLLING_HEADER); 246.3417 + } 246.3418 + if (mode === this.Mode_.STANDARD) { 246.3419 + this.header_.classList.add(this.CssClasses_.CASTING_SHADOW); 246.3420 + if (this.tabBar_) { 246.3421 + this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW); 246.3422 + } 246.3423 + } else if (mode === this.Mode_.SEAMED || mode === this.Mode_.SCROLL) { 246.3424 + this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW); 246.3425 + if (this.tabBar_) { 246.3426 + this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW); 246.3427 + } 246.3428 + } else if (mode === this.Mode_.WATERFALL) { 246.3429 + // Add and remove shadows depending on scroll position. 246.3430 + // Also add/remove auxiliary class for styling of the compact version of 246.3431 + // the header. 246.3432 + this.content_.addEventListener('scroll', this.contentScrollHandler_.bind(this)); 246.3433 + this.contentScrollHandler_(); 246.3434 + } 246.3435 + } 246.3436 + // Add drawer toggling button to our layout, if we have an openable drawer. 246.3437 + if (this.drawer_) { 246.3438 + var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN); 246.3439 + if (!drawerButton) { 246.3440 + drawerButton = document.createElement('div'); 246.3441 + drawerButton.setAttribute('aria-expanded', 'false'); 246.3442 + drawerButton.setAttribute('role', 'button'); 246.3443 + drawerButton.setAttribute('tabindex', '0'); 246.3444 + drawerButton.classList.add(this.CssClasses_.DRAWER_BTN); 246.3445 + var drawerButtonIcon = document.createElement('i'); 246.3446 + drawerButtonIcon.classList.add(this.CssClasses_.ICON); 246.3447 + drawerButtonIcon.innerHTML = this.Constant_.MENU_ICON; 246.3448 + drawerButton.appendChild(drawerButtonIcon); 246.3449 + } 246.3450 + if (this.drawer_.classList.contains(this.CssClasses_.ON_LARGE_SCREEN)) { 246.3451 + //If drawer has ON_LARGE_SCREEN class then add it to the drawer toggle button as well. 246.3452 + drawerButton.classList.add(this.CssClasses_.ON_LARGE_SCREEN); 246.3453 + } else if (this.drawer_.classList.contains(this.CssClasses_.ON_SMALL_SCREEN)) { 246.3454 + //If drawer has ON_SMALL_SCREEN class then add it to the drawer toggle button as well. 246.3455 + drawerButton.classList.add(this.CssClasses_.ON_SMALL_SCREEN); 246.3456 + } 246.3457 + drawerButton.addEventListener('click', this.drawerToggleHandler_.bind(this)); 246.3458 + drawerButton.addEventListener('keydown', this.drawerToggleHandler_.bind(this)); 246.3459 + // Add a class if the layout has a drawer, for altering the left padding. 246.3460 + // Adds the HAS_DRAWER to the elements since this.header_ may or may 246.3461 + // not be present. 246.3462 + this.element_.classList.add(this.CssClasses_.HAS_DRAWER); 246.3463 + // If we have a fixed header, add the button to the header rather than 246.3464 + // the layout. 246.3465 + if (this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)) { 246.3466 + this.header_.insertBefore(drawerButton, this.header_.firstChild); 246.3467 + } else { 246.3468 + this.element_.insertBefore(drawerButton, this.content_); 246.3469 + } 246.3470 + var obfuscator = document.createElement('div'); 246.3471 + obfuscator.classList.add(this.CssClasses_.OBFUSCATOR); 246.3472 + this.element_.appendChild(obfuscator); 246.3473 + obfuscator.addEventListener('click', this.drawerToggleHandler_.bind(this)); 246.3474 + this.obfuscator_ = obfuscator; 246.3475 + this.drawer_.addEventListener('keydown', this.keyboardEventHandler_.bind(this)); 246.3476 + this.drawer_.setAttribute('aria-hidden', 'true'); 246.3477 + } 246.3478 + // Keep an eye on screen size, and add/remove auxiliary class for styling 246.3479 + // of small screens. 246.3480 + this.screenSizeMediaQuery_ = window.matchMedia(this.Constant_.MAX_WIDTH); 246.3481 + this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this)); 246.3482 + this.screenSizeHandler_(); 246.3483 + // Initialize tabs, if any. 246.3484 + if (this.header_ && this.tabBar_) { 246.3485 + this.element_.classList.add(this.CssClasses_.HAS_TABS); 246.3486 + var tabContainer = document.createElement('div'); 246.3487 + tabContainer.classList.add(this.CssClasses_.TAB_CONTAINER); 246.3488 + this.header_.insertBefore(tabContainer, this.tabBar_); 246.3489 + this.header_.removeChild(this.tabBar_); 246.3490 + var leftButton = document.createElement('div'); 246.3491 + leftButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON); 246.3492 + leftButton.classList.add(this.CssClasses_.TAB_BAR_LEFT_BUTTON); 246.3493 + var leftButtonIcon = document.createElement('i'); 246.3494 + leftButtonIcon.classList.add(this.CssClasses_.ICON); 246.3495 + leftButtonIcon.textContent = this.Constant_.CHEVRON_LEFT; 246.3496 + leftButton.appendChild(leftButtonIcon); 246.3497 + leftButton.addEventListener('click', function () { 246.3498 + this.tabBar_.scrollLeft -= this.Constant_.TAB_SCROLL_PIXELS; 246.3499 + }.bind(this)); 246.3500 + var rightButton = document.createElement('div'); 246.3501 + rightButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON); 246.3502 + rightButton.classList.add(this.CssClasses_.TAB_BAR_RIGHT_BUTTON); 246.3503 + var rightButtonIcon = document.createElement('i'); 246.3504 + rightButtonIcon.classList.add(this.CssClasses_.ICON); 246.3505 + rightButtonIcon.textContent = this.Constant_.CHEVRON_RIGHT; 246.3506 + rightButton.appendChild(rightButtonIcon); 246.3507 + rightButton.addEventListener('click', function () { 246.3508 + this.tabBar_.scrollLeft += this.Constant_.TAB_SCROLL_PIXELS; 246.3509 + }.bind(this)); 246.3510 + tabContainer.appendChild(leftButton); 246.3511 + tabContainer.appendChild(this.tabBar_); 246.3512 + tabContainer.appendChild(rightButton); 246.3513 + // Add and remove tab buttons depending on scroll position and total 246.3514 + // window size. 246.3515 + var tabUpdateHandler = function () { 246.3516 + if (this.tabBar_.scrollLeft > 0) { 246.3517 + leftButton.classList.add(this.CssClasses_.IS_ACTIVE); 246.3518 + } else { 246.3519 + leftButton.classList.remove(this.CssClasses_.IS_ACTIVE); 246.3520 + } 246.3521 + if (this.tabBar_.scrollLeft < this.tabBar_.scrollWidth - this.tabBar_.offsetWidth) { 246.3522 + rightButton.classList.add(this.CssClasses_.IS_ACTIVE); 246.3523 + } else { 246.3524 + rightButton.classList.remove(this.CssClasses_.IS_ACTIVE); 246.3525 + } 246.3526 + }.bind(this); 246.3527 + this.tabBar_.addEventListener('scroll', tabUpdateHandler); 246.3528 + tabUpdateHandler(); 246.3529 + // Update tabs when the window resizes. 246.3530 + var windowResizeHandler = function () { 246.3531 + // Use timeouts to make sure it doesn't happen too often. 246.3532 + if (this.resizeTimeoutId_) { 246.3533 + clearTimeout(this.resizeTimeoutId_); 246.3534 + } 246.3535 + this.resizeTimeoutId_ = setTimeout(function () { 246.3536 + tabUpdateHandler(); 246.3537 + this.resizeTimeoutId_ = null; 246.3538 + }.bind(this), this.Constant_.RESIZE_TIMEOUT); 246.3539 + }.bind(this); 246.3540 + window.addEventListener('resize', windowResizeHandler); 246.3541 + if (this.tabBar_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) { 246.3542 + this.tabBar_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS); 246.3543 + } 246.3544 + // Select element tabs, document panels 246.3545 + var tabs = this.tabBar_.querySelectorAll('.' + this.CssClasses_.TAB); 246.3546 + var panels = this.content_.querySelectorAll('.' + this.CssClasses_.PANEL); 246.3547 + // Create new tabs for each tab element 246.3548 + for (var i = 0; i < tabs.length; i++) { 246.3549 + new MaterialLayoutTab(tabs[i], tabs, panels, this); 246.3550 + } 246.3551 + } 246.3552 + this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 246.3553 + } 246.3554 +}; 246.3555 +/** 246.3556 + * Constructor for an individual tab. 246.3557 + * 246.3558 + * @constructor 246.3559 + * @param {HTMLElement} tab The HTML element for the tab. 246.3560 + * @param {!Array<HTMLElement>} tabs Array with HTML elements for all tabs. 246.3561 + * @param {!Array<HTMLElement>} panels Array with HTML elements for all panels. 246.3562 + * @param {MaterialLayout} layout The MaterialLayout object that owns the tab. 246.3563 + */ 246.3564 +function MaterialLayoutTab(tab, tabs, panels, layout) { 246.3565 + /** 246.3566 + * Auxiliary method to programmatically select a tab in the UI. 246.3567 + */ 246.3568 + function selectTab() { 246.3569 + var href = tab.href.split('#')[1]; 246.3570 + var panel = layout.content_.querySelector('#' + href); 246.3571 + layout.resetTabState_(tabs); 246.3572 + layout.resetPanelState_(panels); 246.3573 + tab.classList.add(layout.CssClasses_.IS_ACTIVE); 246.3574 + panel.classList.add(layout.CssClasses_.IS_ACTIVE); 246.3575 + } 246.3576 + if (layout.tabBar_.classList.contains(layout.CssClasses_.JS_RIPPLE_EFFECT)) { 246.3577 + var rippleContainer = document.createElement('span'); 246.3578 + rippleContainer.classList.add(layout.CssClasses_.RIPPLE_CONTAINER); 246.3579 + rippleContainer.classList.add(layout.CssClasses_.JS_RIPPLE_EFFECT); 246.3580 + var ripple = document.createElement('span'); 246.3581 + ripple.classList.add(layout.CssClasses_.RIPPLE); 246.3582 + rippleContainer.appendChild(ripple); 246.3583 + tab.appendChild(rippleContainer); 246.3584 + } 246.3585 + tab.addEventListener('click', function (e) { 246.3586 + if (tab.getAttribute('href').charAt(0) === '#') { 246.3587 + e.preventDefault(); 246.3588 + selectTab(); 246.3589 + } 246.3590 + }); 246.3591 + tab.show = selectTab; 246.3592 +} 246.3593 +window['MaterialLayoutTab'] = MaterialLayoutTab; 246.3594 +// The component registers itself. It can assume componentHandler is available 246.3595 +// in the global scope. 246.3596 +componentHandler.register({ 246.3597 + constructor: MaterialLayout, 246.3598 + classAsString: 'MaterialLayout', 246.3599 + cssClass: 'mdl-js-layout' 246.3600 +}); 246.3601 +/** 246.3602 + * @license 246.3603 + * Copyright 2015 Google Inc. All Rights Reserved. 246.3604 + * 246.3605 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.3606 + * you may not use this file except in compliance with the License. 246.3607 + * You may obtain a copy of the License at 246.3608 + * 246.3609 + * http://www.apache.org/licenses/LICENSE-2.0 246.3610 + * 246.3611 + * Unless required by applicable law or agreed to in writing, software 246.3612 + * distributed under the License is distributed on an "AS IS" BASIS, 246.3613 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.3614 + * See the License for the specific language governing permissions and 246.3615 + * limitations under the License. 246.3616 + */ 246.3617 +/** 246.3618 + * Class constructor for Data Table Card MDL component. 246.3619 + * Implements MDL component design pattern defined at: 246.3620 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.3621 + * 246.3622 + * @constructor 246.3623 + * @param {Element} element The element that will be upgraded. 246.3624 + */ 246.3625 +var MaterialDataTable = function MaterialDataTable(element) { 246.3626 + this.element_ = element; 246.3627 + // Initialize instance. 246.3628 + this.init(); 246.3629 +}; 246.3630 +window['MaterialDataTable'] = MaterialDataTable; 246.3631 +/** 246.3632 + * Store constants in one place so they can be updated easily. 246.3633 + * 246.3634 + * @enum {string | number} 246.3635 + * @private 246.3636 + */ 246.3637 +MaterialDataTable.prototype.Constant_ = {}; 246.3638 +/** 246.3639 + * Store strings for class names defined by this component that are used in 246.3640 + * JavaScript. This allows us to simply change it in one place should we 246.3641 + * decide to modify at a later date. 246.3642 + * 246.3643 + * @enum {string} 246.3644 + * @private 246.3645 + */ 246.3646 +MaterialDataTable.prototype.CssClasses_ = { 246.3647 + DATA_TABLE: 'mdl-data-table', 246.3648 + SELECTABLE: 'mdl-data-table--selectable', 246.3649 + SELECT_ELEMENT: 'mdl-data-table__select', 246.3650 + IS_SELECTED: 'is-selected', 246.3651 + IS_UPGRADED: 'is-upgraded' 246.3652 +}; 246.3653 +/** 246.3654 + * Generates and returns a function that toggles the selection state of a 246.3655 + * single row (or multiple rows). 246.3656 + * 246.3657 + * @param {Element} checkbox Checkbox that toggles the selection state. 246.3658 + * @param {Element} row Row to toggle when checkbox changes. 246.3659 + * @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes. 246.3660 + * @private 246.3661 + */ 246.3662 +MaterialDataTable.prototype.selectRow_ = function (checkbox, row, opt_rows) { 246.3663 + if (row) { 246.3664 + return function () { 246.3665 + if (checkbox.checked) { 246.3666 + row.classList.add(this.CssClasses_.IS_SELECTED); 246.3667 + } else { 246.3668 + row.classList.remove(this.CssClasses_.IS_SELECTED); 246.3669 + } 246.3670 + }.bind(this); 246.3671 + } 246.3672 + if (opt_rows) { 246.3673 + return function () { 246.3674 + var i; 246.3675 + var el; 246.3676 + if (checkbox.checked) { 246.3677 + for (i = 0; i < opt_rows.length; i++) { 246.3678 + el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox'); 246.3679 + el['MaterialCheckbox'].check(); 246.3680 + opt_rows[i].classList.add(this.CssClasses_.IS_SELECTED); 246.3681 + } 246.3682 + } else { 246.3683 + for (i = 0; i < opt_rows.length; i++) { 246.3684 + el = opt_rows[i].querySelector('td').querySelector('.mdl-checkbox'); 246.3685 + el['MaterialCheckbox'].uncheck(); 246.3686 + opt_rows[i].classList.remove(this.CssClasses_.IS_SELECTED); 246.3687 + } 246.3688 + } 246.3689 + }.bind(this); 246.3690 + } 246.3691 +}; 246.3692 +/** 246.3693 + * Creates a checkbox for a single or or multiple rows and hooks up the 246.3694 + * event handling. 246.3695 + * 246.3696 + * @param {Element} row Row to toggle when checkbox changes. 246.3697 + * @param {(Array<Object>|NodeList)=} opt_rows Rows to toggle when checkbox changes. 246.3698 + * @private 246.3699 + */ 246.3700 +MaterialDataTable.prototype.createCheckbox_ = function (row, opt_rows) { 246.3701 + var label = document.createElement('label'); 246.3702 + var labelClasses = [ 246.3703 + 'mdl-checkbox', 246.3704 + 'mdl-js-checkbox', 246.3705 + 'mdl-js-ripple-effect', 246.3706 + this.CssClasses_.SELECT_ELEMENT 246.3707 + ]; 246.3708 + label.className = labelClasses.join(' '); 246.3709 + var checkbox = document.createElement('input'); 246.3710 + checkbox.type = 'checkbox'; 246.3711 + checkbox.classList.add('mdl-checkbox__input'); 246.3712 + if (row) { 246.3713 + checkbox.checked = row.classList.contains(this.CssClasses_.IS_SELECTED); 246.3714 + checkbox.addEventListener('change', this.selectRow_(checkbox, row)); 246.3715 + } else if (opt_rows) { 246.3716 + checkbox.addEventListener('change', this.selectRow_(checkbox, null, opt_rows)); 246.3717 + } 246.3718 + label.appendChild(checkbox); 246.3719 + componentHandler.upgradeElement(label, 'MaterialCheckbox'); 246.3720 + return label; 246.3721 +}; 246.3722 +/** 246.3723 + * Initialize element. 246.3724 + */ 246.3725 +MaterialDataTable.prototype.init = function () { 246.3726 + if (this.element_) { 246.3727 + var firstHeader = this.element_.querySelector('th'); 246.3728 + var bodyRows = Array.prototype.slice.call(this.element_.querySelectorAll('tbody tr')); 246.3729 + var footRows = Array.prototype.slice.call(this.element_.querySelectorAll('tfoot tr')); 246.3730 + var rows = bodyRows.concat(footRows); 246.3731 + if (this.element_.classList.contains(this.CssClasses_.SELECTABLE)) { 246.3732 + var th = document.createElement('th'); 246.3733 + var headerCheckbox = this.createCheckbox_(null, rows); 246.3734 + th.appendChild(headerCheckbox); 246.3735 + firstHeader.parentElement.insertBefore(th, firstHeader); 246.3736 + for (var i = 0; i < rows.length; i++) { 246.3737 + var firstCell = rows[i].querySelector('td'); 246.3738 + if (firstCell) { 246.3739 + var td = document.createElement('td'); 246.3740 + if (rows[i].parentNode.nodeName.toUpperCase() === 'TBODY') { 246.3741 + var rowCheckbox = this.createCheckbox_(rows[i]); 246.3742 + td.appendChild(rowCheckbox); 246.3743 + } 246.3744 + rows[i].insertBefore(td, firstCell); 246.3745 + } 246.3746 + } 246.3747 + this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 246.3748 + } 246.3749 + } 246.3750 +}; 246.3751 +// The component registers itself. It can assume componentHandler is available 246.3752 +// in the global scope. 246.3753 +componentHandler.register({ 246.3754 + constructor: MaterialDataTable, 246.3755 + classAsString: 'MaterialDataTable', 246.3756 + cssClass: 'mdl-js-data-table' 246.3757 +}); 246.3758 +/** 246.3759 + * @license 246.3760 + * Copyright 2015 Google Inc. All Rights Reserved. 246.3761 + * 246.3762 + * Licensed under the Apache License, Version 2.0 (the "License"); 246.3763 + * you may not use this file except in compliance with the License. 246.3764 + * You may obtain a copy of the License at 246.3765 + * 246.3766 + * http://www.apache.org/licenses/LICENSE-2.0 246.3767 + * 246.3768 + * Unless required by applicable law or agreed to in writing, software 246.3769 + * distributed under the License is distributed on an "AS IS" BASIS, 246.3770 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 246.3771 + * See the License for the specific language governing permissions and 246.3772 + * limitations under the License. 246.3773 + */ 246.3774 +/** 246.3775 + * Class constructor for Ripple MDL component. 246.3776 + * Implements MDL component design pattern defined at: 246.3777 + * https://github.com/jasonmayes/mdl-component-design-pattern 246.3778 + * 246.3779 + * @constructor 246.3780 + * @param {HTMLElement} element The element that will be upgraded. 246.3781 + */ 246.3782 +var MaterialRipple = function MaterialRipple(element) { 246.3783 + this.element_ = element; 246.3784 + // Initialize instance. 246.3785 + this.init(); 246.3786 +}; 246.3787 +window['MaterialRipple'] = MaterialRipple; 246.3788 +/** 246.3789 + * Store constants in one place so they can be updated easily. 246.3790 + * 246.3791 + * @enum {string | number} 246.3792 + * @private 246.3793 + */ 246.3794 +MaterialRipple.prototype.Constant_ = { 246.3795 + INITIAL_SCALE: 'scale(0.0001, 0.0001)', 246.3796 + INITIAL_SIZE: '1px', 246.3797 + INITIAL_OPACITY: '0.4', 246.3798 + FINAL_OPACITY: '0', 246.3799 + FINAL_SCALE: '' 246.3800 +}; 246.3801 +/** 246.3802 + * Store strings for class names defined by this component that are used in 246.3803 + * JavaScript. This allows us to simply change it in one place should we 246.3804 + * decide to modify at a later date. 246.3805 + * 246.3806 + * @enum {string} 246.3807 + * @private 246.3808 + */ 246.3809 +MaterialRipple.prototype.CssClasses_ = { 246.3810 + RIPPLE_CENTER: 'mdl-ripple--center', 246.3811 + RIPPLE_EFFECT_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events', 246.3812 + RIPPLE: 'mdl-ripple', 246.3813 + IS_ANIMATING: 'is-animating', 246.3814 + IS_VISIBLE: 'is-visible' 246.3815 +}; 246.3816 +/** 246.3817 + * Handle mouse / finger down on element. 246.3818 + * 246.3819 + * @param {Event} event The event that fired. 246.3820 + * @private 246.3821 + */ 246.3822 +MaterialRipple.prototype.downHandler_ = function (event) { 246.3823 + if (!this.rippleElement_.style.width && !this.rippleElement_.style.height) { 246.3824 + var rect = this.element_.getBoundingClientRect(); 246.3825 + this.boundHeight = rect.height; 246.3826 + this.boundWidth = rect.width; 246.3827 + this.rippleSize_ = Math.sqrt(rect.width * rect.width + rect.height * rect.height) * 2 + 2; 246.3828 + this.rippleElement_.style.width = this.rippleSize_ + 'px'; 246.3829 + this.rippleElement_.style.height = this.rippleSize_ + 'px'; 246.3830 + } 246.3831 + this.rippleElement_.classList.add(this.CssClasses_.IS_VISIBLE); 246.3832 + if (event.type === 'mousedown' && this.ignoringMouseDown_) { 246.3833 + this.ignoringMouseDown_ = false; 246.3834 + } else { 246.3835 + if (event.type === 'touchstart') { 246.3836 + this.ignoringMouseDown_ = true; 246.3837 + } 246.3838 + var frameCount = this.getFrameCount(); 246.3839 + if (frameCount > 0) { 246.3840 + return; 246.3841 + } 246.3842 + this.setFrameCount(1); 246.3843 + var bound = event.currentTarget.getBoundingClientRect(); 246.3844 + var x; 246.3845 + var y; 246.3846 + // Check if we are handling a keyboard click. 246.3847 + if (event.clientX === 0 && event.clientY === 0) { 246.3848 + x = Math.round(bound.width / 2); 246.3849 + y = Math.round(bound.height / 2); 246.3850 + } else { 246.3851 + var clientX = event.clientX ? event.clientX : event.touches[0].clientX; 246.3852 + var clientY = event.clientY ? event.clientY : event.touches[0].clientY; 246.3853 + x = Math.round(clientX - bound.left); 246.3854 + y = Math.round(clientY - bound.top); 246.3855 + } 246.3856 + this.setRippleXY(x, y); 246.3857 + this.setRippleStyles(true); 246.3858 + window.requestAnimationFrame(this.animFrameHandler.bind(this)); 246.3859 + } 246.3860 +}; 246.3861 +/** 246.3862 + * Handle mouse / finger up on element. 246.3863 + * 246.3864 + * @param {Event} event The event that fired. 246.3865 + * @private 246.3866 + */ 246.3867 +MaterialRipple.prototype.upHandler_ = function (event) { 246.3868 + // Don't fire for the artificial "mouseup" generated by a double-click. 246.3869 + if (event && event.detail !== 2) { 246.3870 + // Allow a repaint to occur before removing this class, so the animation 246.3871 + // shows for tap events, which seem to trigger a mouseup too soon after 246.3872 + // mousedown. 246.3873 + window.setTimeout(function () { 246.3874 + this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE); 246.3875 + }.bind(this), 0); 246.3876 + } 246.3877 +}; 246.3878 +/** 246.3879 + * Initialize element. 246.3880 + */ 246.3881 +MaterialRipple.prototype.init = function () { 246.3882 + if (this.element_) { 246.3883 + var recentering = this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER); 246.3884 + if (!this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)) { 246.3885 + this.rippleElement_ = this.element_.querySelector('.' + this.CssClasses_.RIPPLE); 246.3886 + this.frameCount_ = 0; 246.3887 + this.rippleSize_ = 0; 246.3888 + this.x_ = 0; 246.3889 + this.y_ = 0; 246.3890 + // Touch start produces a compat mouse down event, which would cause a 246.3891 + // second ripples. To avoid that, we use this property to ignore the first 246.3892 + // mouse down after a touch start. 246.3893 + this.ignoringMouseDown_ = false; 246.3894 + this.boundDownHandler = this.downHandler_.bind(this); 246.3895 + this.element_.addEventListener('mousedown', this.boundDownHandler); 246.3896 + this.element_.addEventListener('touchstart', this.boundDownHandler); 246.3897 + this.boundUpHandler = this.upHandler_.bind(this); 246.3898 + this.element_.addEventListener('mouseup', this.boundUpHandler); 246.3899 + this.element_.addEventListener('mouseleave', this.boundUpHandler); 246.3900 + this.element_.addEventListener('touchend', this.boundUpHandler); 246.3901 + this.element_.addEventListener('blur', this.boundUpHandler); 246.3902 + /** 246.3903 + * Getter for frameCount_. 246.3904 + * @return {number} the frame count. 246.3905 + */ 246.3906 + this.getFrameCount = function () { 246.3907 + return this.frameCount_; 246.3908 + }; 246.3909 + /** 246.3910 + * Setter for frameCount_. 246.3911 + * @param {number} fC the frame count. 246.3912 + */ 246.3913 + this.setFrameCount = function (fC) { 246.3914 + this.frameCount_ = fC; 246.3915 + }; 246.3916 + /** 246.3917 + * Getter for rippleElement_. 246.3918 + * @return {Element} the ripple element. 246.3919 + */ 246.3920 + this.getRippleElement = function () { 246.3921 + return this.rippleElement_; 246.3922 + }; 246.3923 + /** 246.3924 + * Sets the ripple X and Y coordinates. 246.3925 + * @param {number} newX the new X coordinate 246.3926 + * @param {number} newY the new Y coordinate 246.3927 + */ 246.3928 + this.setRippleXY = function (newX, newY) { 246.3929 + this.x_ = newX; 246.3930 + this.y_ = newY; 246.3931 + }; 246.3932 + /** 246.3933 + * Sets the ripple styles. 246.3934 + * @param {boolean} start whether or not this is the start frame. 246.3935 + */ 246.3936 + this.setRippleStyles = function (start) { 246.3937 + if (this.rippleElement_ !== null) { 246.3938 + var transformString; 246.3939 + var scale; 246.3940 + var size; 246.3941 + var offset = 'translate(' + this.x_ + 'px, ' + this.y_ + 'px)'; 246.3942 + if (start) { 246.3943 + scale = this.Constant_.INITIAL_SCALE; 246.3944 + size = this.Constant_.INITIAL_SIZE; 246.3945 + } else { 246.3946 + scale = this.Constant_.FINAL_SCALE; 246.3947 + size = this.rippleSize_ + 'px'; 246.3948 + if (recentering) { 246.3949 + offset = 'translate(' + this.boundWidth / 2 + 'px, ' + this.boundHeight / 2 + 'px)'; 246.3950 + } 246.3951 + } 246.3952 + transformString = 'translate(-50%, -50%) ' + offset + scale; 246.3953 + this.rippleElement_.style.webkitTransform = transformString; 246.3954 + this.rippleElement_.style.msTransform = transformString; 246.3955 + this.rippleElement_.style.transform = transformString; 246.3956 + if (start) { 246.3957 + this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING); 246.3958 + } else { 246.3959 + this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING); 246.3960 + } 246.3961 + } 246.3962 + }; 246.3963 + /** 246.3964 + * Handles an animation frame. 246.3965 + */ 246.3966 + this.animFrameHandler = function () { 246.3967 + if (this.frameCount_-- > 0) { 246.3968 + window.requestAnimationFrame(this.animFrameHandler.bind(this)); 246.3969 + } else { 246.3970 + this.setRippleStyles(false); 246.3971 + } 246.3972 + }; 246.3973 + } 246.3974 + } 246.3975 +}; 246.3976 +// The component registers itself. It can assume componentHandler is available 246.3977 +// in the global scope. 246.3978 +componentHandler.register({ 246.3979 + constructor: MaterialRipple, 246.3980 + classAsString: 'MaterialRipple', 246.3981 + cssClass: 'mdl-js-ripple-effect', 246.3982 + widget: false 246.3983 +}); 246.3984 +}());
247.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 247.2 +++ b/static/mdl/material.min.js Sun Jul 15 14:07:29 2018 +0200 247.3 @@ -0,0 +1,10 @@ 247.4 +/** 247.5 + * material-design-lite - Material Design Components in CSS, JS and HTML 247.6 + * @version v1.2.1 247.7 + * @license Apache-2.0 247.8 + * @copyright 2015 Google, Inc. 247.9 + * @link https://github.com/google/material-design-lite 247.10 + */ 247.11 +!function(){"use strict";function e(e,t){if(e){if(t.element_.classList.contains(t.CssClasses_.MDL_JS_RIPPLE_EFFECT)){var s=document.createElement("span");s.classList.add(t.CssClasses_.MDL_RIPPLE_CONTAINER),s.classList.add(t.CssClasses_.MDL_JS_RIPPLE_EFFECT);var i=document.createElement("span");i.classList.add(t.CssClasses_.MDL_RIPPLE),s.appendChild(i),e.appendChild(s)}e.addEventListener("click",function(s){s.preventDefault();var i=e.href.split("#")[1],n=t.element_.querySelector("#"+i);t.resetTabState_(),t.resetPanelState_(),e.classList.add(t.CssClasses_.ACTIVE_CLASS),n.classList.add(t.CssClasses_.ACTIVE_CLASS)})}}function t(e,t,s,i){function n(){var n=e.href.split("#")[1],a=i.content_.querySelector("#"+n);i.resetTabState_(t),i.resetPanelState_(s),e.classList.add(i.CssClasses_.IS_ACTIVE),a.classList.add(i.CssClasses_.IS_ACTIVE)}if(i.tabBar_.classList.contains(i.CssClasses_.JS_RIPPLE_EFFECT)){var a=document.createElement("span");a.classList.add(i.CssClasses_.RIPPLE_CONTAINER),a.classList.add(i.CssClasses_.JS_RIPPLE_EFFECT);var l=document.createElement("span");l.classList.add(i.CssClasses_.RIPPLE),a.appendChild(l),e.appendChild(a)}e.addEventListener("click",function(t){"#"===e.getAttribute("href").charAt(0)&&(t.preventDefault(),n())}),e.show=n}var s={upgradeDom:function(e,t){},upgradeElement:function(e,t){},upgradeElements:function(e){},upgradeAllRegistered:function(){},registerUpgradedCallback:function(e,t){},register:function(e){},downgradeElements:function(e){}};s=function(){function e(e,t){for(var s=0;s<h.length;s++)if(h[s].className===e)return"undefined"!=typeof t&&(h[s]=t),h[s];return!1}function t(e){var t=e.getAttribute("data-upgraded");return null===t?[""]:t.split(",")}function s(e,s){var i=t(e);return i.indexOf(s)!==-1}function i(t,s){if("undefined"==typeof t&&"undefined"==typeof s)for(var a=0;a<h.length;a++)i(h[a].className,h[a].cssClass);else{var l=t;if("undefined"==typeof s){var o=e(l);o&&(s=o.cssClass)}for(var r=document.querySelectorAll("."+s),_=0;_<r.length;_++)n(r[_],l)}}function n(i,n){if(!("object"==typeof i&&i instanceof Element))throw new Error("Invalid argument provided to upgrade MDL element.");var a=t(i),l=[];if(n)s(i,n)||l.push(e(n));else{var o=i.classList;h.forEach(function(e){o.contains(e.cssClass)&&l.indexOf(e)===-1&&!s(i,e.className)&&l.push(e)})}for(var r,_=0,d=l.length;_<d;_++){if(r=l[_],!r)throw new Error("Unable to find a registered component for the given class.");a.push(r.className),i.setAttribute("data-upgraded",a.join(","));var C=new r.classConstructor(i);C[p]=r,c.push(C);for(var u=0,E=r.callbacks.length;u<E;u++)r.callbacks[u](i);r.widget&&(i[r.className]=C);var m;"CustomEvent"in window&&"function"==typeof window.CustomEvent?m=new CustomEvent("mdl-componentupgraded",{bubbles:!0,cancelable:!1}):(m=document.createEvent("Events"),m.initEvent("mdl-componentupgraded",!0,!0)),i.dispatchEvent(m)}}function a(e){Array.isArray(e)||(e=e instanceof Element?[e]:Array.prototype.slice.call(e));for(var t,s=0,i=e.length;s<i;s++)t=e[s],t instanceof HTMLElement&&(n(t),t.children.length>0&&a(t.children))}function l(t){var s="undefined"==typeof t.widget&&"undefined"==typeof t.widget,i=!0;s||(i=t.widget||t.widget);var n={classConstructor:t.constructor||t.constructor,className:t.classAsString||t.classAsString,cssClass:t.cssClass||t.cssClass,widget:i,callbacks:[]};if(h.forEach(function(e){if(e.cssClass===n.cssClass)throw new Error("The provided cssClass has already been registered: "+e.cssClass);if(e.className===n.className)throw new Error("The provided className has already been registered")}),t.constructor.prototype.hasOwnProperty(p))throw new Error("MDL component classes must not have "+p+" defined as a property.");var a=e(t.classAsString,n);a||h.push(n)}function o(t,s){var i=e(t);i&&i.callbacks.push(s)}function r(){for(var e=0;e<h.length;e++)i(h[e].className)}function _(e){if(e){var t=c.indexOf(e);c.splice(t,1);var s=e.element_.getAttribute("data-upgraded").split(","),i=s.indexOf(e[p].classAsString);s.splice(i,1),e.element_.setAttribute("data-upgraded",s.join(","));var n;"CustomEvent"in window&&"function"==typeof window.CustomEvent?n=new CustomEvent("mdl-componentdowngraded",{bubbles:!0,cancelable:!1}):(n=document.createEvent("Events"),n.initEvent("mdl-componentdowngraded",!0,!0)),e.element_.dispatchEvent(n)}}function d(e){var t=function(e){c.filter(function(t){return t.element_===e}).forEach(_)};if(e instanceof Array||e instanceof NodeList)for(var s=0;s<e.length;s++)t(e[s]);else{if(!(e instanceof Node))throw new Error("Invalid argument provided to downgrade MDL nodes.");t(e)}}var h=[],c=[],p="mdlComponentConfigInternal_";return{upgradeDom:i,upgradeElement:n,upgradeElements:a,upgradeAllRegistered:r,registerUpgradedCallback:o,register:l,downgradeElements:d}}(),s.ComponentConfigPublic,s.ComponentConfig,s.Component,s.upgradeDom=s.upgradeDom,s.upgradeElement=s.upgradeElement,s.upgradeElements=s.upgradeElements,s.upgradeAllRegistered=s.upgradeAllRegistered,s.registerUpgradedCallback=s.registerUpgradedCallback,s.register=s.register,s.downgradeElements=s.downgradeElements,window.componentHandler=s,window.componentHandler=s,window.addEventListener("load",function(){"classList"in document.createElement("div")&&"querySelector"in document&&"addEventListener"in window&&Array.prototype.forEach?(document.documentElement.classList.add("mdl-js"),s.upgradeAllRegistered()):(s.upgradeElement=function(){},s.register=function(){})}),Date.now||(Date.now=function(){return(new Date).getTime()},Date.now=Date.now);for(var i=["webkit","moz"],n=0;n<i.length&&!window.requestAnimationFrame;++n){var a=i[n];window.requestAnimationFrame=window[a+"RequestAnimationFrame"],window.cancelAnimationFrame=window[a+"CancelAnimationFrame"]||window[a+"CancelRequestAnimationFrame"],window.requestAnimationFrame=window.requestAnimationFrame,window.cancelAnimationFrame=window.cancelAnimationFrame}if(/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent)||!window.requestAnimationFrame||!window.cancelAnimationFrame){var l=0;window.requestAnimationFrame=function(e){var t=Date.now(),s=Math.max(l+16,t);return setTimeout(function(){e(l=s)},s-t)},window.cancelAnimationFrame=clearTimeout,window.requestAnimationFrame=window.requestAnimationFrame,window.cancelAnimationFrame=window.cancelAnimationFrame}var o=function(e){this.element_=e,this.init()};window.MaterialButton=o,o.prototype.Constant_={},o.prototype.CssClasses_={RIPPLE_EFFECT:"mdl-js-ripple-effect",RIPPLE_CONTAINER:"mdl-button__ripple-container",RIPPLE:"mdl-ripple"},o.prototype.blurHandler_=function(e){e&&this.element_.blur()},o.prototype.disable=function(){this.element_.disabled=!0},o.prototype.disable=o.prototype.disable,o.prototype.enable=function(){this.element_.disabled=!1},o.prototype.enable=o.prototype.enable,o.prototype.init=function(){if(this.element_){if(this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)){var e=document.createElement("span");e.classList.add(this.CssClasses_.RIPPLE_CONTAINER),this.rippleElement_=document.createElement("span"),this.rippleElement_.classList.add(this.CssClasses_.RIPPLE),e.appendChild(this.rippleElement_),this.boundRippleBlurHandler=this.blurHandler_.bind(this),this.rippleElement_.addEventListener("mouseup",this.boundRippleBlurHandler),this.element_.appendChild(e)}this.boundButtonBlurHandler=this.blurHandler_.bind(this),this.element_.addEventListener("mouseup",this.boundButtonBlurHandler),this.element_.addEventListener("mouseleave",this.boundButtonBlurHandler)}},s.register({constructor:o,classAsString:"MaterialButton",cssClass:"mdl-js-button",widget:!0});var r=function(e){this.element_=e,this.init()};window.MaterialCheckbox=r,r.prototype.Constant_={TINY_TIMEOUT:.001},r.prototype.CssClasses_={INPUT:"mdl-checkbox__input",BOX_OUTLINE:"mdl-checkbox__box-outline",FOCUS_HELPER:"mdl-checkbox__focus-helper",TICK_OUTLINE:"mdl-checkbox__tick-outline",RIPPLE_EFFECT:"mdl-js-ripple-effect",RIPPLE_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events",RIPPLE_CONTAINER:"mdl-checkbox__ripple-container",RIPPLE_CENTER:"mdl-ripple--center",RIPPLE:"mdl-ripple",IS_FOCUSED:"is-focused",IS_DISABLED:"is-disabled",IS_CHECKED:"is-checked",IS_UPGRADED:"is-upgraded"},r.prototype.onChange_=function(e){this.updateClasses_()},r.prototype.onFocus_=function(e){this.element_.classList.add(this.CssClasses_.IS_FOCUSED)},r.prototype.onBlur_=function(e){this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},r.prototype.onMouseUp_=function(e){this.blur_()},r.prototype.updateClasses_=function(){this.checkDisabled(),this.checkToggleState()},r.prototype.blur_=function(){window.setTimeout(function(){this.inputElement_.blur()}.bind(this),this.Constant_.TINY_TIMEOUT)},r.prototype.checkToggleState=function(){this.inputElement_.checked?this.element_.classList.add(this.CssClasses_.IS_CHECKED):this.element_.classList.remove(this.CssClasses_.IS_CHECKED)},r.prototype.checkToggleState=r.prototype.checkToggleState,r.prototype.checkDisabled=function(){this.inputElement_.disabled?this.element_.classList.add(this.CssClasses_.IS_DISABLED):this.element_.classList.remove(this.CssClasses_.IS_DISABLED)},r.prototype.checkDisabled=r.prototype.checkDisabled,r.prototype.disable=function(){this.inputElement_.disabled=!0,this.updateClasses_()},r.prototype.disable=r.prototype.disable,r.prototype.enable=function(){this.inputElement_.disabled=!1,this.updateClasses_()},r.prototype.enable=r.prototype.enable,r.prototype.check=function(){this.inputElement_.checked=!0,this.updateClasses_()},r.prototype.check=r.prototype.check,r.prototype.uncheck=function(){this.inputElement_.checked=!1,this.updateClasses_()},r.prototype.uncheck=r.prototype.uncheck,r.prototype.init=function(){if(this.element_){this.inputElement_=this.element_.querySelector("."+this.CssClasses_.INPUT);var e=document.createElement("span");e.classList.add(this.CssClasses_.BOX_OUTLINE);var t=document.createElement("span");t.classList.add(this.CssClasses_.FOCUS_HELPER);var s=document.createElement("span");if(s.classList.add(this.CssClasses_.TICK_OUTLINE),e.appendChild(s),this.element_.appendChild(t),this.element_.appendChild(e),this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)){this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS),this.rippleContainerElement_=document.createElement("span"),this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER),this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT),this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER),this.boundRippleMouseUp=this.onMouseUp_.bind(this),this.rippleContainerElement_.addEventListener("mouseup",this.boundRippleMouseUp);var i=document.createElement("span");i.classList.add(this.CssClasses_.RIPPLE),this.rippleContainerElement_.appendChild(i),this.element_.appendChild(this.rippleContainerElement_)}this.boundInputOnChange=this.onChange_.bind(this),this.boundInputOnFocus=this.onFocus_.bind(this),this.boundInputOnBlur=this.onBlur_.bind(this),this.boundElementMouseUp=this.onMouseUp_.bind(this),this.inputElement_.addEventListener("change",this.boundInputOnChange),this.inputElement_.addEventListener("focus",this.boundInputOnFocus),this.inputElement_.addEventListener("blur",this.boundInputOnBlur),this.element_.addEventListener("mouseup",this.boundElementMouseUp),this.updateClasses_(),this.element_.classList.add(this.CssClasses_.IS_UPGRADED)}},s.register({constructor:r,classAsString:"MaterialCheckbox",cssClass:"mdl-js-checkbox",widget:!0});var _=function(e){this.element_=e,this.init()};window.MaterialIconToggle=_,_.prototype.Constant_={TINY_TIMEOUT:.001},_.prototype.CssClasses_={INPUT:"mdl-icon-toggle__input",JS_RIPPLE_EFFECT:"mdl-js-ripple-effect",RIPPLE_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events",RIPPLE_CONTAINER:"mdl-icon-toggle__ripple-container",RIPPLE_CENTER:"mdl-ripple--center",RIPPLE:"mdl-ripple",IS_FOCUSED:"is-focused",IS_DISABLED:"is-disabled",IS_CHECKED:"is-checked"},_.prototype.onChange_=function(e){this.updateClasses_()},_.prototype.onFocus_=function(e){this.element_.classList.add(this.CssClasses_.IS_FOCUSED)},_.prototype.onBlur_=function(e){this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},_.prototype.onMouseUp_=function(e){this.blur_()},_.prototype.updateClasses_=function(){this.checkDisabled(),this.checkToggleState()},_.prototype.blur_=function(){window.setTimeout(function(){this.inputElement_.blur()}.bind(this),this.Constant_.TINY_TIMEOUT)},_.prototype.checkToggleState=function(){this.inputElement_.checked?this.element_.classList.add(this.CssClasses_.IS_CHECKED):this.element_.classList.remove(this.CssClasses_.IS_CHECKED)},_.prototype.checkToggleState=_.prototype.checkToggleState,_.prototype.checkDisabled=function(){this.inputElement_.disabled?this.element_.classList.add(this.CssClasses_.IS_DISABLED):this.element_.classList.remove(this.CssClasses_.IS_DISABLED)},_.prototype.checkDisabled=_.prototype.checkDisabled,_.prototype.disable=function(){this.inputElement_.disabled=!0,this.updateClasses_()},_.prototype.disable=_.prototype.disable,_.prototype.enable=function(){this.inputElement_.disabled=!1,this.updateClasses_()},_.prototype.enable=_.prototype.enable,_.prototype.check=function(){this.inputElement_.checked=!0,this.updateClasses_()},_.prototype.check=_.prototype.check,_.prototype.uncheck=function(){this.inputElement_.checked=!1,this.updateClasses_()},_.prototype.uncheck=_.prototype.uncheck,_.prototype.init=function(){if(this.element_){if(this.inputElement_=this.element_.querySelector("."+this.CssClasses_.INPUT),this.element_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)){this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS),this.rippleContainerElement_=document.createElement("span"),this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER),this.rippleContainerElement_.classList.add(this.CssClasses_.JS_RIPPLE_EFFECT),this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER),this.boundRippleMouseUp=this.onMouseUp_.bind(this),this.rippleContainerElement_.addEventListener("mouseup",this.boundRippleMouseUp);var e=document.createElement("span");e.classList.add(this.CssClasses_.RIPPLE),this.rippleContainerElement_.appendChild(e),this.element_.appendChild(this.rippleContainerElement_)}this.boundInputOnChange=this.onChange_.bind(this),this.boundInputOnFocus=this.onFocus_.bind(this),this.boundInputOnBlur=this.onBlur_.bind(this),this.boundElementOnMouseUp=this.onMouseUp_.bind(this),this.inputElement_.addEventListener("change",this.boundInputOnChange),this.inputElement_.addEventListener("focus",this.boundInputOnFocus),this.inputElement_.addEventListener("blur",this.boundInputOnBlur),this.element_.addEventListener("mouseup",this.boundElementOnMouseUp),this.updateClasses_(),this.element_.classList.add("is-upgraded")}},s.register({constructor:_,classAsString:"MaterialIconToggle",cssClass:"mdl-js-icon-toggle",widget:!0});var d=function(e){this.element_=e,this.init()};window.MaterialMenu=d,d.prototype.Constant_={TRANSITION_DURATION_SECONDS:.3,TRANSITION_DURATION_FRACTION:.8,CLOSE_TIMEOUT:150},d.prototype.Keycodes_={ENTER:13,ESCAPE:27,SPACE:32,UP_ARROW:38,DOWN_ARROW:40},d.prototype.CssClasses_={CONTAINER:"mdl-menu__container",OUTLINE:"mdl-menu__outline",ITEM:"mdl-menu__item",ITEM_RIPPLE_CONTAINER:"mdl-menu__item-ripple-container",RIPPLE_EFFECT:"mdl-js-ripple-effect",RIPPLE_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events",RIPPLE:"mdl-ripple",IS_UPGRADED:"is-upgraded",IS_VISIBLE:"is-visible",IS_ANIMATING:"is-animating",BOTTOM_LEFT:"mdl-menu--bottom-left",BOTTOM_RIGHT:"mdl-menu--bottom-right",TOP_LEFT:"mdl-menu--top-left",TOP_RIGHT:"mdl-menu--top-right",UNALIGNED:"mdl-menu--unaligned"},d.prototype.init=function(){if(this.element_){var e=document.createElement("div");e.classList.add(this.CssClasses_.CONTAINER),this.element_.parentElement.insertBefore(e,this.element_),this.element_.parentElement.removeChild(this.element_),e.appendChild(this.element_),this.container_=e;var t=document.createElement("div");t.classList.add(this.CssClasses_.OUTLINE),this.outline_=t,e.insertBefore(t,this.element_);var s=this.element_.getAttribute("for")||this.element_.getAttribute("data-mdl-for"),i=null;s&&(i=document.getElementById(s),i&&(this.forElement_=i,i.addEventListener("click",this.handleForClick_.bind(this)),i.addEventListener("keydown",this.handleForKeyboardEvent_.bind(this))));var n=this.element_.querySelectorAll("."+this.CssClasses_.ITEM);this.boundItemKeydown_=this.handleItemKeyboardEvent_.bind(this),this.boundItemClick_=this.handleItemClick_.bind(this);for(var a=0;a<n.length;a++)n[a].addEventListener("click",this.boundItemClick_),n[a].tabIndex="-1",n[a].addEventListener("keydown",this.boundItemKeydown_);if(this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT))for(this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS),a=0;a<n.length;a++){var l=n[a],o=document.createElement("span");o.classList.add(this.CssClasses_.ITEM_RIPPLE_CONTAINER);var r=document.createElement("span");r.classList.add(this.CssClasses_.RIPPLE),o.appendChild(r),l.appendChild(o),l.classList.add(this.CssClasses_.RIPPLE_EFFECT)}this.element_.classList.contains(this.CssClasses_.BOTTOM_LEFT)&&this.outline_.classList.add(this.CssClasses_.BOTTOM_LEFT),this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)&&this.outline_.classList.add(this.CssClasses_.BOTTOM_RIGHT),this.element_.classList.contains(this.CssClasses_.TOP_LEFT)&&this.outline_.classList.add(this.CssClasses_.TOP_LEFT),this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)&&this.outline_.classList.add(this.CssClasses_.TOP_RIGHT),this.element_.classList.contains(this.CssClasses_.UNALIGNED)&&this.outline_.classList.add(this.CssClasses_.UNALIGNED),e.classList.add(this.CssClasses_.IS_UPGRADED)}},d.prototype.handleForClick_=function(e){if(this.element_&&this.forElement_){var t=this.forElement_.getBoundingClientRect(),s=this.forElement_.parentElement.getBoundingClientRect();this.element_.classList.contains(this.CssClasses_.UNALIGNED)||(this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)?(this.container_.style.right=s.right-t.right+"px",this.container_.style.top=this.forElement_.offsetTop+this.forElement_.offsetHeight+"px"):this.element_.classList.contains(this.CssClasses_.TOP_LEFT)?(this.container_.style.left=this.forElement_.offsetLeft+"px",this.container_.style.bottom=s.bottom-t.top+"px"):this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)?(this.container_.style.right=s.right-t.right+"px",this.container_.style.bottom=s.bottom-t.top+"px"):(this.container_.style.left=this.forElement_.offsetLeft+"px",this.container_.style.top=this.forElement_.offsetTop+this.forElement_.offsetHeight+"px"))}this.toggle(e)},d.prototype.handleForKeyboardEvent_=function(e){if(this.element_&&this.container_&&this.forElement_){var t=this.element_.querySelectorAll("."+this.CssClasses_.ITEM+":not([disabled])");t&&t.length>0&&this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)&&(e.keyCode===this.Keycodes_.UP_ARROW?(e.preventDefault(),t[t.length-1].focus()):e.keyCode===this.Keycodes_.DOWN_ARROW&&(e.preventDefault(),t[0].focus()))}},d.prototype.handleItemKeyboardEvent_=function(e){if(this.element_&&this.container_){var t=this.element_.querySelectorAll("."+this.CssClasses_.ITEM+":not([disabled])");if(t&&t.length>0&&this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)){var s=Array.prototype.slice.call(t).indexOf(e.target);if(e.keyCode===this.Keycodes_.UP_ARROW)e.preventDefault(),s>0?t[s-1].focus():t[t.length-1].focus();else if(e.keyCode===this.Keycodes_.DOWN_ARROW)e.preventDefault(),t.length>s+1?t[s+1].focus():t[0].focus();else if(e.keyCode===this.Keycodes_.SPACE||e.keyCode===this.Keycodes_.ENTER){e.preventDefault();var i=new MouseEvent("mousedown");e.target.dispatchEvent(i),i=new MouseEvent("mouseup"),e.target.dispatchEvent(i),e.target.click()}else e.keyCode===this.Keycodes_.ESCAPE&&(e.preventDefault(),this.hide())}}},d.prototype.handleItemClick_=function(e){e.target.hasAttribute("disabled")?e.stopPropagation():(this.closing_=!0,window.setTimeout(function(e){this.hide(),this.closing_=!1}.bind(this),this.Constant_.CLOSE_TIMEOUT))},d.prototype.applyClip_=function(e,t){this.element_.classList.contains(this.CssClasses_.UNALIGNED)?this.element_.style.clip="":this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)?this.element_.style.clip="rect(0 "+t+"px 0 "+t+"px)":this.element_.classList.contains(this.CssClasses_.TOP_LEFT)?this.element_.style.clip="rect("+e+"px 0 "+e+"px 0)":this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)?this.element_.style.clip="rect("+e+"px "+t+"px "+e+"px "+t+"px)":this.element_.style.clip=""},d.prototype.removeAnimationEndListener_=function(e){e.target.classList.remove(d.prototype.CssClasses_.IS_ANIMATING)},d.prototype.addAnimationEndListener_=function(){this.element_.addEventListener("transitionend",this.removeAnimationEndListener_),this.element_.addEventListener("webkitTransitionEnd",this.removeAnimationEndListener_)},d.prototype.show=function(e){if(this.element_&&this.container_&&this.outline_){var t=this.element_.getBoundingClientRect().height,s=this.element_.getBoundingClientRect().width;this.container_.style.width=s+"px",this.container_.style.height=t+"px",this.outline_.style.width=s+"px",this.outline_.style.height=t+"px";for(var i=this.Constant_.TRANSITION_DURATION_SECONDS*this.Constant_.TRANSITION_DURATION_FRACTION,n=this.element_.querySelectorAll("."+this.CssClasses_.ITEM),a=0;a<n.length;a++){var l=null;l=this.element_.classList.contains(this.CssClasses_.TOP_LEFT)||this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)?(t-n[a].offsetTop-n[a].offsetHeight)/t*i+"s":n[a].offsetTop/t*i+"s",n[a].style.transitionDelay=l}this.applyClip_(t,s),window.requestAnimationFrame(function(){this.element_.classList.add(this.CssClasses_.IS_ANIMATING),this.element_.style.clip="rect(0 "+s+"px "+t+"px 0)",this.container_.classList.add(this.CssClasses_.IS_VISIBLE)}.bind(this)),this.addAnimationEndListener_();var o=function(t){t===e||this.closing_||t.target.parentNode===this.element_||(document.removeEventListener("click",o),this.hide())}.bind(this);document.addEventListener("click",o)}},d.prototype.show=d.prototype.show,d.prototype.hide=function(){if(this.element_&&this.container_&&this.outline_){for(var e=this.element_.querySelectorAll("."+this.CssClasses_.ITEM),t=0;t<e.length;t++)e[t].style.removeProperty("transition-delay");var s=this.element_.getBoundingClientRect(),i=s.height,n=s.width;this.element_.classList.add(this.CssClasses_.IS_ANIMATING),this.applyClip_(i,n),this.container_.classList.remove(this.CssClasses_.IS_VISIBLE),this.addAnimationEndListener_()}},d.prototype.hide=d.prototype.hide,d.prototype.toggle=function(e){this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)?this.hide():this.show(e)},d.prototype.toggle=d.prototype.toggle,s.register({constructor:d,classAsString:"MaterialMenu",cssClass:"mdl-js-menu",widget:!0});var h=function(e){this.element_=e,this.init()};window.MaterialProgress=h,h.prototype.Constant_={},h.prototype.CssClasses_={INDETERMINATE_CLASS:"mdl-progress__indeterminate"},h.prototype.setProgress=function(e){this.element_.classList.contains(this.CssClasses_.INDETERMINATE_CLASS)||(this.progressbar_.style.width=e+"%")},h.prototype.setProgress=h.prototype.setProgress,h.prototype.setBuffer=function(e){this.bufferbar_.style.width=e+"%",this.auxbar_.style.width=100-e+"%"},h.prototype.setBuffer=h.prototype.setBuffer,h.prototype.init=function(){if(this.element_){var e=document.createElement("div");e.className="progressbar bar bar1",this.element_.appendChild(e),this.progressbar_=e,e=document.createElement("div"),e.className="bufferbar bar bar2",this.element_.appendChild(e),this.bufferbar_=e,e=document.createElement("div"),e.className="auxbar bar bar3",this.element_.appendChild(e),this.auxbar_=e,this.progressbar_.style.width="0%",this.bufferbar_.style.width="100%",this.auxbar_.style.width="0%",this.element_.classList.add("is-upgraded")}},s.register({constructor:h,classAsString:"MaterialProgress",cssClass:"mdl-js-progress",widget:!0});var c=function(e){this.element_=e,this.init()};window.MaterialRadio=c,c.prototype.Constant_={TINY_TIMEOUT:.001},c.prototype.CssClasses_={IS_FOCUSED:"is-focused",IS_DISABLED:"is-disabled",IS_CHECKED:"is-checked",IS_UPGRADED:"is-upgraded",JS_RADIO:"mdl-js-radio",RADIO_BTN:"mdl-radio__button",RADIO_OUTER_CIRCLE:"mdl-radio__outer-circle",RADIO_INNER_CIRCLE:"mdl-radio__inner-circle",RIPPLE_EFFECT:"mdl-js-ripple-effect",RIPPLE_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events",RIPPLE_CONTAINER:"mdl-radio__ripple-container",RIPPLE_CENTER:"mdl-ripple--center",RIPPLE:"mdl-ripple"},c.prototype.onChange_=function(e){for(var t=document.getElementsByClassName(this.CssClasses_.JS_RADIO),s=0;s<t.length;s++){var i=t[s].querySelector("."+this.CssClasses_.RADIO_BTN);i.getAttribute("name")===this.btnElement_.getAttribute("name")&&t[s].MaterialRadio.updateClasses_()}},c.prototype.onFocus_=function(e){this.element_.classList.add(this.CssClasses_.IS_FOCUSED)},c.prototype.onBlur_=function(e){this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},c.prototype.onMouseup_=function(e){this.blur_()},c.prototype.updateClasses_=function(){this.checkDisabled(),this.checkToggleState()},c.prototype.blur_=function(){window.setTimeout(function(){this.btnElement_.blur()}.bind(this),this.Constant_.TINY_TIMEOUT)},c.prototype.checkDisabled=function(){this.btnElement_.disabled?this.element_.classList.add(this.CssClasses_.IS_DISABLED):this.element_.classList.remove(this.CssClasses_.IS_DISABLED)},c.prototype.checkDisabled=c.prototype.checkDisabled,c.prototype.checkToggleState=function(){this.btnElement_.checked?this.element_.classList.add(this.CssClasses_.IS_CHECKED):this.element_.classList.remove(this.CssClasses_.IS_CHECKED)},c.prototype.checkToggleState=c.prototype.checkToggleState,c.prototype.disable=function(){this.btnElement_.disabled=!0,this.updateClasses_()},c.prototype.disable=c.prototype.disable,c.prototype.enable=function(){this.btnElement_.disabled=!1,this.updateClasses_()},c.prototype.enable=c.prototype.enable,c.prototype.check=function(){this.btnElement_.checked=!0,this.onChange_(null)},c.prototype.check=c.prototype.check,c.prototype.uncheck=function(){this.btnElement_.checked=!1,this.onChange_(null)},c.prototype.uncheck=c.prototype.uncheck,c.prototype.init=function(){if(this.element_){this.btnElement_=this.element_.querySelector("."+this.CssClasses_.RADIO_BTN),this.boundChangeHandler_=this.onChange_.bind(this),this.boundFocusHandler_=this.onChange_.bind(this),this.boundBlurHandler_=this.onBlur_.bind(this),this.boundMouseUpHandler_=this.onMouseup_.bind(this);var e=document.createElement("span");e.classList.add(this.CssClasses_.RADIO_OUTER_CIRCLE);var t=document.createElement("span");t.classList.add(this.CssClasses_.RADIO_INNER_CIRCLE),this.element_.appendChild(e),this.element_.appendChild(t);var s;if(this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)){this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS),s=document.createElement("span"),s.classList.add(this.CssClasses_.RIPPLE_CONTAINER),s.classList.add(this.CssClasses_.RIPPLE_EFFECT),s.classList.add(this.CssClasses_.RIPPLE_CENTER),s.addEventListener("mouseup",this.boundMouseUpHandler_);var i=document.createElement("span");i.classList.add(this.CssClasses_.RIPPLE),s.appendChild(i),this.element_.appendChild(s)}this.btnElement_.addEventListener("change",this.boundChangeHandler_),this.btnElement_.addEventListener("focus",this.boundFocusHandler_),this.btnElement_.addEventListener("blur",this.boundBlurHandler_),this.element_.addEventListener("mouseup",this.boundMouseUpHandler_),this.updateClasses_(),this.element_.classList.add(this.CssClasses_.IS_UPGRADED)}},s.register({constructor:c,classAsString:"MaterialRadio",cssClass:"mdl-js-radio",widget:!0});var p=function(e){this.element_=e,this.isIE_=window.navigator.msPointerEnabled,this.init()};window.MaterialSlider=p,p.prototype.Constant_={},p.prototype.CssClasses_={IE_CONTAINER:"mdl-slider__ie-container",SLIDER_CONTAINER:"mdl-slider__container",BACKGROUND_FLEX:"mdl-slider__background-flex",BACKGROUND_LOWER:"mdl-slider__background-lower",BACKGROUND_UPPER:"mdl-slider__background-upper",IS_LOWEST_VALUE:"is-lowest-value",IS_UPGRADED:"is-upgraded"},p.prototype.onInput_=function(e){this.updateValueStyles_()},p.prototype.onChange_=function(e){this.updateValueStyles_()},p.prototype.onMouseUp_=function(e){e.target.blur()},p.prototype.onContainerMouseDown_=function(e){if(e.target===this.element_.parentElement){e.preventDefault();var t=new MouseEvent("mousedown",{target:e.target,buttons:e.buttons,clientX:e.clientX,clientY:this.element_.getBoundingClientRect().y});this.element_.dispatchEvent(t)}},p.prototype.updateValueStyles_=function(){var e=(this.element_.value-this.element_.min)/(this.element_.max-this.element_.min);0===e?this.element_.classList.add(this.CssClasses_.IS_LOWEST_VALUE):this.element_.classList.remove(this.CssClasses_.IS_LOWEST_VALUE),this.isIE_||(this.backgroundLower_.style.flex=e,this.backgroundLower_.style.webkitFlex=e,this.backgroundUpper_.style.flex=1-e,this.backgroundUpper_.style.webkitFlex=1-e)},p.prototype.disable=function(){this.element_.disabled=!0},p.prototype.disable=p.prototype.disable,p.prototype.enable=function(){this.element_.disabled=!1},p.prototype.enable=p.prototype.enable,p.prototype.change=function(e){"undefined"!=typeof e&&(this.element_.value=e),this.updateValueStyles_()},p.prototype.change=p.prototype.change,p.prototype.init=function(){if(this.element_){if(this.isIE_){var e=document.createElement("div");e.classList.add(this.CssClasses_.IE_CONTAINER),this.element_.parentElement.insertBefore(e,this.element_),this.element_.parentElement.removeChild(this.element_),e.appendChild(this.element_)}else{var t=document.createElement("div");t.classList.add(this.CssClasses_.SLIDER_CONTAINER),this.element_.parentElement.insertBefore(t,this.element_),this.element_.parentElement.removeChild(this.element_),t.appendChild(this.element_);var s=document.createElement("div");s.classList.add(this.CssClasses_.BACKGROUND_FLEX),t.appendChild(s),this.backgroundLower_=document.createElement("div"),this.backgroundLower_.classList.add(this.CssClasses_.BACKGROUND_LOWER),s.appendChild(this.backgroundLower_),this.backgroundUpper_=document.createElement("div"),this.backgroundUpper_.classList.add(this.CssClasses_.BACKGROUND_UPPER),s.appendChild(this.backgroundUpper_)}this.boundInputHandler=this.onInput_.bind(this),this.boundChangeHandler=this.onChange_.bind(this),this.boundMouseUpHandler=this.onMouseUp_.bind(this),this.boundContainerMouseDownHandler=this.onContainerMouseDown_.bind(this),this.element_.addEventListener("input",this.boundInputHandler),this.element_.addEventListener("change",this.boundChangeHandler),this.element_.addEventListener("mouseup",this.boundMouseUpHandler),this.element_.parentElement.addEventListener("mousedown",this.boundContainerMouseDownHandler),this.updateValueStyles_(),this.element_.classList.add(this.CssClasses_.IS_UPGRADED)}},s.register({constructor:p,classAsString:"MaterialSlider",cssClass:"mdl-js-slider",widget:!0});var C=function(e){if(this.element_=e,this.textElement_=this.element_.querySelector("."+this.cssClasses_.MESSAGE),this.actionElement_=this.element_.querySelector("."+this.cssClasses_.ACTION),!this.textElement_)throw new Error("There must be a message element for a snackbar.");if(!this.actionElement_)throw new Error("There must be an action element for a snackbar.");this.active=!1,this.actionHandler_=void 0,this.message_=void 0,this.actionText_=void 0,this.queuedNotifications_=[],this.setActionHidden_(!0)};window.MaterialSnackbar=C,C.prototype.Constant_={ANIMATION_LENGTH:250},C.prototype.cssClasses_={SNACKBAR:"mdl-snackbar",MESSAGE:"mdl-snackbar__text",ACTION:"mdl-snackbar__action",ACTIVE:"mdl-snackbar--active"},C.prototype.displaySnackbar_=function(){this.element_.setAttribute("aria-hidden","true"),this.actionHandler_&&(this.actionElement_.textContent=this.actionText_, 247.12 +this.actionElement_.addEventListener("click",this.actionHandler_),this.setActionHidden_(!1)),this.textElement_.textContent=this.message_,this.element_.classList.add(this.cssClasses_.ACTIVE),this.element_.setAttribute("aria-hidden","false"),setTimeout(this.cleanup_.bind(this),this.timeout_)},C.prototype.showSnackbar=function(e){if(void 0===e)throw new Error("Please provide a data object with at least a message to display.");if(void 0===e.message)throw new Error("Please provide a message to be displayed.");if(e.actionHandler&&!e.actionText)throw new Error("Please provide action text with the handler.");this.active?this.queuedNotifications_.push(e):(this.active=!0,this.message_=e.message,e.timeout?this.timeout_=e.timeout:this.timeout_=2750,e.actionHandler&&(this.actionHandler_=e.actionHandler),e.actionText&&(this.actionText_=e.actionText),this.displaySnackbar_())},C.prototype.showSnackbar=C.prototype.showSnackbar,C.prototype.checkQueue_=function(){this.queuedNotifications_.length>0&&this.showSnackbar(this.queuedNotifications_.shift())},C.prototype.cleanup_=function(){this.element_.classList.remove(this.cssClasses_.ACTIVE),setTimeout(function(){this.element_.setAttribute("aria-hidden","true"),this.textElement_.textContent="",Boolean(this.actionElement_.getAttribute("aria-hidden"))||(this.setActionHidden_(!0),this.actionElement_.textContent="",this.actionElement_.removeEventListener("click",this.actionHandler_)),this.actionHandler_=void 0,this.message_=void 0,this.actionText_=void 0,this.active=!1,this.checkQueue_()}.bind(this),this.Constant_.ANIMATION_LENGTH)},C.prototype.setActionHidden_=function(e){e?this.actionElement_.setAttribute("aria-hidden","true"):this.actionElement_.removeAttribute("aria-hidden")},s.register({constructor:C,classAsString:"MaterialSnackbar",cssClass:"mdl-js-snackbar",widget:!0});var u=function(e){this.element_=e,this.init()};window.MaterialSpinner=u,u.prototype.Constant_={MDL_SPINNER_LAYER_COUNT:4},u.prototype.CssClasses_={MDL_SPINNER_LAYER:"mdl-spinner__layer",MDL_SPINNER_CIRCLE_CLIPPER:"mdl-spinner__circle-clipper",MDL_SPINNER_CIRCLE:"mdl-spinner__circle",MDL_SPINNER_GAP_PATCH:"mdl-spinner__gap-patch",MDL_SPINNER_LEFT:"mdl-spinner__left",MDL_SPINNER_RIGHT:"mdl-spinner__right"},u.prototype.createLayer=function(e){var t=document.createElement("div");t.classList.add(this.CssClasses_.MDL_SPINNER_LAYER),t.classList.add(this.CssClasses_.MDL_SPINNER_LAYER+"-"+e);var s=document.createElement("div");s.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER),s.classList.add(this.CssClasses_.MDL_SPINNER_LEFT);var i=document.createElement("div");i.classList.add(this.CssClasses_.MDL_SPINNER_GAP_PATCH);var n=document.createElement("div");n.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE_CLIPPER),n.classList.add(this.CssClasses_.MDL_SPINNER_RIGHT);for(var a=[s,i,n],l=0;l<a.length;l++){var o=document.createElement("div");o.classList.add(this.CssClasses_.MDL_SPINNER_CIRCLE),a[l].appendChild(o)}t.appendChild(s),t.appendChild(i),t.appendChild(n),this.element_.appendChild(t)},u.prototype.createLayer=u.prototype.createLayer,u.prototype.stop=function(){this.element_.classList.remove("is-active")},u.prototype.stop=u.prototype.stop,u.prototype.start=function(){this.element_.classList.add("is-active")},u.prototype.start=u.prototype.start,u.prototype.init=function(){if(this.element_){for(var e=1;e<=this.Constant_.MDL_SPINNER_LAYER_COUNT;e++)this.createLayer(e);this.element_.classList.add("is-upgraded")}},s.register({constructor:u,classAsString:"MaterialSpinner",cssClass:"mdl-js-spinner",widget:!0});var E=function(e){this.element_=e,this.init()};window.MaterialSwitch=E,E.prototype.Constant_={TINY_TIMEOUT:.001},E.prototype.CssClasses_={INPUT:"mdl-switch__input",TRACK:"mdl-switch__track",THUMB:"mdl-switch__thumb",FOCUS_HELPER:"mdl-switch__focus-helper",RIPPLE_EFFECT:"mdl-js-ripple-effect",RIPPLE_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events",RIPPLE_CONTAINER:"mdl-switch__ripple-container",RIPPLE_CENTER:"mdl-ripple--center",RIPPLE:"mdl-ripple",IS_FOCUSED:"is-focused",IS_DISABLED:"is-disabled",IS_CHECKED:"is-checked"},E.prototype.onChange_=function(e){this.updateClasses_()},E.prototype.onFocus_=function(e){this.element_.classList.add(this.CssClasses_.IS_FOCUSED)},E.prototype.onBlur_=function(e){this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},E.prototype.onMouseUp_=function(e){this.blur_()},E.prototype.updateClasses_=function(){this.checkDisabled(),this.checkToggleState()},E.prototype.blur_=function(){window.setTimeout(function(){this.inputElement_.blur()}.bind(this),this.Constant_.TINY_TIMEOUT)},E.prototype.checkDisabled=function(){this.inputElement_.disabled?this.element_.classList.add(this.CssClasses_.IS_DISABLED):this.element_.classList.remove(this.CssClasses_.IS_DISABLED)},E.prototype.checkDisabled=E.prototype.checkDisabled,E.prototype.checkToggleState=function(){this.inputElement_.checked?this.element_.classList.add(this.CssClasses_.IS_CHECKED):this.element_.classList.remove(this.CssClasses_.IS_CHECKED)},E.prototype.checkToggleState=E.prototype.checkToggleState,E.prototype.disable=function(){this.inputElement_.disabled=!0,this.updateClasses_()},E.prototype.disable=E.prototype.disable,E.prototype.enable=function(){this.inputElement_.disabled=!1,this.updateClasses_()},E.prototype.enable=E.prototype.enable,E.prototype.on=function(){this.inputElement_.checked=!0,this.updateClasses_()},E.prototype.on=E.prototype.on,E.prototype.off=function(){this.inputElement_.checked=!1,this.updateClasses_()},E.prototype.off=E.prototype.off,E.prototype.init=function(){if(this.element_){this.inputElement_=this.element_.querySelector("."+this.CssClasses_.INPUT);var e=document.createElement("div");e.classList.add(this.CssClasses_.TRACK);var t=document.createElement("div");t.classList.add(this.CssClasses_.THUMB);var s=document.createElement("span");if(s.classList.add(this.CssClasses_.FOCUS_HELPER),t.appendChild(s),this.element_.appendChild(e),this.element_.appendChild(t),this.boundMouseUpHandler=this.onMouseUp_.bind(this),this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)){this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS),this.rippleContainerElement_=document.createElement("span"),this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CONTAINER),this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_EFFECT),this.rippleContainerElement_.classList.add(this.CssClasses_.RIPPLE_CENTER),this.rippleContainerElement_.addEventListener("mouseup",this.boundMouseUpHandler);var i=document.createElement("span");i.classList.add(this.CssClasses_.RIPPLE),this.rippleContainerElement_.appendChild(i),this.element_.appendChild(this.rippleContainerElement_)}this.boundChangeHandler=this.onChange_.bind(this),this.boundFocusHandler=this.onFocus_.bind(this),this.boundBlurHandler=this.onBlur_.bind(this),this.inputElement_.addEventListener("change",this.boundChangeHandler),this.inputElement_.addEventListener("focus",this.boundFocusHandler),this.inputElement_.addEventListener("blur",this.boundBlurHandler),this.element_.addEventListener("mouseup",this.boundMouseUpHandler),this.updateClasses_(),this.element_.classList.add("is-upgraded")}},s.register({constructor:E,classAsString:"MaterialSwitch",cssClass:"mdl-js-switch",widget:!0});var m=function(e){this.element_=e,this.init()};window.MaterialTabs=m,m.prototype.Constant_={},m.prototype.CssClasses_={TAB_CLASS:"mdl-tabs__tab",PANEL_CLASS:"mdl-tabs__panel",ACTIVE_CLASS:"is-active",UPGRADED_CLASS:"is-upgraded",MDL_JS_RIPPLE_EFFECT:"mdl-js-ripple-effect",MDL_RIPPLE_CONTAINER:"mdl-tabs__ripple-container",MDL_RIPPLE:"mdl-ripple",MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events"},m.prototype.initTabs_=function(){this.element_.classList.contains(this.CssClasses_.MDL_JS_RIPPLE_EFFECT)&&this.element_.classList.add(this.CssClasses_.MDL_JS_RIPPLE_EFFECT_IGNORE_EVENTS),this.tabs_=this.element_.querySelectorAll("."+this.CssClasses_.TAB_CLASS),this.panels_=this.element_.querySelectorAll("."+this.CssClasses_.PANEL_CLASS);for(var t=0;t<this.tabs_.length;t++)new e(this.tabs_[t],this);this.element_.classList.add(this.CssClasses_.UPGRADED_CLASS)},m.prototype.resetTabState_=function(){for(var e=0;e<this.tabs_.length;e++)this.tabs_[e].classList.remove(this.CssClasses_.ACTIVE_CLASS)},m.prototype.resetPanelState_=function(){for(var e=0;e<this.panels_.length;e++)this.panels_[e].classList.remove(this.CssClasses_.ACTIVE_CLASS)},m.prototype.init=function(){this.element_&&this.initTabs_()},s.register({constructor:m,classAsString:"MaterialTabs",cssClass:"mdl-js-tabs"});var L=function(e){this.element_=e,this.maxRows=this.Constant_.NO_MAX_ROWS,this.init()};window.MaterialTextfield=L,L.prototype.Constant_={NO_MAX_ROWS:-1,MAX_ROWS_ATTRIBUTE:"maxrows"},L.prototype.CssClasses_={LABEL:"mdl-textfield__label",INPUT:"mdl-textfield__input",IS_DIRTY:"is-dirty",IS_FOCUSED:"is-focused",IS_DISABLED:"is-disabled",IS_INVALID:"is-invalid",IS_UPGRADED:"is-upgraded",HAS_PLACEHOLDER:"has-placeholder"},L.prototype.onKeyDown_=function(e){var t=e.target.value.split("\n").length;13===e.keyCode&&t>=this.maxRows&&e.preventDefault()},L.prototype.onFocus_=function(e){this.element_.classList.add(this.CssClasses_.IS_FOCUSED)},L.prototype.onBlur_=function(e){this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},L.prototype.onReset_=function(e){this.updateClasses_()},L.prototype.updateClasses_=function(){this.checkDisabled(),this.checkValidity(),this.checkDirty(),this.checkFocus()},L.prototype.checkDisabled=function(){this.input_.disabled?this.element_.classList.add(this.CssClasses_.IS_DISABLED):this.element_.classList.remove(this.CssClasses_.IS_DISABLED)},L.prototype.checkDisabled=L.prototype.checkDisabled,L.prototype.checkFocus=function(){Boolean(this.element_.querySelector(":focus"))?this.element_.classList.add(this.CssClasses_.IS_FOCUSED):this.element_.classList.remove(this.CssClasses_.IS_FOCUSED)},L.prototype.checkFocus=L.prototype.checkFocus,L.prototype.checkValidity=function(){this.input_.validity&&(this.input_.validity.valid?this.element_.classList.remove(this.CssClasses_.IS_INVALID):this.element_.classList.add(this.CssClasses_.IS_INVALID))},L.prototype.checkValidity=L.prototype.checkValidity,L.prototype.checkDirty=function(){this.input_.value&&this.input_.value.length>0?this.element_.classList.add(this.CssClasses_.IS_DIRTY):this.element_.classList.remove(this.CssClasses_.IS_DIRTY)},L.prototype.checkDirty=L.prototype.checkDirty,L.prototype.disable=function(){this.input_.disabled=!0,this.updateClasses_()},L.prototype.disable=L.prototype.disable,L.prototype.enable=function(){this.input_.disabled=!1,this.updateClasses_()},L.prototype.enable=L.prototype.enable,L.prototype.change=function(e){this.input_.value=e||"",this.updateClasses_()},L.prototype.change=L.prototype.change,L.prototype.init=function(){if(this.element_&&(this.label_=this.element_.querySelector("."+this.CssClasses_.LABEL),this.input_=this.element_.querySelector("."+this.CssClasses_.INPUT),this.input_)){this.input_.hasAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE)&&(this.maxRows=parseInt(this.input_.getAttribute(this.Constant_.MAX_ROWS_ATTRIBUTE),10),isNaN(this.maxRows)&&(this.maxRows=this.Constant_.NO_MAX_ROWS)),this.input_.hasAttribute("placeholder")&&this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER),this.boundUpdateClassesHandler=this.updateClasses_.bind(this),this.boundFocusHandler=this.onFocus_.bind(this),this.boundBlurHandler=this.onBlur_.bind(this),this.boundResetHandler=this.onReset_.bind(this),this.input_.addEventListener("input",this.boundUpdateClassesHandler),this.input_.addEventListener("focus",this.boundFocusHandler),this.input_.addEventListener("blur",this.boundBlurHandler),this.input_.addEventListener("reset",this.boundResetHandler),this.maxRows!==this.Constant_.NO_MAX_ROWS&&(this.boundKeyDownHandler=this.onKeyDown_.bind(this),this.input_.addEventListener("keydown",this.boundKeyDownHandler));var e=this.element_.classList.contains(this.CssClasses_.IS_INVALID);this.updateClasses_(),this.element_.classList.add(this.CssClasses_.IS_UPGRADED),e&&this.element_.classList.add(this.CssClasses_.IS_INVALID),this.input_.hasAttribute("autofocus")&&(this.element_.focus(),this.checkFocus())}},s.register({constructor:L,classAsString:"MaterialTextfield",cssClass:"mdl-js-textfield",widget:!0});var I=function(e){this.element_=e,this.init()};window.MaterialTooltip=I,I.prototype.Constant_={},I.prototype.CssClasses_={IS_ACTIVE:"is-active",BOTTOM:"mdl-tooltip--bottom",LEFT:"mdl-tooltip--left",RIGHT:"mdl-tooltip--right",TOP:"mdl-tooltip--top"},I.prototype.handleMouseEnter_=function(e){var t=e.target.getBoundingClientRect(),s=t.left+t.width/2,i=t.top+t.height/2,n=-1*(this.element_.offsetWidth/2),a=-1*(this.element_.offsetHeight/2);this.element_.classList.contains(this.CssClasses_.LEFT)||this.element_.classList.contains(this.CssClasses_.RIGHT)?(s=t.width/2,i+a<0?(this.element_.style.top="0",this.element_.style.marginTop="0"):(this.element_.style.top=i+"px",this.element_.style.marginTop=a+"px")):s+n<0?(this.element_.style.left="0",this.element_.style.marginLeft="0"):(this.element_.style.left=s+"px",this.element_.style.marginLeft=n+"px"),this.element_.classList.contains(this.CssClasses_.TOP)?this.element_.style.top=t.top-this.element_.offsetHeight-10+"px":this.element_.classList.contains(this.CssClasses_.RIGHT)?this.element_.style.left=t.left+t.width+10+"px":this.element_.classList.contains(this.CssClasses_.LEFT)?this.element_.style.left=t.left-this.element_.offsetWidth-10+"px":this.element_.style.top=t.top+t.height+10+"px",this.element_.classList.add(this.CssClasses_.IS_ACTIVE)},I.prototype.hideTooltip_=function(){this.element_.classList.remove(this.CssClasses_.IS_ACTIVE)},I.prototype.init=function(){if(this.element_){var e=this.element_.getAttribute("for")||this.element_.getAttribute("data-mdl-for");e&&(this.forElement_=document.getElementById(e)),this.forElement_&&(this.forElement_.hasAttribute("tabindex")||this.forElement_.setAttribute("tabindex","0"),this.boundMouseEnterHandler=this.handleMouseEnter_.bind(this),this.boundMouseLeaveAndScrollHandler=this.hideTooltip_.bind(this),this.forElement_.addEventListener("mouseenter",this.boundMouseEnterHandler,!1),this.forElement_.addEventListener("touchend",this.boundMouseEnterHandler,!1),this.forElement_.addEventListener("mouseleave",this.boundMouseLeaveAndScrollHandler,!1),window.addEventListener("scroll",this.boundMouseLeaveAndScrollHandler,!0),window.addEventListener("touchstart",this.boundMouseLeaveAndScrollHandler))}},s.register({constructor:I,classAsString:"MaterialTooltip",cssClass:"mdl-tooltip"});var f=function(e){this.element_=e,this.init()};window.MaterialLayout=f,f.prototype.Constant_={MAX_WIDTH:"(max-width: 1024px)",TAB_SCROLL_PIXELS:100,RESIZE_TIMEOUT:100,MENU_ICON:"",CHEVRON_LEFT:"chevron_left",CHEVRON_RIGHT:"chevron_right"},f.prototype.Keycodes_={ENTER:13,ESCAPE:27,SPACE:32},f.prototype.Mode_={STANDARD:0,SEAMED:1,WATERFALL:2,SCROLL:3},f.prototype.CssClasses_={CONTAINER:"mdl-layout__container",HEADER:"mdl-layout__header",DRAWER:"mdl-layout__drawer",CONTENT:"mdl-layout__content",DRAWER_BTN:"mdl-layout__drawer-button",ICON:"material-icons",JS_RIPPLE_EFFECT:"mdl-js-ripple-effect",RIPPLE_CONTAINER:"mdl-layout__tab-ripple-container",RIPPLE:"mdl-ripple",RIPPLE_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events",HEADER_SEAMED:"mdl-layout__header--seamed",HEADER_WATERFALL:"mdl-layout__header--waterfall",HEADER_SCROLL:"mdl-layout__header--scroll",FIXED_HEADER:"mdl-layout--fixed-header",OBFUSCATOR:"mdl-layout__obfuscator",TAB_BAR:"mdl-layout__tab-bar",TAB_CONTAINER:"mdl-layout__tab-bar-container",TAB:"mdl-layout__tab",TAB_BAR_BUTTON:"mdl-layout__tab-bar-button",TAB_BAR_LEFT_BUTTON:"mdl-layout__tab-bar-left-button",TAB_BAR_RIGHT_BUTTON:"mdl-layout__tab-bar-right-button",PANEL:"mdl-layout__tab-panel",HAS_DRAWER:"has-drawer",HAS_TABS:"has-tabs",HAS_SCROLLING_HEADER:"has-scrolling-header",CASTING_SHADOW:"is-casting-shadow",IS_COMPACT:"is-compact",IS_SMALL_SCREEN:"is-small-screen",IS_DRAWER_OPEN:"is-visible",IS_ACTIVE:"is-active",IS_UPGRADED:"is-upgraded",IS_ANIMATING:"is-animating",ON_LARGE_SCREEN:"mdl-layout--large-screen-only",ON_SMALL_SCREEN:"mdl-layout--small-screen-only"},f.prototype.contentScrollHandler_=function(){if(!this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)){var e=!this.element_.classList.contains(this.CssClasses_.IS_SMALL_SCREEN)||this.element_.classList.contains(this.CssClasses_.FIXED_HEADER);this.content_.scrollTop>0&&!this.header_.classList.contains(this.CssClasses_.IS_COMPACT)?(this.header_.classList.add(this.CssClasses_.CASTING_SHADOW),this.header_.classList.add(this.CssClasses_.IS_COMPACT),e&&this.header_.classList.add(this.CssClasses_.IS_ANIMATING)):this.content_.scrollTop<=0&&this.header_.classList.contains(this.CssClasses_.IS_COMPACT)&&(this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW),this.header_.classList.remove(this.CssClasses_.IS_COMPACT),e&&this.header_.classList.add(this.CssClasses_.IS_ANIMATING))}},f.prototype.keyboardEventHandler_=function(e){e.keyCode===this.Keycodes_.ESCAPE&&this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)&&this.toggleDrawer()},f.prototype.screenSizeHandler_=function(){this.screenSizeMediaQuery_.matches?this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN):(this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN),this.drawer_&&(this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN),this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN)))},f.prototype.drawerToggleHandler_=function(e){if(e&&"keydown"===e.type){if(e.keyCode!==this.Keycodes_.SPACE&&e.keyCode!==this.Keycodes_.ENTER)return;e.preventDefault()}this.toggleDrawer()},f.prototype.headerTransitionEndHandler_=function(){this.header_.classList.remove(this.CssClasses_.IS_ANIMATING)},f.prototype.headerClickHandler_=function(){this.header_.classList.contains(this.CssClasses_.IS_COMPACT)&&(this.header_.classList.remove(this.CssClasses_.IS_COMPACT),this.header_.classList.add(this.CssClasses_.IS_ANIMATING))},f.prototype.resetTabState_=function(e){for(var t=0;t<e.length;t++)e[t].classList.remove(this.CssClasses_.IS_ACTIVE)},f.prototype.resetPanelState_=function(e){for(var t=0;t<e.length;t++)e[t].classList.remove(this.CssClasses_.IS_ACTIVE)},f.prototype.toggleDrawer=function(){var e=this.element_.querySelector("."+this.CssClasses_.DRAWER_BTN);this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN),this.obfuscator_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN),this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)?(this.drawer_.setAttribute("aria-hidden","false"),e.setAttribute("aria-expanded","true")):(this.drawer_.setAttribute("aria-hidden","true"),e.setAttribute("aria-expanded","false"))},f.prototype.toggleDrawer=f.prototype.toggleDrawer,f.prototype.init=function(){if(this.element_){var e=document.createElement("div");e.classList.add(this.CssClasses_.CONTAINER);var s=this.element_.querySelector(":focus");this.element_.parentElement.insertBefore(e,this.element_),this.element_.parentElement.removeChild(this.element_),e.appendChild(this.element_),s&&s.focus();for(var i=this.element_.childNodes,n=i.length,a=0;a<n;a++){var l=i[a];l.classList&&l.classList.contains(this.CssClasses_.HEADER)&&(this.header_=l),l.classList&&l.classList.contains(this.CssClasses_.DRAWER)&&(this.drawer_=l),l.classList&&l.classList.contains(this.CssClasses_.CONTENT)&&(this.content_=l)}window.addEventListener("pageshow",function(e){e.persisted&&(this.element_.style.overflowY="hidden",requestAnimationFrame(function(){this.element_.style.overflowY=""}.bind(this)))}.bind(this),!1),this.header_&&(this.tabBar_=this.header_.querySelector("."+this.CssClasses_.TAB_BAR));var o=this.Mode_.STANDARD;if(this.header_&&(this.header_.classList.contains(this.CssClasses_.HEADER_SEAMED)?o=this.Mode_.SEAMED:this.header_.classList.contains(this.CssClasses_.HEADER_WATERFALL)?(o=this.Mode_.WATERFALL,this.header_.addEventListener("transitionend",this.headerTransitionEndHandler_.bind(this)),this.header_.addEventListener("click",this.headerClickHandler_.bind(this))):this.header_.classList.contains(this.CssClasses_.HEADER_SCROLL)&&(o=this.Mode_.SCROLL,e.classList.add(this.CssClasses_.HAS_SCROLLING_HEADER)),o===this.Mode_.STANDARD?(this.header_.classList.add(this.CssClasses_.CASTING_SHADOW),this.tabBar_&&this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW)):o===this.Mode_.SEAMED||o===this.Mode_.SCROLL?(this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW),this.tabBar_&&this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW)):o===this.Mode_.WATERFALL&&(this.content_.addEventListener("scroll",this.contentScrollHandler_.bind(this)),this.contentScrollHandler_())),this.drawer_){var r=this.element_.querySelector("."+this.CssClasses_.DRAWER_BTN);if(!r){r=document.createElement("div"),r.setAttribute("aria-expanded","false"),r.setAttribute("role","button"),r.setAttribute("tabindex","0"),r.classList.add(this.CssClasses_.DRAWER_BTN);var _=document.createElement("i");_.classList.add(this.CssClasses_.ICON),_.innerHTML=this.Constant_.MENU_ICON,r.appendChild(_)}this.drawer_.classList.contains(this.CssClasses_.ON_LARGE_SCREEN)?r.classList.add(this.CssClasses_.ON_LARGE_SCREEN):this.drawer_.classList.contains(this.CssClasses_.ON_SMALL_SCREEN)&&r.classList.add(this.CssClasses_.ON_SMALL_SCREEN),r.addEventListener("click",this.drawerToggleHandler_.bind(this)),r.addEventListener("keydown",this.drawerToggleHandler_.bind(this)),this.element_.classList.add(this.CssClasses_.HAS_DRAWER),this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)?this.header_.insertBefore(r,this.header_.firstChild):this.element_.insertBefore(r,this.content_);var d=document.createElement("div");d.classList.add(this.CssClasses_.OBFUSCATOR),this.element_.appendChild(d),d.addEventListener("click",this.drawerToggleHandler_.bind(this)),this.obfuscator_=d,this.drawer_.addEventListener("keydown",this.keyboardEventHandler_.bind(this)),this.drawer_.setAttribute("aria-hidden","true")}if(this.screenSizeMediaQuery_=window.matchMedia(this.Constant_.MAX_WIDTH),this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this)),this.screenSizeHandler_(),this.header_&&this.tabBar_){this.element_.classList.add(this.CssClasses_.HAS_TABS);var h=document.createElement("div");h.classList.add(this.CssClasses_.TAB_CONTAINER),this.header_.insertBefore(h,this.tabBar_),this.header_.removeChild(this.tabBar_);var c=document.createElement("div");c.classList.add(this.CssClasses_.TAB_BAR_BUTTON),c.classList.add(this.CssClasses_.TAB_BAR_LEFT_BUTTON);var p=document.createElement("i");p.classList.add(this.CssClasses_.ICON),p.textContent=this.Constant_.CHEVRON_LEFT,c.appendChild(p),c.addEventListener("click",function(){this.tabBar_.scrollLeft-=this.Constant_.TAB_SCROLL_PIXELS}.bind(this));var C=document.createElement("div");C.classList.add(this.CssClasses_.TAB_BAR_BUTTON),C.classList.add(this.CssClasses_.TAB_BAR_RIGHT_BUTTON);var u=document.createElement("i");u.classList.add(this.CssClasses_.ICON),u.textContent=this.Constant_.CHEVRON_RIGHT,C.appendChild(u),C.addEventListener("click",function(){this.tabBar_.scrollLeft+=this.Constant_.TAB_SCROLL_PIXELS}.bind(this)),h.appendChild(c),h.appendChild(this.tabBar_),h.appendChild(C);var E=function(){this.tabBar_.scrollLeft>0?c.classList.add(this.CssClasses_.IS_ACTIVE):c.classList.remove(this.CssClasses_.IS_ACTIVE),this.tabBar_.scrollLeft<this.tabBar_.scrollWidth-this.tabBar_.offsetWidth?C.classList.add(this.CssClasses_.IS_ACTIVE):C.classList.remove(this.CssClasses_.IS_ACTIVE)}.bind(this);this.tabBar_.addEventListener("scroll",E),E();var m=function(){this.resizeTimeoutId_&&clearTimeout(this.resizeTimeoutId_),this.resizeTimeoutId_=setTimeout(function(){E(),this.resizeTimeoutId_=null}.bind(this),this.Constant_.RESIZE_TIMEOUT)}.bind(this);window.addEventListener("resize",m),this.tabBar_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)&&this.tabBar_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);for(var L=this.tabBar_.querySelectorAll("."+this.CssClasses_.TAB),I=this.content_.querySelectorAll("."+this.CssClasses_.PANEL),f=0;f<L.length;f++)new t(L[f],L,I,this)}this.element_.classList.add(this.CssClasses_.IS_UPGRADED)}},window.MaterialLayoutTab=t,s.register({constructor:f,classAsString:"MaterialLayout",cssClass:"mdl-js-layout"});var b=function(e){this.element_=e,this.init()};window.MaterialDataTable=b,b.prototype.Constant_={},b.prototype.CssClasses_={DATA_TABLE:"mdl-data-table",SELECTABLE:"mdl-data-table--selectable",SELECT_ELEMENT:"mdl-data-table__select",IS_SELECTED:"is-selected",IS_UPGRADED:"is-upgraded"},b.prototype.selectRow_=function(e,t,s){return t?function(){e.checked?t.classList.add(this.CssClasses_.IS_SELECTED):t.classList.remove(this.CssClasses_.IS_SELECTED)}.bind(this):s?function(){var t,i;if(e.checked)for(t=0;t<s.length;t++)i=s[t].querySelector("td").querySelector(".mdl-checkbox"),i.MaterialCheckbox.check(),s[t].classList.add(this.CssClasses_.IS_SELECTED);else for(t=0;t<s.length;t++)i=s[t].querySelector("td").querySelector(".mdl-checkbox"),i.MaterialCheckbox.uncheck(),s[t].classList.remove(this.CssClasses_.IS_SELECTED)}.bind(this):void 0},b.prototype.createCheckbox_=function(e,t){var i=document.createElement("label"),n=["mdl-checkbox","mdl-js-checkbox","mdl-js-ripple-effect",this.CssClasses_.SELECT_ELEMENT];i.className=n.join(" ");var a=document.createElement("input");return a.type="checkbox",a.classList.add("mdl-checkbox__input"),e?(a.checked=e.classList.contains(this.CssClasses_.IS_SELECTED),a.addEventListener("change",this.selectRow_(a,e))):t&&a.addEventListener("change",this.selectRow_(a,null,t)),i.appendChild(a),s.upgradeElement(i,"MaterialCheckbox"),i},b.prototype.init=function(){if(this.element_){var e=this.element_.querySelector("th"),t=Array.prototype.slice.call(this.element_.querySelectorAll("tbody tr")),s=Array.prototype.slice.call(this.element_.querySelectorAll("tfoot tr")),i=t.concat(s);if(this.element_.classList.contains(this.CssClasses_.SELECTABLE)){var n=document.createElement("th"),a=this.createCheckbox_(null,i);n.appendChild(a),e.parentElement.insertBefore(n,e);for(var l=0;l<i.length;l++){var o=i[l].querySelector("td");if(o){var r=document.createElement("td");if("TBODY"===i[l].parentNode.nodeName.toUpperCase()){var _=this.createCheckbox_(i[l]);r.appendChild(_)}i[l].insertBefore(r,o)}}this.element_.classList.add(this.CssClasses_.IS_UPGRADED)}}},s.register({constructor:b,classAsString:"MaterialDataTable",cssClass:"mdl-js-data-table"});var y=function(e){this.element_=e,this.init()};window.MaterialRipple=y,y.prototype.Constant_={INITIAL_SCALE:"scale(0.0001, 0.0001)",INITIAL_SIZE:"1px",INITIAL_OPACITY:"0.4",FINAL_OPACITY:"0",FINAL_SCALE:""},y.prototype.CssClasses_={RIPPLE_CENTER:"mdl-ripple--center",RIPPLE_EFFECT_IGNORE_EVENTS:"mdl-js-ripple-effect--ignore-events",RIPPLE:"mdl-ripple",IS_ANIMATING:"is-animating",IS_VISIBLE:"is-visible"},y.prototype.downHandler_=function(e){if(!this.rippleElement_.style.width&&!this.rippleElement_.style.height){var t=this.element_.getBoundingClientRect();this.boundHeight=t.height,this.boundWidth=t.width,this.rippleSize_=2*Math.sqrt(t.width*t.width+t.height*t.height)+2,this.rippleElement_.style.width=this.rippleSize_+"px",this.rippleElement_.style.height=this.rippleSize_+"px"}if(this.rippleElement_.classList.add(this.CssClasses_.IS_VISIBLE),"mousedown"===e.type&&this.ignoringMouseDown_)this.ignoringMouseDown_=!1;else{"touchstart"===e.type&&(this.ignoringMouseDown_=!0);var s=this.getFrameCount();if(s>0)return;this.setFrameCount(1);var i,n,a=e.currentTarget.getBoundingClientRect();if(0===e.clientX&&0===e.clientY)i=Math.round(a.width/2),n=Math.round(a.height/2);else{var l=e.clientX?e.clientX:e.touches[0].clientX,o=e.clientY?e.clientY:e.touches[0].clientY;i=Math.round(l-a.left),n=Math.round(o-a.top)}this.setRippleXY(i,n),this.setRippleStyles(!0),window.requestAnimationFrame(this.animFrameHandler.bind(this))}},y.prototype.upHandler_=function(e){e&&2!==e.detail&&window.setTimeout(function(){this.rippleElement_.classList.remove(this.CssClasses_.IS_VISIBLE)}.bind(this),0)},y.prototype.init=function(){if(this.element_){var e=this.element_.classList.contains(this.CssClasses_.RIPPLE_CENTER);this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT_IGNORE_EVENTS)||(this.rippleElement_=this.element_.querySelector("."+this.CssClasses_.RIPPLE),this.frameCount_=0,this.rippleSize_=0,this.x_=0,this.y_=0,this.ignoringMouseDown_=!1,this.boundDownHandler=this.downHandler_.bind(this),this.element_.addEventListener("mousedown",this.boundDownHandler),this.element_.addEventListener("touchstart",this.boundDownHandler),this.boundUpHandler=this.upHandler_.bind(this),this.element_.addEventListener("mouseup",this.boundUpHandler),this.element_.addEventListener("mouseleave",this.boundUpHandler),this.element_.addEventListener("touchend",this.boundUpHandler),this.element_.addEventListener("blur",this.boundUpHandler),this.getFrameCount=function(){return this.frameCount_},this.setFrameCount=function(e){this.frameCount_=e},this.getRippleElement=function(){return this.rippleElement_},this.setRippleXY=function(e,t){this.x_=e,this.y_=t},this.setRippleStyles=function(t){if(null!==this.rippleElement_){var s,i,n,a="translate("+this.x_+"px, "+this.y_+"px)";t?(i=this.Constant_.INITIAL_SCALE,n=this.Constant_.INITIAL_SIZE):(i=this.Constant_.FINAL_SCALE,n=this.rippleSize_+"px",e&&(a="translate("+this.boundWidth/2+"px, "+this.boundHeight/2+"px)")),s="translate(-50%, -50%) "+a+i,this.rippleElement_.style.webkitTransform=s,this.rippleElement_.style.msTransform=s,this.rippleElement_.style.transform=s,t?this.rippleElement_.classList.remove(this.CssClasses_.IS_ANIMATING):this.rippleElement_.classList.add(this.CssClasses_.IS_ANIMATING)}},this.animFrameHandler=function(){this.frameCount_-- >0?window.requestAnimationFrame(this.animFrameHandler.bind(this)):this.setRippleStyles(!1)})}},s.register({constructor:y,classAsString:"MaterialRipple",cssClass:"mdl-js-ripple-effect",widget:!1})}(); 247.13 +//# sourceMappingURL=material.min.js.map
248.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 248.2 +++ b/static/mdl/tick-mask.svg Sun Jul 15 14:07:29 2018 +0200 248.3 @@ -0,0 +1,30 @@ 248.4 +<?xml version="1.0" encoding="UTF-8" standalone="no"?> 248.5 +<svg 248.6 + xmlns:dc="http://purl.org/dc/elements/1.1/" 248.7 + xmlns:cc="http://creativecommons.org/ns#" 248.8 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 248.9 + xmlns:svg="http://www.w3.org/2000/svg" 248.10 + xmlns="http://www.w3.org/2000/svg" 248.11 + version="1.1" 248.12 + viewBox="0 0 1 1" 248.13 + preserveAspectRatio="xMinYMin meet"> 248.14 + <defs> 248.15 + <clipPath id="clip"> 248.16 + <path 248.17 + d="M 0,0 0,1 1,1 1,0 0,0 z M 0.8534375,0.1671875 0.9596875,0.273125 0.429375,0.8034375 0.323125,0.9096875 0.2171875,0.8034375 0.0403125,0.626875 0.1465625,0.520625 0.323125,0.6975 0.8534375,0.1671875 z" 248.18 + style="fill:#ffffff;fill-opacity:1;stroke:none" /> 248.19 + </clipPath> 248.20 + <mask id="mask" maskUnits="objectBoundingBox" maskContentUnits="objectBoundingBox"> 248.21 + <path 248.22 + d="M 0,0 0,1 1,1 1,0 0,0 z M 0.8534375,0.1671875 0.9596875,0.273125 0.429375,0.8034375 0.323125,0.9096875 0.2171875,0.8034375 0.0403125,0.626875 0.1465625,0.520625 0.323125,0.6975 0.8534375,0.1671875 z" 248.23 + style="fill:#ffffff;fill-opacity:1;stroke:none" /> 248.24 + </mask> 248.25 + </defs> 248.26 + <rect 248.27 + width="1" 248.28 + height="1" 248.29 + x="0" 248.30 + y="0" 248.31 + clip-path="url(#clip)" 248.32 + style="fill:#000000;fill-opacity:1;stroke:none" /> 248.33 +</svg>
249.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 249.2 +++ b/static/mdl/tick.svg Sun Jul 15 14:07:29 2018 +0200 249.3 @@ -0,0 +1,15 @@ 249.4 +<?xml version="1.0" encoding="UTF-8" standalone="no"?> 249.5 +<svg 249.6 + xmlns:dc="http://purl.org/dc/elements/1.1/" 249.7 + xmlns:cc="http://creativecommons.org/ns#" 249.8 + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 249.9 + xmlns:svg="http://www.w3.org/2000/svg" 249.10 + xmlns="http://www.w3.org/2000/svg" 249.11 + version="1.1" 249.12 + viewBox="0 0 1 1" 249.13 + preserveAspectRatio="xMinYMin meet"> 249.14 + <path 249.15 + d="M 0.04038059,0.6267767 0.14644661,0.52071068 0.42928932,0.80355339 0.3232233,0.90961941 z M 0.21715729,0.80355339 0.85355339,0.16715729 0.95961941,0.2732233 0.3232233,0.90961941 z" 249.16 + id="rect3780" 249.17 + style="fill:#ffffff;fill-opacity:1;stroke:none" /> 249.18 +</svg>
250.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 250.2 +++ b/static/wysihtml/advanced.js Sun Jul 15 14:07:29 2018 +0200 250.3 @@ -0,0 +1,558 @@ 250.4 +/** 250.5 + * Full HTML5 compatibility rule set 250.6 + * These rules define which tags and CSS classes are supported and which tags should be specially treated. 250.7 + * 250.8 + * Examples based on this rule set: 250.9 + * 250.10 + * <a href="http://foobar.com">foo</a> 250.11 + * ... becomes ... 250.12 + * <a href="http://foobar.com" target="_blank" rel="nofollow">foo</a> 250.13 + * 250.14 + * <img align="left" src="http://foobar.com/image.png"> 250.15 + * ... becomes ... 250.16 + * <img class="wysiwyg-float-left" src="http://foobar.com/image.png" alt=""> 250.17 + * 250.18 + * <div>foo<script>alert(document.cookie)</script></div> 250.19 + * ... becomes ... 250.20 + * <div>foo</div> 250.21 + * 250.22 + * <marquee>foo</marquee> 250.23 + * ... becomes ... 250.24 + * <span>foo</span> 250.25 + * 250.26 + * foo <br clear="both"> bar 250.27 + * ... becomes ... 250.28 + * foo <br class="wysiwyg-clear-both"> bar 250.29 + * 250.30 + * <div>hello <iframe src="http://google.com"></iframe></div> 250.31 + * ... becomes ... 250.32 + * <div>hello </div> 250.33 + * 250.34 + * <center>hello</center> 250.35 + * ... becomes ... 250.36 + * <div class="wysiwyg-text-align-center">hello</div> 250.37 + */ 250.38 +var wysihtmlParserRules = { 250.39 + /** 250.40 + * CSS Class white-list 250.41 + * Following CSS classes won't be removed when parsed by the wysihtml HTML parser 250.42 + */ 250.43 + "classes": { 250.44 + "wysiwyg-clear-both": 1, 250.45 + "wysiwyg-clear-left": 1, 250.46 + "wysiwyg-clear-right": 1, 250.47 + "wysiwyg-color-aqua": 1, 250.48 + "wysiwyg-color-black": 1, 250.49 + "wysiwyg-color-blue": 1, 250.50 + "wysiwyg-color-fuchsia": 1, 250.51 + "wysiwyg-color-gray": 1, 250.52 + "wysiwyg-color-green": 1, 250.53 + "wysiwyg-color-lime": 1, 250.54 + "wysiwyg-color-maroon": 1, 250.55 + "wysiwyg-color-navy": 1, 250.56 + "wysiwyg-color-olive": 1, 250.57 + "wysiwyg-color-purple": 1, 250.58 + "wysiwyg-color-red": 1, 250.59 + "wysiwyg-color-silver": 1, 250.60 + "wysiwyg-color-teal": 1, 250.61 + "wysiwyg-color-white": 1, 250.62 + "wysiwyg-color-yellow": 1, 250.63 + "wysiwyg-float-left": 1, 250.64 + "wysiwyg-float-right": 1, 250.65 + "wysiwyg-font-size-large": 1, 250.66 + "wysiwyg-font-size-larger": 1, 250.67 + "wysiwyg-font-size-medium": 1, 250.68 + "wysiwyg-font-size-small": 1, 250.69 + "wysiwyg-font-size-smaller": 1, 250.70 + "wysiwyg-font-size-x-large": 1, 250.71 + "wysiwyg-font-size-x-small": 1, 250.72 + "wysiwyg-font-size-xx-large": 1, 250.73 + "wysiwyg-font-size-xx-small": 1, 250.74 + "wysiwyg-text-align-center": 1, 250.75 + "wysiwyg-text-align-justify": 1, 250.76 + "wysiwyg-text-align-left": 1, 250.77 + "wysiwyg-text-align-right": 1 250.78 + }, 250.79 + /** 250.80 + * Tag list 250.81 + * 250.82 + * The following options are available: 250.83 + * 250.84 + * - add_class: converts and deletes the given HTML4 attribute (align, clear, ...) via the given method to a css class 250.85 + * The following methods are implemented in wysihtml.dom.parse: 250.86 + * - align_text: converts align attribute values (right/left/center/justify) to their corresponding css class "wysiwyg-text-align-*") 250.87 + * <p align="center">foo</p> ... becomes ... <p class="wysiwyg-text-align-center">foo</p> 250.88 + * - clear_br: converts clear attribute values left/right/all/both to their corresponding css class "wysiwyg-clear-*" 250.89 + * <br clear="all"> ... becomes ... <br class="wysiwyg-clear-both"> 250.90 + * - align_img: converts align attribute values (right/left) on <img> to their corresponding css class "wysiwyg-float-*" 250.91 + * 250.92 + * - add_style: converts and deletes the given HTML4 attribute (align) via the given method to a css style 250.93 + * The following methods are implemented in wysihtml.dom.parse: 250.94 + * - align_text: converts align attribute values (right/left/center) to their corresponding css style) 250.95 + * <p align="center">foo</p> ... becomes ... <p style="text-align:center">foo</p> 250.96 + * 250.97 + * - remove: removes the element and its content 250.98 + * 250.99 + * - unwrap removes element but leaves content 250.100 + * 250.101 + * - rename_tag: renames the element to the given tag 250.102 + * 250.103 + * - set_class: adds the given class to the element (note: make sure that the class is in the "classes" white list above) 250.104 + * 250.105 + * - set_attributes: sets/overrides the given attributes 250.106 + * 250.107 + * - check_attributes: checks the given HTML attribute via the given method 250.108 + * - url: allows only valid urls (starting with http:// or https://) 250.109 + * - src: allows something like "/foobar.jpg", "http://google.com", ... 250.110 + * - href: allows something like "mailto:bert@foo.com", "http://google.com", "/foobar.jpg" 250.111 + * - alt: strips unwanted characters. if the attribute is not set, then it gets set (to ensure valid and compatible HTML) 250.112 + * - numbers: ensures that the attribute only contains numeric (integer) characters (no float values or units) 250.113 + * - dimension: for with/height attributes where floating point numbrs and percentages are allowed 250.114 + * - any: allows anything to pass 250.115 + */ 250.116 + "tags": { 250.117 + "tr": { 250.118 + "add_class": { 250.119 + "align": "align_text" 250.120 + } 250.121 + }, 250.122 + "strike": { 250.123 + "remove": 1 250.124 + }, 250.125 + "form": { 250.126 + "rename_tag": "div" 250.127 + }, 250.128 + "rt": { 250.129 + "rename_tag": "span" 250.130 + }, 250.131 + "code": {}, 250.132 + "acronym": { 250.133 + "rename_tag": "span" 250.134 + }, 250.135 + "br": { 250.136 + "add_class": { 250.137 + "clear": "clear_br" 250.138 + } 250.139 + }, 250.140 + "details": { 250.141 + "rename_tag": "div" 250.142 + }, 250.143 + "h4": { 250.144 + "add_class": { 250.145 + "align": "align_text" 250.146 + } 250.147 + }, 250.148 + "em": {}, 250.149 + "title": { 250.150 + "remove": 1 250.151 + }, 250.152 + "multicol": { 250.153 + "rename_tag": "div" 250.154 + }, 250.155 + "figure": { 250.156 + "rename_tag": "div" 250.157 + }, 250.158 + "xmp": { 250.159 + "rename_tag": "span" 250.160 + }, 250.161 + "small": { 250.162 + "rename_tag": "span", 250.163 + "set_class": "wysiwyg-font-size-smaller" 250.164 + }, 250.165 + "area": { 250.166 + "remove": 1 250.167 + }, 250.168 + "time": { 250.169 + "rename_tag": "span" 250.170 + }, 250.171 + "dir": { 250.172 + "rename_tag": "ul" 250.173 + }, 250.174 + "bdi": { 250.175 + "rename_tag": "span" 250.176 + }, 250.177 + "command": { 250.178 + "remove": 1 250.179 + }, 250.180 + "ul": {}, 250.181 + "progress": { 250.182 + "rename_tag": "span" 250.183 + }, 250.184 + "dfn": { 250.185 + "rename_tag": "span" 250.186 + }, 250.187 + "iframe": { 250.188 + "remove": 1 250.189 + }, 250.190 + "figcaption": { 250.191 + "rename_tag": "div" 250.192 + }, 250.193 + "a": { 250.194 + "check_attributes": { 250.195 + "target": "any", 250.196 + "href": "url" // if you compiled master manually then change this from 'url' to 'href' 250.197 + }, 250.198 + "set_attributes": { 250.199 + "rel": "nofollow" 250.200 + } 250.201 + }, 250.202 + "img": { 250.203 + "check_attributes": { 250.204 + "width": "dimension", 250.205 + "alt": "alt", 250.206 + "src": "url", // if you compiled master manually then change this from 'url' to 'src' 250.207 + "height": "dimension" 250.208 + }, 250.209 + "add_class": { 250.210 + "align": "align_img" 250.211 + } 250.212 + }, 250.213 + "rb": { 250.214 + "rename_tag": "span" 250.215 + }, 250.216 + "footer": { 250.217 + "rename_tag": "div" 250.218 + }, 250.219 + "noframes": { 250.220 + "remove": 1 250.221 + }, 250.222 + "abbr": { 250.223 + "rename_tag": "span" 250.224 + }, 250.225 + "u": {}, 250.226 + "bgsound": { 250.227 + "remove": 1 250.228 + }, 250.229 + "address": { 250.230 + "rename_tag": "div" 250.231 + }, 250.232 + "basefont": { 250.233 + "remove": 1 250.234 + }, 250.235 + "nav": { 250.236 + "rename_tag": "div" 250.237 + }, 250.238 + "h1": { 250.239 + "add_class": { 250.240 + "align": "align_text" 250.241 + } 250.242 + }, 250.243 + "head": { 250.244 + "remove": 1 250.245 + }, 250.246 + "tbody": { 250.247 + "add_class": { 250.248 + "align": "align_text" 250.249 + } 250.250 + }, 250.251 + "dd": { 250.252 + "rename_tag": "div" 250.253 + }, 250.254 + "s": { 250.255 + "rename_tag": "span" 250.256 + }, 250.257 + "li": {}, 250.258 + "td": { 250.259 + "check_attributes": { 250.260 + "rowspan": "numbers", 250.261 + "colspan": "numbers" 250.262 + }, 250.263 + "add_class": { 250.264 + "align": "align_text" 250.265 + } 250.266 + }, 250.267 + "object": { 250.268 + "remove": 1 250.269 + }, 250.270 + "div": { 250.271 + "add_class": { 250.272 + "align": "align_text" 250.273 + } 250.274 + }, 250.275 + "option": { 250.276 + "rename_tag": "span" 250.277 + }, 250.278 + "select": { 250.279 + "rename_tag": "span" 250.280 + }, 250.281 + "i": {}, 250.282 + "track": { 250.283 + "remove": 1 250.284 + }, 250.285 + "wbr": { 250.286 + "remove": 1 250.287 + }, 250.288 + "fieldset": { 250.289 + "rename_tag": "div" 250.290 + }, 250.291 + "big": { 250.292 + "rename_tag": "span", 250.293 + "set_class": "wysiwyg-font-size-larger" 250.294 + }, 250.295 + "button": { 250.296 + "rename_tag": "span" 250.297 + }, 250.298 + "noscript": { 250.299 + "remove": 1 250.300 + }, 250.301 + "svg": { 250.302 + "remove": 1 250.303 + }, 250.304 + "input": { 250.305 + "remove": 1 250.306 + }, 250.307 + "table": {}, 250.308 + "keygen": { 250.309 + "remove": 1 250.310 + }, 250.311 + "h5": { 250.312 + "add_class": { 250.313 + "align": "align_text" 250.314 + } 250.315 + }, 250.316 + "meta": { 250.317 + "remove": 1 250.318 + }, 250.319 + "map": { 250.320 + "rename_tag": "div" 250.321 + }, 250.322 + "isindex": { 250.323 + "remove": 1 250.324 + }, 250.325 + "mark": { 250.326 + "rename_tag": "span" 250.327 + }, 250.328 + "caption": { 250.329 + "add_class": { 250.330 + "align": "align_text" 250.331 + } 250.332 + }, 250.333 + "tfoot": { 250.334 + "add_class": { 250.335 + "align": "align_text" 250.336 + } 250.337 + }, 250.338 + "base": { 250.339 + "remove": 1 250.340 + }, 250.341 + "video": { 250.342 + "remove": 1 250.343 + }, 250.344 + "strong": {}, 250.345 + "canvas": { 250.346 + "remove": 1 250.347 + }, 250.348 + "output": { 250.349 + "rename_tag": "span" 250.350 + }, 250.351 + "marquee": { 250.352 + "rename_tag": "span" 250.353 + }, 250.354 + "b": {}, 250.355 + "q": { 250.356 + "check_attributes": { 250.357 + "cite": "url" 250.358 + } 250.359 + }, 250.360 + "applet": { 250.361 + "remove": 1 250.362 + }, 250.363 + "span": {}, 250.364 + "rp": { 250.365 + "rename_tag": "span" 250.366 + }, 250.367 + "spacer": { 250.368 + "remove": 1 250.369 + }, 250.370 + "source": { 250.371 + "remove": 1 250.372 + }, 250.373 + "aside": { 250.374 + "rename_tag": "div" 250.375 + }, 250.376 + "frame": { 250.377 + "remove": 1 250.378 + }, 250.379 + "section": { 250.380 + "rename_tag": "div" 250.381 + }, 250.382 + "body": { 250.383 + "rename_tag": "div" 250.384 + }, 250.385 + "ol": {}, 250.386 + "nobr": { 250.387 + "rename_tag": "span" 250.388 + }, 250.389 + "html": { 250.390 + "rename_tag": "div" 250.391 + }, 250.392 + "summary": { 250.393 + "rename_tag": "span" 250.394 + }, 250.395 + "var": { 250.396 + "rename_tag": "span" 250.397 + }, 250.398 + "del": { 250.399 + "remove": 1 250.400 + }, 250.401 + "blockquote": { 250.402 + "check_attributes": { 250.403 + "cite": "url" 250.404 + } 250.405 + }, 250.406 + "style": { 250.407 + "remove": 1 250.408 + }, 250.409 + "device": { 250.410 + "remove": 1 250.411 + }, 250.412 + "meter": { 250.413 + "rename_tag": "span" 250.414 + }, 250.415 + "h3": { 250.416 + "add_class": { 250.417 + "align": "align_text" 250.418 + } 250.419 + }, 250.420 + "textarea": { 250.421 + "rename_tag": "span" 250.422 + }, 250.423 + "embed": { 250.424 + "remove": 1 250.425 + }, 250.426 + "hgroup": { 250.427 + "rename_tag": "div" 250.428 + }, 250.429 + "font": { 250.430 + "rename_tag": "span", 250.431 + "add_class": { 250.432 + "size": "size_font" 250.433 + } 250.434 + }, 250.435 + "tt": { 250.436 + "rename_tag": "span" 250.437 + }, 250.438 + "noembed": { 250.439 + "remove": 1 250.440 + }, 250.441 + "thead": { 250.442 + "add_class": { 250.443 + "align": "align_text" 250.444 + } 250.445 + }, 250.446 + "blink": { 250.447 + "rename_tag": "span" 250.448 + }, 250.449 + "plaintext": { 250.450 + "rename_tag": "span" 250.451 + }, 250.452 + "xml": { 250.453 + "remove": 1 250.454 + }, 250.455 + "h6": { 250.456 + "add_class": { 250.457 + "align": "align_text" 250.458 + } 250.459 + }, 250.460 + "param": { 250.461 + "remove": 1 250.462 + }, 250.463 + "th": { 250.464 + "check_attributes": { 250.465 + "rowspan": "numbers", 250.466 + "colspan": "numbers" 250.467 + }, 250.468 + "add_class": { 250.469 + "align": "align_text" 250.470 + } 250.471 + }, 250.472 + "legend": { 250.473 + "rename_tag": "span" 250.474 + }, 250.475 + "hr": {}, 250.476 + "label": { 250.477 + "rename_tag": "span" 250.478 + }, 250.479 + "dl": { 250.480 + "rename_tag": "div" 250.481 + }, 250.482 + "kbd": { 250.483 + "rename_tag": "span" 250.484 + }, 250.485 + "listing": { 250.486 + "rename_tag": "div" 250.487 + }, 250.488 + "dt": { 250.489 + "rename_tag": "span" 250.490 + }, 250.491 + "nextid": { 250.492 + "remove": 1 250.493 + }, 250.494 + "pre": {}, 250.495 + "center": { 250.496 + "rename_tag": "div", 250.497 + "set_class": "wysiwyg-text-align-center" 250.498 + }, 250.499 + "audio": { 250.500 + "remove": 1 250.501 + }, 250.502 + "datalist": { 250.503 + "rename_tag": "span" 250.504 + }, 250.505 + "samp": { 250.506 + "rename_tag": "span" 250.507 + }, 250.508 + "col": { 250.509 + "remove": 1 250.510 + }, 250.511 + "article": { 250.512 + "rename_tag": "div" 250.513 + }, 250.514 + "cite": {}, 250.515 + "link": { 250.516 + "remove": 1 250.517 + }, 250.518 + "script": { 250.519 + "remove": 1 250.520 + }, 250.521 + "bdo": { 250.522 + "rename_tag": "span" 250.523 + }, 250.524 + "menu": { 250.525 + "rename_tag": "ul" 250.526 + }, 250.527 + "colgroup": { 250.528 + "remove": 1 250.529 + }, 250.530 + "ruby": { 250.531 + "rename_tag": "span" 250.532 + }, 250.533 + "h2": { 250.534 + "add_class": { 250.535 + "align": "align_text" 250.536 + } 250.537 + }, 250.538 + "ins": { 250.539 + "rename_tag": "span" 250.540 + }, 250.541 + "p": { 250.542 + "add_class": { 250.543 + "align": "align_text" 250.544 + } 250.545 + }, 250.546 + "sub": {}, 250.547 + "comment": { 250.548 + "remove": 1 250.549 + }, 250.550 + "frameset": { 250.551 + "remove": 1 250.552 + }, 250.553 + "optgroup": { 250.554 + "rename_tag": "span" 250.555 + }, 250.556 + "header": { 250.557 + "rename_tag": "div" 250.558 + }, 250.559 + "sup": {} 250.560 + } 250.561 +};
251.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 251.2 +++ b/static/wysihtml/example.html Sun Jul 15 14:07:29 2018 +0200 251.3 @@ -0,0 +1,183 @@ 251.4 +<!DOCTYPE html> 251.5 + 251.6 +<meta http-equiv="X-UA-Compatible" content="IE=Edge"> 251.7 +<meta charset="utf-8"> 251.8 + 251.9 +<title>wysihtml - Advanced Demo</title> 251.10 + 251.11 +<style> 251.12 + body { 251.13 + font-family: Verdana; 251.14 + font-size: 11px; 251.15 + } 251.16 + 251.17 + h2 { 251.18 + margin-bottom: 0; 251.19 + } 251.20 + 251.21 + small { 251.22 + display: block; 251.23 + margin-top: 40px; 251.24 + font-size: 9px; 251.25 + } 251.26 + 251.27 + small, 251.28 + small a { 251.29 + color: #666; 251.30 + } 251.31 + 251.32 + a { 251.33 + color: #000; 251.34 + text-decoration: underline; 251.35 + cursor: pointer; 251.36 + } 251.37 + 251.38 + #toolbar [data-wysihtml-action] { 251.39 + float: right; 251.40 + } 251.41 + 251.42 + #toolbar, 251.43 + textarea { 251.44 + width: 920px; 251.45 + padding: 5px; 251.46 + -webkit-box-sizing: border-box; 251.47 + -ms-box-sizing: border-box; 251.48 + -moz-box-sizing: border-box; 251.49 + box-sizing: border-box; 251.50 + } 251.51 + 251.52 + textarea { 251.53 + height: 280px; 251.54 + border: 2px solid green; 251.55 + font-family: Verdana; 251.56 + font-size: 11px; 251.57 + } 251.58 + 251.59 + textarea:focus { 251.60 + color: black; 251.61 + border: 2px solid black; 251.62 + } 251.63 + 251.64 + .wysihtml-command-active { 251.65 + font-weight: bold; 251.66 + } 251.67 + 251.68 + [data-wysihtml-dialog] { 251.69 + margin: 5px 0 0; 251.70 + padding: 5px; 251.71 + border: 1px solid #666; 251.72 + } 251.73 + 251.74 + a[data-wysihtml-command-value="red"] { 251.75 + color: red; 251.76 + } 251.77 + 251.78 + a[data-wysihtml-command-value="green"] { 251.79 + color: green; 251.80 + } 251.81 + 251.82 + a[data-wysihtml-command-value="blue"] { 251.83 + color: blue; 251.84 + } 251.85 + 251.86 + div.editable { 251.87 + border: 1px dashed gray; 251.88 + padding: 10px; 251.89 + } 251.90 +</style> 251.91 + 251.92 +<h1>wysihtml - Advanced Editor Example</h1> 251.93 + 251.94 +<h2>Example of editor with iframe wrapper and as textarea replacement</h2> 251.95 +<br> 251.96 +<form class="ewrapper"> 251.97 + <div class="toolbar" style="display: none;"> 251.98 + <a data-wysihtml-command="bold" title="CTRL+B">bold</a> | 251.99 + <a data-wysihtml-command="italic" title="CTRL+I">italic</a> | 251.100 + <a data-wysihtml-command="superscript" title="sup">superscript</a> | 251.101 + <a data-wysihtml-command="subscript" title="sub">subscript</a> | 251.102 + <a data-wysihtml-command="createLink">link</a> | 251.103 + <a data-wysihtml-command="removeLink"><s>link</s></a> | 251.104 + <a data-wysihtml-command="formatBlock" data-wysihtml-command-value="h1">h1</a> | 251.105 + <a data-wysihtml-command="formatBlock" data-wysihtml-command-value="h2">h2</a> | 251.106 + <a data-wysihtml-command="formatBlock" data-wysihtml-command-blank-value="true">plaintext</a> | 251.107 + <a data-wysihtml-command="insertUnorderedList">insertUnorderedList</a> | 251.108 + <a data-wysihtml-command="insertOrderedList">insertOrderedList</a> | 251.109 + <a data-wysihtml-command="formatCode" data-wysihtml-command-value="language-html">Code</a> | 251.110 + <a data-wysihtml-command="undo">undo</a> | 251.111 + <a data-wysihtml-command="redo">redo</a> | 251.112 + <a data-wysihtml-command="insertSpeech">speech</a> 251.113 + <a data-wysihtml-action="change_view">switch to html view</a> 251.114 + 251.115 + <div data-wysihtml-dialog="createLink" style="display: none;"> 251.116 + <label> 251.117 + Link: 251.118 + <input data-wysihtml-dialog-field="href" value="http://"> 251.119 + </label> 251.120 + <a data-wysihtml-dialog-action="save">OK</a> <a data-wysihtml-dialog-action="cancel">Cancel</a> 251.121 + </div> 251.122 + 251.123 + </div> 251.124 + <br> 251.125 + <textarea class="editable" placeholder="Enter text ..."></textarea> 251.126 + <br><input type="reset" value="Reset form!"> 251.127 +</form> 251.128 + 251.129 +<br/><br><h2>Example of editor without iframe wrapper</h2><br/> 251.130 +<form class="ewrapper"> 251.131 + <div class="toolbar" style="display: none;"> 251.132 + <a data-wysihtml-command="bold" title="CTRL+B">bold</a> | 251.133 + <a data-wysihtml-command="italic" title="CTRL+I">italic</a> | 251.134 + <a data-wysihtml-command="createLink">link</a> | 251.135 + <a data-wysihtml-command="removeLink"><s>link</s></a> | 251.136 + <a data-wysihtml-command="formatBlock" data-wysihtml-command-value="h1">h1</a> | 251.137 + <a data-wysihtml-command="formatBlock" data-wysihtml-command-value="h2">h2</a> | 251.138 + <a data-wysihtml-command="formatBlock" data-wysihtml-command-blank-value="true">plaintext</a> | 251.139 + <a data-wysihtml-command="insertUnorderedList">insertUnorderedList</a> | 251.140 + <a data-wysihtml-command="insertOrderedList">insertOrderedList</a> | 251.141 + <a data-wysihtml-command="undo">undo</a> | 251.142 + <a data-wysihtml-command="redo">redo</a> | 251.143 + <a data-wysihtml-command="insertSpeech">speech</a> 251.144 + <a data-wysihtml-action="change_view">switch to html view</a> 251.145 + 251.146 + <div data-wysihtml-dialog="createLink" style="display: none;"> 251.147 + <label> 251.148 + Link: 251.149 + <input data-wysihtml-dialog-field="href" value="http://"> 251.150 + </label> 251.151 + <a data-wysihtml-dialog-action="save">OK</a> <a data-wysihtml-dialog-action="cancel">Cancel</a> 251.152 + </div> 251.153 + 251.154 + </div> 251.155 + <br> 251.156 + <div class="editable" data-placeholder="Enter text ..."></div> 251.157 + <br><input type="reset" value="Reset form!"> 251.158 +</form> 251.159 + 251.160 +<h2>Events:</h2> 251.161 +<div id="log"></div> 251.162 + 251.163 +<small>powered by <a href="https://github.com/Voog/wysihtml" target="_blank">wysihtml</a>.</small> 251.164 + 251.165 +<script src="jquery.1.10.2.js"></script> 251.166 + 251.167 +<script src="wysihtml.js"></script> 251.168 +<script src="wysihtml.all-commands.js"></script> 251.169 +<!--<script src="wysihtml.table_editing.js"></script>--> 251.170 +<script src="wysihtml.toolbar.js"></script> 251.171 + 251.172 +<script src="wysihtml_liquidfeedback_rules.js"></script> 251.173 + 251.174 +<script> 251.175 +var editors = []; 251.176 + $('.ewrapper').each(function(idx, wrapper) { 251.177 + var e = new wysihtml.Editor($(wrapper).find('.editable').get(0), { 251.178 + toolbar: $(wrapper).find('.toolbar').get(0), 251.179 + parserRules: wysihtmlParserRules, 251.180 + stylesheets: "css/stylesheet.css", 251.181 + useLineBreaks: true 251.182 + //showToolbarAfterInit: false 251.183 + }); 251.184 + editors.push(e); 251.185 + }); 251.186 +</script>
252.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 252.2 +++ b/static/wysihtml/jquery.1.10.2.js Sun Jul 15 14:07:29 2018 +0200 252.3 @@ -0,0 +1,6 @@ 252.4 +/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license 252.5 +//@ sourceMappingURL=jquery-1.10.2.min.map 252.6 +*/ 252.7 +(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav></:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t 252.8 +}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Ct=/^(?:checkbox|radio)$/i,Nt=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); 252.9 +u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=un(e,t),Pt.detach()),Gt[e]=n),n}function un(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,n){x.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(x.css(e,"display"))?x.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x.support.opacity||(x.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=x.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===x.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,n){return n?x.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,n){x.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?x(e).position()[n]+"px":r):t}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!x.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||x.css(e,"display"))},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(x.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Ct.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),x.param=function(e,n){var r,i=[],o=function(e,t){t=x.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var mn,yn,vn=x.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Cn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Nn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=x.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=o.href}catch(Ln){yn=a.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(T)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(l){var u;return o[l]=!0,x.each(e[l]||[],function(e,l){var c=l(n,r,i);return"string"!=typeof c||a||o[c]?a?!(u=c):t:(n.dataTypes.unshift(c),s(c),!1)}),u}return s(n.dataTypes[0])||!o["*"]&&s("*")}function _n(e,n){var r,i,o=x.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,l=e.indexOf(" ");return l>=0&&(i=e.slice(l,e.length),e=e.slice(0,l)),x.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&x.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?x("<div>").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
253.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 253.2 +++ b/static/wysihtml/simple.js Sun Jul 15 14:07:29 2018 +0200 253.3 @@ -0,0 +1,32 @@ 253.4 +/** 253.5 + * Very simple basic rule set 253.6 + * 253.7 + * Allows 253.8 + * <i>, <em>, <b>, <strong>, <p>, <div>, <a href="http://foo"></a>, <br>, <span>, <ol>, <ul>, <li> 253.9 + * 253.10 + * For a proper documentation of the format check advanced.js 253.11 + */ 253.12 +var wysihtmlParserRules = { 253.13 + tags: { 253.14 + strong: {}, 253.15 + b: {}, 253.16 + i: {}, 253.17 + em: {}, 253.18 + br: {}, 253.19 + p: {}, 253.20 + div: {}, 253.21 + span: {}, 253.22 + ul: {}, 253.23 + ol: {}, 253.24 + li: {}, 253.25 + a: { 253.26 + set_attributes: { 253.27 + target: "_blank", 253.28 + rel: "nofollow" 253.29 + }, 253.30 + check_attributes: { 253.31 + href: "url" // important to avoid XSS 253.32 + } 253.33 + } 253.34 + } 253.35 +};
254.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 254.2 +++ b/static/wysihtml/wysihtml.all-commands.js Sun Jul 15 14:07:29 2018 +0200 254.3 @@ -0,0 +1,630 @@ 254.4 +wysihtml.commands.alignCenterStyle = (function() { 254.5 + var nodeOptions = { 254.6 + styleProperty: "textAlign", 254.7 + styleValue: "center", 254.8 + toggle: true 254.9 + }; 254.10 + 254.11 + return { 254.12 + exec: function(composer, command) { 254.13 + return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 254.14 + }, 254.15 + 254.16 + state: function(composer, command) { 254.17 + return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 254.18 + } 254.19 + }; 254.20 +})(); 254.21 + 254.22 +wysihtml.commands.alignJustifyStyle = (function() { 254.23 + var nodeOptions = { 254.24 + styleProperty: "textAlign", 254.25 + styleValue: "justify", 254.26 + toggle: true 254.27 + }; 254.28 + 254.29 + return { 254.30 + exec: function(composer, command) { 254.31 + return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 254.32 + }, 254.33 + 254.34 + state: function(composer, command) { 254.35 + return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 254.36 + } 254.37 + }; 254.38 +})(); 254.39 + 254.40 +wysihtml.commands.alignLeftStyle = (function() { 254.41 + var nodeOptions = { 254.42 + styleProperty: "textAlign", 254.43 + styleValue: "left", 254.44 + toggle: true 254.45 + }; 254.46 + 254.47 + return { 254.48 + exec: function(composer, command) { 254.49 + return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 254.50 + }, 254.51 + 254.52 + state: function(composer, command) { 254.53 + return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 254.54 + } 254.55 + }; 254.56 +})(); 254.57 + 254.58 +wysihtml.commands.alignRightStyle = (function() { 254.59 + var nodeOptions = { 254.60 + styleProperty: "textAlign", 254.61 + styleValue: "right", 254.62 + toggle: true 254.63 + }; 254.64 + 254.65 + return { 254.66 + exec: function(composer, command) { 254.67 + return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 254.68 + }, 254.69 + 254.70 + state: function(composer, command) { 254.71 + return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 254.72 + } 254.73 + }; 254.74 +})(); 254.75 + 254.76 +/* Sets text background color by inline styles */ 254.77 +wysihtml.commands.bgColorStyle = (function() { 254.78 + return { 254.79 + exec: function(composer, command, color) { 254.80 + var colorVals = wysihtml.quirks.styleParser.parseColor("background-color:" + (color.color || color), "background-color"), 254.81 + colString; 254.82 + 254.83 + if (colorVals) { 254.84 + colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(', ') : "rgba(" + colorVals.join(', ')) + ')'; 254.85 + wysihtml.commands.formatInline.exec(composer, command, {styleProperty: 'backgroundColor', styleValue: colString}); 254.86 + } 254.87 + }, 254.88 + 254.89 + state: function(composer, command, color) { 254.90 + var colorVals = color ? wysihtml.quirks.styleParser.parseColor("background-color:" + (color.color || color), "background-color") : null, 254.91 + colString; 254.92 + 254.93 + if (colorVals) { 254.94 + colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(', ') : "rgba(" + colorVals.join(', ')) + ')'; 254.95 + } 254.96 + 254.97 + return wysihtml.commands.formatInline.state(composer, command, {styleProperty: 'backgroundColor', styleValue: colString}); 254.98 + }, 254.99 + 254.100 + remove: function(composer, command) { 254.101 + return wysihtml.commands.formatInline.remove(composer, command, {styleProperty: 'backgroundColor'}); 254.102 + }, 254.103 + 254.104 + stateValue: function(composer, command, props) { 254.105 + var st = this.state(composer, command), 254.106 + colorStr, 254.107 + val = false; 254.108 + 254.109 + if (st && wysihtml.lang.object(st).isArray()) { 254.110 + st = st[0]; 254.111 + } 254.112 + 254.113 + if (st) { 254.114 + colorStr = st.getAttribute('style'); 254.115 + if (colorStr) { 254.116 + val = wysihtml.quirks.styleParser.parseColor(colorStr, "background-color"); 254.117 + return wysihtml.quirks.styleParser.unparseColor(val, props); 254.118 + } 254.119 + } 254.120 + return false; 254.121 + } 254.122 + }; 254.123 +})(); 254.124 + 254.125 +wysihtml.commands.bold = (function() { 254.126 + var nodeOptions = { 254.127 + nodeName: "B", 254.128 + toggle: true 254.129 + }; 254.130 + 254.131 + return { 254.132 + exec: function(composer, command) { 254.133 + wysihtml.commands.formatInline.exec(composer, command, nodeOptions); 254.134 + }, 254.135 + 254.136 + state: function(composer, command) { 254.137 + return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 254.138 + } 254.139 + }; 254.140 +})(); 254.141 + 254.142 +/* Formats block for as a <pre><code class="classname"></code></pre> block 254.143 + * Useful in conjuction for sytax highlight utility: highlight.js 254.144 + * 254.145 + * Usage: 254.146 + * 254.147 + * editorInstance.composer.commands.exec("formatCode", "language-html"); 254.148 +*/ 254.149 +wysihtml.commands.formatCode = (function() { 254.150 + return { 254.151 + exec: function(composer, command, classname) { 254.152 + var pre = this.state(composer)[0], 254.153 + code, range, selectedNodes; 254.154 + 254.155 + if (pre) { 254.156 + // caret is already within a <pre><code>...</code></pre> 254.157 + composer.selection.executeAndRestore(function() { 254.158 + code = pre.querySelector("code"); 254.159 + wysihtml.dom.replaceWithChildNodes(pre); 254.160 + if (code) { 254.161 + wysihtml.dom.replaceWithChildNodes(code); 254.162 + } 254.163 + }); 254.164 + } else { 254.165 + // Wrap in <pre><code>...</code></pre> 254.166 + range = composer.selection.getRange(); 254.167 + selectedNodes = range.extractContents(); 254.168 + pre = composer.doc.createElement("pre"); 254.169 + code = composer.doc.createElement("code"); 254.170 + 254.171 + if (classname) { 254.172 + code.className = classname; 254.173 + } 254.174 + 254.175 + pre.appendChild(code); 254.176 + code.appendChild(selectedNodes); 254.177 + range.insertNode(pre); 254.178 + composer.selection.selectNode(pre); 254.179 + } 254.180 + }, 254.181 + 254.182 + state: function(composer) { 254.183 + var selectedNode = composer.selection.getSelectedNode(), node; 254.184 + if (selectedNode && selectedNode.nodeName && selectedNode.nodeName == "PRE"&& 254.185 + selectedNode.firstChild && selectedNode.firstChild.nodeName && selectedNode.firstChild.nodeName == "CODE") { 254.186 + return [selectedNode]; 254.187 + } else { 254.188 + node = wysihtml.dom.getParentElement(selectedNode, { query: "pre code" }); 254.189 + return node ? [node.parentNode] : false; 254.190 + } 254.191 + } 254.192 + }; 254.193 +})(); 254.194 + 254.195 +/** 254.196 + * Inserts an <img> 254.197 + * If selection is already an image link, it removes it 254.198 + * 254.199 + * @example 254.200 + * // either ... 254.201 + * wysihtml.commands.insertImage.exec(composer, "insertImage", "http://www.google.de/logo.jpg"); 254.202 + * // ... or ... 254.203 + * wysihtml.commands.insertImage.exec(composer, "insertImage", { src: "http://www.google.de/logo.jpg", title: "foo" }); 254.204 + */ 254.205 +wysihtml.commands.insertImage = (function() { 254.206 + var NODE_NAME = "IMG"; 254.207 + return { 254.208 + exec: function(composer, command, value) { 254.209 + value = typeof(value) === "object" ? value : { src: value }; 254.210 + 254.211 + var doc = composer.doc, 254.212 + image = this.state(composer), 254.213 + textNode, 254.214 + parent; 254.215 + 254.216 + // If image is selected and src ie empty, set the caret before it and delete the image 254.217 + if (image && !value.src) { 254.218 + composer.selection.setBefore(image); 254.219 + parent = image.parentNode; 254.220 + parent.removeChild(image); 254.221 + 254.222 + // and it's parent <a> too if it hasn't got any other relevant child nodes 254.223 + wysihtml.dom.removeEmptyTextNodes(parent); 254.224 + if (parent.nodeName === "A" && !parent.firstChild) { 254.225 + composer.selection.setAfter(parent); 254.226 + parent.parentNode.removeChild(parent); 254.227 + } 254.228 + 254.229 + // firefox and ie sometimes don't remove the image handles, even though the image got removed 254.230 + wysihtml.quirks.redraw(composer.element); 254.231 + return; 254.232 + } 254.233 + 254.234 + // If image selected change attributes accordingly 254.235 + if (image) { 254.236 + for (var key in value) { 254.237 + if (value.hasOwnProperty(key)) { 254.238 + image.setAttribute(key === "className" ? "class" : key, value[key]); 254.239 + } 254.240 + } 254.241 + return; 254.242 + } 254.243 + 254.244 + // Otherwise lets create the image 254.245 + image = doc.createElement(NODE_NAME); 254.246 + 254.247 + for (var i in value) { 254.248 + image.setAttribute(i === "className" ? "class" : i, value[i]); 254.249 + } 254.250 + 254.251 + composer.selection.insertNode(image); 254.252 + if (wysihtml.browser.hasProblemsSettingCaretAfterImg()) { 254.253 + textNode = doc.createTextNode(wysihtml.INVISIBLE_SPACE); 254.254 + composer.selection.insertNode(textNode); 254.255 + composer.selection.setAfter(textNode); 254.256 + } else { 254.257 + composer.selection.setAfter(image); 254.258 + } 254.259 + }, 254.260 + 254.261 + state: function(composer) { 254.262 + var doc = composer.doc, 254.263 + selectedNode, 254.264 + text, 254.265 + imagesInSelection; 254.266 + 254.267 + if (!wysihtml.dom.hasElementWithTagName(doc, NODE_NAME)) { 254.268 + return false; 254.269 + } 254.270 + 254.271 + selectedNode = composer.selection.getSelectedNode(); 254.272 + if (!selectedNode) { 254.273 + return false; 254.274 + } 254.275 + 254.276 + if (selectedNode.nodeName === NODE_NAME) { 254.277 + // This works perfectly in IE 254.278 + return selectedNode; 254.279 + } 254.280 + 254.281 + if (selectedNode.nodeType !== wysihtml.ELEMENT_NODE) { 254.282 + return false; 254.283 + } 254.284 + 254.285 + text = composer.selection.getText(); 254.286 + text = wysihtml.lang.string(text).trim(); 254.287 + if (text) { 254.288 + return false; 254.289 + } 254.290 + 254.291 + imagesInSelection = composer.selection.getNodes(wysihtml.ELEMENT_NODE, function(node) { 254.292 + return node.nodeName === "IMG"; 254.293 + }); 254.294 + 254.295 + if (imagesInSelection.length !== 1) { 254.296 + return false; 254.297 + } 254.298 + 254.299 + return imagesInSelection[0]; 254.300 + } 254.301 + }; 254.302 +})(); 254.303 + 254.304 +wysihtml.commands.fontSize = (function() { 254.305 + var REG_EXP = /wysiwyg-font-size-[0-9a-z\-]+/g; 254.306 + 254.307 + return { 254.308 + exec: function(composer, command, size) { 254.309 + wysihtml.commands.formatInline.exec(composer, command, {className: "wysiwyg-font-size-" + size, classRegExp: REG_EXP, toggle: true}); 254.310 + }, 254.311 + 254.312 + state: function(composer, command, size) { 254.313 + return wysihtml.commands.formatInline.state(composer, command, {className: "wysiwyg-font-size-" + size}); 254.314 + } 254.315 + }; 254.316 +})(); 254.317 + 254.318 +/* Set font size by inline style */ 254.319 +wysihtml.commands.fontSizeStyle = (function() { 254.320 + return { 254.321 + exec: function(composer, command, size) { 254.322 + size = size.size || size; 254.323 + if (!(/^\s*$/).test(size)) { 254.324 + wysihtml.commands.formatInline.exec(composer, command, {styleProperty: "fontSize", styleValue: size, toggle: false}); 254.325 + } 254.326 + }, 254.327 + 254.328 + state: function(composer, command, size) { 254.329 + return wysihtml.commands.formatInline.state(composer, command, {styleProperty: "fontSize", styleValue: size || undefined}); 254.330 + }, 254.331 + 254.332 + remove: function(composer, command) { 254.333 + return wysihtml.commands.formatInline.remove(composer, command, {styleProperty: "fontSize"}); 254.334 + }, 254.335 + 254.336 + stateValue: function(composer, command) { 254.337 + var styleStr, 254.338 + st = this.state(composer, command); 254.339 + 254.340 + if (st && wysihtml.lang.object(st).isArray()) { 254.341 + st = st[0]; 254.342 + } 254.343 + if (st) { 254.344 + styleStr = st.getAttribute("style"); 254.345 + if (styleStr) { 254.346 + return wysihtml.quirks.styleParser.parseFontSize(styleStr); 254.347 + } 254.348 + } 254.349 + return false; 254.350 + } 254.351 + }; 254.352 +})(); 254.353 + 254.354 +wysihtml.commands.foreColor = (function() { 254.355 + var REG_EXP = /wysiwyg-color-[0-9a-z]+/g; 254.356 + 254.357 + return { 254.358 + exec: function(composer, command, color) { 254.359 + wysihtml.commands.formatInline.exec(composer, command, {className: "wysiwyg-color-" + color, classRegExp: REG_EXP, toggle: true}); 254.360 + }, 254.361 + 254.362 + state: function(composer, command, color) { 254.363 + return wysihtml.commands.formatInline.state(composer, command, {className: "wysiwyg-color-" + color}); 254.364 + } 254.365 + }; 254.366 +})(); 254.367 + 254.368 +/* Sets text color by inline styles */ 254.369 +wysihtml.commands.foreColorStyle = (function() { 254.370 + return { 254.371 + exec: function(composer, command, color) { 254.372 + var colorVals, colString; 254.373 + 254.374 + if (!color) { return; } 254.375 + 254.376 + colorVals = wysihtml.quirks.styleParser.parseColor("color:" + (color.color || color), "color"); 254.377 + 254.378 + if (colorVals) { 254.379 + colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(", ") : "rgba(" + colorVals.join(', ')) + ')'; 254.380 + wysihtml.commands.formatInline.exec(composer, command, {styleProperty: "color", styleValue: colString}); 254.381 + } 254.382 + }, 254.383 + 254.384 + state: function(composer, command, color) { 254.385 + var colorVals = color ? wysihtml.quirks.styleParser.parseColor("color:" + (color.color || color), "color") : null, 254.386 + colString; 254.387 + 254.388 + 254.389 + if (colorVals) { 254.390 + colString = (colorVals[3] === 1 ? "rgb(" + [colorVals[0], colorVals[1], colorVals[2]].join(", ") : "rgba(" + colorVals.join(', ')) + ')'; 254.391 + } 254.392 + 254.393 + return wysihtml.commands.formatInline.state(composer, command, {styleProperty: "color", styleValue: colString}); 254.394 + }, 254.395 + 254.396 + remove: function(composer, command) { 254.397 + return wysihtml.commands.formatInline.remove(composer, command, {styleProperty: "color"}); 254.398 + }, 254.399 + 254.400 + stateValue: function(composer, command, props) { 254.401 + var st = this.state(composer, command), 254.402 + colorStr, 254.403 + val = false; 254.404 + 254.405 + if (st && wysihtml.lang.object(st).isArray()) { 254.406 + st = st[0]; 254.407 + } 254.408 + 254.409 + if (st) { 254.410 + colorStr = st.getAttribute("style"); 254.411 + if (colorStr) { 254.412 + val = wysihtml.quirks.styleParser.parseColor(colorStr, "color"); 254.413 + return wysihtml.quirks.styleParser.unparseColor(val, props); 254.414 + } 254.415 + } 254.416 + return false; 254.417 + } 254.418 + }; 254.419 +})(); 254.420 + 254.421 +wysihtml.commands.insertBlockQuote = (function() { 254.422 + var nodeOptions = { 254.423 + nodeName: "BLOCKQUOTE", 254.424 + toggle: true 254.425 + }; 254.426 + 254.427 + return { 254.428 + exec: function(composer, command) { 254.429 + return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 254.430 + }, 254.431 + 254.432 + state: function(composer, command) { 254.433 + return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 254.434 + } 254.435 + }; 254.436 +})(); 254.437 + 254.438 +wysihtml.commands.insertHorizontalRule = (function() { 254.439 + return { 254.440 + exec: function(composer) { 254.441 + var node = composer.selection.getSelectedNode(), 254.442 + phrasingOnlyParent = wysihtml.dom.getParentElement(node, { query: wysihtml.PERMITTED_PHRASING_CONTENT_ONLY }, null, composer.editableArea), 254.443 + elem = document.createElement('hr'), 254.444 + range, idx; 254.445 + 254.446 + // HR is not allowed into some elements (where only phrasing content is allowed) 254.447 + // thus the HR insertion must break out of those https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories 254.448 + if (phrasingOnlyParent) { 254.449 + composer.selection.splitElementAtCaret(phrasingOnlyParent, elem); 254.450 + } else { 254.451 + composer.selection.insertNode(elem); 254.452 + } 254.453 + 254.454 + if (elem.nextSibling) { 254.455 + composer.selection.setBefore(elem.nextSibling); 254.456 + } else { 254.457 + composer.selection.setAfter(elem); 254.458 + } 254.459 + }, 254.460 + state: function() { 254.461 + return false; // :( 254.462 + } 254.463 + }; 254.464 +})(); 254.465 + 254.466 +wysihtml.commands.insertOrderedList = (function() { 254.467 + return { 254.468 + exec: function(composer, command) { 254.469 + wysihtml.commands.insertList.exec(composer, command, "OL"); 254.470 + }, 254.471 + 254.472 + state: function(composer, command) { 254.473 + return wysihtml.commands.insertList.state(composer, command, "OL"); 254.474 + } 254.475 + }; 254.476 +})(); 254.477 + 254.478 +wysihtml.commands.insertUnorderedList = (function() { 254.479 + return { 254.480 + exec: function(composer, command) { 254.481 + wysihtml.commands.insertList.exec(composer, command, "UL"); 254.482 + }, 254.483 + 254.484 + state: function(composer, command) { 254.485 + return wysihtml.commands.insertList.state(composer, command, "UL"); 254.486 + } 254.487 + }; 254.488 +})(); 254.489 + 254.490 +wysihtml.commands.italic = (function() { 254.491 + var nodeOptions = { 254.492 + nodeName: "I", 254.493 + toggle: true 254.494 + }; 254.495 + 254.496 + return { 254.497 + exec: function(composer, command) { 254.498 + wysihtml.commands.formatInline.exec(composer, command, nodeOptions); 254.499 + }, 254.500 + 254.501 + state: function(composer, command) { 254.502 + return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 254.503 + } 254.504 + }; 254.505 + 254.506 +})(); 254.507 + 254.508 +wysihtml.commands.justifyCenter = (function() { 254.509 + var nodeOptions = { 254.510 + className: "wysiwyg-text-align-center", 254.511 + classRegExp: /wysiwyg-text-align-[0-9a-z]+/g, 254.512 + toggle: true 254.513 + }; 254.514 + 254.515 + return { 254.516 + exec: function(composer, command) { 254.517 + return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 254.518 + }, 254.519 + 254.520 + state: function(composer, command) { 254.521 + return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 254.522 + } 254.523 + }; 254.524 + 254.525 +})(); 254.526 + 254.527 +wysihtml.commands.justifyFull = (function() { 254.528 + var nodeOptions = { 254.529 + className: "wysiwyg-text-align-justify", 254.530 + classRegExp: /wysiwyg-text-align-[0-9a-z]+/g, 254.531 + toggle: true 254.532 + }; 254.533 + 254.534 + return { 254.535 + exec: function(composer, command) { 254.536 + return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 254.537 + }, 254.538 + 254.539 + state: function(composer, command) { 254.540 + return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 254.541 + } 254.542 + }; 254.543 +})(); 254.544 + 254.545 +wysihtml.commands.justifyLeft = (function() { 254.546 + var nodeOptions = { 254.547 + className: "wysiwyg-text-align-left", 254.548 + classRegExp: /wysiwyg-text-align-[0-9a-z]+/g, 254.549 + toggle: true 254.550 + }; 254.551 + 254.552 + return { 254.553 + exec: function(composer, command) { 254.554 + return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 254.555 + }, 254.556 + 254.557 + state: function(composer, command) { 254.558 + return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 254.559 + } 254.560 + }; 254.561 +})(); 254.562 + 254.563 +wysihtml.commands.justifyRight = (function() { 254.564 + var nodeOptions = { 254.565 + className: "wysiwyg-text-align-right", 254.566 + classRegExp: /wysiwyg-text-align-[0-9a-z]+/g, 254.567 + toggle: true 254.568 + }; 254.569 + 254.570 + return { 254.571 + exec: function(composer, command) { 254.572 + return wysihtml.commands.formatBlock.exec(composer, "formatBlock", nodeOptions); 254.573 + }, 254.574 + 254.575 + state: function(composer, command) { 254.576 + return wysihtml.commands.formatBlock.state(composer, "formatBlock", nodeOptions); 254.577 + } 254.578 + }; 254.579 +})(); 254.580 + 254.581 +wysihtml.commands.subscript = (function() { 254.582 + var nodeOptions = { 254.583 + nodeName: "SUB", 254.584 + toggle: true 254.585 + }; 254.586 + 254.587 + return { 254.588 + exec: function(composer, command) { 254.589 + wysihtml.commands.formatInline.exec(composer, command, nodeOptions); 254.590 + }, 254.591 + 254.592 + state: function(composer, command) { 254.593 + return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 254.594 + } 254.595 + }; 254.596 + 254.597 +})(); 254.598 + 254.599 +wysihtml.commands.superscript = (function() { 254.600 + var nodeOptions = { 254.601 + nodeName: "SUP", 254.602 + toggle: true 254.603 + }; 254.604 + 254.605 + return { 254.606 + exec: function(composer, command) { 254.607 + wysihtml.commands.formatInline.exec(composer, command, nodeOptions); 254.608 + }, 254.609 + 254.610 + state: function(composer, command) { 254.611 + return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 254.612 + } 254.613 + }; 254.614 + 254.615 +})(); 254.616 + 254.617 +wysihtml.commands.underline = (function() { 254.618 + var nodeOptions = { 254.619 + nodeName: "U", 254.620 + toggle: true 254.621 + }; 254.622 + 254.623 + return { 254.624 + exec: function(composer, command) { 254.625 + wysihtml.commands.formatInline.exec(composer, command, nodeOptions); 254.626 + }, 254.627 + 254.628 + state: function(composer, command) { 254.629 + return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 254.630 + } 254.631 + }; 254.632 + 254.633 +})();
255.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 255.2 +++ b/static/wysihtml/wysihtml.js Sun Jul 15 14:07:29 2018 +0200 255.3 @@ -0,0 +1,16102 @@ 255.4 +/** 255.5 + * @license wysihtml v0.6.0-beta1 255.6 + * https://github.com/Voog/wysihtml 255.7 + * 255.8 + * Author: Christopher Blum (https://github.com/tiff) 255.9 + * Secondary author of extended features: Oliver Pulges (https://github.com/pulges) 255.10 + * 255.11 + * Copyright (C) 2012 XING AG 255.12 + * Licensed under the MIT license (MIT) 255.13 + * 255.14 + */ 255.15 +var wysihtml = { 255.16 + version: '0.6.0-beta1', 255.17 + 255.18 + // namespaces 255.19 + commands: {}, 255.20 + dom: {}, 255.21 + quirks: {}, 255.22 + toolbar: {}, 255.23 + lang: {}, 255.24 + selection: {}, 255.25 + views: {}, 255.26 + 255.27 + editorExtenders: [], 255.28 + extendEditor: function(extender) { 255.29 + this.editorExtenders.push(extender); 255.30 + }, 255.31 + 255.32 + INVISIBLE_SPACE: '\uFEFF', 255.33 + INVISIBLE_SPACE_REG_EXP: /\uFEFF/g, 255.34 + 255.35 + VOID_ELEMENTS: 'area, base, br, col, embed, hr, img, input, keygen, link, meta, param, source, track, wbr', 255.36 + PERMITTED_PHRASING_CONTENT_ONLY: 'h1, h2, h3, h4, h5, h6, p, pre', 255.37 + 255.38 + EMPTY_FUNCTION: function() {}, 255.39 + 255.40 + ELEMENT_NODE: 1, 255.41 + TEXT_NODE: 3, 255.42 + 255.43 + BACKSPACE_KEY: 8, 255.44 + ENTER_KEY: 13, 255.45 + ESCAPE_KEY: 27, 255.46 + SPACE_KEY: 32, 255.47 + TAB_KEY: 9, 255.48 + DELETE_KEY: 46 255.49 +}; 255.50 + 255.51 +wysihtml.polyfills = function(win, doc) { 255.52 + 255.53 + var methods = { 255.54 + 255.55 + // Safary has a bug of not restoring selection after node.normalize correctly. 255.56 + // Detects the misbegaviour and patches it 255.57 + normalizeHasCaretError: function() { 255.58 + if ("createRange" in doc && "getSelection" in win) { 255.59 + var originalTarget, 255.60 + scrollTop = window.pageYOffset, 255.61 + scrollLeft = window.pageXOffset, 255.62 + e = doc.createElement('div'), 255.63 + t1 = doc.createTextNode('a'), 255.64 + t2 = doc.createTextNode('a'), 255.65 + t3 = doc.createTextNode('a'), 255.66 + r = doc.createRange(), 255.67 + s, ret; 255.68 + 255.69 + if (document.activeElement) { 255.70 + if (document.activeElement.nodeType === 1 && ['INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'].indexOf(document.activeElement.nodeName) > -1) { 255.71 + originalTarget = { 255.72 + type: 'form', 255.73 + node: document.activeElement, 255.74 + start: document.activeElement.selectionStart, 255.75 + end: document.activeElement.selectionEnd, 255.76 + }; 255.77 + } else { 255.78 + s = win.getSelection(); 255.79 + if (s && s.anchorNode) { 255.80 + originalTarget = { 255.81 + type: 'range', 255.82 + anchorNode: s.anchorNode, 255.83 + anchorOffset: s.anchorOffset, 255.84 + focusNode: s.focusNode, 255.85 + focusOffset: s.focusOffset 255.86 + }; 255.87 + } 255.88 + } 255.89 + } 255.90 + 255.91 + e.setAttribute('contenteditable', 'true'); 255.92 + e.appendChild(t1); 255.93 + e.appendChild(t2); 255.94 + e.appendChild(t3); 255.95 + doc.body.appendChild(e); 255.96 + r.setStart(t2, 1); 255.97 + r.setEnd(t2, 1); 255.98 + 255.99 + s = win.getSelection(); 255.100 + s.removeAllRanges(); 255.101 + s.addRange(r); 255.102 + e.normalize(); 255.103 + s = win.getSelection(); 255.104 + 255.105 + ret = (e.childNodes.length !== 1 || s.anchorNode !== e.firstChild || s.anchorOffset !== 2); 255.106 + e.parentNode.removeChild(e); 255.107 + s.removeAllRanges(); 255.108 + 255.109 + if (originalTarget) { 255.110 + if (originalTarget.type === 'form') { 255.111 + // The selection parameters are not present for all form elements 255.112 + if (typeof originalTarget.start !== 'undefined' && typeof originalTarget.end !== 'undefined') { 255.113 + originalTarget.node.setSelectionRange(originalTarget.start, originalTarget.end); 255.114 + } 255.115 + originalTarget.node.focus(); 255.116 + } else if (originalTarget.type === 'range') { 255.117 + r = doc.createRange(); 255.118 + r.setStart(originalTarget.anchorNode, originalTarget.anchorOffset); 255.119 + r.setEnd(originalTarget.focusNode, originalTarget.focusOffset); 255.120 + s.addRange(r); 255.121 + } 255.122 + } 255.123 + 255.124 + if (scrollTop !== window.pageYOffset || scrollLeft !== window.pageXOffset) { 255.125 + win.scrollTo(scrollLeft, scrollTop); 255.126 + } 255.127 + 255.128 + return ret; 255.129 + } 255.130 + }, 255.131 + 255.132 + apply: function() { 255.133 + // closest, matches, and remove polyfill 255.134 + // https://github.com/jonathantneal/closest 255.135 + (function (ELEMENT) { 255.136 + ELEMENT.matches = ELEMENT.matches || ELEMENT.mozMatchesSelector || ELEMENT.msMatchesSelector || ELEMENT.oMatchesSelector || ELEMENT.webkitMatchesSelector || function matches(selector) { 255.137 + var 255.138 + element = this, 255.139 + elements = (element.document || element.ownerDocument).querySelectorAll(selector), 255.140 + index = 0; 255.141 + 255.142 + while (elements[index] && elements[index] !== element) { 255.143 + ++index; 255.144 + } 255.145 + 255.146 + return elements[index] ? true : false; 255.147 + }; 255.148 + 255.149 + ELEMENT.closest = ELEMENT.closest || function closest(selector) { 255.150 + var element = this; 255.151 + 255.152 + while (element) { 255.153 + if (element.matches(selector)) { 255.154 + break; 255.155 + } 255.156 + 255.157 + element = element.parentElement; 255.158 + } 255.159 + 255.160 + return element; 255.161 + }; 255.162 + 255.163 + ELEMENT.remove = ELEMENT.remove || function remove() { 255.164 + if (this.parentNode) { 255.165 + this.parentNode.removeChild(this); 255.166 + } 255.167 + }; 255.168 + 255.169 + }(win.Element.prototype)); 255.170 + 255.171 + if (!('classList' in doc.documentElement) && win.Object.defineProperty && typeof win.HTMLElement !== 'undefined') { 255.172 + win.Object.defineProperty(win.HTMLElement.prototype, 'classList', { 255.173 + get: function() { 255.174 + var self = this; 255.175 + function update(fn) { 255.176 + return function(value) { 255.177 + var classes = self.className.split(/\s+/), 255.178 + index = classes.indexOf(value); 255.179 + 255.180 + fn(classes, index, value); 255.181 + self.className = classes.join(' '); 255.182 + }; 255.183 + } 255.184 + 255.185 + var ret = { 255.186 + add: update(function(classes, index, value) { 255.187 + ~index || classes.push(value); 255.188 + }), 255.189 + 255.190 + remove: update(function(classes, index) { 255.191 + ~index && classes.splice(index, 1); 255.192 + }), 255.193 + 255.194 + toggle: update(function(classes, index, value) { 255.195 + ~index ? classes.splice(index, 1) : classes.push(value); 255.196 + }), 255.197 + 255.198 + contains: function(value) { 255.199 + return !!~self.className.split(/\s+/).indexOf(value); 255.200 + }, 255.201 + 255.202 + item: function(i) { 255.203 + return self.className.split(/\s+/)[i] || null; 255.204 + } 255.205 + }; 255.206 + 255.207 + win.Object.defineProperty(ret, 'length', { 255.208 + get: function() { 255.209 + return self.className.split(/\s+/).length; 255.210 + } 255.211 + }); 255.212 + 255.213 + return ret; 255.214 + } 255.215 + }); 255.216 + } 255.217 + 255.218 + var getTextNodes = function(node){ 255.219 + var all = []; 255.220 + for (node=node.firstChild;node;node=node.nextSibling){ 255.221 + if (node.nodeType == 3) { 255.222 + all.push(node); 255.223 + } else { 255.224 + all = all.concat(getTextNodes(node)); 255.225 + } 255.226 + } 255.227 + return all; 255.228 + }; 255.229 + 255.230 + var isInDom = function(node) { 255.231 + var doc = node.ownerDocument, 255.232 + n = node; 255.233 + 255.234 + do { 255.235 + if (n === doc) { 255.236 + return true; 255.237 + } 255.238 + n = n.parentNode; 255.239 + } while(n); 255.240 + 255.241 + return false; 255.242 + }; 255.243 + 255.244 + var normalizeFix = function() { 255.245 + var f = win.Node.prototype.normalize; 255.246 + var nf = function() { 255.247 + var texts = getTextNodes(this), 255.248 + s = this.ownerDocument.defaultView.getSelection(), 255.249 + anode = s.anchorNode, 255.250 + aoffset = s.anchorOffset, 255.251 + aelement = anode && anode.nodeType === 1 && anode.childNodes.length > 0 ? anode.childNodes[aoffset] : undefined, 255.252 + fnode = s.focusNode, 255.253 + foffset = s.focusOffset, 255.254 + felement = fnode && fnode.nodeType === 1 && foffset > 0 ? fnode.childNodes[foffset -1] : undefined, 255.255 + r = this.ownerDocument.createRange(), 255.256 + prevTxt = texts.shift(), 255.257 + curText = prevTxt ? texts.shift() : null; 255.258 + 255.259 + if (felement && felement.nodeType === 3) { 255.260 + fnode = felement; 255.261 + foffset = felement.nodeValue.length; 255.262 + felement = undefined; 255.263 + } 255.264 + 255.265 + if (aelement && aelement.nodeType === 3) { 255.266 + anode = aelement; 255.267 + aoffset = 0; 255.268 + aelement = undefined; 255.269 + } 255.270 + 255.271 + if ((anode === fnode && foffset < aoffset) || (anode !== fnode && (anode.compareDocumentPosition(fnode) & win.Node.DOCUMENT_POSITION_PRECEDING) && !(anode.compareDocumentPosition(fnode) & win.Node.DOCUMENT_POSITION_CONTAINS))) { 255.272 + fnode = [anode, anode = fnode][0]; 255.273 + foffset = [aoffset, aoffset = foffset][0]; 255.274 + } 255.275 + 255.276 + while(prevTxt && curText) { 255.277 + if (curText.previousSibling && curText.previousSibling === prevTxt) { 255.278 + if (anode === curText) { 255.279 + anode = prevTxt; 255.280 + aoffset = prevTxt.nodeValue.length + aoffset; 255.281 + } 255.282 + if (fnode === curText) { 255.283 + fnode = prevTxt; 255.284 + foffset = prevTxt.nodeValue.length + foffset; 255.285 + } 255.286 + prevTxt.nodeValue = prevTxt.nodeValue + curText.nodeValue; 255.287 + curText.parentNode.removeChild(curText); 255.288 + curText = texts.shift(); 255.289 + } else { 255.290 + prevTxt = curText; 255.291 + curText = texts.shift(); 255.292 + } 255.293 + } 255.294 + 255.295 + if (felement) { 255.296 + foffset = Array.prototype.indexOf.call(felement.parentNode.childNodes, felement) + 1; 255.297 + } 255.298 + 255.299 + if (aelement) { 255.300 + aoffset = Array.prototype.indexOf.call(aelement.parentNode.childNodes, aelement); 255.301 + } 255.302 + 255.303 + if (isInDom(this) && anode && anode.parentNode && fnode && fnode.parentNode) { 255.304 + r.setStart(anode, aoffset); 255.305 + r.setEnd(fnode, foffset); 255.306 + s.removeAllRanges(); 255.307 + s.addRange(r); 255.308 + } 255.309 + }; 255.310 + win.Node.prototype.normalize = nf; 255.311 + }; 255.312 + 255.313 + var F = function() { 255.314 + win.removeEventListener("load", F); 255.315 + if ("Node" in win && "normalize" in win.Node.prototype && methods.normalizeHasCaretError()) { 255.316 + normalizeFix(); 255.317 + } 255.318 + }; 255.319 + 255.320 + if (doc.readyState !== "complete") { 255.321 + win.addEventListener("load", F); 255.322 + } else { 255.323 + F(); 255.324 + } 255.325 + 255.326 + // CustomEvent for ie9 and up 255.327 + function nativeCustomEventSupported() { 255.328 + try { 255.329 + var p = new win.CustomEvent('cat', {detail: {foo: 'bar'}}); 255.330 + return 'cat' === p.type && 'bar' === p.detail.foo; 255.331 + } catch (e) {} 255.332 + return false; 255.333 + } 255.334 + 255.335 + // Polyfills CustomEvent object for IE9 and up 255.336 + (function() { 255.337 + if (!nativeCustomEventSupported() && "CustomEvent" in win) { 255.338 + function CustomEvent(event, params) { 255.339 + params = params || {bubbles: false, cancelable: false, detail: undefined}; 255.340 + var evt = doc.createEvent('CustomEvent'); 255.341 + evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); 255.342 + return evt; 255.343 + } 255.344 + CustomEvent.prototype = win.Event.prototype; 255.345 + win.CustomEvent = CustomEvent; 255.346 + } 255.347 + })(); 255.348 + } 255.349 + } 255.350 + 255.351 + return methods; 255.352 +}; 255.353 + 255.354 +wysihtml.polyfills(window, document).apply(); 255.355 + 255.356 +/* 255.357 + Base.js, version 1.1a 255.358 + Copyright 2006-2010, Dean Edwards 255.359 + License: http://www.opensource.org/licenses/mit-license.php 255.360 +*/ 255.361 + 255.362 +var Base = function() { 255.363 + // dummy 255.364 +}; 255.365 + 255.366 +Base.extend = function(_instance, _static) { // subclass 255.367 + var extend = Base.prototype.extend; 255.368 + 255.369 + // build the prototype 255.370 + Base._prototyping = true; 255.371 + var proto = new this; 255.372 + extend.call(proto, _instance); 255.373 + proto.base = function() { 255.374 + // call this method from any other method to invoke that method's ancestor 255.375 + }; 255.376 + delete Base._prototyping; 255.377 + 255.378 + // create the wrapper for the constructor function 255.379 + //var constructor = proto.constructor.valueOf(); //-dean 255.380 + var constructor = proto.constructor; 255.381 + var klass = proto.constructor = function() { 255.382 + if (!Base._prototyping) { 255.383 + if (this._constructing || this.constructor == klass) { // instantiation 255.384 + this._constructing = true; 255.385 + constructor.apply(this, arguments); 255.386 + delete this._constructing; 255.387 + } else if (arguments[0] != null) { // casting 255.388 + return (arguments[0].extend || extend).call(arguments[0], proto); 255.389 + } 255.390 + } 255.391 + }; 255.392 + 255.393 + // build the class interface 255.394 + klass.ancestor = this; 255.395 + klass.extend = this.extend; 255.396 + klass.forEach = this.forEach; 255.397 + klass.implement = this.implement; 255.398 + klass.prototype = proto; 255.399 + klass.toString = this.toString; 255.400 + klass.valueOf = function(type) { 255.401 + //return (type == "object") ? klass : constructor; //-dean 255.402 + return (type == "object") ? klass : constructor.valueOf(); 255.403 + }; 255.404 + extend.call(klass, _static); 255.405 + // class initialisation 255.406 + if (typeof klass.init == "function") klass.init(); 255.407 + return klass; 255.408 +}; 255.409 + 255.410 +Base.prototype = { 255.411 + extend: function(source, value) { 255.412 + if (arguments.length > 1) { // extending with a name/value pair 255.413 + var ancestor = this[source]; 255.414 + if (ancestor && (typeof value == "function") && // overriding a method? 255.415 + // the valueOf() comparison is to avoid circular references 255.416 + (!ancestor.valueOf || ancestor.valueOf() != value.valueOf()) && 255.417 + /\bbase\b/.test(value)) { 255.418 + // get the underlying method 255.419 + var method = value.valueOf(); 255.420 + // override 255.421 + value = function() { 255.422 + var previous = this.base || Base.prototype.base; 255.423 + this.base = ancestor; 255.424 + var returnValue = method.apply(this, arguments); 255.425 + this.base = previous; 255.426 + return returnValue; 255.427 + }; 255.428 + // point to the underlying method 255.429 + value.valueOf = function(type) { 255.430 + return (type == "object") ? value : method; 255.431 + }; 255.432 + value.toString = Base.toString; 255.433 + } 255.434 + this[source] = value; 255.435 + } else if (source) { // extending with an object literal 255.436 + var extend = Base.prototype.extend; 255.437 + // if this object has a customised extend method then use it 255.438 + if (!Base._prototyping && typeof this != "function") { 255.439 + extend = this.extend || extend; 255.440 + } 255.441 + var proto = {toSource: null}; 255.442 + // do the "toString" and other methods manually 255.443 + var hidden = ["constructor", "toString", "valueOf"]; 255.444 + // if we are prototyping then include the constructor 255.445 + var i = Base._prototyping ? 0 : 1; 255.446 + while (key = hidden[i++]) { 255.447 + if (source[key] != proto[key]) { 255.448 + extend.call(this, key, source[key]); 255.449 + 255.450 + } 255.451 + } 255.452 + // copy each of the source object's properties to this object 255.453 + for (var key in source) { 255.454 + if (!proto[key]) extend.call(this, key, source[key]); 255.455 + } 255.456 + } 255.457 + return this; 255.458 + } 255.459 +}; 255.460 + 255.461 +// initialise 255.462 +Base = Base.extend({ 255.463 + constructor: function() { 255.464 + this.extend(arguments[0]); 255.465 + } 255.466 +}, { 255.467 + ancestor: Object, 255.468 + version: "1.1", 255.469 + 255.470 + forEach: function(object, block, context) { 255.471 + for (var key in object) { 255.472 + if (this.prototype[key] === undefined) { 255.473 + block.call(context, object[key], key, object); 255.474 + } 255.475 + } 255.476 + }, 255.477 + 255.478 + implement: function() { 255.479 + for (var i = 0; i < arguments.length; i++) { 255.480 + if (typeof arguments[i] == "function") { 255.481 + // if it's a function, call it 255.482 + arguments[i](this.prototype); 255.483 + } else { 255.484 + // add the interface using the extend method 255.485 + this.prototype.extend(arguments[i]); 255.486 + } 255.487 + } 255.488 + return this; 255.489 + }, 255.490 + 255.491 + toString: function() { 255.492 + return String(this.valueOf()); 255.493 + } 255.494 +}); 255.495 +/** 255.496 + * Rangy, a cross-browser JavaScript range and selection library 255.497 + * https://github.com/timdown/rangy 255.498 + * 255.499 + * Copyright 2015, Tim Down 255.500 + * Licensed under the MIT license. 255.501 + * Version: 1.3.1-dev 255.502 + * Build date: 20 May 2015 255.503 + * 255.504 + * NOTE: UMD wrapper removed manually for bundling (Oliver Pulges) 255.505 + */ 255.506 +var rangy; 255.507 + 255.508 +(function() { 255.509 + var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; 255.510 + 255.511 + // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START 255.512 + // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113. 255.513 + var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", 255.514 + "commonAncestorContainer"]; 255.515 + 255.516 + // Minimal set of methods required for DOM Level 2 Range compliance 255.517 + var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", 255.518 + "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", 255.519 + "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; 255.520 + 255.521 + var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; 255.522 + 255.523 + // Subset of TextRange's full set of methods that we're interested in 255.524 + var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select", 255.525 + "setEndPoint", "getBoundingClientRect"]; 255.526 + 255.527 + /*----------------------------------------------------------------------------------------------------------------*/ 255.528 + 255.529 + // Trio of functions taken from Peter Michaux's article: 255.530 + // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting 255.531 + function isHostMethod(o, p) { 255.532 + var t = typeof o[p]; 255.533 + return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; 255.534 + } 255.535 + 255.536 + function isHostObject(o, p) { 255.537 + return !!(typeof o[p] == OBJECT && o[p]); 255.538 + } 255.539 + 255.540 + function isHostProperty(o, p) { 255.541 + return typeof o[p] != UNDEFINED; 255.542 + } 255.543 + 255.544 + // Creates a convenience function to save verbose repeated calls to tests functions 255.545 + function createMultiplePropertyTest(testFunc) { 255.546 + return function(o, props) { 255.547 + var i = props.length; 255.548 + while (i--) { 255.549 + if (!testFunc(o, props[i])) { 255.550 + return false; 255.551 + } 255.552 + } 255.553 + return true; 255.554 + }; 255.555 + } 255.556 + 255.557 + // Next trio of functions are a convenience to save verbose repeated calls to previous two functions 255.558 + var areHostMethods = createMultiplePropertyTest(isHostMethod); 255.559 + var areHostObjects = createMultiplePropertyTest(isHostObject); 255.560 + var areHostProperties = createMultiplePropertyTest(isHostProperty); 255.561 + 255.562 + function isTextRange(range) { 255.563 + return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); 255.564 + } 255.565 + 255.566 + function getBody(doc) { 255.567 + return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; 255.568 + } 255.569 + 255.570 + var forEach = [].forEach ? 255.571 + function(arr, func) { 255.572 + arr.forEach(func); 255.573 + } : 255.574 + function(arr, func) { 255.575 + for (var i = 0, len = arr.length; i < len; ++i) { 255.576 + func(arr[i], i); 255.577 + } 255.578 + }; 255.579 + 255.580 + var modules = {}; 255.581 + 255.582 + var isBrowser = (typeof window != UNDEFINED && typeof document != UNDEFINED); 255.583 + 255.584 + var util = { 255.585 + isHostMethod: isHostMethod, 255.586 + isHostObject: isHostObject, 255.587 + isHostProperty: isHostProperty, 255.588 + areHostMethods: areHostMethods, 255.589 + areHostObjects: areHostObjects, 255.590 + areHostProperties: areHostProperties, 255.591 + isTextRange: isTextRange, 255.592 + getBody: getBody, 255.593 + forEach: forEach 255.594 + }; 255.595 + 255.596 + var api = { 255.597 + version: "1.3.1-dev", 255.598 + initialized: false, 255.599 + isBrowser: isBrowser, 255.600 + supported: true, 255.601 + util: util, 255.602 + features: {}, 255.603 + modules: modules, 255.604 + config: { 255.605 + alertOnFail: false, 255.606 + alertOnWarn: false, 255.607 + preferTextRange: false, 255.608 + autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize 255.609 + } 255.610 + }; 255.611 + 255.612 + function consoleLog(msg) { 255.613 + if (typeof console != UNDEFINED && isHostMethod(console, "log")) { 255.614 + console.log(msg); 255.615 + } 255.616 + } 255.617 + 255.618 + function alertOrLog(msg, shouldAlert) { 255.619 + if (isBrowser && shouldAlert) { 255.620 + alert(msg); 255.621 + } else { 255.622 + consoleLog(msg); 255.623 + } 255.624 + } 255.625 + 255.626 + function fail(reason) { 255.627 + api.initialized = true; 255.628 + api.supported = false; 255.629 + alertOrLog("Rangy is not supported in this environment. Reason: " + reason, api.config.alertOnFail); 255.630 + } 255.631 + 255.632 + api.fail = fail; 255.633 + 255.634 + function warn(msg) { 255.635 + alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn); 255.636 + } 255.637 + 255.638 + api.warn = warn; 255.639 + 255.640 + // Add utility extend() method 255.641 + var extend; 255.642 + if ({}.hasOwnProperty) { 255.643 + util.extend = extend = function(obj, props, deep) { 255.644 + var o, p; 255.645 + for (var i in props) { 255.646 + if (props.hasOwnProperty(i)) { 255.647 + o = obj[i]; 255.648 + p = props[i]; 255.649 + if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") { 255.650 + extend(o, p, true); 255.651 + } 255.652 + obj[i] = p; 255.653 + } 255.654 + } 255.655 + // Special case for toString, which does not show up in for...in loops in IE <= 8 255.656 + if (props.hasOwnProperty("toString")) { 255.657 + obj.toString = props.toString; 255.658 + } 255.659 + return obj; 255.660 + }; 255.661 + 255.662 + util.createOptions = function(optionsParam, defaults) { 255.663 + var options = {}; 255.664 + extend(options, defaults); 255.665 + if (optionsParam) { 255.666 + extend(options, optionsParam); 255.667 + } 255.668 + return options; 255.669 + }; 255.670 + } else { 255.671 + fail("hasOwnProperty not supported"); 255.672 + } 255.673 + 255.674 + // Test whether we're in a browser and bail out if not 255.675 + if (!isBrowser) { 255.676 + fail("Rangy can only run in a browser"); 255.677 + } 255.678 + 255.679 + // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not 255.680 + (function() { 255.681 + var toArray; 255.682 + 255.683 + if (isBrowser) { 255.684 + var el = document.createElement("div"); 255.685 + el.appendChild(document.createElement("span")); 255.686 + var slice = [].slice; 255.687 + try { 255.688 + if (slice.call(el.childNodes, 0)[0].nodeType == 1) { 255.689 + toArray = function(arrayLike) { 255.690 + return slice.call(arrayLike, 0); 255.691 + }; 255.692 + } 255.693 + } catch (e) {} 255.694 + } 255.695 + 255.696 + if (!toArray) { 255.697 + toArray = function(arrayLike) { 255.698 + var arr = []; 255.699 + for (var i = 0, len = arrayLike.length; i < len; ++i) { 255.700 + arr[i] = arrayLike[i]; 255.701 + } 255.702 + return arr; 255.703 + }; 255.704 + } 255.705 + 255.706 + util.toArray = toArray; 255.707 + })(); 255.708 + 255.709 + // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or 255.710 + // normalization of event properties 255.711 + var addListener; 255.712 + if (isBrowser) { 255.713 + if (isHostMethod(document, "addEventListener")) { 255.714 + addListener = function(obj, eventType, listener) { 255.715 + obj.addEventListener(eventType, listener, false); 255.716 + }; 255.717 + } else if (isHostMethod(document, "attachEvent")) { 255.718 + addListener = function(obj, eventType, listener) { 255.719 + obj.attachEvent("on" + eventType, listener); 255.720 + }; 255.721 + } else { 255.722 + fail("Document does not have required addEventListener or attachEvent method"); 255.723 + } 255.724 + 255.725 + util.addListener = addListener; 255.726 + } 255.727 + 255.728 + var initListeners = []; 255.729 + 255.730 + function getErrorDesc(ex) { 255.731 + return ex.message || ex.description || String(ex); 255.732 + } 255.733 + 255.734 + // Initialization 255.735 + function init() { 255.736 + if (!isBrowser || api.initialized) { 255.737 + return; 255.738 + } 255.739 + var testRange; 255.740 + var implementsDomRange = false, implementsTextRange = false; 255.741 + 255.742 + // First, perform basic feature tests 255.743 + 255.744 + if (isHostMethod(document, "createRange")) { 255.745 + testRange = document.createRange(); 255.746 + if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { 255.747 + implementsDomRange = true; 255.748 + } 255.749 + } 255.750 + 255.751 + var body = getBody(document); 255.752 + if (!body || body.nodeName.toLowerCase() != "body") { 255.753 + fail("No body element found"); 255.754 + return; 255.755 + } 255.756 + 255.757 + if (body && isHostMethod(body, "createTextRange")) { 255.758 + testRange = body.createTextRange(); 255.759 + if (isTextRange(testRange)) { 255.760 + implementsTextRange = true; 255.761 + } 255.762 + } 255.763 + 255.764 + if (!implementsDomRange && !implementsTextRange) { 255.765 + fail("Neither Range nor TextRange are available"); 255.766 + return; 255.767 + } 255.768 + 255.769 + api.initialized = true; 255.770 + api.features = { 255.771 + implementsDomRange: implementsDomRange, 255.772 + implementsTextRange: implementsTextRange 255.773 + }; 255.774 + 255.775 + // Initialize modules 255.776 + var module, errorMessage; 255.777 + for (var moduleName in modules) { 255.778 + if ( (module = modules[moduleName]) instanceof Module ) { 255.779 + module.init(module, api); 255.780 + } 255.781 + } 255.782 + 255.783 + // Call init listeners 255.784 + for (var i = 0, len = initListeners.length; i < len; ++i) { 255.785 + try { 255.786 + initListeners[i](api); 255.787 + } catch (ex) { 255.788 + errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex); 255.789 + consoleLog(errorMessage); 255.790 + } 255.791 + } 255.792 + } 255.793 + 255.794 + function deprecationNotice(deprecated, replacement, module) { 255.795 + if (module) { 255.796 + deprecated += " in module " + module.name; 255.797 + } 255.798 + api.warn("DEPRECATED: " + deprecated + " is deprecated. Please use " + 255.799 + replacement + " instead."); 255.800 + } 255.801 + 255.802 + function createAliasForDeprecatedMethod(owner, deprecated, replacement, module) { 255.803 + owner[deprecated] = function() { 255.804 + deprecationNotice(deprecated, replacement, module); 255.805 + return owner[replacement].apply(owner, util.toArray(arguments)); 255.806 + }; 255.807 + } 255.808 + 255.809 + util.deprecationNotice = deprecationNotice; 255.810 + util.createAliasForDeprecatedMethod = createAliasForDeprecatedMethod; 255.811 + 255.812 + // Allow external scripts to initialize this library in case it's loaded after the document has loaded 255.813 + api.init = init; 255.814 + 255.815 + // Execute listener immediately if already initialized 255.816 + api.addInitListener = function(listener) { 255.817 + if (api.initialized) { 255.818 + listener(api); 255.819 + } else { 255.820 + initListeners.push(listener); 255.821 + } 255.822 + }; 255.823 + 255.824 + var shimListeners = []; 255.825 + 255.826 + api.addShimListener = function(listener) { 255.827 + shimListeners.push(listener); 255.828 + }; 255.829 + 255.830 + function shim(win) { 255.831 + win = win || window; 255.832 + init(); 255.833 + 255.834 + // Notify listeners 255.835 + for (var i = 0, len = shimListeners.length; i < len; ++i) { 255.836 + shimListeners[i](win); 255.837 + } 255.838 + } 255.839 + 255.840 + if (isBrowser) { 255.841 + api.shim = api.createMissingNativeApi = shim; 255.842 + createAliasForDeprecatedMethod(api, "createMissingNativeApi", "shim"); 255.843 + } 255.844 + 255.845 + function Module(name, dependencies, initializer) { 255.846 + this.name = name; 255.847 + this.dependencies = dependencies; 255.848 + this.initialized = false; 255.849 + this.supported = false; 255.850 + this.initializer = initializer; 255.851 + } 255.852 + 255.853 + Module.prototype = { 255.854 + init: function() { 255.855 + var requiredModuleNames = this.dependencies || []; 255.856 + for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) { 255.857 + moduleName = requiredModuleNames[i]; 255.858 + 255.859 + requiredModule = modules[moduleName]; 255.860 + if (!requiredModule || !(requiredModule instanceof Module)) { 255.861 + throw new Error("required module '" + moduleName + "' not found"); 255.862 + } 255.863 + 255.864 + requiredModule.init(); 255.865 + 255.866 + if (!requiredModule.supported) { 255.867 + throw new Error("required module '" + moduleName + "' not supported"); 255.868 + } 255.869 + } 255.870 + 255.871 + // Now run initializer 255.872 + this.initializer(this); 255.873 + }, 255.874 + 255.875 + fail: function(reason) { 255.876 + this.initialized = true; 255.877 + this.supported = false; 255.878 + throw new Error(reason); 255.879 + }, 255.880 + 255.881 + warn: function(msg) { 255.882 + api.warn("Module " + this.name + ": " + msg); 255.883 + }, 255.884 + 255.885 + deprecationNotice: function(deprecated, replacement) { 255.886 + api.warn("DEPRECATED: " + deprecated + " in module " + this.name + " is deprecated. Please use " + 255.887 + replacement + " instead"); 255.888 + }, 255.889 + 255.890 + createError: function(msg) { 255.891 + return new Error("Error in Rangy " + this.name + " module: " + msg); 255.892 + } 255.893 + }; 255.894 + 255.895 + function createModule(name, dependencies, initFunc) { 255.896 + var newModule = new Module(name, dependencies, function(module) { 255.897 + if (!module.initialized) { 255.898 + module.initialized = true; 255.899 + try { 255.900 + initFunc(api, module); 255.901 + module.supported = true; 255.902 + } catch (ex) { 255.903 + var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex); 255.904 + consoleLog(errorMessage); 255.905 + if (ex.stack) { 255.906 + consoleLog(ex.stack); 255.907 + } 255.908 + } 255.909 + } 255.910 + }); 255.911 + modules[name] = newModule; 255.912 + return newModule; 255.913 + } 255.914 + 255.915 + api.createModule = function(name) { 255.916 + // Allow 2 or 3 arguments (second argument is an optional array of dependencies) 255.917 + var initFunc, dependencies; 255.918 + if (arguments.length == 2) { 255.919 + initFunc = arguments[1]; 255.920 + dependencies = []; 255.921 + } else { 255.922 + initFunc = arguments[2]; 255.923 + dependencies = arguments[1]; 255.924 + } 255.925 + 255.926 + var module = createModule(name, dependencies, initFunc); 255.927 + 255.928 + // Initialize the module immediately if the core is already initialized 255.929 + if (api.initialized && api.supported) { 255.930 + module.init(); 255.931 + } 255.932 + }; 255.933 + 255.934 + api.createCoreModule = function(name, dependencies, initFunc) { 255.935 + createModule(name, dependencies, initFunc); 255.936 + }; 255.937 + 255.938 + /*----------------------------------------------------------------------------------------------------------------*/ 255.939 + 255.940 + // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately 255.941 + 255.942 + function RangePrototype() {} 255.943 + api.RangePrototype = RangePrototype; 255.944 + api.rangePrototype = new RangePrototype(); 255.945 + 255.946 + function SelectionPrototype() {} 255.947 + api.selectionPrototype = new SelectionPrototype(); 255.948 + 255.949 + /*----------------------------------------------------------------------------------------------------------------*/ 255.950 + 255.951 + // DOM utility methods used by Rangy 255.952 + api.createCoreModule("DomUtil", [], function(api, module) { 255.953 + var UNDEF = "undefined"; 255.954 + var util = api.util; 255.955 + var getBody = util.getBody; 255.956 + 255.957 + // Perform feature tests 255.958 + if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { 255.959 + module.fail("document missing a Node creation method"); 255.960 + } 255.961 + 255.962 + if (!util.isHostMethod(document, "getElementsByTagName")) { 255.963 + module.fail("document missing getElementsByTagName method"); 255.964 + } 255.965 + 255.966 + var el = document.createElement("div"); 255.967 + if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || 255.968 + !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { 255.969 + module.fail("Incomplete Element implementation"); 255.970 + } 255.971 + 255.972 + // innerHTML is required for Range's createContextualFragment method 255.973 + if (!util.isHostProperty(el, "innerHTML")) { 255.974 + module.fail("Element is missing innerHTML property"); 255.975 + } 255.976 + 255.977 + var textNode = document.createTextNode("test"); 255.978 + if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || 255.979 + !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || 255.980 + !util.areHostProperties(textNode, ["data"]))) { 255.981 + module.fail("Incomplete Text Node implementation"); 255.982 + } 255.983 + 255.984 + /*----------------------------------------------------------------------------------------------------------------*/ 255.985 + 255.986 + // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been 255.987 + // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that 255.988 + // contains just the document as a single element and the value searched for is the document. 255.989 + var arrayContains = /*Array.prototype.indexOf ? 255.990 + function(arr, val) { 255.991 + return arr.indexOf(val) > -1; 255.992 + }:*/ 255.993 + 255.994 + function(arr, val) { 255.995 + var i = arr.length; 255.996 + while (i--) { 255.997 + if (arr[i] === val) { 255.998 + return true; 255.999 + } 255.1000 + } 255.1001 + return false; 255.1002 + }; 255.1003 + 255.1004 + // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI 255.1005 + function isHtmlNamespace(node) { 255.1006 + var ns; 255.1007 + return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); 255.1008 + } 255.1009 + 255.1010 + function parentElement(node) { 255.1011 + var parent = node.parentNode; 255.1012 + return (parent.nodeType == 1) ? parent : null; 255.1013 + } 255.1014 + 255.1015 + function getNodeIndex(node) { 255.1016 + var i = 0; 255.1017 + while( (node = node.previousSibling) ) { 255.1018 + ++i; 255.1019 + } 255.1020 + return i; 255.1021 + } 255.1022 + 255.1023 + function getNodeLength(node) { 255.1024 + switch (node.nodeType) { 255.1025 + case 7: 255.1026 + case 10: 255.1027 + return 0; 255.1028 + case 3: 255.1029 + case 8: 255.1030 + return node.length; 255.1031 + default: 255.1032 + return node.childNodes.length; 255.1033 + } 255.1034 + } 255.1035 + 255.1036 + function getCommonAncestor(node1, node2) { 255.1037 + var ancestors = [], n; 255.1038 + for (n = node1; n; n = n.parentNode) { 255.1039 + ancestors.push(n); 255.1040 + } 255.1041 + 255.1042 + for (n = node2; n; n = n.parentNode) { 255.1043 + if (arrayContains(ancestors, n)) { 255.1044 + return n; 255.1045 + } 255.1046 + } 255.1047 + 255.1048 + return null; 255.1049 + } 255.1050 + 255.1051 + function isAncestorOf(ancestor, descendant, selfIsAncestor) { 255.1052 + var n = selfIsAncestor ? descendant : descendant.parentNode; 255.1053 + while (n) { 255.1054 + if (n === ancestor) { 255.1055 + return true; 255.1056 + } else { 255.1057 + n = n.parentNode; 255.1058 + } 255.1059 + } 255.1060 + return false; 255.1061 + } 255.1062 + 255.1063 + function isOrIsAncestorOf(ancestor, descendant) { 255.1064 + return isAncestorOf(ancestor, descendant, true); 255.1065 + } 255.1066 + 255.1067 + function getClosestAncestorIn(node, ancestor, selfIsAncestor) { 255.1068 + var p, n = selfIsAncestor ? node : node.parentNode; 255.1069 + while (n) { 255.1070 + p = n.parentNode; 255.1071 + if (p === ancestor) { 255.1072 + return n; 255.1073 + } 255.1074 + n = p; 255.1075 + } 255.1076 + return null; 255.1077 + } 255.1078 + 255.1079 + function isCharacterDataNode(node) { 255.1080 + var t = node.nodeType; 255.1081 + return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment 255.1082 + } 255.1083 + 255.1084 + function isTextOrCommentNode(node) { 255.1085 + if (!node) { 255.1086 + return false; 255.1087 + } 255.1088 + var t = node.nodeType; 255.1089 + return t == 3 || t == 8 ; // Text or Comment 255.1090 + } 255.1091 + 255.1092 + function insertAfter(node, precedingNode) { 255.1093 + var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; 255.1094 + if (nextNode) { 255.1095 + parent.insertBefore(node, nextNode); 255.1096 + } else { 255.1097 + parent.appendChild(node); 255.1098 + } 255.1099 + return node; 255.1100 + } 255.1101 + 255.1102 + // Note that we cannot use splitText() because it is bugridden in IE 9. 255.1103 + function splitDataNode(node, index, positionsToPreserve) { 255.1104 + var newNode = node.cloneNode(false); 255.1105 + newNode.deleteData(0, index); 255.1106 + node.deleteData(index, node.length - index); 255.1107 + insertAfter(newNode, node); 255.1108 + 255.1109 + // Preserve positions 255.1110 + if (positionsToPreserve) { 255.1111 + for (var i = 0, position; position = positionsToPreserve[i++]; ) { 255.1112 + // Handle case where position was inside the portion of node after the split point 255.1113 + if (position.node == node && position.offset > index) { 255.1114 + position.node = newNode; 255.1115 + position.offset -= index; 255.1116 + } 255.1117 + // Handle the case where the position is a node offset within node's parent 255.1118 + else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) { 255.1119 + ++position.offset; 255.1120 + } 255.1121 + } 255.1122 + } 255.1123 + return newNode; 255.1124 + } 255.1125 + 255.1126 + function getDocument(node) { 255.1127 + if (node.nodeType == 9) { 255.1128 + return node; 255.1129 + } else if (typeof node.ownerDocument != UNDEF) { 255.1130 + return node.ownerDocument; 255.1131 + } else if (typeof node.document != UNDEF) { 255.1132 + return node.document; 255.1133 + } else if (node.parentNode) { 255.1134 + return getDocument(node.parentNode); 255.1135 + } else { 255.1136 + throw module.createError("getDocument: no document found for node"); 255.1137 + } 255.1138 + } 255.1139 + 255.1140 + function getWindow(node) { 255.1141 + var doc = getDocument(node); 255.1142 + if (typeof doc.defaultView != UNDEF) { 255.1143 + return doc.defaultView; 255.1144 + } else if (typeof doc.parentWindow != UNDEF) { 255.1145 + return doc.parentWindow; 255.1146 + } else { 255.1147 + throw module.createError("Cannot get a window object for node"); 255.1148 + } 255.1149 + } 255.1150 + 255.1151 + function getIframeDocument(iframeEl) { 255.1152 + if (typeof iframeEl.contentDocument != UNDEF) { 255.1153 + return iframeEl.contentDocument; 255.1154 + } else if (typeof iframeEl.contentWindow != UNDEF) { 255.1155 + return iframeEl.contentWindow.document; 255.1156 + } else { 255.1157 + throw module.createError("getIframeDocument: No Document object found for iframe element"); 255.1158 + } 255.1159 + } 255.1160 + 255.1161 + function getIframeWindow(iframeEl) { 255.1162 + if (typeof iframeEl.contentWindow != UNDEF) { 255.1163 + return iframeEl.contentWindow; 255.1164 + } else if (typeof iframeEl.contentDocument != UNDEF) { 255.1165 + return iframeEl.contentDocument.defaultView; 255.1166 + } else { 255.1167 + throw module.createError("getIframeWindow: No Window object found for iframe element"); 255.1168 + } 255.1169 + } 255.1170 + 255.1171 + // This looks bad. Is it worth it? 255.1172 + function isWindow(obj) { 255.1173 + return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document"); 255.1174 + } 255.1175 + 255.1176 + function getContentDocument(obj, module, methodName) { 255.1177 + var doc; 255.1178 + 255.1179 + if (!obj) { 255.1180 + doc = document; 255.1181 + } 255.1182 + 255.1183 + // Test if a DOM node has been passed and obtain a document object for it if so 255.1184 + else if (util.isHostProperty(obj, "nodeType")) { 255.1185 + doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") ? 255.1186 + getIframeDocument(obj) : getDocument(obj); 255.1187 + } 255.1188 + 255.1189 + // Test if the doc parameter appears to be a Window object 255.1190 + else if (isWindow(obj)) { 255.1191 + doc = obj.document; 255.1192 + } 255.1193 + 255.1194 + if (!doc) { 255.1195 + throw module.createError(methodName + "(): Parameter must be a Window object or DOM node"); 255.1196 + } 255.1197 + 255.1198 + return doc; 255.1199 + } 255.1200 + 255.1201 + function getRootContainer(node) { 255.1202 + var parent; 255.1203 + while ( (parent = node.parentNode) ) { 255.1204 + node = parent; 255.1205 + } 255.1206 + return node; 255.1207 + } 255.1208 + 255.1209 + function comparePoints(nodeA, offsetA, nodeB, offsetB) { 255.1210 + // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing 255.1211 + var nodeC, root, childA, childB, n; 255.1212 + if (nodeA == nodeB) { 255.1213 + // Case 1: nodes are the same 255.1214 + return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; 255.1215 + } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { 255.1216 + // Case 2: node C (container B or an ancestor) is a child node of A 255.1217 + return offsetA <= getNodeIndex(nodeC) ? -1 : 1; 255.1218 + } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { 255.1219 + // Case 3: node C (container A or an ancestor) is a child node of B 255.1220 + return getNodeIndex(nodeC) < offsetB ? -1 : 1; 255.1221 + } else { 255.1222 + root = getCommonAncestor(nodeA, nodeB); 255.1223 + if (!root) { 255.1224 + throw new Error("comparePoints error: nodes have no common ancestor"); 255.1225 + } 255.1226 + 255.1227 + // Case 4: containers are siblings or descendants of siblings 255.1228 + childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); 255.1229 + childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); 255.1230 + 255.1231 + if (childA === childB) { 255.1232 + // This shouldn't be possible 255.1233 + throw module.createError("comparePoints got to case 4 and childA and childB are the same!"); 255.1234 + } else { 255.1235 + n = root.firstChild; 255.1236 + while (n) { 255.1237 + if (n === childA) { 255.1238 + return -1; 255.1239 + } else if (n === childB) { 255.1240 + return 1; 255.1241 + } 255.1242 + n = n.nextSibling; 255.1243 + } 255.1244 + } 255.1245 + } 255.1246 + } 255.1247 + 255.1248 + /*----------------------------------------------------------------------------------------------------------------*/ 255.1249 + 255.1250 + // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried 255.1251 + var crashyTextNodes = false; 255.1252 + 255.1253 + function isBrokenNode(node) { 255.1254 + var n; 255.1255 + try { 255.1256 + n = node.parentNode; 255.1257 + return false; 255.1258 + } catch (e) { 255.1259 + return true; 255.1260 + } 255.1261 + } 255.1262 + 255.1263 + (function() { 255.1264 + var el = document.createElement("b"); 255.1265 + el.innerHTML = "1"; 255.1266 + var textNode = el.firstChild; 255.1267 + el.innerHTML = "<br />"; 255.1268 + crashyTextNodes = isBrokenNode(textNode); 255.1269 + 255.1270 + api.features.crashyTextNodes = crashyTextNodes; 255.1271 + })(); 255.1272 + 255.1273 + /*----------------------------------------------------------------------------------------------------------------*/ 255.1274 + 255.1275 + function inspectNode(node) { 255.1276 + if (!node) { 255.1277 + return "[No node]"; 255.1278 + } 255.1279 + if (crashyTextNodes && isBrokenNode(node)) { 255.1280 + return "[Broken node]"; 255.1281 + } 255.1282 + if (isCharacterDataNode(node)) { 255.1283 + return '"' + node.data + '"'; 255.1284 + } 255.1285 + if (node.nodeType == 1) { 255.1286 + var idAttr = node.id ? ' id="' + node.id + '"' : ""; 255.1287 + return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]"; 255.1288 + } 255.1289 + return node.nodeName; 255.1290 + } 255.1291 + 255.1292 + function fragmentFromNodeChildren(node) { 255.1293 + var fragment = getDocument(node).createDocumentFragment(), child; 255.1294 + while ( (child = node.firstChild) ) { 255.1295 + fragment.appendChild(child); 255.1296 + } 255.1297 + return fragment; 255.1298 + } 255.1299 + 255.1300 + var getComputedStyleProperty; 255.1301 + if (typeof window.getComputedStyle != UNDEF) { 255.1302 + getComputedStyleProperty = function(el, propName) { 255.1303 + return getWindow(el).getComputedStyle(el, null)[propName]; 255.1304 + }; 255.1305 + } else if (typeof document.documentElement.currentStyle != UNDEF) { 255.1306 + getComputedStyleProperty = function(el, propName) { 255.1307 + return el.currentStyle ? el.currentStyle[propName] : ""; 255.1308 + }; 255.1309 + } else { 255.1310 + module.fail("No means of obtaining computed style properties found"); 255.1311 + } 255.1312 + 255.1313 + function createTestElement(doc, html, contentEditable) { 255.1314 + var body = getBody(doc); 255.1315 + var el = doc.createElement("div"); 255.1316 + el.contentEditable = "" + !!contentEditable; 255.1317 + if (html) { 255.1318 + el.innerHTML = html; 255.1319 + } 255.1320 + 255.1321 + // Insert the test element at the start of the body to prevent scrolling to the bottom in iOS (issue #292) 255.1322 + var bodyFirstChild = body.firstChild; 255.1323 + if (bodyFirstChild) { 255.1324 + body.insertBefore(el, bodyFirstChild); 255.1325 + } else { 255.1326 + body.appendChild(el); 255.1327 + } 255.1328 + 255.1329 + return el; 255.1330 + } 255.1331 + 255.1332 + function removeNode(node) { 255.1333 + return node.parentNode.removeChild(node); 255.1334 + } 255.1335 + 255.1336 + function NodeIterator(root) { 255.1337 + this.root = root; 255.1338 + this._next = root; 255.1339 + } 255.1340 + 255.1341 + NodeIterator.prototype = { 255.1342 + _current: null, 255.1343 + 255.1344 + hasNext: function() { 255.1345 + return !!this._next; 255.1346 + }, 255.1347 + 255.1348 + next: function() { 255.1349 + var n = this._current = this._next; 255.1350 + var child, next; 255.1351 + if (this._current) { 255.1352 + child = n.firstChild; 255.1353 + if (child) { 255.1354 + this._next = child; 255.1355 + } else { 255.1356 + next = null; 255.1357 + while ((n !== this.root) && !(next = n.nextSibling)) { 255.1358 + n = n.parentNode; 255.1359 + } 255.1360 + this._next = next; 255.1361 + } 255.1362 + } 255.1363 + return this._current; 255.1364 + }, 255.1365 + 255.1366 + detach: function() { 255.1367 + this._current = this._next = this.root = null; 255.1368 + } 255.1369 + }; 255.1370 + 255.1371 + function createIterator(root) { 255.1372 + return new NodeIterator(root); 255.1373 + } 255.1374 + 255.1375 + function DomPosition(node, offset) { 255.1376 + this.node = node; 255.1377 + this.offset = offset; 255.1378 + } 255.1379 + 255.1380 + DomPosition.prototype = { 255.1381 + equals: function(pos) { 255.1382 + return !!pos && this.node === pos.node && this.offset == pos.offset; 255.1383 + }, 255.1384 + 255.1385 + inspect: function() { 255.1386 + return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; 255.1387 + }, 255.1388 + 255.1389 + toString: function() { 255.1390 + return this.inspect(); 255.1391 + } 255.1392 + }; 255.1393 + 255.1394 + function DOMException(codeName) { 255.1395 + this.code = this[codeName]; 255.1396 + this.codeName = codeName; 255.1397 + this.message = "DOMException: " + this.codeName; 255.1398 + } 255.1399 + 255.1400 + DOMException.prototype = { 255.1401 + INDEX_SIZE_ERR: 1, 255.1402 + HIERARCHY_REQUEST_ERR: 3, 255.1403 + WRONG_DOCUMENT_ERR: 4, 255.1404 + NO_MODIFICATION_ALLOWED_ERR: 7, 255.1405 + NOT_FOUND_ERR: 8, 255.1406 + NOT_SUPPORTED_ERR: 9, 255.1407 + INVALID_STATE_ERR: 11, 255.1408 + INVALID_NODE_TYPE_ERR: 24 255.1409 + }; 255.1410 + 255.1411 + DOMException.prototype.toString = function() { 255.1412 + return this.message; 255.1413 + }; 255.1414 + 255.1415 + api.dom = { 255.1416 + arrayContains: arrayContains, 255.1417 + isHtmlNamespace: isHtmlNamespace, 255.1418 + parentElement: parentElement, 255.1419 + getNodeIndex: getNodeIndex, 255.1420 + getNodeLength: getNodeLength, 255.1421 + getCommonAncestor: getCommonAncestor, 255.1422 + isAncestorOf: isAncestorOf, 255.1423 + isOrIsAncestorOf: isOrIsAncestorOf, 255.1424 + getClosestAncestorIn: getClosestAncestorIn, 255.1425 + isCharacterDataNode: isCharacterDataNode, 255.1426 + isTextOrCommentNode: isTextOrCommentNode, 255.1427 + insertAfter: insertAfter, 255.1428 + splitDataNode: splitDataNode, 255.1429 + getDocument: getDocument, 255.1430 + getWindow: getWindow, 255.1431 + getIframeWindow: getIframeWindow, 255.1432 + getIframeDocument: getIframeDocument, 255.1433 + getBody: getBody, 255.1434 + isWindow: isWindow, 255.1435 + getContentDocument: getContentDocument, 255.1436 + getRootContainer: getRootContainer, 255.1437 + comparePoints: comparePoints, 255.1438 + isBrokenNode: isBrokenNode, 255.1439 + inspectNode: inspectNode, 255.1440 + getComputedStyleProperty: getComputedStyleProperty, 255.1441 + createTestElement: createTestElement, 255.1442 + removeNode: removeNode, 255.1443 + fragmentFromNodeChildren: fragmentFromNodeChildren, 255.1444 + createIterator: createIterator, 255.1445 + DomPosition: DomPosition 255.1446 + }; 255.1447 + 255.1448 + api.DOMException = DOMException; 255.1449 + }); 255.1450 + 255.1451 + /*----------------------------------------------------------------------------------------------------------------*/ 255.1452 + 255.1453 + // Pure JavaScript implementation of DOM Range 255.1454 + api.createCoreModule("DomRange", ["DomUtil"], function(api, module) { 255.1455 + var dom = api.dom; 255.1456 + var util = api.util; 255.1457 + var DomPosition = dom.DomPosition; 255.1458 + var DOMException = api.DOMException; 255.1459 + 255.1460 + var isCharacterDataNode = dom.isCharacterDataNode; 255.1461 + var getNodeIndex = dom.getNodeIndex; 255.1462 + var isOrIsAncestorOf = dom.isOrIsAncestorOf; 255.1463 + var getDocument = dom.getDocument; 255.1464 + var comparePoints = dom.comparePoints; 255.1465 + var splitDataNode = dom.splitDataNode; 255.1466 + var getClosestAncestorIn = dom.getClosestAncestorIn; 255.1467 + var getNodeLength = dom.getNodeLength; 255.1468 + var arrayContains = dom.arrayContains; 255.1469 + var getRootContainer = dom.getRootContainer; 255.1470 + var crashyTextNodes = api.features.crashyTextNodes; 255.1471 + 255.1472 + var removeNode = dom.removeNode; 255.1473 + 255.1474 + /*----------------------------------------------------------------------------------------------------------------*/ 255.1475 + 255.1476 + // Utility functions 255.1477 + 255.1478 + function isNonTextPartiallySelected(node, range) { 255.1479 + return (node.nodeType != 3) && 255.1480 + (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer)); 255.1481 + } 255.1482 + 255.1483 + function getRangeDocument(range) { 255.1484 + return range.document || getDocument(range.startContainer); 255.1485 + } 255.1486 + 255.1487 + function getRangeRoot(range) { 255.1488 + return getRootContainer(range.startContainer); 255.1489 + } 255.1490 + 255.1491 + function getBoundaryBeforeNode(node) { 255.1492 + return new DomPosition(node.parentNode, getNodeIndex(node)); 255.1493 + } 255.1494 + 255.1495 + function getBoundaryAfterNode(node) { 255.1496 + return new DomPosition(node.parentNode, getNodeIndex(node) + 1); 255.1497 + } 255.1498 + 255.1499 + function insertNodeAtPosition(node, n, o) { 255.1500 + var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; 255.1501 + if (isCharacterDataNode(n)) { 255.1502 + if (o == n.length) { 255.1503 + dom.insertAfter(node, n); 255.1504 + } else { 255.1505 + n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o)); 255.1506 + } 255.1507 + } else if (o >= n.childNodes.length) { 255.1508 + n.appendChild(node); 255.1509 + } else { 255.1510 + n.insertBefore(node, n.childNodes[o]); 255.1511 + } 255.1512 + return firstNodeInserted; 255.1513 + } 255.1514 + 255.1515 + function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) { 255.1516 + assertRangeValid(rangeA); 255.1517 + assertRangeValid(rangeB); 255.1518 + 255.1519 + if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) { 255.1520 + throw new DOMException("WRONG_DOCUMENT_ERR"); 255.1521 + } 255.1522 + 255.1523 + var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset), 255.1524 + endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset); 255.1525 + 255.1526 + return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; 255.1527 + } 255.1528 + 255.1529 + function cloneSubtree(iterator) { 255.1530 + var partiallySelected; 255.1531 + for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { 255.1532 + partiallySelected = iterator.isPartiallySelectedSubtree(); 255.1533 + node = node.cloneNode(!partiallySelected); 255.1534 + if (partiallySelected) { 255.1535 + subIterator = iterator.getSubtreeIterator(); 255.1536 + node.appendChild(cloneSubtree(subIterator)); 255.1537 + subIterator.detach(); 255.1538 + } 255.1539 + 255.1540 + if (node.nodeType == 10) { // DocumentType 255.1541 + throw new DOMException("HIERARCHY_REQUEST_ERR"); 255.1542 + } 255.1543 + frag.appendChild(node); 255.1544 + } 255.1545 + return frag; 255.1546 + } 255.1547 + 255.1548 + function iterateSubtree(rangeIterator, func, iteratorState) { 255.1549 + var it, n; 255.1550 + iteratorState = iteratorState || { stop: false }; 255.1551 + for (var node, subRangeIterator; node = rangeIterator.next(); ) { 255.1552 + if (rangeIterator.isPartiallySelectedSubtree()) { 255.1553 + if (func(node) === false) { 255.1554 + iteratorState.stop = true; 255.1555 + return; 255.1556 + } else { 255.1557 + // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of 255.1558 + // the node selected by the Range. 255.1559 + subRangeIterator = rangeIterator.getSubtreeIterator(); 255.1560 + iterateSubtree(subRangeIterator, func, iteratorState); 255.1561 + subRangeIterator.detach(); 255.1562 + if (iteratorState.stop) { 255.1563 + return; 255.1564 + } 255.1565 + } 255.1566 + } else { 255.1567 + // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its 255.1568 + // descendants 255.1569 + it = dom.createIterator(node); 255.1570 + while ( (n = it.next()) ) { 255.1571 + if (func(n) === false) { 255.1572 + iteratorState.stop = true; 255.1573 + return; 255.1574 + } 255.1575 + } 255.1576 + } 255.1577 + } 255.1578 + } 255.1579 + 255.1580 + function deleteSubtree(iterator) { 255.1581 + var subIterator; 255.1582 + while (iterator.next()) { 255.1583 + if (iterator.isPartiallySelectedSubtree()) { 255.1584 + subIterator = iterator.getSubtreeIterator(); 255.1585 + deleteSubtree(subIterator); 255.1586 + subIterator.detach(); 255.1587 + } else { 255.1588 + iterator.remove(); 255.1589 + } 255.1590 + } 255.1591 + } 255.1592 + 255.1593 + function extractSubtree(iterator) { 255.1594 + for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { 255.1595 + 255.1596 + if (iterator.isPartiallySelectedSubtree()) { 255.1597 + node = node.cloneNode(false); 255.1598 + subIterator = iterator.getSubtreeIterator(); 255.1599 + node.appendChild(extractSubtree(subIterator)); 255.1600 + subIterator.detach(); 255.1601 + } else { 255.1602 + iterator.remove(); 255.1603 + } 255.1604 + if (node.nodeType == 10) { // DocumentType 255.1605 + throw new DOMException("HIERARCHY_REQUEST_ERR"); 255.1606 + } 255.1607 + frag.appendChild(node); 255.1608 + } 255.1609 + return frag; 255.1610 + } 255.1611 + 255.1612 + function getNodesInRange(range, nodeTypes, filter) { 255.1613 + var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; 255.1614 + var filterExists = !!filter; 255.1615 + if (filterNodeTypes) { 255.1616 + regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); 255.1617 + } 255.1618 + 255.1619 + var nodes = []; 255.1620 + iterateSubtree(new RangeIterator(range, false), function(node) { 255.1621 + if (filterNodeTypes && !regex.test(node.nodeType)) { 255.1622 + return; 255.1623 + } 255.1624 + if (filterExists && !filter(node)) { 255.1625 + return; 255.1626 + } 255.1627 + // Don't include a boundary container if it is a character data node and the range does not contain any 255.1628 + // of its character data. See issue 190. 255.1629 + var sc = range.startContainer; 255.1630 + if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) { 255.1631 + return; 255.1632 + } 255.1633 + 255.1634 + var ec = range.endContainer; 255.1635 + if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) { 255.1636 + return; 255.1637 + } 255.1638 + 255.1639 + nodes.push(node); 255.1640 + }); 255.1641 + return nodes; 255.1642 + } 255.1643 + 255.1644 + function inspect(range) { 255.1645 + var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); 255.1646 + return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + 255.1647 + dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; 255.1648 + } 255.1649 + 255.1650 + /*----------------------------------------------------------------------------------------------------------------*/ 255.1651 + 255.1652 + // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) 255.1653 + 255.1654 + function RangeIterator(range, clonePartiallySelectedTextNodes) { 255.1655 + this.range = range; 255.1656 + this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; 255.1657 + 255.1658 + 255.1659 + if (!range.collapsed) { 255.1660 + this.sc = range.startContainer; 255.1661 + this.so = range.startOffset; 255.1662 + this.ec = range.endContainer; 255.1663 + this.eo = range.endOffset; 255.1664 + var root = range.commonAncestorContainer; 255.1665 + 255.1666 + if (this.sc === this.ec && isCharacterDataNode(this.sc)) { 255.1667 + this.isSingleCharacterDataNode = true; 255.1668 + this._first = this._last = this._next = this.sc; 255.1669 + } else { 255.1670 + this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ? 255.1671 + this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true); 255.1672 + this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ? 255.1673 + this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true); 255.1674 + } 255.1675 + } 255.1676 + } 255.1677 + 255.1678 + RangeIterator.prototype = { 255.1679 + _current: null, 255.1680 + _next: null, 255.1681 + _first: null, 255.1682 + _last: null, 255.1683 + isSingleCharacterDataNode: false, 255.1684 + 255.1685 + reset: function() { 255.1686 + this._current = null; 255.1687 + this._next = this._first; 255.1688 + }, 255.1689 + 255.1690 + hasNext: function() { 255.1691 + return !!this._next; 255.1692 + }, 255.1693 + 255.1694 + next: function() { 255.1695 + // Move to next node 255.1696 + var current = this._current = this._next; 255.1697 + if (current) { 255.1698 + this._next = (current !== this._last) ? current.nextSibling : null; 255.1699 + 255.1700 + // Check for partially selected text nodes 255.1701 + if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { 255.1702 + if (current === this.ec) { 255.1703 + (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); 255.1704 + } 255.1705 + if (this._current === this.sc) { 255.1706 + (current = current.cloneNode(true)).deleteData(0, this.so); 255.1707 + } 255.1708 + } 255.1709 + } 255.1710 + 255.1711 + return current; 255.1712 + }, 255.1713 + 255.1714 + remove: function() { 255.1715 + var current = this._current, start, end; 255.1716 + 255.1717 + if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { 255.1718 + start = (current === this.sc) ? this.so : 0; 255.1719 + end = (current === this.ec) ? this.eo : current.length; 255.1720 + if (start != end) { 255.1721 + current.deleteData(start, end - start); 255.1722 + } 255.1723 + } else { 255.1724 + if (current.parentNode) { 255.1725 + removeNode(current); 255.1726 + } else { 255.1727 + } 255.1728 + } 255.1729 + }, 255.1730 + 255.1731 + // Checks if the current node is partially selected 255.1732 + isPartiallySelectedSubtree: function() { 255.1733 + var current = this._current; 255.1734 + return isNonTextPartiallySelected(current, this.range); 255.1735 + }, 255.1736 + 255.1737 + getSubtreeIterator: function() { 255.1738 + var subRange; 255.1739 + if (this.isSingleCharacterDataNode) { 255.1740 + subRange = this.range.cloneRange(); 255.1741 + subRange.collapse(false); 255.1742 + } else { 255.1743 + subRange = new Range(getRangeDocument(this.range)); 255.1744 + var current = this._current; 255.1745 + var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current); 255.1746 + 255.1747 + if (isOrIsAncestorOf(current, this.sc)) { 255.1748 + startContainer = this.sc; 255.1749 + startOffset = this.so; 255.1750 + } 255.1751 + if (isOrIsAncestorOf(current, this.ec)) { 255.1752 + endContainer = this.ec; 255.1753 + endOffset = this.eo; 255.1754 + } 255.1755 + 255.1756 + updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); 255.1757 + } 255.1758 + return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); 255.1759 + }, 255.1760 + 255.1761 + detach: function() { 255.1762 + this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; 255.1763 + } 255.1764 + }; 255.1765 + 255.1766 + /*----------------------------------------------------------------------------------------------------------------*/ 255.1767 + 255.1768 + var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; 255.1769 + var rootContainerNodeTypes = [2, 9, 11]; 255.1770 + var readonlyNodeTypes = [5, 6, 10, 12]; 255.1771 + var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; 255.1772 + var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; 255.1773 + 255.1774 + function createAncestorFinder(nodeTypes) { 255.1775 + return function(node, selfIsAncestor) { 255.1776 + var t, n = selfIsAncestor ? node : node.parentNode; 255.1777 + while (n) { 255.1778 + t = n.nodeType; 255.1779 + if (arrayContains(nodeTypes, t)) { 255.1780 + return n; 255.1781 + } 255.1782 + n = n.parentNode; 255.1783 + } 255.1784 + return null; 255.1785 + }; 255.1786 + } 255.1787 + 255.1788 + var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); 255.1789 + var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); 255.1790 + var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); 255.1791 + 255.1792 + function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { 255.1793 + if (getDocTypeNotationEntityAncestor(node, allowSelf)) { 255.1794 + throw new DOMException("INVALID_NODE_TYPE_ERR"); 255.1795 + } 255.1796 + } 255.1797 + 255.1798 + function assertValidNodeType(node, invalidTypes) { 255.1799 + if (!arrayContains(invalidTypes, node.nodeType)) { 255.1800 + throw new DOMException("INVALID_NODE_TYPE_ERR"); 255.1801 + } 255.1802 + } 255.1803 + 255.1804 + function assertValidOffset(node, offset) { 255.1805 + if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) { 255.1806 + throw new DOMException("INDEX_SIZE_ERR"); 255.1807 + } 255.1808 + } 255.1809 + 255.1810 + function assertSameDocumentOrFragment(node1, node2) { 255.1811 + if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { 255.1812 + throw new DOMException("WRONG_DOCUMENT_ERR"); 255.1813 + } 255.1814 + } 255.1815 + 255.1816 + function assertNodeNotReadOnly(node) { 255.1817 + if (getReadonlyAncestor(node, true)) { 255.1818 + throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); 255.1819 + } 255.1820 + } 255.1821 + 255.1822 + function assertNode(node, codeName) { 255.1823 + if (!node) { 255.1824 + throw new DOMException(codeName); 255.1825 + } 255.1826 + } 255.1827 + 255.1828 + function isValidOffset(node, offset) { 255.1829 + return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length); 255.1830 + } 255.1831 + 255.1832 + function isRangeValid(range) { 255.1833 + return (!!range.startContainer && !!range.endContainer && 255.1834 + !(crashyTextNodes && (dom.isBrokenNode(range.startContainer) || dom.isBrokenNode(range.endContainer))) && 255.1835 + getRootContainer(range.startContainer) == getRootContainer(range.endContainer) && 255.1836 + isValidOffset(range.startContainer, range.startOffset) && 255.1837 + isValidOffset(range.endContainer, range.endOffset)); 255.1838 + } 255.1839 + 255.1840 + function assertRangeValid(range) { 255.1841 + if (!isRangeValid(range)) { 255.1842 + throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: (" + range.inspect() + ")"); 255.1843 + } 255.1844 + } 255.1845 + 255.1846 + /*----------------------------------------------------------------------------------------------------------------*/ 255.1847 + 255.1848 + // Test the browser's innerHTML support to decide how to implement createContextualFragment 255.1849 + var styleEl = document.createElement("style"); 255.1850 + var htmlParsingConforms = false; 255.1851 + try { 255.1852 + styleEl.innerHTML = "<b>x</b>"; 255.1853 + htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node 255.1854 + } catch (e) { 255.1855 + // IE 6 and 7 throw 255.1856 + } 255.1857 + 255.1858 + api.features.htmlParsingConforms = htmlParsingConforms; 255.1859 + 255.1860 + var createContextualFragment = htmlParsingConforms ? 255.1861 + 255.1862 + // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See 255.1863 + // discussion and base code for this implementation at issue 67. 255.1864 + // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface 255.1865 + // Thanks to Aleks Williams. 255.1866 + function(fragmentStr) { 255.1867 + // "Let node the context object's start's node." 255.1868 + var node = this.startContainer; 255.1869 + var doc = getDocument(node); 255.1870 + 255.1871 + // "If the context object's start's node is null, raise an INVALID_STATE_ERR 255.1872 + // exception and abort these steps." 255.1873 + if (!node) { 255.1874 + throw new DOMException("INVALID_STATE_ERR"); 255.1875 + } 255.1876 + 255.1877 + // "Let element be as follows, depending on node's interface:" 255.1878 + // Document, Document Fragment: null 255.1879 + var el = null; 255.1880 + 255.1881 + // "Element: node" 255.1882 + if (node.nodeType == 1) { 255.1883 + el = node; 255.1884 + 255.1885 + // "Text, Comment: node's parentElement" 255.1886 + } else if (isCharacterDataNode(node)) { 255.1887 + el = dom.parentElement(node); 255.1888 + } 255.1889 + 255.1890 + // "If either element is null or element's ownerDocument is an HTML document 255.1891 + // and element's local name is "html" and element's namespace is the HTML 255.1892 + // namespace" 255.1893 + if (el === null || ( 255.1894 + el.nodeName == "HTML" && 255.1895 + dom.isHtmlNamespace(getDocument(el).documentElement) && 255.1896 + dom.isHtmlNamespace(el) 255.1897 + )) { 255.1898 + 255.1899 + // "let element be a new Element with "body" as its local name and the HTML 255.1900 + // namespace as its namespace."" 255.1901 + el = doc.createElement("body"); 255.1902 + } else { 255.1903 + el = el.cloneNode(false); 255.1904 + } 255.1905 + 255.1906 + // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." 255.1907 + // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." 255.1908 + // "In either case, the algorithm must be invoked with fragment as the input 255.1909 + // and element as the context element." 255.1910 + el.innerHTML = fragmentStr; 255.1911 + 255.1912 + // "If this raises an exception, then abort these steps. Otherwise, let new 255.1913 + // children be the nodes returned." 255.1914 + 255.1915 + // "Let fragment be a new DocumentFragment." 255.1916 + // "Append all new children to fragment." 255.1917 + // "Return fragment." 255.1918 + return dom.fragmentFromNodeChildren(el); 255.1919 + } : 255.1920 + 255.1921 + // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that 255.1922 + // previous versions of Rangy used (with the exception of using a body element rather than a div) 255.1923 + function(fragmentStr) { 255.1924 + var doc = getRangeDocument(this); 255.1925 + var el = doc.createElement("body"); 255.1926 + el.innerHTML = fragmentStr; 255.1927 + 255.1928 + return dom.fragmentFromNodeChildren(el); 255.1929 + }; 255.1930 + 255.1931 + function splitRangeBoundaries(range, positionsToPreserve) { 255.1932 + assertRangeValid(range); 255.1933 + 255.1934 + var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset; 255.1935 + var startEndSame = (sc === ec); 255.1936 + 255.1937 + if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { 255.1938 + splitDataNode(ec, eo, positionsToPreserve); 255.1939 + } 255.1940 + 255.1941 + if (isCharacterDataNode(sc) && so > 0 && so < sc.length) { 255.1942 + sc = splitDataNode(sc, so, positionsToPreserve); 255.1943 + if (startEndSame) { 255.1944 + eo -= so; 255.1945 + ec = sc; 255.1946 + } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) { 255.1947 + eo++; 255.1948 + } 255.1949 + so = 0; 255.1950 + } 255.1951 + range.setStartAndEnd(sc, so, ec, eo); 255.1952 + } 255.1953 + 255.1954 + function rangeToHtml(range) { 255.1955 + assertRangeValid(range); 255.1956 + var container = range.commonAncestorContainer.parentNode.cloneNode(false); 255.1957 + container.appendChild( range.cloneContents() ); 255.1958 + return container.innerHTML; 255.1959 + } 255.1960 + 255.1961 + /*----------------------------------------------------------------------------------------------------------------*/ 255.1962 + 255.1963 + var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", 255.1964 + "commonAncestorContainer"]; 255.1965 + 255.1966 + var s2s = 0, s2e = 1, e2e = 2, e2s = 3; 255.1967 + var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; 255.1968 + 255.1969 + util.extend(api.rangePrototype, { 255.1970 + compareBoundaryPoints: function(how, range) { 255.1971 + assertRangeValid(this); 255.1972 + assertSameDocumentOrFragment(this.startContainer, range.startContainer); 255.1973 + 255.1974 + var nodeA, offsetA, nodeB, offsetB; 255.1975 + var prefixA = (how == e2s || how == s2s) ? "start" : "end"; 255.1976 + var prefixB = (how == s2e || how == s2s) ? "start" : "end"; 255.1977 + nodeA = this[prefixA + "Container"]; 255.1978 + offsetA = this[prefixA + "Offset"]; 255.1979 + nodeB = range[prefixB + "Container"]; 255.1980 + offsetB = range[prefixB + "Offset"]; 255.1981 + return comparePoints(nodeA, offsetA, nodeB, offsetB); 255.1982 + }, 255.1983 + 255.1984 + insertNode: function(node) { 255.1985 + assertRangeValid(this); 255.1986 + assertValidNodeType(node, insertableNodeTypes); 255.1987 + assertNodeNotReadOnly(this.startContainer); 255.1988 + 255.1989 + if (isOrIsAncestorOf(node, this.startContainer)) { 255.1990 + throw new DOMException("HIERARCHY_REQUEST_ERR"); 255.1991 + } 255.1992 + 255.1993 + // No check for whether the container of the start of the Range is of a type that does not allow 255.1994 + // children of the type of node: the browser's DOM implementation should do this for us when we attempt 255.1995 + // to add the node 255.1996 + 255.1997 + var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); 255.1998 + this.setStartBefore(firstNodeInserted); 255.1999 + }, 255.2000 + 255.2001 + cloneContents: function() { 255.2002 + assertRangeValid(this); 255.2003 + 255.2004 + var clone, frag; 255.2005 + if (this.collapsed) { 255.2006 + return getRangeDocument(this).createDocumentFragment(); 255.2007 + } else { 255.2008 + if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) { 255.2009 + clone = this.startContainer.cloneNode(true); 255.2010 + clone.data = clone.data.slice(this.startOffset, this.endOffset); 255.2011 + frag = getRangeDocument(this).createDocumentFragment(); 255.2012 + frag.appendChild(clone); 255.2013 + return frag; 255.2014 + } else { 255.2015 + var iterator = new RangeIterator(this, true); 255.2016 + clone = cloneSubtree(iterator); 255.2017 + iterator.detach(); 255.2018 + } 255.2019 + return clone; 255.2020 + } 255.2021 + }, 255.2022 + 255.2023 + canSurroundContents: function() { 255.2024 + assertRangeValid(this); 255.2025 + assertNodeNotReadOnly(this.startContainer); 255.2026 + assertNodeNotReadOnly(this.endContainer); 255.2027 + 255.2028 + // Check if the contents can be surrounded. Specifically, this means whether the range partially selects 255.2029 + // no non-text nodes. 255.2030 + var iterator = new RangeIterator(this, true); 255.2031 + var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || 255.2032 + (iterator._last && isNonTextPartiallySelected(iterator._last, this))); 255.2033 + iterator.detach(); 255.2034 + return !boundariesInvalid; 255.2035 + }, 255.2036 + 255.2037 + surroundContents: function(node) { 255.2038 + assertValidNodeType(node, surroundNodeTypes); 255.2039 + 255.2040 + if (!this.canSurroundContents()) { 255.2041 + throw new DOMException("INVALID_STATE_ERR"); 255.2042 + } 255.2043 + 255.2044 + // Extract the contents 255.2045 + var content = this.extractContents(); 255.2046 + 255.2047 + // Clear the children of the node 255.2048 + if (node.hasChildNodes()) { 255.2049 + while (node.lastChild) { 255.2050 + node.removeChild(node.lastChild); 255.2051 + } 255.2052 + } 255.2053 + 255.2054 + // Insert the new node and add the extracted contents 255.2055 + insertNodeAtPosition(node, this.startContainer, this.startOffset); 255.2056 + node.appendChild(content); 255.2057 + 255.2058 + this.selectNode(node); 255.2059 + }, 255.2060 + 255.2061 + cloneRange: function() { 255.2062 + assertRangeValid(this); 255.2063 + var range = new Range(getRangeDocument(this)); 255.2064 + var i = rangeProperties.length, prop; 255.2065 + while (i--) { 255.2066 + prop = rangeProperties[i]; 255.2067 + range[prop] = this[prop]; 255.2068 + } 255.2069 + return range; 255.2070 + }, 255.2071 + 255.2072 + toString: function() { 255.2073 + assertRangeValid(this); 255.2074 + var sc = this.startContainer; 255.2075 + if (sc === this.endContainer && isCharacterDataNode(sc)) { 255.2076 + return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; 255.2077 + } else { 255.2078 + var textParts = [], iterator = new RangeIterator(this, true); 255.2079 + iterateSubtree(iterator, function(node) { 255.2080 + // Accept only text or CDATA nodes, not comments 255.2081 + if (node.nodeType == 3 || node.nodeType == 4) { 255.2082 + textParts.push(node.data); 255.2083 + } 255.2084 + }); 255.2085 + iterator.detach(); 255.2086 + return textParts.join(""); 255.2087 + } 255.2088 + }, 255.2089 + 255.2090 + // The methods below are all non-standard. The following batch were introduced by Mozilla but have since 255.2091 + // been removed from Mozilla. 255.2092 + 255.2093 + compareNode: function(node) { 255.2094 + assertRangeValid(this); 255.2095 + 255.2096 + var parent = node.parentNode; 255.2097 + var nodeIndex = getNodeIndex(node); 255.2098 + 255.2099 + if (!parent) { 255.2100 + throw new DOMException("NOT_FOUND_ERR"); 255.2101 + } 255.2102 + 255.2103 + var startComparison = this.comparePoint(parent, nodeIndex), 255.2104 + endComparison = this.comparePoint(parent, nodeIndex + 1); 255.2105 + 255.2106 + if (startComparison < 0) { // Node starts before 255.2107 + return (endComparison > 0) ? n_b_a : n_b; 255.2108 + } else { 255.2109 + return (endComparison > 0) ? n_a : n_i; 255.2110 + } 255.2111 + }, 255.2112 + 255.2113 + comparePoint: function(node, offset) { 255.2114 + assertRangeValid(this); 255.2115 + assertNode(node, "HIERARCHY_REQUEST_ERR"); 255.2116 + assertSameDocumentOrFragment(node, this.startContainer); 255.2117 + 255.2118 + if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { 255.2119 + return -1; 255.2120 + } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { 255.2121 + return 1; 255.2122 + } 255.2123 + return 0; 255.2124 + }, 255.2125 + 255.2126 + createContextualFragment: createContextualFragment, 255.2127 + 255.2128 + toHtml: function() { 255.2129 + return rangeToHtml(this); 255.2130 + }, 255.2131 + 255.2132 + // touchingIsIntersecting determines whether this method considers a node that borders a range intersects 255.2133 + // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) 255.2134 + intersectsNode: function(node, touchingIsIntersecting) { 255.2135 + assertRangeValid(this); 255.2136 + if (getRootContainer(node) != getRangeRoot(this)) { 255.2137 + return false; 255.2138 + } 255.2139 + 255.2140 + var parent = node.parentNode, offset = getNodeIndex(node); 255.2141 + if (!parent) { 255.2142 + return true; 255.2143 + } 255.2144 + 255.2145 + var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset), 255.2146 + endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset); 255.2147 + 255.2148 + return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; 255.2149 + }, 255.2150 + 255.2151 + isPointInRange: function(node, offset) { 255.2152 + assertRangeValid(this); 255.2153 + assertNode(node, "HIERARCHY_REQUEST_ERR"); 255.2154 + assertSameDocumentOrFragment(node, this.startContainer); 255.2155 + 255.2156 + return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && 255.2157 + (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); 255.2158 + }, 255.2159 + 255.2160 + // The methods below are non-standard and invented by me. 255.2161 + 255.2162 + // Sharing a boundary start-to-end or end-to-start does not count as intersection. 255.2163 + intersectsRange: function(range) { 255.2164 + return rangesIntersect(this, range, false); 255.2165 + }, 255.2166 + 255.2167 + // Sharing a boundary start-to-end or end-to-start does count as intersection. 255.2168 + intersectsOrTouchesRange: function(range) { 255.2169 + return rangesIntersect(this, range, true); 255.2170 + }, 255.2171 + 255.2172 + intersection: function(range) { 255.2173 + if (this.intersectsRange(range)) { 255.2174 + var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), 255.2175 + endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); 255.2176 + 255.2177 + var intersectionRange = this.cloneRange(); 255.2178 + if (startComparison == -1) { 255.2179 + intersectionRange.setStart(range.startContainer, range.startOffset); 255.2180 + } 255.2181 + if (endComparison == 1) { 255.2182 + intersectionRange.setEnd(range.endContainer, range.endOffset); 255.2183 + } 255.2184 + return intersectionRange; 255.2185 + } 255.2186 + return null; 255.2187 + }, 255.2188 + 255.2189 + union: function(range) { 255.2190 + if (this.intersectsOrTouchesRange(range)) { 255.2191 + var unionRange = this.cloneRange(); 255.2192 + if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { 255.2193 + unionRange.setStart(range.startContainer, range.startOffset); 255.2194 + } 255.2195 + if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { 255.2196 + unionRange.setEnd(range.endContainer, range.endOffset); 255.2197 + } 255.2198 + return unionRange; 255.2199 + } else { 255.2200 + throw new DOMException("Ranges do not intersect"); 255.2201 + } 255.2202 + }, 255.2203 + 255.2204 + containsNode: function(node, allowPartial) { 255.2205 + if (allowPartial) { 255.2206 + return this.intersectsNode(node, false); 255.2207 + } else { 255.2208 + return this.compareNode(node) == n_i; 255.2209 + } 255.2210 + }, 255.2211 + 255.2212 + containsNodeContents: function(node) { 255.2213 + return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0; 255.2214 + }, 255.2215 + 255.2216 + containsRange: function(range) { 255.2217 + var intersection = this.intersection(range); 255.2218 + return intersection !== null && range.equals(intersection); 255.2219 + }, 255.2220 + 255.2221 + containsNodeText: function(node) { 255.2222 + var nodeRange = this.cloneRange(); 255.2223 + nodeRange.selectNode(node); 255.2224 + var textNodes = nodeRange.getNodes([3]); 255.2225 + if (textNodes.length > 0) { 255.2226 + nodeRange.setStart(textNodes[0], 0); 255.2227 + var lastTextNode = textNodes.pop(); 255.2228 + nodeRange.setEnd(lastTextNode, lastTextNode.length); 255.2229 + return this.containsRange(nodeRange); 255.2230 + } else { 255.2231 + return this.containsNodeContents(node); 255.2232 + } 255.2233 + }, 255.2234 + 255.2235 + getNodes: function(nodeTypes, filter) { 255.2236 + assertRangeValid(this); 255.2237 + return getNodesInRange(this, nodeTypes, filter); 255.2238 + }, 255.2239 + 255.2240 + getDocument: function() { 255.2241 + return getRangeDocument(this); 255.2242 + }, 255.2243 + 255.2244 + collapseBefore: function(node) { 255.2245 + this.setEndBefore(node); 255.2246 + this.collapse(false); 255.2247 + }, 255.2248 + 255.2249 + collapseAfter: function(node) { 255.2250 + this.setStartAfter(node); 255.2251 + this.collapse(true); 255.2252 + }, 255.2253 + 255.2254 + getBookmark: function(containerNode) { 255.2255 + var doc = getRangeDocument(this); 255.2256 + var preSelectionRange = api.createRange(doc); 255.2257 + containerNode = containerNode || dom.getBody(doc); 255.2258 + preSelectionRange.selectNodeContents(containerNode); 255.2259 + var range = this.intersection(preSelectionRange); 255.2260 + var start = 0, end = 0; 255.2261 + if (range) { 255.2262 + preSelectionRange.setEnd(range.startContainer, range.startOffset); 255.2263 + start = preSelectionRange.toString().length; 255.2264 + end = start + range.toString().length; 255.2265 + } 255.2266 + 255.2267 + return { 255.2268 + start: start, 255.2269 + end: end, 255.2270 + containerNode: containerNode 255.2271 + }; 255.2272 + }, 255.2273 + 255.2274 + moveToBookmark: function(bookmark) { 255.2275 + var containerNode = bookmark.containerNode; 255.2276 + var charIndex = 0; 255.2277 + this.setStart(containerNode, 0); 255.2278 + this.collapse(true); 255.2279 + var nodeStack = [containerNode], node, foundStart = false, stop = false; 255.2280 + var nextCharIndex, i, childNodes; 255.2281 + 255.2282 + while (!stop && (node = nodeStack.pop())) { 255.2283 + if (node.nodeType == 3) { 255.2284 + nextCharIndex = charIndex + node.length; 255.2285 + if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) { 255.2286 + this.setStart(node, bookmark.start - charIndex); 255.2287 + foundStart = true; 255.2288 + } 255.2289 + if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) { 255.2290 + this.setEnd(node, bookmark.end - charIndex); 255.2291 + stop = true; 255.2292 + } 255.2293 + charIndex = nextCharIndex; 255.2294 + } else { 255.2295 + childNodes = node.childNodes; 255.2296 + i = childNodes.length; 255.2297 + while (i--) { 255.2298 + nodeStack.push(childNodes[i]); 255.2299 + } 255.2300 + } 255.2301 + } 255.2302 + }, 255.2303 + 255.2304 + getName: function() { 255.2305 + return "DomRange"; 255.2306 + }, 255.2307 + 255.2308 + equals: function(range) { 255.2309 + return Range.rangesEqual(this, range); 255.2310 + }, 255.2311 + 255.2312 + isValid: function() { 255.2313 + return isRangeValid(this); 255.2314 + }, 255.2315 + 255.2316 + inspect: function() { 255.2317 + return inspect(this); 255.2318 + }, 255.2319 + 255.2320 + detach: function() { 255.2321 + // In DOM4, detach() is now a no-op. 255.2322 + } 255.2323 + }); 255.2324 + 255.2325 + function copyComparisonConstantsToObject(obj) { 255.2326 + obj.START_TO_START = s2s; 255.2327 + obj.START_TO_END = s2e; 255.2328 + obj.END_TO_END = e2e; 255.2329 + obj.END_TO_START = e2s; 255.2330 + 255.2331 + obj.NODE_BEFORE = n_b; 255.2332 + obj.NODE_AFTER = n_a; 255.2333 + obj.NODE_BEFORE_AND_AFTER = n_b_a; 255.2334 + obj.NODE_INSIDE = n_i; 255.2335 + } 255.2336 + 255.2337 + function copyComparisonConstants(constructor) { 255.2338 + copyComparisonConstantsToObject(constructor); 255.2339 + copyComparisonConstantsToObject(constructor.prototype); 255.2340 + } 255.2341 + 255.2342 + function createRangeContentRemover(remover, boundaryUpdater) { 255.2343 + return function() { 255.2344 + assertRangeValid(this); 255.2345 + 255.2346 + var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; 255.2347 + 255.2348 + var iterator = new RangeIterator(this, true); 255.2349 + 255.2350 + // Work out where to position the range after content removal 255.2351 + var node, boundary; 255.2352 + if (sc !== root) { 255.2353 + node = getClosestAncestorIn(sc, root, true); 255.2354 + boundary = getBoundaryAfterNode(node); 255.2355 + sc = boundary.node; 255.2356 + so = boundary.offset; 255.2357 + } 255.2358 + 255.2359 + // Check none of the range is read-only 255.2360 + iterateSubtree(iterator, assertNodeNotReadOnly); 255.2361 + 255.2362 + iterator.reset(); 255.2363 + 255.2364 + // Remove the content 255.2365 + var returnValue = remover(iterator); 255.2366 + iterator.detach(); 255.2367 + 255.2368 + // Move to the new position 255.2369 + boundaryUpdater(this, sc, so, sc, so); 255.2370 + 255.2371 + return returnValue; 255.2372 + }; 255.2373 + } 255.2374 + 255.2375 + function createPrototypeRange(constructor, boundaryUpdater) { 255.2376 + function createBeforeAfterNodeSetter(isBefore, isStart) { 255.2377 + return function(node) { 255.2378 + assertValidNodeType(node, beforeAfterNodeTypes); 255.2379 + assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); 255.2380 + 255.2381 + var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); 255.2382 + (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); 255.2383 + }; 255.2384 + } 255.2385 + 255.2386 + function setRangeStart(range, node, offset) { 255.2387 + var ec = range.endContainer, eo = range.endOffset; 255.2388 + if (node !== range.startContainer || offset !== range.startOffset) { 255.2389 + // Check the root containers of the range and the new boundary, and also check whether the new boundary 255.2390 + // is after the current end. In either case, collapse the range to the new position 255.2391 + if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) { 255.2392 + ec = node; 255.2393 + eo = offset; 255.2394 + } 255.2395 + boundaryUpdater(range, node, offset, ec, eo); 255.2396 + } 255.2397 + } 255.2398 + 255.2399 + function setRangeEnd(range, node, offset) { 255.2400 + var sc = range.startContainer, so = range.startOffset; 255.2401 + if (node !== range.endContainer || offset !== range.endOffset) { 255.2402 + // Check the root containers of the range and the new boundary, and also check whether the new boundary 255.2403 + // is after the current end. In either case, collapse the range to the new position 255.2404 + if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) { 255.2405 + sc = node; 255.2406 + so = offset; 255.2407 + } 255.2408 + boundaryUpdater(range, sc, so, node, offset); 255.2409 + } 255.2410 + } 255.2411 + 255.2412 + // Set up inheritance 255.2413 + var F = function() {}; 255.2414 + F.prototype = api.rangePrototype; 255.2415 + constructor.prototype = new F(); 255.2416 + 255.2417 + util.extend(constructor.prototype, { 255.2418 + setStart: function(node, offset) { 255.2419 + assertNoDocTypeNotationEntityAncestor(node, true); 255.2420 + assertValidOffset(node, offset); 255.2421 + 255.2422 + setRangeStart(this, node, offset); 255.2423 + }, 255.2424 + 255.2425 + setEnd: function(node, offset) { 255.2426 + assertNoDocTypeNotationEntityAncestor(node, true); 255.2427 + assertValidOffset(node, offset); 255.2428 + 255.2429 + setRangeEnd(this, node, offset); 255.2430 + }, 255.2431 + 255.2432 + /** 255.2433 + * Convenience method to set a range's start and end boundaries. Overloaded as follows: 255.2434 + * - Two parameters (node, offset) creates a collapsed range at that position 255.2435 + * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at 255.2436 + * startOffset and ending at endOffset 255.2437 + * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in 255.2438 + * startNode and ending at endOffset in endNode 255.2439 + */ 255.2440 + setStartAndEnd: function() { 255.2441 + var args = arguments; 255.2442 + var sc = args[0], so = args[1], ec = sc, eo = so; 255.2443 + 255.2444 + switch (args.length) { 255.2445 + case 3: 255.2446 + eo = args[2]; 255.2447 + break; 255.2448 + case 4: 255.2449 + ec = args[2]; 255.2450 + eo = args[3]; 255.2451 + break; 255.2452 + } 255.2453 + 255.2454 + boundaryUpdater(this, sc, so, ec, eo); 255.2455 + }, 255.2456 + 255.2457 + setBoundary: function(node, offset, isStart) { 255.2458 + this["set" + (isStart ? "Start" : "End")](node, offset); 255.2459 + }, 255.2460 + 255.2461 + setStartBefore: createBeforeAfterNodeSetter(true, true), 255.2462 + setStartAfter: createBeforeAfterNodeSetter(false, true), 255.2463 + setEndBefore: createBeforeAfterNodeSetter(true, false), 255.2464 + setEndAfter: createBeforeAfterNodeSetter(false, false), 255.2465 + 255.2466 + collapse: function(isStart) { 255.2467 + assertRangeValid(this); 255.2468 + if (isStart) { 255.2469 + boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); 255.2470 + } else { 255.2471 + boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); 255.2472 + } 255.2473 + }, 255.2474 + 255.2475 + selectNodeContents: function(node) { 255.2476 + assertNoDocTypeNotationEntityAncestor(node, true); 255.2477 + 255.2478 + boundaryUpdater(this, node, 0, node, getNodeLength(node)); 255.2479 + }, 255.2480 + 255.2481 + selectNode: function(node) { 255.2482 + assertNoDocTypeNotationEntityAncestor(node, false); 255.2483 + assertValidNodeType(node, beforeAfterNodeTypes); 255.2484 + 255.2485 + var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); 255.2486 + boundaryUpdater(this, start.node, start.offset, end.node, end.offset); 255.2487 + }, 255.2488 + 255.2489 + extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), 255.2490 + 255.2491 + deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), 255.2492 + 255.2493 + canSurroundContents: function() { 255.2494 + assertRangeValid(this); 255.2495 + assertNodeNotReadOnly(this.startContainer); 255.2496 + assertNodeNotReadOnly(this.endContainer); 255.2497 + 255.2498 + // Check if the contents can be surrounded. Specifically, this means whether the range partially selects 255.2499 + // no non-text nodes. 255.2500 + var iterator = new RangeIterator(this, true); 255.2501 + var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) || 255.2502 + (iterator._last && isNonTextPartiallySelected(iterator._last, this))); 255.2503 + iterator.detach(); 255.2504 + return !boundariesInvalid; 255.2505 + }, 255.2506 + 255.2507 + splitBoundaries: function() { 255.2508 + splitRangeBoundaries(this); 255.2509 + }, 255.2510 + 255.2511 + splitBoundariesPreservingPositions: function(positionsToPreserve) { 255.2512 + splitRangeBoundaries(this, positionsToPreserve); 255.2513 + }, 255.2514 + 255.2515 + normalizeBoundaries: function() { 255.2516 + assertRangeValid(this); 255.2517 + 255.2518 + var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; 255.2519 + 255.2520 + var mergeForward = function(node) { 255.2521 + var sibling = node.nextSibling; 255.2522 + if (sibling && sibling.nodeType == node.nodeType) { 255.2523 + ec = node; 255.2524 + eo = node.length; 255.2525 + node.appendData(sibling.data); 255.2526 + removeNode(sibling); 255.2527 + } 255.2528 + }; 255.2529 + 255.2530 + var mergeBackward = function(node) { 255.2531 + var sibling = node.previousSibling; 255.2532 + if (sibling && sibling.nodeType == node.nodeType) { 255.2533 + sc = node; 255.2534 + var nodeLength = node.length; 255.2535 + so = sibling.length; 255.2536 + node.insertData(0, sibling.data); 255.2537 + removeNode(sibling); 255.2538 + if (sc == ec) { 255.2539 + eo += so; 255.2540 + ec = sc; 255.2541 + } else if (ec == node.parentNode) { 255.2542 + var nodeIndex = getNodeIndex(node); 255.2543 + if (eo == nodeIndex) { 255.2544 + ec = node; 255.2545 + eo = nodeLength; 255.2546 + } else if (eo > nodeIndex) { 255.2547 + eo--; 255.2548 + } 255.2549 + } 255.2550 + } 255.2551 + }; 255.2552 + 255.2553 + var normalizeStart = true; 255.2554 + var sibling; 255.2555 + 255.2556 + if (isCharacterDataNode(ec)) { 255.2557 + if (eo == ec.length) { 255.2558 + mergeForward(ec); 255.2559 + } else if (eo == 0) { 255.2560 + sibling = ec.previousSibling; 255.2561 + if (sibling && sibling.nodeType == ec.nodeType) { 255.2562 + eo = sibling.length; 255.2563 + if (sc == ec) { 255.2564 + normalizeStart = false; 255.2565 + } 255.2566 + sibling.appendData(ec.data); 255.2567 + removeNode(ec); 255.2568 + ec = sibling; 255.2569 + } 255.2570 + } 255.2571 + } else { 255.2572 + if (eo > 0) { 255.2573 + var endNode = ec.childNodes[eo - 1]; 255.2574 + if (endNode && isCharacterDataNode(endNode)) { 255.2575 + mergeForward(endNode); 255.2576 + } 255.2577 + } 255.2578 + normalizeStart = !this.collapsed; 255.2579 + } 255.2580 + 255.2581 + if (normalizeStart) { 255.2582 + if (isCharacterDataNode(sc)) { 255.2583 + if (so == 0) { 255.2584 + mergeBackward(sc); 255.2585 + } else if (so == sc.length) { 255.2586 + sibling = sc.nextSibling; 255.2587 + if (sibling && sibling.nodeType == sc.nodeType) { 255.2588 + if (ec == sibling) { 255.2589 + ec = sc; 255.2590 + eo += sc.length; 255.2591 + } 255.2592 + sc.appendData(sibling.data); 255.2593 + removeNode(sibling); 255.2594 + } 255.2595 + } 255.2596 + } else { 255.2597 + if (so < sc.childNodes.length) { 255.2598 + var startNode = sc.childNodes[so]; 255.2599 + if (startNode && isCharacterDataNode(startNode)) { 255.2600 + mergeBackward(startNode); 255.2601 + } 255.2602 + } 255.2603 + } 255.2604 + } else { 255.2605 + sc = ec; 255.2606 + so = eo; 255.2607 + } 255.2608 + 255.2609 + boundaryUpdater(this, sc, so, ec, eo); 255.2610 + }, 255.2611 + 255.2612 + collapseToPoint: function(node, offset) { 255.2613 + assertNoDocTypeNotationEntityAncestor(node, true); 255.2614 + assertValidOffset(node, offset); 255.2615 + this.setStartAndEnd(node, offset); 255.2616 + } 255.2617 + }); 255.2618 + 255.2619 + copyComparisonConstants(constructor); 255.2620 + } 255.2621 + 255.2622 + /*----------------------------------------------------------------------------------------------------------------*/ 255.2623 + 255.2624 + // Updates commonAncestorContainer and collapsed after boundary change 255.2625 + function updateCollapsedAndCommonAncestor(range) { 255.2626 + range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); 255.2627 + range.commonAncestorContainer = range.collapsed ? 255.2628 + range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); 255.2629 + } 255.2630 + 255.2631 + function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { 255.2632 + range.startContainer = startContainer; 255.2633 + range.startOffset = startOffset; 255.2634 + range.endContainer = endContainer; 255.2635 + range.endOffset = endOffset; 255.2636 + range.document = dom.getDocument(startContainer); 255.2637 + 255.2638 + updateCollapsedAndCommonAncestor(range); 255.2639 + } 255.2640 + 255.2641 + function Range(doc) { 255.2642 + this.startContainer = doc; 255.2643 + this.startOffset = 0; 255.2644 + this.endContainer = doc; 255.2645 + this.endOffset = 0; 255.2646 + this.document = doc; 255.2647 + updateCollapsedAndCommonAncestor(this); 255.2648 + } 255.2649 + 255.2650 + createPrototypeRange(Range, updateBoundaries); 255.2651 + 255.2652 + util.extend(Range, { 255.2653 + rangeProperties: rangeProperties, 255.2654 + RangeIterator: RangeIterator, 255.2655 + copyComparisonConstants: copyComparisonConstants, 255.2656 + createPrototypeRange: createPrototypeRange, 255.2657 + inspect: inspect, 255.2658 + toHtml: rangeToHtml, 255.2659 + getRangeDocument: getRangeDocument, 255.2660 + rangesEqual: function(r1, r2) { 255.2661 + return r1.startContainer === r2.startContainer && 255.2662 + r1.startOffset === r2.startOffset && 255.2663 + r1.endContainer === r2.endContainer && 255.2664 + r1.endOffset === r2.endOffset; 255.2665 + } 255.2666 + }); 255.2667 + 255.2668 + api.DomRange = Range; 255.2669 + }); 255.2670 + 255.2671 + /*----------------------------------------------------------------------------------------------------------------*/ 255.2672 + 255.2673 + // Wrappers for the browser's native DOM Range and/or TextRange implementation 255.2674 + api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { 255.2675 + var WrappedRange, WrappedTextRange; 255.2676 + var dom = api.dom; 255.2677 + var util = api.util; 255.2678 + var DomPosition = dom.DomPosition; 255.2679 + var DomRange = api.DomRange; 255.2680 + var getBody = dom.getBody; 255.2681 + var getContentDocument = dom.getContentDocument; 255.2682 + var isCharacterDataNode = dom.isCharacterDataNode; 255.2683 + 255.2684 + 255.2685 + /*----------------------------------------------------------------------------------------------------------------*/ 255.2686 + 255.2687 + if (api.features.implementsDomRange) { 255.2688 + // This is a wrapper around the browser's native DOM Range. It has two aims: 255.2689 + // - Provide workarounds for specific browser bugs 255.2690 + // - provide convenient extensions, which are inherited from Rangy's DomRange 255.2691 + 255.2692 + (function() { 255.2693 + var rangeProto; 255.2694 + var rangeProperties = DomRange.rangeProperties; 255.2695 + 255.2696 + function updateRangeProperties(range) { 255.2697 + var i = rangeProperties.length, prop; 255.2698 + while (i--) { 255.2699 + prop = rangeProperties[i]; 255.2700 + range[prop] = range.nativeRange[prop]; 255.2701 + } 255.2702 + // Fix for broken collapsed property in IE 9. 255.2703 + range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); 255.2704 + } 255.2705 + 255.2706 + function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) { 255.2707 + var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset); 255.2708 + var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset); 255.2709 + var nativeRangeDifferent = !range.equals(range.nativeRange); 255.2710 + 255.2711 + // Always set both boundaries for the benefit of IE9 (see issue 35) 255.2712 + if (startMoved || endMoved || nativeRangeDifferent) { 255.2713 + range.setEnd(endContainer, endOffset); 255.2714 + range.setStart(startContainer, startOffset); 255.2715 + } 255.2716 + } 255.2717 + 255.2718 + var createBeforeAfterNodeSetter; 255.2719 + 255.2720 + WrappedRange = function(range) { 255.2721 + if (!range) { 255.2722 + throw module.createError("WrappedRange: Range must be specified"); 255.2723 + } 255.2724 + this.nativeRange = range; 255.2725 + updateRangeProperties(this); 255.2726 + }; 255.2727 + 255.2728 + DomRange.createPrototypeRange(WrappedRange, updateNativeRange); 255.2729 + 255.2730 + rangeProto = WrappedRange.prototype; 255.2731 + 255.2732 + rangeProto.selectNode = function(node) { 255.2733 + this.nativeRange.selectNode(node); 255.2734 + updateRangeProperties(this); 255.2735 + }; 255.2736 + 255.2737 + rangeProto.cloneContents = function() { 255.2738 + return this.nativeRange.cloneContents(); 255.2739 + }; 255.2740 + 255.2741 + // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect, 255.2742 + // insertNode() is never delegated to the native range. 255.2743 + 255.2744 + rangeProto.surroundContents = function(node) { 255.2745 + this.nativeRange.surroundContents(node); 255.2746 + updateRangeProperties(this); 255.2747 + }; 255.2748 + 255.2749 + rangeProto.collapse = function(isStart) { 255.2750 + this.nativeRange.collapse(isStart); 255.2751 + updateRangeProperties(this); 255.2752 + }; 255.2753 + 255.2754 + rangeProto.cloneRange = function() { 255.2755 + return new WrappedRange(this.nativeRange.cloneRange()); 255.2756 + }; 255.2757 + 255.2758 + rangeProto.refresh = function() { 255.2759 + updateRangeProperties(this); 255.2760 + }; 255.2761 + 255.2762 + rangeProto.toString = function() { 255.2763 + return this.nativeRange.toString(); 255.2764 + }; 255.2765 + 255.2766 + // Create test range and node for feature detection 255.2767 + 255.2768 + var testTextNode = document.createTextNode("test"); 255.2769 + getBody(document).appendChild(testTextNode); 255.2770 + var range = document.createRange(); 255.2771 + 255.2772 + /*--------------------------------------------------------------------------------------------------------*/ 255.2773 + 255.2774 + // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and 255.2775 + // correct for it 255.2776 + 255.2777 + range.setStart(testTextNode, 0); 255.2778 + range.setEnd(testTextNode, 0); 255.2779 + 255.2780 + try { 255.2781 + range.setStart(testTextNode, 1); 255.2782 + 255.2783 + rangeProto.setStart = function(node, offset) { 255.2784 + this.nativeRange.setStart(node, offset); 255.2785 + updateRangeProperties(this); 255.2786 + }; 255.2787 + 255.2788 + rangeProto.setEnd = function(node, offset) { 255.2789 + this.nativeRange.setEnd(node, offset); 255.2790 + updateRangeProperties(this); 255.2791 + }; 255.2792 + 255.2793 + createBeforeAfterNodeSetter = function(name) { 255.2794 + return function(node) { 255.2795 + this.nativeRange[name](node); 255.2796 + updateRangeProperties(this); 255.2797 + }; 255.2798 + }; 255.2799 + 255.2800 + } catch(ex) { 255.2801 + 255.2802 + rangeProto.setStart = function(node, offset) { 255.2803 + try { 255.2804 + this.nativeRange.setStart(node, offset); 255.2805 + } catch (ex) { 255.2806 + this.nativeRange.setEnd(node, offset); 255.2807 + this.nativeRange.setStart(node, offset); 255.2808 + } 255.2809 + updateRangeProperties(this); 255.2810 + }; 255.2811 + 255.2812 + rangeProto.setEnd = function(node, offset) { 255.2813 + try { 255.2814 + this.nativeRange.setEnd(node, offset); 255.2815 + } catch (ex) { 255.2816 + this.nativeRange.setStart(node, offset); 255.2817 + this.nativeRange.setEnd(node, offset); 255.2818 + } 255.2819 + updateRangeProperties(this); 255.2820 + }; 255.2821 + 255.2822 + createBeforeAfterNodeSetter = function(name, oppositeName) { 255.2823 + return function(node) { 255.2824 + try { 255.2825 + this.nativeRange[name](node); 255.2826 + } catch (ex) { 255.2827 + this.nativeRange[oppositeName](node); 255.2828 + this.nativeRange[name](node); 255.2829 + } 255.2830 + updateRangeProperties(this); 255.2831 + }; 255.2832 + }; 255.2833 + } 255.2834 + 255.2835 + rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore"); 255.2836 + rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter"); 255.2837 + rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore"); 255.2838 + rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter"); 255.2839 + 255.2840 + /*--------------------------------------------------------------------------------------------------------*/ 255.2841 + 255.2842 + // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing 255.2843 + // whether the native implementation can be trusted 255.2844 + rangeProto.selectNodeContents = function(node) { 255.2845 + this.setStartAndEnd(node, 0, dom.getNodeLength(node)); 255.2846 + }; 255.2847 + 255.2848 + /*--------------------------------------------------------------------------------------------------------*/ 255.2849 + 255.2850 + // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for 255.2851 + // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738 255.2852 + 255.2853 + range.selectNodeContents(testTextNode); 255.2854 + range.setEnd(testTextNode, 3); 255.2855 + 255.2856 + var range2 = document.createRange(); 255.2857 + range2.selectNodeContents(testTextNode); 255.2858 + range2.setEnd(testTextNode, 4); 255.2859 + range2.setStart(testTextNode, 2); 255.2860 + 255.2861 + if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 && 255.2862 + range.compareBoundaryPoints(range.END_TO_START, range2) == 1) { 255.2863 + // This is the wrong way round, so correct for it 255.2864 + 255.2865 + rangeProto.compareBoundaryPoints = function(type, range) { 255.2866 + range = range.nativeRange || range; 255.2867 + if (type == range.START_TO_END) { 255.2868 + type = range.END_TO_START; 255.2869 + } else if (type == range.END_TO_START) { 255.2870 + type = range.START_TO_END; 255.2871 + } 255.2872 + return this.nativeRange.compareBoundaryPoints(type, range); 255.2873 + }; 255.2874 + } else { 255.2875 + rangeProto.compareBoundaryPoints = function(type, range) { 255.2876 + return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range); 255.2877 + }; 255.2878 + } 255.2879 + 255.2880 + /*--------------------------------------------------------------------------------------------------------*/ 255.2881 + 255.2882 + // Test for IE deleteContents() and extractContents() bug and correct it. See issue 107. 255.2883 + 255.2884 + var el = document.createElement("div"); 255.2885 + el.innerHTML = "123"; 255.2886 + var textNode = el.firstChild; 255.2887 + var body = getBody(document); 255.2888 + body.appendChild(el); 255.2889 + 255.2890 + range.setStart(textNode, 1); 255.2891 + range.setEnd(textNode, 2); 255.2892 + range.deleteContents(); 255.2893 + 255.2894 + if (textNode.data == "13") { 255.2895 + // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and 255.2896 + // extractContents() 255.2897 + rangeProto.deleteContents = function() { 255.2898 + this.nativeRange.deleteContents(); 255.2899 + updateRangeProperties(this); 255.2900 + }; 255.2901 + 255.2902 + rangeProto.extractContents = function() { 255.2903 + var frag = this.nativeRange.extractContents(); 255.2904 + updateRangeProperties(this); 255.2905 + return frag; 255.2906 + }; 255.2907 + } else { 255.2908 + } 255.2909 + 255.2910 + body.removeChild(el); 255.2911 + body = null; 255.2912 + 255.2913 + /*--------------------------------------------------------------------------------------------------------*/ 255.2914 + 255.2915 + // Test for existence of createContextualFragment and delegate to it if it exists 255.2916 + if (util.isHostMethod(range, "createContextualFragment")) { 255.2917 + rangeProto.createContextualFragment = function(fragmentStr) { 255.2918 + return this.nativeRange.createContextualFragment(fragmentStr); 255.2919 + }; 255.2920 + } 255.2921 + 255.2922 + /*--------------------------------------------------------------------------------------------------------*/ 255.2923 + 255.2924 + // Clean up 255.2925 + getBody(document).removeChild(testTextNode); 255.2926 + 255.2927 + rangeProto.getName = function() { 255.2928 + return "WrappedRange"; 255.2929 + }; 255.2930 + 255.2931 + api.WrappedRange = WrappedRange; 255.2932 + 255.2933 + api.createNativeRange = function(doc) { 255.2934 + doc = getContentDocument(doc, module, "createNativeRange"); 255.2935 + return doc.createRange(); 255.2936 + }; 255.2937 + })(); 255.2938 + } 255.2939 + 255.2940 + if (api.features.implementsTextRange) { 255.2941 + /* 255.2942 + This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() 255.2943 + method. For example, in the following (where pipes denote the selection boundaries): 255.2944 + 255.2945 + <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul> 255.2946 + 255.2947 + var range = document.selection.createRange(); 255.2948 + alert(range.parentElement().id); // Should alert "ul" but alerts "b" 255.2949 + 255.2950 + This method returns the common ancestor node of the following: 255.2951 + - the parentElement() of the textRange 255.2952 + - the parentElement() of the textRange after calling collapse(true) 255.2953 + - the parentElement() of the textRange after calling collapse(false) 255.2954 + */ 255.2955 + var getTextRangeContainerElement = function(textRange) { 255.2956 + var parentEl = textRange.parentElement(); 255.2957 + var range = textRange.duplicate(); 255.2958 + range.collapse(true); 255.2959 + var startEl = range.parentElement(); 255.2960 + range = textRange.duplicate(); 255.2961 + range.collapse(false); 255.2962 + var endEl = range.parentElement(); 255.2963 + var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); 255.2964 + 255.2965 + return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); 255.2966 + }; 255.2967 + 255.2968 + var textRangeIsCollapsed = function(textRange) { 255.2969 + return textRange.compareEndPoints("StartToEnd", textRange) == 0; 255.2970 + }; 255.2971 + 255.2972 + // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started 255.2973 + // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) 255.2974 + // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange 255.2975 + // bugs, handling for inputs and images, plus optimizations. 255.2976 + var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) { 255.2977 + var workingRange = textRange.duplicate(); 255.2978 + workingRange.collapse(isStart); 255.2979 + var containerElement = workingRange.parentElement(); 255.2980 + 255.2981 + // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so 255.2982 + // check for that 255.2983 + if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) { 255.2984 + containerElement = wholeRangeContainerElement; 255.2985 + } 255.2986 + 255.2987 + 255.2988 + // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and 255.2989 + // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx 255.2990 + if (!containerElement.canHaveHTML) { 255.2991 + var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); 255.2992 + return { 255.2993 + boundaryPosition: pos, 255.2994 + nodeInfo: { 255.2995 + nodeIndex: pos.offset, 255.2996 + containerElement: pos.node 255.2997 + } 255.2998 + }; 255.2999 + } 255.3000 + 255.3001 + var workingNode = dom.getDocument(containerElement).createElement("span"); 255.3002 + 255.3003 + // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5 255.3004 + // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64 255.3005 + if (workingNode.parentNode) { 255.3006 + dom.removeNode(workingNode); 255.3007 + } 255.3008 + 255.3009 + var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; 255.3010 + var previousNode, nextNode, boundaryPosition, boundaryNode; 255.3011 + var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0; 255.3012 + var childNodeCount = containerElement.childNodes.length; 255.3013 + var end = childNodeCount; 255.3014 + 255.3015 + // Check end first. Code within the loop assumes that the endth child node of the container is definitely 255.3016 + // after the range boundary. 255.3017 + var nodeIndex = end; 255.3018 + 255.3019 + while (true) { 255.3020 + if (nodeIndex == childNodeCount) { 255.3021 + containerElement.appendChild(workingNode); 255.3022 + } else { 255.3023 + containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]); 255.3024 + } 255.3025 + workingRange.moveToElementText(workingNode); 255.3026 + comparison = workingRange.compareEndPoints(workingComparisonType, textRange); 255.3027 + if (comparison == 0 || start == end) { 255.3028 + break; 255.3029 + } else if (comparison == -1) { 255.3030 + if (end == start + 1) { 255.3031 + // We know the endth child node is after the range boundary, so we must be done. 255.3032 + break; 255.3033 + } else { 255.3034 + start = nodeIndex; 255.3035 + } 255.3036 + } else { 255.3037 + end = (end == start + 1) ? start : nodeIndex; 255.3038 + } 255.3039 + nodeIndex = Math.floor((start + end) / 2); 255.3040 + containerElement.removeChild(workingNode); 255.3041 + } 255.3042 + 255.3043 + 255.3044 + // We've now reached or gone past the boundary of the text range we're interested in 255.3045 + // so have identified the node we want 255.3046 + boundaryNode = workingNode.nextSibling; 255.3047 + 255.3048 + if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) { 255.3049 + // This is a character data node (text, comment, cdata). The working range is collapsed at the start of 255.3050 + // the node containing the text range's boundary, so we move the end of the working range to the 255.3051 + // boundary point and measure the length of its text to get the boundary's offset within the node. 255.3052 + workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); 255.3053 + 255.3054 + var offset; 255.3055 + 255.3056 + if (/[\r\n]/.test(boundaryNode.data)) { 255.3057 + /* 255.3058 + For the particular case of a boundary within a text node containing rendered line breaks (within a 255.3059 + <pre> element, for example), we need a slightly complicated approach to get the boundary's offset in 255.3060 + IE. The facts: 255.3061 + 255.3062 + - Each line break is represented as \r in the text node's data/nodeValue properties 255.3063 + - Each line break is represented as \r\n in the TextRange's 'text' property 255.3064 + - The 'text' property of the TextRange does not contain trailing line breaks 255.3065 + 255.3066 + To get round the problem presented by the final fact above, we can use the fact that TextRange's 255.3067 + moveStart() and moveEnd() methods return the actual number of characters moved, which is not 255.3068 + necessarily the same as the number of characters it was instructed to move. The simplest approach is 255.3069 + to use this to store the characters moved when moving both the start and end of the range to the 255.3070 + start of the document body and subtracting the start offset from the end offset (the 255.3071 + "move-negative-gazillion" method). However, this is extremely slow when the document is large and 255.3072 + the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to 255.3073 + the end of the document) has the same problem. 255.3074 + 255.3075 + Another approach that works is to use moveStart() to move the start boundary of the range up to the 255.3076 + end boundary one character at a time and incrementing a counter with the value returned by the 255.3077 + moveStart() call. However, the check for whether the start boundary has reached the end boundary is 255.3078 + expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected 255.3079 + by the location of the range within the document). 255.3080 + 255.3081 + The approach used below is a hybrid of the two methods above. It uses the fact that a string 255.3082 + containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot 255.3083 + be longer than the text of the TextRange, so the start of the range is moved that length initially 255.3084 + and then a character at a time to make up for any trailing line breaks not contained in the 'text' 255.3085 + property. This has good performance in most situations compared to the previous two methods. 255.3086 + */ 255.3087 + var tempRange = workingRange.duplicate(); 255.3088 + var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length; 255.3089 + 255.3090 + offset = tempRange.moveStart("character", rangeLength); 255.3091 + while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) { 255.3092 + offset++; 255.3093 + tempRange.moveStart("character", 1); 255.3094 + } 255.3095 + } else { 255.3096 + offset = workingRange.text.length; 255.3097 + } 255.3098 + boundaryPosition = new DomPosition(boundaryNode, offset); 255.3099 + } else { 255.3100 + 255.3101 + // If the boundary immediately follows a character data node and this is the end boundary, we should favour 255.3102 + // a position within that, and likewise for a start boundary preceding a character data node 255.3103 + previousNode = (isCollapsed || !isStart) && workingNode.previousSibling; 255.3104 + nextNode = (isCollapsed || isStart) && workingNode.nextSibling; 255.3105 + if (nextNode && isCharacterDataNode(nextNode)) { 255.3106 + boundaryPosition = new DomPosition(nextNode, 0); 255.3107 + } else if (previousNode && isCharacterDataNode(previousNode)) { 255.3108 + boundaryPosition = new DomPosition(previousNode, previousNode.data.length); 255.3109 + } else { 255.3110 + boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode)); 255.3111 + } 255.3112 + } 255.3113 + 255.3114 + // Clean up 255.3115 + dom.removeNode(workingNode); 255.3116 + 255.3117 + return { 255.3118 + boundaryPosition: boundaryPosition, 255.3119 + nodeInfo: { 255.3120 + nodeIndex: nodeIndex, 255.3121 + containerElement: containerElement 255.3122 + } 255.3123 + }; 255.3124 + }; 255.3125 + 255.3126 + // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that 255.3127 + // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange 255.3128 + // (http://code.google.com/p/ierange/) 255.3129 + var createBoundaryTextRange = function(boundaryPosition, isStart) { 255.3130 + var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset; 255.3131 + var doc = dom.getDocument(boundaryPosition.node); 255.3132 + var workingNode, childNodes, workingRange = getBody(doc).createTextRange(); 255.3133 + var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node); 255.3134 + 255.3135 + if (nodeIsDataNode) { 255.3136 + boundaryNode = boundaryPosition.node; 255.3137 + boundaryParent = boundaryNode.parentNode; 255.3138 + } else { 255.3139 + childNodes = boundaryPosition.node.childNodes; 255.3140 + boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null; 255.3141 + boundaryParent = boundaryPosition.node; 255.3142 + } 255.3143 + 255.3144 + // Position the range immediately before the node containing the boundary 255.3145 + workingNode = doc.createElement("span"); 255.3146 + 255.3147 + // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within 255.3148 + // the element rather than immediately before or after it 255.3149 + workingNode.innerHTML = "&#feff;"; 255.3150 + 255.3151 + // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report 255.3152 + // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12 255.3153 + if (boundaryNode) { 255.3154 + boundaryParent.insertBefore(workingNode, boundaryNode); 255.3155 + } else { 255.3156 + boundaryParent.appendChild(workingNode); 255.3157 + } 255.3158 + 255.3159 + workingRange.moveToElementText(workingNode); 255.3160 + workingRange.collapse(!isStart); 255.3161 + 255.3162 + // Clean up 255.3163 + boundaryParent.removeChild(workingNode); 255.3164 + 255.3165 + // Move the working range to the text offset, if required 255.3166 + if (nodeIsDataNode) { 255.3167 + workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset); 255.3168 + } 255.3169 + 255.3170 + return workingRange; 255.3171 + }; 255.3172 + 255.3173 + /*------------------------------------------------------------------------------------------------------------*/ 255.3174 + 255.3175 + // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a 255.3176 + // prototype 255.3177 + 255.3178 + WrappedTextRange = function(textRange) { 255.3179 + this.textRange = textRange; 255.3180 + this.refresh(); 255.3181 + }; 255.3182 + 255.3183 + WrappedTextRange.prototype = new DomRange(document); 255.3184 + 255.3185 + WrappedTextRange.prototype.refresh = function() { 255.3186 + var start, end, startBoundary; 255.3187 + 255.3188 + // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that. 255.3189 + var rangeContainerElement = getTextRangeContainerElement(this.textRange); 255.3190 + 255.3191 + if (textRangeIsCollapsed(this.textRange)) { 255.3192 + end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, 255.3193 + true).boundaryPosition; 255.3194 + } else { 255.3195 + startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false); 255.3196 + start = startBoundary.boundaryPosition; 255.3197 + 255.3198 + // An optimization used here is that if the start and end boundaries have the same parent element, the 255.3199 + // search scope for the end boundary can be limited to exclude the portion of the element that precedes 255.3200 + // the start boundary 255.3201 + end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false, 255.3202 + startBoundary.nodeInfo).boundaryPosition; 255.3203 + } 255.3204 + 255.3205 + this.setStart(start.node, start.offset); 255.3206 + this.setEnd(end.node, end.offset); 255.3207 + }; 255.3208 + 255.3209 + WrappedTextRange.prototype.getName = function() { 255.3210 + return "WrappedTextRange"; 255.3211 + }; 255.3212 + 255.3213 + DomRange.copyComparisonConstants(WrappedTextRange); 255.3214 + 255.3215 + var rangeToTextRange = function(range) { 255.3216 + if (range.collapsed) { 255.3217 + return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true); 255.3218 + } else { 255.3219 + var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true); 255.3220 + var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false); 255.3221 + var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange(); 255.3222 + textRange.setEndPoint("StartToStart", startRange); 255.3223 + textRange.setEndPoint("EndToEnd", endRange); 255.3224 + return textRange; 255.3225 + } 255.3226 + }; 255.3227 + 255.3228 + WrappedTextRange.rangeToTextRange = rangeToTextRange; 255.3229 + 255.3230 + WrappedTextRange.prototype.toTextRange = function() { 255.3231 + return rangeToTextRange(this); 255.3232 + }; 255.3233 + 255.3234 + api.WrappedTextRange = WrappedTextRange; 255.3235 + 255.3236 + // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which 255.3237 + // implementation to use by default. 255.3238 + if (!api.features.implementsDomRange || api.config.preferTextRange) { 255.3239 + // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work 255.3240 + var globalObj = (function(f) { return f("return this;")(); })(Function); 255.3241 + if (typeof globalObj.Range == "undefined") { 255.3242 + globalObj.Range = WrappedTextRange; 255.3243 + } 255.3244 + 255.3245 + api.createNativeRange = function(doc) { 255.3246 + doc = getContentDocument(doc, module, "createNativeRange"); 255.3247 + return getBody(doc).createTextRange(); 255.3248 + }; 255.3249 + 255.3250 + api.WrappedRange = WrappedTextRange; 255.3251 + } 255.3252 + } 255.3253 + 255.3254 + api.createRange = function(doc) { 255.3255 + doc = getContentDocument(doc, module, "createRange"); 255.3256 + return new api.WrappedRange(api.createNativeRange(doc)); 255.3257 + }; 255.3258 + 255.3259 + api.createRangyRange = function(doc) { 255.3260 + doc = getContentDocument(doc, module, "createRangyRange"); 255.3261 + return new DomRange(doc); 255.3262 + }; 255.3263 + 255.3264 + util.createAliasForDeprecatedMethod(api, "createIframeRange", "createRange"); 255.3265 + util.createAliasForDeprecatedMethod(api, "createIframeRangyRange", "createRangyRange"); 255.3266 + 255.3267 + api.addShimListener(function(win) { 255.3268 + var doc = win.document; 255.3269 + if (typeof doc.createRange == "undefined") { 255.3270 + doc.createRange = function() { 255.3271 + return api.createRange(doc); 255.3272 + }; 255.3273 + } 255.3274 + doc = win = null; 255.3275 + }); 255.3276 + }); 255.3277 + 255.3278 + /*----------------------------------------------------------------------------------------------------------------*/ 255.3279 + 255.3280 + // This module creates a selection object wrapper that conforms as closely as possible to the Selection specification 255.3281 + // in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections) 255.3282 + api.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) { 255.3283 + api.config.checkSelectionRanges = true; 255.3284 + 255.3285 + var BOOLEAN = "boolean"; 255.3286 + var NUMBER = "number"; 255.3287 + var dom = api.dom; 255.3288 + var util = api.util; 255.3289 + var isHostMethod = util.isHostMethod; 255.3290 + var DomRange = api.DomRange; 255.3291 + var WrappedRange = api.WrappedRange; 255.3292 + var DOMException = api.DOMException; 255.3293 + var DomPosition = dom.DomPosition; 255.3294 + var getNativeSelection; 255.3295 + var selectionIsCollapsed; 255.3296 + var features = api.features; 255.3297 + var CONTROL = "Control"; 255.3298 + var getDocument = dom.getDocument; 255.3299 + var getBody = dom.getBody; 255.3300 + var rangesEqual = DomRange.rangesEqual; 255.3301 + 255.3302 + 255.3303 + // Utility function to support direction parameters in the API that may be a string ("backward", "backwards", 255.3304 + // "forward" or "forwards") or a Boolean (true for backwards). 255.3305 + function isDirectionBackward(dir) { 255.3306 + return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir; 255.3307 + } 255.3308 + 255.3309 + function getWindow(win, methodName) { 255.3310 + if (!win) { 255.3311 + return window; 255.3312 + } else if (dom.isWindow(win)) { 255.3313 + return win; 255.3314 + } else if (win instanceof WrappedSelection) { 255.3315 + return win.win; 255.3316 + } else { 255.3317 + var doc = dom.getContentDocument(win, module, methodName); 255.3318 + return dom.getWindow(doc); 255.3319 + } 255.3320 + } 255.3321 + 255.3322 + function getWinSelection(winParam) { 255.3323 + return getWindow(winParam, "getWinSelection").getSelection(); 255.3324 + } 255.3325 + 255.3326 + function getDocSelection(winParam) { 255.3327 + return getWindow(winParam, "getDocSelection").document.selection; 255.3328 + } 255.3329 + 255.3330 + function winSelectionIsBackward(sel) { 255.3331 + var backward = false; 255.3332 + if (sel.anchorNode) { 255.3333 + backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1); 255.3334 + } 255.3335 + return backward; 255.3336 + } 255.3337 + 255.3338 + // Test for the Range/TextRange and Selection features required 255.3339 + // Test for ability to retrieve selection 255.3340 + var implementsWinGetSelection = isHostMethod(window, "getSelection"), 255.3341 + implementsDocSelection = util.isHostObject(document, "selection"); 255.3342 + 255.3343 + features.implementsWinGetSelection = implementsWinGetSelection; 255.3344 + features.implementsDocSelection = implementsDocSelection; 255.3345 + 255.3346 + var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange); 255.3347 + 255.3348 + if (useDocumentSelection) { 255.3349 + getNativeSelection = getDocSelection; 255.3350 + api.isSelectionValid = function(winParam) { 255.3351 + var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection; 255.3352 + 255.3353 + // Check whether the selection TextRange is actually contained within the correct document 255.3354 + return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc); 255.3355 + }; 255.3356 + } else if (implementsWinGetSelection) { 255.3357 + getNativeSelection = getWinSelection; 255.3358 + api.isSelectionValid = function() { 255.3359 + return true; 255.3360 + }; 255.3361 + } else { 255.3362 + module.fail("Neither document.selection or window.getSelection() detected."); 255.3363 + return false; 255.3364 + } 255.3365 + 255.3366 + api.getNativeSelection = getNativeSelection; 255.3367 + 255.3368 + var testSelection = getNativeSelection(); 255.3369 + 255.3370 + // In Firefox, the selection is null in an iframe with display: none. See issue #138. 255.3371 + if (!testSelection) { 255.3372 + module.fail("Native selection was null (possibly issue 138?)"); 255.3373 + return false; 255.3374 + } 255.3375 + 255.3376 + var testRange = api.createNativeRange(document); 255.3377 + var body = getBody(document); 255.3378 + 255.3379 + // Obtaining a range from a selection 255.3380 + var selectionHasAnchorAndFocus = util.areHostProperties(testSelection, 255.3381 + ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]); 255.3382 + 255.3383 + features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus; 255.3384 + 255.3385 + // Test for existence of native selection extend() method 255.3386 + var selectionHasExtend = isHostMethod(testSelection, "extend"); 255.3387 + features.selectionHasExtend = selectionHasExtend; 255.3388 + 255.3389 + // Test if rangeCount exists 255.3390 + var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER); 255.3391 + features.selectionHasRangeCount = selectionHasRangeCount; 255.3392 + 255.3393 + var selectionSupportsMultipleRanges = false; 255.3394 + var collapsedNonEditableSelectionsSupported = true; 255.3395 + 255.3396 + var addRangeBackwardToNative = selectionHasExtend ? 255.3397 + function(nativeSelection, range) { 255.3398 + var doc = DomRange.getRangeDocument(range); 255.3399 + var endRange = api.createRange(doc); 255.3400 + endRange.collapseToPoint(range.endContainer, range.endOffset); 255.3401 + nativeSelection.addRange(getNativeRange(endRange)); 255.3402 + nativeSelection.extend(range.startContainer, range.startOffset); 255.3403 + } : null; 255.3404 + 255.3405 + if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) && 255.3406 + typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) { 255.3407 + 255.3408 + (function() { 255.3409 + // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are 255.3410 + // performed on the current document's selection. See issue 109. 255.3411 + 255.3412 + // Note also that if a selection previously existed, it is wiped and later restored by these tests. This 255.3413 + // will result in the selection direction begin reversed if the original selection was backwards and the 255.3414 + // browser does not support setting backwards selections (Internet Explorer, I'm looking at you). 255.3415 + var sel = window.getSelection(); 255.3416 + if (sel) { 255.3417 + // Store the current selection 255.3418 + var originalSelectionRangeCount = sel.rangeCount; 255.3419 + var selectionHasMultipleRanges = (originalSelectionRangeCount > 1); 255.3420 + var originalSelectionRanges = []; 255.3421 + var originalSelectionBackward = winSelectionIsBackward(sel); 255.3422 + for (var i = 0; i < originalSelectionRangeCount; ++i) { 255.3423 + originalSelectionRanges[i] = sel.getRangeAt(i); 255.3424 + } 255.3425 + 255.3426 + // Create some test elements 255.3427 + var testEl = dom.createTestElement(document, "", false); 255.3428 + var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") ); 255.3429 + 255.3430 + // Test whether the native selection will allow a collapsed selection within a non-editable element 255.3431 + var r1 = document.createRange(); 255.3432 + 255.3433 + r1.setStart(textNode, 1); 255.3434 + r1.collapse(true); 255.3435 + sel.removeAllRanges(); 255.3436 + sel.addRange(r1); 255.3437 + collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1); 255.3438 + sel.removeAllRanges(); 255.3439 + 255.3440 + // Test whether the native selection is capable of supporting multiple ranges. 255.3441 + if (!selectionHasMultipleRanges) { 255.3442 + // Doing the original feature test here in Chrome 36 (and presumably later versions) prints a 255.3443 + // console error of "Discontiguous selection is not supported." that cannot be suppressed. There's 255.3444 + // nothing we can do about this while retaining the feature test so we have to resort to a browser 255.3445 + // sniff. I'm not happy about it. See 255.3446 + // https://code.google.com/p/chromium/issues/detail?id=399791 255.3447 + var chromeMatch = window.navigator.appVersion.match(/Chrome\/(.*?) /); 255.3448 + if (chromeMatch && parseInt(chromeMatch[1]) >= 36) { 255.3449 + selectionSupportsMultipleRanges = false; 255.3450 + } else { 255.3451 + var r2 = r1.cloneRange(); 255.3452 + r1.setStart(textNode, 0); 255.3453 + r2.setEnd(textNode, 3); 255.3454 + r2.setStart(textNode, 2); 255.3455 + sel.addRange(r1); 255.3456 + sel.addRange(r2); 255.3457 + selectionSupportsMultipleRanges = (sel.rangeCount == 2); 255.3458 + } 255.3459 + } 255.3460 + 255.3461 + // Clean up 255.3462 + dom.removeNode(testEl); 255.3463 + sel.removeAllRanges(); 255.3464 + 255.3465 + for (i = 0; i < originalSelectionRangeCount; ++i) { 255.3466 + if (i == 0 && originalSelectionBackward) { 255.3467 + if (addRangeBackwardToNative) { 255.3468 + addRangeBackwardToNative(sel, originalSelectionRanges[i]); 255.3469 + } else { 255.3470 + api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because the browser does not support Selection.extend"); 255.3471 + sel.addRange(originalSelectionRanges[i]); 255.3472 + } 255.3473 + } else { 255.3474 + sel.addRange(originalSelectionRanges[i]); 255.3475 + } 255.3476 + } 255.3477 + } 255.3478 + })(); 255.3479 + } 255.3480 + 255.3481 + features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges; 255.3482 + features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported; 255.3483 + 255.3484 + // ControlRanges 255.3485 + var implementsControlRange = false, testControlRange; 255.3486 + 255.3487 + if (body && isHostMethod(body, "createControlRange")) { 255.3488 + testControlRange = body.createControlRange(); 255.3489 + if (util.areHostProperties(testControlRange, ["item", "add"])) { 255.3490 + implementsControlRange = true; 255.3491 + } 255.3492 + } 255.3493 + features.implementsControlRange = implementsControlRange; 255.3494 + 255.3495 + // Selection collapsedness 255.3496 + if (selectionHasAnchorAndFocus) { 255.3497 + selectionIsCollapsed = function(sel) { 255.3498 + return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset; 255.3499 + }; 255.3500 + } else { 255.3501 + selectionIsCollapsed = function(sel) { 255.3502 + return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false; 255.3503 + }; 255.3504 + } 255.3505 + 255.3506 + function updateAnchorAndFocusFromRange(sel, range, backward) { 255.3507 + var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end"; 255.3508 + sel.anchorNode = range[anchorPrefix + "Container"]; 255.3509 + sel.anchorOffset = range[anchorPrefix + "Offset"]; 255.3510 + sel.focusNode = range[focusPrefix + "Container"]; 255.3511 + sel.focusOffset = range[focusPrefix + "Offset"]; 255.3512 + } 255.3513 + 255.3514 + function updateAnchorAndFocusFromNativeSelection(sel) { 255.3515 + var nativeSel = sel.nativeSelection; 255.3516 + sel.anchorNode = nativeSel.anchorNode; 255.3517 + sel.anchorOffset = nativeSel.anchorOffset; 255.3518 + sel.focusNode = nativeSel.focusNode; 255.3519 + sel.focusOffset = nativeSel.focusOffset; 255.3520 + } 255.3521 + 255.3522 + function updateEmptySelection(sel) { 255.3523 + sel.anchorNode = sel.focusNode = null; 255.3524 + sel.anchorOffset = sel.focusOffset = 0; 255.3525 + sel.rangeCount = 0; 255.3526 + sel.isCollapsed = true; 255.3527 + sel._ranges.length = 0; 255.3528 + } 255.3529 + 255.3530 + function getNativeRange(range) { 255.3531 + var nativeRange; 255.3532 + if (range instanceof DomRange) { 255.3533 + nativeRange = api.createNativeRange(range.getDocument()); 255.3534 + nativeRange.setEnd(range.endContainer, range.endOffset); 255.3535 + nativeRange.setStart(range.startContainer, range.startOffset); 255.3536 + } else if (range instanceof WrappedRange) { 255.3537 + nativeRange = range.nativeRange; 255.3538 + } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) { 255.3539 + nativeRange = range; 255.3540 + } 255.3541 + return nativeRange; 255.3542 + } 255.3543 + 255.3544 + function rangeContainsSingleElement(rangeNodes) { 255.3545 + if (!rangeNodes.length || rangeNodes[0].nodeType != 1) { 255.3546 + return false; 255.3547 + } 255.3548 + for (var i = 1, len = rangeNodes.length; i < len; ++i) { 255.3549 + if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) { 255.3550 + return false; 255.3551 + } 255.3552 + } 255.3553 + return true; 255.3554 + } 255.3555 + 255.3556 + function getSingleElementFromRange(range) { 255.3557 + var nodes = range.getNodes(); 255.3558 + if (!rangeContainsSingleElement(nodes)) { 255.3559 + throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element"); 255.3560 + } 255.3561 + return nodes[0]; 255.3562 + } 255.3563 + 255.3564 + // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange 255.3565 + function isTextRange(range) { 255.3566 + return !!range && typeof range.text != "undefined"; 255.3567 + } 255.3568 + 255.3569 + function updateFromTextRange(sel, range) { 255.3570 + // Create a Range from the selected TextRange 255.3571 + var wrappedRange = new WrappedRange(range); 255.3572 + sel._ranges = [wrappedRange]; 255.3573 + 255.3574 + updateAnchorAndFocusFromRange(sel, wrappedRange, false); 255.3575 + sel.rangeCount = 1; 255.3576 + sel.isCollapsed = wrappedRange.collapsed; 255.3577 + } 255.3578 + 255.3579 + function updateControlSelection(sel) { 255.3580 + // Update the wrapped selection based on what's now in the native selection 255.3581 + sel._ranges.length = 0; 255.3582 + if (sel.docSelection.type == "None") { 255.3583 + updateEmptySelection(sel); 255.3584 + } else { 255.3585 + var controlRange = sel.docSelection.createRange(); 255.3586 + if (isTextRange(controlRange)) { 255.3587 + // This case (where the selection type is "Control" and calling createRange() on the selection returns 255.3588 + // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected 255.3589 + // ControlRange have been removed from the ControlRange and removed from the document. 255.3590 + updateFromTextRange(sel, controlRange); 255.3591 + } else { 255.3592 + sel.rangeCount = controlRange.length; 255.3593 + var range, doc = getDocument(controlRange.item(0)); 255.3594 + for (var i = 0; i < sel.rangeCount; ++i) { 255.3595 + range = api.createRange(doc); 255.3596 + range.selectNode(controlRange.item(i)); 255.3597 + sel._ranges.push(range); 255.3598 + } 255.3599 + sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed; 255.3600 + updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false); 255.3601 + } 255.3602 + } 255.3603 + } 255.3604 + 255.3605 + function addRangeToControlSelection(sel, range) { 255.3606 + var controlRange = sel.docSelection.createRange(); 255.3607 + var rangeElement = getSingleElementFromRange(range); 255.3608 + 255.3609 + // Create a new ControlRange containing all the elements in the selected ControlRange plus the element 255.3610 + // contained by the supplied range 255.3611 + var doc = getDocument(controlRange.item(0)); 255.3612 + var newControlRange = getBody(doc).createControlRange(); 255.3613 + for (var i = 0, len = controlRange.length; i < len; ++i) { 255.3614 + newControlRange.add(controlRange.item(i)); 255.3615 + } 255.3616 + try { 255.3617 + newControlRange.add(rangeElement); 255.3618 + } catch (ex) { 255.3619 + throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)"); 255.3620 + } 255.3621 + newControlRange.select(); 255.3622 + 255.3623 + // Update the wrapped selection based on what's now in the native selection 255.3624 + updateControlSelection(sel); 255.3625 + } 255.3626 + 255.3627 + var getSelectionRangeAt; 255.3628 + 255.3629 + if (isHostMethod(testSelection, "getRangeAt")) { 255.3630 + // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation. 255.3631 + // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a 255.3632 + // lesson to us all, especially me. 255.3633 + getSelectionRangeAt = function(sel, index) { 255.3634 + try { 255.3635 + return sel.getRangeAt(index); 255.3636 + } catch (ex) { 255.3637 + return null; 255.3638 + } 255.3639 + }; 255.3640 + } else if (selectionHasAnchorAndFocus) { 255.3641 + getSelectionRangeAt = function(sel) { 255.3642 + var doc = getDocument(sel.anchorNode); 255.3643 + var range = api.createRange(doc); 255.3644 + range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset); 255.3645 + 255.3646 + // Handle the case when the selection was selected backwards (from the end to the start in the 255.3647 + // document) 255.3648 + if (range.collapsed !== this.isCollapsed) { 255.3649 + range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset); 255.3650 + } 255.3651 + 255.3652 + return range; 255.3653 + }; 255.3654 + } 255.3655 + 255.3656 + function WrappedSelection(selection, docSelection, win) { 255.3657 + this.nativeSelection = selection; 255.3658 + this.docSelection = docSelection; 255.3659 + this._ranges = []; 255.3660 + this.win = win; 255.3661 + this.refresh(); 255.3662 + } 255.3663 + 255.3664 + WrappedSelection.prototype = api.selectionPrototype; 255.3665 + 255.3666 + function deleteProperties(sel) { 255.3667 + sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null; 255.3668 + sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0; 255.3669 + sel.detached = true; 255.3670 + } 255.3671 + 255.3672 + var cachedRangySelections = []; 255.3673 + 255.3674 + function actOnCachedSelection(win, action) { 255.3675 + var i = cachedRangySelections.length, cached, sel; 255.3676 + while (i--) { 255.3677 + cached = cachedRangySelections[i]; 255.3678 + sel = cached.selection; 255.3679 + if (action == "deleteAll") { 255.3680 + deleteProperties(sel); 255.3681 + } else if (cached.win == win) { 255.3682 + if (action == "delete") { 255.3683 + cachedRangySelections.splice(i, 1); 255.3684 + return true; 255.3685 + } else { 255.3686 + return sel; 255.3687 + } 255.3688 + } 255.3689 + } 255.3690 + if (action == "deleteAll") { 255.3691 + cachedRangySelections.length = 0; 255.3692 + } 255.3693 + return null; 255.3694 + } 255.3695 + 255.3696 + var getSelection = function(win) { 255.3697 + // Check if the parameter is a Rangy Selection object 255.3698 + if (win && win instanceof WrappedSelection) { 255.3699 + win.refresh(); 255.3700 + return win; 255.3701 + } 255.3702 + 255.3703 + win = getWindow(win, "getNativeSelection"); 255.3704 + 255.3705 + var sel = actOnCachedSelection(win); 255.3706 + var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null; 255.3707 + if (sel) { 255.3708 + sel.nativeSelection = nativeSel; 255.3709 + sel.docSelection = docSel; 255.3710 + sel.refresh(); 255.3711 + } else { 255.3712 + sel = new WrappedSelection(nativeSel, docSel, win); 255.3713 + cachedRangySelections.push( { win: win, selection: sel } ); 255.3714 + } 255.3715 + return sel; 255.3716 + }; 255.3717 + 255.3718 + api.getSelection = getSelection; 255.3719 + 255.3720 + util.createAliasForDeprecatedMethod(api, "getIframeSelection", "getSelection"); 255.3721 + 255.3722 + var selProto = WrappedSelection.prototype; 255.3723 + 255.3724 + function createControlSelection(sel, ranges) { 255.3725 + // Ensure that the selection becomes of type "Control" 255.3726 + var doc = getDocument(ranges[0].startContainer); 255.3727 + var controlRange = getBody(doc).createControlRange(); 255.3728 + for (var i = 0, el, len = ranges.length; i < len; ++i) { 255.3729 + el = getSingleElementFromRange(ranges[i]); 255.3730 + try { 255.3731 + controlRange.add(el); 255.3732 + } catch (ex) { 255.3733 + throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)"); 255.3734 + } 255.3735 + } 255.3736 + controlRange.select(); 255.3737 + 255.3738 + // Update the wrapped selection based on what's now in the native selection 255.3739 + updateControlSelection(sel); 255.3740 + } 255.3741 + 255.3742 + // Selecting a range 255.3743 + if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) { 255.3744 + selProto.removeAllRanges = function() { 255.3745 + this.nativeSelection.removeAllRanges(); 255.3746 + updateEmptySelection(this); 255.3747 + }; 255.3748 + 255.3749 + var addRangeBackward = function(sel, range) { 255.3750 + addRangeBackwardToNative(sel.nativeSelection, range); 255.3751 + sel.refresh(); 255.3752 + }; 255.3753 + 255.3754 + if (selectionHasRangeCount) { 255.3755 + selProto.addRange = function(range, direction) { 255.3756 + if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) { 255.3757 + addRangeToControlSelection(this, range); 255.3758 + } else { 255.3759 + if (isDirectionBackward(direction) && selectionHasExtend) { 255.3760 + addRangeBackward(this, range); 255.3761 + } else { 255.3762 + var previousRangeCount; 255.3763 + if (selectionSupportsMultipleRanges) { 255.3764 + previousRangeCount = this.rangeCount; 255.3765 + } else { 255.3766 + this.removeAllRanges(); 255.3767 + previousRangeCount = 0; 255.3768 + } 255.3769 + // Clone the native range so that changing the selected range does not affect the selection. 255.3770 + // This is contrary to the spec but is the only way to achieve consistency between browsers. See 255.3771 + // issue 80. 255.3772 + var clonedNativeRange = getNativeRange(range).cloneRange(); 255.3773 + try { 255.3774 + this.nativeSelection.addRange(clonedNativeRange); 255.3775 + } catch (ex) { 255.3776 + } 255.3777 + 255.3778 + // Check whether adding the range was successful 255.3779 + this.rangeCount = this.nativeSelection.rangeCount; 255.3780 + 255.3781 + if (this.rangeCount == previousRangeCount + 1) { 255.3782 + // The range was added successfully 255.3783 + 255.3784 + // Check whether the range that we added to the selection is reflected in the last range extracted from 255.3785 + // the selection 255.3786 + if (api.config.checkSelectionRanges) { 255.3787 + var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1); 255.3788 + if (nativeRange && !rangesEqual(nativeRange, range)) { 255.3789 + // Happens in WebKit with, for example, a selection placed at the start of a text node 255.3790 + range = new WrappedRange(nativeRange); 255.3791 + } 255.3792 + } 255.3793 + this._ranges[this.rangeCount - 1] = range; 255.3794 + updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection)); 255.3795 + this.isCollapsed = selectionIsCollapsed(this); 255.3796 + } else { 255.3797 + // The range was not added successfully. The simplest thing is to refresh 255.3798 + this.refresh(); 255.3799 + } 255.3800 + } 255.3801 + } 255.3802 + }; 255.3803 + } else { 255.3804 + selProto.addRange = function(range, direction) { 255.3805 + if (isDirectionBackward(direction) && selectionHasExtend) { 255.3806 + addRangeBackward(this, range); 255.3807 + } else { 255.3808 + this.nativeSelection.addRange(getNativeRange(range)); 255.3809 + this.refresh(); 255.3810 + } 255.3811 + }; 255.3812 + } 255.3813 + 255.3814 + selProto.setRanges = function(ranges) { 255.3815 + if (implementsControlRange && implementsDocSelection && ranges.length > 1) { 255.3816 + createControlSelection(this, ranges); 255.3817 + } else { 255.3818 + this.removeAllRanges(); 255.3819 + for (var i = 0, len = ranges.length; i < len; ++i) { 255.3820 + this.addRange(ranges[i]); 255.3821 + } 255.3822 + } 255.3823 + }; 255.3824 + } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") && 255.3825 + implementsControlRange && useDocumentSelection) { 255.3826 + 255.3827 + selProto.removeAllRanges = function() { 255.3828 + // Added try/catch as fix for issue #21 255.3829 + try { 255.3830 + this.docSelection.empty(); 255.3831 + 255.3832 + // Check for empty() not working (issue #24) 255.3833 + if (this.docSelection.type != "None") { 255.3834 + // Work around failure to empty a control selection by instead selecting a TextRange and then 255.3835 + // calling empty() 255.3836 + var doc; 255.3837 + if (this.anchorNode) { 255.3838 + doc = getDocument(this.anchorNode); 255.3839 + } else if (this.docSelection.type == CONTROL) { 255.3840 + var controlRange = this.docSelection.createRange(); 255.3841 + if (controlRange.length) { 255.3842 + doc = getDocument( controlRange.item(0) ); 255.3843 + } 255.3844 + } 255.3845 + if (doc) { 255.3846 + var textRange = getBody(doc).createTextRange(); 255.3847 + textRange.select(); 255.3848 + this.docSelection.empty(); 255.3849 + } 255.3850 + } 255.3851 + } catch(ex) {} 255.3852 + updateEmptySelection(this); 255.3853 + }; 255.3854 + 255.3855 + selProto.addRange = function(range) { 255.3856 + if (this.docSelection.type == CONTROL) { 255.3857 + addRangeToControlSelection(this, range); 255.3858 + } else { 255.3859 + api.WrappedTextRange.rangeToTextRange(range).select(); 255.3860 + this._ranges[0] = range; 255.3861 + this.rangeCount = 1; 255.3862 + this.isCollapsed = this._ranges[0].collapsed; 255.3863 + updateAnchorAndFocusFromRange(this, range, false); 255.3864 + } 255.3865 + }; 255.3866 + 255.3867 + selProto.setRanges = function(ranges) { 255.3868 + this.removeAllRanges(); 255.3869 + var rangeCount = ranges.length; 255.3870 + if (rangeCount > 1) { 255.3871 + createControlSelection(this, ranges); 255.3872 + } else if (rangeCount) { 255.3873 + this.addRange(ranges[0]); 255.3874 + } 255.3875 + }; 255.3876 + } else { 255.3877 + module.fail("No means of selecting a Range or TextRange was found"); 255.3878 + return false; 255.3879 + } 255.3880 + 255.3881 + selProto.getRangeAt = function(index) { 255.3882 + if (index < 0 || index >= this.rangeCount) { 255.3883 + throw new DOMException("INDEX_SIZE_ERR"); 255.3884 + } else { 255.3885 + // Clone the range to preserve selection-range independence. See issue 80. 255.3886 + return this._ranges[index].cloneRange(); 255.3887 + } 255.3888 + }; 255.3889 + 255.3890 + var refreshSelection; 255.3891 + 255.3892 + if (useDocumentSelection) { 255.3893 + refreshSelection = function(sel) { 255.3894 + var range; 255.3895 + if (api.isSelectionValid(sel.win)) { 255.3896 + range = sel.docSelection.createRange(); 255.3897 + } else { 255.3898 + range = getBody(sel.win.document).createTextRange(); 255.3899 + range.collapse(true); 255.3900 + } 255.3901 + 255.3902 + if (sel.docSelection.type == CONTROL) { 255.3903 + updateControlSelection(sel); 255.3904 + } else if (isTextRange(range)) { 255.3905 + updateFromTextRange(sel, range); 255.3906 + } else { 255.3907 + updateEmptySelection(sel); 255.3908 + } 255.3909 + }; 255.3910 + } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) { 255.3911 + refreshSelection = function(sel) { 255.3912 + if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) { 255.3913 + updateControlSelection(sel); 255.3914 + } else { 255.3915 + sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount; 255.3916 + if (sel.rangeCount) { 255.3917 + for (var i = 0, len = sel.rangeCount; i < len; ++i) { 255.3918 + sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i)); 255.3919 + } 255.3920 + updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection)); 255.3921 + sel.isCollapsed = selectionIsCollapsed(sel); 255.3922 + } else { 255.3923 + updateEmptySelection(sel); 255.3924 + } 255.3925 + } 255.3926 + }; 255.3927 + } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) { 255.3928 + refreshSelection = function(sel) { 255.3929 + var range, nativeSel = sel.nativeSelection; 255.3930 + if (nativeSel.anchorNode) { 255.3931 + range = getSelectionRangeAt(nativeSel, 0); 255.3932 + sel._ranges = [range]; 255.3933 + sel.rangeCount = 1; 255.3934 + updateAnchorAndFocusFromNativeSelection(sel); 255.3935 + sel.isCollapsed = selectionIsCollapsed(sel); 255.3936 + } else { 255.3937 + updateEmptySelection(sel); 255.3938 + } 255.3939 + }; 255.3940 + } else { 255.3941 + module.fail("No means of obtaining a Range or TextRange from the user's selection was found"); 255.3942 + return false; 255.3943 + } 255.3944 + 255.3945 + selProto.refresh = function(checkForChanges) { 255.3946 + var oldRanges = checkForChanges ? this._ranges.slice(0) : null; 255.3947 + var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset; 255.3948 + 255.3949 + refreshSelection(this); 255.3950 + if (checkForChanges) { 255.3951 + // Check the range count first 255.3952 + var i = oldRanges.length; 255.3953 + if (i != this._ranges.length) { 255.3954 + return true; 255.3955 + } 255.3956 + 255.3957 + // Now check the direction. Checking the anchor position is the same is enough since we're checking all the 255.3958 + // ranges after this 255.3959 + if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) { 255.3960 + return true; 255.3961 + } 255.3962 + 255.3963 + // Finally, compare each range in turn 255.3964 + while (i--) { 255.3965 + if (!rangesEqual(oldRanges[i], this._ranges[i])) { 255.3966 + return true; 255.3967 + } 255.3968 + } 255.3969 + return false; 255.3970 + } 255.3971 + }; 255.3972 + 255.3973 + // Removal of a single range 255.3974 + var removeRangeManually = function(sel, range) { 255.3975 + var ranges = sel.getAllRanges(); 255.3976 + sel.removeAllRanges(); 255.3977 + for (var i = 0, len = ranges.length; i < len; ++i) { 255.3978 + if (!rangesEqual(range, ranges[i])) { 255.3979 + sel.addRange(ranges[i]); 255.3980 + } 255.3981 + } 255.3982 + if (!sel.rangeCount) { 255.3983 + updateEmptySelection(sel); 255.3984 + } 255.3985 + }; 255.3986 + 255.3987 + if (implementsControlRange && implementsDocSelection) { 255.3988 + selProto.removeRange = function(range) { 255.3989 + if (this.docSelection.type == CONTROL) { 255.3990 + var controlRange = this.docSelection.createRange(); 255.3991 + var rangeElement = getSingleElementFromRange(range); 255.3992 + 255.3993 + // Create a new ControlRange containing all the elements in the selected ControlRange minus the 255.3994 + // element contained by the supplied range 255.3995 + var doc = getDocument(controlRange.item(0)); 255.3996 + var newControlRange = getBody(doc).createControlRange(); 255.3997 + var el, removed = false; 255.3998 + for (var i = 0, len = controlRange.length; i < len; ++i) { 255.3999 + el = controlRange.item(i); 255.4000 + if (el !== rangeElement || removed) { 255.4001 + newControlRange.add(controlRange.item(i)); 255.4002 + } else { 255.4003 + removed = true; 255.4004 + } 255.4005 + } 255.4006 + newControlRange.select(); 255.4007 + 255.4008 + // Update the wrapped selection based on what's now in the native selection 255.4009 + updateControlSelection(this); 255.4010 + } else { 255.4011 + removeRangeManually(this, range); 255.4012 + } 255.4013 + }; 255.4014 + } else { 255.4015 + selProto.removeRange = function(range) { 255.4016 + removeRangeManually(this, range); 255.4017 + }; 255.4018 + } 255.4019 + 255.4020 + // Detecting if a selection is backward 255.4021 + var selectionIsBackward; 255.4022 + if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) { 255.4023 + selectionIsBackward = winSelectionIsBackward; 255.4024 + 255.4025 + selProto.isBackward = function() { 255.4026 + return selectionIsBackward(this); 255.4027 + }; 255.4028 + } else { 255.4029 + selectionIsBackward = selProto.isBackward = function() { 255.4030 + return false; 255.4031 + }; 255.4032 + } 255.4033 + 255.4034 + // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards" 255.4035 + selProto.isBackwards = selProto.isBackward; 255.4036 + 255.4037 + // Selection stringifier 255.4038 + // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation. 255.4039 + // The current spec does not yet define this method. 255.4040 + selProto.toString = function() { 255.4041 + var rangeTexts = []; 255.4042 + for (var i = 0, len = this.rangeCount; i < len; ++i) { 255.4043 + rangeTexts[i] = "" + this._ranges[i]; 255.4044 + } 255.4045 + return rangeTexts.join(""); 255.4046 + }; 255.4047 + 255.4048 + function assertNodeInSameDocument(sel, node) { 255.4049 + if (sel.win.document != getDocument(node)) { 255.4050 + throw new DOMException("WRONG_DOCUMENT_ERR"); 255.4051 + } 255.4052 + } 255.4053 + 255.4054 + // No current browser conforms fully to the spec for this method, so Rangy's own method is always used 255.4055 + selProto.collapse = function(node, offset) { 255.4056 + assertNodeInSameDocument(this, node); 255.4057 + var range = api.createRange(node); 255.4058 + range.collapseToPoint(node, offset); 255.4059 + this.setSingleRange(range); 255.4060 + this.isCollapsed = true; 255.4061 + }; 255.4062 + 255.4063 + selProto.collapseToStart = function() { 255.4064 + if (this.rangeCount) { 255.4065 + var range = this._ranges[0]; 255.4066 + this.collapse(range.startContainer, range.startOffset); 255.4067 + } else { 255.4068 + throw new DOMException("INVALID_STATE_ERR"); 255.4069 + } 255.4070 + }; 255.4071 + 255.4072 + selProto.collapseToEnd = function() { 255.4073 + if (this.rangeCount) { 255.4074 + var range = this._ranges[this.rangeCount - 1]; 255.4075 + this.collapse(range.endContainer, range.endOffset); 255.4076 + } else { 255.4077 + throw new DOMException("INVALID_STATE_ERR"); 255.4078 + } 255.4079 + }; 255.4080 + 255.4081 + // The spec is very specific on how selectAllChildren should be implemented and not all browsers implement it as 255.4082 + // specified so the native implementation is never used by Rangy. 255.4083 + selProto.selectAllChildren = function(node) { 255.4084 + assertNodeInSameDocument(this, node); 255.4085 + var range = api.createRange(node); 255.4086 + range.selectNodeContents(node); 255.4087 + this.setSingleRange(range); 255.4088 + }; 255.4089 + 255.4090 + selProto.deleteFromDocument = function() { 255.4091 + // Sepcial behaviour required for IE's control selections 255.4092 + if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) { 255.4093 + var controlRange = this.docSelection.createRange(); 255.4094 + var element; 255.4095 + while (controlRange.length) { 255.4096 + element = controlRange.item(0); 255.4097 + controlRange.remove(element); 255.4098 + dom.removeNode(element); 255.4099 + } 255.4100 + this.refresh(); 255.4101 + } else if (this.rangeCount) { 255.4102 + var ranges = this.getAllRanges(); 255.4103 + if (ranges.length) { 255.4104 + this.removeAllRanges(); 255.4105 + for (var i = 0, len = ranges.length; i < len; ++i) { 255.4106 + ranges[i].deleteContents(); 255.4107 + } 255.4108 + // The spec says nothing about what the selection should contain after calling deleteContents on each 255.4109 + // range. Firefox moves the selection to where the final selected range was, so we emulate that 255.4110 + this.addRange(ranges[len - 1]); 255.4111 + } 255.4112 + } 255.4113 + }; 255.4114 + 255.4115 + // The following are non-standard extensions 255.4116 + selProto.eachRange = function(func, returnValue) { 255.4117 + for (var i = 0, len = this._ranges.length; i < len; ++i) { 255.4118 + if ( func( this.getRangeAt(i) ) ) { 255.4119 + return returnValue; 255.4120 + } 255.4121 + } 255.4122 + }; 255.4123 + 255.4124 + selProto.getAllRanges = function() { 255.4125 + var ranges = []; 255.4126 + this.eachRange(function(range) { 255.4127 + ranges.push(range); 255.4128 + }); 255.4129 + return ranges; 255.4130 + }; 255.4131 + 255.4132 + selProto.setSingleRange = function(range, direction) { 255.4133 + this.removeAllRanges(); 255.4134 + this.addRange(range, direction); 255.4135 + }; 255.4136 + 255.4137 + selProto.callMethodOnEachRange = function(methodName, params) { 255.4138 + var results = []; 255.4139 + this.eachRange( function(range) { 255.4140 + results.push( range[methodName].apply(range, params || []) ); 255.4141 + } ); 255.4142 + return results; 255.4143 + }; 255.4144 + 255.4145 + function createStartOrEndSetter(isStart) { 255.4146 + return function(node, offset) { 255.4147 + var range; 255.4148 + if (this.rangeCount) { 255.4149 + range = this.getRangeAt(0); 255.4150 + range["set" + (isStart ? "Start" : "End")](node, offset); 255.4151 + } else { 255.4152 + range = api.createRange(this.win.document); 255.4153 + range.setStartAndEnd(node, offset); 255.4154 + } 255.4155 + this.setSingleRange(range, this.isBackward()); 255.4156 + }; 255.4157 + } 255.4158 + 255.4159 + selProto.setStart = createStartOrEndSetter(true); 255.4160 + selProto.setEnd = createStartOrEndSetter(false); 255.4161 + 255.4162 + // Add select() method to Range prototype. Any existing selection will be removed. 255.4163 + api.rangePrototype.select = function(direction) { 255.4164 + getSelection( this.getDocument() ).setSingleRange(this, direction); 255.4165 + }; 255.4166 + 255.4167 + selProto.changeEachRange = function(func) { 255.4168 + var ranges = []; 255.4169 + var backward = this.isBackward(); 255.4170 + 255.4171 + this.eachRange(function(range) { 255.4172 + func(range); 255.4173 + ranges.push(range); 255.4174 + }); 255.4175 + 255.4176 + this.removeAllRanges(); 255.4177 + if (backward && ranges.length == 1) { 255.4178 + this.addRange(ranges[0], "backward"); 255.4179 + } else { 255.4180 + this.setRanges(ranges); 255.4181 + } 255.4182 + }; 255.4183 + 255.4184 + selProto.containsNode = function(node, allowPartial) { 255.4185 + return this.eachRange( function(range) { 255.4186 + return range.containsNode(node, allowPartial); 255.4187 + }, true ) || false; 255.4188 + }; 255.4189 + 255.4190 + selProto.getBookmark = function(containerNode) { 255.4191 + return { 255.4192 + backward: this.isBackward(), 255.4193 + rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode]) 255.4194 + }; 255.4195 + }; 255.4196 + 255.4197 + selProto.moveToBookmark = function(bookmark) { 255.4198 + var selRanges = []; 255.4199 + for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) { 255.4200 + range = api.createRange(this.win); 255.4201 + range.moveToBookmark(rangeBookmark); 255.4202 + selRanges.push(range); 255.4203 + } 255.4204 + if (bookmark.backward) { 255.4205 + this.setSingleRange(selRanges[0], "backward"); 255.4206 + } else { 255.4207 + this.setRanges(selRanges); 255.4208 + } 255.4209 + }; 255.4210 + 255.4211 + selProto.saveRanges = function() { 255.4212 + return { 255.4213 + backward: this.isBackward(), 255.4214 + ranges: this.callMethodOnEachRange("cloneRange") 255.4215 + }; 255.4216 + }; 255.4217 + 255.4218 + selProto.restoreRanges = function(selRanges) { 255.4219 + this.removeAllRanges(); 255.4220 + for (var i = 0, range; range = selRanges.ranges[i]; ++i) { 255.4221 + this.addRange(range, (selRanges.backward && i == 0)); 255.4222 + } 255.4223 + }; 255.4224 + 255.4225 + selProto.toHtml = function() { 255.4226 + var rangeHtmls = []; 255.4227 + this.eachRange(function(range) { 255.4228 + rangeHtmls.push( DomRange.toHtml(range) ); 255.4229 + }); 255.4230 + return rangeHtmls.join(""); 255.4231 + }; 255.4232 + 255.4233 + if (features.implementsTextRange) { 255.4234 + selProto.getNativeTextRange = function() { 255.4235 + var sel, textRange; 255.4236 + if ( (sel = this.docSelection) ) { 255.4237 + var range = sel.createRange(); 255.4238 + if (isTextRange(range)) { 255.4239 + return range; 255.4240 + } else { 255.4241 + throw module.createError("getNativeTextRange: selection is a control selection"); 255.4242 + } 255.4243 + } else if (this.rangeCount > 0) { 255.4244 + return api.WrappedTextRange.rangeToTextRange( this.getRangeAt(0) ); 255.4245 + } else { 255.4246 + throw module.createError("getNativeTextRange: selection contains no range"); 255.4247 + } 255.4248 + }; 255.4249 + } 255.4250 + 255.4251 + function inspect(sel) { 255.4252 + var rangeInspects = []; 255.4253 + var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset); 255.4254 + var focus = new DomPosition(sel.focusNode, sel.focusOffset); 255.4255 + var name = (typeof sel.getName == "function") ? sel.getName() : "Selection"; 255.4256 + 255.4257 + if (typeof sel.rangeCount != "undefined") { 255.4258 + for (var i = 0, len = sel.rangeCount; i < len; ++i) { 255.4259 + rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i)); 255.4260 + } 255.4261 + } 255.4262 + return "[" + name + "(Ranges: " + rangeInspects.join(", ") + 255.4263 + ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]"; 255.4264 + } 255.4265 + 255.4266 + selProto.getName = function() { 255.4267 + return "WrappedSelection"; 255.4268 + }; 255.4269 + 255.4270 + selProto.inspect = function() { 255.4271 + return inspect(this); 255.4272 + }; 255.4273 + 255.4274 + selProto.detach = function() { 255.4275 + actOnCachedSelection(this.win, "delete"); 255.4276 + deleteProperties(this); 255.4277 + }; 255.4278 + 255.4279 + WrappedSelection.detachAll = function() { 255.4280 + actOnCachedSelection(null, "deleteAll"); 255.4281 + }; 255.4282 + 255.4283 + WrappedSelection.inspect = inspect; 255.4284 + WrappedSelection.isDirectionBackward = isDirectionBackward; 255.4285 + 255.4286 + api.Selection = WrappedSelection; 255.4287 + 255.4288 + api.selectionPrototype = selProto; 255.4289 + 255.4290 + api.addShimListener(function(win) { 255.4291 + if (typeof win.getSelection == "undefined") { 255.4292 + win.getSelection = function() { 255.4293 + return getSelection(win); 255.4294 + }; 255.4295 + } 255.4296 + win = null; 255.4297 + }); 255.4298 + }); 255.4299 + 255.4300 + 255.4301 + /*----------------------------------------------------------------------------------------------------------------*/ 255.4302 + 255.4303 + // Wait for document to load before initializing 255.4304 + var docReady = false; 255.4305 + 255.4306 + var loadHandler = function(e) { 255.4307 + if (!docReady) { 255.4308 + docReady = true; 255.4309 + if (!api.initialized && api.config.autoInitialize) { 255.4310 + init(); 255.4311 + } 255.4312 + } 255.4313 + }; 255.4314 + 255.4315 + if (isBrowser) { 255.4316 + // Test whether the document has already been loaded and initialize immediately if so 255.4317 + if (document.readyState == "complete") { 255.4318 + loadHandler(); 255.4319 + } else { 255.4320 + if (isHostMethod(document, "addEventListener")) { 255.4321 + document.addEventListener("DOMContentLoaded", loadHandler, false); 255.4322 + } 255.4323 + 255.4324 + // Add a fallback in case the DOMContentLoaded event isn't supported 255.4325 + addListener(window, "load", loadHandler); 255.4326 + } 255.4327 + } 255.4328 + 255.4329 + rangy = api; 255.4330 +})(); 255.4331 + 255.4332 +/** 255.4333 + * Selection save and restore module for Rangy. 255.4334 + * Saves and restores user selections using marker invisible elements in the DOM. 255.4335 + * 255.4336 + * Part of Rangy, a cross-browser JavaScript range and selection library 255.4337 + * https://github.com/timdown/rangy 255.4338 + * 255.4339 + * Depends on Rangy core. 255.4340 + * 255.4341 + * Copyright 2015, Tim Down 255.4342 + * Licensed under the MIT license. 255.4343 + * Version: 1.3.1-dev 255.4344 + * Build date: 20 May 2015 255.4345 + * 255.4346 +* NOTE: UMD wrapper removed manually for bundling (Oliver Pulges) 255.4347 +*/ 255.4348 +rangy.createModule("SaveRestore", ["WrappedRange"], function(api, module) { 255.4349 + var dom = api.dom; 255.4350 + var removeNode = dom.removeNode; 255.4351 + var isDirectionBackward = api.Selection.isDirectionBackward; 255.4352 + var markerTextChar = "\ufeff"; 255.4353 + 255.4354 + function gEBI(id, doc) { 255.4355 + return (doc || document).getElementById(id); 255.4356 + } 255.4357 + 255.4358 + function insertRangeBoundaryMarker(range, atStart) { 255.4359 + var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2); 255.4360 + var markerEl; 255.4361 + var doc = dom.getDocument(range.startContainer); 255.4362 + 255.4363 + // Clone the Range and collapse to the appropriate boundary point 255.4364 + var boundaryRange = range.cloneRange(); 255.4365 + boundaryRange.collapse(atStart); 255.4366 + 255.4367 + // Create the marker element containing a single invisible character using DOM methods and insert it 255.4368 + markerEl = doc.createElement("span"); 255.4369 + markerEl.id = markerId; 255.4370 + markerEl.style.lineHeight = "0"; 255.4371 + markerEl.style.display = "none"; 255.4372 + markerEl.className = "rangySelectionBoundary"; 255.4373 + markerEl.appendChild(doc.createTextNode(markerTextChar)); 255.4374 + 255.4375 + boundaryRange.insertNode(markerEl); 255.4376 + return markerEl; 255.4377 + } 255.4378 + 255.4379 + function setRangeBoundary(doc, range, markerId, atStart) { 255.4380 + var markerEl = gEBI(markerId, doc); 255.4381 + if (markerEl) { 255.4382 + range[atStart ? "setStartBefore" : "setEndBefore"](markerEl); 255.4383 + removeNode(markerEl); 255.4384 + } else { 255.4385 + module.warn("Marker element has been removed. Cannot restore selection."); 255.4386 + } 255.4387 + } 255.4388 + 255.4389 + function compareRanges(r1, r2) { 255.4390 + return r2.compareBoundaryPoints(r1.START_TO_START, r1); 255.4391 + } 255.4392 + 255.4393 + function saveRange(range, direction) { 255.4394 + var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString(); 255.4395 + var backward = isDirectionBackward(direction); 255.4396 + 255.4397 + if (range.collapsed) { 255.4398 + endEl = insertRangeBoundaryMarker(range, false); 255.4399 + return { 255.4400 + document: doc, 255.4401 + markerId: endEl.id, 255.4402 + collapsed: true 255.4403 + }; 255.4404 + } else { 255.4405 + endEl = insertRangeBoundaryMarker(range, false); 255.4406 + startEl = insertRangeBoundaryMarker(range, true); 255.4407 + 255.4408 + return { 255.4409 + document: doc, 255.4410 + startMarkerId: startEl.id, 255.4411 + endMarkerId: endEl.id, 255.4412 + collapsed: false, 255.4413 + backward: backward, 255.4414 + toString: function() { 255.4415 + return "original text: '" + text + "', new text: '" + range.toString() + "'"; 255.4416 + } 255.4417 + }; 255.4418 + } 255.4419 + } 255.4420 + 255.4421 + function restoreRange(rangeInfo, normalize) { 255.4422 + var doc = rangeInfo.document; 255.4423 + if (typeof normalize == "undefined") { 255.4424 + normalize = true; 255.4425 + } 255.4426 + var range = api.createRange(doc); 255.4427 + if (rangeInfo.collapsed) { 255.4428 + var markerEl = gEBI(rangeInfo.markerId, doc); 255.4429 + if (markerEl) { 255.4430 + markerEl.style.display = "inline"; 255.4431 + var previousNode = markerEl.previousSibling; 255.4432 + 255.4433 + // Workaround for issue 17 255.4434 + if (previousNode && previousNode.nodeType == 3) { 255.4435 + removeNode(markerEl); 255.4436 + range.collapseToPoint(previousNode, previousNode.length); 255.4437 + } else { 255.4438 + range.collapseBefore(markerEl); 255.4439 + removeNode(markerEl); 255.4440 + } 255.4441 + } else { 255.4442 + module.warn("Marker element has been removed. Cannot restore selection."); 255.4443 + } 255.4444 + } else { 255.4445 + setRangeBoundary(doc, range, rangeInfo.startMarkerId, true); 255.4446 + setRangeBoundary(doc, range, rangeInfo.endMarkerId, false); 255.4447 + } 255.4448 + 255.4449 + if (normalize) { 255.4450 + range.normalizeBoundaries(); 255.4451 + } 255.4452 + 255.4453 + return range; 255.4454 + } 255.4455 + 255.4456 + function saveRanges(ranges, direction) { 255.4457 + var rangeInfos = [], range, doc; 255.4458 + var backward = isDirectionBackward(direction); 255.4459 + 255.4460 + // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched 255.4461 + ranges = ranges.slice(0); 255.4462 + ranges.sort(compareRanges); 255.4463 + 255.4464 + for (var i = 0, len = ranges.length; i < len; ++i) { 255.4465 + rangeInfos[i] = saveRange(ranges[i], backward); 255.4466 + } 255.4467 + 255.4468 + // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie 255.4469 + // between its markers 255.4470 + for (i = len - 1; i >= 0; --i) { 255.4471 + range = ranges[i]; 255.4472 + doc = api.DomRange.getRangeDocument(range); 255.4473 + if (range.collapsed) { 255.4474 + range.collapseAfter(gEBI(rangeInfos[i].markerId, doc)); 255.4475 + } else { 255.4476 + range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc)); 255.4477 + range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc)); 255.4478 + } 255.4479 + } 255.4480 + 255.4481 + return rangeInfos; 255.4482 + } 255.4483 + 255.4484 + function saveSelection(win) { 255.4485 + if (!api.isSelectionValid(win)) { 255.4486 + module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."); 255.4487 + return null; 255.4488 + } 255.4489 + var sel = api.getSelection(win); 255.4490 + var ranges = sel.getAllRanges(); 255.4491 + var backward = (ranges.length == 1 && sel.isBackward()); 255.4492 + 255.4493 + var rangeInfos = saveRanges(ranges, backward); 255.4494 + 255.4495 + // Ensure current selection is unaffected 255.4496 + if (backward) { 255.4497 + sel.setSingleRange(ranges[0], backward); 255.4498 + } else { 255.4499 + sel.setRanges(ranges); 255.4500 + } 255.4501 + 255.4502 + return { 255.4503 + win: win, 255.4504 + rangeInfos: rangeInfos, 255.4505 + restored: false 255.4506 + }; 255.4507 + } 255.4508 + 255.4509 + function restoreRanges(rangeInfos) { 255.4510 + var ranges = []; 255.4511 + 255.4512 + // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid 255.4513 + // normalization affecting previously restored ranges. 255.4514 + var rangeCount = rangeInfos.length; 255.4515 + 255.4516 + for (var i = rangeCount - 1; i >= 0; i--) { 255.4517 + ranges[i] = restoreRange(rangeInfos[i], true); 255.4518 + } 255.4519 + 255.4520 + return ranges; 255.4521 + } 255.4522 + 255.4523 + function restoreSelection(savedSelection, preserveDirection) { 255.4524 + if (!savedSelection.restored) { 255.4525 + var rangeInfos = savedSelection.rangeInfos; 255.4526 + var sel = api.getSelection(savedSelection.win); 255.4527 + var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length; 255.4528 + 255.4529 + if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) { 255.4530 + sel.removeAllRanges(); 255.4531 + sel.addRange(ranges[0], true); 255.4532 + } else { 255.4533 + sel.setRanges(ranges); 255.4534 + } 255.4535 + 255.4536 + savedSelection.restored = true; 255.4537 + } 255.4538 + } 255.4539 + 255.4540 + function removeMarkerElement(doc, markerId) { 255.4541 + var markerEl = gEBI(markerId, doc); 255.4542 + if (markerEl) { 255.4543 + removeNode(markerEl); 255.4544 + } 255.4545 + } 255.4546 + 255.4547 + function removeMarkers(savedSelection) { 255.4548 + var rangeInfos = savedSelection.rangeInfos; 255.4549 + for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) { 255.4550 + rangeInfo = rangeInfos[i]; 255.4551 + if (rangeInfo.collapsed) { 255.4552 + removeMarkerElement(savedSelection.doc, rangeInfo.markerId); 255.4553 + } else { 255.4554 + removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId); 255.4555 + removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId); 255.4556 + } 255.4557 + } 255.4558 + } 255.4559 + 255.4560 + api.util.extend(api, { 255.4561 + saveRange: saveRange, 255.4562 + restoreRange: restoreRange, 255.4563 + saveRanges: saveRanges, 255.4564 + restoreRanges: restoreRanges, 255.4565 + saveSelection: saveSelection, 255.4566 + restoreSelection: restoreSelection, 255.4567 + removeMarkerElement: removeMarkerElement, 255.4568 + removeMarkers: removeMarkers 255.4569 + }); 255.4570 +}); 255.4571 + 255.4572 +/** 255.4573 + * Text range module for Rangy. 255.4574 + * Text-based manipulation and searching of ranges and selections. 255.4575 + * 255.4576 + * Features 255.4577 + * 255.4578 + * - Ability to move range boundaries by character or word offsets 255.4579 + * - Customizable word tokenizer 255.4580 + * - Ignores text nodes inside <script> or <style> elements or those hidden by CSS display and visibility properties 255.4581 + * - Range findText method to search for text or regex within the page or within a range. Flags for whole words and case 255.4582 + * sensitivity 255.4583 + * - Selection and range save/restore as text offsets within a node 255.4584 + * - Methods to return visible text within a range or selection 255.4585 + * - innerText method for elements 255.4586 + * 255.4587 + * References 255.4588 + * 255.4589 + * https://www.w3.org/Bugs/Public/show_bug.cgi?id=13145 255.4590 + * http://aryeh.name/spec/innertext/innertext.html 255.4591 + * http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html 255.4592 + * 255.4593 + * Part of Rangy, a cross-browser JavaScript range and selection library 255.4594 + * https://github.com/timdown/rangy 255.4595 + * 255.4596 + * Depends on Rangy core. 255.4597 + * 255.4598 + * Copyright 2015, Tim Down 255.4599 + * Licensed under the MIT license. 255.4600 + * Version: 1.3.1-dev 255.4601 + * Build date: 20 May 2015 255.4602 + */ 255.4603 + 255.4604 +/** 255.4605 + * Problem: handling of trailing spaces before line breaks is handled inconsistently between browsers. 255.4606 + * 255.4607 + * First, a <br>: this is relatively simple. For the following HTML: 255.4608 + * 255.4609 + * 1 <br>2 255.4610 + * 255.4611 + * - IE and WebKit render the space, include it in the selection (i.e. when the content is selected and pasted into a 255.4612 + * textarea, the space is present) and allow the caret to be placed after it. 255.4613 + * - Firefox does not acknowledge the space in the selection but it is possible to place the caret after it. 255.4614 + * - Opera does not render the space but has two separate caret positions on either side of the space (left and right 255.4615 + * arrow keys show this) and includes the space in the selection. 255.4616 + * 255.4617 + * The other case is the line break or breaks implied by block elements. For the following HTML: 255.4618 + * 255.4619 + * <p>1 </p><p>2<p> 255.4620 + * 255.4621 + * - WebKit does not acknowledge the space in any way 255.4622 + * - Firefox, IE and Opera as per <br> 255.4623 + * 255.4624 + * One more case is trailing spaces before line breaks in elements with white-space: pre-line. For the following HTML: 255.4625 + * 255.4626 + * <p style="white-space: pre-line">1 255.4627 + * 2</p> 255.4628 + * 255.4629 + * - Firefox and WebKit include the space in caret positions 255.4630 + * - IE does not support pre-line up to and including version 9 255.4631 + * - Opera ignores the space 255.4632 + * - Trailing space only renders if there is a non-collapsed character in the line 255.4633 + * 255.4634 + * Problem is whether Rangy should ever acknowledge the space and if so, when. Another problem is whether this can be 255.4635 + * feature-tested 255.4636 + * 255.4637 + * NOTE: UMD wrapper removed manually for bundling (Oliver Pulges) 255.4638 +*/ 255.4639 +rangy.createModule("TextRange", ["WrappedSelection"], function(api, module) { 255.4640 + var UNDEF = "undefined"; 255.4641 + var CHARACTER = "character", WORD = "word"; 255.4642 + var dom = api.dom, util = api.util; 255.4643 + var extend = util.extend; 255.4644 + var createOptions = util.createOptions; 255.4645 + var getBody = dom.getBody; 255.4646 + 255.4647 + 255.4648 + var spacesRegex = /^[ \t\f\r\n]+$/; 255.4649 + var spacesMinusLineBreaksRegex = /^[ \t\f\r]+$/; 255.4650 + var allWhiteSpaceRegex = /^[\t-\r \u0085\u00A0\u1680\u180E\u2000-\u200B\u2028\u2029\u202F\u205F\u3000]+$/; 255.4651 + var nonLineBreakWhiteSpaceRegex = /^[\t \u00A0\u1680\u180E\u2000-\u200B\u202F\u205F\u3000]+$/; 255.4652 + var lineBreakRegex = /^[\n-\r\u0085\u2028\u2029]$/; 255.4653 + 255.4654 + var defaultLanguage = "en"; 255.4655 + 255.4656 + var isDirectionBackward = api.Selection.isDirectionBackward; 255.4657 + 255.4658 + // Properties representing whether trailing spaces inside blocks are completely collapsed (as they are in WebKit, 255.4659 + // but not other browsers). Also test whether trailing spaces before <br> elements are collapsed. 255.4660 + var trailingSpaceInBlockCollapses = false; 255.4661 + var trailingSpaceBeforeBrCollapses = false; 255.4662 + var trailingSpaceBeforeBlockCollapses = false; 255.4663 + var trailingSpaceBeforeLineBreakInPreLineCollapses = true; 255.4664 + 255.4665 + (function() { 255.4666 + var el = dom.createTestElement(document, "<p>1 </p><p></p>", true); 255.4667 + var p = el.firstChild; 255.4668 + var sel = api.getSelection(); 255.4669 + sel.collapse(p.lastChild, 2); 255.4670 + sel.setStart(p.firstChild, 0); 255.4671 + trailingSpaceInBlockCollapses = ("" + sel).length == 1; 255.4672 + 255.4673 + el.innerHTML = "1 <br />"; 255.4674 + sel.collapse(el, 2); 255.4675 + sel.setStart(el.firstChild, 0); 255.4676 + trailingSpaceBeforeBrCollapses = ("" + sel).length == 1; 255.4677 + 255.4678 + el.innerHTML = "1 <p>1</p>"; 255.4679 + sel.collapse(el, 2); 255.4680 + sel.setStart(el.firstChild, 0); 255.4681 + trailingSpaceBeforeBlockCollapses = ("" + sel).length == 1; 255.4682 + 255.4683 + dom.removeNode(el); 255.4684 + sel.removeAllRanges(); 255.4685 + })(); 255.4686 + 255.4687 + /*----------------------------------------------------------------------------------------------------------------*/ 255.4688 + 255.4689 + // This function must create word and non-word tokens for the whole of the text supplied to it 255.4690 + function defaultTokenizer(chars, wordOptions) { 255.4691 + var word = chars.join(""), result, tokenRanges = []; 255.4692 + 255.4693 + function createTokenRange(start, end, isWord) { 255.4694 + tokenRanges.push( { start: start, end: end, isWord: isWord } ); 255.4695 + } 255.4696 + 255.4697 + // Match words and mark characters 255.4698 + var lastWordEnd = 0, wordStart, wordEnd; 255.4699 + while ( (result = wordOptions.wordRegex.exec(word)) ) { 255.4700 + wordStart = result.index; 255.4701 + wordEnd = wordStart + result[0].length; 255.4702 + 255.4703 + // Create token for non-word characters preceding this word 255.4704 + if (wordStart > lastWordEnd) { 255.4705 + createTokenRange(lastWordEnd, wordStart, false); 255.4706 + } 255.4707 + 255.4708 + // Get trailing space characters for word 255.4709 + if (wordOptions.includeTrailingSpace) { 255.4710 + while ( nonLineBreakWhiteSpaceRegex.test(chars[wordEnd]) ) { 255.4711 + ++wordEnd; 255.4712 + } 255.4713 + } 255.4714 + createTokenRange(wordStart, wordEnd, true); 255.4715 + lastWordEnd = wordEnd; 255.4716 + } 255.4717 + 255.4718 + // Create token for trailing non-word characters, if any exist 255.4719 + if (lastWordEnd < chars.length) { 255.4720 + createTokenRange(lastWordEnd, chars.length, false); 255.4721 + } 255.4722 + 255.4723 + return tokenRanges; 255.4724 + } 255.4725 + 255.4726 + function convertCharRangeToToken(chars, tokenRange) { 255.4727 + var tokenChars = chars.slice(tokenRange.start, tokenRange.end); 255.4728 + var token = { 255.4729 + isWord: tokenRange.isWord, 255.4730 + chars: tokenChars, 255.4731 + toString: function() { 255.4732 + return tokenChars.join(""); 255.4733 + } 255.4734 + }; 255.4735 + for (var i = 0, len = tokenChars.length; i < len; ++i) { 255.4736 + tokenChars[i].token = token; 255.4737 + } 255.4738 + return token; 255.4739 + } 255.4740 + 255.4741 + function tokenize(chars, wordOptions, tokenizer) { 255.4742 + var tokenRanges = tokenizer(chars, wordOptions); 255.4743 + var tokens = []; 255.4744 + for (var i = 0, tokenRange; tokenRange = tokenRanges[i++]; ) { 255.4745 + tokens.push( convertCharRangeToToken(chars, tokenRange) ); 255.4746 + } 255.4747 + return tokens; 255.4748 + } 255.4749 + 255.4750 + var defaultCharacterOptions = { 255.4751 + includeBlockContentTrailingSpace: true, 255.4752 + includeSpaceBeforeBr: true, 255.4753 + includeSpaceBeforeBlock: true, 255.4754 + includePreLineTrailingSpace: true, 255.4755 + ignoreCharacters: "" 255.4756 + }; 255.4757 + 255.4758 + function normalizeIgnoredCharacters(ignoredCharacters) { 255.4759 + // Check if character is ignored 255.4760 + var ignoredChars = ignoredCharacters || ""; 255.4761 + 255.4762 + // Normalize ignored characters into a string consisting of characters in ascending order of character code 255.4763 + var ignoredCharsArray = (typeof ignoredChars == "string") ? ignoredChars.split("") : ignoredChars; 255.4764 + ignoredCharsArray.sort(function(char1, char2) { 255.4765 + return char1.charCodeAt(0) - char2.charCodeAt(0); 255.4766 + }); 255.4767 + 255.4768 + /// Convert back to a string and remove duplicates 255.4769 + return ignoredCharsArray.join("").replace(/(.)\1+/g, "$1"); 255.4770 + } 255.4771 + 255.4772 + var defaultCaretCharacterOptions = { 255.4773 + includeBlockContentTrailingSpace: !trailingSpaceBeforeLineBreakInPreLineCollapses, 255.4774 + includeSpaceBeforeBr: !trailingSpaceBeforeBrCollapses, 255.4775 + includeSpaceBeforeBlock: !trailingSpaceBeforeBlockCollapses, 255.4776 + includePreLineTrailingSpace: true 255.4777 + }; 255.4778 + 255.4779 + var defaultWordOptions = { 255.4780 + "en": { 255.4781 + wordRegex: /[a-z0-9]+('[a-z0-9]+)*/gi, 255.4782 + includeTrailingSpace: false, 255.4783 + tokenizer: defaultTokenizer 255.4784 + } 255.4785 + }; 255.4786 + 255.4787 + var defaultFindOptions = { 255.4788 + caseSensitive: false, 255.4789 + withinRange: null, 255.4790 + wholeWordsOnly: false, 255.4791 + wrap: false, 255.4792 + direction: "forward", 255.4793 + wordOptions: null, 255.4794 + characterOptions: null 255.4795 + }; 255.4796 + 255.4797 + var defaultMoveOptions = { 255.4798 + wordOptions: null, 255.4799 + characterOptions: null 255.4800 + }; 255.4801 + 255.4802 + var defaultExpandOptions = { 255.4803 + wordOptions: null, 255.4804 + characterOptions: null, 255.4805 + trim: false, 255.4806 + trimStart: true, 255.4807 + trimEnd: true 255.4808 + }; 255.4809 + 255.4810 + var defaultWordIteratorOptions = { 255.4811 + wordOptions: null, 255.4812 + characterOptions: null, 255.4813 + direction: "forward" 255.4814 + }; 255.4815 + 255.4816 + function createWordOptions(options) { 255.4817 + var lang, defaults; 255.4818 + if (!options) { 255.4819 + return defaultWordOptions[defaultLanguage]; 255.4820 + } else { 255.4821 + lang = options.language || defaultLanguage; 255.4822 + defaults = {}; 255.4823 + extend(defaults, defaultWordOptions[lang] || defaultWordOptions[defaultLanguage]); 255.4824 + extend(defaults, options); 255.4825 + return defaults; 255.4826 + } 255.4827 + } 255.4828 + 255.4829 + function createNestedOptions(optionsParam, defaults) { 255.4830 + var options = createOptions(optionsParam, defaults); 255.4831 + if (defaults.hasOwnProperty("wordOptions")) { 255.4832 + options.wordOptions = createWordOptions(options.wordOptions); 255.4833 + } 255.4834 + if (defaults.hasOwnProperty("characterOptions")) { 255.4835 + options.characterOptions = createOptions(options.characterOptions, defaultCharacterOptions); 255.4836 + } 255.4837 + return options; 255.4838 + } 255.4839 + 255.4840 + /*----------------------------------------------------------------------------------------------------------------*/ 255.4841 + 255.4842 + /* DOM utility functions */ 255.4843 + var getComputedStyleProperty = dom.getComputedStyleProperty; 255.4844 + 255.4845 + // Create cachable versions of DOM functions 255.4846 + 255.4847 + // Test for old IE's incorrect display properties 255.4848 + var tableCssDisplayBlock; 255.4849 + (function() { 255.4850 + var table = document.createElement("table"); 255.4851 + var body = getBody(document); 255.4852 + body.appendChild(table); 255.4853 + tableCssDisplayBlock = (getComputedStyleProperty(table, "display") == "block"); 255.4854 + body.removeChild(table); 255.4855 + })(); 255.4856 + 255.4857 + var defaultDisplayValueForTag = { 255.4858 + table: "table", 255.4859 + caption: "table-caption", 255.4860 + colgroup: "table-column-group", 255.4861 + col: "table-column", 255.4862 + thead: "table-header-group", 255.4863 + tbody: "table-row-group", 255.4864 + tfoot: "table-footer-group", 255.4865 + tr: "table-row", 255.4866 + td: "table-cell", 255.4867 + th: "table-cell" 255.4868 + }; 255.4869 + 255.4870 + // Corrects IE's "block" value for table-related elements 255.4871 + function getComputedDisplay(el, win) { 255.4872 + var display = getComputedStyleProperty(el, "display", win); 255.4873 + var tagName = el.tagName.toLowerCase(); 255.4874 + return (display == "block" && 255.4875 + tableCssDisplayBlock && 255.4876 + defaultDisplayValueForTag.hasOwnProperty(tagName)) ? 255.4877 + defaultDisplayValueForTag[tagName] : display; 255.4878 + } 255.4879 + 255.4880 + function isHidden(node) { 255.4881 + var ancestors = getAncestorsAndSelf(node); 255.4882 + for (var i = 0, len = ancestors.length; i < len; ++i) { 255.4883 + if (ancestors[i].nodeType == 1 && getComputedDisplay(ancestors[i]) == "none") { 255.4884 + return true; 255.4885 + } 255.4886 + } 255.4887 + 255.4888 + return false; 255.4889 + } 255.4890 + 255.4891 + function isVisibilityHiddenTextNode(textNode) { 255.4892 + var el; 255.4893 + return textNode.nodeType == 3 && 255.4894 + (el = textNode.parentNode) && 255.4895 + getComputedStyleProperty(el, "visibility") == "hidden"; 255.4896 + } 255.4897 + 255.4898 + /*----------------------------------------------------------------------------------------------------------------*/ 255.4899 + 255.4900 + 255.4901 + // "A block node is either an Element whose "display" property does not have 255.4902 + // resolved value "inline" or "inline-block" or "inline-table" or "none", or a 255.4903 + // Document, or a DocumentFragment." 255.4904 + function isBlockNode(node) { 255.4905 + return node && 255.4906 + ((node.nodeType == 1 && !/^(inline(-block|-table)?|none)$/.test(getComputedDisplay(node))) || 255.4907 + node.nodeType == 9 || node.nodeType == 11); 255.4908 + } 255.4909 + 255.4910 + function getLastDescendantOrSelf(node) { 255.4911 + var lastChild = node.lastChild; 255.4912 + return lastChild ? getLastDescendantOrSelf(lastChild) : node; 255.4913 + } 255.4914 + 255.4915 + function containsPositions(node) { 255.4916 + return dom.isCharacterDataNode(node) || 255.4917 + !/^(area|base|basefont|br|col|frame|hr|img|input|isindex|link|meta|param)$/i.test(node.nodeName); 255.4918 + } 255.4919 + 255.4920 + function getAncestors(node) { 255.4921 + var ancestors = []; 255.4922 + while (node.parentNode) { 255.4923 + ancestors.unshift(node.parentNode); 255.4924 + node = node.parentNode; 255.4925 + } 255.4926 + return ancestors; 255.4927 + } 255.4928 + 255.4929 + function getAncestorsAndSelf(node) { 255.4930 + return getAncestors(node).concat([node]); 255.4931 + } 255.4932 + 255.4933 + function nextNodeDescendants(node) { 255.4934 + while (node && !node.nextSibling) { 255.4935 + node = node.parentNode; 255.4936 + } 255.4937 + if (!node) { 255.4938 + return null; 255.4939 + } 255.4940 + return node.nextSibling; 255.4941 + } 255.4942 + 255.4943 + function nextNode(node, excludeChildren) { 255.4944 + if (!excludeChildren && node.hasChildNodes()) { 255.4945 + return node.firstChild; 255.4946 + } 255.4947 + return nextNodeDescendants(node); 255.4948 + } 255.4949 + 255.4950 + function previousNode(node) { 255.4951 + var previous = node.previousSibling; 255.4952 + if (previous) { 255.4953 + node = previous; 255.4954 + while (node.hasChildNodes()) { 255.4955 + node = node.lastChild; 255.4956 + } 255.4957 + return node; 255.4958 + } 255.4959 + var parent = node.parentNode; 255.4960 + if (parent && parent.nodeType == 1) { 255.4961 + return parent; 255.4962 + } 255.4963 + return null; 255.4964 + } 255.4965 + 255.4966 + // Adpated from Aryeh's code. 255.4967 + // "A whitespace node is either a Text node whose data is the empty string; or 255.4968 + // a Text node whose data consists only of one or more tabs (0x0009), line 255.4969 + // feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose 255.4970 + // parent is an Element whose resolved value for "white-space" is "normal" or 255.4971 + // "nowrap"; or a Text node whose data consists only of one or more tabs 255.4972 + // (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose 255.4973 + // parent is an Element whose resolved value for "white-space" is "pre-line"." 255.4974 + function isWhitespaceNode(node) { 255.4975 + if (!node || node.nodeType != 3) { 255.4976 + return false; 255.4977 + } 255.4978 + var text = node.data; 255.4979 + if (text === "") { 255.4980 + return true; 255.4981 + } 255.4982 + var parent = node.parentNode; 255.4983 + if (!parent || parent.nodeType != 1) { 255.4984 + return false; 255.4985 + } 255.4986 + var computedWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace"); 255.4987 + 255.4988 + return (/^[\t\n\r ]+$/.test(text) && /^(normal|nowrap)$/.test(computedWhiteSpace)) || 255.4989 + (/^[\t\r ]+$/.test(text) && computedWhiteSpace == "pre-line"); 255.4990 + } 255.4991 + 255.4992 + // Adpated from Aryeh's code. 255.4993 + // "node is a collapsed whitespace node if the following algorithm returns 255.4994 + // true:" 255.4995 + function isCollapsedWhitespaceNode(node) { 255.4996 + // "If node's data is the empty string, return true." 255.4997 + if (node.data === "") { 255.4998 + return true; 255.4999 + } 255.5000 + 255.5001 + // "If node is not a whitespace node, return false." 255.5002 + if (!isWhitespaceNode(node)) { 255.5003 + return false; 255.5004 + } 255.5005 + 255.5006 + // "Let ancestor be node's parent." 255.5007 + var ancestor = node.parentNode; 255.5008 + 255.5009 + // "If ancestor is null, return true." 255.5010 + if (!ancestor) { 255.5011 + return true; 255.5012 + } 255.5013 + 255.5014 + // "If the "display" property of some ancestor of node has resolved value "none", return true." 255.5015 + if (isHidden(node)) { 255.5016 + return true; 255.5017 + } 255.5018 + 255.5019 + return false; 255.5020 + } 255.5021 + 255.5022 + function isCollapsedNode(node) { 255.5023 + var type = node.nodeType; 255.5024 + return type == 7 /* PROCESSING_INSTRUCTION */ || 255.5025 + type == 8 /* COMMENT */ || 255.5026 + isHidden(node) || 255.5027 + /^(script|style)$/i.test(node.nodeName) || 255.5028 + isVisibilityHiddenTextNode(node) || 255.5029 + isCollapsedWhitespaceNode(node); 255.5030 + } 255.5031 + 255.5032 + function isIgnoredNode(node, win) { 255.5033 + var type = node.nodeType; 255.5034 + return type == 7 /* PROCESSING_INSTRUCTION */ || 255.5035 + type == 8 /* COMMENT */ || 255.5036 + (type == 1 && getComputedDisplay(node, win) == "none"); 255.5037 + } 255.5038 + 255.5039 + /*----------------------------------------------------------------------------------------------------------------*/ 255.5040 + 255.5041 + // Possibly overengineered caching system to prevent repeated DOM calls slowing everything down 255.5042 + 255.5043 + function Cache() { 255.5044 + this.store = {}; 255.5045 + } 255.5046 + 255.5047 + Cache.prototype = { 255.5048 + get: function(key) { 255.5049 + return this.store.hasOwnProperty(key) ? this.store[key] : null; 255.5050 + }, 255.5051 + 255.5052 + set: function(key, value) { 255.5053 + return this.store[key] = value; 255.5054 + } 255.5055 + }; 255.5056 + 255.5057 + var cachedCount = 0, uncachedCount = 0; 255.5058 + 255.5059 + function createCachingGetter(methodName, func, objProperty) { 255.5060 + return function(args) { 255.5061 + var cache = this.cache; 255.5062 + if (cache.hasOwnProperty(methodName)) { 255.5063 + cachedCount++; 255.5064 + return cache[methodName]; 255.5065 + } else { 255.5066 + uncachedCount++; 255.5067 + var value = func.call(this, objProperty ? this[objProperty] : this, args); 255.5068 + cache[methodName] = value; 255.5069 + return value; 255.5070 + } 255.5071 + }; 255.5072 + } 255.5073 + 255.5074 + /*----------------------------------------------------------------------------------------------------------------*/ 255.5075 + 255.5076 + function NodeWrapper(node, session) { 255.5077 + this.node = node; 255.5078 + this.session = session; 255.5079 + this.cache = new Cache(); 255.5080 + this.positions = new Cache(); 255.5081 + } 255.5082 + 255.5083 + var nodeProto = { 255.5084 + getPosition: function(offset) { 255.5085 + var positions = this.positions; 255.5086 + return positions.get(offset) || positions.set(offset, new Position(this, offset)); 255.5087 + }, 255.5088 + 255.5089 + toString: function() { 255.5090 + return "[NodeWrapper(" + dom.inspectNode(this.node) + ")]"; 255.5091 + } 255.5092 + }; 255.5093 + 255.5094 + NodeWrapper.prototype = nodeProto; 255.5095 + 255.5096 + var EMPTY = "EMPTY", 255.5097 + NON_SPACE = "NON_SPACE", 255.5098 + UNCOLLAPSIBLE_SPACE = "UNCOLLAPSIBLE_SPACE", 255.5099 + COLLAPSIBLE_SPACE = "COLLAPSIBLE_SPACE", 255.5100 + TRAILING_SPACE_BEFORE_BLOCK = "TRAILING_SPACE_BEFORE_BLOCK", 255.5101 + TRAILING_SPACE_IN_BLOCK = "TRAILING_SPACE_IN_BLOCK", 255.5102 + TRAILING_SPACE_BEFORE_BR = "TRAILING_SPACE_BEFORE_BR", 255.5103 + PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK = "PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK", 255.5104 + TRAILING_LINE_BREAK_AFTER_BR = "TRAILING_LINE_BREAK_AFTER_BR", 255.5105 + INCLUDED_TRAILING_LINE_BREAK_AFTER_BR = "INCLUDED_TRAILING_LINE_BREAK_AFTER_BR"; 255.5106 + 255.5107 + extend(nodeProto, { 255.5108 + isCharacterDataNode: createCachingGetter("isCharacterDataNode", dom.isCharacterDataNode, "node"), 255.5109 + getNodeIndex: createCachingGetter("nodeIndex", dom.getNodeIndex, "node"), 255.5110 + getLength: createCachingGetter("nodeLength", dom.getNodeLength, "node"), 255.5111 + containsPositions: createCachingGetter("containsPositions", containsPositions, "node"), 255.5112 + isWhitespace: createCachingGetter("isWhitespace", isWhitespaceNode, "node"), 255.5113 + isCollapsedWhitespace: createCachingGetter("isCollapsedWhitespace", isCollapsedWhitespaceNode, "node"), 255.5114 + getComputedDisplay: createCachingGetter("computedDisplay", getComputedDisplay, "node"), 255.5115 + isCollapsed: createCachingGetter("collapsed", isCollapsedNode, "node"), 255.5116 + isIgnored: createCachingGetter("ignored", isIgnoredNode, "node"), 255.5117 + next: createCachingGetter("nextPos", nextNode, "node"), 255.5118 + previous: createCachingGetter("previous", previousNode, "node"), 255.5119 + 255.5120 + getTextNodeInfo: createCachingGetter("textNodeInfo", function(textNode) { 255.5121 + var spaceRegex = null, collapseSpaces = false; 255.5122 + var cssWhitespace = getComputedStyleProperty(textNode.parentNode, "whiteSpace"); 255.5123 + var preLine = (cssWhitespace == "pre-line"); 255.5124 + if (preLine) { 255.5125 + spaceRegex = spacesMinusLineBreaksRegex; 255.5126 + collapseSpaces = true; 255.5127 + } else if (cssWhitespace == "normal" || cssWhitespace == "nowrap") { 255.5128 + spaceRegex = spacesRegex; 255.5129 + collapseSpaces = true; 255.5130 + } 255.5131 + 255.5132 + return { 255.5133 + node: textNode, 255.5134 + text: textNode.data, 255.5135 + spaceRegex: spaceRegex, 255.5136 + collapseSpaces: collapseSpaces, 255.5137 + preLine: preLine 255.5138 + }; 255.5139 + }, "node"), 255.5140 + 255.5141 + hasInnerText: createCachingGetter("hasInnerText", function(el, backward) { 255.5142 + var session = this.session; 255.5143 + var posAfterEl = session.getPosition(el.parentNode, this.getNodeIndex() + 1); 255.5144 + var firstPosInEl = session.getPosition(el, 0); 255.5145 + 255.5146 + var pos = backward ? posAfterEl : firstPosInEl; 255.5147 + var endPos = backward ? firstPosInEl : posAfterEl; 255.5148 + 255.5149 + /* 255.5150 + <body><p>X </p><p>Y</p></body> 255.5151 + 255.5152 + Positions: 255.5153 + 255.5154 + body:0:"" 255.5155 + p:0:"" 255.5156 + text:0:"" 255.5157 + text:1:"X" 255.5158 + text:2:TRAILING_SPACE_IN_BLOCK 255.5159 + text:3:COLLAPSED_SPACE 255.5160 + p:1:"" 255.5161 + body:1:"\n" 255.5162 + p:0:"" 255.5163 + text:0:"" 255.5164 + text:1:"Y" 255.5165 + 255.5166 + A character is a TRAILING_SPACE_IN_BLOCK iff: 255.5167 + 255.5168 + - There is no uncollapsed character after it within the visible containing block element 255.5169 + 255.5170 + A character is a TRAILING_SPACE_BEFORE_BR iff: 255.5171 + 255.5172 + - There is no uncollapsed character after it preceding a <br> element 255.5173 + 255.5174 + An element has inner text iff 255.5175 + 255.5176 + - It is not hidden 255.5177 + - It contains an uncollapsed character 255.5178 + 255.5179 + All trailing spaces (pre-line, before <br>, end of block) require definite non-empty characters to render. 255.5180 + */ 255.5181 + 255.5182 + while (pos !== endPos) { 255.5183 + pos.prepopulateChar(); 255.5184 + if (pos.isDefinitelyNonEmpty()) { 255.5185 + return true; 255.5186 + } 255.5187 + pos = backward ? pos.previousVisible() : pos.nextVisible(); 255.5188 + } 255.5189 + 255.5190 + return false; 255.5191 + }, "node"), 255.5192 + 255.5193 + isRenderedBlock: createCachingGetter("isRenderedBlock", function(el) { 255.5194 + // Ensure that a block element containing a <br> is considered to have inner text 255.5195 + var brs = el.getElementsByTagName("br"); 255.5196 + for (var i = 0, len = brs.length; i < len; ++i) { 255.5197 + if (!isCollapsedNode(brs[i])) { 255.5198 + return true; 255.5199 + } 255.5200 + } 255.5201 + return this.hasInnerText(); 255.5202 + }, "node"), 255.5203 + 255.5204 + getTrailingSpace: createCachingGetter("trailingSpace", function(el) { 255.5205 + if (el.tagName.toLowerCase() == "br") { 255.5206 + return ""; 255.5207 + } else { 255.5208 + switch (this.getComputedDisplay()) { 255.5209 + case "inline": 255.5210 + var child = el.lastChild; 255.5211 + while (child) { 255.5212 + if (!isIgnoredNode(child)) { 255.5213 + return (child.nodeType == 1) ? this.session.getNodeWrapper(child).getTrailingSpace() : ""; 255.5214 + } 255.5215 + child = child.previousSibling; 255.5216 + } 255.5217 + break; 255.5218 + case "inline-block": 255.5219 + case "inline-table": 255.5220 + case "none": 255.5221 + case "table-column": 255.5222 + case "table-column-group": 255.5223 + break; 255.5224 + case "table-cell": 255.5225 + return "\t"; 255.5226 + default: 255.5227 + return this.isRenderedBlock(true) ? "\n" : ""; 255.5228 + } 255.5229 + } 255.5230 + return ""; 255.5231 + }, "node"), 255.5232 + 255.5233 + getLeadingSpace: createCachingGetter("leadingSpace", function(el) { 255.5234 + switch (this.getComputedDisplay()) { 255.5235 + case "inline": 255.5236 + case "inline-block": 255.5237 + case "inline-table": 255.5238 + case "none": 255.5239 + case "table-column": 255.5240 + case "table-column-group": 255.5241 + case "table-cell": 255.5242 + break; 255.5243 + default: 255.5244 + return this.isRenderedBlock(false) ? "\n" : ""; 255.5245 + } 255.5246 + return ""; 255.5247 + }, "node") 255.5248 + }); 255.5249 + 255.5250 + /*----------------------------------------------------------------------------------------------------------------*/ 255.5251 + 255.5252 + function Position(nodeWrapper, offset) { 255.5253 + this.offset = offset; 255.5254 + this.nodeWrapper = nodeWrapper; 255.5255 + this.node = nodeWrapper.node; 255.5256 + this.session = nodeWrapper.session; 255.5257 + this.cache = new Cache(); 255.5258 + } 255.5259 + 255.5260 + function inspectPosition() { 255.5261 + return "[Position(" + dom.inspectNode(this.node) + ":" + this.offset + ")]"; 255.5262 + } 255.5263 + 255.5264 + var positionProto = { 255.5265 + character: "", 255.5266 + characterType: EMPTY, 255.5267 + isBr: false, 255.5268 + 255.5269 + /* 255.5270 + This method: 255.5271 + - Fully populates positions that have characters that can be determined independently of any other characters. 255.5272 + - Populates most types of space positions with a provisional character. The character is finalized later. 255.5273 + */ 255.5274 + prepopulateChar: function() { 255.5275 + var pos = this; 255.5276 + if (!pos.prepopulatedChar) { 255.5277 + var node = pos.node, offset = pos.offset; 255.5278 + var visibleChar = "", charType = EMPTY; 255.5279 + var finalizedChar = false; 255.5280 + if (offset > 0) { 255.5281 + if (node.nodeType == 3) { 255.5282 + var text = node.data; 255.5283 + var textChar = text.charAt(offset - 1); 255.5284 + 255.5285 + var nodeInfo = pos.nodeWrapper.getTextNodeInfo(); 255.5286 + var spaceRegex = nodeInfo.spaceRegex; 255.5287 + if (nodeInfo.collapseSpaces) { 255.5288 + if (spaceRegex.test(textChar)) { 255.5289 + // "If the character at position is from set, append a single space (U+0020) to newdata and advance 255.5290 + // position until the character at position is not from set." 255.5291 + 255.5292 + // We also need to check for the case where we're in a pre-line and we have a space preceding a 255.5293 + // line break, because such spaces are collapsed in some browsers 255.5294 + if (offset > 1 && spaceRegex.test(text.charAt(offset - 2))) { 255.5295 + } else if (nodeInfo.preLine && text.charAt(offset) === "\n") { 255.5296 + visibleChar = " "; 255.5297 + charType = PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK; 255.5298 + } else { 255.5299 + visibleChar = " "; 255.5300 + //pos.checkForFollowingLineBreak = true; 255.5301 + charType = COLLAPSIBLE_SPACE; 255.5302 + } 255.5303 + } else { 255.5304 + visibleChar = textChar; 255.5305 + charType = NON_SPACE; 255.5306 + finalizedChar = true; 255.5307 + } 255.5308 + } else { 255.5309 + visibleChar = textChar; 255.5310 + charType = UNCOLLAPSIBLE_SPACE; 255.5311 + finalizedChar = true; 255.5312 + } 255.5313 + } else { 255.5314 + var nodePassed = node.childNodes[offset - 1]; 255.5315 + if (nodePassed && nodePassed.nodeType == 1 && !isCollapsedNode(nodePassed)) { 255.5316 + if (nodePassed.tagName.toLowerCase() == "br") { 255.5317 + visibleChar = "\n"; 255.5318 + pos.isBr = true; 255.5319 + charType = COLLAPSIBLE_SPACE; 255.5320 + finalizedChar = false; 255.5321 + } else { 255.5322 + pos.checkForTrailingSpace = true; 255.5323 + } 255.5324 + } 255.5325 + 255.5326 + // Check the leading space of the next node for the case when a block element follows an inline 255.5327 + // element or text node. In that case, there is an implied line break between the two nodes. 255.5328 + if (!visibleChar) { 255.5329 + var nextNode = node.childNodes[offset]; 255.5330 + if (nextNode && nextNode.nodeType == 1 && !isCollapsedNode(nextNode)) { 255.5331 + pos.checkForLeadingSpace = true; 255.5332 + } 255.5333 + } 255.5334 + } 255.5335 + } 255.5336 + 255.5337 + pos.prepopulatedChar = true; 255.5338 + pos.character = visibleChar; 255.5339 + pos.characterType = charType; 255.5340 + pos.isCharInvariant = finalizedChar; 255.5341 + } 255.5342 + }, 255.5343 + 255.5344 + isDefinitelyNonEmpty: function() { 255.5345 + var charType = this.characterType; 255.5346 + return charType == NON_SPACE || charType == UNCOLLAPSIBLE_SPACE; 255.5347 + }, 255.5348 + 255.5349 + // Resolve leading and trailing spaces, which may involve prepopulating other positions 255.5350 + resolveLeadingAndTrailingSpaces: function() { 255.5351 + if (!this.prepopulatedChar) { 255.5352 + this.prepopulateChar(); 255.5353 + } 255.5354 + if (this.checkForTrailingSpace) { 255.5355 + var trailingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset - 1]).getTrailingSpace(); 255.5356 + if (trailingSpace) { 255.5357 + this.isTrailingSpace = true; 255.5358 + this.character = trailingSpace; 255.5359 + this.characterType = COLLAPSIBLE_SPACE; 255.5360 + } 255.5361 + this.checkForTrailingSpace = false; 255.5362 + } 255.5363 + if (this.checkForLeadingSpace) { 255.5364 + var leadingSpace = this.session.getNodeWrapper(this.node.childNodes[this.offset]).getLeadingSpace(); 255.5365 + if (leadingSpace) { 255.5366 + this.isLeadingSpace = true; 255.5367 + this.character = leadingSpace; 255.5368 + this.characterType = COLLAPSIBLE_SPACE; 255.5369 + } 255.5370 + this.checkForLeadingSpace = false; 255.5371 + } 255.5372 + }, 255.5373 + 255.5374 + getPrecedingUncollapsedPosition: function(characterOptions) { 255.5375 + var pos = this, character; 255.5376 + while ( (pos = pos.previousVisible()) ) { 255.5377 + character = pos.getCharacter(characterOptions); 255.5378 + if (character !== "") { 255.5379 + return pos; 255.5380 + } 255.5381 + } 255.5382 + 255.5383 + return null; 255.5384 + }, 255.5385 + 255.5386 + getCharacter: function(characterOptions) { 255.5387 + this.resolveLeadingAndTrailingSpaces(); 255.5388 + 255.5389 + var thisChar = this.character, returnChar; 255.5390 + 255.5391 + // Check if character is ignored 255.5392 + var ignoredChars = normalizeIgnoredCharacters(characterOptions.ignoreCharacters); 255.5393 + var isIgnoredCharacter = (thisChar !== "" && ignoredChars.indexOf(thisChar) > -1); 255.5394 + 255.5395 + // Check if this position's character is invariant (i.e. not dependent on character options) and return it 255.5396 + // if so 255.5397 + if (this.isCharInvariant) { 255.5398 + returnChar = isIgnoredCharacter ? "" : thisChar; 255.5399 + return returnChar; 255.5400 + } 255.5401 + 255.5402 + var cacheKey = ["character", characterOptions.includeSpaceBeforeBr, characterOptions.includeBlockContentTrailingSpace, characterOptions.includePreLineTrailingSpace, ignoredChars].join("_"); 255.5403 + var cachedChar = this.cache.get(cacheKey); 255.5404 + if (cachedChar !== null) { 255.5405 + return cachedChar; 255.5406 + } 255.5407 + 255.5408 + // We need to actually get the character now 255.5409 + var character = ""; 255.5410 + var collapsible = (this.characterType == COLLAPSIBLE_SPACE); 255.5411 + 255.5412 + var nextPos, previousPos; 255.5413 + var gotPreviousPos = false; 255.5414 + var pos = this; 255.5415 + 255.5416 + function getPreviousPos() { 255.5417 + if (!gotPreviousPos) { 255.5418 + previousPos = pos.getPrecedingUncollapsedPosition(characterOptions); 255.5419 + gotPreviousPos = true; 255.5420 + } 255.5421 + return previousPos; 255.5422 + } 255.5423 + 255.5424 + // Disallow a collapsible space that is followed by a line break or is the last character 255.5425 + if (collapsible) { 255.5426 + // Allow a trailing space that we've previously determined should be included 255.5427 + if (this.type == INCLUDED_TRAILING_LINE_BREAK_AFTER_BR) { 255.5428 + character = "\n"; 255.5429 + } 255.5430 + // Disallow a collapsible space that follows a trailing space or line break, or is the first character, 255.5431 + // or follows a collapsible included space 255.5432 + else if (thisChar == " " && 255.5433 + (!getPreviousPos() || previousPos.isTrailingSpace || previousPos.character == "\n" || (previousPos.character == " " && previousPos.characterType == COLLAPSIBLE_SPACE))) { 255.5434 + } 255.5435 + // Allow a leading line break unless it follows a line break 255.5436 + else if (thisChar == "\n" && this.isLeadingSpace) { 255.5437 + if (getPreviousPos() && previousPos.character != "\n") { 255.5438 + character = "\n"; 255.5439 + } else { 255.5440 + } 255.5441 + } else { 255.5442 + nextPos = this.nextUncollapsed(); 255.5443 + if (nextPos) { 255.5444 + if (nextPos.isBr) { 255.5445 + this.type = TRAILING_SPACE_BEFORE_BR; 255.5446 + } else if (nextPos.isTrailingSpace && nextPos.character == "\n") { 255.5447 + this.type = TRAILING_SPACE_IN_BLOCK; 255.5448 + } else if (nextPos.isLeadingSpace && nextPos.character == "\n") { 255.5449 + this.type = TRAILING_SPACE_BEFORE_BLOCK; 255.5450 + } 255.5451 + 255.5452 + if (nextPos.character == "\n") { 255.5453 + if (this.type == TRAILING_SPACE_BEFORE_BR && !characterOptions.includeSpaceBeforeBr) { 255.5454 + } else if (this.type == TRAILING_SPACE_BEFORE_BLOCK && !characterOptions.includeSpaceBeforeBlock) { 255.5455 + } else if (this.type == TRAILING_SPACE_IN_BLOCK && nextPos.isTrailingSpace && !characterOptions.includeBlockContentTrailingSpace) { 255.5456 + } else if (this.type == PRE_LINE_TRAILING_SPACE_BEFORE_LINE_BREAK && nextPos.type == NON_SPACE && !characterOptions.includePreLineTrailingSpace) { 255.5457 + } else if (thisChar == "\n") { 255.5458 + if (nextPos.isTrailingSpace) { 255.5459 + if (this.isTrailingSpace) { 255.5460 + } else if (this.isBr) { 255.5461 + nextPos.type = TRAILING_LINE_BREAK_AFTER_BR; 255.5462 + 255.5463 + if (getPreviousPos() && previousPos.isLeadingSpace && !previousPos.isTrailingSpace && previousPos.character == "\n") { 255.5464 + nextPos.character = ""; 255.5465 + } else { 255.5466 + nextPos.type = INCLUDED_TRAILING_LINE_BREAK_AFTER_BR; 255.5467 + } 255.5468 + } 255.5469 + } else { 255.5470 + character = "\n"; 255.5471 + } 255.5472 + } else if (thisChar == " ") { 255.5473 + character = " "; 255.5474 + } else { 255.5475 + } 255.5476 + } else { 255.5477 + character = thisChar; 255.5478 + } 255.5479 + } else { 255.5480 + } 255.5481 + } 255.5482 + } 255.5483 + 255.5484 + if (ignoredChars.indexOf(character) > -1) { 255.5485 + character = ""; 255.5486 + } 255.5487 + 255.5488 + 255.5489 + this.cache.set(cacheKey, character); 255.5490 + 255.5491 + return character; 255.5492 + }, 255.5493 + 255.5494 + equals: function(pos) { 255.5495 + return !!pos && this.node === pos.node && this.offset === pos.offset; 255.5496 + }, 255.5497 + 255.5498 + inspect: inspectPosition, 255.5499 + 255.5500 + toString: function() { 255.5501 + return this.character; 255.5502 + } 255.5503 + }; 255.5504 + 255.5505 + Position.prototype = positionProto; 255.5506 + 255.5507 + extend(positionProto, { 255.5508 + next: createCachingGetter("nextPos", function(pos) { 255.5509 + var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session; 255.5510 + if (!node) { 255.5511 + return null; 255.5512 + } 255.5513 + var nextNode, nextOffset, child; 255.5514 + if (offset == nodeWrapper.getLength()) { 255.5515 + // Move onto the next node 255.5516 + nextNode = node.parentNode; 255.5517 + nextOffset = nextNode ? nodeWrapper.getNodeIndex() + 1 : 0; 255.5518 + } else { 255.5519 + if (nodeWrapper.isCharacterDataNode()) { 255.5520 + nextNode = node; 255.5521 + nextOffset = offset + 1; 255.5522 + } else { 255.5523 + child = node.childNodes[offset]; 255.5524 + // Go into the children next, if children there are 255.5525 + if (session.getNodeWrapper(child).containsPositions()) { 255.5526 + nextNode = child; 255.5527 + nextOffset = 0; 255.5528 + } else { 255.5529 + nextNode = node; 255.5530 + nextOffset = offset + 1; 255.5531 + } 255.5532 + } 255.5533 + } 255.5534 + 255.5535 + return nextNode ? session.getPosition(nextNode, nextOffset) : null; 255.5536 + }), 255.5537 + 255.5538 + previous: createCachingGetter("previous", function(pos) { 255.5539 + var nodeWrapper = pos.nodeWrapper, node = pos.node, offset = pos.offset, session = nodeWrapper.session; 255.5540 + var previousNode, previousOffset, child; 255.5541 + if (offset == 0) { 255.5542 + previousNode = node.parentNode; 255.5543 + previousOffset = previousNode ? nodeWrapper.getNodeIndex() : 0; 255.5544 + } else { 255.5545 + if (nodeWrapper.isCharacterDataNode()) { 255.5546 + previousNode = node; 255.5547 + previousOffset = offset - 1; 255.5548 + } else { 255.5549 + child = node.childNodes[offset - 1]; 255.5550 + // Go into the children next, if children there are 255.5551 + if (session.getNodeWrapper(child).containsPositions()) { 255.5552 + previousNode = child; 255.5553 + previousOffset = dom.getNodeLength(child); 255.5554 + } else { 255.5555 + previousNode = node; 255.5556 + previousOffset = offset - 1; 255.5557 + } 255.5558 + } 255.5559 + } 255.5560 + return previousNode ? session.getPosition(previousNode, previousOffset) : null; 255.5561 + }), 255.5562 + 255.5563 + /* 255.5564 + Next and previous position moving functions that filter out 255.5565 + 255.5566 + - Hidden (CSS visibility/display) elements 255.5567 + - Script and style elements 255.5568 + */ 255.5569 + nextVisible: createCachingGetter("nextVisible", function(pos) { 255.5570 + var next = pos.next(); 255.5571 + if (!next) { 255.5572 + return null; 255.5573 + } 255.5574 + var nodeWrapper = next.nodeWrapper, node = next.node; 255.5575 + var newPos = next; 255.5576 + if (nodeWrapper.isCollapsed()) { 255.5577 + // We're skipping this node and all its descendants 255.5578 + newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex() + 1); 255.5579 + } 255.5580 + return newPos; 255.5581 + }), 255.5582 + 255.5583 + nextUncollapsed: createCachingGetter("nextUncollapsed", function(pos) { 255.5584 + var nextPos = pos; 255.5585 + while ( (nextPos = nextPos.nextVisible()) ) { 255.5586 + nextPos.resolveLeadingAndTrailingSpaces(); 255.5587 + if (nextPos.character !== "") { 255.5588 + return nextPos; 255.5589 + } 255.5590 + } 255.5591 + return null; 255.5592 + }), 255.5593 + 255.5594 + previousVisible: createCachingGetter("previousVisible", function(pos) { 255.5595 + var previous = pos.previous(); 255.5596 + if (!previous) { 255.5597 + return null; 255.5598 + } 255.5599 + var nodeWrapper = previous.nodeWrapper, node = previous.node; 255.5600 + var newPos = previous; 255.5601 + if (nodeWrapper.isCollapsed()) { 255.5602 + // We're skipping this node and all its descendants 255.5603 + newPos = nodeWrapper.session.getPosition(node.parentNode, nodeWrapper.getNodeIndex()); 255.5604 + } 255.5605 + return newPos; 255.5606 + }) 255.5607 + }); 255.5608 + 255.5609 + /*----------------------------------------------------------------------------------------------------------------*/ 255.5610 + 255.5611 + var currentSession = null; 255.5612 + 255.5613 + var Session = (function() { 255.5614 + function createWrapperCache(nodeProperty) { 255.5615 + var cache = new Cache(); 255.5616 + 255.5617 + return { 255.5618 + get: function(node) { 255.5619 + var wrappersByProperty = cache.get(node[nodeProperty]); 255.5620 + if (wrappersByProperty) { 255.5621 + for (var i = 0, wrapper; wrapper = wrappersByProperty[i++]; ) { 255.5622 + if (wrapper.node === node) { 255.5623 + return wrapper; 255.5624 + } 255.5625 + } 255.5626 + } 255.5627 + return null; 255.5628 + }, 255.5629 + 255.5630 + set: function(nodeWrapper) { 255.5631 + var property = nodeWrapper.node[nodeProperty]; 255.5632 + var wrappersByProperty = cache.get(property) || cache.set(property, []); 255.5633 + wrappersByProperty.push(nodeWrapper); 255.5634 + } 255.5635 + }; 255.5636 + } 255.5637 + 255.5638 + var uniqueIDSupported = util.isHostProperty(document.documentElement, "uniqueID"); 255.5639 + 255.5640 + function Session() { 255.5641 + this.initCaches(); 255.5642 + } 255.5643 + 255.5644 + Session.prototype = { 255.5645 + initCaches: function() { 255.5646 + this.elementCache = uniqueIDSupported ? (function() { 255.5647 + var elementsCache = new Cache(); 255.5648 + 255.5649 + return { 255.5650 + get: function(el) { 255.5651 + return elementsCache.get(el.uniqueID); 255.5652 + }, 255.5653 + 255.5654 + set: function(elWrapper) { 255.5655 + elementsCache.set(elWrapper.node.uniqueID, elWrapper); 255.5656 + } 255.5657 + }; 255.5658 + })() : createWrapperCache("tagName"); 255.5659 + 255.5660 + // Store text nodes keyed by data, although we may need to truncate this 255.5661 + this.textNodeCache = createWrapperCache("data"); 255.5662 + this.otherNodeCache = createWrapperCache("nodeName"); 255.5663 + }, 255.5664 + 255.5665 + getNodeWrapper: function(node) { 255.5666 + var wrapperCache; 255.5667 + switch (node.nodeType) { 255.5668 + case 1: 255.5669 + wrapperCache = this.elementCache; 255.5670 + break; 255.5671 + case 3: 255.5672 + wrapperCache = this.textNodeCache; 255.5673 + break; 255.5674 + default: 255.5675 + wrapperCache = this.otherNodeCache; 255.5676 + break; 255.5677 + } 255.5678 + 255.5679 + var wrapper = wrapperCache.get(node); 255.5680 + if (!wrapper) { 255.5681 + wrapper = new NodeWrapper(node, this); 255.5682 + wrapperCache.set(wrapper); 255.5683 + } 255.5684 + return wrapper; 255.5685 + }, 255.5686 + 255.5687 + getPosition: function(node, offset) { 255.5688 + return this.getNodeWrapper(node).getPosition(offset); 255.5689 + }, 255.5690 + 255.5691 + getRangeBoundaryPosition: function(range, isStart) { 255.5692 + var prefix = isStart ? "start" : "end"; 255.5693 + return this.getPosition(range[prefix + "Container"], range[prefix + "Offset"]); 255.5694 + }, 255.5695 + 255.5696 + detach: function() { 255.5697 + this.elementCache = this.textNodeCache = this.otherNodeCache = null; 255.5698 + } 255.5699 + }; 255.5700 + 255.5701 + return Session; 255.5702 + })(); 255.5703 + 255.5704 + /*----------------------------------------------------------------------------------------------------------------*/ 255.5705 + 255.5706 + function startSession() { 255.5707 + endSession(); 255.5708 + return (currentSession = new Session()); 255.5709 + } 255.5710 + 255.5711 + function getSession() { 255.5712 + return currentSession || startSession(); 255.5713 + } 255.5714 + 255.5715 + function endSession() { 255.5716 + if (currentSession) { 255.5717 + currentSession.detach(); 255.5718 + } 255.5719 + currentSession = null; 255.5720 + } 255.5721 + 255.5722 + /*----------------------------------------------------------------------------------------------------------------*/ 255.5723 + 255.5724 + // Extensions to the rangy.dom utility object 255.5725 + 255.5726 + extend(dom, { 255.5727 + nextNode: nextNode, 255.5728 + previousNode: previousNode 255.5729 + }); 255.5730 + 255.5731 + /*----------------------------------------------------------------------------------------------------------------*/ 255.5732 + 255.5733 + function createCharacterIterator(startPos, backward, endPos, characterOptions) { 255.5734 + 255.5735 + // Adjust the end position to ensure that it is actually reached 255.5736 + if (endPos) { 255.5737 + if (backward) { 255.5738 + if (isCollapsedNode(endPos.node)) { 255.5739 + endPos = startPos.previousVisible(); 255.5740 + } 255.5741 + } else { 255.5742 + if (isCollapsedNode(endPos.node)) { 255.5743 + endPos = endPos.nextVisible(); 255.5744 + } 255.5745 + } 255.5746 + } 255.5747 + 255.5748 + var pos = startPos, finished = false; 255.5749 + 255.5750 + function next() { 255.5751 + var charPos = null; 255.5752 + if (backward) { 255.5753 + charPos = pos; 255.5754 + if (!finished) { 255.5755 + pos = pos.previousVisible(); 255.5756 + finished = !pos || (endPos && pos.equals(endPos)); 255.5757 + } 255.5758 + } else { 255.5759 + if (!finished) { 255.5760 + charPos = pos = pos.nextVisible(); 255.5761 + finished = !pos || (endPos && pos.equals(endPos)); 255.5762 + } 255.5763 + } 255.5764 + if (finished) { 255.5765 + pos = null; 255.5766 + } 255.5767 + return charPos; 255.5768 + } 255.5769 + 255.5770 + var previousTextPos, returnPreviousTextPos = false; 255.5771 + 255.5772 + return { 255.5773 + next: function() { 255.5774 + if (returnPreviousTextPos) { 255.5775 + returnPreviousTextPos = false; 255.5776 + return previousTextPos; 255.5777 + } else { 255.5778 + var pos, character; 255.5779 + while ( (pos = next()) ) { 255.5780 + character = pos.getCharacter(characterOptions); 255.5781 + if (character) { 255.5782 + previousTextPos = pos; 255.5783 + return pos; 255.5784 + } 255.5785 + } 255.5786 + return null; 255.5787 + } 255.5788 + }, 255.5789 + 255.5790 + rewind: function() { 255.5791 + if (previousTextPos) { 255.5792 + returnPreviousTextPos = true; 255.5793 + } else { 255.5794 + throw module.createError("createCharacterIterator: cannot rewind. Only one position can be rewound."); 255.5795 + } 255.5796 + }, 255.5797 + 255.5798 + dispose: function() { 255.5799 + startPos = endPos = null; 255.5800 + } 255.5801 + }; 255.5802 + } 255.5803 + 255.5804 + var arrayIndexOf = Array.prototype.indexOf ? 255.5805 + function(arr, val) { 255.5806 + return arr.indexOf(val); 255.5807 + } : 255.5808 + function(arr, val) { 255.5809 + for (var i = 0, len = arr.length; i < len; ++i) { 255.5810 + if (arr[i] === val) { 255.5811 + return i; 255.5812 + } 255.5813 + } 255.5814 + return -1; 255.5815 + }; 255.5816 + 255.5817 + // Provides a pair of iterators over text positions, tokenized. Transparently requests more text when next() 255.5818 + // is called and there is no more tokenized text 255.5819 + function createTokenizedTextProvider(pos, characterOptions, wordOptions) { 255.5820 + var forwardIterator = createCharacterIterator(pos, false, null, characterOptions); 255.5821 + var backwardIterator = createCharacterIterator(pos, true, null, characterOptions); 255.5822 + var tokenizer = wordOptions.tokenizer; 255.5823 + 255.5824 + // Consumes a word and the whitespace beyond it 255.5825 + function consumeWord(forward) { 255.5826 + var pos, textChar; 255.5827 + var newChars = [], it = forward ? forwardIterator : backwardIterator; 255.5828 + 255.5829 + var passedWordBoundary = false, insideWord = false; 255.5830 + 255.5831 + while ( (pos = it.next()) ) { 255.5832 + textChar = pos.character; 255.5833 + 255.5834 + 255.5835 + if (allWhiteSpaceRegex.test(textChar)) { 255.5836 + if (insideWord) { 255.5837 + insideWord = false; 255.5838 + passedWordBoundary = true; 255.5839 + } 255.5840 + } else { 255.5841 + if (passedWordBoundary) { 255.5842 + it.rewind(); 255.5843 + break; 255.5844 + } else { 255.5845 + insideWord = true; 255.5846 + } 255.5847 + } 255.5848 + newChars.push(pos); 255.5849 + } 255.5850 + 255.5851 + 255.5852 + return newChars; 255.5853 + } 255.5854 + 255.5855 + // Get initial word surrounding initial position and tokenize it 255.5856 + var forwardChars = consumeWord(true); 255.5857 + var backwardChars = consumeWord(false).reverse(); 255.5858 + var tokens = tokenize(backwardChars.concat(forwardChars), wordOptions, tokenizer); 255.5859 + 255.5860 + // Create initial token buffers 255.5861 + var forwardTokensBuffer = forwardChars.length ? 255.5862 + tokens.slice(arrayIndexOf(tokens, forwardChars[0].token)) : []; 255.5863 + 255.5864 + var backwardTokensBuffer = backwardChars.length ? 255.5865 + tokens.slice(0, arrayIndexOf(tokens, backwardChars.pop().token) + 1) : []; 255.5866 + 255.5867 + function inspectBuffer(buffer) { 255.5868 + var textPositions = ["[" + buffer.length + "]"]; 255.5869 + for (var i = 0; i < buffer.length; ++i) { 255.5870 + textPositions.push("(word: " + buffer[i] + ", is word: " + buffer[i].isWord + ")"); 255.5871 + } 255.5872 + return textPositions; 255.5873 + } 255.5874 + 255.5875 + 255.5876 + return { 255.5877 + nextEndToken: function() { 255.5878 + var lastToken, forwardChars; 255.5879 + 255.5880 + // If we're down to the last token, consume character chunks until we have a word or run out of 255.5881 + // characters to consume 255.5882 + while ( forwardTokensBuffer.length == 1 && 255.5883 + !(lastToken = forwardTokensBuffer[0]).isWord && 255.5884 + (forwardChars = consumeWord(true)).length > 0) { 255.5885 + 255.5886 + // Merge trailing non-word into next word and tokenize 255.5887 + forwardTokensBuffer = tokenize(lastToken.chars.concat(forwardChars), wordOptions, tokenizer); 255.5888 + } 255.5889 + 255.5890 + return forwardTokensBuffer.shift(); 255.5891 + }, 255.5892 + 255.5893 + previousStartToken: function() { 255.5894 + var lastToken, backwardChars; 255.5895 + 255.5896 + // If we're down to the last token, consume character chunks until we have a word or run out of 255.5897 + // characters to consume 255.5898 + while ( backwardTokensBuffer.length == 1 && 255.5899 + !(lastToken = backwardTokensBuffer[0]).isWord && 255.5900 + (backwardChars = consumeWord(false)).length > 0) { 255.5901 + 255.5902 + // Merge leading non-word into next word and tokenize 255.5903 + backwardTokensBuffer = tokenize(backwardChars.reverse().concat(lastToken.chars), wordOptions, tokenizer); 255.5904 + } 255.5905 + 255.5906 + return backwardTokensBuffer.pop(); 255.5907 + }, 255.5908 + 255.5909 + dispose: function() { 255.5910 + forwardIterator.dispose(); 255.5911 + backwardIterator.dispose(); 255.5912 + forwardTokensBuffer = backwardTokensBuffer = null; 255.5913 + } 255.5914 + }; 255.5915 + } 255.5916 + 255.5917 + function movePositionBy(pos, unit, count, characterOptions, wordOptions) { 255.5918 + var unitsMoved = 0, currentPos, newPos = pos, charIterator, nextPos, absCount = Math.abs(count), token; 255.5919 + if (count !== 0) { 255.5920 + var backward = (count < 0); 255.5921 + 255.5922 + switch (unit) { 255.5923 + case CHARACTER: 255.5924 + charIterator = createCharacterIterator(pos, backward, null, characterOptions); 255.5925 + while ( (currentPos = charIterator.next()) && unitsMoved < absCount ) { 255.5926 + ++unitsMoved; 255.5927 + newPos = currentPos; 255.5928 + } 255.5929 + nextPos = currentPos; 255.5930 + charIterator.dispose(); 255.5931 + break; 255.5932 + case WORD: 255.5933 + var tokenizedTextProvider = createTokenizedTextProvider(pos, characterOptions, wordOptions); 255.5934 + var next = backward ? tokenizedTextProvider.previousStartToken : tokenizedTextProvider.nextEndToken; 255.5935 + 255.5936 + while ( (token = next()) && unitsMoved < absCount ) { 255.5937 + if (token.isWord) { 255.5938 + ++unitsMoved; 255.5939 + newPos = backward ? token.chars[0] : token.chars[token.chars.length - 1]; 255.5940 + } 255.5941 + } 255.5942 + break; 255.5943 + default: 255.5944 + throw new Error("movePositionBy: unit '" + unit + "' not implemented"); 255.5945 + } 255.5946 + 255.5947 + // Perform any necessary position tweaks 255.5948 + if (backward) { 255.5949 + newPos = newPos.previousVisible(); 255.5950 + unitsMoved = -unitsMoved; 255.5951 + } else if (newPos && newPos.isLeadingSpace && !newPos.isTrailingSpace) { 255.5952 + // Tweak the position for the case of a leading space. The problem is that an uncollapsed leading space 255.5953 + // before a block element (for example, the line break between "1" and "2" in the following HTML: 255.5954 + // "1<p>2</p>") is considered to be attached to the position immediately before the block element, which 255.5955 + // corresponds with a different selection position in most browsers from the one we want (i.e. at the 255.5956 + // start of the contents of the block element). We get round this by advancing the position returned to 255.5957 + // the last possible equivalent visible position. 255.5958 + if (unit == WORD) { 255.5959 + charIterator = createCharacterIterator(pos, false, null, characterOptions); 255.5960 + nextPos = charIterator.next(); 255.5961 + charIterator.dispose(); 255.5962 + } 255.5963 + if (nextPos) { 255.5964 + newPos = nextPos.previousVisible(); 255.5965 + } 255.5966 + } 255.5967 + } 255.5968 + 255.5969 + 255.5970 + return { 255.5971 + position: newPos, 255.5972 + unitsMoved: unitsMoved 255.5973 + }; 255.5974 + } 255.5975 + 255.5976 + function createRangeCharacterIterator(session, range, characterOptions, backward) { 255.5977 + var rangeStart = session.getRangeBoundaryPosition(range, true); 255.5978 + var rangeEnd = session.getRangeBoundaryPosition(range, false); 255.5979 + var itStart = backward ? rangeEnd : rangeStart; 255.5980 + var itEnd = backward ? rangeStart : rangeEnd; 255.5981 + 255.5982 + return createCharacterIterator(itStart, !!backward, itEnd, characterOptions); 255.5983 + } 255.5984 + 255.5985 + function getRangeCharacters(session, range, characterOptions) { 255.5986 + 255.5987 + var chars = [], it = createRangeCharacterIterator(session, range, characterOptions), pos; 255.5988 + while ( (pos = it.next()) ) { 255.5989 + chars.push(pos); 255.5990 + } 255.5991 + 255.5992 + it.dispose(); 255.5993 + return chars; 255.5994 + } 255.5995 + 255.5996 + function isWholeWord(startPos, endPos, wordOptions) { 255.5997 + var range = api.createRange(startPos.node); 255.5998 + range.setStartAndEnd(startPos.node, startPos.offset, endPos.node, endPos.offset); 255.5999 + return !range.expand("word", { wordOptions: wordOptions }); 255.6000 + } 255.6001 + 255.6002 + function findTextFromPosition(initialPos, searchTerm, isRegex, searchScopeRange, findOptions) { 255.6003 + var backward = isDirectionBackward(findOptions.direction); 255.6004 + var it = createCharacterIterator( 255.6005 + initialPos, 255.6006 + backward, 255.6007 + initialPos.session.getRangeBoundaryPosition(searchScopeRange, backward), 255.6008 + findOptions.characterOptions 255.6009 + ); 255.6010 + var text = "", chars = [], pos, currentChar, matchStartIndex, matchEndIndex; 255.6011 + var result, insideRegexMatch; 255.6012 + var returnValue = null; 255.6013 + 255.6014 + function handleMatch(startIndex, endIndex) { 255.6015 + var startPos = chars[startIndex].previousVisible(); 255.6016 + var endPos = chars[endIndex - 1]; 255.6017 + var valid = (!findOptions.wholeWordsOnly || isWholeWord(startPos, endPos, findOptions.wordOptions)); 255.6018 + 255.6019 + return { 255.6020 + startPos: startPos, 255.6021 + endPos: endPos, 255.6022 + valid: valid 255.6023 + }; 255.6024 + } 255.6025 + 255.6026 + while ( (pos = it.next()) ) { 255.6027 + currentChar = pos.character; 255.6028 + if (!isRegex && !findOptions.caseSensitive) { 255.6029 + currentChar = currentChar.toLowerCase(); 255.6030 + } 255.6031 + 255.6032 + if (backward) { 255.6033 + chars.unshift(pos); 255.6034 + text = currentChar + text; 255.6035 + } else { 255.6036 + chars.push(pos); 255.6037 + text += currentChar; 255.6038 + } 255.6039 + 255.6040 + if (isRegex) { 255.6041 + result = searchTerm.exec(text); 255.6042 + if (result) { 255.6043 + matchStartIndex = result.index; 255.6044 + matchEndIndex = matchStartIndex + result[0].length; 255.6045 + if (insideRegexMatch) { 255.6046 + // Check whether the match is now over 255.6047 + if ((!backward && matchEndIndex < text.length) || (backward && matchStartIndex > 0)) { 255.6048 + returnValue = handleMatch(matchStartIndex, matchEndIndex); 255.6049 + break; 255.6050 + } 255.6051 + } else { 255.6052 + insideRegexMatch = true; 255.6053 + } 255.6054 + } 255.6055 + } else if ( (matchStartIndex = text.indexOf(searchTerm)) != -1 ) { 255.6056 + returnValue = handleMatch(matchStartIndex, matchStartIndex + searchTerm.length); 255.6057 + break; 255.6058 + } 255.6059 + } 255.6060 + 255.6061 + // Check whether regex match extends to the end of the range 255.6062 + if (insideRegexMatch) { 255.6063 + returnValue = handleMatch(matchStartIndex, matchEndIndex); 255.6064 + } 255.6065 + it.dispose(); 255.6066 + 255.6067 + return returnValue; 255.6068 + } 255.6069 + 255.6070 + function createEntryPointFunction(func) { 255.6071 + return function() { 255.6072 + var sessionRunning = !!currentSession; 255.6073 + var session = getSession(); 255.6074 + var args = [session].concat( util.toArray(arguments) ); 255.6075 + var returnValue = func.apply(this, args); 255.6076 + if (!sessionRunning) { 255.6077 + endSession(); 255.6078 + } 255.6079 + return returnValue; 255.6080 + }; 255.6081 + } 255.6082 + 255.6083 + /*----------------------------------------------------------------------------------------------------------------*/ 255.6084 + 255.6085 + // Extensions to the Rangy Range object 255.6086 + 255.6087 + function createRangeBoundaryMover(isStart, collapse) { 255.6088 + /* 255.6089 + Unit can be "character" or "word" 255.6090 + Options: 255.6091 + 255.6092 + - includeTrailingSpace 255.6093 + - wordRegex 255.6094 + - tokenizer 255.6095 + - collapseSpaceBeforeLineBreak 255.6096 + */ 255.6097 + return createEntryPointFunction( 255.6098 + function(session, unit, count, moveOptions) { 255.6099 + if (typeof count == UNDEF) { 255.6100 + count = unit; 255.6101 + unit = CHARACTER; 255.6102 + } 255.6103 + moveOptions = createNestedOptions(moveOptions, defaultMoveOptions); 255.6104 + 255.6105 + var boundaryIsStart = isStart; 255.6106 + if (collapse) { 255.6107 + boundaryIsStart = (count >= 0); 255.6108 + this.collapse(!boundaryIsStart); 255.6109 + } 255.6110 + var moveResult = movePositionBy(session.getRangeBoundaryPosition(this, boundaryIsStart), unit, count, moveOptions.characterOptions, moveOptions.wordOptions); 255.6111 + var newPos = moveResult.position; 255.6112 + this[boundaryIsStart ? "setStart" : "setEnd"](newPos.node, newPos.offset); 255.6113 + return moveResult.unitsMoved; 255.6114 + } 255.6115 + ); 255.6116 + } 255.6117 + 255.6118 + function createRangeTrimmer(isStart) { 255.6119 + return createEntryPointFunction( 255.6120 + function(session, characterOptions) { 255.6121 + characterOptions = createOptions(characterOptions, defaultCharacterOptions); 255.6122 + var pos; 255.6123 + var it = createRangeCharacterIterator(session, this, characterOptions, !isStart); 255.6124 + var trimCharCount = 0; 255.6125 + while ( (pos = it.next()) && allWhiteSpaceRegex.test(pos.character) ) { 255.6126 + ++trimCharCount; 255.6127 + } 255.6128 + it.dispose(); 255.6129 + var trimmed = (trimCharCount > 0); 255.6130 + if (trimmed) { 255.6131 + this[isStart ? "moveStart" : "moveEnd"]( 255.6132 + "character", 255.6133 + isStart ? trimCharCount : -trimCharCount, 255.6134 + { characterOptions: characterOptions } 255.6135 + ); 255.6136 + } 255.6137 + return trimmed; 255.6138 + } 255.6139 + ); 255.6140 + } 255.6141 + 255.6142 + extend(api.rangePrototype, { 255.6143 + moveStart: createRangeBoundaryMover(true, false), 255.6144 + 255.6145 + moveEnd: createRangeBoundaryMover(false, false), 255.6146 + 255.6147 + move: createRangeBoundaryMover(true, true), 255.6148 + 255.6149 + trimStart: createRangeTrimmer(true), 255.6150 + 255.6151 + trimEnd: createRangeTrimmer(false), 255.6152 + 255.6153 + trim: createEntryPointFunction( 255.6154 + function(session, characterOptions) { 255.6155 + var startTrimmed = this.trimStart(characterOptions), endTrimmed = this.trimEnd(characterOptions); 255.6156 + return startTrimmed || endTrimmed; 255.6157 + } 255.6158 + ), 255.6159 + 255.6160 + expand: createEntryPointFunction( 255.6161 + function(session, unit, expandOptions) { 255.6162 + var moved = false; 255.6163 + expandOptions = createNestedOptions(expandOptions, defaultExpandOptions); 255.6164 + var characterOptions = expandOptions.characterOptions; 255.6165 + if (!unit) { 255.6166 + unit = CHARACTER; 255.6167 + } 255.6168 + if (unit == WORD) { 255.6169 + var wordOptions = expandOptions.wordOptions; 255.6170 + var startPos = session.getRangeBoundaryPosition(this, true); 255.6171 + var endPos = session.getRangeBoundaryPosition(this, false); 255.6172 + 255.6173 + var startTokenizedTextProvider = createTokenizedTextProvider(startPos, characterOptions, wordOptions); 255.6174 + var startToken = startTokenizedTextProvider.nextEndToken(); 255.6175 + var newStartPos = startToken.chars[0].previousVisible(); 255.6176 + var endToken, newEndPos; 255.6177 + 255.6178 + if (this.collapsed) { 255.6179 + endToken = startToken; 255.6180 + } else { 255.6181 + var endTokenizedTextProvider = createTokenizedTextProvider(endPos, characterOptions, wordOptions); 255.6182 + endToken = endTokenizedTextProvider.previousStartToken(); 255.6183 + } 255.6184 + newEndPos = endToken.chars[endToken.chars.length - 1]; 255.6185 + 255.6186 + if (!newStartPos.equals(startPos)) { 255.6187 + this.setStart(newStartPos.node, newStartPos.offset); 255.6188 + moved = true; 255.6189 + } 255.6190 + if (newEndPos && !newEndPos.equals(endPos)) { 255.6191 + this.setEnd(newEndPos.node, newEndPos.offset); 255.6192 + moved = true; 255.6193 + } 255.6194 + 255.6195 + if (expandOptions.trim) { 255.6196 + if (expandOptions.trimStart) { 255.6197 + moved = this.trimStart(characterOptions) || moved; 255.6198 + } 255.6199 + if (expandOptions.trimEnd) { 255.6200 + moved = this.trimEnd(characterOptions) || moved; 255.6201 + } 255.6202 + } 255.6203 + 255.6204 + return moved; 255.6205 + } else { 255.6206 + return this.moveEnd(CHARACTER, 1, expandOptions); 255.6207 + } 255.6208 + } 255.6209 + ), 255.6210 + 255.6211 + text: createEntryPointFunction( 255.6212 + function(session, characterOptions) { 255.6213 + return this.collapsed ? 255.6214 + "" : getRangeCharacters(session, this, createOptions(characterOptions, defaultCharacterOptions)).join(""); 255.6215 + } 255.6216 + ), 255.6217 + 255.6218 + selectCharacters: createEntryPointFunction( 255.6219 + function(session, containerNode, startIndex, endIndex, characterOptions) { 255.6220 + var moveOptions = { characterOptions: characterOptions }; 255.6221 + if (!containerNode) { 255.6222 + containerNode = getBody( this.getDocument() ); 255.6223 + } 255.6224 + this.selectNodeContents(containerNode); 255.6225 + this.collapse(true); 255.6226 + this.moveStart("character", startIndex, moveOptions); 255.6227 + this.collapse(true); 255.6228 + this.moveEnd("character", endIndex - startIndex, moveOptions); 255.6229 + } 255.6230 + ), 255.6231 + 255.6232 + // Character indexes are relative to the start of node 255.6233 + toCharacterRange: createEntryPointFunction( 255.6234 + function(session, containerNode, characterOptions) { 255.6235 + if (!containerNode) { 255.6236 + containerNode = getBody( this.getDocument() ); 255.6237 + } 255.6238 + var parent = containerNode.parentNode, nodeIndex = dom.getNodeIndex(containerNode); 255.6239 + var rangeStartsBeforeNode = (dom.comparePoints(this.startContainer, this.endContainer, parent, nodeIndex) == -1); 255.6240 + var rangeBetween = this.cloneRange(); 255.6241 + var startIndex, endIndex; 255.6242 + if (rangeStartsBeforeNode) { 255.6243 + rangeBetween.setStartAndEnd(this.startContainer, this.startOffset, parent, nodeIndex); 255.6244 + startIndex = -rangeBetween.text(characterOptions).length; 255.6245 + } else { 255.6246 + rangeBetween.setStartAndEnd(parent, nodeIndex, this.startContainer, this.startOffset); 255.6247 + startIndex = rangeBetween.text(characterOptions).length; 255.6248 + } 255.6249 + endIndex = startIndex + this.text(characterOptions).length; 255.6250 + 255.6251 + return { 255.6252 + start: startIndex, 255.6253 + end: endIndex 255.6254 + }; 255.6255 + } 255.6256 + ), 255.6257 + 255.6258 + findText: createEntryPointFunction( 255.6259 + function(session, searchTermParam, findOptions) { 255.6260 + // Set up options 255.6261 + findOptions = createNestedOptions(findOptions, defaultFindOptions); 255.6262 + 255.6263 + // Create word options if we're matching whole words only 255.6264 + if (findOptions.wholeWordsOnly) { 255.6265 + // We don't ever want trailing spaces for search results 255.6266 + findOptions.wordOptions.includeTrailingSpace = false; 255.6267 + } 255.6268 + 255.6269 + var backward = isDirectionBackward(findOptions.direction); 255.6270 + 255.6271 + // Create a range representing the search scope if none was provided 255.6272 + var searchScopeRange = findOptions.withinRange; 255.6273 + if (!searchScopeRange) { 255.6274 + searchScopeRange = api.createRange(); 255.6275 + searchScopeRange.selectNodeContents(this.getDocument()); 255.6276 + } 255.6277 + 255.6278 + // Examine and prepare the search term 255.6279 + var searchTerm = searchTermParam, isRegex = false; 255.6280 + if (typeof searchTerm == "string") { 255.6281 + if (!findOptions.caseSensitive) { 255.6282 + searchTerm = searchTerm.toLowerCase(); 255.6283 + } 255.6284 + } else { 255.6285 + isRegex = true; 255.6286 + } 255.6287 + 255.6288 + var initialPos = session.getRangeBoundaryPosition(this, !backward); 255.6289 + 255.6290 + // Adjust initial position if it lies outside the search scope 255.6291 + var comparison = searchScopeRange.comparePoint(initialPos.node, initialPos.offset); 255.6292 + 255.6293 + if (comparison === -1) { 255.6294 + initialPos = session.getRangeBoundaryPosition(searchScopeRange, true); 255.6295 + } else if (comparison === 1) { 255.6296 + initialPos = session.getRangeBoundaryPosition(searchScopeRange, false); 255.6297 + } 255.6298 + 255.6299 + var pos = initialPos; 255.6300 + var wrappedAround = false; 255.6301 + 255.6302 + // Try to find a match and ignore invalid ones 255.6303 + var findResult; 255.6304 + while (true) { 255.6305 + findResult = findTextFromPosition(pos, searchTerm, isRegex, searchScopeRange, findOptions); 255.6306 + 255.6307 + if (findResult) { 255.6308 + if (findResult.valid) { 255.6309 + this.setStartAndEnd(findResult.startPos.node, findResult.startPos.offset, findResult.endPos.node, findResult.endPos.offset); 255.6310 + return true; 255.6311 + } else { 255.6312 + // We've found a match that is not a whole word, so we carry on searching from the point immediately 255.6313 + // after the match 255.6314 + pos = backward ? findResult.startPos : findResult.endPos; 255.6315 + } 255.6316 + } else if (findOptions.wrap && !wrappedAround) { 255.6317 + // No result found but we're wrapping around and limiting the scope to the unsearched part of the range 255.6318 + searchScopeRange = searchScopeRange.cloneRange(); 255.6319 + pos = session.getRangeBoundaryPosition(searchScopeRange, !backward); 255.6320 + searchScopeRange.setBoundary(initialPos.node, initialPos.offset, backward); 255.6321 + wrappedAround = true; 255.6322 + } else { 255.6323 + // Nothing found and we can't wrap around, so we're done 255.6324 + return false; 255.6325 + } 255.6326 + } 255.6327 + } 255.6328 + ), 255.6329 + 255.6330 + pasteHtml: function(html) { 255.6331 + this.deleteContents(); 255.6332 + if (html) { 255.6333 + var frag = this.createContextualFragment(html); 255.6334 + var lastChild = frag.lastChild; 255.6335 + this.insertNode(frag); 255.6336 + this.collapseAfter(lastChild); 255.6337 + } 255.6338 + } 255.6339 + }); 255.6340 + 255.6341 + /*----------------------------------------------------------------------------------------------------------------*/ 255.6342 + 255.6343 + // Extensions to the Rangy Selection object 255.6344 + 255.6345 + function createSelectionTrimmer(methodName) { 255.6346 + return createEntryPointFunction( 255.6347 + function(session, characterOptions) { 255.6348 + var trimmed = false; 255.6349 + this.changeEachRange(function(range) { 255.6350 + trimmed = range[methodName](characterOptions) || trimmed; 255.6351 + }); 255.6352 + return trimmed; 255.6353 + } 255.6354 + ); 255.6355 + } 255.6356 + 255.6357 + extend(api.selectionPrototype, { 255.6358 + expand: createEntryPointFunction( 255.6359 + function(session, unit, expandOptions) { 255.6360 + this.changeEachRange(function(range) { 255.6361 + range.expand(unit, expandOptions); 255.6362 + }); 255.6363 + } 255.6364 + ), 255.6365 + 255.6366 + move: createEntryPointFunction( 255.6367 + function(session, unit, count, options) { 255.6368 + var unitsMoved = 0; 255.6369 + if (this.focusNode) { 255.6370 + this.collapse(this.focusNode, this.focusOffset); 255.6371 + var range = this.getRangeAt(0); 255.6372 + if (!options) { 255.6373 + options = {}; 255.6374 + } 255.6375 + options.characterOptions = createOptions(options.characterOptions, defaultCaretCharacterOptions); 255.6376 + unitsMoved = range.move(unit, count, options); 255.6377 + this.setSingleRange(range); 255.6378 + } 255.6379 + return unitsMoved; 255.6380 + } 255.6381 + ), 255.6382 + 255.6383 + trimStart: createSelectionTrimmer("trimStart"), 255.6384 + trimEnd: createSelectionTrimmer("trimEnd"), 255.6385 + trim: createSelectionTrimmer("trim"), 255.6386 + 255.6387 + selectCharacters: createEntryPointFunction( 255.6388 + function(session, containerNode, startIndex, endIndex, direction, characterOptions) { 255.6389 + var range = api.createRange(containerNode); 255.6390 + range.selectCharacters(containerNode, startIndex, endIndex, characterOptions); 255.6391 + this.setSingleRange(range, direction); 255.6392 + } 255.6393 + ), 255.6394 + 255.6395 + saveCharacterRanges: createEntryPointFunction( 255.6396 + function(session, containerNode, characterOptions) { 255.6397 + var ranges = this.getAllRanges(), rangeCount = ranges.length; 255.6398 + var rangeInfos = []; 255.6399 + 255.6400 + var backward = rangeCount == 1 && this.isBackward(); 255.6401 + 255.6402 + for (var i = 0, len = ranges.length; i < len; ++i) { 255.6403 + rangeInfos[i] = { 255.6404 + characterRange: ranges[i].toCharacterRange(containerNode, characterOptions), 255.6405 + backward: backward, 255.6406 + characterOptions: characterOptions 255.6407 + }; 255.6408 + } 255.6409 + 255.6410 + return rangeInfos; 255.6411 + } 255.6412 + ), 255.6413 + 255.6414 + restoreCharacterRanges: createEntryPointFunction( 255.6415 + function(session, containerNode, saved) { 255.6416 + this.removeAllRanges(); 255.6417 + for (var i = 0, len = saved.length, range, rangeInfo, characterRange; i < len; ++i) { 255.6418 + rangeInfo = saved[i]; 255.6419 + characterRange = rangeInfo.characterRange; 255.6420 + range = api.createRange(containerNode); 255.6421 + range.selectCharacters(containerNode, characterRange.start, characterRange.end, rangeInfo.characterOptions); 255.6422 + this.addRange(range, rangeInfo.backward); 255.6423 + } 255.6424 + } 255.6425 + ), 255.6426 + 255.6427 + text: createEntryPointFunction( 255.6428 + function(session, characterOptions) { 255.6429 + var rangeTexts = []; 255.6430 + for (var i = 0, len = this.rangeCount; i < len; ++i) { 255.6431 + rangeTexts[i] = this.getRangeAt(i).text(characterOptions); 255.6432 + } 255.6433 + return rangeTexts.join(""); 255.6434 + } 255.6435 + ) 255.6436 + }); 255.6437 + 255.6438 + /*----------------------------------------------------------------------------------------------------------------*/ 255.6439 + 255.6440 + // Extensions to the core rangy object 255.6441 + 255.6442 + api.innerText = function(el, characterOptions) { 255.6443 + var range = api.createRange(el); 255.6444 + range.selectNodeContents(el); 255.6445 + var text = range.text(characterOptions); 255.6446 + return text; 255.6447 + }; 255.6448 + 255.6449 + api.createWordIterator = function(startNode, startOffset, iteratorOptions) { 255.6450 + var session = getSession(); 255.6451 + iteratorOptions = createNestedOptions(iteratorOptions, defaultWordIteratorOptions); 255.6452 + var startPos = session.getPosition(startNode, startOffset); 255.6453 + var tokenizedTextProvider = createTokenizedTextProvider(startPos, iteratorOptions.characterOptions, iteratorOptions.wordOptions); 255.6454 + var backward = isDirectionBackward(iteratorOptions.direction); 255.6455 + 255.6456 + return { 255.6457 + next: function() { 255.6458 + return backward ? tokenizedTextProvider.previousStartToken() : tokenizedTextProvider.nextEndToken(); 255.6459 + }, 255.6460 + 255.6461 + dispose: function() { 255.6462 + tokenizedTextProvider.dispose(); 255.6463 + this.next = function() {}; 255.6464 + } 255.6465 + }; 255.6466 + }; 255.6467 + 255.6468 + /*----------------------------------------------------------------------------------------------------------------*/ 255.6469 + 255.6470 + api.noMutation = function(func) { 255.6471 + var session = getSession(); 255.6472 + func(session); 255.6473 + endSession(); 255.6474 + }; 255.6475 + 255.6476 + api.noMutation.createEntryPointFunction = createEntryPointFunction; 255.6477 + 255.6478 + api.textRange = { 255.6479 + isBlockNode: isBlockNode, 255.6480 + isCollapsedWhitespaceNode: isCollapsedWhitespaceNode, 255.6481 + 255.6482 + createPosition: createEntryPointFunction( 255.6483 + function(session, node, offset) { 255.6484 + return session.getPosition(node, offset); 255.6485 + } 255.6486 + ) 255.6487 + }; 255.6488 +}); 255.6489 + 255.6490 +/** 255.6491 + * Detect browser support for specific features 255.6492 + */ 255.6493 +wysihtml.browser = (function() { 255.6494 + var userAgent = navigator.userAgent, 255.6495 + testElement = document.createElement("div"), 255.6496 + // Browser sniffing is unfortunately needed since some behaviors are impossible to feature detect 255.6497 + // We need to be extra careful about Microsoft as it shows increasing tendency of tainting its userAgent strings with false feathers 255.6498 + isGecko = userAgent.indexOf("Gecko") !== -1 && userAgent.indexOf("KHTML") === -1 && !isIE(), 255.6499 + isWebKit = userAgent.indexOf("AppleWebKit/") !== -1 && !isIE(), 255.6500 + isChrome = userAgent.indexOf("Chrome/") !== -1 && !isIE(), 255.6501 + isOpera = userAgent.indexOf("Opera/") !== -1 && !isIE(); 255.6502 + 255.6503 + function iosVersion(userAgent) { 255.6504 + return +((/ipad|iphone|ipod/.test(userAgent) && userAgent.match(/ os (\d+).+? like mac os x/)) || [undefined, 0])[1]; 255.6505 + } 255.6506 + 255.6507 + function androidVersion(userAgent) { 255.6508 + return +(userAgent.match(/android (\d+)/) || [undefined, 0])[1]; 255.6509 + } 255.6510 + 255.6511 + function isIE(version, equation) { 255.6512 + var rv = -1, 255.6513 + re; 255.6514 + 255.6515 + if (navigator.appName == 'Microsoft Internet Explorer') { 255.6516 + re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); 255.6517 + } else if (navigator.appName == 'Netscape') { 255.6518 + if (navigator.userAgent.indexOf("Trident") > -1) { 255.6519 + re = new RegExp("Trident/.*rv:([0-9]{1,}[\.0-9]{0,})"); 255.6520 + } else if ((/Edge\/(\d+)./i).test(navigator.userAgent)) { 255.6521 + re = /Edge\/(\d+)./i; 255.6522 + } 255.6523 + } 255.6524 + 255.6525 + if (re && re.exec(navigator.userAgent) != null) { 255.6526 + rv = parseFloat(RegExp.$1); 255.6527 + } 255.6528 + 255.6529 + if (rv === -1) { return false; } 255.6530 + if (!version) { return true; } 255.6531 + if (!equation) { return version === rv; } 255.6532 + if (equation === "<") { return version < rv; } 255.6533 + if (equation === ">") { return version > rv; } 255.6534 + if (equation === "<=") { return version <= rv; } 255.6535 + if (equation === ">=") { return version >= rv; } 255.6536 + } 255.6537 + 255.6538 + return { 255.6539 + // Static variable needed, publicly accessible, to be able override it in unit tests 255.6540 + USER_AGENT: userAgent, 255.6541 + 255.6542 + /** 255.6543 + * Exclude browsers that are not capable of displaying and handling 255.6544 + * contentEditable as desired: 255.6545 + * - iPhone, iPad (tested iOS 4.2.2) and Android (tested 2.2) refuse to make contentEditables focusable 255.6546 + * - IE < 8 create invalid markup and crash randomly from time to time 255.6547 + * 255.6548 + * @return {Boolean} 255.6549 + */ 255.6550 + supported: function() { 255.6551 + var userAgent = this.USER_AGENT.toLowerCase(), 255.6552 + // Essential for making html elements editable 255.6553 + hasContentEditableSupport = "contentEditable" in testElement, 255.6554 + // Following methods are needed in order to interact with the contentEditable area 255.6555 + hasEditingApiSupport = document.execCommand && document.queryCommandSupported && document.queryCommandState, 255.6556 + // document selector apis are only supported by IE 8+, Safari 4+, Chrome and Firefox 3.5+ 255.6557 + hasQuerySelectorSupport = document.querySelector && document.querySelectorAll, 255.6558 + // contentEditable is unusable in mobile browsers (tested iOS 4.2.2, Android 2.2, Opera Mobile, WebOS 3.05) 255.6559 + isIncompatibleMobileBrowser = (this.isIos() && iosVersion(userAgent) < 5) || (this.isAndroid() && androidVersion(userAgent) < 4) || userAgent.indexOf("opera mobi") !== -1 || userAgent.indexOf("hpwos/") !== -1; 255.6560 + return hasContentEditableSupport 255.6561 + && hasEditingApiSupport 255.6562 + && hasQuerySelectorSupport 255.6563 + && !isIncompatibleMobileBrowser; 255.6564 + }, 255.6565 + 255.6566 + isTouchDevice: function() { 255.6567 + return this.supportsEvent("touchmove"); 255.6568 + }, 255.6569 + 255.6570 + isIos: function() { 255.6571 + return (/ipad|iphone|ipod/i).test(this.USER_AGENT); 255.6572 + }, 255.6573 + 255.6574 + isAndroid: function() { 255.6575 + return this.USER_AGENT.indexOf("Android") !== -1; 255.6576 + }, 255.6577 + 255.6578 + /** 255.6579 + * Whether the browser supports sandboxed iframes 255.6580 + * Currently only IE 6+ offers such feature <iframe security="restricted"> 255.6581 + * 255.6582 + * http://msdn.microsoft.com/en-us/library/ms534622(v=vs.85).aspx 255.6583 + * http://blogs.msdn.com/b/ie/archive/2008/01/18/using-frames-more-securely.aspx 255.6584 + * 255.6585 + * HTML5 sandboxed iframes are still buggy and their DOM is not reachable from the outside (except when using postMessage) 255.6586 + */ 255.6587 + supportsSandboxedIframes: function() { 255.6588 + return isIE(); 255.6589 + }, 255.6590 + 255.6591 + /** 255.6592 + * IE6+7 throw a mixed content warning when the src of an iframe 255.6593 + * is empty/unset or about:blank 255.6594 + * window.querySelector is implemented as of IE8 255.6595 + */ 255.6596 + throwsMixedContentWarningWhenIframeSrcIsEmpty: function() { 255.6597 + return !("querySelector" in document); 255.6598 + }, 255.6599 + 255.6600 + /** 255.6601 + * Whether the caret is correctly displayed in contentEditable elements 255.6602 + * Firefox sometimes shows a huge caret in the beginning after focusing 255.6603 + */ 255.6604 + displaysCaretInEmptyContentEditableCorrectly: function() { 255.6605 + return isIE(12, ">"); 255.6606 + }, 255.6607 + 255.6608 + /** 255.6609 + * Opera and IE are the only browsers who offer the css value 255.6610 + * in the original unit, thx to the currentStyle object 255.6611 + * All other browsers provide the computed style in px via window.getComputedStyle 255.6612 + */ 255.6613 + hasCurrentStyleProperty: function() { 255.6614 + return "currentStyle" in testElement; 255.6615 + }, 255.6616 + 255.6617 + /** 255.6618 + * Whether the browser inserts a <br> when pressing enter in a contentEditable element 255.6619 + */ 255.6620 + insertsLineBreaksOnReturn: function() { 255.6621 + return isGecko; 255.6622 + }, 255.6623 + 255.6624 + supportsPlaceholderAttributeOn: function(element) { 255.6625 + return "placeholder" in element; 255.6626 + }, 255.6627 + 255.6628 + supportsEvent: function(eventName) { 255.6629 + return "on" + eventName in testElement || (function() { 255.6630 + testElement.setAttribute("on" + eventName, "return;"); 255.6631 + return typeof(testElement["on" + eventName]) === "function"; 255.6632 + })(); 255.6633 + }, 255.6634 + 255.6635 + /** 255.6636 + * Opera doesn't correctly fire focus/blur events when clicking in- and outside of iframe 255.6637 + */ 255.6638 + supportsEventsInIframeCorrectly: function() { 255.6639 + return !isOpera; 255.6640 + }, 255.6641 + 255.6642 + /** 255.6643 + * Everything below IE9 doesn't know how to treat HTML5 tags 255.6644 + * 255.6645 + * @param {Object} context The document object on which to check HTML5 support 255.6646 + * 255.6647 + * @example 255.6648 + * wysihtml.browser.supportsHTML5Tags(document); 255.6649 + */ 255.6650 + supportsHTML5Tags: function(context) { 255.6651 + var element = context.createElement("div"), 255.6652 + html5 = "<article>foo</article>"; 255.6653 + element.innerHTML = html5; 255.6654 + return element.innerHTML.toLowerCase() === html5; 255.6655 + }, 255.6656 + 255.6657 + /** 255.6658 + * Checks whether a document supports a certain queryCommand 255.6659 + * In particular, Opera needs a reference to a document that has a contentEditable in it's dom tree 255.6660 + * in oder to report correct results 255.6661 + * 255.6662 + * @param {Object} doc Document object on which to check for a query command 255.6663 + * @param {String} command The query command to check for 255.6664 + * @return {Boolean} 255.6665 + * 255.6666 + * @example 255.6667 + * wysihtml.browser.supportsCommand(document, "bold"); 255.6668 + */ 255.6669 + supportsCommand: (function() { 255.6670 + // Following commands are supported but contain bugs in some browsers 255.6671 + // TODO: investigate if some of these bugs can be tested without altering selection on page, instead of targeting browsers and versions directly 255.6672 + var buggyCommands = { 255.6673 + // formatBlock fails with some tags (eg. <blockquote>) 255.6674 + "formatBlock": isIE(10, "<="), 255.6675 + // When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets 255.6676 + // converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>) 255.6677 + // IE and Opera act a bit different here as they convert the entire content of the current block element into a list 255.6678 + "insertUnorderedList": isIE(), 255.6679 + "insertOrderedList": isIE() 255.6680 + }; 255.6681 + 255.6682 + // Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands 255.6683 + var supported = { 255.6684 + "insertHTML": isGecko 255.6685 + }; 255.6686 + 255.6687 + return function(doc, command) { 255.6688 + var isBuggy = buggyCommands[command]; 255.6689 + if (!isBuggy) { 255.6690 + // Firefox throws errors when invoking queryCommandSupported or queryCommandEnabled 255.6691 + try { 255.6692 + return doc.queryCommandSupported(command); 255.6693 + } catch(e1) {} 255.6694 + 255.6695 + try { 255.6696 + return doc.queryCommandEnabled(command); 255.6697 + } catch(e2) { 255.6698 + return !!supported[command]; 255.6699 + } 255.6700 + } 255.6701 + return false; 255.6702 + }; 255.6703 + })(), 255.6704 + 255.6705 + /** 255.6706 + * IE: URLs starting with: 255.6707 + * www., http://, https://, ftp://, gopher://, mailto:, new:, snews:, telnet:, wasis:, file://, 255.6708 + * nntp://, newsrc:, ldap://, ldaps://, outlook:, mic:// and url: 255.6709 + * will automatically be auto-linked when either the user inserts them via copy&paste or presses the 255.6710 + * space bar when the caret is directly after such an url. 255.6711 + * This behavior cannot easily be avoided in IE < 9 since the logic is hardcoded in the mshtml.dll 255.6712 + * (related blog post on msdn 255.6713 + * http://blogs.msdn.com/b/ieinternals/archive/2009/09/17/prevent-automatic-hyperlinking-in-contenteditable-html.aspx). 255.6714 + */ 255.6715 + doesAutoLinkingInContentEditable: function() { 255.6716 + return isIE(); 255.6717 + }, 255.6718 + 255.6719 + /** 255.6720 + * As stated above, IE auto links urls typed into contentEditable elements 255.6721 + * Since IE9 it's possible to prevent this behavior 255.6722 + */ 255.6723 + canDisableAutoLinking: function() { 255.6724 + return this.supportsCommand(document, "AutoUrlDetect"); 255.6725 + }, 255.6726 + 255.6727 + /** 255.6728 + * IE leaves an empty paragraph in the contentEditable element after clearing it 255.6729 + * Chrome/Safari sometimes an empty <div> 255.6730 + */ 255.6731 + clearsContentEditableCorrectly: function() { 255.6732 + return isGecko || isOpera || isWebKit; 255.6733 + }, 255.6734 + 255.6735 + /** 255.6736 + * IE gives wrong results for getAttribute 255.6737 + */ 255.6738 + supportsGetAttributeCorrectly: function() { 255.6739 + var td = document.createElement("td"); 255.6740 + return td.getAttribute("rowspan") != "1"; 255.6741 + }, 255.6742 + 255.6743 + /** 255.6744 + * When clicking on images in IE, Opera and Firefox, they are selected, which makes it easy to interact with them. 255.6745 + * Chrome and Safari both don't support this 255.6746 + */ 255.6747 + canSelectImagesInContentEditable: function() { 255.6748 + return isGecko || isIE() || isOpera; 255.6749 + }, 255.6750 + 255.6751 + /** 255.6752 + * All browsers except Safari and Chrome automatically scroll the range/caret position into view 255.6753 + */ 255.6754 + autoScrollsToCaret: function() { 255.6755 + return !isWebKit; 255.6756 + }, 255.6757 + 255.6758 + /** 255.6759 + * Check whether the browser automatically closes tags that don't need to be opened 255.6760 + */ 255.6761 + autoClosesUnclosedTags: function() { 255.6762 + var clonedTestElement = testElement.cloneNode(false), 255.6763 + returnValue, 255.6764 + innerHTML; 255.6765 + 255.6766 + clonedTestElement.innerHTML = "<p><div></div>"; 255.6767 + innerHTML = clonedTestElement.innerHTML.toLowerCase(); 255.6768 + returnValue = innerHTML === "<p></p><div></div>" || innerHTML === "<p><div></div></p>"; 255.6769 + 255.6770 + // Cache result by overwriting current function 255.6771 + this.autoClosesUnclosedTags = function() { return returnValue; }; 255.6772 + 255.6773 + return returnValue; 255.6774 + }, 255.6775 + 255.6776 + /** 255.6777 + * Whether the browser supports the native document.getElementsByClassName which returns live NodeLists 255.6778 + */ 255.6779 + supportsNativeGetElementsByClassName: function() { 255.6780 + return String(document.getElementsByClassName).indexOf("[native code]") !== -1; 255.6781 + }, 255.6782 + 255.6783 + /** 255.6784 + * As of now (19.04.2011) only supported by Firefox 4 and Chrome 255.6785 + * See https://developer.mozilla.org/en/DOM/Selection/modify 255.6786 + */ 255.6787 + supportsSelectionModify: function() { 255.6788 + return "getSelection" in window && "modify" in window.getSelection(); 255.6789 + }, 255.6790 + 255.6791 + /** 255.6792 + * Opera needs a white space after a <br> in order to position the caret correctly 255.6793 + */ 255.6794 + needsSpaceAfterLineBreak: function() { 255.6795 + return isOpera; 255.6796 + }, 255.6797 + 255.6798 + /** 255.6799 + * Whether the browser supports the speech api on the given element 255.6800 + * See http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/ 255.6801 + * 255.6802 + * @example 255.6803 + * var input = document.createElement("input"); 255.6804 + * if (wysihtml.browser.supportsSpeechApiOn(input)) { 255.6805 + * // ... 255.6806 + * } 255.6807 + */ 255.6808 + supportsSpeechApiOn: function(input) { 255.6809 + var chromeVersion = userAgent.match(/Chrome\/(\d+)/) || [undefined, 0]; 255.6810 + return chromeVersion[1] >= 11 && ("onwebkitspeechchange" in input || "speech" in input); 255.6811 + }, 255.6812 + 255.6813 + /** 255.6814 + * IE9 crashes when setting a getter via Object.defineProperty on XMLHttpRequest or XDomainRequest 255.6815 + * See https://connect.microsoft.com/ie/feedback/details/650112 255.6816 + * or try the POC http://tifftiff.de/ie9_crash/ 255.6817 + */ 255.6818 + crashesWhenDefineProperty: function(property) { 255.6819 + return isIE(9) && (property === "XMLHttpRequest" || property === "XDomainRequest"); 255.6820 + }, 255.6821 + 255.6822 + /** 255.6823 + * IE is the only browser who fires the "focus" event not immediately when .focus() is called on an element 255.6824 + */ 255.6825 + doesAsyncFocus: function() { 255.6826 + return isIE(12, ">"); 255.6827 + }, 255.6828 + 255.6829 + /** 255.6830 + * In IE it's impssible for the user and for the selection library to set the caret after an <img> when it's the lastChild in the document 255.6831 + */ 255.6832 + hasProblemsSettingCaretAfterImg: function() { 255.6833 + return isIE(); 255.6834 + }, 255.6835 + 255.6836 + /* In IE when deleting with caret at the begining of LI, List get broken into half instead of merging the LI with previous */ 255.6837 + hasLiDeletingProblem: function() { 255.6838 + return isIE(); 255.6839 + }, 255.6840 + 255.6841 + hasUndoInContextMenu: function() { 255.6842 + return isGecko || isChrome || isOpera; 255.6843 + }, 255.6844 + 255.6845 + /** 255.6846 + * Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode) 255.6847 + * is used (regardless if rangy or native) 255.6848 + * This especially happens when the caret is positioned right after a <br> because then 255.6849 + * insertNode() will insert the node right before the <br> 255.6850 + */ 255.6851 + hasInsertNodeIssue: function() { 255.6852 + return isOpera; 255.6853 + }, 255.6854 + 255.6855 + /** 255.6856 + * IE 8+9 don't fire the focus event of the <body> when the iframe gets focused (even though the caret gets set into the <body>) 255.6857 + */ 255.6858 + hasIframeFocusIssue: function() { 255.6859 + return isIE(); 255.6860 + }, 255.6861 + 255.6862 + /** 255.6863 + * Chrome + Safari create invalid nested markup after paste 255.6864 + * 255.6865 + * <p> 255.6866 + * foo 255.6867 + * <p>bar</p> <!-- BOO! --> 255.6868 + * </p> 255.6869 + */ 255.6870 + createsNestedInvalidMarkupAfterPaste: function() { 255.6871 + return isWebKit; 255.6872 + }, 255.6873 + 255.6874 + // In all webkit browsers there are some places where caret can not be placed at the end of blocks and directly before block level element 255.6875 + // when startContainer is element. 255.6876 + hasCaretBlockElementIssue: function() { 255.6877 + return isWebKit; 255.6878 + }, 255.6879 + 255.6880 + supportsMutationEvents: function() { 255.6881 + return ("MutationEvent" in window); 255.6882 + }, 255.6883 + 255.6884 + /** 255.6885 + IE (at least up to 11) does not support clipboardData on event. 255.6886 + It is on window but cannot return text/html 255.6887 + Should actually check for clipboardData on paste event, but cannot in firefox 255.6888 + */ 255.6889 + supportsModernPaste: function () { 255.6890 + return !isIE(); 255.6891 + }, 255.6892 + 255.6893 + // Unifies the property names of element.style by returning the suitable property name for current browser 255.6894 + // Input property key must be the standard 255.6895 + fixStyleKey: function(key) { 255.6896 + if (key === "cssFloat") { 255.6897 + return ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat"; 255.6898 + } 255.6899 + return key; 255.6900 + }, 255.6901 + 255.6902 + usesControlRanges: function() { 255.6903 + return document.body && "createControlRange" in document.body; 255.6904 + }, 255.6905 + 255.6906 + // Webkit browsers have an issue that when caret is at the end of link it is moved outside of link while inserting new characters, 255.6907 + // so all inserted content will be after link. Selection before inserion is reported to be in link though. 255.6908 + // This makes changing link texts from problematic to impossible (if link is just 1 characer long) for the user. 255.6909 + // TODO: needs to be tested better than just browser as it some day might get fixed 255.6910 + hasCaretAtLinkEndInsertionProblems: function() { 255.6911 + return isWebKit; 255.6912 + } 255.6913 + }; 255.6914 +})(); 255.6915 + 255.6916 +wysihtml.lang.array = function(arr) { 255.6917 + return { 255.6918 + /** 255.6919 + * Check whether a given object exists in an array 255.6920 + * 255.6921 + * @example 255.6922 + * wysihtml.lang.array([1, 2]).contains(1); 255.6923 + * // => true 255.6924 + * 255.6925 + * Can be used to match array with array. If intersection is found true is returned 255.6926 + */ 255.6927 + contains: function(needle) { 255.6928 + if (Array.isArray(needle)) { 255.6929 + for (var i = needle.length; i--;) { 255.6930 + if (wysihtml.lang.array(arr).indexOf(needle[i]) !== -1) { 255.6931 + return true; 255.6932 + } 255.6933 + } 255.6934 + return false; 255.6935 + } else { 255.6936 + return wysihtml.lang.array(arr).indexOf(needle) !== -1; 255.6937 + } 255.6938 + }, 255.6939 + 255.6940 + /** 255.6941 + * Check whether a given object exists in an array and return index 255.6942 + * If no elelemt found returns -1 255.6943 + * 255.6944 + * @example 255.6945 + * wysihtml.lang.array([1, 2]).indexOf(2); 255.6946 + * // => 1 255.6947 + */ 255.6948 + indexOf: function(needle) { 255.6949 + if (arr.indexOf) { 255.6950 + return arr.indexOf(needle); 255.6951 + } else { 255.6952 + for (var i=0, length=arr.length; i<length; i++) { 255.6953 + if (arr[i] === needle) { return i; } 255.6954 + } 255.6955 + return -1; 255.6956 + } 255.6957 + }, 255.6958 + 255.6959 + /** 255.6960 + * Substract one array from another 255.6961 + * 255.6962 + * @example 255.6963 + * wysihtml.lang.array([1, 2, 3, 4]).without([3, 4]); 255.6964 + * // => [1, 2] 255.6965 + */ 255.6966 + without: function(arrayToSubstract) { 255.6967 + arrayToSubstract = wysihtml.lang.array(arrayToSubstract); 255.6968 + var newArr = [], 255.6969 + i = 0, 255.6970 + length = arr.length; 255.6971 + for (; i<length; i++) { 255.6972 + if (!arrayToSubstract.contains(arr[i])) { 255.6973 + newArr.push(arr[i]); 255.6974 + } 255.6975 + } 255.6976 + return newArr; 255.6977 + }, 255.6978 + 255.6979 + /** 255.6980 + * Return a clean native array 255.6981 + * 255.6982 + * Following will convert a Live NodeList to a proper Array 255.6983 + * @example 255.6984 + * var childNodes = wysihtml.lang.array(document.body.childNodes).get(); 255.6985 + */ 255.6986 + get: function() { 255.6987 + var i = 0, 255.6988 + length = arr.length, 255.6989 + newArray = []; 255.6990 + for (; i<length; i++) { 255.6991 + newArray.push(arr[i]); 255.6992 + } 255.6993 + return newArray; 255.6994 + }, 255.6995 + 255.6996 + /** 255.6997 + * Creates a new array with the results of calling a provided function on every element in this array. 255.6998 + * optionally this can be provided as second argument 255.6999 + * 255.7000 + * @example 255.7001 + * var childNodes = wysihtml.lang.array([1,2,3,4]).map(function (value, index, array) { 255.7002 + return value * 2; 255.7003 + * }); 255.7004 + * // => [2,4,6,8] 255.7005 + */ 255.7006 + map: function(callback, thisArg) { 255.7007 + if (Array.prototype.map) { 255.7008 + return arr.map(callback, thisArg); 255.7009 + } else { 255.7010 + var len = arr.length >>> 0, 255.7011 + A = new Array(len), 255.7012 + i = 0; 255.7013 + for (; i < len; i++) { 255.7014 + A[i] = callback.call(thisArg, arr[i], i, arr); 255.7015 + } 255.7016 + return A; 255.7017 + } 255.7018 + }, 255.7019 + 255.7020 + /* ReturnS new array without duplicate entries 255.7021 + * 255.7022 + * @example 255.7023 + * var uniq = wysihtml.lang.array([1,2,3,2,1,4]).unique(); 255.7024 + * // => [1,2,3,4] 255.7025 + */ 255.7026 + unique: function() { 255.7027 + var vals = [], 255.7028 + max = arr.length, 255.7029 + idx = 0; 255.7030 + 255.7031 + while (idx < max) { 255.7032 + if (!wysihtml.lang.array(vals).contains(arr[idx])) { 255.7033 + vals.push(arr[idx]); 255.7034 + } 255.7035 + idx++; 255.7036 + } 255.7037 + return vals; 255.7038 + } 255.7039 + 255.7040 + }; 255.7041 +}; 255.7042 + 255.7043 +wysihtml.lang.Dispatcher = Base.extend( 255.7044 + /** @scope wysihtml.lang.Dialog.prototype */ { 255.7045 + on: function(eventName, handler) { 255.7046 + this.events = this.events || {}; 255.7047 + this.events[eventName] = this.events[eventName] || []; 255.7048 + this.events[eventName].push(handler); 255.7049 + return this; 255.7050 + }, 255.7051 + 255.7052 + off: function(eventName, handler) { 255.7053 + this.events = this.events || {}; 255.7054 + var i = 0, 255.7055 + handlers, 255.7056 + newHandlers; 255.7057 + if (eventName) { 255.7058 + handlers = this.events[eventName] || [], 255.7059 + newHandlers = []; 255.7060 + for (; i<handlers.length; i++) { 255.7061 + if (handlers[i] !== handler && handler) { 255.7062 + newHandlers.push(handlers[i]); 255.7063 + } 255.7064 + } 255.7065 + this.events[eventName] = newHandlers; 255.7066 + } else { 255.7067 + // Clean up all events 255.7068 + this.events = {}; 255.7069 + } 255.7070 + return this; 255.7071 + }, 255.7072 + 255.7073 + fire: function(eventName, payload) { 255.7074 + this.events = this.events || {}; 255.7075 + var handlers = this.events[eventName] || [], 255.7076 + i = 0; 255.7077 + for (; i<handlers.length; i++) { 255.7078 + handlers[i].call(this, payload); 255.7079 + } 255.7080 + return this; 255.7081 + }, 255.7082 + 255.7083 + // deprecated, use .on() 255.7084 + observe: function() { 255.7085 + return this.on.apply(this, arguments); 255.7086 + }, 255.7087 + 255.7088 + // deprecated, use .off() 255.7089 + stopObserving: function() { 255.7090 + return this.off.apply(this, arguments); 255.7091 + } 255.7092 +}); 255.7093 + 255.7094 +wysihtml.lang.object = function(obj) { 255.7095 + return { 255.7096 + /** 255.7097 + * @example 255.7098 + * wysihtml.lang.object({ foo: 1, bar: 1 }).merge({ bar: 2, baz: 3 }).get(); 255.7099 + * // => { foo: 1, bar: 2, baz: 3 } 255.7100 + */ 255.7101 + merge: function(otherObj, deep) { 255.7102 + for (var i in otherObj) { 255.7103 + if (deep && wysihtml.lang.object(otherObj[i]).isPlainObject() && (typeof obj[i] === "undefined" || wysihtml.lang.object(obj[i]).isPlainObject())) { 255.7104 + if (typeof obj[i] === "undefined") { 255.7105 + obj[i] = wysihtml.lang.object(otherObj[i]).clone(true); 255.7106 + } else { 255.7107 + wysihtml.lang.object(obj[i]).merge(wysihtml.lang.object(otherObj[i]).clone(true)); 255.7108 + } 255.7109 + } else { 255.7110 + obj[i] = wysihtml.lang.object(otherObj[i]).isPlainObject() ? wysihtml.lang.object(otherObj[i]).clone(true) : otherObj[i]; 255.7111 + } 255.7112 + } 255.7113 + return this; 255.7114 + }, 255.7115 + 255.7116 + difference: function (otherObj) { 255.7117 + var diffObj = {}; 255.7118 + 255.7119 + // Get old values not in comparing object 255.7120 + for (var i in obj) { 255.7121 + if (obj.hasOwnProperty(i)) { 255.7122 + if (!otherObj.hasOwnProperty(i)) { 255.7123 + diffObj[i] = obj[i]; 255.7124 + } 255.7125 + } 255.7126 + } 255.7127 + 255.7128 + // Get new and different values in comparing object 255.7129 + for (var o in otherObj) { 255.7130 + if (otherObj.hasOwnProperty(o)) { 255.7131 + if (!obj.hasOwnProperty(o) || obj[o] !== otherObj[o]) { 255.7132 + diffObj[0] = obj[0]; 255.7133 + } 255.7134 + } 255.7135 + } 255.7136 + return diffObj; 255.7137 + }, 255.7138 + 255.7139 + get: function() { 255.7140 + return obj; 255.7141 + }, 255.7142 + 255.7143 + /** 255.7144 + * @example 255.7145 + * wysihtml.lang.object({ foo: 1 }).clone(); 255.7146 + * // => { foo: 1 } 255.7147 + * 255.7148 + * v0.4.14 adds options for deep clone : wysihtml.lang.object({ foo: 1 }).clone(true); 255.7149 + */ 255.7150 + clone: function(deep) { 255.7151 + var newObj = {}, 255.7152 + i; 255.7153 + 255.7154 + if (obj === null || !wysihtml.lang.object(obj).isPlainObject()) { 255.7155 + return obj; 255.7156 + } 255.7157 + 255.7158 + for (i in obj) { 255.7159 + if(obj.hasOwnProperty(i)) { 255.7160 + if (deep) { 255.7161 + newObj[i] = wysihtml.lang.object(obj[i]).clone(deep); 255.7162 + } else { 255.7163 + newObj[i] = obj[i]; 255.7164 + } 255.7165 + } 255.7166 + } 255.7167 + return newObj; 255.7168 + }, 255.7169 + 255.7170 + /** 255.7171 + * @example 255.7172 + * wysihtml.lang.object([]).isArray(); 255.7173 + * // => true 255.7174 + */ 255.7175 + isArray: function() { 255.7176 + return Object.prototype.toString.call(obj) === "[object Array]"; 255.7177 + }, 255.7178 + 255.7179 + /** 255.7180 + * @example 255.7181 + * wysihtml.lang.object(function() {}).isFunction(); 255.7182 + * // => true 255.7183 + */ 255.7184 + isFunction: function() { 255.7185 + return Object.prototype.toString.call(obj) === '[object Function]'; 255.7186 + }, 255.7187 + 255.7188 + isPlainObject: function () { 255.7189 + return obj && Object.prototype.toString.call(obj) === '[object Object]' && !(("Node" in window) ? obj instanceof Node : obj instanceof Element || obj instanceof Text); 255.7190 + }, 255.7191 + 255.7192 + /** 255.7193 + * @example 255.7194 + * wysihtml.lang.object({}).isEmpty(); 255.7195 + * // => true 255.7196 + */ 255.7197 + isEmpty: function() { 255.7198 + for (var i in obj) { 255.7199 + if (obj.hasOwnProperty(i)) { 255.7200 + return false; 255.7201 + } 255.7202 + } 255.7203 + return true; 255.7204 + } 255.7205 + }; 255.7206 +}; 255.7207 + 255.7208 +(function() { 255.7209 + var WHITE_SPACE_START = /^\s+/, 255.7210 + WHITE_SPACE_END = /\s+$/, 255.7211 + ENTITY_REG_EXP = /[&<>\t"]/g, 255.7212 + ENTITY_MAP = { 255.7213 + '&': '&', 255.7214 + '<': '<', 255.7215 + '>': '>', 255.7216 + '"': """, 255.7217 + '\t':" " 255.7218 + }; 255.7219 + wysihtml.lang.string = function(str) { 255.7220 + str = String(str); 255.7221 + return { 255.7222 + /** 255.7223 + * @example 255.7224 + * wysihtml.lang.string(" foo ").trim(); 255.7225 + * // => "foo" 255.7226 + */ 255.7227 + trim: function() { 255.7228 + return str.replace(WHITE_SPACE_START, "").replace(WHITE_SPACE_END, ""); 255.7229 + }, 255.7230 + 255.7231 + /** 255.7232 + * @example 255.7233 + * wysihtml.lang.string("Hello #{name}").interpolate({ name: "Christopher" }); 255.7234 + * // => "Hello Christopher" 255.7235 + */ 255.7236 + interpolate: function(vars) { 255.7237 + for (var i in vars) { 255.7238 + str = this.replace("#{" + i + "}").by(vars[i]); 255.7239 + } 255.7240 + return str; 255.7241 + }, 255.7242 + 255.7243 + /** 255.7244 + * @example 255.7245 + * wysihtml.lang.string("Hello Tom").replace("Tom").with("Hans"); 255.7246 + * // => "Hello Hans" 255.7247 + */ 255.7248 + replace: function(search) { 255.7249 + return { 255.7250 + by: function(replace) { 255.7251 + return str.split(search).join(replace); 255.7252 + } 255.7253 + }; 255.7254 + }, 255.7255 + 255.7256 + /** 255.7257 + * @example 255.7258 + * wysihtml.lang.string("hello<br>").escapeHTML(); 255.7259 + * // => "hello<br>" 255.7260 + */ 255.7261 + escapeHTML: function(linebreaks, convertSpaces) { 255.7262 + var html = str.replace(ENTITY_REG_EXP, function(c) { return ENTITY_MAP[c]; }); 255.7263 + if (linebreaks) { 255.7264 + html = html.replace(/(?:\r\n|\r|\n)/g, '<br />'); 255.7265 + } 255.7266 + if (convertSpaces) { 255.7267 + html = html.replace(/ /gi, " "); 255.7268 + } 255.7269 + return html; 255.7270 + } 255.7271 + }; 255.7272 + }; 255.7273 +})(); 255.7274 + 255.7275 +/** 255.7276 + * Find urls in descendant text nodes of an element and auto-links them 255.7277 + * Inspired by http://james.padolsey.com/javascript/find-and-replace-text-with-javascript/ 255.7278 + * 255.7279 + * @param {Element} element Container element in which to search for urls 255.7280 + * 255.7281 + * @example 255.7282 + * <div id="text-container">Please click here: www.google.com</div> 255.7283 + * <script>wysihtml.dom.autoLink(document.getElementById("text-container"));</script> 255.7284 + */ 255.7285 +(function(wysihtml) { 255.7286 + var /** 255.7287 + * Don't auto-link urls that are contained in the following elements: 255.7288 + */ 255.7289 + IGNORE_URLS_IN = wysihtml.lang.array(["CODE", "PRE", "A", "SCRIPT", "HEAD", "TITLE", "STYLE"]), 255.7290 + /** 255.7291 + * revision 1: 255.7292 + * /(\S+\.{1}[^\s\,\.\!]+)/g 255.7293 + * 255.7294 + * revision 2: 255.7295 + * /(\b(((https?|ftp):\/\/)|(www\.))[-A-Z0-9+&@#\/%?=~_|!:,.;\[\]]*[-A-Z0-9+&@#\/%=~_|])/gim 255.7296 + * 255.7297 + * put this in the beginning if you don't wan't to match within a word 255.7298 + * (^|[\>\(\{\[\s\>]) 255.7299 + */ 255.7300 + URL_REG_EXP = /((https?:\/\/|www\.)[^\s<]{3,})/gi, 255.7301 + TRAILING_CHAR_REG_EXP = /([^\w\/\-](,?))$/i, 255.7302 + MAX_DISPLAY_LENGTH = 100, 255.7303 + BRACKETS = { ")": "(", "]": "[", "}": "{" }; 255.7304 + 255.7305 + function autoLink(element, ignoreInClasses) { 255.7306 + if (_hasParentThatShouldBeIgnored(element, ignoreInClasses)) { 255.7307 + return element; 255.7308 + } 255.7309 + 255.7310 + if (element === element.ownerDocument.documentElement) { 255.7311 + element = element.ownerDocument.body; 255.7312 + } 255.7313 + 255.7314 + return _parseNode(element, ignoreInClasses); 255.7315 + } 255.7316 + 255.7317 + /** 255.7318 + * This is basically a rebuild of 255.7319 + * the rails auto_link_urls text helper 255.7320 + */ 255.7321 + function _convertUrlsToLinks(str) { 255.7322 + return str.replace(URL_REG_EXP, function(match, url) { 255.7323 + var punctuation = (url.match(TRAILING_CHAR_REG_EXP) || [])[1] || "", 255.7324 + opening = BRACKETS[punctuation]; 255.7325 + url = url.replace(TRAILING_CHAR_REG_EXP, ""); 255.7326 + 255.7327 + if (url.split(opening).length > url.split(punctuation).length) { 255.7328 + url = url + punctuation; 255.7329 + punctuation = ""; 255.7330 + } 255.7331 + var realUrl = url, 255.7332 + displayUrl = url; 255.7333 + if (url.length > MAX_DISPLAY_LENGTH) { 255.7334 + displayUrl = displayUrl.substr(0, MAX_DISPLAY_LENGTH) + "..."; 255.7335 + } 255.7336 + // Add http prefix if necessary 255.7337 + if (realUrl.substr(0, 4) === "www.") { 255.7338 + realUrl = "http://" + realUrl; 255.7339 + } 255.7340 + 255.7341 + return '<a href="' + realUrl + '">' + displayUrl + '</a>' + punctuation; 255.7342 + }); 255.7343 + } 255.7344 + 255.7345 + /** 255.7346 + * Creates or (if already cached) returns a temp element 255.7347 + * for the given document object 255.7348 + */ 255.7349 + function _getTempElement(context) { 255.7350 + var tempElement = context._wysihtml_tempElement; 255.7351 + if (!tempElement) { 255.7352 + tempElement = context._wysihtml_tempElement = context.createElement("div"); 255.7353 + } 255.7354 + return tempElement; 255.7355 + } 255.7356 + 255.7357 + /** 255.7358 + * Replaces the original text nodes with the newly auto-linked dom tree 255.7359 + */ 255.7360 + function _wrapMatchesInNode(textNode) { 255.7361 + var parentNode = textNode.parentNode, 255.7362 + nodeValue = wysihtml.lang.string(textNode.data).escapeHTML(), 255.7363 + tempElement = _getTempElement(parentNode.ownerDocument); 255.7364 + 255.7365 + // We need to insert an empty/temporary <span /> to fix IE quirks 255.7366 + // Elsewise IE would strip white space in the beginning 255.7367 + tempElement.innerHTML = "<span></span>" + _convertUrlsToLinks(nodeValue); 255.7368 + tempElement.removeChild(tempElement.firstChild); 255.7369 + 255.7370 + while (tempElement.firstChild) { 255.7371 + // inserts tempElement.firstChild before textNode 255.7372 + parentNode.insertBefore(tempElement.firstChild, textNode); 255.7373 + } 255.7374 + parentNode.removeChild(textNode); 255.7375 + } 255.7376 + 255.7377 + function _hasParentThatShouldBeIgnored(node, ignoreInClasses) { 255.7378 + var nodeName; 255.7379 + while (node.parentNode) { 255.7380 + node = node.parentNode; 255.7381 + nodeName = node.nodeName; 255.7382 + if (node.className && wysihtml.lang.array(node.className.split(' ')).contains(ignoreInClasses)) { 255.7383 + return true; 255.7384 + } 255.7385 + if (IGNORE_URLS_IN.contains(nodeName)) { 255.7386 + return true; 255.7387 + } else if (nodeName === "body") { 255.7388 + return false; 255.7389 + } 255.7390 + } 255.7391 + return false; 255.7392 + } 255.7393 + 255.7394 + function _parseNode(element, ignoreInClasses) { 255.7395 + if (IGNORE_URLS_IN.contains(element.nodeName)) { 255.7396 + return; 255.7397 + } 255.7398 + 255.7399 + if (element.className && wysihtml.lang.array(element.className.split(' ')).contains(ignoreInClasses)) { 255.7400 + return; 255.7401 + } 255.7402 + 255.7403 + if (element.nodeType === wysihtml.TEXT_NODE && element.data.match(URL_REG_EXP)) { 255.7404 + _wrapMatchesInNode(element); 255.7405 + return; 255.7406 + } 255.7407 + 255.7408 + var childNodes = wysihtml.lang.array(element.childNodes).get(), 255.7409 + childNodesLength = childNodes.length, 255.7410 + i = 0; 255.7411 + 255.7412 + for (; i<childNodesLength; i++) { 255.7413 + _parseNode(childNodes[i], ignoreInClasses); 255.7414 + } 255.7415 + 255.7416 + return element; 255.7417 + } 255.7418 + 255.7419 + wysihtml.dom.autoLink = autoLink; 255.7420 + 255.7421 + // Reveal url reg exp to the outside 255.7422 + wysihtml.dom.autoLink.URL_REG_EXP = URL_REG_EXP; 255.7423 +})(wysihtml); 255.7424 + 255.7425 +(function(wysihtml) { 255.7426 + var api = wysihtml.dom; 255.7427 + 255.7428 + api.addClass = function(element, className) { 255.7429 + var classList = element.classList; 255.7430 + if (classList) { 255.7431 + return classList.add(className); 255.7432 + } 255.7433 + if (api.hasClass(element, className)) { 255.7434 + return; 255.7435 + } 255.7436 + element.className += " " + className; 255.7437 + }; 255.7438 + 255.7439 + api.removeClass = function(element, className) { 255.7440 + var classList = element.classList; 255.7441 + if (classList) { 255.7442 + return classList.remove(className); 255.7443 + } 255.7444 + 255.7445 + element.className = element.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), " "); 255.7446 + }; 255.7447 + 255.7448 + api.hasClass = function(element, className) { 255.7449 + var classList = element.classList; 255.7450 + if (classList) { 255.7451 + return classList.contains(className); 255.7452 + } 255.7453 + 255.7454 + var elementClassName = element.className; 255.7455 + return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); 255.7456 + }; 255.7457 +})(wysihtml); 255.7458 + 255.7459 +wysihtml.dom.compareDocumentPosition = (function() { 255.7460 + var documentElement = document.documentElement; 255.7461 + if (documentElement.compareDocumentPosition) { 255.7462 + return function(container, element) { 255.7463 + return container.compareDocumentPosition(element); 255.7464 + }; 255.7465 + } else { 255.7466 + return function( container, element ) { 255.7467 + // implementation borrowed from https://github.com/tmpvar/jsdom/blob/681a8524b663281a0f58348c6129c8c184efc62c/lib/jsdom/level3/core.js // MIT license 255.7468 + var thisOwner, otherOwner; 255.7469 + 255.7470 + if( container.nodeType === 9) // Node.DOCUMENT_NODE 255.7471 + thisOwner = container; 255.7472 + else 255.7473 + thisOwner = container.ownerDocument; 255.7474 + 255.7475 + if( element.nodeType === 9) // Node.DOCUMENT_NODE 255.7476 + otherOwner = element; 255.7477 + else 255.7478 + otherOwner = element.ownerDocument; 255.7479 + 255.7480 + if( container === element ) return 0; 255.7481 + if( container === element.ownerDocument ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY; 255.7482 + if( container.ownerDocument === element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS; 255.7483 + if( thisOwner !== otherOwner ) return 1; // Node.DOCUMENT_POSITION_DISCONNECTED; 255.7484 + 255.7485 + // Text nodes for attributes does not have a _parentNode. So we need to find them as attribute child. 255.7486 + if( container.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && container.childNodes && wysihtml.lang.array(container.childNodes).indexOf( element ) !== -1) 255.7487 + return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY; 255.7488 + 255.7489 + if( element.nodeType === 2 /*Node.ATTRIBUTE_NODE*/ && element.childNodes && wysihtml.lang.array(element.childNodes).indexOf( container ) !== -1) 255.7490 + return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS; 255.7491 + 255.7492 + var point = container; 255.7493 + var parents = [ ]; 255.7494 + var previous = null; 255.7495 + while( point ) { 255.7496 + if( point == element ) return 2 + 8; //Node.DOCUMENT_POSITION_PRECEDING + Node.DOCUMENT_POSITION_CONTAINS; 255.7497 + parents.push( point ); 255.7498 + point = point.parentNode; 255.7499 + } 255.7500 + point = element; 255.7501 + previous = null; 255.7502 + while( point ) { 255.7503 + if( point == container ) return 4 + 16; //Node.DOCUMENT_POSITION_FOLLOWING + Node.DOCUMENT_POSITION_CONTAINED_BY; 255.7504 + var location_index = wysihtml.lang.array(parents).indexOf( point ); 255.7505 + if( location_index !== -1) { 255.7506 + var smallest_common_ancestor = parents[ location_index ]; 255.7507 + var this_index = wysihtml.lang.array(smallest_common_ancestor.childNodes).indexOf( parents[location_index - 1]);//smallest_common_ancestor.childNodes.toArray().indexOf( parents[location_index - 1] ); 255.7508 + var other_index = wysihtml.lang.array(smallest_common_ancestor.childNodes).indexOf( previous ); //smallest_common_ancestor.childNodes.toArray().indexOf( previous ); 255.7509 + if( this_index > other_index ) { 255.7510 + return 2; //Node.DOCUMENT_POSITION_PRECEDING; 255.7511 + } 255.7512 + else { 255.7513 + return 4; //Node.DOCUMENT_POSITION_FOLLOWING; 255.7514 + } 255.7515 + } 255.7516 + previous = point; 255.7517 + point = point.parentNode; 255.7518 + } 255.7519 + return 1; //Node.DOCUMENT_POSITION_DISCONNECTED; 255.7520 + }; 255.7521 + } 255.7522 +})(); 255.7523 + 255.7524 +wysihtml.dom.contains = (function() { 255.7525 + var documentElement = document.documentElement; 255.7526 + if (documentElement.contains) { 255.7527 + return function(container, element) { 255.7528 + if (element.nodeType !== wysihtml.ELEMENT_NODE) { 255.7529 + if (element.parentNode === container) { 255.7530 + return true; 255.7531 + } 255.7532 + element = element.parentNode; 255.7533 + } 255.7534 + return container !== element && container.contains(element); 255.7535 + }; 255.7536 + } else if (documentElement.compareDocumentPosition) { 255.7537 + return function(container, element) { 255.7538 + // https://developer.mozilla.org/en/DOM/Node.compareDocumentPosition 255.7539 + return !!(container.compareDocumentPosition(element) & 16); 255.7540 + }; 255.7541 + } 255.7542 +})(); 255.7543 + 255.7544 +(function(wysihtml) { 255.7545 + var doc = document; 255.7546 + wysihtml.dom.ContentEditableArea = Base.extend({ 255.7547 + getContentEditable: function() { 255.7548 + return this.element; 255.7549 + }, 255.7550 + 255.7551 + getWindow: function() { 255.7552 + return this.element.ownerDocument.defaultView || this.element.ownerDocument.parentWindow; 255.7553 + }, 255.7554 + 255.7555 + getDocument: function() { 255.7556 + return this.element.ownerDocument; 255.7557 + }, 255.7558 + 255.7559 + constructor: function(readyCallback, config, contentEditable) { 255.7560 + this.callback = readyCallback || wysihtml.EMPTY_FUNCTION; 255.7561 + this.config = wysihtml.lang.object({}).merge(config).get(); 255.7562 + if (!this.config.className) { 255.7563 + this.config.className = "wysihtml-sandbox"; 255.7564 + } 255.7565 + if (contentEditable) { 255.7566 + this.element = this._bindElement(contentEditable); 255.7567 + } else { 255.7568 + this.element = this._createElement(); 255.7569 + } 255.7570 + }, 255.7571 + 255.7572 + destroy: function() { 255.7573 + 255.7574 + }, 255.7575 + 255.7576 + // creates a new contenteditable and initiates it 255.7577 + _createElement: function() { 255.7578 + var element = doc.createElement("div"); 255.7579 + element.className = this.config.className; 255.7580 + this._loadElement(element); 255.7581 + return element; 255.7582 + }, 255.7583 + 255.7584 + // initiates an allready existent contenteditable 255.7585 + _bindElement: function(contentEditable) { 255.7586 + contentEditable.className = contentEditable.className ? contentEditable.className + " wysihtml-sandbox" : "wysihtml-sandbox"; 255.7587 + this._loadElement(contentEditable, true); 255.7588 + return contentEditable; 255.7589 + }, 255.7590 + 255.7591 + _loadElement: function(element, contentExists) { 255.7592 + var that = this; 255.7593 + 255.7594 + if (!contentExists) { 255.7595 + var innerHtml = this._getHtml(); 255.7596 + element.innerHTML = innerHtml; 255.7597 + } 255.7598 + 255.7599 + this.loaded = true; 255.7600 + // Trigger the callback 255.7601 + setTimeout(function() { that.callback(that); }, 0); 255.7602 + }, 255.7603 + 255.7604 + _getHtml: function(templateVars) { 255.7605 + return ''; 255.7606 + } 255.7607 + 255.7608 + }); 255.7609 +})(wysihtml); 255.7610 + 255.7611 +/** 255.7612 + * Converts an HTML fragment/element into a unordered/ordered list 255.7613 + * 255.7614 + * @param {Element} element The element which should be turned into a list 255.7615 + * @param {String} listType The list type in which to convert the tree (either "ul" or "ol") 255.7616 + * @return {Element} The created list 255.7617 + * 255.7618 + * @example 255.7619 + * <!-- Assume the following dom: --> 255.7620 + * <span id="pseudo-list"> 255.7621 + * eminem<br> 255.7622 + * dr. dre 255.7623 + * <div>50 Cent</div> 255.7624 + * </span> 255.7625 + * 255.7626 + * <script> 255.7627 + * wysihtml.dom.convertToList(document.getElementById("pseudo-list"), "ul"); 255.7628 + * </script> 255.7629 + * 255.7630 + * <!-- Will result in: --> 255.7631 + * <ul> 255.7632 + * <li>eminem</li> 255.7633 + * <li>dr. dre</li> 255.7634 + * <li>50 Cent</li> 255.7635 + * </ul> 255.7636 + */ 255.7637 +wysihtml.dom.convertToList = (function() { 255.7638 + function _createListItem(doc, list) { 255.7639 + var listItem = doc.createElement("li"); 255.7640 + list.appendChild(listItem); 255.7641 + return listItem; 255.7642 + } 255.7643 + 255.7644 + function _createList(doc, type) { 255.7645 + return doc.createElement(type); 255.7646 + } 255.7647 + 255.7648 + function convertToList(element, listType, uneditableClass) { 255.7649 + if (element.nodeName === "UL" || element.nodeName === "OL" || element.nodeName === "MENU") { 255.7650 + // Already a list 255.7651 + return element; 255.7652 + } 255.7653 + 255.7654 + var doc = element.ownerDocument, 255.7655 + list = _createList(doc, listType), 255.7656 + lineBreaks = element.querySelectorAll("br"), 255.7657 + lineBreaksLength = lineBreaks.length, 255.7658 + childNodes, 255.7659 + childNodesLength, 255.7660 + childNode, 255.7661 + lineBreak, 255.7662 + parentNode, 255.7663 + isBlockElement, 255.7664 + isLineBreak, 255.7665 + currentListItem, 255.7666 + i; 255.7667 + 255.7668 + // First find <br> at the end of inline elements and move them behind them 255.7669 + for (i=0; i<lineBreaksLength; i++) { 255.7670 + lineBreak = lineBreaks[i]; 255.7671 + while ((parentNode = lineBreak.parentNode) && parentNode !== element && parentNode.lastChild === lineBreak) { 255.7672 + if (wysihtml.dom.getStyle("display").from(parentNode) === "block") { 255.7673 + parentNode.removeChild(lineBreak); 255.7674 + break; 255.7675 + } 255.7676 + wysihtml.dom.insert(lineBreak).after(lineBreak.parentNode); 255.7677 + } 255.7678 + } 255.7679 + 255.7680 + childNodes = wysihtml.lang.array(element.childNodes).get(); 255.7681 + childNodesLength = childNodes.length; 255.7682 + 255.7683 + for (i=0; i<childNodesLength; i++) { 255.7684 + currentListItem = currentListItem || _createListItem(doc, list); 255.7685 + childNode = childNodes[i]; 255.7686 + isBlockElement = wysihtml.dom.getStyle("display").from(childNode) === "block"; 255.7687 + isLineBreak = childNode.nodeName === "BR"; 255.7688 + 255.7689 + // consider uneditable as an inline element 255.7690 + if (isBlockElement && (!uneditableClass || !wysihtml.dom.hasClass(childNode, uneditableClass))) { 255.7691 + // Append blockElement to current <li> if empty, otherwise create a new one 255.7692 + currentListItem = currentListItem.firstChild ? _createListItem(doc, list) : currentListItem; 255.7693 + currentListItem.appendChild(childNode); 255.7694 + currentListItem = null; 255.7695 + continue; 255.7696 + } 255.7697 + 255.7698 + if (isLineBreak) { 255.7699 + // Only create a new list item in the next iteration when the current one has already content 255.7700 + currentListItem = currentListItem.firstChild ? null : currentListItem; 255.7701 + continue; 255.7702 + } 255.7703 + 255.7704 + currentListItem.appendChild(childNode); 255.7705 + } 255.7706 + 255.7707 + if (childNodes.length === 0) { 255.7708 + _createListItem(doc, list); 255.7709 + } 255.7710 + 255.7711 + element.parentNode.replaceChild(list, element); 255.7712 + return list; 255.7713 + } 255.7714 + 255.7715 + return convertToList; 255.7716 +})(); 255.7717 + 255.7718 +/** 255.7719 + * Copy a set of attributes from one element to another 255.7720 + * 255.7721 + * @param {Array} attributesToCopy List of attributes which should be copied 255.7722 + * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to 255.7723 + * copy the attributes from., this again returns an object which provides a method named "to" which can be invoked 255.7724 + * with the element where to copy the attributes to (see example) 255.7725 + * 255.7726 + * @example 255.7727 + * var textarea = document.querySelector("textarea"), 255.7728 + * div = document.querySelector("div[contenteditable=true]"), 255.7729 + * anotherDiv = document.querySelector("div.preview"); 255.7730 + * wysihtml.dom.copyAttributes(["spellcheck", "value", "placeholder"]).from(textarea).to(div).andTo(anotherDiv); 255.7731 + * 255.7732 + */ 255.7733 +wysihtml.dom.copyAttributes = function(attributesToCopy) { 255.7734 + return { 255.7735 + from: function(elementToCopyFrom) { 255.7736 + return { 255.7737 + to: function pasteElementAttributesTo(elementToCopyTo) { 255.7738 + var attribute, 255.7739 + i = 0, 255.7740 + length = attributesToCopy.length; 255.7741 + for (; i<length; i++) { 255.7742 + attribute = attributesToCopy[i]; 255.7743 + if (typeof(elementToCopyFrom[attribute]) !== "undefined" && elementToCopyFrom[attribute] !== "") { 255.7744 + elementToCopyTo[attribute] = elementToCopyFrom[attribute]; 255.7745 + } 255.7746 + } 255.7747 + return { andTo: pasteElementAttributesTo }; 255.7748 + } 255.7749 + }; 255.7750 + } 255.7751 + }; 255.7752 +}; 255.7753 + 255.7754 +/** 255.7755 + * Copy a set of styles from one element to another 255.7756 + * Please note that this only works properly across browsers when the element from which to copy the styles 255.7757 + * is in the dom 255.7758 + * 255.7759 + * Interesting article on how to copy styles 255.7760 + * 255.7761 + * @param {Array} stylesToCopy List of styles which should be copied 255.7762 + * @return {Object} Returns an object which offers the "from" method which can be invoked with the element where to 255.7763 + * copy the styles from., this again returns an object which provides a method named "to" which can be invoked 255.7764 + * with the element where to copy the styles to (see example) 255.7765 + * 255.7766 + * @example 255.7767 + * var textarea = document.querySelector("textarea"), 255.7768 + * div = document.querySelector("div[contenteditable=true]"), 255.7769 + * anotherDiv = document.querySelector("div.preview"); 255.7770 + * wysihtml.dom.copyStyles(["overflow-y", "width", "height"]).from(textarea).to(div).andTo(anotherDiv); 255.7771 + * 255.7772 + */ 255.7773 +(function(dom) { 255.7774 + 255.7775 + /** 255.7776 + * Mozilla, WebKit and Opera recalculate the computed width when box-sizing: boder-box; is set 255.7777 + * So if an element has "width: 200px; -moz-box-sizing: border-box; border: 1px;" then 255.7778 + * its computed css width will be 198px 255.7779 + * 255.7780 + * See https://bugzilla.mozilla.org/show_bug.cgi?id=520992 255.7781 + */ 255.7782 + var BOX_SIZING_PROPERTIES = ["-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing"]; 255.7783 + 255.7784 + var shouldIgnoreBoxSizingBorderBox = function(element) { 255.7785 + if (hasBoxSizingBorderBox(element)) { 255.7786 + return parseInt(dom.getStyle("width").from(element), 10) < element.offsetWidth; 255.7787 + } 255.7788 + return false; 255.7789 + }; 255.7790 + 255.7791 + var hasBoxSizingBorderBox = function(element) { 255.7792 + var i = 0, 255.7793 + length = BOX_SIZING_PROPERTIES.length; 255.7794 + for (; i<length; i++) { 255.7795 + if (dom.getStyle(BOX_SIZING_PROPERTIES[i]).from(element) === "border-box") { 255.7796 + return BOX_SIZING_PROPERTIES[i]; 255.7797 + } 255.7798 + } 255.7799 + }; 255.7800 + 255.7801 + dom.copyStyles = function(stylesToCopy) { 255.7802 + return { 255.7803 + from: function(element) { 255.7804 + if (shouldIgnoreBoxSizingBorderBox(element)) { 255.7805 + stylesToCopy = wysihtml.lang.array(stylesToCopy).without(BOX_SIZING_PROPERTIES); 255.7806 + } 255.7807 + 255.7808 + var cssText = "", 255.7809 + length = stylesToCopy.length, 255.7810 + i = 0, 255.7811 + property; 255.7812 + for (; i<length; i++) { 255.7813 + property = stylesToCopy[i]; 255.7814 + cssText += property + ":" + dom.getStyle(property).from(element) + ";"; 255.7815 + } 255.7816 + 255.7817 + return { 255.7818 + to: function pasteStylesTo(element) { 255.7819 + dom.setStyles(cssText).on(element); 255.7820 + return { andTo: pasteStylesTo }; 255.7821 + } 255.7822 + }; 255.7823 + } 255.7824 + }; 255.7825 + }; 255.7826 +})(wysihtml.dom); 255.7827 + 255.7828 +/** 255.7829 + * Event Delegation 255.7830 + * 255.7831 + * @example 255.7832 + * wysihtml.dom.delegate(document.body, "a", "click", function() { 255.7833 + * // foo 255.7834 + * }); 255.7835 + */ 255.7836 +(function(wysihtml) { 255.7837 + wysihtml.dom.delegate = function(container, selector, eventName, handler) { 255.7838 + var callback = function(event) { 255.7839 + var target = event.target, 255.7840 + element = (target.nodeType === 3) ? target.parentNode : target, // IE has .contains only seeing elements not textnodes 255.7841 + matches = container.querySelectorAll(selector); 255.7842 + 255.7843 + for (var i = 0, max = matches.length; i < max; i++) { 255.7844 + if (matches[i].contains(element)) { 255.7845 + handler.call(matches[i], event); 255.7846 + } 255.7847 + } 255.7848 + }; 255.7849 + 255.7850 + container.addEventListener(eventName, callback, false); 255.7851 + return { 255.7852 + stop: function() { 255.7853 + container.removeEventListener(eventName, callback, false); 255.7854 + } 255.7855 + }; 255.7856 + }; 255.7857 +})(wysihtml); 255.7858 + 255.7859 +// TODO: Refactor dom tree traversing here 255.7860 +(function(wysihtml) { 255.7861 + 255.7862 + // Finds parents of a node, returning the outermost node first in Array 255.7863 + // if contain node is given parents search is stopped at the container 255.7864 + function parents(node, container) { 255.7865 + var nodes = [node], n = node; 255.7866 + 255.7867 + // iterate parents while parent exists and it is not container element 255.7868 + while((container && n && n !== container) || (!container && n)) { 255.7869 + nodes.unshift(n); 255.7870 + n = n.parentNode; 255.7871 + } 255.7872 + return nodes; 255.7873 + } 255.7874 + 255.7875 + wysihtml.dom.domNode = function(node) { 255.7876 + var defaultNodeTypes = [wysihtml.ELEMENT_NODE, wysihtml.TEXT_NODE]; 255.7877 + 255.7878 + return { 255.7879 + 255.7880 + is: { 255.7881 + emptyTextNode: function(ignoreWhitespace) { 255.7882 + var regx = ignoreWhitespace ? (/^\s*$/g) : (/^[\r\n]*$/g); 255.7883 + return node && node.nodeType === wysihtml.TEXT_NODE && (regx).test(node.data); 255.7884 + }, 255.7885 + 255.7886 + // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring) 255.7887 + rangyBookmark: function() { 255.7888 + return node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary'); 255.7889 + }, 255.7890 + 255.7891 + visible: function() { 255.7892 + var isVisible = !(/^\s*$/g).test(wysihtml.dom.getTextContent(node)); 255.7893 + 255.7894 + if (!isVisible) { 255.7895 + if (node.nodeType === 1 && node.querySelector('img, br, hr, object, embed, canvas, input, textarea')) { 255.7896 + isVisible = true; 255.7897 + } 255.7898 + } 255.7899 + return isVisible; 255.7900 + }, 255.7901 + lineBreak: function() { 255.7902 + return node && node.nodeType === 1 && node.nodeName === "BR"; 255.7903 + }, 255.7904 + block: function() { 255.7905 + return node && node.nodeType === 1 && node.ownerDocument.defaultView.getComputedStyle(node).display === "block"; 255.7906 + }, 255.7907 + // Void elements are elemens that can not have content 255.7908 + // In most cases browsers should solve the cases for you when you try to insert content into those, 255.7909 + // but IE does not and it is not nice to do so anyway. 255.7910 + voidElement: function() { 255.7911 + return wysihtml.dom.domNode(node).test({ 255.7912 + query: wysihtml.VOID_ELEMENTS 255.7913 + }); 255.7914 + } 255.7915 + }, 255.7916 + 255.7917 + // var node = wysihtml.dom.domNode(element).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); 255.7918 + prev: function(options) { 255.7919 + var prevNode = node.previousSibling, 255.7920 + types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes; 255.7921 + 255.7922 + if (!prevNode) { 255.7923 + return null; 255.7924 + } 255.7925 + 255.7926 + if ( 255.7927 + wysihtml.dom.domNode(prevNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass) 255.7928 + (!wysihtml.lang.array(types).contains(prevNode.nodeType)) || // nodeTypes check. 255.7929 + (options && options.ignoreBlankTexts && wysihtml.dom.domNode(prevNode).is.emptyTextNode(true)) // Blank text nodes bypassed if set 255.7930 + ) { 255.7931 + return wysihtml.dom.domNode(prevNode).prev(options); 255.7932 + } 255.7933 + 255.7934 + return prevNode; 255.7935 + }, 255.7936 + 255.7937 + // var node = wysihtml.dom.domNode(element).next({nodeTypes: [1,3], ignoreBlankTexts: true}); 255.7938 + next: function(options) { 255.7939 + var nextNode = node.nextSibling, 255.7940 + types = (options && options.nodeTypes) ? options.nodeTypes : defaultNodeTypes; 255.7941 + 255.7942 + if (!nextNode) { 255.7943 + return null; 255.7944 + } 255.7945 + 255.7946 + if ( 255.7947 + wysihtml.dom.domNode(nextNode).is.rangyBookmark() || // is Rangy temporary boomark element (bypass) 255.7948 + (!wysihtml.lang.array(types).contains(nextNode.nodeType)) || // nodeTypes check. 255.7949 + (options && options.ignoreBlankTexts && wysihtml.dom.domNode(nextNode).is.emptyTextNode(true)) // blank text nodes bypassed if set 255.7950 + ) { 255.7951 + return wysihtml.dom.domNode(nextNode).next(options); 255.7952 + } 255.7953 + 255.7954 + return nextNode; 255.7955 + }, 255.7956 + 255.7957 + // Finds the common acnestor container of two nodes 255.7958 + // If container given stops search at the container 255.7959 + // If no common ancestor found returns null 255.7960 + // var node = wysihtml.dom.domNode(element).commonAncestor(node2, container); 255.7961 + commonAncestor: function(node2, container) { 255.7962 + var parents1 = parents(node, container), 255.7963 + parents2 = parents(node2, container); 255.7964 + 255.7965 + // Ensure we have found a common ancestor, which will be the first one if anything 255.7966 + if (parents1[0] != parents2[0]) { 255.7967 + return null; 255.7968 + } 255.7969 + 255.7970 + // Traverse up the hierarchy of parents until we reach where they're no longer 255.7971 + // the same. Then return previous which was the common ancestor. 255.7972 + for (var i = 0; i < parents1.length; i++) { 255.7973 + if (parents1[i] != parents2[i]) { 255.7974 + return parents1[i - 1]; 255.7975 + } 255.7976 + } 255.7977 + 255.7978 + return null; 255.7979 + }, 255.7980 + 255.7981 + // Traverses a node for last children and their chidren (including itself), and finds the last node that has no children. 255.7982 + // Array of classes for forced last-leaves (ex: uneditable-container) can be defined (options = {leafClasses: [...]}) 255.7983 + // Useful for finding the actually visible element before cursor 255.7984 + lastLeafNode: function(options) { 255.7985 + var lastChild; 255.7986 + 255.7987 + // Returns non-element nodes 255.7988 + if (node.nodeType !== 1) { 255.7989 + return node; 255.7990 + } 255.7991 + 255.7992 + // Returns if element is leaf 255.7993 + lastChild = node.lastChild; 255.7994 + if (!lastChild) { 255.7995 + return node; 255.7996 + } 255.7997 + 255.7998 + // Returns if element is of of options.leafClasses leaf 255.7999 + if (options && options.leafClasses) { 255.8000 + for (var i = options.leafClasses.length; i--;) { 255.8001 + if (wysihtml.dom.hasClass(node, options.leafClasses[i])) { 255.8002 + return node; 255.8003 + } 255.8004 + } 255.8005 + } 255.8006 + 255.8007 + return wysihtml.dom.domNode(lastChild).lastLeafNode(options); 255.8008 + }, 255.8009 + 255.8010 + // Splits element at childnode and extracts the childNode out of the element context 255.8011 + // Example: 255.8012 + // var node = wysihtml.dom.domNode(node).escapeParent(parentNode); 255.8013 + escapeParent: function(element, newWrapper) { 255.8014 + var parent, split2, nodeWrap, 255.8015 + curNode = node; 255.8016 + 255.8017 + // Stop if node is not a descendant of element 255.8018 + if (!wysihtml.dom.contains(element, node)) { 255.8019 + throw new Error("Child is not a descendant of node."); 255.8020 + } 255.8021 + 255.8022 + // Climb up the node tree untill node is reached 255.8023 + do { 255.8024 + // Get current parent of node 255.8025 + parent = curNode.parentNode; 255.8026 + 255.8027 + // Move after nodes to new clone wrapper 255.8028 + split2 = parent.cloneNode(false); 255.8029 + while (parent.lastChild && parent.lastChild !== curNode) { 255.8030 + split2.insertBefore(parent.lastChild, split2.firstChild); 255.8031 + } 255.8032 + 255.8033 + // Move node up a level. If parent is not yet the container to escape, clone the parent around node, so inner nodes are escaped out too 255.8034 + if (parent !== element) { 255.8035 + nodeWrap = parent.cloneNode(false); 255.8036 + nodeWrap.appendChild(curNode); 255.8037 + curNode = nodeWrap; 255.8038 + } 255.8039 + parent.parentNode.insertBefore(curNode, parent.nextSibling); 255.8040 + 255.8041 + // Add after nodes (unless empty) 255.8042 + if (split2.innerHTML !== '') { 255.8043 + // if contents are empty insert without wrap 255.8044 + if ((/^\s+$/).test(split2.innerHTML)) { 255.8045 + while (split2.lastChild) { 255.8046 + parent.parentNode.insertBefore(split2.lastChild, curNode.nextSibling); 255.8047 + } 255.8048 + } else { 255.8049 + parent.parentNode.insertBefore(split2, curNode.nextSibling); 255.8050 + } 255.8051 + } 255.8052 + 255.8053 + // If the node left behind before the split (parent) is now empty then remove 255.8054 + if (parent.innerHTML === '') { 255.8055 + parent.parentNode.removeChild(parent); 255.8056 + } else if ((/^\s+$/).test(parent.innerHTML)) { 255.8057 + while (parent.firstChild) { 255.8058 + parent.parentNode.insertBefore(parent.firstChild, parent); 255.8059 + } 255.8060 + parent.parentNode.removeChild(parent); 255.8061 + } 255.8062 + 255.8063 + } while (parent && parent !== element); 255.8064 + 255.8065 + if (newWrapper && curNode) { 255.8066 + curNode.parentNode.insertBefore(newWrapper, curNode); 255.8067 + newWrapper.appendChild(curNode); 255.8068 + } 255.8069 + }, 255.8070 + 255.8071 + transferContentTo: function(targetNode, removeOldWrapper) { 255.8072 + if (node.nodeType === 1) { 255.8073 + if (wysihtml.dom.domNode(targetNode).is.voidElement() || targetNode.nodeType === 3) { 255.8074 + while (node.lastChild) { 255.8075 + targetNode.parentNode.insertBefore(node.lastChild, targetNode.nextSibling); 255.8076 + } 255.8077 + } else { 255.8078 + while (node.firstChild) { 255.8079 + targetNode.appendChild(node.firstChild); 255.8080 + } 255.8081 + } 255.8082 + if (removeOldWrapper) { 255.8083 + node.parentNode.removeChild(node); 255.8084 + } 255.8085 + } else if (node.nodeType === 3 || node.nodeType === 8){ 255.8086 + if (wysihtml.dom.domNode(targetNode).is.voidElement()) { 255.8087 + targetNode.parentNode.insertBefore(node, targetNode.nextSibling); 255.8088 + } else { 255.8089 + targetNode.appendChild(node); 255.8090 + } 255.8091 + } 255.8092 + }, 255.8093 + 255.8094 + /* 255.8095 + Tests a node against properties, and returns true if matches. 255.8096 + Tests on principle that all properties defined must have at least one match. 255.8097 + styleValue parameter works in context of styleProperty and has no effect otherwise. 255.8098 + Returns true if element matches and false if it does not. 255.8099 + 255.8100 + Properties for filtering element: 255.8101 + { 255.8102 + query: selector string, 255.8103 + nodeName: string (uppercase), 255.8104 + className: string, 255.8105 + classRegExp: regex, 255.8106 + styleProperty: string or [], 255.8107 + styleValue: string, [] or regex 255.8108 + } 255.8109 + 255.8110 + Example: 255.8111 + var node = wysihtml.dom.domNode(element).test({}) 255.8112 + */ 255.8113 + test: function(properties) { 255.8114 + var prop; 255.8115 + 255.8116 + // return false if properties object is not defined 255.8117 + if (!properties) { 255.8118 + return false; 255.8119 + } 255.8120 + 255.8121 + // Only element nodes can be tested for these properties 255.8122 + if (node.nodeType !== 1) { 255.8123 + return false; 255.8124 + } 255.8125 + 255.8126 + if (properties.query) { 255.8127 + if (!node.matches(properties.query)) { 255.8128 + return false; 255.8129 + } 255.8130 + } 255.8131 + 255.8132 + if (properties.nodeName && node.nodeName.toLowerCase() !== properties.nodeName.toLowerCase()) { 255.8133 + return false; 255.8134 + } 255.8135 + 255.8136 + if (properties.className && !node.classList.contains(properties.className)) { 255.8137 + return false; 255.8138 + } 255.8139 + 255.8140 + // classRegExp check (useful for classname begins with logic) 255.8141 + if (properties.classRegExp) { 255.8142 + var matches = (node.className || "").match(properties.classRegExp) || []; 255.8143 + if (matches.length === 0) { 255.8144 + return false; 255.8145 + } 255.8146 + } 255.8147 + 255.8148 + // styleProperty check 255.8149 + if (properties.styleProperty && properties.styleProperty.length > 0) { 255.8150 + var hasOneStyle = false, 255.8151 + styles = (Array.isArray(properties.styleProperty)) ? properties.styleProperty : [properties.styleProperty]; 255.8152 + for (var j = 0, maxStyleP = styles.length; j < maxStyleP; j++) { 255.8153 + // Some old IE-s have different property name for cssFloat 255.8154 + prop = wysihtml.browser.fixStyleKey(styles[j]); 255.8155 + if (node.style[prop]) { 255.8156 + if (properties.styleValue) { 255.8157 + // Style value as additional parameter 255.8158 + if (properties.styleValue instanceof RegExp) { 255.8159 + // style value as Regexp 255.8160 + if (node.style[prop].trim().match(properties.styleValue).length > 0) { 255.8161 + hasOneStyle = true; 255.8162 + break; 255.8163 + } 255.8164 + } else if (Array.isArray(properties.styleValue)) { 255.8165 + // style value as array 255.8166 + if (properties.styleValue.indexOf(node.style[prop].trim())) { 255.8167 + hasOneStyle = true; 255.8168 + break; 255.8169 + } 255.8170 + } else { 255.8171 + // style value as string 255.8172 + if (properties.styleValue === node.style[prop].trim().replace(/, /g, ",")) { 255.8173 + hasOneStyle = true; 255.8174 + break; 255.8175 + } 255.8176 + } 255.8177 + } else { 255.8178 + hasOneStyle = true; 255.8179 + break; 255.8180 + } 255.8181 + } 255.8182 + if (!hasOneStyle) { 255.8183 + return false; 255.8184 + } 255.8185 + } 255.8186 + } 255.8187 + 255.8188 + if (properties.attribute) { 255.8189 + var attr = wysihtml.dom.getAttributes(node), 255.8190 + attrList = [], 255.8191 + hasOneAttribute = false; 255.8192 + 255.8193 + if (Array.isArray(properties.attribute)) { 255.8194 + attrList = properties.attribute; 255.8195 + } else { 255.8196 + attrList[properties.attribute] = properties.attributeValue; 255.8197 + } 255.8198 + 255.8199 + for (var a in attrList) { 255.8200 + if (attrList.hasOwnProperty(a)) { 255.8201 + if (typeof attrList[a] === "undefined") { 255.8202 + if (typeof attr[a] !== "undefined") { 255.8203 + hasOneAttribute = true; 255.8204 + break; 255.8205 + } 255.8206 + } else if (attr[a] === attrList[a]) { 255.8207 + hasOneAttribute = true; 255.8208 + break; 255.8209 + } 255.8210 + } 255.8211 + } 255.8212 + 255.8213 + if (!hasOneAttribute) { 255.8214 + return false; 255.8215 + } 255.8216 + 255.8217 + } 255.8218 + 255.8219 + return true; 255.8220 + } 255.8221 + 255.8222 + }; 255.8223 + }; 255.8224 +})(wysihtml); 255.8225 + 255.8226 +/** 255.8227 + * Returns the given html wrapped in a div element 255.8228 + * 255.8229 + * Fixing IE's inability to treat unknown elements (HTML5 section, article, ...) correctly 255.8230 + * when inserted via innerHTML 255.8231 + * 255.8232 + * @param {String} html The html which should be wrapped in a dom element 255.8233 + * @param {Obejct} [context] Document object of the context the html belongs to 255.8234 + * 255.8235 + * @example 255.8236 + * wysihtml.dom.getAsDom("<article>foo</article>"); 255.8237 + */ 255.8238 +wysihtml.dom.getAsDom = (function() { 255.8239 + 255.8240 + var _innerHTMLShiv = function(html, context) { 255.8241 + var tempElement = context.createElement("div"); 255.8242 + tempElement.style.display = "none"; 255.8243 + context.body.appendChild(tempElement); 255.8244 + // IE throws an exception when trying to insert <frameset></frameset> via innerHTML 255.8245 + try { tempElement.innerHTML = html; } catch(e) {} 255.8246 + context.body.removeChild(tempElement); 255.8247 + return tempElement; 255.8248 + }; 255.8249 + 255.8250 + /** 255.8251 + * Make sure IE supports HTML5 tags, which is accomplished by simply creating one instance of each element 255.8252 + */ 255.8253 + var _ensureHTML5Compatibility = function(context) { 255.8254 + if (context._wysihtml_supportsHTML5Tags) { 255.8255 + return; 255.8256 + } 255.8257 + for (var i=0, length=HTML5_ELEMENTS.length; i<length; i++) { 255.8258 + context.createElement(HTML5_ELEMENTS[i]); 255.8259 + } 255.8260 + context._wysihtml_supportsHTML5Tags = true; 255.8261 + }; 255.8262 + 255.8263 + 255.8264 + /** 255.8265 + * List of html5 tags 255.8266 + * taken from http://simon.html5.org/html5-elements 255.8267 + */ 255.8268 + var HTML5_ELEMENTS = [ 255.8269 + "abbr", "article", "aside", "audio", "bdi", "canvas", "command", "datalist", "details", "figcaption", 255.8270 + "figure", "footer", "header", "hgroup", "keygen", "mark", "meter", "nav", "output", "progress", 255.8271 + "rp", "rt", "ruby", "svg", "section", "source", "summary", "time", "track", "video", "wbr" 255.8272 + ]; 255.8273 + 255.8274 + return function(html, context) { 255.8275 + context = context || document; 255.8276 + var tempElement; 255.8277 + if (typeof(html) === "object" && html.nodeType) { 255.8278 + tempElement = context.createElement("div"); 255.8279 + tempElement.appendChild(html); 255.8280 + } else if (wysihtml.browser.supportsHTML5Tags(context)) { 255.8281 + tempElement = context.createElement("div"); 255.8282 + tempElement.innerHTML = html; 255.8283 + } else { 255.8284 + _ensureHTML5Compatibility(context); 255.8285 + tempElement = _innerHTMLShiv(html, context); 255.8286 + } 255.8287 + return tempElement; 255.8288 + }; 255.8289 +})(); 255.8290 + 255.8291 +/** 255.8292 + * Get a set of attribute from one element 255.8293 + * 255.8294 + * IE gives wrong results for hasAttribute/getAttribute, for example: 255.8295 + * var td = document.createElement("td"); 255.8296 + * td.getAttribute("rowspan"); // => "1" in IE 255.8297 + * 255.8298 + * Therefore we have to check the element's outerHTML for the attribute 255.8299 +*/ 255.8300 + 255.8301 +wysihtml.dom.getAttribute = function(node, attributeName) { 255.8302 + var HAS_GET_ATTRIBUTE_BUG = !wysihtml.browser.supportsGetAttributeCorrectly(); 255.8303 + attributeName = attributeName.toLowerCase(); 255.8304 + var nodeName = node.nodeName; 255.8305 + if (nodeName == "IMG" && attributeName == "src" && wysihtml.dom.isLoadedImage(node) === true) { 255.8306 + // Get 'src' attribute value via object property since this will always contain the 255.8307 + // full absolute url (http://...) 255.8308 + // this fixes a very annoying bug in firefox (ver 3.6 & 4) and IE 8 where images copied from the same host 255.8309 + // will have relative paths, which the sanitizer strips out (see attributeCheckMethods.url) 255.8310 + return node.src; 255.8311 + } else if (HAS_GET_ATTRIBUTE_BUG && "outerHTML" in node) { 255.8312 + // Don't trust getAttribute/hasAttribute in IE 6-8, instead check the element's outerHTML 255.8313 + var outerHTML = node.outerHTML.toLowerCase(), 255.8314 + // TODO: This might not work for attributes without value: <input disabled> 255.8315 + hasAttribute = outerHTML.indexOf(" " + attributeName + "=") != -1; 255.8316 + 255.8317 + return hasAttribute ? node.getAttribute(attributeName) : null; 255.8318 + } else{ 255.8319 + return node.getAttribute(attributeName); 255.8320 + } 255.8321 +}; 255.8322 + 255.8323 +/** 255.8324 + * Get all attributes of an element 255.8325 + * 255.8326 + * IE gives wrong results for hasAttribute/getAttribute, for example: 255.8327 + * var td = document.createElement("td"); 255.8328 + * td.getAttribute("rowspan"); // => "1" in IE 255.8329 + * 255.8330 + * Therefore we have to check the element's outerHTML for the attribute 255.8331 +*/ 255.8332 + 255.8333 +wysihtml.dom.getAttributes = function(node) { 255.8334 + var HAS_GET_ATTRIBUTE_BUG = !wysihtml.browser.supportsGetAttributeCorrectly(), 255.8335 + nodeName = node.nodeName, 255.8336 + attributes = [], 255.8337 + attr; 255.8338 + 255.8339 + for (attr in node.attributes) { 255.8340 + if ((node.attributes.hasOwnProperty && node.attributes.hasOwnProperty(attr)) || (!node.attributes.hasOwnProperty && Object.prototype.hasOwnProperty.call(node.attributes, attr))) { 255.8341 + if (node.attributes[attr].specified) { 255.8342 + if (nodeName == "IMG" && node.attributes[attr].name.toLowerCase() == "src" && wysihtml.dom.isLoadedImage(node) === true) { 255.8343 + attributes['src'] = node.src; 255.8344 + } else if (wysihtml.lang.array(['rowspan', 'colspan']).contains(node.attributes[attr].name.toLowerCase()) && HAS_GET_ATTRIBUTE_BUG) { 255.8345 + if (node.attributes[attr].value !== 1) { 255.8346 + attributes[node.attributes[attr].name] = node.attributes[attr].value; 255.8347 + } 255.8348 + } else { 255.8349 + attributes[node.attributes[attr].name] = node.attributes[attr].value; 255.8350 + } 255.8351 + } 255.8352 + } 255.8353 + } 255.8354 + return attributes; 255.8355 +}; 255.8356 + 255.8357 +/** 255.8358 + * Walks the dom tree from the given node up until it finds a match 255.8359 + * 255.8360 + * @param {Element} node The from which to check the parent nodes 255.8361 + * @param {Object} matchingSet Object to match against, Properties for filtering element: 255.8362 + * { 255.8363 + * query: selector string, 255.8364 + * classRegExp: regex, 255.8365 + * styleProperty: string or [], 255.8366 + * styleValue: string, [] or regex 255.8367 + * } 255.8368 + * @param {Number} [levels] How many parents should the function check up from the current node (defaults to 50) 255.8369 + * @param {Element} Optional, defines the container that limits the search 255.8370 + * 255.8371 + * @return {null|Element} Returns the first element that matched the desiredNodeName(s) 255.8372 +*/ 255.8373 + 255.8374 +wysihtml.dom.getParentElement = (function() { 255.8375 + 255.8376 + return function(node, properties, levels, container) { 255.8377 + levels = levels || 50; 255.8378 + while (levels-- && node && node.nodeName !== "BODY" && (!container || node !== container)) { 255.8379 + if (wysihtml.dom.domNode(node).test(properties)) { 255.8380 + return node; 255.8381 + } 255.8382 + node = node.parentNode; 255.8383 + } 255.8384 + return null; 255.8385 + }; 255.8386 + 255.8387 +})(); 255.8388 + 255.8389 +/* 255.8390 + * Methods for fetching pasted html before it gets inserted into content 255.8391 +**/ 255.8392 + 255.8393 +/* Modern event.clipboardData driven approach. 255.8394 + * Advantage is that it does not have to loose selection or modify dom to catch the data. 255.8395 + * IE does not support though. 255.8396 +**/ 255.8397 +wysihtml.dom.getPastedHtml = function(event) { 255.8398 + var html; 255.8399 + if (wysihtml.browser.supportsModernPaste() && event.clipboardData) { 255.8400 + if (wysihtml.lang.array(event.clipboardData.types).contains('text/html')) { 255.8401 + html = event.clipboardData.getData('text/html'); 255.8402 + } else if (wysihtml.lang.array(event.clipboardData.types).contains('text/plain')) { 255.8403 + html = wysihtml.lang.string(event.clipboardData.getData('text/plain')).escapeHTML(true, true); 255.8404 + } 255.8405 + } 255.8406 + return html; 255.8407 +}; 255.8408 + 255.8409 +/* Older temprorary contenteditable as paste source catcher method for fallbacks */ 255.8410 +wysihtml.dom.getPastedHtmlWithDiv = function (composer, f) { 255.8411 + var selBookmark = composer.selection.getBookmark(), 255.8412 + doc = composer.element.ownerDocument, 255.8413 + cleanerDiv = doc.createElement('DIV'), 255.8414 + scrollPos = composer.getScrollPos(); 255.8415 + 255.8416 + doc.body.appendChild(cleanerDiv); 255.8417 + 255.8418 + cleanerDiv.style.width = "1px"; 255.8419 + cleanerDiv.style.height = "1px"; 255.8420 + cleanerDiv.style.overflow = "hidden"; 255.8421 + cleanerDiv.style.position = "absolute"; 255.8422 + cleanerDiv.style.top = scrollPos.y + "px"; 255.8423 + cleanerDiv.style.left = scrollPos.x + "px"; 255.8424 + 255.8425 + cleanerDiv.setAttribute('contenteditable', 'true'); 255.8426 + cleanerDiv.focus(); 255.8427 + 255.8428 + setTimeout(function () { 255.8429 + var html; 255.8430 + 255.8431 + composer.selection.setBookmark(selBookmark); 255.8432 + html = cleanerDiv.innerHTML; 255.8433 + if (html && (/^<br\/?>$/i).test(html.trim())) { 255.8434 + html = false; 255.8435 + } 255.8436 + f(html); 255.8437 + cleanerDiv.parentNode.removeChild(cleanerDiv); 255.8438 + }, 0); 255.8439 +}; 255.8440 + 255.8441 +/** 255.8442 + * Get element's style for a specific css property 255.8443 + * 255.8444 + * @param {Element} element The element on which to retrieve the style 255.8445 + * @param {String} property The CSS property to retrieve ("float", "display", "text-align", ...) 255.8446 + * 255.8447 + * @example 255.8448 + * wysihtml.dom.getStyle("display").from(document.body); 255.8449 + * // => "block" 255.8450 + */ 255.8451 +wysihtml.dom.getStyle = (function() { 255.8452 + var stylePropertyMapping = { 255.8453 + "float": ("styleFloat" in document.createElement("div").style) ? "styleFloat" : "cssFloat" 255.8454 + }, 255.8455 + REG_EXP_CAMELIZE = /\-[a-z]/g; 255.8456 + 255.8457 + function camelize(str) { 255.8458 + return str.replace(REG_EXP_CAMELIZE, function(match) { 255.8459 + return match.charAt(1).toUpperCase(); 255.8460 + }); 255.8461 + } 255.8462 + 255.8463 + return function(property) { 255.8464 + return { 255.8465 + from: function(element) { 255.8466 + if (element.nodeType !== wysihtml.ELEMENT_NODE) { 255.8467 + return; 255.8468 + } 255.8469 + 255.8470 + var doc = element.ownerDocument, 255.8471 + camelizedProperty = stylePropertyMapping[property] || camelize(property), 255.8472 + style = element.style, 255.8473 + currentStyle = element.currentStyle, 255.8474 + styleValue = style[camelizedProperty]; 255.8475 + if (styleValue) { 255.8476 + return styleValue; 255.8477 + } 255.8478 + 255.8479 + // currentStyle is no standard and only supported by Opera and IE but it has one important advantage over the standard-compliant 255.8480 + // window.getComputedStyle, since it returns css property values in their original unit: 255.8481 + // If you set an elements width to "50%", window.getComputedStyle will give you it's current width in px while currentStyle 255.8482 + // gives you the original "50%". 255.8483 + // Opera supports both, currentStyle and window.getComputedStyle, that's why checking for currentStyle should have higher prio 255.8484 + if (currentStyle) { 255.8485 + try { 255.8486 + return currentStyle[camelizedProperty]; 255.8487 + } catch(e) { 255.8488 + //ie will occasionally fail for unknown reasons. swallowing exception 255.8489 + } 255.8490 + } 255.8491 + 255.8492 + var win = doc.defaultView || doc.parentWindow, 255.8493 + needsOverflowReset = (property === "height" || property === "width") && element.nodeName === "TEXTAREA", 255.8494 + originalOverflow, 255.8495 + returnValue; 255.8496 + 255.8497 + if (win.getComputedStyle) { 255.8498 + // Chrome and Safari both calculate a wrong width and height for textareas when they have scroll bars 255.8499 + // therfore we remove and restore the scrollbar and calculate the value in between 255.8500 + if (needsOverflowReset) { 255.8501 + originalOverflow = style.overflow; 255.8502 + style.overflow = "hidden"; 255.8503 + } 255.8504 + returnValue = win.getComputedStyle(element, null).getPropertyValue(property); 255.8505 + if (needsOverflowReset) { 255.8506 + style.overflow = originalOverflow || ""; 255.8507 + } 255.8508 + return returnValue; 255.8509 + } 255.8510 + } 255.8511 + }; 255.8512 + }; 255.8513 +})(); 255.8514 + 255.8515 +wysihtml.dom.getTextNodes = function(node, ingoreEmpty){ 255.8516 + var all = []; 255.8517 + for (node=node.firstChild;node;node=node.nextSibling){ 255.8518 + if (node.nodeType == 3) { 255.8519 + if (!ingoreEmpty || !(/^\s*$/).test(node.innerText || node.textContent)) { 255.8520 + all.push(node); 255.8521 + } 255.8522 + } else { 255.8523 + all = all.concat(wysihtml.dom.getTextNodes(node, ingoreEmpty)); 255.8524 + } 255.8525 + } 255.8526 + return all; 255.8527 +}; 255.8528 + 255.8529 +/** 255.8530 + * High performant way to check whether an element with a specific class name is in the given document 255.8531 + * Optimized for being heavily executed 255.8532 + * Unleashes the power of live node lists 255.8533 + * 255.8534 + * @param {Object} doc The document object of the context where to check 255.8535 + * @param {String} tagName Upper cased tag name 255.8536 + * @example 255.8537 + * wysihtml.dom.hasElementWithClassName(document, "foobar"); 255.8538 + */ 255.8539 +(function(wysihtml) { 255.8540 + var LIVE_CACHE = {}, 255.8541 + DOCUMENT_IDENTIFIER = 1; 255.8542 + 255.8543 + function _getDocumentIdentifier(doc) { 255.8544 + return doc._wysihtml_identifier || (doc._wysihtml_identifier = DOCUMENT_IDENTIFIER++); 255.8545 + } 255.8546 + 255.8547 + wysihtml.dom.hasElementWithClassName = function(doc, className) { 255.8548 + // getElementsByClassName is not supported by IE<9 255.8549 + // but is sometimes mocked via library code (which then doesn't return live node lists) 255.8550 + if (!wysihtml.browser.supportsNativeGetElementsByClassName()) { 255.8551 + return !!doc.querySelector("." + className); 255.8552 + } 255.8553 + 255.8554 + var key = _getDocumentIdentifier(doc) + ":" + className, 255.8555 + cacheEntry = LIVE_CACHE[key]; 255.8556 + if (!cacheEntry) { 255.8557 + cacheEntry = LIVE_CACHE[key] = doc.getElementsByClassName(className); 255.8558 + } 255.8559 + 255.8560 + return cacheEntry.length > 0; 255.8561 + }; 255.8562 +})(wysihtml); 255.8563 + 255.8564 +/** 255.8565 + * High performant way to check whether an element with a specific tag name is in the given document 255.8566 + * Optimized for being heavily executed 255.8567 + * Unleashes the power of live node lists 255.8568 + * 255.8569 + * @param {Object} doc The document object of the context where to check 255.8570 + * @param {String} tagName Upper cased tag name 255.8571 + * @example 255.8572 + * wysihtml.dom.hasElementWithTagName(document, "IMG"); 255.8573 + */ 255.8574 +wysihtml.dom.hasElementWithTagName = (function() { 255.8575 + var LIVE_CACHE = {}, 255.8576 + DOCUMENT_IDENTIFIER = 1; 255.8577 + 255.8578 + function _getDocumentIdentifier(doc) { 255.8579 + return doc._wysihtml_identifier || (doc._wysihtml_identifier = DOCUMENT_IDENTIFIER++); 255.8580 + } 255.8581 + 255.8582 + return function(doc, tagName) { 255.8583 + var key = _getDocumentIdentifier(doc) + ":" + tagName, 255.8584 + cacheEntry = LIVE_CACHE[key]; 255.8585 + if (!cacheEntry) { 255.8586 + cacheEntry = LIVE_CACHE[key] = doc.getElementsByTagName(tagName); 255.8587 + } 255.8588 + 255.8589 + return cacheEntry.length > 0; 255.8590 + }; 255.8591 +})(); 255.8592 + 255.8593 +wysihtml.dom.insert = function(elementToInsert) { 255.8594 + return { 255.8595 + after: function(element) { 255.8596 + element.parentNode.insertBefore(elementToInsert, element.nextSibling); 255.8597 + }, 255.8598 + 255.8599 + before: function(element) { 255.8600 + element.parentNode.insertBefore(elementToInsert, element); 255.8601 + }, 255.8602 + 255.8603 + into: function(element) { 255.8604 + element.appendChild(elementToInsert); 255.8605 + } 255.8606 + }; 255.8607 +}; 255.8608 + 255.8609 +wysihtml.dom.insertCSS = function(rules) { 255.8610 + rules = rules.join("\n"); 255.8611 + 255.8612 + return { 255.8613 + into: function(doc) { 255.8614 + var styleElement = doc.createElement("style"); 255.8615 + styleElement.type = "text/css"; 255.8616 + 255.8617 + if (styleElement.styleSheet) { 255.8618 + styleElement.styleSheet.cssText = rules; 255.8619 + } else { 255.8620 + styleElement.appendChild(doc.createTextNode(rules)); 255.8621 + } 255.8622 + 255.8623 + var link = doc.querySelector("head link"); 255.8624 + if (link) { 255.8625 + link.parentNode.insertBefore(styleElement, link); 255.8626 + return; 255.8627 + } else { 255.8628 + var head = doc.querySelector("head"); 255.8629 + if (head) { 255.8630 + head.appendChild(styleElement); 255.8631 + } 255.8632 + } 255.8633 + } 255.8634 + }; 255.8635 +}; 255.8636 + 255.8637 +/** 255.8638 + * Check whether the given node is a proper loaded image 255.8639 + * FIXME: Returns undefined when unknown (Chrome, Safari) 255.8640 +*/ 255.8641 + 255.8642 +wysihtml.dom.isLoadedImage = function (node) { 255.8643 + try { 255.8644 + return node.complete && !node.mozMatchesSelector(":-moz-broken"); 255.8645 + } catch(e) { 255.8646 + if (node.complete && node.readyState === "complete") { 255.8647 + return true; 255.8648 + } 255.8649 + } 255.8650 +}; 255.8651 + 255.8652 +// TODO: Refactor dom tree traversing here 255.8653 +(function(wysihtml) { 255.8654 + wysihtml.dom.lineBreaks = function(node) { 255.8655 + 255.8656 + function _isLineBreak(n) { 255.8657 + return n.nodeName === "BR"; 255.8658 + } 255.8659 + 255.8660 + /** 255.8661 + * Checks whether the elment causes a visual line break 255.8662 + * (<br> or block elements) 255.8663 + */ 255.8664 + function _isLineBreakOrBlockElement(element) { 255.8665 + if (_isLineBreak(element)) { 255.8666 + return true; 255.8667 + } 255.8668 + 255.8669 + if (wysihtml.dom.getStyle("display").from(element) === "block") { 255.8670 + return true; 255.8671 + } 255.8672 + 255.8673 + return false; 255.8674 + } 255.8675 + 255.8676 + return { 255.8677 + 255.8678 + /* wysihtml.dom.lineBreaks(element).add(); 255.8679 + * 255.8680 + * Adds line breaks before and after the given node if the previous and next siblings 255.8681 + * aren't already causing a visual line break (block element or <br>) 255.8682 + */ 255.8683 + add: function(options) { 255.8684 + var doc = node.ownerDocument, 255.8685 + nextSibling = wysihtml.dom.domNode(node).next({ignoreBlankTexts: true}), 255.8686 + previousSibling = wysihtml.dom.domNode(node).prev({ignoreBlankTexts: true}); 255.8687 + 255.8688 + if (nextSibling && !_isLineBreakOrBlockElement(nextSibling)) { 255.8689 + wysihtml.dom.insert(doc.createElement("br")).after(node); 255.8690 + } 255.8691 + if (previousSibling && !_isLineBreakOrBlockElement(previousSibling)) { 255.8692 + wysihtml.dom.insert(doc.createElement("br")).before(node); 255.8693 + } 255.8694 + }, 255.8695 + 255.8696 + /* wysihtml.dom.lineBreaks(element).remove(); 255.8697 + * 255.8698 + * Removes line breaks before and after the given node 255.8699 + */ 255.8700 + remove: function(options) { 255.8701 + var nextSibling = wysihtml.dom.domNode(node).next({ignoreBlankTexts: true}), 255.8702 + previousSibling = wysihtml.dom.domNode(node).prev({ignoreBlankTexts: true}); 255.8703 + 255.8704 + if (nextSibling && _isLineBreak(nextSibling)) { 255.8705 + nextSibling.parentNode.removeChild(nextSibling); 255.8706 + } 255.8707 + if (previousSibling && _isLineBreak(previousSibling)) { 255.8708 + previousSibling.parentNode.removeChild(previousSibling); 255.8709 + } 255.8710 + } 255.8711 + }; 255.8712 + }; 255.8713 +})(wysihtml); 255.8714 +/** 255.8715 + * Method to set dom events 255.8716 + * 255.8717 + * @example 255.8718 + * wysihtml.dom.observe(iframe.contentWindow.document.body, ["focus", "blur"], function() { ... }); 255.8719 + */ 255.8720 +wysihtml.dom.observe = function(element, eventNames, handler) { 255.8721 + eventNames = typeof(eventNames) === "string" ? [eventNames] : eventNames; 255.8722 + 255.8723 + var handlerWrapper, 255.8724 + eventName, 255.8725 + i = 0, 255.8726 + length = eventNames.length; 255.8727 + 255.8728 + for (; i<length; i++) { 255.8729 + eventName = eventNames[i]; 255.8730 + if (element.addEventListener) { 255.8731 + element.addEventListener(eventName, handler, false); 255.8732 + } else { 255.8733 + handlerWrapper = function(event) { 255.8734 + if (!("target" in event)) { 255.8735 + event.target = event.srcElement; 255.8736 + } 255.8737 + event.preventDefault = event.preventDefault || function() { 255.8738 + this.returnValue = false; 255.8739 + }; 255.8740 + event.stopPropagation = event.stopPropagation || function() { 255.8741 + this.cancelBubble = true; 255.8742 + }; 255.8743 + handler.call(element, event); 255.8744 + }; 255.8745 + element.attachEvent("on" + eventName, handlerWrapper); 255.8746 + } 255.8747 + } 255.8748 + 255.8749 + return { 255.8750 + stop: function() { 255.8751 + var eventName, 255.8752 + i = 0, 255.8753 + length = eventNames.length; 255.8754 + for (; i<length; i++) { 255.8755 + eventName = eventNames[i]; 255.8756 + if (element.removeEventListener) { 255.8757 + element.removeEventListener(eventName, handler, false); 255.8758 + } else { 255.8759 + element.detachEvent("on" + eventName, handlerWrapper); 255.8760 + } 255.8761 + } 255.8762 + } 255.8763 + }; 255.8764 +}; 255.8765 + 255.8766 +/** 255.8767 + * HTML Sanitizer 255.8768 + * Rewrites the HTML based on given rules 255.8769 + * 255.8770 + * @param {Element|String} elementOrHtml HTML String to be sanitized OR element whose content should be sanitized 255.8771 + * @param {Object} [rules] List of rules for rewriting the HTML, if there's no rule for an element it will 255.8772 + * be converted to a "span". Each rule is a key/value pair where key is the tag to convert, and value the 255.8773 + * desired substitution. 255.8774 + * @param {Object} context Document object in which to parse the html, needed to sandbox the parsing 255.8775 + * 255.8776 + * @return {Element|String} Depends on the elementOrHtml parameter. When html then the sanitized html as string elsewise the element. 255.8777 + * 255.8778 + * @example 255.8779 + * var userHTML = '<div id="foo" onclick="alert(1);"><p><font color="red">foo</font><script>alert(1);</script></p></div>'; 255.8780 + * wysihtml.dom.parse(userHTML, { 255.8781 + * tags { 255.8782 + * p: "div", // Rename p tags to div tags 255.8783 + * font: "span" // Rename font tags to span tags 255.8784 + * div: true, // Keep them, also possible (same result when passing: "div" or true) 255.8785 + * script: undefined // Remove script elements 255.8786 + * } 255.8787 + * }); 255.8788 + * // => <div><div><span>foo bar</span></div></div> 255.8789 + * 255.8790 + * var userHTML = '<table><tbody><tr><td>I'm a table!</td></tr></tbody></table>'; 255.8791 + * wysihtml.dom.parse(userHTML); 255.8792 + * // => '<span><span><span><span>I'm a table!</span></span></span></span>' 255.8793 + * 255.8794 + * var userHTML = '<div>foobar<br>foobar</div>'; 255.8795 + * wysihtml.dom.parse(userHTML, { 255.8796 + * tags: { 255.8797 + * div: undefined, 255.8798 + * br: true 255.8799 + * } 255.8800 + * }); 255.8801 + * // => '' 255.8802 + * 255.8803 + * var userHTML = '<div class="red">foo</div><div class="pink">bar</div>'; 255.8804 + * wysihtml.dom.parse(userHTML, { 255.8805 + * classes: { 255.8806 + * red: 1, 255.8807 + * green: 1 255.8808 + * }, 255.8809 + * tags: { 255.8810 + * div: { 255.8811 + * rename_tag: "p" 255.8812 + * } 255.8813 + * } 255.8814 + * }); 255.8815 + * // => '<p class="red">foo</p><p>bar</p>' 255.8816 + */ 255.8817 + 255.8818 +wysihtml.dom.parse = function(elementOrHtml_current, config_current) { 255.8819 + /* TODO: Currently escaped module pattern as otherwise folloowing default swill be shared among multiple editors. 255.8820 + * Refactor whole code as this method while workind is kind of awkward too */ 255.8821 + 255.8822 + /** 255.8823 + * It's not possible to use a XMLParser/DOMParser as HTML5 is not always well-formed XML 255.8824 + * new DOMParser().parseFromString('<img src="foo.gif">') will cause a parseError since the 255.8825 + * node isn't closed 255.8826 + * 255.8827 + * Therefore we've to use the browser's ordinary HTML parser invoked by setting innerHTML. 255.8828 + */ 255.8829 + var NODE_TYPE_MAPPING = { 255.8830 + "1": _handleElement, 255.8831 + "3": _handleText, 255.8832 + "8": _handleComment 255.8833 + }, 255.8834 + // Rename unknown tags to this 255.8835 + DEFAULT_NODE_NAME = "span", 255.8836 + WHITE_SPACE_REG_EXP = /\s+/, 255.8837 + defaultRules = { tags: {}, classes: {} }, 255.8838 + currentRules = {}, 255.8839 + blockElements = ["ADDRESS" ,"BLOCKQUOTE" ,"CENTER" ,"DIR" ,"DIV" ,"DL" ,"FIELDSET" , 255.8840 + "FORM", "H1" ,"H2" ,"H3" ,"H4" ,"H5" ,"H6" ,"ISINDEX" ,"MENU", 255.8841 + "NOFRAMES", "NOSCRIPT" ,"OL" ,"P" ,"PRE","TABLE", "UL"]; 255.8842 + 255.8843 + /** 255.8844 + * Iterates over all childs of the element, recreates them, appends them into a document fragment 255.8845 + * which later replaces the entire body content 255.8846 + */ 255.8847 + function parse(elementOrHtml, config) { 255.8848 + wysihtml.lang.object(currentRules).merge(defaultRules).merge(config.rules).get(); 255.8849 + 255.8850 + var context = config.context || elementOrHtml.ownerDocument || document, 255.8851 + fragment = context.createDocumentFragment(), 255.8852 + isString = typeof(elementOrHtml) === "string", 255.8853 + clearInternals = false, 255.8854 + element, 255.8855 + newNode, 255.8856 + firstChild; 255.8857 + 255.8858 + if (config.clearInternals === true) { 255.8859 + clearInternals = true; 255.8860 + } 255.8861 + 255.8862 + if (isString) { 255.8863 + element = wysihtml.dom.getAsDom(elementOrHtml, context); 255.8864 + } else { 255.8865 + element = elementOrHtml; 255.8866 + } 255.8867 + 255.8868 + if (currentRules.selectors) { 255.8869 + _applySelectorRules(element, currentRules.selectors); 255.8870 + } 255.8871 + 255.8872 + while (element.firstChild) { 255.8873 + firstChild = element.firstChild; 255.8874 + newNode = _convert(firstChild, config.cleanUp, clearInternals, config.uneditableClass); 255.8875 + if (newNode) { 255.8876 + fragment.appendChild(newNode); 255.8877 + } 255.8878 + if (firstChild !== newNode) { 255.8879 + element.removeChild(firstChild); 255.8880 + } 255.8881 + } 255.8882 + 255.8883 + if (config.unjoinNbsps) { 255.8884 + // replace joined non-breakable spaces with unjoined 255.8885 + var txtnodes = wysihtml.dom.getTextNodes(fragment); 255.8886 + for (var n = txtnodes.length; n--;) { 255.8887 + txtnodes[n].nodeValue = txtnodes[n].nodeValue.replace(/([\S\u00A0])\u00A0/gi, "$1 "); 255.8888 + } 255.8889 + } 255.8890 + 255.8891 + // Clear element contents 255.8892 + element.innerHTML = ""; 255.8893 + 255.8894 + // Insert new DOM tree 255.8895 + element.appendChild(fragment); 255.8896 + 255.8897 + return isString ? wysihtml.quirks.getCorrectInnerHTML(element) : element; 255.8898 + } 255.8899 + 255.8900 + function _convert(oldNode, cleanUp, clearInternals, uneditableClass) { 255.8901 + var oldNodeType = oldNode.nodeType, 255.8902 + oldChilds = oldNode.childNodes, 255.8903 + oldChildsLength = oldChilds.length, 255.8904 + method = NODE_TYPE_MAPPING[oldNodeType], 255.8905 + i = 0, 255.8906 + fragment, 255.8907 + newNode, 255.8908 + newChild, 255.8909 + nodeDisplay; 255.8910 + 255.8911 + // Passes directly elemets with uneditable class 255.8912 + if (uneditableClass && oldNodeType === 1 && wysihtml.dom.hasClass(oldNode, uneditableClass)) { 255.8913 + return oldNode; 255.8914 + } 255.8915 + 255.8916 + newNode = method && method(oldNode, clearInternals); 255.8917 + 255.8918 + // Remove or unwrap node in case of return value null or false 255.8919 + if (!newNode) { 255.8920 + if (newNode === false) { 255.8921 + // false defines that tag should be removed but contents should remain (unwrap) 255.8922 + fragment = oldNode.ownerDocument.createDocumentFragment(); 255.8923 + 255.8924 + for (i = oldChildsLength; i--;) { 255.8925 + if (oldChilds[i]) { 255.8926 + newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass); 255.8927 + if (newChild) { 255.8928 + if (oldChilds[i] === newChild) { 255.8929 + i--; 255.8930 + } 255.8931 + fragment.insertBefore(newChild, fragment.firstChild); 255.8932 + } 255.8933 + } 255.8934 + } 255.8935 + 255.8936 + nodeDisplay = wysihtml.dom.getStyle("display").from(oldNode); 255.8937 + 255.8938 + if (nodeDisplay === '') { 255.8939 + // Handle display style when element not in dom 255.8940 + nodeDisplay = wysihtml.lang.array(blockElements).contains(oldNode.tagName) ? "block" : ""; 255.8941 + } 255.8942 + if (wysihtml.lang.array(["block", "flex", "table"]).contains(nodeDisplay)) { 255.8943 + fragment.appendChild(oldNode.ownerDocument.createElement("br")); 255.8944 + } 255.8945 + 255.8946 + // TODO: try to minimize surplus spaces 255.8947 + if (wysihtml.lang.array([ 255.8948 + "div", "pre", "p", 255.8949 + "table", "td", "th", 255.8950 + "ul", "ol", "li", 255.8951 + "dd", "dl", 255.8952 + "footer", "header", "section", 255.8953 + "h1", "h2", "h3", "h4", "h5", "h6" 255.8954 + ]).contains(oldNode.nodeName.toLowerCase()) && oldNode.parentNode.lastChild !== oldNode) { 255.8955 + // add space at first when unwraping non-textflow elements 255.8956 + if (!oldNode.nextSibling || oldNode.nextSibling.nodeType !== 3 || !(/^\s/).test(oldNode.nextSibling.nodeValue)) { 255.8957 + fragment.appendChild(oldNode.ownerDocument.createTextNode(" ")); 255.8958 + } 255.8959 + } 255.8960 + 255.8961 + if (fragment.normalize) { 255.8962 + fragment.normalize(); 255.8963 + } 255.8964 + return fragment; 255.8965 + } else { 255.8966 + // Remove 255.8967 + return null; 255.8968 + } 255.8969 + } 255.8970 + 255.8971 + // Converts all childnodes 255.8972 + for (i=0; i<oldChildsLength; i++) { 255.8973 + if (oldChilds[i]) { 255.8974 + newChild = _convert(oldChilds[i], cleanUp, clearInternals, uneditableClass); 255.8975 + if (newChild) { 255.8976 + if (oldChilds[i] === newChild) { 255.8977 + i--; 255.8978 + } 255.8979 + newNode.appendChild(newChild); 255.8980 + } 255.8981 + } 255.8982 + } 255.8983 + 255.8984 + // Cleanup senseless <span> elements 255.8985 + if (cleanUp && 255.8986 + newNode.nodeName.toLowerCase() === DEFAULT_NODE_NAME && 255.8987 + (!newNode.childNodes.length || 255.8988 + ((/^\s*$/gi).test(newNode.innerHTML) && (clearInternals || (oldNode.className !== "_wysihtml-temp-placeholder" && oldNode.className !== "rangySelectionBoundary"))) || 255.8989 + !newNode.attributes.length) 255.8990 + ) { 255.8991 + fragment = newNode.ownerDocument.createDocumentFragment(); 255.8992 + while (newNode.firstChild) { 255.8993 + fragment.appendChild(newNode.firstChild); 255.8994 + } 255.8995 + if (fragment.normalize) { 255.8996 + fragment.normalize(); 255.8997 + } 255.8998 + return fragment; 255.8999 + } 255.9000 + 255.9001 + if (newNode.normalize) { 255.9002 + newNode.normalize(); 255.9003 + } 255.9004 + return newNode; 255.9005 + } 255.9006 + 255.9007 + function _applySelectorRules (element, selectorRules) { 255.9008 + var sel, method, els; 255.9009 + 255.9010 + for (sel in selectorRules) { 255.9011 + if (selectorRules.hasOwnProperty(sel)) { 255.9012 + if (wysihtml.lang.object(selectorRules[sel]).isFunction()) { 255.9013 + method = selectorRules[sel]; 255.9014 + } else if (typeof(selectorRules[sel]) === "string" && elementHandlingMethods[selectorRules[sel]]) { 255.9015 + method = elementHandlingMethods[selectorRules[sel]]; 255.9016 + } 255.9017 + els = element.querySelectorAll(sel); 255.9018 + for (var i = els.length; i--;) { 255.9019 + method(els[i]); 255.9020 + } 255.9021 + } 255.9022 + } 255.9023 + } 255.9024 + 255.9025 + function _handleElement(oldNode, clearInternals) { 255.9026 + var rule, 255.9027 + newNode, 255.9028 + tagRules = currentRules.tags, 255.9029 + nodeName = oldNode.nodeName.toLowerCase(), 255.9030 + scopeName = oldNode.scopeName, 255.9031 + renameTag; 255.9032 + 255.9033 + /** 255.9034 + * We already parsed that element 255.9035 + * ignore it! (yes, this sometimes happens in IE8 when the html is invalid) 255.9036 + */ 255.9037 + if (oldNode._wysihtml) { 255.9038 + return null; 255.9039 + } 255.9040 + oldNode._wysihtml = 1; 255.9041 + 255.9042 + if (oldNode.className === "wysihtml-temp") { 255.9043 + return null; 255.9044 + } 255.9045 + 255.9046 + /** 255.9047 + * IE is the only browser who doesn't include the namespace in the 255.9048 + * nodeName, that's why we have to prepend it by ourselves 255.9049 + * scopeName is a proprietary IE feature 255.9050 + * read more here http://msdn.microsoft.com/en-us/library/ms534388(v=vs.85).aspx 255.9051 + */ 255.9052 + if (scopeName && scopeName != "HTML") { 255.9053 + nodeName = scopeName + ":" + nodeName; 255.9054 + } 255.9055 + /** 255.9056 + * Repair node 255.9057 + * IE is a bit bitchy when it comes to invalid nested markup which includes unclosed tags 255.9058 + * A <p> doesn't need to be closed according HTML4-5 spec, we simply replace it with a <div> to preserve its content and layout 255.9059 + */ 255.9060 + if ("outerHTML" in oldNode) { 255.9061 + if (!wysihtml.browser.autoClosesUnclosedTags() && 255.9062 + oldNode.nodeName === "P" && 255.9063 + oldNode.outerHTML.slice(-4).toLowerCase() !== "</p>") { 255.9064 + nodeName = "div"; 255.9065 + } 255.9066 + } 255.9067 + 255.9068 + if (nodeName in tagRules) { 255.9069 + rule = tagRules[nodeName]; 255.9070 + if (!rule || rule.remove) { 255.9071 + return null; 255.9072 + } else if (rule.unwrap) { 255.9073 + return false; 255.9074 + } 255.9075 + rule = typeof(rule) === "string" ? { rename_tag: rule } : rule; 255.9076 + } else if (oldNode.firstChild) { 255.9077 + rule = { rename_tag: DEFAULT_NODE_NAME }; 255.9078 + } else { 255.9079 + // Remove empty unknown elements 255.9080 + return null; 255.9081 + } 255.9082 + 255.9083 + // tests if type condition is met or node should be removed/unwrapped/renamed 255.9084 + if (rule.one_of_type && !_testTypes(oldNode, currentRules, rule.one_of_type, clearInternals)) { 255.9085 + if (rule.remove_action) { 255.9086 + if (rule.remove_action === "unwrap") { 255.9087 + return false; 255.9088 + } else if (rule.remove_action === "rename") { 255.9089 + renameTag = rule.remove_action_rename_to || DEFAULT_NODE_NAME; 255.9090 + } else { 255.9091 + return null; 255.9092 + } 255.9093 + } else { 255.9094 + return null; 255.9095 + } 255.9096 + } 255.9097 + 255.9098 + newNode = oldNode.ownerDocument.createElement(renameTag || rule.rename_tag || nodeName); 255.9099 + _handleAttributes(oldNode, newNode, rule, clearInternals); 255.9100 + _handleStyles(oldNode, newNode, rule); 255.9101 + 255.9102 + oldNode = null; 255.9103 + 255.9104 + if (newNode.normalize) { newNode.normalize(); } 255.9105 + return newNode; 255.9106 + } 255.9107 + 255.9108 + function _testTypes(oldNode, rules, types, clearInternals) { 255.9109 + var definition, type; 255.9110 + 255.9111 + // do not interfere with placeholder span or pasting caret position is not maintained 255.9112 + if (oldNode.nodeName === "SPAN" && !clearInternals && (oldNode.className === "_wysihtml-temp-placeholder" || oldNode.className === "rangySelectionBoundary")) { 255.9113 + return true; 255.9114 + } 255.9115 + 255.9116 + for (type in types) { 255.9117 + if (types.hasOwnProperty(type) && rules.type_definitions && rules.type_definitions[type]) { 255.9118 + definition = rules.type_definitions[type]; 255.9119 + if (_testType(oldNode, definition)) { 255.9120 + return true; 255.9121 + } 255.9122 + } 255.9123 + } 255.9124 + return false; 255.9125 + } 255.9126 + 255.9127 + function array_contains(a, obj) { 255.9128 + var i = a.length; 255.9129 + while (i--) { 255.9130 + if (a[i] === obj) { 255.9131 + return true; 255.9132 + } 255.9133 + } 255.9134 + return false; 255.9135 + } 255.9136 + 255.9137 + function _testType(oldNode, definition) { 255.9138 + 255.9139 + var nodeClasses = oldNode.getAttribute("class"), 255.9140 + nodeStyles = oldNode.getAttribute("style"), 255.9141 + classesLength, s, s_corrected, a, attr, currentClass, styleProp; 255.9142 + 255.9143 + // test for methods 255.9144 + if (definition.methods) { 255.9145 + for (var m in definition.methods) { 255.9146 + if (definition.methods.hasOwnProperty(m) && typeCeckMethods[m]) { 255.9147 + 255.9148 + if (typeCeckMethods[m](oldNode)) { 255.9149 + return true; 255.9150 + } 255.9151 + } 255.9152 + } 255.9153 + } 255.9154 + 255.9155 + // test for classes, if one found return true 255.9156 + if (nodeClasses && definition.classes) { 255.9157 + nodeClasses = nodeClasses.replace(/^\s+/g, '').replace(/\s+$/g, '').split(WHITE_SPACE_REG_EXP); 255.9158 + classesLength = nodeClasses.length; 255.9159 + for (var i = 0; i < classesLength; i++) { 255.9160 + if (definition.classes[nodeClasses[i]]) { 255.9161 + return true; 255.9162 + } 255.9163 + } 255.9164 + } 255.9165 + 255.9166 + // test for styles, if one found return true 255.9167 + if (nodeStyles && definition.styles) { 255.9168 + 255.9169 + nodeStyles = nodeStyles.split(';'); 255.9170 + for (s in definition.styles) { 255.9171 + if (definition.styles.hasOwnProperty(s)) { 255.9172 + for (var sp = nodeStyles.length; sp--;) { 255.9173 + styleProp = nodeStyles[sp].split(':'); 255.9174 + 255.9175 + if (styleProp[0].replace(/\s/g, '').toLowerCase() === s) { 255.9176 + if (definition.styles[s] === true || definition.styles[s] === 1 || wysihtml.lang.array(definition.styles[s]).contains(styleProp[1].replace(/\s/g, '').toLowerCase()) ) { 255.9177 + return true; 255.9178 + } 255.9179 + } 255.9180 + } 255.9181 + } 255.9182 + } 255.9183 + } 255.9184 + 255.9185 + // test for attributes in general against regex match 255.9186 + if (definition.attrs) { 255.9187 + for (a in definition.attrs) { 255.9188 + if (definition.attrs.hasOwnProperty(a)) { 255.9189 + attr = wysihtml.dom.getAttribute(oldNode, a); 255.9190 + if (typeof(attr) === "string") { 255.9191 + if (attr.search(definition.attrs[a]) > -1) { 255.9192 + return true; 255.9193 + } 255.9194 + } 255.9195 + } 255.9196 + } 255.9197 + } 255.9198 + return false; 255.9199 + } 255.9200 + 255.9201 + function _handleStyles(oldNode, newNode, rule) { 255.9202 + var s, v; 255.9203 + if(rule && rule.keep_styles) { 255.9204 + for (s in rule.keep_styles) { 255.9205 + if (rule.keep_styles.hasOwnProperty(s)) { 255.9206 + v = (s === "float") ? oldNode.style.styleFloat || oldNode.style.cssFloat : oldNode.style[s]; 255.9207 + // value can be regex and if so should match or style skipped 255.9208 + if (rule.keep_styles[s] instanceof RegExp && !(rule.keep_styles[s].test(v))) { 255.9209 + continue; 255.9210 + } 255.9211 + if (s === "float") { 255.9212 + // IE compability 255.9213 + newNode.style[(oldNode.style.styleFloat) ? 'styleFloat': 'cssFloat'] = v; 255.9214 + } else if (oldNode.style[s]) { 255.9215 + newNode.style[s] = v; 255.9216 + } 255.9217 + } 255.9218 + } 255.9219 + } 255.9220 + }; 255.9221 + 255.9222 + function _getAttributesBeginningWith(beginning, attributes) { 255.9223 + var returnAttributes = []; 255.9224 + for (var attr in attributes) { 255.9225 + if (attributes.hasOwnProperty(attr) && attr.indexOf(beginning) === 0) { 255.9226 + returnAttributes.push(attr); 255.9227 + } 255.9228 + } 255.9229 + return returnAttributes; 255.9230 + } 255.9231 + 255.9232 + function _checkAttribute(attributeName, attributeValue, methodName, nodeName) { 255.9233 + var method = wysihtml.lang.object(methodName).isFunction() ? methodName : attributeCheckMethods[methodName], 255.9234 + newAttributeValue; 255.9235 + 255.9236 + if (method) { 255.9237 + newAttributeValue = method(attributeValue, nodeName); 255.9238 + if (typeof(newAttributeValue) === "string") { 255.9239 + return newAttributeValue; 255.9240 + } 255.9241 + } 255.9242 + 255.9243 + return false; 255.9244 + } 255.9245 + 255.9246 + function _checkAttributes(oldNode, local_attributes) { 255.9247 + var globalAttributes = wysihtml.lang.object(currentRules.attributes || {}).clone(), // global values for check/convert values of attributes 255.9248 + checkAttributes = wysihtml.lang.object(globalAttributes).merge( wysihtml.lang.object(local_attributes || {}).clone()).get(), 255.9249 + attributes = {}, 255.9250 + oldAttributes = wysihtml.dom.getAttributes(oldNode), 255.9251 + attributeName, newValue, matchingAttributes; 255.9252 + 255.9253 + for (attributeName in checkAttributes) { 255.9254 + if ((/\*$/).test(attributeName)) { 255.9255 + 255.9256 + matchingAttributes = _getAttributesBeginningWith(attributeName.slice(0,-1), oldAttributes); 255.9257 + for (var i = 0, imax = matchingAttributes.length; i < imax; i++) { 255.9258 + 255.9259 + newValue = _checkAttribute(matchingAttributes[i], oldAttributes[matchingAttributes[i]], checkAttributes[attributeName], oldNode.nodeName); 255.9260 + if (newValue !== false) { 255.9261 + attributes[matchingAttributes[i]] = newValue; 255.9262 + } 255.9263 + } 255.9264 + } else { 255.9265 + newValue = _checkAttribute(attributeName, oldAttributes[attributeName], checkAttributes[attributeName], oldNode.nodeName); 255.9266 + if (newValue !== false) { 255.9267 + attributes[attributeName] = newValue; 255.9268 + } 255.9269 + } 255.9270 + } 255.9271 + 255.9272 + return attributes; 255.9273 + } 255.9274 + 255.9275 + // TODO: refactor. Too long to read 255.9276 + function _handleAttributes(oldNode, newNode, rule, clearInternals) { 255.9277 + var attributes = {}, // fresh new set of attributes to set on newNode 255.9278 + setClass = rule.set_class, // classes to set 255.9279 + addClass = rule.add_class, // add classes based on existing attributes 255.9280 + addStyle = rule.add_style, // add styles based on existing attributes 255.9281 + setAttributes = rule.set_attributes, // attributes to set on the current node 255.9282 + allowedClasses = currentRules.classes, 255.9283 + i = 0, 255.9284 + classes = [], 255.9285 + styles = [], 255.9286 + newClasses = [], 255.9287 + oldClasses = [], 255.9288 + classesLength, 255.9289 + newClassesLength, 255.9290 + currentClass, 255.9291 + newClass, 255.9292 + attributeName, 255.9293 + method; 255.9294 + 255.9295 + if (setAttributes) { 255.9296 + attributes = wysihtml.lang.object(setAttributes).clone(); 255.9297 + } 255.9298 + 255.9299 + // check/convert values of attributes 255.9300 + attributes = wysihtml.lang.object(attributes).merge(_checkAttributes(oldNode, rule.check_attributes)).get(); 255.9301 + 255.9302 + if (setClass) { 255.9303 + classes.push(setClass); 255.9304 + } 255.9305 + 255.9306 + if (addClass) { 255.9307 + for (attributeName in addClass) { 255.9308 + method = addClassMethods[addClass[attributeName]]; 255.9309 + if (!method) { 255.9310 + continue; 255.9311 + } 255.9312 + newClass = method(wysihtml.dom.getAttribute(oldNode, attributeName)); 255.9313 + if (typeof(newClass) === "string") { 255.9314 + classes.push(newClass); 255.9315 + } 255.9316 + } 255.9317 + } 255.9318 + 255.9319 + if (addStyle) { 255.9320 + for (attributeName in addStyle) { 255.9321 + method = addStyleMethods[addStyle[attributeName]]; 255.9322 + if (!method) { 255.9323 + continue; 255.9324 + } 255.9325 + 255.9326 + newStyle = method(wysihtml.dom.getAttribute(oldNode, attributeName)); 255.9327 + if (typeof(newStyle) === "string") { 255.9328 + styles.push(newStyle); 255.9329 + } 255.9330 + } 255.9331 + } 255.9332 + 255.9333 + 255.9334 + if (typeof(allowedClasses) === "string" && allowedClasses === "any") { 255.9335 + if (oldNode.getAttribute("class")) { 255.9336 + if (currentRules.classes_blacklist) { 255.9337 + oldClasses = oldNode.getAttribute("class"); 255.9338 + if (oldClasses) { 255.9339 + classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP)); 255.9340 + } 255.9341 + 255.9342 + classesLength = classes.length; 255.9343 + for (; i<classesLength; i++) { 255.9344 + currentClass = classes[i]; 255.9345 + if (!currentRules.classes_blacklist[currentClass]) { 255.9346 + newClasses.push(currentClass); 255.9347 + } 255.9348 + } 255.9349 + 255.9350 + if (newClasses.length) { 255.9351 + attributes["class"] = wysihtml.lang.array(newClasses).unique().join(" "); 255.9352 + } 255.9353 + 255.9354 + } else { 255.9355 + attributes["class"] = oldNode.getAttribute("class"); 255.9356 + } 255.9357 + } else { 255.9358 + if(classes && classes.length > 0) { 255.9359 + attributes["class"] = wysihtml.lang.array(classes).unique().join(" "); 255.9360 + } 255.9361 + } 255.9362 + } else { 255.9363 + // make sure that wysihtml temp class doesn't get stripped out 255.9364 + if (!clearInternals) { 255.9365 + allowedClasses["_wysihtml-temp-placeholder"] = 1; 255.9366 + allowedClasses["_rangySelectionBoundary"] = 1; 255.9367 + allowedClasses["wysiwyg-tmp-selected-cell"] = 1; 255.9368 + } 255.9369 + 255.9370 + // add old classes last 255.9371 + oldClasses = oldNode.getAttribute("class"); 255.9372 + if (oldClasses) { 255.9373 + classes = classes.concat(oldClasses.split(WHITE_SPACE_REG_EXP)); 255.9374 + } 255.9375 + classesLength = classes.length; 255.9376 + for (; i<classesLength; i++) { 255.9377 + currentClass = classes[i]; 255.9378 + if (allowedClasses[currentClass]) { 255.9379 + newClasses.push(currentClass); 255.9380 + } 255.9381 + } 255.9382 + 255.9383 + if (newClasses.length) { 255.9384 + attributes["class"] = wysihtml.lang.array(newClasses).unique().join(" "); 255.9385 + } 255.9386 + } 255.9387 + 255.9388 + // remove table selection class if present 255.9389 + if (attributes["class"] && clearInternals) { 255.9390 + attributes["class"] = attributes["class"].replace("wysiwyg-tmp-selected-cell", ""); 255.9391 + if ((/^\s*$/g).test(attributes["class"])) { 255.9392 + delete attributes["class"]; 255.9393 + } 255.9394 + } 255.9395 + 255.9396 + if (styles.length) { 255.9397 + attributes["style"] = wysihtml.lang.array(styles).unique().join(" "); 255.9398 + } 255.9399 + 255.9400 + // set attributes on newNode 255.9401 + for (attributeName in attributes) { 255.9402 + // Setting attributes can cause a js error in IE under certain circumstances 255.9403 + // eg. on a <img> under https when it's new attribute value is non-https 255.9404 + // TODO: Investigate this further and check for smarter handling 255.9405 + try { 255.9406 + newNode.setAttribute(attributeName, attributes[attributeName]); 255.9407 + } catch(e) {} 255.9408 + } 255.9409 + 255.9410 + // IE8 sometimes loses the width/height attributes when those are set before the "src" 255.9411 + // so we make sure to set them again 255.9412 + if (attributes.src) { 255.9413 + if (typeof(attributes.width) !== "undefined") { 255.9414 + newNode.setAttribute("width", attributes.width); 255.9415 + } 255.9416 + if (typeof(attributes.height) !== "undefined") { 255.9417 + newNode.setAttribute("height", attributes.height); 255.9418 + } 255.9419 + } 255.9420 + } 255.9421 + 255.9422 + function _handleText(oldNode) { 255.9423 + var nextSibling = oldNode.nextSibling; 255.9424 + if (nextSibling && nextSibling.nodeType === wysihtml.TEXT_NODE) { 255.9425 + // Concatenate text nodes 255.9426 + nextSibling.data = oldNode.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, "") + nextSibling.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ""); 255.9427 + } else { 255.9428 + // \uFEFF = wysihtml.INVISIBLE_SPACE (used as a hack in certain rich text editing situations) 255.9429 + var data = oldNode.data.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ""); 255.9430 + return oldNode.ownerDocument.createTextNode(data); 255.9431 + } 255.9432 + } 255.9433 + 255.9434 + function _handleComment(oldNode) { 255.9435 + if (currentRules.comments) { 255.9436 + return oldNode.ownerDocument.createComment(oldNode.nodeValue); 255.9437 + } 255.9438 + } 255.9439 + 255.9440 + // ------------ attribute checks ------------ \\ 255.9441 + var attributeCheckMethods = { 255.9442 + url: (function() { 255.9443 + var REG_EXP = /^https?:\/\//i; 255.9444 + return function(attributeValue) { 255.9445 + if (!attributeValue || !attributeValue.match(REG_EXP)) { 255.9446 + return null; 255.9447 + } 255.9448 + return attributeValue.replace(REG_EXP, function(match) { 255.9449 + return match.toLowerCase(); 255.9450 + }); 255.9451 + }; 255.9452 + })(), 255.9453 + 255.9454 + src: (function() { 255.9455 + var REG_EXP = /^(\/|https?:\/\/)/i; 255.9456 + return function(attributeValue) { 255.9457 + if (!attributeValue || !attributeValue.match(REG_EXP)) { 255.9458 + return null; 255.9459 + } 255.9460 + return attributeValue.replace(REG_EXP, function(match) { 255.9461 + return match.toLowerCase(); 255.9462 + }); 255.9463 + }; 255.9464 + })(), 255.9465 + 255.9466 + href: (function() { 255.9467 + var REG_EXP = /^(#|\/|https?:\/\/|mailto:|tel:)/i; 255.9468 + return function(attributeValue) { 255.9469 + if (!attributeValue || !attributeValue.match(REG_EXP)) { 255.9470 + return null; 255.9471 + } 255.9472 + return attributeValue.replace(REG_EXP, function(match) { 255.9473 + return match.toLowerCase(); 255.9474 + }); 255.9475 + }; 255.9476 + })(), 255.9477 + 255.9478 + alt: (function() { 255.9479 + var REG_EXP = /[^ a-z0-9_\-]/gi; 255.9480 + return function(attributeValue, nodeName) { 255.9481 + if (!attributeValue) { 255.9482 + if (nodeName === "IMG") { 255.9483 + return ""; 255.9484 + } else { 255.9485 + return null; 255.9486 + } 255.9487 + } 255.9488 + return attributeValue.replace(REG_EXP, ""); 255.9489 + }; 255.9490 + })(), 255.9491 + 255.9492 + // Integers. Does not work with floating point numbers and units 255.9493 + numbers: (function() { 255.9494 + var REG_EXP = /\D/g; 255.9495 + return function(attributeValue) { 255.9496 + attributeValue = (attributeValue || "").replace(REG_EXP, ""); 255.9497 + return attributeValue || null; 255.9498 + }; 255.9499 + })(), 255.9500 + 255.9501 + // Useful for with/height attributes where floating points and percentages are allowed 255.9502 + dimension: (function() { 255.9503 + var REG_EXP = /\D*(\d+)(\.\d+)?\s?(%)?\D*/; 255.9504 + return function(attributeValue) { 255.9505 + attributeValue = (attributeValue || "").replace(REG_EXP, "$1$2$3"); 255.9506 + return attributeValue || null; 255.9507 + }; 255.9508 + })(), 255.9509 + 255.9510 + any: (function() { 255.9511 + return function(attributeValue) { 255.9512 + if (!attributeValue) { 255.9513 + return null; 255.9514 + } 255.9515 + return attributeValue; 255.9516 + }; 255.9517 + })() 255.9518 + }; 255.9519 + 255.9520 + // ------------ style converter (converts an html attribute to a style) ------------ \\ 255.9521 + var addStyleMethods = { 255.9522 + align_text: (function() { 255.9523 + var mapping = { 255.9524 + left: "text-align: left;", 255.9525 + right: "text-align: right;", 255.9526 + center: "text-align: center;" 255.9527 + }; 255.9528 + return function(attributeValue) { 255.9529 + return mapping[String(attributeValue).toLowerCase()]; 255.9530 + }; 255.9531 + })(), 255.9532 + }; 255.9533 + 255.9534 + // ------------ class converter (converts an html attribute to a class name) ------------ \\ 255.9535 + var addClassMethods = { 255.9536 + align_img: (function() { 255.9537 + var mapping = { 255.9538 + left: "wysiwyg-float-left", 255.9539 + right: "wysiwyg-float-right" 255.9540 + }; 255.9541 + return function(attributeValue) { 255.9542 + return mapping[String(attributeValue).toLowerCase()]; 255.9543 + }; 255.9544 + })(), 255.9545 + 255.9546 + align_text: (function() { 255.9547 + var mapping = { 255.9548 + left: "wysiwyg-text-align-left", 255.9549 + right: "wysiwyg-text-align-right", 255.9550 + center: "wysiwyg-text-align-center", 255.9551 + justify: "wysiwyg-text-align-justify" 255.9552 + }; 255.9553 + return function(attributeValue) { 255.9554 + return mapping[String(attributeValue).toLowerCase()]; 255.9555 + }; 255.9556 + })(), 255.9557 + 255.9558 + clear_br: (function() { 255.9559 + var mapping = { 255.9560 + left: "wysiwyg-clear-left", 255.9561 + right: "wysiwyg-clear-right", 255.9562 + both: "wysiwyg-clear-both", 255.9563 + all: "wysiwyg-clear-both" 255.9564 + }; 255.9565 + return function(attributeValue) { 255.9566 + return mapping[String(attributeValue).toLowerCase()]; 255.9567 + }; 255.9568 + })(), 255.9569 + 255.9570 + size_font: (function() { 255.9571 + var mapping = { 255.9572 + "1": "wysiwyg-font-size-xx-small", 255.9573 + "2": "wysiwyg-font-size-small", 255.9574 + "3": "wysiwyg-font-size-medium", 255.9575 + "4": "wysiwyg-font-size-large", 255.9576 + "5": "wysiwyg-font-size-x-large", 255.9577 + "6": "wysiwyg-font-size-xx-large", 255.9578 + "7": "wysiwyg-font-size-xx-large", 255.9579 + "-": "wysiwyg-font-size-smaller", 255.9580 + "+": "wysiwyg-font-size-larger" 255.9581 + }; 255.9582 + return function(attributeValue) { 255.9583 + return mapping[String(attributeValue).charAt(0)]; 255.9584 + }; 255.9585 + })() 255.9586 + }; 255.9587 + 255.9588 + // checks if element is possibly visible 255.9589 + var typeCeckMethods = { 255.9590 + has_visible_contet: (function() { 255.9591 + var txt, 255.9592 + isVisible = false, 255.9593 + visibleElements = ['img', 'video', 'picture', 'br', 'script', 'noscript', 255.9594 + 'style', 'table', 'iframe', 'object', 'embed', 'audio', 255.9595 + 'svg', 'input', 'button', 'select','textarea', 'canvas']; 255.9596 + 255.9597 + return function(el) { 255.9598 + 255.9599 + // has visible innertext. so is visible 255.9600 + txt = (el.innerText || el.textContent).replace(/\s/g, ''); 255.9601 + if (txt && txt.length > 0) { 255.9602 + return true; 255.9603 + } 255.9604 + 255.9605 + // matches list of visible dimensioned elements 255.9606 + for (var i = visibleElements.length; i--;) { 255.9607 + if (el.querySelector(visibleElements[i])) { 255.9608 + return true; 255.9609 + } 255.9610 + } 255.9611 + 255.9612 + // try to measure dimesions in last resort. (can find only of elements in dom) 255.9613 + if (el.offsetWidth && el.offsetWidth > 0 && el.offsetHeight && el.offsetHeight > 0) { 255.9614 + return true; 255.9615 + } 255.9616 + 255.9617 + return false; 255.9618 + }; 255.9619 + })() 255.9620 + }; 255.9621 + 255.9622 + var elementHandlingMethods = { 255.9623 + unwrap: function (element) { 255.9624 + wysihtml.dom.unwrap(element); 255.9625 + }, 255.9626 + 255.9627 + remove: function (element) { 255.9628 + element.parentNode.removeChild(element); 255.9629 + } 255.9630 + }; 255.9631 + 255.9632 + return parse(elementOrHtml_current, config_current); 255.9633 +}; 255.9634 + 255.9635 +// does a selector query on element or array of elements 255.9636 +wysihtml.dom.query = function(elements, query) { 255.9637 + var ret = [], 255.9638 + q; 255.9639 + 255.9640 + if (elements.nodeType) { 255.9641 + elements = [elements]; 255.9642 + } 255.9643 + 255.9644 + for (var e = 0, len = elements.length; e < len; e++) { 255.9645 + q = elements[e].querySelectorAll(query); 255.9646 + if (q) { 255.9647 + for(var i = q.length; i--; ret.unshift(q[i])); 255.9648 + } 255.9649 + } 255.9650 + return ret; 255.9651 +}; 255.9652 + 255.9653 +/** 255.9654 + * Checks for empty text node childs and removes them 255.9655 + * 255.9656 + * @param {Element} node The element in which to cleanup 255.9657 + * @example 255.9658 + * wysihtml.dom.removeEmptyTextNodes(element); 255.9659 + */ 255.9660 +wysihtml.dom.removeEmptyTextNodes = function(node) { 255.9661 + var childNode, 255.9662 + childNodes = wysihtml.lang.array(node.childNodes).get(), 255.9663 + childNodesLength = childNodes.length, 255.9664 + i = 0; 255.9665 + 255.9666 + for (; i<childNodesLength; i++) { 255.9667 + childNode = childNodes[i]; 255.9668 + if (childNode.nodeType === wysihtml.TEXT_NODE && (/^[\n\r]*$/).test(childNode.data)) { 255.9669 + childNode.parentNode.removeChild(childNode); 255.9670 + } 255.9671 + } 255.9672 +}; 255.9673 + 255.9674 +wysihtml.dom.removeInvisibleSpaces = function(node) { 255.9675 + var textNodes = wysihtml.dom.getTextNodes(node); 255.9676 + for (var n = textNodes.length; n--;) { 255.9677 + textNodes[n].nodeValue = textNodes[n].nodeValue.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ""); 255.9678 + } 255.9679 +}; 255.9680 + 255.9681 +/** 255.9682 + * Renames an element (eg. a <div> to a <p>) and keeps its childs 255.9683 + * 255.9684 + * @param {Element} element The list element which should be renamed 255.9685 + * @param {Element} newNodeName The desired tag name 255.9686 + * 255.9687 + * @example 255.9688 + * <!-- Assume the following dom: --> 255.9689 + * <ul id="list"> 255.9690 + * <li>eminem</li> 255.9691 + * <li>dr. dre</li> 255.9692 + * <li>50 Cent</li> 255.9693 + * </ul> 255.9694 + * 255.9695 + * <script> 255.9696 + * wysihtml.dom.renameElement(document.getElementById("list"), "ol"); 255.9697 + * </script> 255.9698 + * 255.9699 + * <!-- Will result in: --> 255.9700 + * <ol> 255.9701 + * <li>eminem</li> 255.9702 + * <li>dr. dre</li> 255.9703 + * <li>50 Cent</li> 255.9704 + * </ol> 255.9705 + */ 255.9706 +wysihtml.dom.renameElement = function(element, newNodeName) { 255.9707 + var newElement = element.ownerDocument.createElement(newNodeName), 255.9708 + firstChild; 255.9709 + while (firstChild = element.firstChild) { 255.9710 + newElement.appendChild(firstChild); 255.9711 + } 255.9712 + wysihtml.dom.copyAttributes(["align", "className"]).from(element).to(newElement); 255.9713 + 255.9714 + if (element.parentNode) { 255.9715 + element.parentNode.replaceChild(newElement, element); 255.9716 + } 255.9717 + 255.9718 + return newElement; 255.9719 +}; 255.9720 + 255.9721 +/** 255.9722 + * Takes an element, removes it and replaces it with it's childs 255.9723 + * 255.9724 + * @param {Object} node The node which to replace with it's child nodes 255.9725 + * @example 255.9726 + * <div id="foo"> 255.9727 + * <span>hello</span> 255.9728 + * </div> 255.9729 + * <script> 255.9730 + * // Remove #foo and replace with it's children 255.9731 + * wysihtml.dom.replaceWithChildNodes(document.getElementById("foo")); 255.9732 + * </script> 255.9733 + */ 255.9734 +wysihtml.dom.replaceWithChildNodes = function(node) { 255.9735 + if (!node.parentNode) { 255.9736 + return; 255.9737 + } 255.9738 + 255.9739 + while (node.firstChild) { 255.9740 + node.parentNode.insertBefore(node.firstChild, node); 255.9741 + } 255.9742 + node.parentNode.removeChild(node); 255.9743 +}; 255.9744 + 255.9745 +/** 255.9746 + * Unwraps an unordered/ordered list 255.9747 + * 255.9748 + * @param {Element} element The list element which should be unwrapped 255.9749 + * 255.9750 + * @example 255.9751 + * <!-- Assume the following dom: --> 255.9752 + * <ul id="list"> 255.9753 + * <li>eminem</li> 255.9754 + * <li>dr. dre</li> 255.9755 + * <li>50 Cent</li> 255.9756 + * </ul> 255.9757 + * 255.9758 + * <script> 255.9759 + * wysihtml.dom.resolveList(document.getElementById("list")); 255.9760 + * </script> 255.9761 + * 255.9762 + * <!-- Will result in: --> 255.9763 + * eminem<br> 255.9764 + * dr. dre<br> 255.9765 + * 50 Cent<br> 255.9766 + */ 255.9767 +(function(dom) { 255.9768 + function _isBlockElement(node) { 255.9769 + return dom.getStyle("display").from(node) === "block"; 255.9770 + } 255.9771 + 255.9772 + function _isLineBreak(node) { 255.9773 + return node.nodeName === "BR"; 255.9774 + } 255.9775 + 255.9776 + function _appendLineBreak(element) { 255.9777 + var lineBreak = element.ownerDocument.createElement("br"); 255.9778 + element.appendChild(lineBreak); 255.9779 + } 255.9780 + 255.9781 + function resolveList(list, useLineBreaks) { 255.9782 + if (!list.nodeName.match(/^(MENU|UL|OL)$/)) { 255.9783 + return; 255.9784 + } 255.9785 + 255.9786 + var doc = list.ownerDocument, 255.9787 + fragment = doc.createDocumentFragment(), 255.9788 + previousSibling = wysihtml.dom.domNode(list).prev({ignoreBlankTexts: true}), 255.9789 + nextSibling = wysihtml.dom.domNode(list).next({ignoreBlankTexts: true}), 255.9790 + firstChild, 255.9791 + lastChild, 255.9792 + isLastChild, 255.9793 + shouldAppendLineBreak, 255.9794 + paragraph, 255.9795 + listItem, 255.9796 + lastListItem = list.lastElementChild || list.lastChild, 255.9797 + isLastItem; 255.9798 + 255.9799 + if (useLineBreaks) { 255.9800 + // Insert line break if list is after a non-block element 255.9801 + if (previousSibling && !_isBlockElement(previousSibling) && !_isLineBreak(previousSibling)) { 255.9802 + _appendLineBreak(fragment); 255.9803 + } 255.9804 + 255.9805 + while (listItem = (list.firstElementChild || list.firstChild)) { 255.9806 + lastChild = listItem.lastChild; 255.9807 + isLastItem = listItem === lastListItem; 255.9808 + while (firstChild = listItem.firstChild) { 255.9809 + isLastChild = firstChild === lastChild; 255.9810 + // This needs to be done before appending it to the fragment, as it otherwise will lose style information 255.9811 + shouldAppendLineBreak = (!isLastItem || (nextSibling && !_isBlockElement(nextSibling))) && isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild); 255.9812 + fragment.appendChild(firstChild); 255.9813 + if (shouldAppendLineBreak) { 255.9814 + _appendLineBreak(fragment); 255.9815 + } 255.9816 + } 255.9817 + 255.9818 + listItem.parentNode.removeChild(listItem); 255.9819 + } 255.9820 + } else { 255.9821 + while (listItem = (list.firstElementChild || list.firstChild)) { 255.9822 + if (listItem.querySelector && listItem.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6")) { 255.9823 + while (firstChild = listItem.firstChild) { 255.9824 + fragment.appendChild(firstChild); 255.9825 + } 255.9826 + } else { 255.9827 + paragraph = doc.createElement("p"); 255.9828 + while (firstChild = listItem.firstChild) { 255.9829 + paragraph.appendChild(firstChild); 255.9830 + } 255.9831 + fragment.appendChild(paragraph); 255.9832 + } 255.9833 + listItem.parentNode.removeChild(listItem); 255.9834 + } 255.9835 + } 255.9836 + 255.9837 + list.parentNode.replaceChild(fragment, list); 255.9838 + } 255.9839 + 255.9840 + dom.resolveList = resolveList; 255.9841 +})(wysihtml.dom); 255.9842 + 255.9843 +/** 255.9844 + * Sandbox for executing javascript, parsing css styles and doing dom operations in a secure way 255.9845 + * 255.9846 + * Browser Compatibility: 255.9847 + * - Secure in MSIE 6+, but only when the user hasn't made changes to his security level "restricted" 255.9848 + * - Partially secure in other browsers (Firefox, Opera, Safari, Chrome, ...) 255.9849 + * 255.9850 + * Please note that this class can't benefit from the HTML5 sandbox attribute for the following reasons: 255.9851 + * - sandboxing doesn't work correctly with inlined content (src="javascript:'<html>...</html>'") 255.9852 + * - sandboxing of physical documents causes that the dom isn't accessible anymore from the outside (iframe.contentWindow, ...) 255.9853 + * - setting the "allow-same-origin" flag would fix that, but then still javascript and dom events refuse to fire 255.9854 + * - therefore the "allow-scripts" flag is needed, which then would deactivate any security, as the js executed inside the iframe 255.9855 + * can do anything as if the sandbox attribute wasn't set 255.9856 + * 255.9857 + * @param {Function} [readyCallback] Method that gets invoked when the sandbox is ready 255.9858 + * @param {Object} [config] Optional parameters 255.9859 + * 255.9860 + * @example 255.9861 + * new wysihtml.dom.Sandbox(function(sandbox) { 255.9862 + * sandbox.getWindow().document.body.innerHTML = '<img src=foo.gif onerror="alert(document.cookie)">'; 255.9863 + * }); 255.9864 + */ 255.9865 +(function(wysihtml) { 255.9866 + var /** 255.9867 + * Default configuration 255.9868 + */ 255.9869 + doc = document, 255.9870 + /** 255.9871 + * Properties to unset/protect on the window object 255.9872 + */ 255.9873 + windowProperties = [ 255.9874 + "parent", "top", "opener", "frameElement", "frames", 255.9875 + "localStorage", "globalStorage", "sessionStorage", "indexedDB" 255.9876 + ], 255.9877 + /** 255.9878 + * Properties on the window object which are set to an empty function 255.9879 + */ 255.9880 + windowProperties2 = [ 255.9881 + "open", "close", "openDialog", "showModalDialog", 255.9882 + "alert", "confirm", "prompt", 255.9883 + "openDatabase", "postMessage", 255.9884 + "XMLHttpRequest", "XDomainRequest" 255.9885 + ], 255.9886 + /** 255.9887 + * Properties to unset/protect on the document object 255.9888 + */ 255.9889 + documentProperties = [ 255.9890 + "referrer", 255.9891 + "write", "open", "close" 255.9892 + ]; 255.9893 + 255.9894 + wysihtml.dom.Sandbox = Base.extend( 255.9895 + /** @scope wysihtml.dom.Sandbox.prototype */ { 255.9896 + 255.9897 + constructor: function(readyCallback, config) { 255.9898 + this.callback = readyCallback || wysihtml.EMPTY_FUNCTION; 255.9899 + this.config = wysihtml.lang.object({}).merge(config).get(); 255.9900 + if (!this.config.className) { 255.9901 + this.config.className = "wysihtml-sandbox"; 255.9902 + } 255.9903 + this.editableArea = this._createIframe(); 255.9904 + }, 255.9905 + 255.9906 + insertInto: function(element) { 255.9907 + if (typeof(element) === "string") { 255.9908 + element = doc.getElementById(element); 255.9909 + } 255.9910 + 255.9911 + element.appendChild(this.editableArea); 255.9912 + }, 255.9913 + 255.9914 + getIframe: function() { 255.9915 + return this.editableArea; 255.9916 + }, 255.9917 + 255.9918 + getWindow: function() { 255.9919 + this._readyError(); 255.9920 + }, 255.9921 + 255.9922 + getDocument: function() { 255.9923 + this._readyError(); 255.9924 + }, 255.9925 + 255.9926 + destroy: function() { 255.9927 + var iframe = this.getIframe(); 255.9928 + iframe.parentNode.removeChild(iframe); 255.9929 + }, 255.9930 + 255.9931 + _readyError: function() { 255.9932 + throw new Error("wysihtml.Sandbox: Sandbox iframe isn't loaded yet"); 255.9933 + }, 255.9934 + 255.9935 + /** 255.9936 + * Creates the sandbox iframe 255.9937 + * 255.9938 + * Some important notes: 255.9939 + * - We can't use HTML5 sandbox for now: 255.9940 + * setting it causes that the iframe's dom can't be accessed from the outside 255.9941 + * Therefore we need to set the "allow-same-origin" flag which enables accessing the iframe's dom 255.9942 + * But then there's another problem, DOM events (focus, blur, change, keypress, ...) aren't fired. 255.9943 + * In order to make this happen we need to set the "allow-scripts" flag. 255.9944 + * A combination of allow-scripts and allow-same-origin is almost the same as setting no sandbox attribute at all. 255.9945 + * - Chrome & Safari, doesn't seem to support sandboxing correctly when the iframe's html is inlined (no physical document) 255.9946 + * - IE needs to have the security="restricted" attribute set before the iframe is 255.9947 + * inserted into the dom tree 255.9948 + * - Believe it or not but in IE "security" in document.createElement("iframe") is false, even 255.9949 + * though it supports it 255.9950 + * - When an iframe has security="restricted", in IE eval() & execScript() don't work anymore 255.9951 + * - IE doesn't fire the onload event when the content is inlined in the src attribute, therefore we rely 255.9952 + * on the onreadystatechange event 255.9953 + */ 255.9954 + _createIframe: function() { 255.9955 + var that = this, 255.9956 + iframe = doc.createElement("iframe"); 255.9957 + iframe.className = this.config.className; 255.9958 + wysihtml.dom.setAttributes({ 255.9959 + "security": "restricted", 255.9960 + "allowtransparency": "true", 255.9961 + "frameborder": 0, 255.9962 + "width": 0, 255.9963 + "height": 0, 255.9964 + "marginwidth": 0, 255.9965 + "marginheight": 0 255.9966 + }).on(iframe); 255.9967 + 255.9968 + // Setting the src like this prevents ssl warnings in IE6 255.9969 + if (wysihtml.browser.throwsMixedContentWarningWhenIframeSrcIsEmpty()) { 255.9970 + iframe.src = "javascript:'<html></html>'"; 255.9971 + } 255.9972 + 255.9973 + iframe.onload = function() { 255.9974 + iframe.onreadystatechange = iframe.onload = null; 255.9975 + that._onLoadIframe(iframe); 255.9976 + }; 255.9977 + 255.9978 + iframe.onreadystatechange = function() { 255.9979 + if (/loaded|complete/.test(iframe.readyState)) { 255.9980 + iframe.onreadystatechange = iframe.onload = null; 255.9981 + that._onLoadIframe(iframe); 255.9982 + } 255.9983 + }; 255.9984 + 255.9985 + return iframe; 255.9986 + }, 255.9987 + 255.9988 + /** 255.9989 + * Callback for when the iframe has finished loading 255.9990 + */ 255.9991 + _onLoadIframe: function(iframe) { 255.9992 + // don't resume when the iframe got unloaded (eg. by removing it from the dom) 255.9993 + if (!wysihtml.dom.contains(doc.documentElement, iframe)) { 255.9994 + return; 255.9995 + } 255.9996 + 255.9997 + var that = this, 255.9998 + iframeWindow = iframe.contentWindow, 255.9999 + iframeDocument = iframe.contentWindow.document, 255.10000 + charset = doc.characterSet || doc.charset || "utf-8", 255.10001 + sandboxHtml = this._getHtml({ 255.10002 + charset: charset, 255.10003 + stylesheets: this.config.stylesheets 255.10004 + }); 255.10005 + 255.10006 + // Create the basic dom tree including proper DOCTYPE and charset 255.10007 + iframeDocument.open("text/html", "replace"); 255.10008 + iframeDocument.write(sandboxHtml); 255.10009 + iframeDocument.close(); 255.10010 + 255.10011 + this.getWindow = function() { return iframe.contentWindow; }; 255.10012 + this.getDocument = function() { return iframe.contentWindow.document; }; 255.10013 + 255.10014 + // Catch js errors and pass them to the parent's onerror event 255.10015 + // addEventListener("error") doesn't work properly in some browsers 255.10016 + // TODO: apparently this doesn't work in IE9! 255.10017 + iframeWindow.onerror = function(errorMessage, fileName, lineNumber) { 255.10018 + throw new Error("wysihtml.Sandbox: " + errorMessage, fileName, lineNumber); 255.10019 + }; 255.10020 + 255.10021 + if (!wysihtml.browser.supportsSandboxedIframes()) { 255.10022 + // Unset a bunch of sensitive variables 255.10023 + // Please note: This isn't hack safe! 255.10024 + // It more or less just takes care of basic attacks and prevents accidental theft of sensitive information 255.10025 + // IE is secure though, which is the most important thing, since IE is the only browser, who 255.10026 + // takes over scripts & styles into contentEditable elements when copied from external websites 255.10027 + // or applications (Microsoft Word, ...) 255.10028 + var i, length; 255.10029 + for (i=0, length=windowProperties.length; i<length; i++) { 255.10030 + this._unset(iframeWindow, windowProperties[i]); 255.10031 + } 255.10032 + for (i=0, length=windowProperties2.length; i<length; i++) { 255.10033 + this._unset(iframeWindow, windowProperties2[i], wysihtml.EMPTY_FUNCTION); 255.10034 + } 255.10035 + for (i=0, length=documentProperties.length; i<length; i++) { 255.10036 + this._unset(iframeDocument, documentProperties[i]); 255.10037 + } 255.10038 + // This doesn't work in Safari 5 255.10039 + // See http://stackoverflow.com/questions/992461/is-it-possible-to-override-document-cookie-in-webkit 255.10040 + this._unset(iframeDocument, "cookie", "", true); 255.10041 + } 255.10042 + 255.10043 + if (wysihtml.polyfills) { 255.10044 + wysihtml.polyfills(iframeWindow, iframeDocument).apply(); 255.10045 + } 255.10046 + 255.10047 + this.loaded = true; 255.10048 + 255.10049 + // Trigger the callback 255.10050 + setTimeout(function() { that.callback(that); }, 0); 255.10051 + }, 255.10052 + 255.10053 + _getHtml: function(templateVars) { 255.10054 + var stylesheets = templateVars.stylesheets, 255.10055 + html = "", 255.10056 + i = 0, 255.10057 + length; 255.10058 + stylesheets = typeof(stylesheets) === "string" ? [stylesheets] : stylesheets; 255.10059 + if (stylesheets) { 255.10060 + length = stylesheets.length; 255.10061 + for (; i<length; i++) { 255.10062 + html += '<link rel="stylesheet" href="' + stylesheets[i] + '">'; 255.10063 + } 255.10064 + } 255.10065 + templateVars.stylesheets = html; 255.10066 + 255.10067 + return wysihtml.lang.string( 255.10068 + '<!DOCTYPE html><html><head>' 255.10069 + + '<meta charset="#{charset}">#{stylesheets}</head>' 255.10070 + + '<body></body></html>' 255.10071 + ).interpolate(templateVars); 255.10072 + }, 255.10073 + 255.10074 + /** 255.10075 + * Method to unset/override existing variables 255.10076 + * @example 255.10077 + * // Make cookie unreadable and unwritable 255.10078 + * this._unset(document, "cookie", "", true); 255.10079 + */ 255.10080 + _unset: function(object, property, value, setter) { 255.10081 + try { object[property] = value; } catch(e) {} 255.10082 + 255.10083 + try { object.__defineGetter__(property, function() { return value; }); } catch(e) {} 255.10084 + if (setter) { 255.10085 + try { object.__defineSetter__(property, function() {}); } catch(e) {} 255.10086 + } 255.10087 + 255.10088 + if (!wysihtml.browser.crashesWhenDefineProperty(property)) { 255.10089 + try { 255.10090 + var config = { 255.10091 + get: function() { return value; } 255.10092 + }; 255.10093 + if (setter) { 255.10094 + config.set = function() {}; 255.10095 + } 255.10096 + Object.defineProperty(object, property, config); 255.10097 + } catch(e) {} 255.10098 + } 255.10099 + } 255.10100 + }); 255.10101 +})(wysihtml); 255.10102 + 255.10103 +(function() { 255.10104 + var mapping = { 255.10105 + "className": "class" 255.10106 + }; 255.10107 + wysihtml.dom.setAttributes = function(attributes) { 255.10108 + return { 255.10109 + on: function(element) { 255.10110 + for (var i in attributes) { 255.10111 + element.setAttribute(mapping[i] || i, attributes[i]); 255.10112 + } 255.10113 + } 255.10114 + }; 255.10115 + }; 255.10116 +})(); 255.10117 + 255.10118 +wysihtml.dom.setStyles = function(styles) { 255.10119 + return { 255.10120 + on: function(element) { 255.10121 + var style = element.style; 255.10122 + if (typeof(styles) === "string") { 255.10123 + style.cssText += ";" + styles; 255.10124 + return; 255.10125 + } 255.10126 + for (var i in styles) { 255.10127 + if (i === "float") { 255.10128 + style.cssFloat = styles[i]; 255.10129 + style.styleFloat = styles[i]; 255.10130 + } else { 255.10131 + style[i] = styles[i]; 255.10132 + } 255.10133 + } 255.10134 + } 255.10135 + }; 255.10136 +}; 255.10137 + 255.10138 +/** 255.10139 + * Simulate HTML5 placeholder attribute 255.10140 + * 255.10141 + * Needed since 255.10142 + * - div[contentEditable] elements don't support it 255.10143 + * - older browsers (such as IE8 and Firefox 3.6) don't support it at all 255.10144 + * 255.10145 + * @param {Object} parent Instance of main wysihtml.Editor class 255.10146 + * @param {Element} view Instance of wysihtml.views.* class 255.10147 + * @param {String} placeholderText 255.10148 + * 255.10149 + * @example 255.10150 + * wysihtml.dom.simulatePlaceholder(this, composer, "Foobar"); 255.10151 + */ 255.10152 +(function(dom) { 255.10153 + dom.simulatePlaceholder = function(editor, view, placeholderText, placeholderClassName) { 255.10154 + var CLASS_NAME = placeholderClassName || "wysihtml-placeholder", 255.10155 + unset = function() { 255.10156 + var composerIsVisible = view.element.offsetWidth > 0 && view.element.offsetHeight > 0; 255.10157 + if (view.hasPlaceholderSet()) { 255.10158 + view.clear(); 255.10159 + view.element.focus(); 255.10160 + if (composerIsVisible ) { 255.10161 + setTimeout(function() { 255.10162 + var sel = view.selection.getSelection(); 255.10163 + if (!sel.focusNode || !sel.anchorNode) { 255.10164 + view.selection.selectNode(view.element.firstChild || view.element); 255.10165 + } 255.10166 + }, 0); 255.10167 + } 255.10168 + } 255.10169 + view.placeholderSet = false; 255.10170 + dom.removeClass(view.element, CLASS_NAME); 255.10171 + }, 255.10172 + set = function() { 255.10173 + if (view.isEmpty() && !view.placeholderSet) { 255.10174 + view.placeholderSet = true; 255.10175 + view.setValue(placeholderText, false); 255.10176 + dom.addClass(view.element, CLASS_NAME); 255.10177 + } 255.10178 + }; 255.10179 + 255.10180 + editor 255.10181 + .on("set_placeholder", set) 255.10182 + .on("unset_placeholder", unset) 255.10183 + .on("focus:composer", unset) 255.10184 + .on("paste:composer", unset) 255.10185 + .on("blur:composer", set); 255.10186 + 255.10187 + set(); 255.10188 + }; 255.10189 +})(wysihtml.dom); 255.10190 + 255.10191 +(function(dom) { 255.10192 + var documentElement = document.documentElement; 255.10193 + if ("textContent" in documentElement) { 255.10194 + dom.setTextContent = function(element, text) { 255.10195 + element.textContent = text; 255.10196 + }; 255.10197 + 255.10198 + dom.getTextContent = function(element) { 255.10199 + return element.textContent; 255.10200 + }; 255.10201 + } else if ("innerText" in documentElement) { 255.10202 + dom.setTextContent = function(element, text) { 255.10203 + element.innerText = text; 255.10204 + }; 255.10205 + 255.10206 + dom.getTextContent = function(element) { 255.10207 + return element.innerText; 255.10208 + }; 255.10209 + } else { 255.10210 + dom.setTextContent = function(element, text) { 255.10211 + element.nodeValue = text; 255.10212 + }; 255.10213 + 255.10214 + dom.getTextContent = function(element) { 255.10215 + return element.nodeValue; 255.10216 + }; 255.10217 + } 255.10218 +})(wysihtml.dom); 255.10219 + 255.10220 +/* Unwraps element and returns list of childNodes that the node contained. 255.10221 + * 255.10222 + * Example: 255.10223 + * var childnodes = wysihtml.dom.unwrap(document.querySelector('.unwrap-me')); 255.10224 +*/ 255.10225 + 255.10226 +wysihtml.dom.unwrap = function(node) { 255.10227 + var children = []; 255.10228 + if (node.parentNode) { 255.10229 + while (node.lastChild) { 255.10230 + children.unshift(node.lastChild); 255.10231 + wysihtml.dom.insert(node.lastChild).after(node); 255.10232 + } 255.10233 + node.parentNode.removeChild(node); 255.10234 + } 255.10235 + return children; 255.10236 +}; 255.10237 + 255.10238 +/** 255.10239 + * Fix most common html formatting misbehaviors of browsers implementation when inserting 255.10240 + * content via copy & paste contentEditable 255.10241 + * 255.10242 + * @author Christopher Blum 255.10243 + */ 255.10244 +wysihtml.quirks.cleanPastedHTML = (function() { 255.10245 + 255.10246 + var styleToRegex = function (styleStr) { 255.10247 + var trimmedStr = wysihtml.lang.string(styleStr).trim(), 255.10248 + escapedStr = trimmedStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); 255.10249 + 255.10250 + return new RegExp("^((?!^" + escapedStr + "$).)*$", "i"); 255.10251 + }; 255.10252 + 255.10253 + var extendRulesWithStyleExceptions = function (rules, exceptStyles) { 255.10254 + var newRules = wysihtml.lang.object(rules).clone(true), 255.10255 + tag, style; 255.10256 + 255.10257 + for (tag in newRules.tags) { 255.10258 + 255.10259 + if (newRules.tags.hasOwnProperty(tag)) { 255.10260 + if (newRules.tags[tag].keep_styles) { 255.10261 + for (style in newRules.tags[tag].keep_styles) { 255.10262 + if (newRules.tags[tag].keep_styles.hasOwnProperty(style)) { 255.10263 + if (exceptStyles[style]) { 255.10264 + newRules.tags[tag].keep_styles[style] = styleToRegex(exceptStyles[style]); 255.10265 + } 255.10266 + } 255.10267 + } 255.10268 + } 255.10269 + } 255.10270 + } 255.10271 + 255.10272 + return newRules; 255.10273 + }; 255.10274 + 255.10275 + var pickRuleset = function(ruleset, html) { 255.10276 + var pickedSet, defaultSet; 255.10277 + 255.10278 + if (!ruleset) { 255.10279 + return null; 255.10280 + } 255.10281 + 255.10282 + for (var i = 0, max = ruleset.length; i < max; i++) { 255.10283 + if (!ruleset[i].condition) { 255.10284 + defaultSet = ruleset[i].set; 255.10285 + } 255.10286 + if (ruleset[i].condition && ruleset[i].condition.test(html)) { 255.10287 + return ruleset[i].set; 255.10288 + } 255.10289 + } 255.10290 + 255.10291 + return defaultSet; 255.10292 + }; 255.10293 + 255.10294 + return function(html, options) { 255.10295 + var exceptStyles = { 255.10296 + 'color': wysihtml.dom.getStyle("color").from(options.referenceNode), 255.10297 + 'fontSize': wysihtml.dom.getStyle("font-size").from(options.referenceNode) 255.10298 + }, 255.10299 + rules = extendRulesWithStyleExceptions(pickRuleset(options.rules, html) || {}, exceptStyles), 255.10300 + newHtml; 255.10301 + 255.10302 + newHtml = wysihtml.dom.parse(html, { 255.10303 + "rules": rules, 255.10304 + "cleanUp": true, // <span> elements, empty or without attributes, should be removed/replaced with their content 255.10305 + "context": options.referenceNode.ownerDocument, 255.10306 + "uneditableClass": options.uneditableClass, 255.10307 + "clearInternals" : true, // don't paste temprorary selection and other markings 255.10308 + "unjoinNbsps" : true 255.10309 + }); 255.10310 + 255.10311 + return newHtml; 255.10312 + }; 255.10313 + 255.10314 +})(); 255.10315 + 255.10316 +/** 255.10317 + * IE and Opera leave an empty paragraph in the contentEditable element after clearing it 255.10318 + * 255.10319 + * @param {Object} contentEditableElement The contentEditable element to observe for clearing events 255.10320 + * @exaple 255.10321 + * wysihtml.quirks.ensureProperClearing(myContentEditableElement); 255.10322 + */ 255.10323 +wysihtml.quirks.ensureProperClearing = (function() { 255.10324 + var clearIfNecessary = function() { 255.10325 + var element = this; 255.10326 + setTimeout(function() { 255.10327 + var innerHTML = element.innerHTML.toLowerCase(); 255.10328 + if (innerHTML == "<p> </p>" || 255.10329 + innerHTML == "<p> </p><p> </p>") { 255.10330 + element.innerHTML = ""; 255.10331 + } 255.10332 + }, 0); 255.10333 + }; 255.10334 + 255.10335 + return function(composer) { 255.10336 + wysihtml.dom.observe(composer.element, ["cut", "keydown"], clearIfNecessary); 255.10337 + }; 255.10338 +})(); 255.10339 + 255.10340 +// See https://bugzilla.mozilla.org/show_bug.cgi?id=664398 255.10341 +// 255.10342 +// In Firefox this: 255.10343 +// var d = document.createElement("div"); 255.10344 +// d.innerHTML ='<a href="~"></a>'; 255.10345 +// d.innerHTML; 255.10346 +// will result in: 255.10347 +// <a href="%7E"></a> 255.10348 +// which is wrong 255.10349 +(function(wysihtml) { 255.10350 + var TILDE_ESCAPED = "%7E"; 255.10351 + wysihtml.quirks.getCorrectInnerHTML = function(element) { 255.10352 + var innerHTML = element.innerHTML; 255.10353 + if (innerHTML.indexOf(TILDE_ESCAPED) === -1) { 255.10354 + return innerHTML; 255.10355 + } 255.10356 + 255.10357 + var elementsWithTilde = element.querySelectorAll("[href*='~'], [src*='~']"), 255.10358 + url, 255.10359 + urlToSearch, 255.10360 + length, 255.10361 + i; 255.10362 + for (i=0, length=elementsWithTilde.length; i<length; i++) { 255.10363 + url = elementsWithTilde[i].href || elementsWithTilde[i].src; 255.10364 + urlToSearch = wysihtml.lang.string(url).replace("~").by(TILDE_ESCAPED); 255.10365 + innerHTML = wysihtml.lang.string(innerHTML).replace(urlToSearch).by(url); 255.10366 + } 255.10367 + return innerHTML; 255.10368 + }; 255.10369 +})(wysihtml); 255.10370 + 255.10371 +/** 255.10372 + * Force rerendering of a given element 255.10373 + * Needed to fix display misbehaviors of IE 255.10374 + * 255.10375 + * @param {Element} element The element object which needs to be rerendered 255.10376 + * @example 255.10377 + * wysihtml.quirks.redraw(document.body); 255.10378 + */ 255.10379 +(function(wysihtml) { 255.10380 + var CLASS_NAME = "wysihtml-quirks-redraw"; 255.10381 + 255.10382 + wysihtml.quirks.redraw = function(element) { 255.10383 + wysihtml.dom.addClass(element, CLASS_NAME); 255.10384 + wysihtml.dom.removeClass(element, CLASS_NAME); 255.10385 + 255.10386 + // Following hack is needed for firefox to make sure that image resize handles are properly removed 255.10387 + try { 255.10388 + var doc = element.ownerDocument; 255.10389 + doc.execCommand("italic", false, null); 255.10390 + doc.execCommand("italic", false, null); 255.10391 + } catch(e) {} 255.10392 + }; 255.10393 +})(wysihtml); 255.10394 + 255.10395 +(function(wysihtml) { 255.10396 + 255.10397 + // List of supported color format parsing methods 255.10398 + // If radix is not defined 10 is expected as default 255.10399 + var colorParseMethods = { 255.10400 + rgba : { 255.10401 + regex: /^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d\.]+)\s*\)/i, 255.10402 + name: "rgba" 255.10403 + }, 255.10404 + rgb : { 255.10405 + regex: /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/i, 255.10406 + name: "rgb" 255.10407 + }, 255.10408 + hex6 : { 255.10409 + regex: /^#([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])/i, 255.10410 + name: "hex", 255.10411 + radix: 16 255.10412 + }, 255.10413 + hex3 : { 255.10414 + regex: /^#([0-9a-f])([0-9a-f])([0-9a-f])/i, 255.10415 + name: "hex", 255.10416 + radix: 16 255.10417 + } 255.10418 + }, 255.10419 + // Takes a style key name as an argument and makes a regex that can be used to the match key:value pair from style string 255.10420 + makeParamRegExp = function (p) { 255.10421 + return new RegExp("(^|\\s|;)" + p + "\\s*:\\s*[^;$]+", "gi"); 255.10422 + }; 255.10423 + 255.10424 + // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns suitable parsing method for it 255.10425 + function getColorParseMethod (colorStr) { 255.10426 + var prop, colorTypeConf; 255.10427 + 255.10428 + for (prop in colorParseMethods) { 255.10429 + if (!colorParseMethods.hasOwnProperty(prop)) { continue; } 255.10430 + 255.10431 + colorTypeConf = colorParseMethods[prop]; 255.10432 + 255.10433 + if (colorTypeConf.regex.test(colorStr)) { 255.10434 + return colorTypeConf; 255.10435 + } 255.10436 + } 255.10437 + } 255.10438 + 255.10439 + // Takes color string value ("#abc", "rgb(1,2,3)", ...) as an argument and returns the type of that color format "hex", "rgb", "rgba". 255.10440 + function getColorFormat (colorStr) { 255.10441 + var type = getColorParseMethod(colorStr); 255.10442 + 255.10443 + return type ? type.name : undefined; 255.10444 + } 255.10445 + 255.10446 + // Public API functions for styleParser 255.10447 + wysihtml.quirks.styleParser = { 255.10448 + 255.10449 + // Takes color string value as an argument and returns suitable parsing method for it 255.10450 + getColorParseMethod : getColorParseMethod, 255.10451 + 255.10452 + // Takes color string value as an argument and returns the type of that color format "hex", "rgb", "rgba". 255.10453 + getColorFormat : getColorFormat, 255.10454 + 255.10455 + /* Parses a color string to and array of [red, green, blue, alpha]. 255.10456 + * paramName: optional argument to parse color value directly from style string parameter 255.10457 + * 255.10458 + * Examples: 255.10459 + * var colorArray = wysihtml.quirks.styleParser.parseColor("#ABC"); // [170, 187, 204, 1] 255.10460 + * var colorArray = wysihtml.quirks.styleParser.parseColor("#AABBCC"); // [170, 187, 204, 1] 255.10461 + * var colorArray = wysihtml.quirks.styleParser.parseColor("rgb(1,2,3)"); // [1, 2, 3, 1] 255.10462 + * var colorArray = wysihtml.quirks.styleParser.parseColor("rgba(1,2,3,0.5)"); // [1, 2, 3, 0.5] 255.10463 + * 255.10464 + * var colorArray = wysihtml.quirks.styleParser.parseColor("background-color: #ABC; color: #000;", "background-color"); // [170, 187, 204, 1] 255.10465 + * var colorArray = wysihtml.quirks.styleParser.parseColor("background-color: #ABC; color: #000;", "color"); // [0, 0, 0, 1] 255.10466 + */ 255.10467 + parseColor : function (stylesStr, paramName) { 255.10468 + var paramsRegex, params, colorType, colorMatch, radix, 255.10469 + colorStr = stylesStr; 255.10470 + 255.10471 + if (paramName) { 255.10472 + paramsRegex = makeParamRegExp(paramName); 255.10473 + 255.10474 + if (!(params = stylesStr.match(paramsRegex))) { return false; } 255.10475 + 255.10476 + params = params.pop().split(":")[1]; 255.10477 + colorStr = wysihtml.lang.string(params).trim(); 255.10478 + } 255.10479 + 255.10480 + if (!(colorType = getColorParseMethod(colorStr))) { return false; } 255.10481 + if (!(colorMatch = colorStr.match(colorType.regex))) { return false; } 255.10482 + 255.10483 + radix = colorType.radix || 10; 255.10484 + 255.10485 + if (colorType === colorParseMethods.hex3) { 255.10486 + colorMatch.shift(); 255.10487 + colorMatch.push(1); 255.10488 + return wysihtml.lang.array(colorMatch).map(function(d, idx) { 255.10489 + return (idx < 3) ? (parseInt(d, radix) * radix) + parseInt(d, radix): parseFloat(d); 255.10490 + }); 255.10491 + } 255.10492 + 255.10493 + colorMatch.shift(); 255.10494 + 255.10495 + if (!colorMatch[3]) { 255.10496 + colorMatch.push(1); 255.10497 + } 255.10498 + 255.10499 + return wysihtml.lang.array(colorMatch).map(function(d, idx) { 255.10500 + return (idx < 3) ? parseInt(d, radix): parseFloat(d); 255.10501 + }); 255.10502 + }, 255.10503 + 255.10504 + /* Takes rgba color array [r,g,b,a] as a value and formats it to color string with given format type 255.10505 + * If no format is given, rgba/rgb is returned based on alpha value 255.10506 + * 255.10507 + * Example: 255.10508 + * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "hash"); // "#AABBCC" 255.10509 + * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "hex"); // "AABBCC" 255.10510 + * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "csv"); // "170, 187, 204, 1" 255.10511 + * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "rgba"); // "rgba(170,187,204,1)" 255.10512 + * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1], "rgb"); // "rgb(170,187,204)" 255.10513 + * 255.10514 + * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 0.5]); // "rgba(170,187,204,0.5)" 255.10515 + * var colorStr = wysihtml.quirks.styleParser.unparseColor([170, 187, 204, 1]); // "rgb(170,187,204)" 255.10516 + */ 255.10517 + unparseColor: function(val, colorFormat) { 255.10518 + var hexRadix = 16; 255.10519 + 255.10520 + if (colorFormat === "hex") { 255.10521 + return (val[0].toString(hexRadix) + val[1].toString(hexRadix) + val[2].toString(hexRadix)).toUpperCase(); 255.10522 + } else if (colorFormat === "hash") { 255.10523 + return "#" + (val[0].toString(hexRadix) + val[1].toString(hexRadix) + val[2].toString(hexRadix)).toUpperCase(); 255.10524 + } else if (colorFormat === "rgb") { 255.10525 + return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")"; 255.10526 + } else if (colorFormat === "rgba") { 255.10527 + return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")"; 255.10528 + } else if (colorFormat === "csv") { 255.10529 + return val[0] + "," + val[1] + "," + val[2] + "," + val[3]; 255.10530 + } 255.10531 + 255.10532 + if (val[3] && val[3] !== 1) { 255.10533 + return "rgba(" + val[0] + "," + val[1] + "," + val[2] + "," + val[3] + ")"; 255.10534 + } else { 255.10535 + return "rgb(" + val[0] + "," + val[1] + "," + val[2] + ")"; 255.10536 + } 255.10537 + }, 255.10538 + 255.10539 + // Parses font size value from style string 255.10540 + parseFontSize: function(stylesStr) { 255.10541 + var params = stylesStr.match(makeParamRegExp("font-size")); 255.10542 + if (params) { 255.10543 + return wysihtml.lang.string(params[params.length - 1].split(":")[1]).trim(); 255.10544 + } 255.10545 + return false; 255.10546 + } 255.10547 + }; 255.10548 + 255.10549 +})(wysihtml); 255.10550 + 255.10551 +/** 255.10552 + * Selection API 255.10553 + * 255.10554 + * @example 255.10555 + * var selection = new wysihtml.Selection(editor); 255.10556 + */ 255.10557 +(function(wysihtml) { 255.10558 + var dom = wysihtml.dom; 255.10559 + 255.10560 + function _getCumulativeOffsetTop(element) { 255.10561 + var top = 0; 255.10562 + if (element.parentNode) { 255.10563 + do { 255.10564 + top += element.offsetTop || 0; 255.10565 + element = element.offsetParent; 255.10566 + } while (element); 255.10567 + } 255.10568 + return top; 255.10569 + } 255.10570 + 255.10571 + // Provides the depth of ``descendant`` relative to ``ancestor`` 255.10572 + function getDepth(ancestor, descendant) { 255.10573 + var ret = 0; 255.10574 + while (descendant !== ancestor) { 255.10575 + ret++; 255.10576 + descendant = descendant.parentNode; 255.10577 + if (!descendant) 255.10578 + throw new Error("not a descendant of ancestor!"); 255.10579 + } 255.10580 + return ret; 255.10581 + } 255.10582 + 255.10583 + function getRangeNode(node, offset) { 255.10584 + if (node.nodeType === 3) { 255.10585 + return node; 255.10586 + } else { 255.10587 + return node.childNodes[offset] || node; 255.10588 + } 255.10589 + } 255.10590 + 255.10591 + function getWebkitSelectionFixNode(container) { 255.10592 + var blankNode = document.createElement('span'); 255.10593 + 255.10594 + var placeholderRemover = function(event) { 255.10595 + // Self-destructs the caret and keeps the text inserted into it by user 255.10596 + var lastChild; 255.10597 + 255.10598 + container.removeEventListener('mouseup', placeholderRemover); 255.10599 + container.removeEventListener('keydown', placeholderRemover); 255.10600 + container.removeEventListener('touchstart', placeholderRemover); 255.10601 + container.removeEventListener('focus', placeholderRemover); 255.10602 + container.removeEventListener('blur', placeholderRemover); 255.10603 + container.removeEventListener('paste', delayedPlaceholderRemover); 255.10604 + container.removeEventListener('drop', delayedPlaceholderRemover); 255.10605 + container.removeEventListener('beforepaste', delayedPlaceholderRemover); 255.10606 + 255.10607 + if (blankNode && blankNode.parentNode) { 255.10608 + blankNode.parentNode.removeChild(blankNode); 255.10609 + } 255.10610 + }, 255.10611 + delayedPlaceholderRemover = function (event) { 255.10612 + if (blankNode && blankNode.parentNode) { 255.10613 + setTimeout(placeholderRemover, 0); 255.10614 + } 255.10615 + }; 255.10616 + 255.10617 + blankNode.appendChild(container.ownerDocument.createTextNode(wysihtml.INVISIBLE_SPACE)); 255.10618 + blankNode.className = '_wysihtml-temp-caret-fix'; 255.10619 + blankNode.style.display = 'block'; 255.10620 + blankNode.style.minWidth = '1px'; 255.10621 + blankNode.style.height = '0px'; 255.10622 + 255.10623 + container.addEventListener('mouseup', placeholderRemover); 255.10624 + container.addEventListener('keydown', placeholderRemover); 255.10625 + container.addEventListener('touchstart', placeholderRemover); 255.10626 + container.addEventListener('focus', placeholderRemover); 255.10627 + container.addEventListener('blur', placeholderRemover); 255.10628 + container.addEventListener('paste', delayedPlaceholderRemover); 255.10629 + container.addEventListener('drop', delayedPlaceholderRemover); 255.10630 + container.addEventListener('beforepaste', delayedPlaceholderRemover); 255.10631 + 255.10632 + return blankNode; 255.10633 + } 255.10634 + 255.10635 + // Should fix the obtained ranges that cannot surrond contents normally to apply changes upon 255.10636 + // Being considerate to firefox that sets range start start out of span and end inside on doubleclick initiated selection 255.10637 + function expandRangeToSurround(range) { 255.10638 + if (range.canSurroundContents()) return; 255.10639 + 255.10640 + var common = range.commonAncestorContainer, 255.10641 + start_depth = getDepth(common, range.startContainer), 255.10642 + end_depth = getDepth(common, range.endContainer); 255.10643 + 255.10644 + while(!range.canSurroundContents()) { 255.10645 + // In the following branches, we cannot just decrement the depth variables because the setStartBefore/setEndAfter may move the start or end of the range more than one level relative to ``common``. So we need to recompute the depth. 255.10646 + if (start_depth > end_depth) { 255.10647 + range.setStartBefore(range.startContainer); 255.10648 + start_depth = getDepth(common, range.startContainer); 255.10649 + } 255.10650 + else { 255.10651 + range.setEndAfter(range.endContainer); 255.10652 + end_depth = getDepth(common, range.endContainer); 255.10653 + } 255.10654 + } 255.10655 + } 255.10656 + 255.10657 + wysihtml.Selection = Base.extend( 255.10658 + /** @scope wysihtml.Selection.prototype */ { 255.10659 + constructor: function(editor, contain, unselectableClass) { 255.10660 + // Make sure that our external range library is initialized 255.10661 + rangy.init(); 255.10662 + 255.10663 + this.editor = editor; 255.10664 + this.composer = editor.composer; 255.10665 + this.doc = this.composer.doc; 255.10666 + this.win = this.composer.win; 255.10667 + this.contain = contain; 255.10668 + this.unselectableClass = unselectableClass || false; 255.10669 + }, 255.10670 + 255.10671 + /** 255.10672 + * Get the current selection as a bookmark to be able to later restore it 255.10673 + * 255.10674 + * @return {Object} An object that represents the current selection 255.10675 + */ 255.10676 + getBookmark: function() { 255.10677 + var range = this.getRange(); 255.10678 + return range && range.cloneRange(); 255.10679 + }, 255.10680 + 255.10681 + /** 255.10682 + * Restore a selection retrieved via wysihtml.Selection.prototype.getBookmark 255.10683 + * 255.10684 + * @param {Object} bookmark An object that represents the current selection 255.10685 + */ 255.10686 + setBookmark: function(bookmark) { 255.10687 + if (!bookmark) { 255.10688 + return; 255.10689 + } 255.10690 + 255.10691 + this.setSelection(bookmark); 255.10692 + }, 255.10693 + 255.10694 + /** 255.10695 + * Set the caret in front of the given node 255.10696 + * 255.10697 + * @param {Object} node The element or text node where to position the caret in front of 255.10698 + * @example 255.10699 + * selection.setBefore(myElement); 255.10700 + */ 255.10701 + setBefore: function(node) { 255.10702 + var range = rangy.createRange(this.doc); 255.10703 + range.setStartBefore(node); 255.10704 + range.setEndBefore(node); 255.10705 + return this.setSelection(range); 255.10706 + }, 255.10707 + 255.10708 + // Constructs a self removing whitespace (ain absolute positioned span) for placing selection caret when normal methods fail. 255.10709 + // Webkit has an issue with placing caret into places where there are no textnodes near by. 255.10710 + createTemporaryCaretSpaceAfter: function (node) { 255.10711 + var caretPlaceholder = this.doc.createElement('span'), 255.10712 + caretPlaceholderText = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE), 255.10713 + placeholderRemover = (function(event) { 255.10714 + // Self-destructs the caret and keeps the text inserted into it by user 255.10715 + var lastChild; 255.10716 + 255.10717 + this.contain.removeEventListener('mouseup', placeholderRemover); 255.10718 + this.contain.removeEventListener('keydown', keyDownHandler); 255.10719 + this.contain.removeEventListener('touchstart', placeholderRemover); 255.10720 + this.contain.removeEventListener('focus', placeholderRemover); 255.10721 + this.contain.removeEventListener('blur', placeholderRemover); 255.10722 + this.contain.removeEventListener('paste', delayedPlaceholderRemover); 255.10723 + this.contain.removeEventListener('drop', delayedPlaceholderRemover); 255.10724 + this.contain.removeEventListener('beforepaste', delayedPlaceholderRemover); 255.10725 + 255.10726 + // If user inserted sth it is in the placeholder and sgould be unwrapped and stripped of invisible whitespace hack 255.10727 + // Otherwise the wrapper can just be removed 255.10728 + if (caretPlaceholder && caretPlaceholder.parentNode) { 255.10729 + caretPlaceholder.innerHTML = caretPlaceholder.innerHTML.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ""); 255.10730 + if ((/[^\s]+/).test(caretPlaceholder.innerHTML)) { 255.10731 + lastChild = caretPlaceholder.lastChild; 255.10732 + wysihtml.dom.unwrap(caretPlaceholder); 255.10733 + this.setAfter(lastChild); 255.10734 + } else { 255.10735 + caretPlaceholder.parentNode.removeChild(caretPlaceholder); 255.10736 + } 255.10737 + 255.10738 + } 255.10739 + }).bind(this), 255.10740 + delayedPlaceholderRemover = function (event) { 255.10741 + if (caretPlaceholder && caretPlaceholder.parentNode) { 255.10742 + setTimeout(placeholderRemover, 0); 255.10743 + } 255.10744 + }, 255.10745 + keyDownHandler = function(event) { 255.10746 + if (event.which !== 8 && event.which !== 91 && event.which !== 17 && (event.which !== 86 || (!event.ctrlKey && !event.metaKey))) { 255.10747 + placeholderRemover(); 255.10748 + } 255.10749 + }; 255.10750 + 255.10751 + caretPlaceholder.className = '_wysihtml-temp-caret-fix'; 255.10752 + caretPlaceholder.style.position = 'absolute'; 255.10753 + caretPlaceholder.style.display = 'block'; 255.10754 + caretPlaceholder.style.minWidth = '1px'; 255.10755 + caretPlaceholder.style.zIndex = '99999'; 255.10756 + caretPlaceholder.appendChild(caretPlaceholderText); 255.10757 + 255.10758 + node.parentNode.insertBefore(caretPlaceholder, node.nextSibling); 255.10759 + this.setBefore(caretPlaceholderText); 255.10760 + 255.10761 + // Remove the caret fix on any of the following events (some are delayed as content change happens after event) 255.10762 + this.contain.addEventListener('mouseup', placeholderRemover); 255.10763 + this.contain.addEventListener('keydown', keyDownHandler); 255.10764 + this.contain.addEventListener('touchstart', placeholderRemover); 255.10765 + this.contain.addEventListener('focus', placeholderRemover); 255.10766 + this.contain.addEventListener('blur', placeholderRemover); 255.10767 + this.contain.addEventListener('paste', delayedPlaceholderRemover); 255.10768 + this.contain.addEventListener('drop', delayedPlaceholderRemover); 255.10769 + this.contain.addEventListener('beforepaste', delayedPlaceholderRemover); 255.10770 + 255.10771 + return caretPlaceholder; 255.10772 + }, 255.10773 + 255.10774 + /** 255.10775 + * Set the caret after the given node 255.10776 + * 255.10777 + * @param {Object} node The element or text node where to position the caret in front of 255.10778 + * @example 255.10779 + * selection.setBefore(myElement); 255.10780 + * callback is an optional parameter accepting a function to execute when selection ahs been set 255.10781 + */ 255.10782 + setAfter: function(node, notVisual, callback) { 255.10783 + var win = this.win, 255.10784 + range = rangy.createRange(this.doc), 255.10785 + fixWebkitSelection = function() { 255.10786 + // Webkit fails to add selection if there are no textnodes in that region 255.10787 + // (like an uneditable container at the end of content). 255.10788 + var parent = node.parentNode, 255.10789 + lastSibling = parent ? parent.childNodes[parent.childNodes.length - 1] : null; 255.10790 + 255.10791 + if (!sel || (lastSibling === node && node.nodeType === 1 && win.getComputedStyle(node).display === "block")) { 255.10792 + if (notVisual) { 255.10793 + // If setAfter is used as internal between actions, self-removing caretPlaceholder has simpler implementation 255.10794 + // and remove itself in call stack end instead on user interaction 255.10795 + var caretPlaceholder = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE); 255.10796 + node.parentNode.insertBefore(caretPlaceholder, node.nextSibling); 255.10797 + this.selectNode(caretPlaceholder); 255.10798 + setTimeout(function() { 255.10799 + if (caretPlaceholder && caretPlaceholder.parentNode) { 255.10800 + caretPlaceholder.parentNode.removeChild(caretPlaceholder); 255.10801 + } 255.10802 + }, 0); 255.10803 + } else { 255.10804 + this.createTemporaryCaretSpaceAfter(node); 255.10805 + } 255.10806 + } 255.10807 + }.bind(this), 255.10808 + sel; 255.10809 + 255.10810 + range.setStartAfter(node); 255.10811 + range.setEndAfter(node); 255.10812 + 255.10813 + // In IE contenteditable must be focused before we can set selection 255.10814 + // thus setting the focus if activeElement is not this composer 255.10815 + if (!document.activeElement || document.activeElement !== this.composer.element) { 255.10816 + var scrollPos = this.composer.getScrollPos(); 255.10817 + this.composer.element.focus(); 255.10818 + this.composer.setScrollPos(scrollPos); 255.10819 + setTimeout(function() { 255.10820 + sel = this.setSelection(range); 255.10821 + fixWebkitSelection(); 255.10822 + if (callback) { 255.10823 + callback(sel); 255.10824 + } 255.10825 + }.bind(this), 0); 255.10826 + } else { 255.10827 + sel = this.setSelection(range); 255.10828 + fixWebkitSelection(); 255.10829 + if (callback) { 255.10830 + callback(sel); 255.10831 + } 255.10832 + } 255.10833 + }, 255.10834 + 255.10835 + /** 255.10836 + * Ability to select/mark nodes 255.10837 + * 255.10838 + * @param {Element} node The node/element to select 255.10839 + * @example 255.10840 + * selection.selectNode(document.getElementById("my-image")); 255.10841 + */ 255.10842 + selectNode: function(node, avoidInvisibleSpace) { 255.10843 + var range = rangy.createRange(this.doc), 255.10844 + isElement = node.nodeType === wysihtml.ELEMENT_NODE, 255.10845 + canHaveHTML = "canHaveHTML" in node ? node.canHaveHTML : (node.nodeName !== "IMG"), 255.10846 + content = isElement ? node.innerHTML : node.data, 255.10847 + isEmpty = (content === "" || content === wysihtml.INVISIBLE_SPACE), 255.10848 + displayStyle = dom.getStyle("display").from(node), 255.10849 + isBlockElement = (displayStyle === "block" || displayStyle === "list-item"); 255.10850 + 255.10851 + if (isEmpty && isElement && canHaveHTML && !avoidInvisibleSpace) { 255.10852 + // Make sure that caret is visible in node by inserting a zero width no breaking space 255.10853 + try { node.innerHTML = wysihtml.INVISIBLE_SPACE; } catch(e) {} 255.10854 + } 255.10855 + if (canHaveHTML) { 255.10856 + range.selectNodeContents(node); 255.10857 + } else { 255.10858 + range.selectNode(node); 255.10859 + } 255.10860 + 255.10861 + if (canHaveHTML && isEmpty && isElement) { 255.10862 + range.collapse(isBlockElement); 255.10863 + } else if (canHaveHTML && isEmpty) { 255.10864 + range.setStartAfter(node); 255.10865 + range.setEndAfter(node); 255.10866 + } 255.10867 + 255.10868 + this.setSelection(range); 255.10869 + }, 255.10870 + 255.10871 + /** 255.10872 + * Get the node which contains the selection 255.10873 + * 255.10874 + * @param {Boolean} [controlRange] (only IE) Whether it should return the selected ControlRange element when the selection type is a "ControlRange" 255.10875 + * @return {Object} The node that contains the caret 255.10876 + * @example 255.10877 + * var nodeThatContainsCaret = selection.getSelectedNode(); 255.10878 + */ 255.10879 + getSelectedNode: function(controlRange) { 255.10880 + var selection, 255.10881 + range; 255.10882 + 255.10883 + if (controlRange && this.doc.selection && this.doc.selection.type === "Control") { 255.10884 + range = this.doc.selection.createRange(); 255.10885 + if (range && range.length) { 255.10886 + return range.item(0); 255.10887 + } 255.10888 + } 255.10889 + 255.10890 + selection = this.getSelection(this.doc); 255.10891 + if (selection.focusNode === selection.anchorNode) { 255.10892 + return selection.focusNode; 255.10893 + } else { 255.10894 + range = this.getRange(this.doc); 255.10895 + return range ? range.commonAncestorContainer : this.doc.body; 255.10896 + } 255.10897 + }, 255.10898 + 255.10899 + fixSelBorders: function() { 255.10900 + var range = this.getRange(); 255.10901 + expandRangeToSurround(range); 255.10902 + this.setSelection(range); 255.10903 + }, 255.10904 + 255.10905 + getSelectedOwnNodes: function(controlRange) { 255.10906 + var selection, 255.10907 + ranges = this.getOwnRanges(), 255.10908 + ownNodes = []; 255.10909 + 255.10910 + for (var i = 0, maxi = ranges.length; i < maxi; i++) { 255.10911 + ownNodes.push(ranges[i].commonAncestorContainer || this.doc.body); 255.10912 + } 255.10913 + return ownNodes; 255.10914 + }, 255.10915 + 255.10916 + findNodesInSelection: function(nodeTypes) { 255.10917 + var ranges = this.getOwnRanges(), 255.10918 + nodes = [], curNodes; 255.10919 + for (var i = 0, maxi = ranges.length; i < maxi; i++) { 255.10920 + curNodes = ranges[i].getNodes([1], function(node) { 255.10921 + return wysihtml.lang.array(nodeTypes).contains(node.nodeName); 255.10922 + }); 255.10923 + nodes = nodes.concat(curNodes); 255.10924 + } 255.10925 + return nodes; 255.10926 + }, 255.10927 + 255.10928 + filterElements: function(filter) { 255.10929 + var ranges = this.getOwnRanges(), 255.10930 + nodes = [], curNodes; 255.10931 + 255.10932 + for (var i = 0, maxi = ranges.length; i < maxi; i++) { 255.10933 + curNodes = ranges[i].getNodes([1], function(element){ 255.10934 + return filter(element, ranges[i]); 255.10935 + }); 255.10936 + nodes = nodes.concat(curNodes); 255.10937 + } 255.10938 + return nodes; 255.10939 + }, 255.10940 + 255.10941 + containsUneditable: function() { 255.10942 + var uneditables = this.getOwnUneditables(), 255.10943 + selection = this.getSelection(); 255.10944 + 255.10945 + for (var i = 0, maxi = uneditables.length; i < maxi; i++) { 255.10946 + if (selection.containsNode(uneditables[i])) { 255.10947 + return true; 255.10948 + } 255.10949 + } 255.10950 + 255.10951 + return false; 255.10952 + }, 255.10953 + 255.10954 + // Deletes selection contents making sure uneditables/unselectables are not partially deleted 255.10955 + // Triggers wysihtml:uneditable:delete custom event on all deleted uneditables if customevents suppoorted 255.10956 + deleteContents: function() { 255.10957 + var range = this.getRange(); 255.10958 + this.deleteRangeContents(range); 255.10959 + this.setSelection(range); 255.10960 + }, 255.10961 + 255.10962 + // Makes sure all uneditable sare notified before deleting contents 255.10963 + deleteRangeContents: function (range) { 255.10964 + var startParent, endParent, uneditables, ev; 255.10965 + 255.10966 + if (this.unselectableClass) { 255.10967 + if ((startParent = wysihtml.dom.getParentElement(range.startContainer, { query: "." + this.unselectableClass }, false, this.contain))) { 255.10968 + range.setStartBefore(startParent); 255.10969 + } 255.10970 + if ((endParent = wysihtml.dom.getParentElement(range.endContainer, { query: "." + this.unselectableClass }, false, this.contain))) { 255.10971 + range.setEndAfter(endParent); 255.10972 + } 255.10973 + 255.10974 + // If customevents present notify uneditable elements of being deleted 255.10975 + uneditables = range.getNodes([1], (function (node) { 255.10976 + return wysihtml.dom.hasClass(node, this.unselectableClass); 255.10977 + }).bind(this)); 255.10978 + for (var i = uneditables.length; i--;) { 255.10979 + try { 255.10980 + ev = new CustomEvent("wysihtml:uneditable:delete"); 255.10981 + uneditables[i].dispatchEvent(ev); 255.10982 + } catch (err) {} 255.10983 + } 255.10984 + } 255.10985 + range.deleteContents(); 255.10986 + }, 255.10987 + 255.10988 + getCaretNode: function () { 255.10989 + var selection = this.getSelection(); 255.10990 + return (selection && selection.anchorNode) ? getRangeNode(selection.anchorNode, selection.anchorOffset) : null; 255.10991 + }, 255.10992 + 255.10993 + getPreviousNode: function(node, ignoreEmpty) { 255.10994 + var displayStyle; 255.10995 + if (!node) { 255.10996 + var selection = this.getSelection(); 255.10997 + node = (selection && selection.anchorNode) ? getRangeNode(selection.anchorNode, selection.anchorOffset) : null; 255.10998 + } 255.10999 + 255.11000 + if (node === this.contain) { 255.11001 + return false; 255.11002 + } 255.11003 + 255.11004 + var ret = node.previousSibling, 255.11005 + parent; 255.11006 + 255.11007 + if (ret === this.contain) { 255.11008 + return false; 255.11009 + } 255.11010 + 255.11011 + if (ret && ret.nodeType !== 3 && ret.nodeType !== 1) { 255.11012 + // do not count comments and other node types 255.11013 + ret = this.getPreviousNode(ret, ignoreEmpty); 255.11014 + } else if (ret && ret.nodeType === 3 && (/^\s*$/).test(ret.textContent)) { 255.11015 + // do not count empty textnodes as previous nodes 255.11016 + ret = this.getPreviousNode(ret, ignoreEmpty); 255.11017 + } else if (ignoreEmpty && ret && ret.nodeType === 1) { 255.11018 + // Do not count empty nodes if param set. 255.11019 + // Contenteditable tends to bypass and delete these silently when deleting with caret when element is inline-like 255.11020 + displayStyle = wysihtml.dom.getStyle("display").from(ret); 255.11021 + if ( 255.11022 + !wysihtml.lang.array(["BR", "HR", "IMG"]).contains(ret.nodeName) && 255.11023 + !wysihtml.lang.array(["block", "inline-block", "flex", "list-item", "table"]).contains(displayStyle) && 255.11024 + (/^[\s]*$/).test(ret.innerHTML) 255.11025 + ) { 255.11026 + ret = this.getPreviousNode(ret, ignoreEmpty); 255.11027 + } 255.11028 + } else if (!ret && node !== this.contain) { 255.11029 + parent = node.parentNode; 255.11030 + if (parent !== this.contain) { 255.11031 + ret = this.getPreviousNode(parent, ignoreEmpty); 255.11032 + } 255.11033 + } 255.11034 + 255.11035 + return (ret !== this.contain) ? ret : false; 255.11036 + }, 255.11037 + 255.11038 + // Gather info about caret location (caret node, previous and next node) 255.11039 + getNodesNearCaret: function() { 255.11040 + if (!this.isCollapsed()) { 255.11041 + throw "Selection must be caret when using selection.getNodesNearCaret()"; 255.11042 + } 255.11043 + 255.11044 + var r = this.getOwnRanges(), 255.11045 + caretNode, prevNode, nextNode, offset; 255.11046 + 255.11047 + if (r && r.length > 0) { 255.11048 + if (r[0].startContainer.nodeType === 1) { 255.11049 + caretNode = r[0].startContainer.childNodes[r[0].startOffset - 1]; 255.11050 + if (!caretNode && r[0].startOffset === 0) { 255.11051 + // Is first position before all nodes 255.11052 + nextNode = r[0].startContainer.childNodes[0]; 255.11053 + } else if (caretNode) { 255.11054 + prevNode = caretNode.previousSibling; 255.11055 + nextNode = caretNode.nextSibling; 255.11056 + } 255.11057 + } else { 255.11058 + if (r[0].startOffset === 0 && r[0].startContainer.previousSibling) { 255.11059 + caretNode = r[0].startContainer.previousSibling; 255.11060 + if (caretNode.nodeType === 3) { 255.11061 + offset = caretNode.data.length; 255.11062 + } 255.11063 + } else { 255.11064 + caretNode = r[0].startContainer; 255.11065 + offset = r[0].startOffset; 255.11066 + } 255.11067 + prevNode = caretNode.previousSibling; 255.11068 + nextNode = caretNode.nextSibling; 255.11069 + } 255.11070 + 255.11071 + return { 255.11072 + "caretNode": caretNode, 255.11073 + "prevNode": prevNode, 255.11074 + "nextNode": nextNode, 255.11075 + "textOffset": offset 255.11076 + }; 255.11077 + } 255.11078 + 255.11079 + return null; 255.11080 + }, 255.11081 + 255.11082 + getSelectionParentsByTag: function(tagName) { 255.11083 + var nodes = this.getSelectedOwnNodes(), 255.11084 + curEl, parents = []; 255.11085 + 255.11086 + for (var i = 0, maxi = nodes.length; i < maxi; i++) { 255.11087 + curEl = (nodes[i].nodeName && nodes[i].nodeName === 'LI') ? nodes[i] : wysihtml.dom.getParentElement(nodes[i], { query: 'li'}, false, this.contain); 255.11088 + if (curEl) { 255.11089 + parents.push(curEl); 255.11090 + } 255.11091 + } 255.11092 + return (parents.length) ? parents : null; 255.11093 + }, 255.11094 + 255.11095 + getRangeToNodeEnd: function() { 255.11096 + if (this.isCollapsed()) { 255.11097 + var range = this.getRange(), 255.11098 + sNode, pos, lastR; 255.11099 + if (range) { 255.11100 + sNode = range.startContainer; 255.11101 + pos = range.startOffset; 255.11102 + lastR = rangy.createRange(this.doc); 255.11103 + 255.11104 + lastR.selectNodeContents(sNode); 255.11105 + lastR.setStart(sNode, pos); 255.11106 + return lastR; 255.11107 + } 255.11108 + } 255.11109 + }, 255.11110 + 255.11111 + getRangeToNodeBeginning: function() { 255.11112 + if (this.isCollapsed()) { 255.11113 + var range = this.getRange(), 255.11114 + sNode = range.startContainer, 255.11115 + pos = range.startOffset, 255.11116 + lastR = rangy.createRange(this.doc); 255.11117 + 255.11118 + lastR.selectNodeContents(sNode); 255.11119 + lastR.setEnd(sNode, pos); 255.11120 + return lastR; 255.11121 + } 255.11122 + }, 255.11123 + 255.11124 + // This function returns if caret is last in a node (no textual visible content follows) 255.11125 + caretIsInTheEndOfNode: function(ignoreIfSpaceIsBeforeCaret) { 255.11126 + var r = rangy.createRange(this.doc), 255.11127 + s = this.getSelection(), 255.11128 + rangeToNodeEnd = this.getRangeToNodeEnd(), 255.11129 + endc, endtxt, beginc, begintxt; 255.11130 + 255.11131 + if (rangeToNodeEnd) { 255.11132 + endc = rangeToNodeEnd.cloneContents(); 255.11133 + endtxt = endc.textContent; 255.11134 + 255.11135 + if ((/^\s*$/).test(endtxt)) { 255.11136 + if (ignoreIfSpaceIsBeforeCaret) { 255.11137 + beginc = this.getRangeToNodeBeginning().cloneContents(); 255.11138 + begintxt = beginc.textContent; 255.11139 + return !(/[\u00A0 ][\s\uFEFF]*$/).test(begintxt); 255.11140 + } else { 255.11141 + return true; 255.11142 + } 255.11143 + } else { 255.11144 + return false; 255.11145 + } 255.11146 + } else { 255.11147 + return false; 255.11148 + } 255.11149 + }, 255.11150 + 255.11151 + caretIsFirstInSelection: function(includeLineBreaks) { 255.11152 + var r = rangy.createRange(this.doc), 255.11153 + s = this.getSelection(), 255.11154 + range = this.getRange(), 255.11155 + startNode = getRangeNode(range.startContainer, range.startOffset); 255.11156 + 255.11157 + if (startNode) { 255.11158 + if (startNode.nodeType === wysihtml.TEXT_NODE) { 255.11159 + if (!startNode.parentNode) { 255.11160 + return false; 255.11161 + } 255.11162 + if (!this.isCollapsed() || (startNode.parentNode.firstChild !== startNode && !wysihtml.dom.domNode(startNode.previousSibling).is.block())) { 255.11163 + return false; 255.11164 + } 255.11165 + var ws = this.win.getComputedStyle(startNode.parentNode).whiteSpace; 255.11166 + return (ws === "pre" || ws === "pre-wrap") ? range.startOffset === 0 : (/^\s*$/).test(startNode.data.substr(0,range.startOffset)); 255.11167 + } else if (includeLineBreaks && wysihtml.dom.domNode(startNode).is.lineBreak()) { 255.11168 + return true; 255.11169 + } else { 255.11170 + r.selectNodeContents(this.getRange().commonAncestorContainer); 255.11171 + r.collapse(true); 255.11172 + return (this.isCollapsed() && (r.startContainer === s.anchorNode || r.endContainer === s.anchorNode) && r.startOffset === s.anchorOffset); 255.11173 + } 255.11174 + } 255.11175 + }, 255.11176 + 255.11177 + caretIsInTheBeginnig: function(ofNode) { 255.11178 + var selection = this.getSelection(), 255.11179 + node = selection.anchorNode, 255.11180 + offset = selection.anchorOffset; 255.11181 + if (ofNode && node) { 255.11182 + return (offset === 0 && (node.nodeName && node.nodeName === ofNode.toUpperCase() || wysihtml.dom.getParentElement(node.parentNode, { query: ofNode }, 1))); 255.11183 + } else if (node) { 255.11184 + return (offset === 0 && !this.getPreviousNode(node, true)); 255.11185 + } 255.11186 + }, 255.11187 + 255.11188 + // Returns object describing node/text before selection 255.11189 + // If includePrevLeaves is true returns also previous last leaf child if selection is in the beginning of current node 255.11190 + getBeforeSelection: function(includePrevLeaves) { 255.11191 + var sel = this.getSelection(), 255.11192 + startNode = (sel.isBackwards()) ? sel.focusNode : sel.anchorNode, 255.11193 + startOffset = (sel.isBackwards()) ? sel.focusOffset : sel.anchorOffset, 255.11194 + rng = this.createRange(), endNode, inTmpCaret; 255.11195 + 255.11196 + // If start is textnode and all is whitespace before caret. Set start offset to 0 255.11197 + if (startNode && startNode.nodeType === 3 && (/^\s*$/).test(startNode.data.slice(0, startOffset))) { 255.11198 + startOffset = 0; 255.11199 + } 255.11200 + 255.11201 + // Escape temproray helper nodes if selection in them 255.11202 + inTmpCaret = wysihtml.dom.getParentElement(startNode, { query: '._wysihtml-temp-caret-fix' }, 1); 255.11203 + if (inTmpCaret) { 255.11204 + startNode = inTmpCaret.parentNode; 255.11205 + startOffset = Array.prototype.indexOf.call(startNode.childNodes, inTmpCaret); 255.11206 + } 255.11207 + 255.11208 + if (startNode) { 255.11209 + if (startOffset > 0) { 255.11210 + if (startNode.nodeType === 3) { 255.11211 + rng.setStart(startNode, 0); 255.11212 + rng.setEnd(startNode, startOffset); 255.11213 + return { 255.11214 + type: "text", 255.11215 + range: rng, 255.11216 + offset : startOffset, 255.11217 + node: startNode 255.11218 + }; 255.11219 + } else { 255.11220 + rng.setStartBefore(startNode.childNodes[0]); 255.11221 + endNode = startNode.childNodes[startOffset - 1]; 255.11222 + rng.setEndAfter(endNode); 255.11223 + return { 255.11224 + type: "element", 255.11225 + range: rng, 255.11226 + offset : startOffset, 255.11227 + node: endNode 255.11228 + }; 255.11229 + } 255.11230 + } else { 255.11231 + rng.setStartAndEnd(startNode, 0); 255.11232 + 255.11233 + if (includePrevLeaves) { 255.11234 + var prevNode = this.getPreviousNode(startNode, true), 255.11235 + prevLeaf = null; 255.11236 + 255.11237 + if(prevNode) { 255.11238 + if (prevNode.nodeType === 1 && wysihtml.dom.hasClass(prevNode, this.unselectableClass)) { 255.11239 + prevLeaf = prevNode; 255.11240 + } else { 255.11241 + prevLeaf = wysihtml.dom.domNode(prevNode).lastLeafNode(); 255.11242 + } 255.11243 + } 255.11244 + 255.11245 + if (prevLeaf) { 255.11246 + return { 255.11247 + type: "leafnode", 255.11248 + range: rng, 255.11249 + offset : startOffset, 255.11250 + node: prevLeaf 255.11251 + }; 255.11252 + } 255.11253 + } 255.11254 + 255.11255 + return { 255.11256 + type: "none", 255.11257 + range: rng, 255.11258 + offset : startOffset, 255.11259 + node: startNode 255.11260 + }; 255.11261 + } 255.11262 + } 255.11263 + return null; 255.11264 + }, 255.11265 + 255.11266 + // TODO: Figure out a method from following 2 that would work universally 255.11267 + executeAndRestoreRangy: function(method, restoreScrollPosition) { 255.11268 + var sel = rangy.saveSelection(this.win); 255.11269 + if (!sel) { 255.11270 + method(); 255.11271 + } else { 255.11272 + try { 255.11273 + method(); 255.11274 + } catch(e) { 255.11275 + setTimeout(function() { throw e; }, 0); 255.11276 + } 255.11277 + } 255.11278 + rangy.restoreSelection(sel); 255.11279 + }, 255.11280 + 255.11281 + // TODO: has problems in chrome 12. investigate block level and uneditable area inbetween 255.11282 + executeAndRestore: function(method, restoreScrollPosition) { 255.11283 + var body = this.doc.body, 255.11284 + oldScrollTop = restoreScrollPosition && body.scrollTop, 255.11285 + oldScrollLeft = restoreScrollPosition && body.scrollLeft, 255.11286 + className = "_wysihtml-temp-placeholder", 255.11287 + placeholderHtml = '<span class="' + className + '">' + wysihtml.INVISIBLE_SPACE + '</span>', 255.11288 + range = this.getRange(true), 255.11289 + caretPlaceholder, 255.11290 + newCaretPlaceholder, 255.11291 + nextSibling, prevSibling, 255.11292 + node, node2, range2, 255.11293 + newRange; 255.11294 + 255.11295 + // Nothing selected, execute and say goodbye 255.11296 + if (!range) { 255.11297 + method(body, body); 255.11298 + return; 255.11299 + } 255.11300 + 255.11301 + if (!range.collapsed) { 255.11302 + range2 = range.cloneRange(); 255.11303 + node2 = range2.createContextualFragment(placeholderHtml); 255.11304 + range2.collapse(false); 255.11305 + range2.insertNode(node2); 255.11306 + range2.detach(); 255.11307 + } 255.11308 + 255.11309 + node = range.createContextualFragment(placeholderHtml); 255.11310 + range.insertNode(node); 255.11311 + 255.11312 + if (node2) { 255.11313 + caretPlaceholder = this.contain.querySelectorAll("." + className); 255.11314 + range.setStartBefore(caretPlaceholder[0]); 255.11315 + range.setEndAfter(caretPlaceholder[caretPlaceholder.length -1]); 255.11316 + } 255.11317 + this.setSelection(range); 255.11318 + 255.11319 + // Make sure that a potential error doesn't cause our placeholder element to be left as a placeholder 255.11320 + try { 255.11321 + method(range.startContainer, range.endContainer); 255.11322 + } catch(e) { 255.11323 + setTimeout(function() { throw e; }, 0); 255.11324 + } 255.11325 + caretPlaceholder = this.contain.querySelectorAll("." + className); 255.11326 + if (caretPlaceholder && caretPlaceholder.length) { 255.11327 + newRange = rangy.createRange(this.doc); 255.11328 + nextSibling = caretPlaceholder[0].nextSibling; 255.11329 + if (caretPlaceholder.length > 1) { 255.11330 + prevSibling = caretPlaceholder[caretPlaceholder.length -1].previousSibling; 255.11331 + } 255.11332 + if (prevSibling && nextSibling) { 255.11333 + newRange.setStartBefore(nextSibling); 255.11334 + newRange.setEndAfter(prevSibling); 255.11335 + } else { 255.11336 + newCaretPlaceholder = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE); 255.11337 + dom.insert(newCaretPlaceholder).after(caretPlaceholder[0]); 255.11338 + newRange.setStartBefore(newCaretPlaceholder); 255.11339 + newRange.setEndAfter(newCaretPlaceholder); 255.11340 + } 255.11341 + this.setSelection(newRange); 255.11342 + for (var i = caretPlaceholder.length; i--;) { 255.11343 + caretPlaceholder[i].parentNode.removeChild(caretPlaceholder[i]); 255.11344 + } 255.11345 + 255.11346 + } else { 255.11347 + // fallback for when all hell breaks loose 255.11348 + this.contain.focus(); 255.11349 + } 255.11350 + 255.11351 + if (restoreScrollPosition) { 255.11352 + body.scrollTop = oldScrollTop; 255.11353 + body.scrollLeft = oldScrollLeft; 255.11354 + } 255.11355 + 255.11356 + // Remove it again, just to make sure that the placeholder is definitely out of the dom tree 255.11357 + try { 255.11358 + caretPlaceholder.parentNode.removeChild(caretPlaceholder); 255.11359 + } catch(e2) {} 255.11360 + }, 255.11361 + 255.11362 + set: function(node, offset) { 255.11363 + var newRange = rangy.createRange(this.doc); 255.11364 + newRange.setStart(node, offset || 0); 255.11365 + this.setSelection(newRange); 255.11366 + }, 255.11367 + 255.11368 + /** 255.11369 + * Insert html at the caret or selection position and move the cursor after the inserted html 255.11370 + * Replaces selection content if present 255.11371 + * 255.11372 + * @param {String} html HTML string to insert 255.11373 + * @example 255.11374 + * selection.insertHTML("<p>foobar</p>"); 255.11375 + */ 255.11376 + insertHTML: function(html) { 255.11377 + var range = this.getRange(), 255.11378 + node = this.doc.createElement('DIV'), 255.11379 + fragment = this.doc.createDocumentFragment(), 255.11380 + lastChild, lastEditorElement; 255.11381 + 255.11382 + if (range) { 255.11383 + range.deleteContents(); 255.11384 + node.innerHTML = html; 255.11385 + lastChild = node.lastChild; 255.11386 + 255.11387 + while (node.firstChild) { 255.11388 + fragment.appendChild(node.firstChild); 255.11389 + } 255.11390 + range.insertNode(fragment); 255.11391 + 255.11392 + lastEditorElement = this.contain.lastChild; 255.11393 + while (lastEditorElement && lastEditorElement.nodeType === 3 && lastEditorElement.previousSibling && (/^\s*$/).test(lastEditorElement.data)) { 255.11394 + lastEditorElement = lastEditorElement.previousSibling; 255.11395 + } 255.11396 + 255.11397 + if (lastChild) { 255.11398 + // fixes some pad cases mostly on webkit where last nr is needed 255.11399 + if (lastEditorElement && lastChild === lastEditorElement && lastChild.nodeType === 1) { 255.11400 + this.contain.appendChild(this.doc.createElement('br')); 255.11401 + } 255.11402 + this.setAfter(lastChild); 255.11403 + } 255.11404 + } 255.11405 + }, 255.11406 + 255.11407 + /** 255.11408 + * Insert a node at the caret position and move the cursor behind it 255.11409 + * 255.11410 + * @param {Object} node HTML string to insert 255.11411 + * @example 255.11412 + * selection.insertNode(document.createTextNode("foobar")); 255.11413 + */ 255.11414 + insertNode: function(node) { 255.11415 + var range = this.getRange(); 255.11416 + if (range) { 255.11417 + range.deleteContents(); 255.11418 + range.insertNode(node); 255.11419 + } 255.11420 + }, 255.11421 + 255.11422 + canAppendChild: function (node) { 255.11423 + var anchorNode, anchorNodeTagNameLower, 255.11424 + voidElements = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"], 255.11425 + range = this.getRange(); 255.11426 + 255.11427 + anchorNode = node || range.startContainer; 255.11428 + 255.11429 + if (anchorNode) { 255.11430 + anchorNodeTagNameLower = (anchorNode.tagName || anchorNode.nodeName).toLowerCase(); 255.11431 + } 255.11432 + 255.11433 + return voidElements.indexOf(anchorNodeTagNameLower) === -1; 255.11434 + }, 255.11435 + 255.11436 + splitElementAtCaret: function (element, insertNode) { 255.11437 + var sel = this.getSelection(), 255.11438 + range, contentAfterRangeStart, 255.11439 + firstChild, lastChild, childNodes; 255.11440 + 255.11441 + if (sel.rangeCount > 0) { 255.11442 + range = sel.getRangeAt(0).cloneRange(); // Create a copy of the selection range to work with 255.11443 + 255.11444 + range.setEndAfter(element); // Place the end of the range after the element 255.11445 + contentAfterRangeStart = range.extractContents(); // Extract the contents of the element after the caret into a fragment 255.11446 + 255.11447 + childNodes = contentAfterRangeStart.childNodes; 255.11448 + 255.11449 + // Empty elements are cleaned up from extracted content 255.11450 + for (var i = childNodes.length; i --;) { 255.11451 + if (!wysihtml.dom.domNode(childNodes[i]).is.visible()) { 255.11452 + contentAfterRangeStart.removeChild(childNodes[i]); 255.11453 + } 255.11454 + } 255.11455 + 255.11456 + element.parentNode.insertBefore(contentAfterRangeStart, element.nextSibling); 255.11457 + 255.11458 + if (insertNode) { 255.11459 + firstChild = insertNode.firstChild || insertNode; 255.11460 + lastChild = insertNode.lastChild || insertNode; 255.11461 + 255.11462 + element.parentNode.insertBefore(insertNode, element.nextSibling); 255.11463 + 255.11464 + // Select inserted node contents 255.11465 + if (firstChild && lastChild) { 255.11466 + range.setStartBefore(firstChild); 255.11467 + range.setEndAfter(lastChild); 255.11468 + this.setSelection(range); 255.11469 + } 255.11470 + } else { 255.11471 + range.setStartAfter(element); 255.11472 + range.setEndAfter(element); 255.11473 + } 255.11474 + 255.11475 + if (!wysihtml.dom.domNode(element).is.visible()) { 255.11476 + if (wysihtml.dom.getTextContent(element) === '') { 255.11477 + element.parentNode.removeChild(element); 255.11478 + } else { 255.11479 + element.parentNode.replaceChild(this.doc.createTextNode(" "), element); 255.11480 + } 255.11481 + } 255.11482 + 255.11483 + 255.11484 + } 255.11485 + }, 255.11486 + 255.11487 + /** 255.11488 + * Wraps current selection with the given node 255.11489 + * 255.11490 + * @param {Object} node The node to surround the selected elements with 255.11491 + */ 255.11492 + surround: function(nodeOptions) { 255.11493 + var ranges = this.getOwnRanges(), 255.11494 + node, nodes = []; 255.11495 + if (ranges.length == 0) { 255.11496 + return nodes; 255.11497 + } 255.11498 + 255.11499 + for (var i = ranges.length; i--;) { 255.11500 + node = this.doc.createElement(nodeOptions.nodeName); 255.11501 + nodes.push(node); 255.11502 + if (nodeOptions.className) { 255.11503 + node.className = nodeOptions.className; 255.11504 + } 255.11505 + if (nodeOptions.cssStyle) { 255.11506 + node.setAttribute('style', nodeOptions.cssStyle); 255.11507 + } 255.11508 + try { 255.11509 + // This only works when the range boundaries are not overlapping other elements 255.11510 + ranges[i].surroundContents(node); 255.11511 + this.selectNode(node); 255.11512 + } catch(e) { 255.11513 + // fallback 255.11514 + node.appendChild(ranges[i].extractContents()); 255.11515 + ranges[i].insertNode(node); 255.11516 + } 255.11517 + } 255.11518 + return nodes; 255.11519 + }, 255.11520 + 255.11521 + /** 255.11522 + * Scroll the current caret position into the view 255.11523 + * FIXME: This is a bit hacky, there might be a smarter way of doing this 255.11524 + * 255.11525 + * @example 255.11526 + * selection.scrollIntoView(); 255.11527 + */ 255.11528 + scrollIntoView: function() { 255.11529 + var doc = this.doc, 255.11530 + tolerance = 5, // px 255.11531 + hasScrollBars = doc.documentElement.scrollHeight > doc.documentElement.offsetHeight, 255.11532 + tempElement = doc._wysihtmlScrollIntoViewElement = doc._wysihtmlScrollIntoViewElement || (function() { 255.11533 + var element = doc.createElement("span"); 255.11534 + // The element needs content in order to be able to calculate it's position properly 255.11535 + element.innerHTML = wysihtml.INVISIBLE_SPACE; 255.11536 + return element; 255.11537 + })(), 255.11538 + offsetTop; 255.11539 + 255.11540 + if (hasScrollBars) { 255.11541 + this.insertNode(tempElement); 255.11542 + offsetTop = _getCumulativeOffsetTop(tempElement); 255.11543 + tempElement.parentNode.removeChild(tempElement); 255.11544 + if (offsetTop >= (doc.body.scrollTop + doc.documentElement.offsetHeight - tolerance)) { 255.11545 + doc.body.scrollTop = offsetTop; 255.11546 + } 255.11547 + } 255.11548 + }, 255.11549 + 255.11550 + /** 255.11551 + * Select line where the caret is in 255.11552 + */ 255.11553 + selectLine: function() { 255.11554 + var r = rangy.createRange(); 255.11555 + if (wysihtml.browser.supportsSelectionModify()) { 255.11556 + this._selectLine_W3C(); 255.11557 + } else if (r.nativeRange && r.nativeRange.getBoundingClientRect) { 255.11558 + // For IE Edge as it ditched the old api and did not fully implement the new one (as expected) 255.11559 + this._selectLineUniversal(); 255.11560 + } 255.11561 + }, 255.11562 + 255.11563 + includeRangyRangeHelpers: function() { 255.11564 + var s = this.getSelection(), 255.11565 + r = s.getRangeAt(0), 255.11566 + isHelperNode = function(node) { 255.11567 + return (node && node.nodeType === 1 && node.classList.contains('rangySelectionBoundary')); 255.11568 + }, 255.11569 + getNodeLength = function (node) { 255.11570 + if (node.nodeType === 1) { 255.11571 + return node.childNodes && node.childNodes.length || 0; 255.11572 + } else { 255.11573 + return node.data && node.data.length || 0; 255.11574 + } 255.11575 + }, 255.11576 + anode = s.anchorNode.nodeType === 1 ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode, 255.11577 + fnode = s.focusNode.nodeType === 1 ? s.focusNode.childNodes[s.focusOffset] : s.focusNode; 255.11578 + 255.11579 + if (fnode && s.focusOffset === getNodeLength(fnode) && fnode.nextSibling && isHelperNode(fnode.nextSibling)) { 255.11580 + r.setEndAfter(fnode.nextSibling); 255.11581 + } 255.11582 + if (anode && s.anchorOffset === 0 && anode.previousSibling && isHelperNode(anode.previousSibling)) { 255.11583 + r.setStartBefore(anode.previousSibling); 255.11584 + } 255.11585 + r.select(); 255.11586 + }, 255.11587 + 255.11588 + /** 255.11589 + * See https://developer.mozilla.org/en/DOM/Selection/modify 255.11590 + */ 255.11591 + _selectLine_W3C: function() { 255.11592 + var selection = this.win.getSelection(), 255.11593 + initialBoundry = [selection.anchorNode, selection.anchorOffset, selection.focusNode, selection.focusOffset]; 255.11594 + 255.11595 + selection.modify("move", "left", "lineboundary"); 255.11596 + selection.modify("extend", "right", "lineboundary"); 255.11597 + 255.11598 + // IF lineboundary extending did not change selection try universal fallback (FF fails sometimes without a reason) 255.11599 + if (selection.anchorNode === initialBoundry[0] && 255.11600 + selection.anchorOffset === initialBoundry[1] && 255.11601 + selection.focusNode === initialBoundry[2] && 255.11602 + selection.focusOffset === initialBoundry[3] 255.11603 + ) { 255.11604 + this._selectLineUniversal(); 255.11605 + } else { 255.11606 + this.includeRangyRangeHelpers(); 255.11607 + } 255.11608 + }, 255.11609 + 255.11610 + // collapses selection to current line beginning or end 255.11611 + toLineBoundary: function (location, collapse) { 255.11612 + collapse = (typeof collapse === 'undefined') ? false : collapse; 255.11613 + if (wysihtml.browser.supportsSelectionModify()) { 255.11614 + var selection = this.win.getSelection(); 255.11615 + 255.11616 + selection.modify("extend", location, "lineboundary"); 255.11617 + if (collapse) { 255.11618 + if (location === "left") { 255.11619 + selection.collapseToStart(); 255.11620 + } else if (location === "right") { 255.11621 + selection.collapseToEnd(); 255.11622 + } 255.11623 + } 255.11624 + } 255.11625 + }, 255.11626 + 255.11627 + getRangeRect: function(r) { 255.11628 + var textNode = this.doc.createTextNode("i"), 255.11629 + testNode = this.doc.createTextNode("i"), 255.11630 + rect, cr; 255.11631 + 255.11632 + /*testNode.style.visibility = "hidden"; 255.11633 + testNode.style.width = "0px"; 255.11634 + testNode.style.display = "inline-block"; 255.11635 + testNode.style.overflow = "hidden"; 255.11636 + testNode.appendChild(textNode);*/ 255.11637 + 255.11638 + if (r.collapsed) { 255.11639 + r.insertNode(testNode); 255.11640 + r.selectNode(testNode); 255.11641 + rect = r.nativeRange.getBoundingClientRect(); 255.11642 + r.deleteContents(); 255.11643 + 255.11644 + } else { 255.11645 + rect = r.nativeRange.getBoundingClientRect(); 255.11646 + } 255.11647 + 255.11648 + return rect; 255.11649 + 255.11650 + }, 255.11651 + 255.11652 + _selectLineUniversal: function() { 255.11653 + var s = this.getSelection(), 255.11654 + r = s.getRangeAt(0), 255.11655 + rect, 255.11656 + startRange, endRange, testRange, 255.11657 + count = 0, 255.11658 + amount, testRect, found, 255.11659 + that = this, 255.11660 + isLineBreakingElement = function(el) { 255.11661 + return el && el.nodeType === 1 && (that.win.getComputedStyle(el).display === "block" || wysihtml.lang.array(['BR', 'HR']).contains(el.nodeName)); 255.11662 + }, 255.11663 + prevNode = function(node) { 255.11664 + var pnode = node; 255.11665 + if (pnode) { 255.11666 + while (pnode && ((pnode.nodeType === 1 && pnode.classList.contains('rangySelectionBoundary')) || (pnode.nodeType === 3 && (/^\s*$/).test(pnode.data)))) { 255.11667 + pnode = pnode.previousSibling; 255.11668 + } 255.11669 + } 255.11670 + return pnode; 255.11671 + }; 255.11672 + 255.11673 + startRange = r.cloneRange(); 255.11674 + endRange = r.cloneRange(); 255.11675 + 255.11676 + if (r.collapsed) { 255.11677 + // Collapsed state can not have a bounding rect. Thus need to expand it at least by 1 character first while not crossing line boundary 255.11678 + // TODO: figure out a shorter and more readable way 255.11679 + if (r.startContainer.nodeType === 3 && r.startOffset < r.startContainer.data.length) { 255.11680 + r.moveEnd('character', 1); 255.11681 + } else if (r.startContainer.nodeType === 1 && r.startContainer.childNodes[r.startOffset] && r.startContainer.childNodes[r.startOffset].nodeType === 3 && r.startContainer.childNodes[r.startOffset].data.length > 0) { 255.11682 + r.moveEnd('character', 1); 255.11683 + } else if ( 255.11684 + r.startOffset > 0 && 255.11685 + ( 255.11686 + r.startContainer.nodeType === 3 || 255.11687 + ( 255.11688 + r.startContainer.nodeType === 1 && 255.11689 + !isLineBreakingElement(prevNode(r.startContainer.childNodes[r.startOffset - 1])) 255.11690 + ) 255.11691 + ) 255.11692 + ) { 255.11693 + r.moveStart('character', -1); 255.11694 + } 255.11695 + } 255.11696 + if (!r.collapsed) { 255.11697 + r.insertNode(this.doc.createTextNode(wysihtml.INVISIBLE_SPACE)); 255.11698 + } 255.11699 + 255.11700 + // Is probably just empty line as can not be expanded 255.11701 + rect = r.nativeRange.getBoundingClientRect(); 255.11702 + // If startnode is not line break allready move the start position of range by -1 character until clientRect top changes; 255.11703 + do { 255.11704 + amount = r.moveStart('character', -1); 255.11705 + testRect = r.nativeRange.getBoundingClientRect(); 255.11706 + 255.11707 + if (!testRect || Math.floor(testRect.top) !== Math.floor(rect.top)) { 255.11708 + r.moveStart('character', 1); 255.11709 + found = true; 255.11710 + } 255.11711 + count++; 255.11712 + } while (amount !== 0 && !found && count < 2000); 255.11713 + count = 0; 255.11714 + found = false; 255.11715 + rect = r.nativeRange.getBoundingClientRect(); 255.11716 + 255.11717 + if (r.endContainer !== this.contain || (this.contain.lastChild && this.contain.childNodes[r.endOffset] !== this.contain.lastChild)) { 255.11718 + do { 255.11719 + amount = r.moveEnd('character', 1); 255.11720 + testRect = r.nativeRange.getBoundingClientRect(); 255.11721 + if (!testRect || Math.floor(testRect.bottom) !== Math.floor(rect.bottom)) { 255.11722 + r.moveEnd('character', -1); 255.11723 + 255.11724 + // Fix a IE line end marked by linebreak element although caret is before it 255.11725 + // If causes problems should be changed to be applied only to IE 255.11726 + if (r.endContainer && r.endContainer.nodeType === 1 && r.endContainer.childNodes[r.endOffset] && r.endContainer.childNodes[r.endOffset].nodeType === 1 && r.endContainer.childNodes[r.endOffset].nodeName === "BR" && r.endContainer.childNodes[r.endOffset].previousSibling) { 255.11727 + if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 1) { 255.11728 + r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.childNodes.length); 255.11729 + } else if (r.endContainer.childNodes[r.endOffset].previousSibling.nodeType === 3) { 255.11730 + r.setEnd(r.endContainer.childNodes[r.endOffset].previousSibling, r.endContainer.childNodes[r.endOffset].previousSibling.data.length); 255.11731 + } 255.11732 + } 255.11733 + found = true; 255.11734 + } 255.11735 + count++; 255.11736 + } while (amount !== 0 && !found && count < 2000); 255.11737 + } 255.11738 + r.select(); 255.11739 + this.includeRangyRangeHelpers(); 255.11740 + }, 255.11741 + 255.11742 + getText: function() { 255.11743 + var selection = this.getSelection(); 255.11744 + return selection ? selection.toString() : ""; 255.11745 + }, 255.11746 + 255.11747 + getNodes: function(nodeType, filter) { 255.11748 + var range = this.getRange(); 255.11749 + if (range) { 255.11750 + return range.getNodes(Array.isArray(nodeType) ? nodeType : [nodeType], filter); 255.11751 + } else { 255.11752 + return []; 255.11753 + } 255.11754 + }, 255.11755 + 255.11756 + // Gets all the elements in selection with nodeType 255.11757 + // Ignores the elements not belonging to current editable area 255.11758 + // If filter is defined nodes must pass the filter function with true to be included in list 255.11759 + getOwnNodes: function(nodeType, filter, splitBounds) { 255.11760 + var ranges = this.getOwnRanges(), 255.11761 + nodes = []; 255.11762 + for (var r = 0, rmax = ranges.length; r < rmax; r++) { 255.11763 + if (ranges[r]) { 255.11764 + if (splitBounds) { 255.11765 + ranges[r].splitBoundaries(); 255.11766 + } 255.11767 + nodes = nodes.concat(ranges[r].getNodes(Array.isArray(nodeType) ? nodeType : [nodeType], filter)); 255.11768 + } 255.11769 + } 255.11770 + 255.11771 + return nodes; 255.11772 + }, 255.11773 + 255.11774 + fixRangeOverflow: function(range) { 255.11775 + if (this.contain && this.contain.firstChild && range) { 255.11776 + var containment = range.compareNode(this.contain); 255.11777 + if (containment !== 2) { 255.11778 + if (containment === 1) { 255.11779 + range.setStartBefore(this.contain.firstChild); 255.11780 + } 255.11781 + if (containment === 0) { 255.11782 + range.setEndAfter(this.contain.lastChild); 255.11783 + } 255.11784 + if (containment === 3) { 255.11785 + range.setStartBefore(this.contain.firstChild); 255.11786 + range.setEndAfter(this.contain.lastChild); 255.11787 + } 255.11788 + } else if (this._detectInlineRangeProblems(range)) { 255.11789 + var previousElementSibling = range.endContainer.previousElementSibling; 255.11790 + if (previousElementSibling) { 255.11791 + range.setEnd(previousElementSibling, this._endOffsetForNode(previousElementSibling)); 255.11792 + } 255.11793 + } 255.11794 + } 255.11795 + }, 255.11796 + 255.11797 + _endOffsetForNode: function(node) { 255.11798 + var range = document.createRange(); 255.11799 + range.selectNodeContents(node); 255.11800 + return range.endOffset; 255.11801 + }, 255.11802 + 255.11803 + _detectInlineRangeProblems: function(range) { 255.11804 + var position = dom.compareDocumentPosition(range.startContainer, range.endContainer); 255.11805 + return ( 255.11806 + range.endOffset == 0 && 255.11807 + position & 4 //Node.DOCUMENT_POSITION_FOLLOWING 255.11808 + ); 255.11809 + }, 255.11810 + 255.11811 + getRange: function(dontFix) { 255.11812 + var selection = this.getSelection(), 255.11813 + range = selection && selection.rangeCount && selection.getRangeAt(0); 255.11814 + 255.11815 + if (dontFix !== true) { 255.11816 + this.fixRangeOverflow(range); 255.11817 + } 255.11818 + 255.11819 + return range; 255.11820 + }, 255.11821 + 255.11822 + getOwnUneditables: function() { 255.11823 + var allUneditables = dom.query(this.contain, '.' + this.unselectableClass), 255.11824 + deepUneditables = dom.query(allUneditables, '.' + this.unselectableClass); 255.11825 + 255.11826 + return wysihtml.lang.array(allUneditables).without(deepUneditables); 255.11827 + }, 255.11828 + 255.11829 + // Returns an array of ranges that belong only to this editable 255.11830 + // Needed as uneditable block in contenteditabel can split range into pieces 255.11831 + // If manipulating content reverse loop is usually needed as manipulation can shift subsequent ranges 255.11832 + getOwnRanges: function() { 255.11833 + var ranges = [], 255.11834 + r = this.getRange(), 255.11835 + tmpRanges; 255.11836 + 255.11837 + if (r) { ranges.push(r); } 255.11838 + 255.11839 + if (this.unselectableClass && this.contain && r) { 255.11840 + var uneditables = this.getOwnUneditables(), 255.11841 + tmpRange; 255.11842 + if (uneditables.length > 0) { 255.11843 + for (var i = 0, imax = uneditables.length; i < imax; i++) { 255.11844 + tmpRanges = []; 255.11845 + for (var j = 0, jmax = ranges.length; j < jmax; j++) { 255.11846 + if (ranges[j]) { 255.11847 + switch (ranges[j].compareNode(uneditables[i])) { 255.11848 + case 2: 255.11849 + // all selection inside uneditable. remove 255.11850 + break; 255.11851 + case 3: 255.11852 + //section begins before and ends after uneditable. spilt 255.11853 + tmpRange = ranges[j].cloneRange(); 255.11854 + tmpRange.setEndBefore(uneditables[i]); 255.11855 + tmpRanges.push(tmpRange); 255.11856 + 255.11857 + tmpRange = ranges[j].cloneRange(); 255.11858 + tmpRange.setStartAfter(uneditables[i]); 255.11859 + tmpRanges.push(tmpRange); 255.11860 + break; 255.11861 + default: 255.11862 + // in all other cases uneditable does not touch selection. dont modify 255.11863 + tmpRanges.push(ranges[j]); 255.11864 + } 255.11865 + } 255.11866 + ranges = tmpRanges; 255.11867 + } 255.11868 + } 255.11869 + } 255.11870 + } 255.11871 + return ranges; 255.11872 + }, 255.11873 + 255.11874 + getSelection: function() { 255.11875 + return rangy.getSelection(this.win); 255.11876 + }, 255.11877 + 255.11878 + // Sets selection in document to a given range 255.11879 + // Set selection method detects if it fails to set any selection in document and returns null on fail 255.11880 + // (especially needed in webkit where some ranges just can not create selection for no reason) 255.11881 + setSelection: function(range) { 255.11882 + var selection = rangy.getSelection(this.win); 255.11883 + selection.setSingleRange(range); 255.11884 + return (selection && selection.anchorNode && selection.focusNode) ? selection : null; 255.11885 + }, 255.11886 + 255.11887 + 255.11888 + 255.11889 + // Webkit has an ancient error of not selecting all contents when uneditable block element is first or last in editable area 255.11890 + selectAll: function() { 255.11891 + var range = this.createRange(), 255.11892 + composer = this.composer, 255.11893 + that = this, 255.11894 + blankEndNode = getWebkitSelectionFixNode(this.composer.element), 255.11895 + blankStartNode = getWebkitSelectionFixNode(this.composer.element), 255.11896 + s; 255.11897 + 255.11898 + var doSelect = function() { 255.11899 + range.setStart(composer.element, 0); 255.11900 + range.setEnd(composer.element, composer.element.childNodes.length); 255.11901 + s = that.setSelection(range); 255.11902 + }; 255.11903 + 255.11904 + var notSelected = function() { 255.11905 + return !s || (s.nativeSelection && s.nativeSelection.type && (s.nativeSelection.type === "Caret" || s.nativeSelection.type === "None")); 255.11906 + } 255.11907 + 255.11908 + wysihtml.dom.removeInvisibleSpaces(this.composer.element); 255.11909 + doSelect(); 255.11910 + 255.11911 + if (this.composer.element.firstChild && notSelected()) { 255.11912 + // Try fixing end 255.11913 + this.composer.element.appendChild(blankEndNode); 255.11914 + doSelect(); 255.11915 + 255.11916 + if (notSelected()) { 255.11917 + // Remove end fix 255.11918 + blankEndNode.parentNode.removeChild(blankEndNode); 255.11919 + 255.11920 + // Try fixing beginning 255.11921 + this.composer.element.insertBefore(blankStartNode, this.composer.element.firstChild); 255.11922 + doSelect(); 255.11923 + 255.11924 + if (notSelected()) { 255.11925 + // Try fixing both 255.11926 + this.composer.element.appendChild(blankEndNode); 255.11927 + doSelect(); 255.11928 + } 255.11929 + } 255.11930 + } 255.11931 + }, 255.11932 + 255.11933 + createRange: function() { 255.11934 + return rangy.createRange(this.doc); 255.11935 + }, 255.11936 + 255.11937 + isCollapsed: function() { 255.11938 + return this.getSelection().isCollapsed; 255.11939 + }, 255.11940 + 255.11941 + getHtml: function() { 255.11942 + return this.getSelection().toHtml(); 255.11943 + }, 255.11944 + 255.11945 + getPlainText: function () { 255.11946 + return this.getSelection().toString(); 255.11947 + }, 255.11948 + 255.11949 + isEndToEndInNode: function(nodeNames) { 255.11950 + var range = this.getRange(), 255.11951 + parentElement = range.commonAncestorContainer, 255.11952 + startNode = range.startContainer, 255.11953 + endNode = range.endContainer; 255.11954 + 255.11955 + 255.11956 + if (parentElement.nodeType === wysihtml.TEXT_NODE) { 255.11957 + parentElement = parentElement.parentNode; 255.11958 + } 255.11959 + 255.11960 + if (startNode.nodeType === wysihtml.TEXT_NODE && !(/^\s*$/).test(startNode.data.substr(range.startOffset))) { 255.11961 + return false; 255.11962 + } 255.11963 + 255.11964 + if (endNode.nodeType === wysihtml.TEXT_NODE && !(/^\s*$/).test(endNode.data.substr(range.endOffset))) { 255.11965 + return false; 255.11966 + } 255.11967 + 255.11968 + while (startNode && startNode !== parentElement) { 255.11969 + if (startNode.nodeType !== wysihtml.TEXT_NODE && !wysihtml.dom.contains(parentElement, startNode)) { 255.11970 + return false; 255.11971 + } 255.11972 + if (wysihtml.dom.domNode(startNode).prev({ignoreBlankTexts: true})) { 255.11973 + return false; 255.11974 + } 255.11975 + startNode = startNode.parentNode; 255.11976 + } 255.11977 + 255.11978 + while (endNode && endNode !== parentElement) { 255.11979 + if (endNode.nodeType !== wysihtml.TEXT_NODE && !wysihtml.dom.contains(parentElement, endNode)) { 255.11980 + return false; 255.11981 + } 255.11982 + if (wysihtml.dom.domNode(endNode).next({ignoreBlankTexts: true})) { 255.11983 + return false; 255.11984 + } 255.11985 + endNode = endNode.parentNode; 255.11986 + } 255.11987 + 255.11988 + return (wysihtml.lang.array(nodeNames).contains(parentElement.nodeName)) ? parentElement : false; 255.11989 + }, 255.11990 + 255.11991 + isInThisEditable: function() { 255.11992 + var sel = this.getSelection(), 255.11993 + fnode = sel.focusNode, 255.11994 + anode = sel.anchorNode; 255.11995 + 255.11996 + // In IE node contains will not work for textnodes, thus taking parentNode 255.11997 + if (fnode && fnode.nodeType !== 1) { 255.11998 + fnode = fnode.parentNode; 255.11999 + } 255.12000 + 255.12001 + if (anode && anode.nodeType !== 1) { 255.12002 + anode = anode.parentNode; 255.12003 + } 255.12004 + 255.12005 + return anode && fnode && 255.12006 + (wysihtml.dom.contains(this.composer.element, fnode) || this.composer.element === fnode) && 255.12007 + (wysihtml.dom.contains(this.composer.element, anode) || this.composer.element === anode); 255.12008 + }, 255.12009 + 255.12010 + deselect: function() { 255.12011 + var sel = this.getSelection(); 255.12012 + sel && sel.removeAllRanges(); 255.12013 + } 255.12014 + }); 255.12015 + 255.12016 +})(wysihtml); 255.12017 + 255.12018 +/** 255.12019 + * Rich Text Query/Formatting Commands 255.12020 + * 255.12021 + * @example 255.12022 + * var commands = new wysihtml.Commands(editor); 255.12023 + */ 255.12024 +wysihtml.Commands = Base.extend( 255.12025 + /** @scope wysihtml.Commands.prototype */ { 255.12026 + constructor: function(editor) { 255.12027 + this.editor = editor; 255.12028 + this.composer = editor.composer; 255.12029 + this.doc = this.composer.doc; 255.12030 + }, 255.12031 + 255.12032 + /** 255.12033 + * Check whether the browser supports the given command 255.12034 + * 255.12035 + * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList") 255.12036 + * @example 255.12037 + * commands.supports("createLink"); 255.12038 + */ 255.12039 + support: function(command) { 255.12040 + return wysihtml.browser.supportsCommand(this.doc, command); 255.12041 + }, 255.12042 + 255.12043 + /** 255.12044 + * Check whether the browser supports the given command 255.12045 + * 255.12046 + * @param {String} command The command string which to execute (eg. "bold", "italic", "insertUnorderedList") 255.12047 + * @param {String} [value] The command value parameter, needed for some commands ("createLink", "insertImage", ...), optional for commands that don't require one ("bold", "underline", ...) 255.12048 + * @example 255.12049 + * commands.exec("insertImage", "http://a1.twimg.com/profile_images/113868655/schrei_twitter_reasonably_small.jpg"); 255.12050 + */ 255.12051 + exec: function(command, value) { 255.12052 + var obj = wysihtml.commands[command], 255.12053 + args = wysihtml.lang.array(arguments).get(), 255.12054 + method = obj && obj.exec, 255.12055 + result = null; 255.12056 + 255.12057 + // If composer ahs placeholder unset it before command 255.12058 + // Do not apply on commands that are behavioral 255.12059 + if (this.composer.hasPlaceholderSet() && !wysihtml.lang.array(['styleWithCSS', 'enableObjectResizing', 'enableInlineTableEditing']).contains(command)) { 255.12060 + this.composer.element.innerHTML = ""; 255.12061 + this.composer.selection.selectNode(this.composer.element); 255.12062 + } 255.12063 + 255.12064 + this.editor.fire("beforecommand:composer"); 255.12065 + 255.12066 + if (method) { 255.12067 + args.unshift(this.composer); 255.12068 + result = method.apply(obj, args); 255.12069 + } else { 255.12070 + try { 255.12071 + // try/catch for buggy firefox 255.12072 + result = this.doc.execCommand(command, false, value); 255.12073 + } catch(e) {} 255.12074 + } 255.12075 + 255.12076 + this.editor.fire("aftercommand:composer"); 255.12077 + return result; 255.12078 + }, 255.12079 + 255.12080 + remove: function(command, commandValue) { 255.12081 + var obj = wysihtml.commands[command], 255.12082 + args = wysihtml.lang.array(arguments).get(), 255.12083 + method = obj && obj.remove; 255.12084 + if (method) { 255.12085 + args.unshift(this.composer); 255.12086 + return method.apply(obj, args); 255.12087 + } 255.12088 + }, 255.12089 + 255.12090 + /** 255.12091 + * Check whether the current command is active 255.12092 + * If the caret is within a bold text, then calling this with command "bold" should return true 255.12093 + * 255.12094 + * @param {String} command The command string which to check (eg. "bold", "italic", "insertUnorderedList") 255.12095 + * @param {String} [commandValue] The command value parameter (eg. for "insertImage" the image src) 255.12096 + * @return {Boolean} Whether the command is active 255.12097 + * @example 255.12098 + * var isCurrentSelectionBold = commands.state("bold"); 255.12099 + */ 255.12100 + state: function(command, commandValue) { 255.12101 + var obj = wysihtml.commands[command], 255.12102 + args = wysihtml.lang.array(arguments).get(), 255.12103 + method = obj && obj.state; 255.12104 + if (method) { 255.12105 + args.unshift(this.composer); 255.12106 + return method.apply(obj, args); 255.12107 + } else { 255.12108 + try { 255.12109 + // try/catch for buggy firefox 255.12110 + return this.doc.queryCommandState(command); 255.12111 + } catch(e) { 255.12112 + return false; 255.12113 + } 255.12114 + } 255.12115 + }, 255.12116 + 255.12117 + /* Get command state parsed value if command has stateValue parsing function */ 255.12118 + stateValue: function(command) { 255.12119 + var obj = wysihtml.commands[command], 255.12120 + args = wysihtml.lang.array(arguments).get(), 255.12121 + method = obj && obj.stateValue; 255.12122 + if (method) { 255.12123 + args.unshift(this.composer); 255.12124 + return method.apply(obj, args); 255.12125 + } else { 255.12126 + return false; 255.12127 + } 255.12128 + } 255.12129 +}); 255.12130 + 255.12131 +(function(wysihtml) { 255.12132 + 255.12133 + var nodeOptions = { 255.12134 + nodeName: "A", 255.12135 + toggle: false 255.12136 + }; 255.12137 + 255.12138 + function getOptions(value) { 255.12139 + var options = typeof value === 'object' ? value : {'href': value}; 255.12140 + return wysihtml.lang.object({}).merge(nodeOptions).merge({'attribute': value}).get(); 255.12141 + } 255.12142 + 255.12143 + wysihtml.commands.createLink = { 255.12144 + exec: function(composer, command, value) { 255.12145 + var opts = getOptions(value); 255.12146 + 255.12147 + if (composer.selection.isCollapsed() && !this.state(composer, command)) { 255.12148 + var textNode = composer.doc.createTextNode(opts.attribute.href); 255.12149 + composer.selection.insertNode(textNode); 255.12150 + composer.selection.selectNode(textNode); 255.12151 + } 255.12152 + wysihtml.commands.formatInline.exec(composer, command, opts); 255.12153 + }, 255.12154 + 255.12155 + state: function(composer, command) { 255.12156 + return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 255.12157 + } 255.12158 + }; 255.12159 + 255.12160 +})(wysihtml); 255.12161 + 255.12162 +/* Formatblock 255.12163 + * Is used to insert block level elements 255.12164 + * It tries to solve the case that some block elements should not contain other block level elements (h1-6, p, ...) 255.12165 + * 255.12166 +*/ 255.12167 +(function(wysihtml) { 255.12168 + 255.12169 + var dom = wysihtml.dom, 255.12170 + // When the caret is within a H1 and the H4 is invoked, the H1 should turn into H4 255.12171 + // instead of creating a H4 within a H1 which would result in semantically invalid html 255.12172 + UNNESTABLE_BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre", 255.12173 + BLOCK_ELEMENTS = "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote", 255.12174 + INLINE_ELEMENTS = "b, big, i, small, tt, abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var, a, bdo, br, q, span, sub, sup, button, label, textarea, input, select, u"; 255.12175 + 255.12176 + function correctOptionsForSimilarityCheck(options) { 255.12177 + return { 255.12178 + nodeName: options.nodeName || null, 255.12179 + className: (!options.classRegExp) ? options.className || null : null, 255.12180 + classRegExp: options.classRegExp || null, 255.12181 + styleProperty: options.styleProperty || null 255.12182 + }; 255.12183 + } 255.12184 + 255.12185 + function getRangeNode(node, offset) { 255.12186 + if (node.nodeType === 3) { 255.12187 + return node; 255.12188 + } else { 255.12189 + return node.childNodes[offset] || node; 255.12190 + } 255.12191 + } 255.12192 + 255.12193 + // Returns if node is a line break 255.12194 + function isBr(n) { 255.12195 + return n && n.nodeType === 1 && n.nodeName === "BR"; 255.12196 + } 255.12197 + 255.12198 + // Is block level element 255.12199 + function isBlock(n, composer) { 255.12200 + return n && n.nodeType === 1 && composer.win.getComputedStyle(n).display === "block"; 255.12201 + } 255.12202 + 255.12203 + // Returns if node is the rangy selection bookmark element (that must not be taken into account in most situatons and is removed on selection restoring) 255.12204 + function isBookmark(n) { 255.12205 + return n && n.nodeType === 1 && n.classList.contains('rangySelectionBoundary'); 255.12206 + } 255.12207 + 255.12208 + // Is line breaking node 255.12209 + function isLineBreaking(n, composer) { 255.12210 + return isBr(n) || isBlock(n, composer); 255.12211 + } 255.12212 + 255.12213 + // Removes empty block level elements 255.12214 + function cleanup(composer, newBlockElements) { 255.12215 + wysihtml.dom.removeInvisibleSpaces(composer.element); 255.12216 + var container = composer.element, 255.12217 + allElements = container.querySelectorAll(BLOCK_ELEMENTS), 255.12218 + noEditQuery = composer.config.classNames.uneditableContainer + ([""]).concat(BLOCK_ELEMENTS.split(',')).join(", " + composer.config.classNames.uneditableContainer + ' '), 255.12219 + uneditables = container.querySelectorAll(noEditQuery), 255.12220 + elements = wysihtml.lang.array(allElements).without(uneditables), // Lets not touch uneditable elements and their contents 255.12221 + nbIdx; 255.12222 + 255.12223 + for (var i = elements.length; i--;) { 255.12224 + if (elements[i].innerHTML.replace(/[\uFEFF]/g, '') === "" && (newBlockElements.length === 0 || elements[i] !== newBlockElements[newBlockElements.length - 1])) { 255.12225 + // If cleanup removes some new block elements. remove them from newblocks array too 255.12226 + nbIdx = wysihtml.lang.array(newBlockElements).indexOf(elements[i]); 255.12227 + if (nbIdx > -1) { 255.12228 + newBlockElements.splice(nbIdx, 1); 255.12229 + } 255.12230 + elements[i].parentNode.removeChild(elements[i]); 255.12231 + } 255.12232 + } 255.12233 + 255.12234 + return newBlockElements; 255.12235 + } 255.12236 + 255.12237 + function defaultNodeName(composer) { 255.12238 + return composer.config.useLineBreaks ? "DIV" : "P"; 255.12239 + } 255.12240 + 255.12241 + // The outermost un-nestable block element parent of from node 255.12242 + function findOuterBlock(node, container, allBlocks) { 255.12243 + var n = node, 255.12244 + block = null; 255.12245 + 255.12246 + while (n && container && n !== container) { 255.12247 + if (n.nodeType === 1 && n.matches(allBlocks ? BLOCK_ELEMENTS : UNNESTABLE_BLOCK_ELEMENTS)) { 255.12248 + block = n; 255.12249 + } 255.12250 + n = n.parentNode; 255.12251 + } 255.12252 + 255.12253 + return block; 255.12254 + } 255.12255 + 255.12256 + // Clone for splitting the inner inline element out of its parent inline elements context 255.12257 + // For example if selection is in bold and italic, clone the outer nodes and wrap these around content and return 255.12258 + function cloneOuterInlines(node, container) { 255.12259 + var n = node, 255.12260 + innerNode, 255.12261 + parentNode, 255.12262 + el = null, 255.12263 + el2; 255.12264 + 255.12265 + while (n && container && n !== container) { 255.12266 + if (n.nodeType === 1 && n.matches(INLINE_ELEMENTS)) { 255.12267 + parentNode = n; 255.12268 + if (el === null) { 255.12269 + el = n.cloneNode(false); 255.12270 + innerNode = el; 255.12271 + } else { 255.12272 + el2 = n.cloneNode(false); 255.12273 + el2.appendChild(el); 255.12274 + el = el2; 255.12275 + } 255.12276 + } 255.12277 + n = n.parentNode; 255.12278 + } 255.12279 + 255.12280 + return { 255.12281 + parent: parentNode, 255.12282 + outerNode: el, 255.12283 + innerNode: innerNode 255.12284 + }; 255.12285 + } 255.12286 + 255.12287 + // Formats an element according to options nodeName, className, styleProperty, styleValue 255.12288 + // If element is not defined, creates new element 255.12289 + // if opotions is null, remove format instead 255.12290 + function applyOptionsToElement(element, options, composer) { 255.12291 + 255.12292 + if (!element) { 255.12293 + element = composer.doc.createElement(options.nodeName || defaultNodeName(composer)); 255.12294 + // Add invisible space as otherwise webkit cannot set selection or range to it correctly 255.12295 + element.appendChild(composer.doc.createTextNode(wysihtml.INVISIBLE_SPACE)); 255.12296 + } 255.12297 + 255.12298 + if (options.nodeName && element.nodeName !== options.nodeName) { 255.12299 + element = dom.renameElement(element, options.nodeName); 255.12300 + } 255.12301 + 255.12302 + // Remove similar classes before applying className 255.12303 + if (options.classRegExp) { 255.12304 + element.className = element.className.replace(options.classRegExp, ""); 255.12305 + } 255.12306 + if (options.className) { 255.12307 + element.classList.add(options.className); 255.12308 + } 255.12309 + 255.12310 + if (options.styleProperty && typeof options.styleValue !== "undefined") { 255.12311 + element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue; 255.12312 + } 255.12313 + 255.12314 + return element; 255.12315 + } 255.12316 + 255.12317 + // Unsets element properties by options 255.12318 + // If nodename given and matches current element, element is unwrapped or converted to default node (depending on presence of class and style attributes) 255.12319 + function removeOptionsFromElement(element, options, composer) { 255.12320 + var style, classes, 255.12321 + prevNode = element.previousSibling, 255.12322 + nextNode = element.nextSibling, 255.12323 + unwrapped = false; 255.12324 + 255.12325 + if (options.styleProperty) { 255.12326 + element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = ''; 255.12327 + } 255.12328 + if (options.className) { 255.12329 + element.classList.remove(options.className); 255.12330 + } 255.12331 + 255.12332 + if (options.classRegExp) { 255.12333 + element.className = element.className.replace(options.classRegExp, ""); 255.12334 + } 255.12335 + 255.12336 + // Clean up blank class attribute 255.12337 + if (element.getAttribute('class') !== null && element.getAttribute('class').trim() === "") { 255.12338 + element.removeAttribute('class'); 255.12339 + } 255.12340 + 255.12341 + if (options.nodeName && element.nodeName.toLowerCase() === options.nodeName.toLowerCase()) { 255.12342 + style = element.getAttribute('style'); 255.12343 + if (!style || style.trim() === '') { 255.12344 + dom.unwrap(element); 255.12345 + unwrapped = true; 255.12346 + } else { 255.12347 + element = dom.renameElement(element, defaultNodeName(composer)); 255.12348 + } 255.12349 + } 255.12350 + 255.12351 + // Clean up blank style attribute 255.12352 + if (element.getAttribute('style') !== null && element.getAttribute('style').trim() === "") { 255.12353 + element.removeAttribute('style'); 255.12354 + } 255.12355 + 255.12356 + if (unwrapped) { 255.12357 + applySurroundingLineBreaks(prevNode, nextNode, composer); 255.12358 + } 255.12359 + } 255.12360 + 255.12361 + // Unwraps block level elements from inside content 255.12362 + // Useful as not all block level elements can contain other block-levels 255.12363 + function unwrapBlocksFromContent(element) { 255.12364 + var blocks = element.querySelectorAll(BLOCK_ELEMENTS) || [], // Find unnestable block elements in extracted contents 255.12365 + nextEl, prevEl; 255.12366 + 255.12367 + for (var i = blocks.length; i--;) { 255.12368 + nextEl = wysihtml.dom.domNode(blocks[i]).next({nodeTypes: [1,3], ignoreBlankTexts: true}), 255.12369 + prevEl = wysihtml.dom.domNode(blocks[i]).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); 255.12370 + 255.12371 + if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') { 255.12372 + if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') { 255.12373 + blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl); 255.12374 + } 255.12375 + } 255.12376 + if (nextEl && nextEl.nodeType !== 1 && nextEl.nodeName !== 'BR') { 255.12377 + if ((blocks[i].innerHTML || blocks[i].nodeValue || '').trim() !== '') { 255.12378 + blocks[i].parentNode.insertBefore(blocks[i].ownerDocument.createElement('BR'), nextEl); 255.12379 + } 255.12380 + } 255.12381 + wysihtml.dom.unwrap(blocks[i]); 255.12382 + } 255.12383 + } 255.12384 + 255.12385 + // Fix ranges that visually cover whole block element to actually cover the block 255.12386 + function fixRangeCoverage(range, composer) { 255.12387 + var node, 255.12388 + start = range.startContainer, 255.12389 + end = range.endContainer; 255.12390 + 255.12391 + // If range has only one childNode and it is end to end the range, extend the range to contain the container element too 255.12392 + // This ensures the wrapper node is modified and optios added to it 255.12393 + if (start && start.nodeType === 1 && start === end) { 255.12394 + if (start.firstChild === start.lastChild && range.endOffset === 1) { 255.12395 + if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') { 255.12396 + range.setStartBefore(start); 255.12397 + range.setEndAfter(end); 255.12398 + } 255.12399 + } 255.12400 + return; 255.12401 + } 255.12402 + 255.12403 + // If range starts outside of node and ends inside at textrange and covers the whole node visually, extend end to cover the node end too 255.12404 + if (start && start.nodeType === 1 && end.nodeType === 3) { 255.12405 + if (start.firstChild === end && range.endOffset === end.data.length) { 255.12406 + if (start !== composer.element && start.nodeName !== 'LI' && start.nodeName !== 'TD') { 255.12407 + range.setEndAfter(start); 255.12408 + } 255.12409 + } 255.12410 + return; 255.12411 + } 255.12412 + 255.12413 + // If range ends outside of node and starts inside at textrange and covers the whole node visually, extend start to cover the node start too 255.12414 + if (end && end.nodeType === 1 && start.nodeType === 3) { 255.12415 + if (end.firstChild === start && range.startOffset === 0) { 255.12416 + if (end !== composer.element && end.nodeName !== 'LI' && end.nodeName !== 'TD') { 255.12417 + range.setStartBefore(end); 255.12418 + } 255.12419 + } 255.12420 + return; 255.12421 + } 255.12422 + 255.12423 + // If range covers a whole textnode and the textnode is the only child of node, extend range to node 255.12424 + if (start && start.nodeType === 3 && start === end && start.parentNode.childNodes.length === 1) { 255.12425 + if (range.endOffset == end.data.length && range.startOffset === 0) { 255.12426 + node = start.parentNode; 255.12427 + if (node !== composer.element && node.nodeName !== 'LI' && node.nodeName !== 'TD') { 255.12428 + range.setStartBefore(node); 255.12429 + range.setEndAfter(node); 255.12430 + } 255.12431 + } 255.12432 + return; 255.12433 + } 255.12434 + } 255.12435 + 255.12436 + // Scans ranges array for insertion points that are not allowed to insert block tags fixes/splits illegal ranges 255.12437 + // Some places do not allow block level elements inbetween (inside ul and outside li) 255.12438 + // TODO: might need extending for other nodes besides li (maybe dd,dl,dt) 255.12439 + function fixNotPermittedInsertionPoints(ranges) { 255.12440 + var newRanges = [], 255.12441 + lis, j, maxj, tmpRange, rangePos, closestLI; 255.12442 + 255.12443 + for (var i = 0, maxi = ranges.length; i < maxi; i++) { 255.12444 + 255.12445 + // Fixes range start and end positions if inside UL or OL element (outside of LI) 255.12446 + if (ranges[i].startContainer.nodeType === 1 && ranges[i].startContainer.matches('ul, ol')) { 255.12447 + ranges[i].setStart(ranges[i].startContainer.childNodes[ranges[i].startOffset], 0); 255.12448 + } 255.12449 + if (ranges[i].endContainer.nodeType === 1 && ranges[i].endContainer.matches('ul, ol')) { 255.12450 + closestLI = ranges[i].endContainer.childNodes[Math.max(ranges[i].endOffset - 1, 0)]; 255.12451 + if (closestLI.childNodes) { 255.12452 + ranges[i].setEnd(closestLI, closestLI.childNodes.length); 255.12453 + } 255.12454 + } 255.12455 + 255.12456 + // Get all LI eleemnts in selection (fully or partially covered) 255.12457 + // And make sure ranges are either inside LI or outside UL/OL 255.12458 + // Split and add new ranges as needed to cover same range content 255.12459 + // TODO: Needs improvement to accept DL, DD, DT 255.12460 + lis = ranges[i].getNodes([1], function(node) { 255.12461 + return node.nodeName === "LI"; 255.12462 + }); 255.12463 + if (lis.length > 0) { 255.12464 + 255.12465 + for (j = 0, maxj = lis.length; j < maxj; j++) { 255.12466 + rangePos = ranges[i].compareNode(lis[j]); 255.12467 + 255.12468 + // Fixes start of range that crosses LI border 255.12469 + if (rangePos === ranges[i].NODE_AFTER || rangePos === ranges[i].NODE_INSIDE) { 255.12470 + // Range starts before and ends inside the node 255.12471 + 255.12472 + tmpRange = ranges[i].cloneRange(); 255.12473 + closestLI = wysihtml.dom.domNode(lis[j]).prev({nodeTypes: [1]}); 255.12474 + 255.12475 + if (closestLI) { 255.12476 + tmpRange.setEnd(closestLI, closestLI.childNodes.length); 255.12477 + } else if (lis[j].closest('ul, ol')) { 255.12478 + tmpRange.setEndBefore(lis[j].closest('ul, ol')); 255.12479 + } else { 255.12480 + tmpRange.setEndBefore(lis[j]); 255.12481 + } 255.12482 + newRanges.push(tmpRange); 255.12483 + ranges[i].setStart(lis[j], 0); 255.12484 + } 255.12485 + 255.12486 + // Fixes end of range that crosses li border 255.12487 + if (rangePos === ranges[i].NODE_BEFORE || rangePos === ranges[i].NODE_INSIDE) { 255.12488 + // Range starts inside the node and ends after node 255.12489 + 255.12490 + tmpRange = ranges[i].cloneRange(); 255.12491 + tmpRange.setEnd(lis[j], lis[j].childNodes.length); 255.12492 + newRanges.push(tmpRange); 255.12493 + 255.12494 + // Find next LI in list and if present set range to it, else 255.12495 + closestLI = wysihtml.dom.domNode(lis[j]).next({nodeTypes: [1]}); 255.12496 + if (closestLI) { 255.12497 + ranges[i].setStart(closestLI, 0); 255.12498 + } else if (lis[j].closest('ul, ol')) { 255.12499 + ranges[i].setStartAfter(lis[j].closest('ul, ol')); 255.12500 + } else { 255.12501 + ranges[i].setStartAfter(lis[j]); 255.12502 + } 255.12503 + } 255.12504 + } 255.12505 + newRanges.push(ranges[i]); 255.12506 + } else { 255.12507 + newRanges.push(ranges[i]); 255.12508 + } 255.12509 + } 255.12510 + return newRanges; 255.12511 + } 255.12512 + 255.12513 + // Return options object with nodeName set if original did not have any 255.12514 + // Node name is set to local or global default 255.12515 + function getOptionsWithNodename(options, defaultName, composer) { 255.12516 + var correctedOptions = (options) ? wysihtml.lang.object(options).clone(true) : null; 255.12517 + if (correctedOptions) { 255.12518 + correctedOptions.nodeName = correctedOptions.nodeName || defaultName || defaultNodeName(composer); 255.12519 + } 255.12520 + return correctedOptions; 255.12521 + } 255.12522 + 255.12523 + // Injects document fragment to range ensuring outer elements are split to a place where block elements are allowed to be inserted 255.12524 + // Also wraps empty clones of split parent tags around fragment to keep formatting 255.12525 + // If firstOuterBlock is given assume that instead of finding outer (useful for solving cases of some blocks are allowed into others while others are not) 255.12526 + function injectFragmentToRange(fragment, range, composer, firstOuterBlock) { 255.12527 + var rangeStartContainer = range.startContainer, 255.12528 + firstOuterBlock = firstOuterBlock || findOuterBlock(rangeStartContainer, composer.element, true), 255.12529 + outerInlines, first, last, prev, next; 255.12530 + 255.12531 + if (firstOuterBlock) { 255.12532 + // If selection starts inside un-nestable block, split-escape the unnestable point and insert node between 255.12533 + first = fragment.firstChild; 255.12534 + last = fragment.lastChild; 255.12535 + 255.12536 + composer.selection.splitElementAtCaret(firstOuterBlock, fragment); 255.12537 + 255.12538 + next = wysihtml.dom.domNode(last).next({nodeTypes: [1,3], ignoreBlankTexts: true}); 255.12539 + prev = wysihtml.dom.domNode(first).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); 255.12540 + 255.12541 + if (first && !isLineBreaking(first, composer) && prev && !isLineBreaking(prev, composer)) { 255.12542 + first.parentNode.insertBefore(composer.doc.createElement('br'), first); 255.12543 + } 255.12544 + 255.12545 + if (last && !isLineBreaking(last, composer) && next && !isLineBreaking(next, composer)) { 255.12546 + next.parentNode.insertBefore(composer.doc.createElement('br'), next); 255.12547 + } 255.12548 + 255.12549 + } else { 255.12550 + // Ensure node does not get inserted into an inline where it is not allowed 255.12551 + outerInlines = cloneOuterInlines(rangeStartContainer, composer.element); 255.12552 + if (outerInlines.outerNode && outerInlines.innerNode && outerInlines.parent) { 255.12553 + if (fragment.childNodes.length === 1) { 255.12554 + while(fragment.firstChild.firstChild) { 255.12555 + outerInlines.innerNode.appendChild(fragment.firstChild.firstChild); 255.12556 + } 255.12557 + fragment.firstChild.appendChild(outerInlines.outerNode); 255.12558 + } 255.12559 + composer.selection.splitElementAtCaret(outerInlines.parent, fragment); 255.12560 + } else { 255.12561 + var fc = fragment.firstChild, 255.12562 + lc = fragment.lastChild; 255.12563 + 255.12564 + range.insertNode(fragment); 255.12565 + // restore range position as it might get lost in webkit sometimes 255.12566 + range.setStartBefore(fc); 255.12567 + range.setEndAfter(lc); 255.12568 + } 255.12569 + } 255.12570 + } 255.12571 + 255.12572 + // Removes all block formatting from range 255.12573 + function clearRangeBlockFromating(range, closestBlockName, composer) { 255.12574 + var r = range.cloneRange(), 255.12575 + prevNode = getRangeNode(r.startContainer, r.startOffset).previousSibling, 255.12576 + nextNode = getRangeNode(r.endContainer, r.endOffset).nextSibling, 255.12577 + content = r.extractContents(), 255.12578 + fragment = composer.doc.createDocumentFragment(), 255.12579 + children, blocks, 255.12580 + first = true; 255.12581 + 255.12582 + while(content.firstChild) { 255.12583 + // Iterate over all selection content first level childNodes 255.12584 + if (content.firstChild.nodeType === 1 && content.firstChild.matches(BLOCK_ELEMENTS)) { 255.12585 + // If node is a block element 255.12586 + // Split block formating and add new block to wrap caret 255.12587 + 255.12588 + unwrapBlocksFromContent(content.firstChild); 255.12589 + children = wysihtml.dom.unwrap(content.firstChild); 255.12590 + 255.12591 + // Add line break before if needed 255.12592 + if (children.length > 0) { 255.12593 + if ( 255.12594 + (fragment.lastChild && (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer))) || 255.12595 + (!fragment.lastChild && prevNode && (prevNode.nodeType !== 1 || isLineBreaking(prevNode, composer))) 255.12596 + ){ 255.12597 + fragment.appendChild(composer.doc.createElement('BR')); 255.12598 + } 255.12599 + } 255.12600 + 255.12601 + for (var c = 0, cmax = children.length; c < cmax; c++) { 255.12602 + fragment.appendChild(children[c]); 255.12603 + } 255.12604 + 255.12605 + // Add line break after if needed 255.12606 + if (children.length > 0) { 255.12607 + if (fragment.lastChild.nodeType !== 1 || !isLineBreaking(fragment.lastChild, composer)) { 255.12608 + if (nextNode || fragment.lastChild !== content.lastChild) { 255.12609 + fragment.appendChild(composer.doc.createElement('BR')); 255.12610 + } 255.12611 + } 255.12612 + } 255.12613 + 255.12614 + } else { 255.12615 + fragment.appendChild(content.firstChild); 255.12616 + } 255.12617 + 255.12618 + first = false; 255.12619 + } 255.12620 + blocks = wysihtml.lang.array(fragment.childNodes).get(); 255.12621 + injectFragmentToRange(fragment, r, composer); 255.12622 + return blocks; 255.12623 + } 255.12624 + 255.12625 + // When block node is inserted, look surrounding nodes and remove surplous linebreak tags (as block format breaks line itself) 255.12626 + function removeSurroundingLineBreaks(prevNode, nextNode, composer) { 255.12627 + var prevPrev = prevNode && wysihtml.dom.domNode(prevNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); 255.12628 + if (isBr(nextNode)) { 255.12629 + nextNode.parentNode.removeChild(nextNode); 255.12630 + } 255.12631 + if (isBr(prevNode) && (!prevPrev || prevPrev.nodeType !== 1 || composer.win.getComputedStyle(prevPrev).display !== "block")) { 255.12632 + prevNode.parentNode.removeChild(prevNode); 255.12633 + } 255.12634 + } 255.12635 + 255.12636 + function applySurroundingLineBreaks(prevNode, nextNode, composer) { 255.12637 + var prevPrev; 255.12638 + 255.12639 + if (prevNode && isBookmark(prevNode)) { 255.12640 + prevNode = prevNode.previousSibling; 255.12641 + } 255.12642 + if (nextNode && isBookmark(nextNode)) { 255.12643 + nextNode = nextNode.nextSibling; 255.12644 + } 255.12645 + 255.12646 + prevPrev = prevNode && prevNode.previousSibling; 255.12647 + 255.12648 + if (prevNode && (prevNode.nodeType !== 1 || (composer.win.getComputedStyle(prevNode).display !== "block" && !isBr(prevNode))) && prevNode.parentNode) { 255.12649 + prevNode.parentNode.insertBefore(composer.doc.createElement('br'), prevNode.nextSibling); 255.12650 + } 255.12651 + 255.12652 + if (nextNode && (nextNode.nodeType !== 1 || composer.win.getComputedStyle(nextNode).display !== "block") && nextNode.parentNode) { 255.12653 + nextNode.parentNode.insertBefore(composer.doc.createElement('br'), nextNode); 255.12654 + } 255.12655 + } 255.12656 + 255.12657 + var isWhitespaceBefore = function (textNode, offset) { 255.12658 + var str = textNode.data ? textNode.data.slice(0, offset) : ""; 255.12659 + return (/^\s*$/).test(str); 255.12660 + } 255.12661 + 255.12662 + var isWhitespaceAfter = function (textNode, offset) { 255.12663 + var str = textNode.data ? textNode.data.slice(offset) : ""; 255.12664 + return (/^\s*$/).test(str); 255.12665 + } 255.12666 + 255.12667 + var trimBlankTextsAndBreaks = function(fragment) { 255.12668 + if (fragment) { 255.12669 + while (fragment.firstChild && fragment.firstChild.nodeType === 3 && (/^\s*$/).test(fragment.firstChild.data) && fragment.lastChild !== fragment.firstChild) { 255.12670 + fragment.removeChild(fragment.firstChild); 255.12671 + } 255.12672 + 255.12673 + while (fragment.lastChild && fragment.lastChild.nodeType === 3 && (/^\s*$/).test(fragment.lastChild.data) && fragment.lastChild !== fragment.firstChild) { 255.12674 + fragment.removeChild(fragment.lastChild); 255.12675 + } 255.12676 + 255.12677 + if (fragment.firstChild && fragment.firstChild.nodeType === 1 && fragment.firstChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) { 255.12678 + fragment.removeChild(fragment.firstChild); 255.12679 + } 255.12680 + 255.12681 + if (fragment.lastChild && fragment.lastChild.nodeType === 1 && fragment.lastChild.nodeName === "BR" && fragment.lastChild !== fragment.firstChild) { 255.12682 + fragment.removeChild(fragment.lastChild); 255.12683 + } 255.12684 + } 255.12685 + } 255.12686 + 255.12687 + // Wrap the range with a block level element 255.12688 + // If element is one of unnestable block elements (ex: h2 inside h1), split nodes and insert between so nesting does not occur 255.12689 + function wrapRangeWithElement(range, options, closestBlockName, composer) { 255.12690 + var similarOptions = options ? correctOptionsForSimilarityCheck(options) : null, 255.12691 + r = range.cloneRange(), 255.12692 + rangeStartContainer = r.startContainer, 255.12693 + startNode = getRangeNode(r.startContainer, r.startOffset), 255.12694 + endNode = getRangeNode(r.endContainer, r.endOffset), 255.12695 + prevNode = (r.startContainer === startNode && startNode.nodeType === 3 && !isWhitespaceBefore(startNode, r.startOffset)) ? startNode : wysihtml.dom.domNode(startNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}), 255.12696 + nextNode = ( 255.12697 + ( 255.12698 + r.endContainer.nodeType === 1 && 255.12699 + r.endContainer.childNodes[r.endOffset] === endNode && 255.12700 + ( 255.12701 + endNode.nodeType === 1 || 255.12702 + !isWhitespaceAfter(endNode, r.endOffset) && 255.12703 + !wysihtml.dom.domNode(endNode).is.rangyBookmark() 255.12704 + ) 255.12705 + ) || ( 255.12706 + r.endContainer === endNode && 255.12707 + endNode.nodeType === 3 && 255.12708 + !isWhitespaceAfter(endNode, r.endOffset) 255.12709 + ) 255.12710 + ) ? endNode : wysihtml.dom.domNode(endNode).next({nodeTypes: [1,3], ignoreBlankTexts: true}), 255.12711 + content = r.extractContents(), 255.12712 + fragment = composer.doc.createDocumentFragment(), 255.12713 + similarOuterBlock = similarOptions ? wysihtml.dom.getParentElement(rangeStartContainer, similarOptions, null, composer.element) : null, 255.12714 + splitAllBlocks = !closestBlockName || !options || (options.nodeName === "BLOCKQUOTE" && closestBlockName === "BLOCKQUOTE"), 255.12715 + firstOuterBlock = similarOuterBlock || findOuterBlock(rangeStartContainer, composer.element, splitAllBlocks), // The outermost un-nestable block element parent of selection start 255.12716 + wrapper, blocks, children, 255.12717 + firstc, lastC; 255.12718 + 255.12719 + if (wysihtml.dom.domNode(nextNode).is.rangyBookmark()) { 255.12720 + endNode = nextNode; 255.12721 + nextNode = endNode.nextSibling; 255.12722 + } 255.12723 + 255.12724 + trimBlankTextsAndBreaks(content); 255.12725 + 255.12726 + if (options && options.nodeName === "BLOCKQUOTE") { 255.12727 + 255.12728 + // If blockquote is to be inserted no quessing just add it as outermost block on line or selection 255.12729 + var tmpEl = applyOptionsToElement(null, options, composer); 255.12730 + tmpEl.appendChild(content); 255.12731 + fragment.appendChild(tmpEl); 255.12732 + blocks = [tmpEl]; 255.12733 + 255.12734 + } else { 255.12735 + 255.12736 + if (!content.firstChild) { 255.12737 + // IF selection is caret (can happen if line is empty) add format around tag 255.12738 + fragment.appendChild(applyOptionsToElement(null, options, composer)); 255.12739 + } else { 255.12740 + 255.12741 + while(content.firstChild) { 255.12742 + // Iterate over all selection content first level childNodes 255.12743 + 255.12744 + if (content.firstChild.nodeType == 1 && content.firstChild.matches(BLOCK_ELEMENTS)) { 255.12745 + 255.12746 + // If node is a block element 255.12747 + // Escape(split) block formatting at caret 255.12748 + applyOptionsToElement(content.firstChild, options, composer); 255.12749 + if (content.firstChild.matches(UNNESTABLE_BLOCK_ELEMENTS)) { 255.12750 + unwrapBlocksFromContent(content.firstChild); 255.12751 + } 255.12752 + fragment.appendChild(content.firstChild); 255.12753 + 255.12754 + } else { 255.12755 + 255.12756 + // Wrap subsequent non-block nodes inside new block element 255.12757 + wrapper = applyOptionsToElement(null, getOptionsWithNodename(options, closestBlockName, composer), composer); 255.12758 + while(content.firstChild && (content.firstChild.nodeType !== 1 || !content.firstChild.matches(BLOCK_ELEMENTS))) { 255.12759 + if (content.firstChild.nodeType == 1 && wrapper.matches(UNNESTABLE_BLOCK_ELEMENTS)) { 255.12760 + unwrapBlocksFromContent(content.firstChild); 255.12761 + } 255.12762 + wrapper.appendChild(content.firstChild); 255.12763 + } 255.12764 + fragment.appendChild(wrapper); 255.12765 + } 255.12766 + } 255.12767 + } 255.12768 + 255.12769 + blocks = wysihtml.lang.array(fragment.childNodes).get(); 255.12770 + } 255.12771 + injectFragmentToRange(fragment, r, composer, firstOuterBlock); 255.12772 + removeSurroundingLineBreaks(prevNode, nextNode, composer); 255.12773 + 255.12774 + // Fix webkit madness by inserting linebreak rangy after cursor marker to blank last block 255.12775 + // (if it contains rangy bookmark, so selection can be restored later correctly) 255.12776 + if (blocks.length > 0 && 255.12777 + ( 255.12778 + typeof blocks[blocks.length - 1].lastChild === "undefined" || wysihtml.dom.domNode(blocks[blocks.length - 1].lastChild).is.rangyBookmark() 255.12779 + ) 255.12780 + ) { 255.12781 + blocks[blocks.length - 1].appendChild(composer.doc.createElement('br')); 255.12782 + } 255.12783 + return blocks; 255.12784 + } 255.12785 + 255.12786 + // Find closest block level element 255.12787 + function getParentBlockNodeName(element, composer) { 255.12788 + var parentNode = wysihtml.dom.getParentElement(element, { 255.12789 + query: BLOCK_ELEMENTS 255.12790 + }, null, composer.element); 255.12791 + 255.12792 + return (parentNode) ? parentNode.nodeName : null; 255.12793 + } 255.12794 + 255.12795 + // Expands caret to cover the closest block that: 255.12796 + // * cannot contain other block level elements (h1-6,p, etc) 255.12797 + // * Has the same nodeName that is to be inserted 255.12798 + // * has insertingNodeName 255.12799 + // * is DIV if insertingNodeName is not present 255.12800 + // 255.12801 + // If nothing found selects the current line 255.12802 + function expandCaretToBlock(composer, insertingNodeName) { 255.12803 + var parent = wysihtml.dom.getParentElement(composer.selection.getOwnRanges()[0].startContainer, { 255.12804 + query: UNNESTABLE_BLOCK_ELEMENTS + ', ' + (insertingNodeName ? insertingNodeName.toLowerCase() : 'div'), 255.12805 + }, null, composer.element), 255.12806 + range; 255.12807 + 255.12808 + if (parent) { 255.12809 + range = composer.selection.createRange(); 255.12810 + range.selectNode(parent); 255.12811 + composer.selection.setSelection(range); 255.12812 + } else if (!composer.isEmpty()) { 255.12813 + composer.selection.selectLine(); 255.12814 + } 255.12815 + } 255.12816 + 255.12817 + // Set selection to begin inside first created block element (beginning of it) and end inside (and after content) of last block element 255.12818 + // TODO: Checking nodetype might be unnescescary as nodes inserted by formatBlock are nodetype 1 anyway 255.12819 + function selectElements(newBlockElements, composer) { 255.12820 + var range = composer.selection.createRange(), 255.12821 + lastEl = newBlockElements[newBlockElements.length - 1], 255.12822 + lastOffset = (lastEl.nodeType === 1 && lastEl.childNodes) ? lastEl.childNodes.length | 0 : lastEl.length || 0; 255.12823 + 255.12824 + range.setStart(newBlockElements[0], 0); 255.12825 + range.setEnd(lastEl, lastOffset); 255.12826 + range.select(); 255.12827 + } 255.12828 + 255.12829 + // Get all ranges from selection (takes out uneditables and out of editor parts) and apply format to each 255.12830 + // Return created/modified block level elements 255.12831 + // Method can be either "apply" or "remove" 255.12832 + function formatSelection(method, composer, options) { 255.12833 + var ranges = composer.selection.getOwnRanges(), 255.12834 + newBlockElements = [], 255.12835 + closestBlockName; 255.12836 + 255.12837 + // Some places do not allow block level elements inbetween (inside ul and outside li, inside table and outside of td/th) 255.12838 + ranges = fixNotPermittedInsertionPoints(ranges); 255.12839 + 255.12840 + for (var i = ranges.length; i--;) { 255.12841 + fixRangeCoverage(ranges[i], composer); 255.12842 + closestBlockName = getParentBlockNodeName(ranges[i].startContainer, composer); 255.12843 + if (method === "remove") { 255.12844 + newBlockElements = newBlockElements.concat(clearRangeBlockFromating(ranges[i], closestBlockName, composer)); 255.12845 + } else { 255.12846 + newBlockElements = newBlockElements.concat(wrapRangeWithElement(ranges[i], options, closestBlockName, composer)); 255.12847 + } 255.12848 + } 255.12849 + return newBlockElements; 255.12850 + } 255.12851 + 255.12852 + // If properties is passed as a string, look for tag with that tagName/query 255.12853 + function parseOptions(options) { 255.12854 + if (typeof options === "string") { 255.12855 + options = { 255.12856 + nodeName: options.toUpperCase() 255.12857 + }; 255.12858 + } 255.12859 + return options; 255.12860 + } 255.12861 + 255.12862 + function caretIsOnEmptyLine(composer) { 255.12863 + var caretInfo; 255.12864 + if (composer.selection.isCollapsed()) { 255.12865 + caretInfo = composer.selection.getNodesNearCaret(); 255.12866 + if (caretInfo && caretInfo.caretNode) { 255.12867 + if ( 255.12868 + // caret is allready breaknode 255.12869 + wysihtml.dom.domNode(caretInfo.caretNode).is.lineBreak() || 255.12870 + // caret is textnode 255.12871 + (caretInfo.caretNode.nodeType === 3 && caretInfo.textOffset === 0 && (!caretInfo.prevNode || wysihtml.dom.domNode(caretInfo.prevNode).is.lineBreak())) || 255.12872 + // Caret is temprorary rangy selection marker 255.12873 + (caretInfo.caretNode.nodeType === 1 && caretInfo.caretNode.classList.contains('rangySelectionBoundary') && 255.12874 + (!caretInfo.prevNode || wysihtml.dom.domNode(caretInfo.prevNode).is.lineBreak() || wysihtml.dom.domNode(caretInfo.prevNode).is.block()) && 255.12875 + (!caretInfo.nextNode || wysihtml.dom.domNode(caretInfo.nextNode).is.lineBreak() || wysihtml.dom.domNode(caretInfo.nextNode).is.block()) 255.12876 + ) 255.12877 + ) { 255.12878 + return true; 255.12879 + } 255.12880 + } 255.12881 + } 255.12882 + return false; 255.12883 + } 255.12884 + 255.12885 + wysihtml.commands.formatBlock = { 255.12886 + exec: function(composer, command, options) { 255.12887 + options = parseOptions(options); 255.12888 + var newBlockElements = [], 255.12889 + ranges, range, bookmark, state, closestBlockName; 255.12890 + 255.12891 + // Find if current format state is active if options.toggle is set as true 255.12892 + // In toggle case active state elemets are formatted instead of working directly on selection 255.12893 + if (options && options.toggle) { 255.12894 + state = this.state(composer, command, options); 255.12895 + } 255.12896 + if (state) { 255.12897 + // Remove format from state nodes if toggle set and state on and selection is collapsed 255.12898 + bookmark = rangy.saveSelection(composer.win); 255.12899 + for (var j = 0, jmax = state.length; j < jmax; j++) { 255.12900 + removeOptionsFromElement(state[j], options, composer); 255.12901 + } 255.12902 + 255.12903 + } else { 255.12904 + // If selection is caret expand it to cover nearest suitable block element or row if none found 255.12905 + if (composer.selection.isCollapsed()) { 255.12906 + bookmark = rangy.saveSelection(composer.win); 255.12907 + if (caretIsOnEmptyLine(composer)) { 255.12908 + composer.selection.selectLine(); 255.12909 + } else { 255.12910 + expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined); 255.12911 + } 255.12912 + } 255.12913 + if (options) { 255.12914 + newBlockElements = formatSelection("apply", composer, options); 255.12915 + } else { 255.12916 + // Options == null means block formatting should be removed from selection 255.12917 + newBlockElements = formatSelection("remove", composer); 255.12918 + } 255.12919 + 255.12920 + } 255.12921 + 255.12922 + // Remove empty block elements that may be left behind 255.12923 + // Also remove them from new blocks list 255.12924 + newBlockElements = cleanup(composer, newBlockElements); 255.12925 + 255.12926 + // Restore selection 255.12927 + if (bookmark) { 255.12928 + rangy.restoreSelection(bookmark); 255.12929 + } else { 255.12930 + selectElements(newBlockElements, composer); 255.12931 + } 255.12932 + }, 255.12933 + 255.12934 + // Removes all block formatting from selection 255.12935 + remove: function(composer, command, options) { 255.12936 + options = parseOptions(options); 255.12937 + var newBlockElements, bookmark; 255.12938 + 255.12939 + // If selection is caret expand it to cover nearest suitable block element or row if none found 255.12940 + if (composer.selection.isCollapsed()) { 255.12941 + bookmark = rangy.saveSelection(composer.win); 255.12942 + expandCaretToBlock(composer, options && options.nodeName ? options.nodeName.toUpperCase() : undefined); 255.12943 + } 255.12944 + 255.12945 + newBlockElements = formatSelection("remove", composer); 255.12946 + newBlockElements = cleanup(composer, newBlockElements); 255.12947 + 255.12948 + // Restore selection 255.12949 + if (bookmark) { 255.12950 + rangy.restoreSelection(bookmark); 255.12951 + } else { 255.12952 + selectElements(newBlockElements, composer); 255.12953 + } 255.12954 + }, 255.12955 + 255.12956 + // If options as null is passed returns status describing all block level elements 255.12957 + state: function(composer, command, options) { 255.12958 + options = parseOptions(options); 255.12959 + 255.12960 + var nodes = composer.selection.filterElements((function (element) { // Finds matching elements inside selection 255.12961 + return wysihtml.dom.domNode(element).test(options || { query: BLOCK_ELEMENTS }); 255.12962 + }).bind(this)), 255.12963 + parentNodes = composer.selection.getSelectedOwnNodes(), 255.12964 + parent; 255.12965 + 255.12966 + // Finds matching elements that are parents of selection and adds to nodes list 255.12967 + for (var i = 0, maxi = parentNodes.length; i < maxi; i++) { 255.12968 + parent = dom.getParentElement(parentNodes[i], options || { query: BLOCK_ELEMENTS }, null, composer.element); 255.12969 + if (parent && nodes.indexOf(parent) === -1) { 255.12970 + nodes.push(parent); 255.12971 + } 255.12972 + } 255.12973 + 255.12974 + return (nodes.length === 0) ? false : nodes; 255.12975 + } 255.12976 + 255.12977 + }; 255.12978 +})(wysihtml); 255.12979 + 255.12980 +/** 255.12981 + * Unifies all inline tags additions and removals 255.12982 + * See https://github.com/Voog/wysihtml/pull/169 for specification of action 255.12983 + */ 255.12984 + 255.12985 +(function(wysihtml) { 255.12986 + 255.12987 + var defaultTag = "SPAN", 255.12988 + INLINE_ELEMENTS = "b, big, i, small, tt, abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var, a, bdo, br, q, span, sub, sup, button, label, textarea, input, select, u", 255.12989 + queryAliasMap = { 255.12990 + "b": "b, strong", 255.12991 + "strong": "b, strong", 255.12992 + "em": "em, i", 255.12993 + "i": "em, i" 255.12994 + }; 255.12995 + 255.12996 + function hasNoClass(element) { 255.12997 + return (/^\s*$/).test(element.className); 255.12998 + } 255.12999 + 255.13000 + function hasNoStyle(element) { 255.13001 + return !element.getAttribute('style') || (/^\s*$/).test(element.getAttribute('style')); 255.13002 + } 255.13003 + 255.13004 + // Associative arrays in javascript are really objects and do not have length defined 255.13005 + // Thus have to check emptyness in a different way 255.13006 + function hasNoAttributes(element) { 255.13007 + var attr = wysihtml.dom.getAttributes(element); 255.13008 + return wysihtml.lang.object(attr).isEmpty(); 255.13009 + } 255.13010 + 255.13011 + // compares two nodes if they are semantically the same 255.13012 + // Used in cleanup to find consequent semantically similar elements for merge 255.13013 + function isSameNode(element1, element2) { 255.13014 + var classes1, classes2, 255.13015 + attr1, attr2; 255.13016 + 255.13017 + if (element1.nodeType !== 1 || element2.nodeType !== 1) { 255.13018 + return false; 255.13019 + } 255.13020 + 255.13021 + if (element1.nodeName !== element2.nodeName) { 255.13022 + return false; 255.13023 + } 255.13024 + 255.13025 + classes1 = element1.className.trim().replace(/\s+/g, ' ').split(' '); 255.13026 + classes2 = element2.className.trim().replace(/\s+/g, ' ').split(' '); 255.13027 + if (wysihtml.lang.array(classes1).without(classes2).length > 0) { 255.13028 + return false; 255.13029 + } 255.13030 + 255.13031 + attr1 = wysihtml.dom.getAttributes(element1); 255.13032 + attr2 = wysihtml.dom.getAttributes(element2); 255.13033 + 255.13034 + if (attr1.length !== attr2.length || !wysihtml.lang.object(wysihtml.lang.object(attr1).difference(attr2)).isEmpty()) { 255.13035 + return false; 255.13036 + } 255.13037 + 255.13038 + return true; 255.13039 + } 255.13040 + 255.13041 + function createWrapNode(textNode, options) { 255.13042 + var nodeName = options && options.nodeName || defaultTag, 255.13043 + element = textNode.ownerDocument.createElement(nodeName); 255.13044 + 255.13045 + // Remove similar classes before applying className 255.13046 + if (options.classRegExp) { 255.13047 + element.className = element.className.replace(options.classRegExp, ""); 255.13048 + } 255.13049 + 255.13050 + if (options.className) { 255.13051 + element.classList.add(options.className); 255.13052 + } 255.13053 + 255.13054 + if (options.styleProperty && typeof options.styleValue !== "undefined") { 255.13055 + element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue; 255.13056 + } 255.13057 + 255.13058 + if (options.attribute) { 255.13059 + if (typeof options.attribute === "object") { 255.13060 + for (var a in options.attribute) { 255.13061 + if (options.attribute.hasOwnProperty(a)) { 255.13062 + element.setAttribute(a, options.attribute[a]); 255.13063 + } 255.13064 + } 255.13065 + } else if (typeof options.attributeValue !== "undefined") { 255.13066 + element.setAttribute(options.attribute, options.attributeValue); 255.13067 + } 255.13068 + } 255.13069 + 255.13070 + return element; 255.13071 + } 255.13072 + 255.13073 + // Tests if attr2 list contains all attributes present in attr1 255.13074 + // Note: attr 1 can have more attributes than attr2 255.13075 + function containsSameAttributes(attr1, attr2) { 255.13076 + for (var a in attr1) { 255.13077 + if (attr1.hasOwnProperty(a)) { 255.13078 + if (typeof attr2[a] === undefined || attr2[a] !== attr1[a]) { 255.13079 + return false; 255.13080 + } 255.13081 + } 255.13082 + } 255.13083 + return true; 255.13084 + } 255.13085 + 255.13086 + // If attrbutes and values are the same > remove 255.13087 + // if attributes or values 255.13088 + function updateElementAttributes(element, newAttributes, toggle) { 255.13089 + var attr = wysihtml.dom.getAttributes(element), 255.13090 + fullContain = containsSameAttributes(newAttributes, attr), 255.13091 + attrDifference = wysihtml.lang.object(attr).difference(newAttributes), 255.13092 + a, b; 255.13093 + 255.13094 + if (fullContain && toggle !== false) { 255.13095 + for (a in newAttributes) { 255.13096 + if (newAttributes.hasOwnProperty(a)) { 255.13097 + element.removeAttribute(a); 255.13098 + } 255.13099 + } 255.13100 + } else { 255.13101 + 255.13102 + /*if (!wysihtml.lang.object(attrDifference).isEmpty()) { 255.13103 + for (b in attrDifference) { 255.13104 + if (attrDifference.hasOwnProperty(b)) { 255.13105 + element.removeAttribute(b); 255.13106 + } 255.13107 + } 255.13108 + }*/ 255.13109 + 255.13110 + for (a in newAttributes) { 255.13111 + if (newAttributes.hasOwnProperty(a)) { 255.13112 + element.setAttribute(a, newAttributes[a]); 255.13113 + } 255.13114 + } 255.13115 + } 255.13116 + } 255.13117 + 255.13118 + function updateFormatOfElement(element, options) { 255.13119 + var attr, newNode, a, newAttributes, nodeNameQuery, nodeQueryMatch; 255.13120 + 255.13121 + if (options.className) { 255.13122 + if (options.toggle !== false && element.classList.contains(options.className)) { 255.13123 + element.classList.remove(options.className); 255.13124 + } else { 255.13125 + if (options.classRegExp) { 255.13126 + element.className = element.className.replace(options.classRegExp, ''); 255.13127 + } 255.13128 + element.classList.add(options.className); 255.13129 + } 255.13130 + if (hasNoClass(element)) { 255.13131 + element.removeAttribute('class'); 255.13132 + } 255.13133 + } 255.13134 + 255.13135 + // change/remove style 255.13136 + if (options.styleProperty) { 255.13137 + if (options.toggle !== false && element.style[wysihtml.browser.fixStyleKey(options.styleProperty)].trim().replace(/, /g, ",") === options.styleValue) { 255.13138 + element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = ''; 255.13139 + } else { 255.13140 + element.style[wysihtml.browser.fixStyleKey(options.styleProperty)] = options.styleValue; 255.13141 + } 255.13142 + } 255.13143 + if (hasNoStyle(element)) { 255.13144 + element.removeAttribute('style'); 255.13145 + } 255.13146 + 255.13147 + if (options.attribute) { 255.13148 + if (typeof options.attribute === "object") { 255.13149 + newAttributes = options.attribute; 255.13150 + } else { 255.13151 + newAttributes = {}; 255.13152 + newAttributes[options.attribute] = options.attributeValue || ''; 255.13153 + } 255.13154 + updateElementAttributes(element, newAttributes, options.toggle); 255.13155 + } 255.13156 + 255.13157 + 255.13158 + // Handle similar semantically same elements (queryAliasMap) 255.13159 + nodeNameQuery = options.nodeName ? queryAliasMap[options.nodeName.toLowerCase()] || options.nodeName.toLowerCase() : null; 255.13160 + nodeQueryMatch = nodeNameQuery ? wysihtml.dom.domNode(element).test({ query: nodeNameQuery }) : false; 255.13161 + 255.13162 + // Unwrap element if no attributes present and node name given 255.13163 + // or no attributes and if no nodename set but node is the default 255.13164 + if (!options.nodeName || options.nodeName === defaultTag || nodeQueryMatch) { 255.13165 + if ( 255.13166 + ((options.toggle !== false && nodeQueryMatch) || (!options.nodeName && element.nodeName === defaultTag)) && 255.13167 + hasNoClass(element) && hasNoStyle(element) && hasNoAttributes(element) 255.13168 + ) { 255.13169 + wysihtml.dom.unwrap(element); 255.13170 + } 255.13171 + 255.13172 + } 255.13173 + } 255.13174 + 255.13175 + // Fetch all textnodes in selection 255.13176 + // Empty textnodes are ignored except the one containing text caret 255.13177 + function getSelectedTextNodes(selection, splitBounds) { 255.13178 + var textNodes = []; 255.13179 + 255.13180 + if (!selection.isCollapsed()) { 255.13181 + textNodes = textNodes.concat(selection.getOwnNodes([3], function(node) { 255.13182 + // Exclude empty nodes except caret node 255.13183 + return (!wysihtml.dom.domNode(node).is.emptyTextNode()); 255.13184 + }, splitBounds)); 255.13185 + } 255.13186 + 255.13187 + return textNodes; 255.13188 + } 255.13189 + 255.13190 + function findSimilarTextNodeWrapper(textNode, options, container, exact) { 255.13191 + var node = textNode, 255.13192 + similarOptions = exact ? options : correctOptionsForSimilarityCheck(options); 255.13193 + 255.13194 + do { 255.13195 + if (node.nodeType === 1 && isSimilarNode(node, similarOptions)) { 255.13196 + return node; 255.13197 + } 255.13198 + node = node.parentNode; 255.13199 + } while (node && node !== container); 255.13200 + 255.13201 + return null; 255.13202 + } 255.13203 + 255.13204 + function correctOptionsForSimilarityCheck(options) { 255.13205 + return { 255.13206 + nodeName: options.nodeName || null, 255.13207 + className: (!options.classRegExp) ? options.className || null : null, 255.13208 + classRegExp: options.classRegExp || null, 255.13209 + styleProperty: options.styleProperty || null 255.13210 + }; 255.13211 + } 255.13212 + 255.13213 + // Finds inline node with similar nodeName/style/className 255.13214 + // If nodeName is specified inline node with the same (or alias) nodeName is expected to prove similar regardless of attributes 255.13215 + function isSimilarNode(node, options) { 255.13216 + var o; 255.13217 + if (options.nodeName) { 255.13218 + var query = queryAliasMap[options.nodeName.toLowerCase()] || options.nodeName.toLowerCase(); 255.13219 + return wysihtml.dom.domNode(node).test({ query: query }); 255.13220 + } else { 255.13221 + o = wysihtml.lang.object(options).clone(); 255.13222 + o.query = INLINE_ELEMENTS; // make sure only inline elements with styles and classes are counted 255.13223 + return wysihtml.dom.domNode(node).test(o); 255.13224 + } 255.13225 + } 255.13226 + 255.13227 + function selectRange(composer, range) { 255.13228 + var d = document.documentElement || document.body, 255.13229 + oldScrollTop = d.scrollTop, 255.13230 + oldScrollLeft = d.scrollLeft, 255.13231 + selection = rangy.getSelection(composer.win); 255.13232 + 255.13233 + rangy.getSelection(composer.win).removeAllRanges(); 255.13234 + 255.13235 + // IE looses focus of contenteditable on removeallranges and can not set new selection unless contenteditable is focused again 255.13236 + try { 255.13237 + rangy.getSelection(composer.win).addRange(range); 255.13238 + } catch (e) {} 255.13239 + if (!composer.doc.activeElement || !wysihtml.dom.contains(composer.element, composer.doc.activeElement)) { 255.13240 + composer.element.focus(); 255.13241 + d.scrollTop = oldScrollTop; 255.13242 + d.scrollLeft = oldScrollLeft; 255.13243 + rangy.getSelection(composer.win).addRange(range); 255.13244 + } 255.13245 + } 255.13246 + 255.13247 + function selectTextNodes(textNodes, composer) { 255.13248 + var range = rangy.createRange(composer.doc), 255.13249 + lastText = textNodes[textNodes.length - 1]; 255.13250 + 255.13251 + if (textNodes[0] && lastText) { 255.13252 + range.setStart(textNodes[0], 0); 255.13253 + range.setEnd(lastText, lastText.length); 255.13254 + selectRange(composer, range); 255.13255 + } 255.13256 + 255.13257 + } 255.13258 + 255.13259 + function selectTextNode(composer, node, start, end) { 255.13260 + var range = rangy.createRange(composer.doc); 255.13261 + if (node) { 255.13262 + range.setStart(node, start); 255.13263 + range.setEnd(node, typeof end !== 'undefined' ? end : start); 255.13264 + selectRange(composer, range); 255.13265 + } 255.13266 + } 255.13267 + 255.13268 + function getState(composer, options, exact) { 255.13269 + var searchNodes = getSelectedTextNodes(composer.selection), 255.13270 + nodes = [], 255.13271 + partial = false, 255.13272 + node, range, caretNode; 255.13273 + 255.13274 + if (composer.selection.isInThisEditable()) { 255.13275 + 255.13276 + if (searchNodes.length === 0 && composer.selection.isCollapsed()) { 255.13277 + caretNode = composer.selection.getSelection().anchorNode; 255.13278 + if (!caretNode) { 255.13279 + // selection not in editor 255.13280 + return { 255.13281 + nodes: [], 255.13282 + partial: false 255.13283 + }; 255.13284 + } 255.13285 + if (caretNode.nodeType === 3) { 255.13286 + searchNodes = [caretNode]; 255.13287 + } 255.13288 + } 255.13289 + 255.13290 + // Handle collapsed selection caret 255.13291 + if (!searchNodes.length) { 255.13292 + range = composer.selection.getOwnRanges()[0]; 255.13293 + if (range) { 255.13294 + searchNodes = [range.endContainer]; 255.13295 + } 255.13296 + } 255.13297 + 255.13298 + for (var i = 0, maxi = searchNodes.length; i < maxi; i++) { 255.13299 + node = findSimilarTextNodeWrapper(searchNodes[i], options, composer.element, exact); 255.13300 + if (node) { 255.13301 + nodes.push(node); 255.13302 + } else { 255.13303 + partial = true; 255.13304 + } 255.13305 + } 255.13306 + 255.13307 + } 255.13308 + 255.13309 + return { 255.13310 + nodes: nodes, 255.13311 + partial: partial 255.13312 + }; 255.13313 + } 255.13314 + 255.13315 + // Returns if caret is inside a word in textnode (not on boundary) 255.13316 + // If selection anchornode is not text node, returns false 255.13317 + function caretIsInsideWord(selection) { 255.13318 + var anchor, offset, beforeChar, afterChar; 255.13319 + if (selection) { 255.13320 + anchor = selection.anchorNode; 255.13321 + offset = selection.anchorOffset; 255.13322 + if (anchor && anchor.nodeType === 3 && offset > 0 && offset < anchor.data.length) { 255.13323 + beforeChar = anchor.data[offset - 1]; 255.13324 + afterChar = anchor.data[offset]; 255.13325 + return (/\w/).test(beforeChar) && (/\w/).test(afterChar); 255.13326 + } 255.13327 + } 255.13328 + return false; 255.13329 + } 255.13330 + 255.13331 + // Returns a range and textnode containing object from caret position covering a whole word 255.13332 + // wordOffsety describes the original position of caret in the new textNode 255.13333 + // Caret has to be inside a textNode. 255.13334 + function getRangeForWord(selection) { 255.13335 + var anchor, offset, doc, range, offsetStart, offsetEnd, beforeChar, afterChar, 255.13336 + txtNodes = []; 255.13337 + if (selection) { 255.13338 + anchor = selection.anchorNode; 255.13339 + offset = offsetStart = offsetEnd = selection.anchorOffset; 255.13340 + doc = anchor.ownerDocument; 255.13341 + range = rangy.createRange(doc); 255.13342 + 255.13343 + if (anchor && anchor.nodeType === 3) { 255.13344 + 255.13345 + while (offsetStart > 0 && (/\w/).test(anchor.data[offsetStart - 1])) { 255.13346 + offsetStart--; 255.13347 + } 255.13348 + 255.13349 + while (offsetEnd < anchor.data.length && (/\w/).test(anchor.data[offsetEnd])) { 255.13350 + offsetEnd++; 255.13351 + } 255.13352 + 255.13353 + range.setStartAndEnd(anchor, offsetStart, offsetEnd); 255.13354 + range.splitBoundaries(); 255.13355 + txtNodes = range.getNodes([3], function(node) { 255.13356 + return (!wysihtml.dom.domNode(node).is.emptyTextNode()); 255.13357 + }); 255.13358 + 255.13359 + return { 255.13360 + wordOffset: offset - offsetStart, 255.13361 + range: range, 255.13362 + textNode: txtNodes[0] 255.13363 + }; 255.13364 + 255.13365 + } 255.13366 + } 255.13367 + return false; 255.13368 + } 255.13369 + 255.13370 + // Contents of 2 elements are merged to fitst element. second element is removed as consequence 255.13371 + function mergeContents(element1, element2) { 255.13372 + while (element2.firstChild) { 255.13373 + element1.appendChild(element2.firstChild); 255.13374 + } 255.13375 + element2.parentNode.removeChild(element2); 255.13376 + } 255.13377 + 255.13378 + function mergeConsequentSimilarElements(elements) { 255.13379 + for (var i = elements.length; i--;) { 255.13380 + 255.13381 + if (elements[i] && elements[i].parentNode) { // Test if node is not allready removed in cleanup 255.13382 + 255.13383 + if (elements[i].nextSibling && isSameNode(elements[i], elements[i].nextSibling)) { 255.13384 + mergeContents(elements[i], elements[i].nextSibling); 255.13385 + } 255.13386 + 255.13387 + if (elements[i].previousSibling && isSameNode(elements[i] , elements[i].previousSibling)) { 255.13388 + mergeContents(elements[i].previousSibling, elements[i]); 255.13389 + } 255.13390 + 255.13391 + } 255.13392 + } 255.13393 + } 255.13394 + 255.13395 + function cleanupAndSetSelection(composer, textNodes, options) { 255.13396 + if (textNodes.length > 0) { 255.13397 + selectTextNodes(textNodes, composer); 255.13398 + } 255.13399 + mergeConsequentSimilarElements(getState(composer, options).nodes); 255.13400 + if (textNodes.length > 0) { 255.13401 + selectTextNodes(textNodes, composer); 255.13402 + } 255.13403 + } 255.13404 + 255.13405 + function cleanupAndSetCaret(composer, textNode, offset, options) { 255.13406 + selectTextNode(composer, textNode, offset); 255.13407 + mergeConsequentSimilarElements(getState(composer, options).nodes); 255.13408 + selectTextNode(composer, textNode, offset); 255.13409 + } 255.13410 + 255.13411 + // Formats a textnode with given options 255.13412 + function formatTextNode(textNode, options) { 255.13413 + var wrapNode = createWrapNode(textNode, options); 255.13414 + 255.13415 + textNode.parentNode.insertBefore(wrapNode, textNode); 255.13416 + wrapNode.appendChild(textNode); 255.13417 + } 255.13418 + 255.13419 + // Changes/toggles format of a textnode 255.13420 + function unformatTextNode(textNode, composer, options) { 255.13421 + var container = composer.element, 255.13422 + wrapNode = findSimilarTextNodeWrapper(textNode, options, container), 255.13423 + newWrapNode; 255.13424 + 255.13425 + if (wrapNode) { 255.13426 + newWrapNode = wrapNode.cloneNode(false); 255.13427 + 255.13428 + wysihtml.dom.domNode(textNode).escapeParent(wrapNode, newWrapNode); 255.13429 + updateFormatOfElement(newWrapNode, options); 255.13430 + } 255.13431 + } 255.13432 + 255.13433 + // Removes the format around textnode 255.13434 + function removeFormatFromTextNode(textNode, composer, options) { 255.13435 + var container = composer.element, 255.13436 + wrapNode = findSimilarTextNodeWrapper(textNode, options, container); 255.13437 + 255.13438 + if (wrapNode) { 255.13439 + wysihtml.dom.domNode(textNode).escapeParent(wrapNode); 255.13440 + } 255.13441 + } 255.13442 + 255.13443 + // Creates node around caret formated with options 255.13444 + function formatTextRange(range, composer, options) { 255.13445 + var wrapNode = createWrapNode(range.endContainer, options); 255.13446 + 255.13447 + range.surroundContents(wrapNode); 255.13448 + composer.selection.selectNode(wrapNode); 255.13449 + } 255.13450 + 255.13451 + // Changes/toggles format of whole selection 255.13452 + function updateFormat(composer, textNodes, state, options) { 255.13453 + var exactState = getState(composer, options, true), 255.13454 + selection = composer.selection.getSelection(), 255.13455 + wordObj, textNode, newNode, i; 255.13456 + 255.13457 + if (!textNodes.length) { 255.13458 + // Selection is caret 255.13459 + 255.13460 + 255.13461 + if (options.toggle !== false) { 255.13462 + if (caretIsInsideWord(selection)) { 255.13463 + 255.13464 + // Unformat whole word 255.13465 + wordObj = getRangeForWord(selection); 255.13466 + textNode = wordObj.textNode; 255.13467 + unformatTextNode(wordObj.textNode, composer, options); 255.13468 + cleanupAndSetCaret(composer, wordObj.textNode, wordObj.wordOffset, options); 255.13469 + 255.13470 + } else { 255.13471 + 255.13472 + // Escape caret out of format 255.13473 + textNode = composer.doc.createTextNode(wysihtml.INVISIBLE_SPACE); 255.13474 + newNode = state.nodes[0].cloneNode(false); 255.13475 + newNode.appendChild(textNode); 255.13476 + composer.selection.splitElementAtCaret(state.nodes[0], newNode); 255.13477 + updateFormatOfElement(newNode, options); 255.13478 + cleanupAndSetSelection(composer, [textNode], options); 255.13479 + var s = composer.selection.getSelection(); 255.13480 + if (s.anchorNode && s.focusNode) { 255.13481 + // Has an error in IE when collapsing selection. probably from rangy 255.13482 + try { 255.13483 + s.collapseToEnd(); 255.13484 + } catch (e) {} 255.13485 + } 255.13486 + } 255.13487 + } else { 255.13488 + // In non-toggle mode the closest state element has to be found and the state updated differently 255.13489 + for (i = state.nodes.length; i--;) { 255.13490 + updateFormatOfElement(state.nodes[i], options); 255.13491 + } 255.13492 + } 255.13493 + 255.13494 + } else { 255.13495 + 255.13496 + if (!exactState.partial && options.toggle !== false) { 255.13497 + 255.13498 + // If whole selection (all textnodes) are in the applied format 255.13499 + // remove the format from selection 255.13500 + // Non-toggle mode never removes. Remove has to be called explicitly 255.13501 + for (i = textNodes.length; i--;) { 255.13502 + unformatTextNode(textNodes[i], composer, options); 255.13503 + } 255.13504 + 255.13505 + } else { 255.13506 + 255.13507 + // Selection is partially in format 255.13508 + // change it to new if format if textnode allreafy in similar state 255.13509 + // else just apply 255.13510 + 255.13511 + for (i = textNodes.length; i--;) { 255.13512 + 255.13513 + if (findSimilarTextNodeWrapper(textNodes[i], options, composer.element)) { 255.13514 + unformatTextNode(textNodes[i], composer, options); 255.13515 + } 255.13516 + 255.13517 + if (!findSimilarTextNodeWrapper(textNodes[i], options, composer.element)) { 255.13518 + formatTextNode(textNodes[i], options); 255.13519 + } 255.13520 + } 255.13521 + 255.13522 + } 255.13523 + 255.13524 + cleanupAndSetSelection(composer, textNodes, options); 255.13525 + } 255.13526 + } 255.13527 + 255.13528 + // Removes format from selection 255.13529 + function removeFormat(composer, textNodes, state, options) { 255.13530 + var textNode, textOffset, newNode, i, 255.13531 + selection = composer.selection.getSelection(); 255.13532 + 255.13533 + if (!textNodes.length) { 255.13534 + textNode = selection.anchorNode; 255.13535 + textOffset = selection.anchorOffset; 255.13536 + 255.13537 + for (i = state.nodes.length; i--;) { 255.13538 + wysihtml.dom.unwrap(state.nodes[i]); 255.13539 + } 255.13540 + 255.13541 + cleanupAndSetCaret(composer, textNode, textOffset, options); 255.13542 + } else { 255.13543 + for (i = textNodes.length; i--;) { 255.13544 + removeFormatFromTextNode(textNodes[i], composer, options); 255.13545 + } 255.13546 + cleanupAndSetSelection(composer, textNodes, options); 255.13547 + } 255.13548 + } 255.13549 + 255.13550 + // Adds format to selection 255.13551 + function applyFormat(composer, textNodes, options) { 255.13552 + var wordObj, i, 255.13553 + selection = composer.selection.getSelection(); 255.13554 + 255.13555 + if (!textNodes.length) { 255.13556 + // Handle collapsed selection caret and return 255.13557 + if (caretIsInsideWord(selection)) { 255.13558 + 255.13559 + wordObj = getRangeForWord(selection); 255.13560 + formatTextNode(wordObj.textNode, options); 255.13561 + cleanupAndSetCaret(composer, wordObj.textNode, wordObj.wordOffset, options); 255.13562 + 255.13563 + } else { 255.13564 + var r = composer.selection.getOwnRanges()[0]; 255.13565 + if (r) { 255.13566 + formatTextRange(r, composer, options); 255.13567 + } 255.13568 + } 255.13569 + 255.13570 + } else { 255.13571 + // Handle textnodes in selection and apply format 255.13572 + for (i = textNodes.length; i--;) { 255.13573 + formatTextNode(textNodes[i], options); 255.13574 + } 255.13575 + cleanupAndSetSelection(composer, textNodes, options); 255.13576 + } 255.13577 + } 255.13578 + 255.13579 + // If properties is passed as a string, correct options with that nodeName 255.13580 + function fixOptions(options) { 255.13581 + options = (typeof options === "string") ? { nodeName: options } : options; 255.13582 + if (options.nodeName) { options.nodeName = options.nodeName.toUpperCase(); } 255.13583 + return options; 255.13584 + } 255.13585 + 255.13586 + wysihtml.commands.formatInline = { 255.13587 + 255.13588 + // Basics: 255.13589 + // In case of plain text or inline state not set wrap all non-empty textnodes with 255.13590 + // In case a similar inline wrapper node is detected on one of textnodes, the wrapper node is changed (if fully contained) or split and changed (partially contained) 255.13591 + // In case of changing mode every textnode is addressed separatly 255.13592 + exec: function(composer, command, options) { 255.13593 + options = fixOptions(options); 255.13594 + 255.13595 + // Join adjactent textnodes first 255.13596 + composer.element.normalize(); 255.13597 + 255.13598 + var textNodes = getSelectedTextNodes(composer.selection, true), 255.13599 + state = getState(composer, options); 255.13600 + if (state.nodes.length > 0) { 255.13601 + // Text allready has the format applied 255.13602 + updateFormat(composer, textNodes, state, options); 255.13603 + } else { 255.13604 + // Selection is not in the applied format 255.13605 + applyFormat(composer, textNodes, options); 255.13606 + } 255.13607 + composer.element.normalize(); 255.13608 + }, 255.13609 + 255.13610 + remove: function(composer, command, options) { 255.13611 + options = fixOptions(options); 255.13612 + composer.element.normalize(); 255.13613 + 255.13614 + var textNodes = getSelectedTextNodes(composer.selection, true), 255.13615 + state = getState(composer, options); 255.13616 + 255.13617 + if (state.nodes.length > 0) { 255.13618 + // Text allready has the format applied 255.13619 + removeFormat(composer, textNodes, state, options); 255.13620 + } 255.13621 + 255.13622 + composer.element.normalize(); 255.13623 + }, 255.13624 + 255.13625 + state: function(composer, command, options) { 255.13626 + options = fixOptions(options); 255.13627 + var nodes = getState(composer, options, true).nodes; 255.13628 + return (nodes.length === 0) ? false : nodes; 255.13629 + } 255.13630 + }; 255.13631 + 255.13632 +})(wysihtml); 255.13633 + 255.13634 +(function(wysihtml){ 255.13635 + wysihtml.commands.indentList = { 255.13636 + exec: function(composer, command, value) { 255.13637 + var listEls = composer.selection.getSelectionParentsByTag('LI'); 255.13638 + if (listEls) { 255.13639 + return this.tryToPushLiLevel(listEls, composer.selection); 255.13640 + } 255.13641 + return false; 255.13642 + }, 255.13643 + 255.13644 + state: function(composer, command) { 255.13645 + return false; 255.13646 + }, 255.13647 + 255.13648 + tryToPushLiLevel: function(liNodes, selection) { 255.13649 + var listTag, list, prevLi, liNode, prevLiList, 255.13650 + found = false; 255.13651 + 255.13652 + selection.executeAndRestoreRangy(function() { 255.13653 + 255.13654 + for (var i = liNodes.length; i--;) { 255.13655 + liNode = liNodes[i]; 255.13656 + listTag = (liNode.parentNode.nodeName === 'OL') ? 'OL' : 'UL'; 255.13657 + list = liNode.ownerDocument.createElement(listTag); 255.13658 + prevLi = wysihtml.dom.domNode(liNode).prev({nodeTypes: [wysihtml.ELEMENT_NODE]}); 255.13659 + prevLiList = (prevLi) ? prevLi.querySelector('ul, ol') : null; 255.13660 + 255.13661 + if (prevLi) { 255.13662 + if (prevLiList) { 255.13663 + prevLiList.appendChild(liNode); 255.13664 + } else { 255.13665 + list.appendChild(liNode); 255.13666 + prevLi.appendChild(list); 255.13667 + } 255.13668 + found = true; 255.13669 + } 255.13670 + } 255.13671 + 255.13672 + }); 255.13673 + return found; 255.13674 + } 255.13675 + }; 255.13676 +}(wysihtml)); 255.13677 + 255.13678 +(function(wysihtml){ 255.13679 + wysihtml.commands.insertHTML = { 255.13680 + exec: function(composer, command, html) { 255.13681 + composer.selection.insertHTML(html); 255.13682 + }, 255.13683 + 255.13684 + state: function() { 255.13685 + return false; 255.13686 + } 255.13687 + }; 255.13688 +}(wysihtml)); 255.13689 + 255.13690 +(function(wysihtml) { 255.13691 + var LINE_BREAK = "<br>" + (wysihtml.browser.needsSpaceAfterLineBreak() ? " " : ""); 255.13692 + 255.13693 + wysihtml.commands.insertLineBreak = { 255.13694 + exec: function(composer, command) { 255.13695 + composer.selection.insertHTML(LINE_BREAK); 255.13696 + }, 255.13697 + 255.13698 + state: function() { 255.13699 + return false; 255.13700 + } 255.13701 + }; 255.13702 +})(wysihtml); 255.13703 + 255.13704 +wysihtml.commands.insertList = (function(wysihtml) { 255.13705 + 255.13706 + var isNode = function(node, name) { 255.13707 + if (node && node.nodeName) { 255.13708 + if (typeof name === 'string') { 255.13709 + name = [name]; 255.13710 + } 255.13711 + for (var n = name.length; n--;) { 255.13712 + if (node.nodeName === name[n]) { 255.13713 + return true; 255.13714 + } 255.13715 + } 255.13716 + } 255.13717 + return false; 255.13718 + }; 255.13719 + 255.13720 + var findListEl = function(node, nodeName, composer) { 255.13721 + var ret = { 255.13722 + el: null, 255.13723 + other: false 255.13724 + }; 255.13725 + 255.13726 + if (node) { 255.13727 + var parentLi = wysihtml.dom.getParentElement(node, { query: "li" }, false, composer.element), 255.13728 + otherNodeName = (nodeName === "UL") ? "OL" : "UL"; 255.13729 + 255.13730 + if (isNode(node, nodeName)) { 255.13731 + ret.el = node; 255.13732 + } else if (isNode(node, otherNodeName)) { 255.13733 + ret = { 255.13734 + el: node, 255.13735 + other: true 255.13736 + }; 255.13737 + } else if (parentLi) { 255.13738 + if (isNode(parentLi.parentNode, nodeName)) { 255.13739 + ret.el = parentLi.parentNode; 255.13740 + } else if (isNode(parentLi.parentNode, otherNodeName)) { 255.13741 + ret = { 255.13742 + el : parentLi.parentNode, 255.13743 + other: true 255.13744 + }; 255.13745 + } 255.13746 + } 255.13747 + } 255.13748 + 255.13749 + // do not count list elements outside of composer 255.13750 + if (ret.el && !composer.element.contains(ret.el)) { 255.13751 + ret.el = null; 255.13752 + } 255.13753 + 255.13754 + return ret; 255.13755 + }; 255.13756 + 255.13757 + var handleSameTypeList = function(el, nodeName, composer) { 255.13758 + var otherNodeName = (nodeName === "UL") ? "OL" : "UL", 255.13759 + otherLists, innerLists; 255.13760 + // Unwrap list 255.13761 + // <ul><li>foo</li><li>bar</li></ul> 255.13762 + // becomes: 255.13763 + // foo<br>bar<br> 255.13764 + 255.13765 + composer.selection.executeAndRestoreRangy(function() { 255.13766 + otherLists = getListsInSelection(otherNodeName, composer); 255.13767 + if (otherLists.length) { 255.13768 + for (var l = otherLists.length; l--;) { 255.13769 + wysihtml.dom.renameElement(otherLists[l], nodeName.toLowerCase()); 255.13770 + } 255.13771 + } else { 255.13772 + innerLists = getListsInSelection(['OL', 'UL'], composer); 255.13773 + for (var i = innerLists.length; i--;) { 255.13774 + wysihtml.dom.resolveList(innerLists[i], composer.config.useLineBreaks); 255.13775 + } 255.13776 + if (innerLists.length === 0) { 255.13777 + wysihtml.dom.resolveList(el, composer.config.useLineBreaks); 255.13778 + } 255.13779 + } 255.13780 + }); 255.13781 + }; 255.13782 + 255.13783 + var handleOtherTypeList = function(el, nodeName, composer) { 255.13784 + var otherNodeName = (nodeName === "UL") ? "OL" : "UL"; 255.13785 + // Turn an ordered list into an unordered list 255.13786 + // <ol><li>foo</li><li>bar</li></ol> 255.13787 + // becomes: 255.13788 + // <ul><li>foo</li><li>bar</li></ul> 255.13789 + // Also rename other lists in selection 255.13790 + composer.selection.executeAndRestoreRangy(function() { 255.13791 + var renameLists = [el].concat(getListsInSelection(otherNodeName, composer)); 255.13792 + 255.13793 + // All selection inner lists get renamed too 255.13794 + for (var l = renameLists.length; l--;) { 255.13795 + wysihtml.dom.renameElement(renameLists[l], nodeName.toLowerCase()); 255.13796 + } 255.13797 + }); 255.13798 + }; 255.13799 + 255.13800 + var getListsInSelection = function(nodeName, composer) { 255.13801 + var ranges = composer.selection.getOwnRanges(), 255.13802 + renameLists = []; 255.13803 + 255.13804 + for (var r = ranges.length; r--;) { 255.13805 + renameLists = renameLists.concat(ranges[r].getNodes([1], function(node) { 255.13806 + return isNode(node, nodeName); 255.13807 + })); 255.13808 + } 255.13809 + 255.13810 + return renameLists; 255.13811 + }; 255.13812 + 255.13813 + var createListFallback = function(nodeName, composer) { 255.13814 + var sel = rangy.saveSelection(composer.win); 255.13815 + 255.13816 + // Fallback for Create list 255.13817 + var tempClassName = "_wysihtml-temp-" + new Date().getTime(), 255.13818 + isEmpty, list; 255.13819 + 255.13820 + composer.commands.exec("formatBlock", { 255.13821 + "nodeName": "div", 255.13822 + "className": tempClassName 255.13823 + }); 255.13824 + 255.13825 + var tempElement = composer.element.querySelector("." + tempClassName); 255.13826 + 255.13827 + // This space causes new lists to never break on enter 255.13828 + var INVISIBLE_SPACE_REG_EXP = /\uFEFF/g; 255.13829 + tempElement.innerHTML = tempElement.innerHTML.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ""); 255.13830 + if (tempElement) { 255.13831 + isEmpty = (/^(\s|(<br>))+$/i).test(tempElement.innerHTML); 255.13832 + list = wysihtml.dom.convertToList(tempElement, nodeName.toLowerCase(), composer.parent.config.classNames.uneditableContainer); 255.13833 + if (sel) { 255.13834 + rangy.restoreSelection(sel); 255.13835 + } 255.13836 + if (isEmpty) { 255.13837 + composer.selection.selectNode(list.querySelector("li"), true); 255.13838 + } 255.13839 + } 255.13840 + }; 255.13841 + 255.13842 + return { 255.13843 + exec: function(composer, command, nodeName) { 255.13844 + var doc = composer.doc, 255.13845 + cmd = (nodeName === "OL") ? "insertOrderedList" : "insertUnorderedList", 255.13846 + s = composer.selection.getSelection(), 255.13847 + anode = s.anchorNode.nodeType === 1 && s.anchorNode.firstChild ? s.anchorNode.childNodes[s.anchorOffset] : s.anchorNode, 255.13848 + fnode = s.focusNode.nodeType === 1 && s.focusNode.firstChild ? s.focusNode.childNodes[s.focusOffset] || s.focusNode.lastChild : s.focusNode, 255.13849 + selectedNode, list; 255.13850 + 255.13851 + if (s.isBackwards()) { 255.13852 + // swap variables 255.13853 + anode = [fnode, fnode = anode][0]; 255.13854 + } 255.13855 + 255.13856 + if (wysihtml.dom.domNode(fnode).is.emptyTextNode(true) && fnode) { 255.13857 + fnode = wysihtml.dom.domNode(fnode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); 255.13858 + } 255.13859 + if (wysihtml.dom.domNode(anode).is.emptyTextNode(true) && anode) { 255.13860 + anode = wysihtml.dom.domNode(anode).next({nodeTypes: [1,3], ignoreBlankTexts: true}); 255.13861 + } 255.13862 + 255.13863 + if (anode && fnode) { 255.13864 + if (anode === fnode) { 255.13865 + selectedNode = anode; 255.13866 + } else { 255.13867 + selectedNode = wysihtml.dom.domNode(anode).commonAncestor(fnode, composer.element); 255.13868 + } 255.13869 + } else { 255.13870 + selectedNode = composer.selection.getSelectedNode(); 255.13871 + } 255.13872 + 255.13873 + list = findListEl(selectedNode, nodeName, composer); 255.13874 + 255.13875 + if (!list.el) { 255.13876 + if (composer.commands.support(cmd)) { 255.13877 + doc.execCommand(cmd, false, null); 255.13878 + } else { 255.13879 + createListFallback(nodeName, composer); 255.13880 + } 255.13881 + } else if (list.other) { 255.13882 + handleOtherTypeList(list.el, nodeName, composer); 255.13883 + } else { 255.13884 + handleSameTypeList(list.el, nodeName, composer); 255.13885 + } 255.13886 + }, 255.13887 + 255.13888 + state: function(composer, command, nodeName) { 255.13889 + var selectedNode = composer.selection.getSelectedNode(), 255.13890 + list = findListEl(selectedNode, nodeName, composer); 255.13891 + 255.13892 + return (list.el && !list.other) ? list.el : false; 255.13893 + } 255.13894 + }; 255.13895 + 255.13896 +})(wysihtml); 255.13897 + 255.13898 +(function(wysihtml){ 255.13899 + 255.13900 + wysihtml.commands.outdentList = { 255.13901 + exec: function(composer, command, value) { 255.13902 + var listEls = composer.selection.getSelectionParentsByTag('LI'); 255.13903 + if (listEls) { 255.13904 + return this.tryToPullLiLevel(listEls, composer); 255.13905 + } 255.13906 + return false; 255.13907 + }, 255.13908 + 255.13909 + state: function(composer, command) { 255.13910 + return false; 255.13911 + }, 255.13912 + 255.13913 + tryToPullLiLevel: function(liNodes, composer) { 255.13914 + var listNode, outerListNode, outerLiNode, list, prevLi, liNode, afterList, 255.13915 + found = false, 255.13916 + that = this; 255.13917 + 255.13918 + composer.selection.executeAndRestoreRangy(function() { 255.13919 + 255.13920 + for (var i = liNodes.length; i--;) { 255.13921 + liNode = liNodes[i]; 255.13922 + if (liNode.parentNode) { 255.13923 + listNode = liNode.parentNode; 255.13924 + 255.13925 + if (listNode.tagName === 'OL' || listNode.tagName === 'UL') { 255.13926 + found = true; 255.13927 + 255.13928 + outerListNode = wysihtml.dom.getParentElement(listNode.parentNode, { query: 'ol, ul' }, false, composer.element); 255.13929 + outerLiNode = wysihtml.dom.getParentElement(listNode.parentNode, { query: 'li' }, false, composer.element); 255.13930 + 255.13931 + if (outerListNode && outerLiNode) { 255.13932 + 255.13933 + if (liNode.nextSibling) { 255.13934 + afterList = that.getAfterList(listNode, liNode); 255.13935 + liNode.appendChild(afterList); 255.13936 + } 255.13937 + outerListNode.insertBefore(liNode, outerLiNode.nextSibling); 255.13938 + 255.13939 + } else { 255.13940 + 255.13941 + if (liNode.nextSibling) { 255.13942 + afterList = that.getAfterList(listNode, liNode); 255.13943 + liNode.appendChild(afterList); 255.13944 + } 255.13945 + 255.13946 + for (var j = liNode.childNodes.length; j--;) { 255.13947 + listNode.parentNode.insertBefore(liNode.childNodes[j], listNode.nextSibling); 255.13948 + } 255.13949 + 255.13950 + listNode.parentNode.insertBefore(document.createElement('br'), listNode.nextSibling); 255.13951 + liNode.parentNode.removeChild(liNode); 255.13952 + 255.13953 + } 255.13954 + 255.13955 + // cleanup 255.13956 + if (listNode.childNodes.length === 0) { 255.13957 + listNode.parentNode.removeChild(listNode); 255.13958 + } 255.13959 + } 255.13960 + } 255.13961 + } 255.13962 + 255.13963 + }); 255.13964 + return found; 255.13965 + }, 255.13966 + 255.13967 + getAfterList: function(listNode, liNode) { 255.13968 + var nodeName = listNode.nodeName, 255.13969 + newList = document.createElement(nodeName); 255.13970 + 255.13971 + while (liNode.nextSibling) { 255.13972 + newList.appendChild(liNode.nextSibling); 255.13973 + } 255.13974 + return newList; 255.13975 + } 255.13976 + 255.13977 + }; 255.13978 +}(wysihtml)); 255.13979 + 255.13980 +(function(wysihtml){ 255.13981 + wysihtml.commands.redo = { 255.13982 + exec: function(composer) { 255.13983 + return composer.undoManager.redo(); 255.13984 + }, 255.13985 + 255.13986 + state: function(composer) { 255.13987 + return false; 255.13988 + } 255.13989 + }; 255.13990 +}(wysihtml)); 255.13991 + 255.13992 +(function(wysihtml) { 255.13993 + 255.13994 + var nodeOptions = { 255.13995 + nodeName: "A" 255.13996 + }; 255.13997 + 255.13998 + wysihtml.commands.removeLink = { 255.13999 + exec: function(composer, command) { 255.14000 + wysihtml.commands.formatInline.remove(composer, command, nodeOptions); 255.14001 + }, 255.14002 + 255.14003 + state: function(composer, command) { 255.14004 + return wysihtml.commands.formatInline.state(composer, command, nodeOptions); 255.14005 + } 255.14006 + }; 255.14007 + 255.14008 +})(wysihtml); 255.14009 + 255.14010 +(function(wysihtml){ 255.14011 + wysihtml.commands.undo = { 255.14012 + exec: function(composer) { 255.14013 + return composer.undoManager.undo(); 255.14014 + }, 255.14015 + 255.14016 + state: function(composer) { 255.14017 + return false; 255.14018 + } 255.14019 + }; 255.14020 +}(wysihtml)); 255.14021 + 255.14022 +/** 255.14023 + * Undo Manager for wysihtml 255.14024 + * slightly inspired by http://rniwa.com/editing/undomanager.html#the-undomanager-interface 255.14025 + */ 255.14026 +(function(wysihtml) { 255.14027 + var Z_KEY = 90, 255.14028 + Y_KEY = 89, 255.14029 + BACKSPACE_KEY = 8, 255.14030 + DELETE_KEY = 46, 255.14031 + MAX_HISTORY_ENTRIES = 25, 255.14032 + DATA_ATTR_NODE = "data-wysihtml-selection-node", 255.14033 + DATA_ATTR_OFFSET = "data-wysihtml-selection-offset", 255.14034 + UNDO_HTML = '<span id="_wysihtml-undo" class="_wysihtml-temp">' + wysihtml.INVISIBLE_SPACE + '</span>', 255.14035 + REDO_HTML = '<span id="_wysihtml-redo" class="_wysihtml-temp">' + wysihtml.INVISIBLE_SPACE + '</span>', 255.14036 + dom = wysihtml.dom; 255.14037 + 255.14038 + function cleanTempElements(doc) { 255.14039 + var tempElement; 255.14040 + while (tempElement = doc.querySelector("._wysihtml-temp")) { 255.14041 + tempElement.parentNode.removeChild(tempElement); 255.14042 + } 255.14043 + } 255.14044 + 255.14045 + wysihtml.UndoManager = wysihtml.lang.Dispatcher.extend( 255.14046 + /** @scope wysihtml.UndoManager.prototype */ { 255.14047 + constructor: function(editor) { 255.14048 + this.editor = editor; 255.14049 + this.composer = editor.composer; 255.14050 + this.element = this.composer.element; 255.14051 + 255.14052 + this.position = 0; 255.14053 + this.historyStr = []; 255.14054 + this.historyDom = []; 255.14055 + 255.14056 + this.transact(); 255.14057 + 255.14058 + this._observe(); 255.14059 + }, 255.14060 + 255.14061 + _observe: function() { 255.14062 + var that = this, 255.14063 + doc = this.composer.sandbox.getDocument(), 255.14064 + lastKey; 255.14065 + 255.14066 + // Catch CTRL+Z and CTRL+Y 255.14067 + dom.observe(this.element, "keydown", function(event) { 255.14068 + if (event.altKey || (!event.ctrlKey && !event.metaKey)) { 255.14069 + return; 255.14070 + } 255.14071 + 255.14072 + var keyCode = event.keyCode, 255.14073 + isUndo = keyCode === Z_KEY && !event.shiftKey, 255.14074 + isRedo = (keyCode === Z_KEY && event.shiftKey) || (keyCode === Y_KEY); 255.14075 + 255.14076 + if (isUndo) { 255.14077 + that.undo(); 255.14078 + event.preventDefault(); 255.14079 + } else if (isRedo) { 255.14080 + that.redo(); 255.14081 + event.preventDefault(); 255.14082 + } 255.14083 + }); 255.14084 + 255.14085 + // Catch delete and backspace 255.14086 + dom.observe(this.element, "keydown", function(event) { 255.14087 + var keyCode = event.keyCode; 255.14088 + if (keyCode === lastKey) { 255.14089 + return; 255.14090 + } 255.14091 + 255.14092 + lastKey = keyCode; 255.14093 + 255.14094 + if (keyCode === BACKSPACE_KEY || keyCode === DELETE_KEY) { 255.14095 + that.transact(); 255.14096 + } 255.14097 + }); 255.14098 + 255.14099 + this.editor 255.14100 + .on("newword:composer", function() { 255.14101 + that.transact(); 255.14102 + }) 255.14103 + 255.14104 + .on("beforecommand:composer", function() { 255.14105 + that.transact(); 255.14106 + }); 255.14107 + }, 255.14108 + 255.14109 + transact: function() { 255.14110 + var previousHtml = this.historyStr[this.position - 1], 255.14111 + currentHtml = this.composer.getValue(false, false), 255.14112 + composerIsVisible = this.element.offsetWidth > 0 && this.element.offsetHeight > 0, 255.14113 + range, node, offset, element, position; 255.14114 + 255.14115 + if (currentHtml === previousHtml) { 255.14116 + return; 255.14117 + } 255.14118 + 255.14119 + var length = this.historyStr.length = this.historyDom.length = this.position; 255.14120 + if (length > MAX_HISTORY_ENTRIES) { 255.14121 + this.historyStr.shift(); 255.14122 + this.historyDom.shift(); 255.14123 + this.position--; 255.14124 + } 255.14125 + 255.14126 + this.position++; 255.14127 + 255.14128 + if (composerIsVisible) { 255.14129 + // Do not start saving selection if composer is not visible 255.14130 + range = this.composer.selection.getRange(); 255.14131 + node = (range && range.startContainer) ? range.startContainer : this.element; 255.14132 + offset = (range && range.startOffset) ? range.startOffset : 0; 255.14133 + 255.14134 + if (node.nodeType === wysihtml.ELEMENT_NODE) { 255.14135 + element = node; 255.14136 + } else { 255.14137 + element = node.parentNode; 255.14138 + position = this.getChildNodeIndex(element, node); 255.14139 + } 255.14140 + 255.14141 + element.setAttribute(DATA_ATTR_OFFSET, offset); 255.14142 + if (typeof(position) !== "undefined") { 255.14143 + element.setAttribute(DATA_ATTR_NODE, position); 255.14144 + } 255.14145 + } 255.14146 + 255.14147 + var clone = this.element.cloneNode(!!currentHtml); 255.14148 + this.historyDom.push(clone); 255.14149 + this.historyStr.push(currentHtml); 255.14150 + 255.14151 + if (element) { 255.14152 + element.removeAttribute(DATA_ATTR_OFFSET); 255.14153 + element.removeAttribute(DATA_ATTR_NODE); 255.14154 + } 255.14155 + 255.14156 + }, 255.14157 + 255.14158 + undo: function() { 255.14159 + this.transact(); 255.14160 + 255.14161 + if (!this.undoPossible()) { 255.14162 + return; 255.14163 + } 255.14164 + 255.14165 + this.set(this.historyDom[--this.position - 1]); 255.14166 + this.editor.fire("undo:composer"); 255.14167 + }, 255.14168 + 255.14169 + redo: function() { 255.14170 + if (!this.redoPossible()) { 255.14171 + return; 255.14172 + } 255.14173 + 255.14174 + this.set(this.historyDom[++this.position - 1]); 255.14175 + this.editor.fire("redo:composer"); 255.14176 + }, 255.14177 + 255.14178 + undoPossible: function() { 255.14179 + return this.position > 1; 255.14180 + }, 255.14181 + 255.14182 + redoPossible: function() { 255.14183 + return this.position < this.historyStr.length; 255.14184 + }, 255.14185 + 255.14186 + set: function(historyEntry) { 255.14187 + this.element.innerHTML = ""; 255.14188 + 255.14189 + var i = 0, 255.14190 + childNodes = historyEntry.childNodes, 255.14191 + length = historyEntry.childNodes.length; 255.14192 + 255.14193 + for (; i<length; i++) { 255.14194 + this.element.appendChild(childNodes[i].cloneNode(true)); 255.14195 + } 255.14196 + 255.14197 + // Restore selection 255.14198 + var offset, 255.14199 + node, 255.14200 + position; 255.14201 + 255.14202 + if (historyEntry.hasAttribute(DATA_ATTR_OFFSET)) { 255.14203 + offset = historyEntry.getAttribute(DATA_ATTR_OFFSET); 255.14204 + position = historyEntry.getAttribute(DATA_ATTR_NODE); 255.14205 + node = this.element; 255.14206 + } else { 255.14207 + node = this.element.querySelector("[" + DATA_ATTR_OFFSET + "]") || this.element; 255.14208 + offset = node.getAttribute(DATA_ATTR_OFFSET); 255.14209 + position = node.getAttribute(DATA_ATTR_NODE); 255.14210 + node.removeAttribute(DATA_ATTR_OFFSET); 255.14211 + node.removeAttribute(DATA_ATTR_NODE); 255.14212 + } 255.14213 + 255.14214 + if (position !== null) { 255.14215 + node = this.getChildNodeByIndex(node, +position); 255.14216 + } 255.14217 + 255.14218 + this.composer.selection.set(node, offset); 255.14219 + }, 255.14220 + 255.14221 + getChildNodeIndex: function(parent, child) { 255.14222 + var i = 0, 255.14223 + childNodes = parent.childNodes, 255.14224 + length = childNodes.length; 255.14225 + for (; i<length; i++) { 255.14226 + if (childNodes[i] === child) { 255.14227 + return i; 255.14228 + } 255.14229 + } 255.14230 + }, 255.14231 + 255.14232 + getChildNodeByIndex: function(parent, index) { 255.14233 + return parent.childNodes[index]; 255.14234 + } 255.14235 + }); 255.14236 +})(wysihtml); 255.14237 + 255.14238 +/** 255.14239 + * TODO: the following methods still need unit test coverage 255.14240 + */ 255.14241 +wysihtml.views.View = Base.extend( 255.14242 + /** @scope wysihtml.views.View.prototype */ { 255.14243 + constructor: function(parent, textareaElement, config) { 255.14244 + this.parent = parent; 255.14245 + this.element = textareaElement; 255.14246 + this.config = config; 255.14247 + if (!this.config.noTextarea) { 255.14248 + this._observeViewChange(); 255.14249 + } 255.14250 + }, 255.14251 + 255.14252 + _observeViewChange: function() { 255.14253 + var that = this; 255.14254 + this.parent.on("beforeload", function() { 255.14255 + that.parent.on("change_view", function(view) { 255.14256 + if (view === that.name) { 255.14257 + that.parent.currentView = that; 255.14258 + that.show(); 255.14259 + // Using tiny delay here to make sure that the placeholder is set before focusing 255.14260 + setTimeout(function() { that.focus(); }, 0); 255.14261 + } else { 255.14262 + that.hide(); 255.14263 + } 255.14264 + }); 255.14265 + }); 255.14266 + }, 255.14267 + 255.14268 + focus: function() { 255.14269 + if (this.element && this.element.ownerDocument && this.element.ownerDocument.querySelector(":focus") === this.element) { 255.14270 + return; 255.14271 + } 255.14272 + 255.14273 + try { if(this.element) { this.element.focus(); } } catch(e) {} 255.14274 + }, 255.14275 + 255.14276 + hide: function() { 255.14277 + this.element.style.display = "none"; 255.14278 + }, 255.14279 + 255.14280 + show: function() { 255.14281 + this.element.style.display = ""; 255.14282 + }, 255.14283 + 255.14284 + disable: function() { 255.14285 + this.element.setAttribute("disabled", "disabled"); 255.14286 + }, 255.14287 + 255.14288 + enable: function() { 255.14289 + this.element.removeAttribute("disabled"); 255.14290 + } 255.14291 +}); 255.14292 + 255.14293 +(function(wysihtml) { 255.14294 + var dom = wysihtml.dom, 255.14295 + browser = wysihtml.browser; 255.14296 + 255.14297 + wysihtml.views.Composer = wysihtml.views.View.extend( 255.14298 + /** @scope wysihtml.views.Composer.prototype */ { 255.14299 + name: "composer", 255.14300 + 255.14301 + constructor: function(parent, editableElement, config) { 255.14302 + this.base(parent, editableElement, config); 255.14303 + if (!this.config.noTextarea) { 255.14304 + this.textarea = this.parent.textarea; 255.14305 + } else { 255.14306 + this.editableArea = editableElement; 255.14307 + } 255.14308 + if (this.config.contentEditableMode) { 255.14309 + this._initContentEditableArea(); 255.14310 + } else { 255.14311 + this._initSandbox(); 255.14312 + } 255.14313 + }, 255.14314 + 255.14315 + clear: function() { 255.14316 + this.element.innerHTML = browser.displaysCaretInEmptyContentEditableCorrectly() ? "" : "<br>"; 255.14317 + }, 255.14318 + 255.14319 + getValue: function(parse, clearInternals) { 255.14320 + var value = this.isEmpty() ? "" : wysihtml.quirks.getCorrectInnerHTML(this.element); 255.14321 + if (parse !== false) { 255.14322 + value = this.parent.parse(value, (clearInternals === false) ? false : true); 255.14323 + } 255.14324 + return value; 255.14325 + }, 255.14326 + 255.14327 + setValue: function(html, parse) { 255.14328 + if (parse !== false) { 255.14329 + html = this.parent.parse(html); 255.14330 + } 255.14331 + 255.14332 + try { 255.14333 + this.element.innerHTML = html; 255.14334 + } catch (e) { 255.14335 + this.element.innerText = html; 255.14336 + } 255.14337 + }, 255.14338 + 255.14339 + cleanUp: function(rules) { 255.14340 + var bookmark; 255.14341 + if (this.selection && this.selection.isInThisEditable()) { 255.14342 + bookmark = rangy.saveSelection(this.win); 255.14343 + } 255.14344 + this.parent.parse(this.element, undefined, rules); 255.14345 + if (bookmark) { 255.14346 + rangy.restoreSelection(bookmark); 255.14347 + } 255.14348 + }, 255.14349 + 255.14350 + show: function() { 255.14351 + this.editableArea.style.display = this._displayStyle || ""; 255.14352 + 255.14353 + if (!this.config.noTextarea && !this.textarea.element.disabled) { 255.14354 + // Firefox needs this, otherwise contentEditable becomes uneditable 255.14355 + this.disable(); 255.14356 + this.enable(); 255.14357 + } 255.14358 + }, 255.14359 + 255.14360 + hide: function() { 255.14361 + this._displayStyle = dom.getStyle("display").from(this.editableArea); 255.14362 + if (this._displayStyle === "none") { 255.14363 + this._displayStyle = null; 255.14364 + } 255.14365 + this.editableArea.style.display = "none"; 255.14366 + }, 255.14367 + 255.14368 + disable: function() { 255.14369 + this.parent.fire("disable:composer"); 255.14370 + this.element.removeAttribute("contentEditable"); 255.14371 + }, 255.14372 + 255.14373 + enable: function() { 255.14374 + this.parent.fire("enable:composer"); 255.14375 + this.element.setAttribute("contentEditable", "true"); 255.14376 + }, 255.14377 + 255.14378 + focus: function(setToEnd) { 255.14379 + // IE 8 fires the focus event after .focus() 255.14380 + // This is needed by our simulate_placeholder.js to work 255.14381 + // therefore we clear it ourselves this time 255.14382 + if (wysihtml.browser.doesAsyncFocus() && this.hasPlaceholderSet()) { 255.14383 + this.clear(); 255.14384 + } 255.14385 + 255.14386 + this.base(); 255.14387 + 255.14388 + var lastChild = this.element.lastChild; 255.14389 + if (setToEnd && lastChild && this.selection) { 255.14390 + if (lastChild.nodeName === "BR") { 255.14391 + this.selection.setBefore(this.element.lastChild); 255.14392 + } else { 255.14393 + this.selection.setAfter(this.element.lastChild); 255.14394 + } 255.14395 + } 255.14396 + }, 255.14397 + 255.14398 + getScrollPos: function() { 255.14399 + if (this.doc && this.win) { 255.14400 + var pos = {}; 255.14401 + 255.14402 + if (typeof this.win.pageYOffset !== "undefined") { 255.14403 + pos.y = this.win.pageYOffset; 255.14404 + } else { 255.14405 + pos.y = (this.doc.documentElement || this.doc.body.parentNode || this.doc.body).scrollTop; 255.14406 + } 255.14407 + 255.14408 + if (typeof this.win.pageXOffset !== "undefined") { 255.14409 + pos.x = this.win.pageXOffset; 255.14410 + } else { 255.14411 + pos.x = (this.doc.documentElement || this.doc.body.parentNode || this.doc.body).scrollLeft; 255.14412 + } 255.14413 + 255.14414 + return pos; 255.14415 + } 255.14416 + }, 255.14417 + 255.14418 + setScrollPos: function(pos) { 255.14419 + if (pos && typeof pos.x !== "undefined" && typeof pos.y !== "undefined") { 255.14420 + this.win.scrollTo(pos.x, pos.y); 255.14421 + } 255.14422 + }, 255.14423 + 255.14424 + getTextContent: function() { 255.14425 + return dom.getTextContent(this.element); 255.14426 + }, 255.14427 + 255.14428 + hasPlaceholderSet: function() { 255.14429 + return this.getTextContent() == ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")) && this.placeholderSet; 255.14430 + }, 255.14431 + 255.14432 + isEmpty: function() { 255.14433 + var innerHTML = this.element.innerHTML.toLowerCase(); 255.14434 + return (/^(\s|<br>|<\/br>|<p>|<\/p>)*$/i).test(innerHTML) || 255.14435 + innerHTML === "" || 255.14436 + innerHTML === "<br>" || 255.14437 + innerHTML === "<p></p>" || 255.14438 + innerHTML === "<p><br></p>" || 255.14439 + this.hasPlaceholderSet(); 255.14440 + }, 255.14441 + 255.14442 + _initContentEditableArea: function() { 255.14443 + var that = this; 255.14444 + if (this.config.noTextarea) { 255.14445 + this.sandbox = new dom.ContentEditableArea(function() { 255.14446 + that._create(); 255.14447 + }, { 255.14448 + className: this.config.classNames.sandbox 255.14449 + }, this.editableArea); 255.14450 + } else { 255.14451 + this.sandbox = new dom.ContentEditableArea(function() { 255.14452 + that._create(); 255.14453 + }, { 255.14454 + className: this.config.classNames.sandbox 255.14455 + }); 255.14456 + this.editableArea = this.sandbox.getContentEditable(); 255.14457 + dom.insert(this.editableArea).after(this.textarea.element); 255.14458 + this._createWysiwygFormField(); 255.14459 + } 255.14460 + }, 255.14461 + 255.14462 + _initSandbox: function() { 255.14463 + var that = this; 255.14464 + this.sandbox = new dom.Sandbox(function() { 255.14465 + that._create(); 255.14466 + }, { 255.14467 + stylesheets: this.config.stylesheets, 255.14468 + className: this.config.classNames.sandbox 255.14469 + }); 255.14470 + this.editableArea = this.sandbox.getIframe(); 255.14471 + 255.14472 + var textareaElement = this.textarea.element; 255.14473 + dom.insert(this.editableArea).after(textareaElement); 255.14474 + 255.14475 + this._createWysiwygFormField(); 255.14476 + }, 255.14477 + 255.14478 + // Creates hidden field which tells the server after submit, that the user used an wysiwyg editor 255.14479 + _createWysiwygFormField: function() { 255.14480 + if (this.textarea.element.form) { 255.14481 + var hiddenField = document.createElement("input"); 255.14482 + hiddenField.type = "hidden"; 255.14483 + hiddenField.name = "_wysihtml_mode"; 255.14484 + hiddenField.value = 1; 255.14485 + dom.insert(hiddenField).after(this.textarea.element); 255.14486 + } 255.14487 + }, 255.14488 + 255.14489 + _create: function() { 255.14490 + var that = this; 255.14491 + this.doc = this.sandbox.getDocument(); 255.14492 + this.win = this.sandbox.getWindow(); 255.14493 + this.element = (this.config.contentEditableMode) ? this.sandbox.getContentEditable() : this.doc.body; 255.14494 + if (!this.config.noTextarea) { 255.14495 + this.textarea = this.parent.textarea; 255.14496 + this.element.innerHTML = this.textarea.getValue(true, false); 255.14497 + } else { 255.14498 + this.cleanUp(); // cleans contenteditable on initiation as it may contain html 255.14499 + } 255.14500 + 255.14501 + // Make sure our selection handler is ready 255.14502 + this.selection = new wysihtml.Selection(this.parent, this.element, this.config.classNames.uneditableContainer); 255.14503 + 255.14504 + // Make sure commands dispatcher is ready 255.14505 + this.commands = new wysihtml.Commands(this.parent); 255.14506 + 255.14507 + if (!this.config.noTextarea) { 255.14508 + dom.copyAttributes([ 255.14509 + "className", "spellcheck", "title", "lang", "dir", "accessKey" 255.14510 + ]).from(this.textarea.element).to(this.element); 255.14511 + } 255.14512 + 255.14513 + this._initAutoLinking(); 255.14514 + 255.14515 + dom.addClass(this.element, this.config.classNames.composer); 255.14516 + // 255.14517 + // Make the editor look like the original textarea, by syncing styles 255.14518 + if (this.config.style && !this.config.contentEditableMode) { 255.14519 + this.style(); 255.14520 + } 255.14521 + 255.14522 + this.observe(); 255.14523 + 255.14524 + var name = this.config.name; 255.14525 + if (name) { 255.14526 + dom.addClass(this.element, name); 255.14527 + if (!this.config.contentEditableMode) { dom.addClass(this.editableArea, name); } 255.14528 + } 255.14529 + 255.14530 + this.enable(); 255.14531 + 255.14532 + if (!this.config.noTextarea && this.textarea.element.disabled) { 255.14533 + this.disable(); 255.14534 + } 255.14535 + 255.14536 + // Simulate html5 placeholder attribute on contentEditable element 255.14537 + var placeholderText = typeof(this.config.placeholder) === "string" 255.14538 + ? this.config.placeholder 255.14539 + : ((this.config.noTextarea) ? this.editableArea.getAttribute("data-placeholder") : this.textarea.element.getAttribute("placeholder")); 255.14540 + if (placeholderText) { 255.14541 + dom.simulatePlaceholder(this.parent, this, placeholderText, this.config.classNames.placeholder); 255.14542 + } 255.14543 + 255.14544 + // Make sure that the browser avoids using inline styles whenever possible 255.14545 + this.commands.exec("styleWithCSS", false); 255.14546 + 255.14547 + this._initObjectResizing(); 255.14548 + this._initUndoManager(); 255.14549 + this._initLineBreaking(); 255.14550 + 255.14551 + // Simulate html5 autofocus on contentEditable element 255.14552 + // This doesn't work on IOS (5.1.1) 255.14553 + if (!this.config.noTextarea && (this.textarea.element.hasAttribute("autofocus") || document.querySelector(":focus") == this.textarea.element) && !browser.isIos()) { 255.14554 + setTimeout(function() { that.focus(true); }, 100); 255.14555 + } 255.14556 + 255.14557 + // IE sometimes leaves a single paragraph, which can't be removed by the user 255.14558 + if (!browser.clearsContentEditableCorrectly()) { 255.14559 + wysihtml.quirks.ensureProperClearing(this); 255.14560 + } 255.14561 + 255.14562 + // Set up a sync that makes sure that textarea and editor have the same content 255.14563 + if (this.initSync && this.config.sync) { 255.14564 + this.initSync(); 255.14565 + } 255.14566 + 255.14567 + // Okay hide the textarea, we are ready to go 255.14568 + if (!this.config.noTextarea) { this.textarea.hide(); } 255.14569 + 255.14570 + // Fire global (before-)load event 255.14571 + this.parent.fire("beforeload").fire("load"); 255.14572 + }, 255.14573 + 255.14574 + _initAutoLinking: function() { 255.14575 + var that = this, 255.14576 + supportsDisablingOfAutoLinking = browser.canDisableAutoLinking(), 255.14577 + supportsAutoLinking = browser.doesAutoLinkingInContentEditable(); 255.14578 + 255.14579 + if (supportsDisablingOfAutoLinking) { 255.14580 + this.commands.exec("AutoUrlDetect", false, false); 255.14581 + } 255.14582 + 255.14583 + if (!this.config.autoLink) { 255.14584 + return; 255.14585 + } 255.14586 + 255.14587 + // Only do the auto linking by ourselves when the browser doesn't support auto linking 255.14588 + // OR when he supports auto linking but we were able to turn it off (IE9+) 255.14589 + if (!supportsAutoLinking || (supportsAutoLinking && supportsDisablingOfAutoLinking)) { 255.14590 + this.parent.on("newword:composer", function() { 255.14591 + if (dom.getTextContent(that.element).match(dom.autoLink.URL_REG_EXP)) { 255.14592 + var nodeWithSelection = that.selection.getSelectedNode(), 255.14593 + uneditables = that.element.querySelectorAll("." + that.config.classNames.uneditableContainer), 255.14594 + isInUneditable = false; 255.14595 + 255.14596 + for (var i = uneditables.length; i--;) { 255.14597 + if (wysihtml.dom.contains(uneditables[i], nodeWithSelection)) { 255.14598 + isInUneditable = true; 255.14599 + } 255.14600 + } 255.14601 + 255.14602 + if (!isInUneditable) dom.autoLink(nodeWithSelection, [that.config.classNames.uneditableContainer]); 255.14603 + } 255.14604 + }); 255.14605 + 255.14606 + dom.observe(this.element, "blur", function() { 255.14607 + dom.autoLink(that.element, [that.config.classNames.uneditableContainer]); 255.14608 + }); 255.14609 + } 255.14610 + 255.14611 + // Assuming we have the following: 255.14612 + // <a href="http://www.google.de">http://www.google.de</a> 255.14613 + // If a user now changes the url in the innerHTML we want to make sure that 255.14614 + // it's synchronized with the href attribute (as long as the innerHTML is still a url) 255.14615 + var // Use a live NodeList to check whether there are any links in the document 255.14616 + links = this.sandbox.getDocument().getElementsByTagName("a"), 255.14617 + // The autoLink helper method reveals a reg exp to detect correct urls 255.14618 + urlRegExp = dom.autoLink.URL_REG_EXP, 255.14619 + getTextContent = function(element) { 255.14620 + var textContent = wysihtml.lang.string(dom.getTextContent(element)).trim(); 255.14621 + if (textContent.substr(0, 4) === "www.") { 255.14622 + textContent = "http://" + textContent; 255.14623 + } 255.14624 + return textContent; 255.14625 + }; 255.14626 + 255.14627 + dom.observe(this.element, "keydown", function(event) { 255.14628 + if (!links.length) { 255.14629 + return; 255.14630 + } 255.14631 + 255.14632 + var selectedNode = that.selection.getSelectedNode(event.target.ownerDocument), 255.14633 + link = dom.getParentElement(selectedNode, { query: "a" }, 4), 255.14634 + textContent; 255.14635 + 255.14636 + if (!link) { 255.14637 + return; 255.14638 + } 255.14639 + 255.14640 + textContent = getTextContent(link); 255.14641 + // keydown is fired before the actual content is changed 255.14642 + // therefore we set a timeout to change the href 255.14643 + setTimeout(function() { 255.14644 + var newTextContent = getTextContent(link); 255.14645 + if (newTextContent === textContent) { 255.14646 + return; 255.14647 + } 255.14648 + 255.14649 + // Only set href when new href looks like a valid url 255.14650 + if (newTextContent.match(urlRegExp)) { 255.14651 + link.setAttribute("href", newTextContent); 255.14652 + } 255.14653 + }, 0); 255.14654 + }); 255.14655 + }, 255.14656 + 255.14657 + _initObjectResizing: function() { 255.14658 + this.commands.exec("enableObjectResizing", true); 255.14659 + 255.14660 + // IE sets inline styles after resizing objects 255.14661 + // The following lines make sure that the width/height css properties 255.14662 + // are copied over to the width/height attributes 255.14663 + if (browser.supportsEvent("resizeend")) { 255.14664 + var properties = ["width", "height"], 255.14665 + propertiesLength = properties.length, 255.14666 + element = this.element; 255.14667 + 255.14668 + dom.observe(element, "resizeend", function(event) { 255.14669 + var target = event.target || event.srcElement, 255.14670 + style = target.style, 255.14671 + i = 0, 255.14672 + property; 255.14673 + 255.14674 + if (target.nodeName !== "IMG") { 255.14675 + return; 255.14676 + } 255.14677 + 255.14678 + for (; i<propertiesLength; i++) { 255.14679 + property = properties[i]; 255.14680 + if (style[property]) { 255.14681 + target.setAttribute(property, parseInt(style[property], 10)); 255.14682 + style[property] = ""; 255.14683 + } 255.14684 + } 255.14685 + 255.14686 + // After resizing IE sometimes forgets to remove the old resize handles 255.14687 + wysihtml.quirks.redraw(element); 255.14688 + }); 255.14689 + } 255.14690 + }, 255.14691 + 255.14692 + _initUndoManager: function() { 255.14693 + this.undoManager = new wysihtml.UndoManager(this.parent); 255.14694 + }, 255.14695 + 255.14696 + _initLineBreaking: function() { 255.14697 + var that = this, 255.14698 + USE_NATIVE_LINE_BREAK_INSIDE_TAGS = "li, p, h1, h2, h3, h4, h5, h6", 255.14699 + LIST_TAGS = "ul, ol, menu"; 255.14700 + 255.14701 + function adjust(selectedNode) { 255.14702 + var parentElement = dom.getParentElement(selectedNode, { query: "p, div" }, 2); 255.14703 + if (parentElement && dom.contains(that.element, parentElement)) { 255.14704 + that.selection.executeAndRestoreRangy(function() { 255.14705 + if (that.config.useLineBreaks) { 255.14706 + if (!parentElement.firstChild || (parentElement.firstChild === parentElement.lastChild && parentElement.firstChild.nodeType === 1 && parentElement.firstChild.classList.contains('rangySelectionBoundary'))) { 255.14707 + parentElement.appendChild(that.doc.createElement('br')); 255.14708 + } 255.14709 + dom.replaceWithChildNodes(parentElement); 255.14710 + } else if (parentElement.nodeName !== "P") { 255.14711 + dom.renameElement(parentElement, "p"); 255.14712 + } 255.14713 + }); 255.14714 + } 255.14715 + } 255.14716 + 255.14717 + // Ensures when editor is empty and not line breaks mode, the inital state has a paragraph in it on focus with caret inside paragraph 255.14718 + if (!this.config.useLineBreaks) { 255.14719 + dom.observe(this.element, ["focus"], function() { 255.14720 + if (that.isEmpty()) { 255.14721 + setTimeout(function() { 255.14722 + var paragraph = that.doc.createElement("P"); 255.14723 + that.element.innerHTML = ""; 255.14724 + that.element.appendChild(paragraph); 255.14725 + if (!browser.displaysCaretInEmptyContentEditableCorrectly()) { 255.14726 + paragraph.innerHTML = "<br>"; 255.14727 + that.selection.setBefore(paragraph.firstChild); 255.14728 + } else { 255.14729 + that.selection.selectNode(paragraph, true); 255.14730 + } 255.14731 + }, 0); 255.14732 + } 255.14733 + }); 255.14734 + } 255.14735 + 255.14736 + dom.observe(this.element, "keydown", function(event) { 255.14737 + var keyCode = event.keyCode; 255.14738 + 255.14739 + if (event.shiftKey || event.ctrlKey || event.defaultPrevented) { 255.14740 + return; 255.14741 + } 255.14742 + 255.14743 + if (keyCode !== wysihtml.ENTER_KEY && keyCode !== wysihtml.BACKSPACE_KEY) { 255.14744 + return; 255.14745 + } 255.14746 + var blockElement = dom.getParentElement(that.selection.getSelectedNode(), { query: USE_NATIVE_LINE_BREAK_INSIDE_TAGS }, 4); 255.14747 + if (blockElement) { 255.14748 + setTimeout(function() { 255.14749 + // Unwrap paragraph after leaving a list or a H1-6 255.14750 + var selectedNode = that.selection.getSelectedNode(), 255.14751 + list; 255.14752 + 255.14753 + if (blockElement.nodeName === "LI") { 255.14754 + if (!selectedNode) { 255.14755 + return; 255.14756 + } 255.14757 + 255.14758 + list = dom.getParentElement(selectedNode, { query: LIST_TAGS }, 2); 255.14759 + 255.14760 + if (!list) { 255.14761 + adjust(selectedNode); 255.14762 + } 255.14763 + } 255.14764 + 255.14765 + if (keyCode === wysihtml.ENTER_KEY && blockElement.nodeName.match(/^H[1-6]$/)) { 255.14766 + adjust(selectedNode); 255.14767 + } 255.14768 + }, 0); 255.14769 + return; 255.14770 + } 255.14771 + if (that.config.useLineBreaks && keyCode === wysihtml.ENTER_KEY && !wysihtml.browser.insertsLineBreaksOnReturn()) { 255.14772 + event.preventDefault(); 255.14773 + that.commands.exec("insertLineBreak"); 255.14774 + } 255.14775 + }); 255.14776 + } 255.14777 + }); 255.14778 +})(wysihtml); 255.14779 + 255.14780 +(function(wysihtml) { 255.14781 + var dom = wysihtml.dom, 255.14782 + doc = document, 255.14783 + win = window, 255.14784 + HOST_TEMPLATE = doc.createElement("div"), 255.14785 + /** 255.14786 + * Styles to copy from textarea to the composer element 255.14787 + */ 255.14788 + TEXT_FORMATTING = [ 255.14789 + "background-color", 255.14790 + "color", "cursor", 255.14791 + "font-family", "font-size", "font-style", "font-variant", "font-weight", 255.14792 + "line-height", "letter-spacing", 255.14793 + "text-align", "text-decoration", "text-indent", "text-rendering", 255.14794 + "word-break", "word-wrap", "word-spacing" 255.14795 + ], 255.14796 + /** 255.14797 + * Styles to copy from textarea to the iframe 255.14798 + */ 255.14799 + BOX_FORMATTING = [ 255.14800 + "background-color", 255.14801 + "border-collapse", 255.14802 + "border-bottom-color", "border-bottom-style", "border-bottom-width", 255.14803 + "border-left-color", "border-left-style", "border-left-width", 255.14804 + "border-right-color", "border-right-style", "border-right-width", 255.14805 + "border-top-color", "border-top-style", "border-top-width", 255.14806 + "clear", "display", "float", 255.14807 + "margin-bottom", "margin-left", "margin-right", "margin-top", 255.14808 + "outline-color", "outline-offset", "outline-width", "outline-style", 255.14809 + "padding-left", "padding-right", "padding-top", "padding-bottom", 255.14810 + "position", "top", "left", "right", "bottom", "z-index", 255.14811 + "vertical-align", "text-align", 255.14812 + "-webkit-box-sizing", "-moz-box-sizing", "-ms-box-sizing", "box-sizing", 255.14813 + "-webkit-box-shadow", "-moz-box-shadow", "-ms-box-shadow","box-shadow", 255.14814 + "-webkit-border-top-right-radius", "-moz-border-radius-topright", "border-top-right-radius", 255.14815 + "-webkit-border-bottom-right-radius", "-moz-border-radius-bottomright", "border-bottom-right-radius", 255.14816 + "-webkit-border-bottom-left-radius", "-moz-border-radius-bottomleft", "border-bottom-left-radius", 255.14817 + "-webkit-border-top-left-radius", "-moz-border-radius-topleft", "border-top-left-radius", 255.14818 + "width", "height" 255.14819 + ], 255.14820 + ADDITIONAL_CSS_RULES = [ 255.14821 + "html { height: 100%; }", 255.14822 + "body { height: 100%; padding: 1px 0 0 0; margin: -1px 0 0 0; }", 255.14823 + "body > p:first-child { margin-top: 0; }", 255.14824 + "._wysihtml-temp { display: none; }", 255.14825 + wysihtml.browser.isGecko ? 255.14826 + "body.placeholder { color: graytext !important; }" : 255.14827 + "body.placeholder { color: #a9a9a9 !important; }", 255.14828 + // Ensure that user see's broken images and can delete them 255.14829 + "img:-moz-broken { -moz-force-broken-image-icon: 1; height: 24px; width: 24px; }" 255.14830 + ]; 255.14831 + 255.14832 + /** 255.14833 + * With "setActive" IE offers a smart way of focusing elements without scrolling them into view: 255.14834 + * http://msdn.microsoft.com/en-us/library/ms536738(v=vs.85).aspx 255.14835 + * 255.14836 + * Other browsers need a more hacky way: (pssst don't tell my mama) 255.14837 + * In order to prevent the element being scrolled into view when focusing it, we simply 255.14838 + * move it out of the scrollable area, focus it, and reset it's position 255.14839 + */ 255.14840 + var focusWithoutScrolling = function(element) { 255.14841 + if (element.setActive) { 255.14842 + // Following line could cause a js error when the textarea is invisible 255.14843 + // See https://github.com/xing/wysihtml5/issues/9 255.14844 + try { element.setActive(); } catch(e) {} 255.14845 + } else { 255.14846 + var elementStyle = element.style, 255.14847 + originalScrollTop = doc.documentElement.scrollTop || doc.body.scrollTop, 255.14848 + originalScrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft, 255.14849 + originalStyles = { 255.14850 + position: elementStyle.position, 255.14851 + top: elementStyle.top, 255.14852 + left: elementStyle.left, 255.14853 + WebkitUserSelect: elementStyle.WebkitUserSelect 255.14854 + }; 255.14855 + 255.14856 + dom.setStyles({ 255.14857 + position: "absolute", 255.14858 + top: "-99999px", 255.14859 + left: "-99999px", 255.14860 + // Don't ask why but temporarily setting -webkit-user-select to none makes the whole thing performing smoother 255.14861 + WebkitUserSelect: "none" 255.14862 + }).on(element); 255.14863 + 255.14864 + element.focus(); 255.14865 + 255.14866 + dom.setStyles(originalStyles).on(element); 255.14867 + 255.14868 + if (win.scrollTo) { 255.14869 + // Some browser extensions unset this method to prevent annoyances 255.14870 + // "Better PopUp Blocker" for Chrome http://code.google.com/p/betterpopupblocker/source/browse/trunk/blockStart.js#100 255.14871 + // Issue: http://code.google.com/p/betterpopupblocker/issues/detail?id=1 255.14872 + win.scrollTo(originalScrollLeft, originalScrollTop); 255.14873 + } 255.14874 + } 255.14875 + }; 255.14876 + 255.14877 + 255.14878 + wysihtml.views.Composer.prototype.style = function() { 255.14879 + var that = this, 255.14880 + originalActiveElement = doc.querySelector(":focus"), 255.14881 + textareaElement = this.textarea.element, 255.14882 + hasPlaceholder = textareaElement.hasAttribute("placeholder"), 255.14883 + originalPlaceholder = hasPlaceholder && textareaElement.getAttribute("placeholder"), 255.14884 + originalDisplayValue = textareaElement.style.display, 255.14885 + originalDisabled = textareaElement.disabled, 255.14886 + displayValueForCopying; 255.14887 + 255.14888 + this.focusStylesHost = HOST_TEMPLATE.cloneNode(false); 255.14889 + this.blurStylesHost = HOST_TEMPLATE.cloneNode(false); 255.14890 + this.disabledStylesHost = HOST_TEMPLATE.cloneNode(false); 255.14891 + 255.14892 + // Remove placeholder before copying (as the placeholder has an affect on the computed style) 255.14893 + if (hasPlaceholder) { 255.14894 + textareaElement.removeAttribute("placeholder"); 255.14895 + } 255.14896 + 255.14897 + if (textareaElement === originalActiveElement) { 255.14898 + textareaElement.blur(); 255.14899 + } 255.14900 + 255.14901 + // enable for copying styles 255.14902 + textareaElement.disabled = false; 255.14903 + 255.14904 + // set textarea to display="none" to get cascaded styles via getComputedStyle 255.14905 + textareaElement.style.display = displayValueForCopying = "none"; 255.14906 + 255.14907 + if ((textareaElement.getAttribute("rows") && dom.getStyle("height").from(textareaElement) === "auto") || 255.14908 + (textareaElement.getAttribute("cols") && dom.getStyle("width").from(textareaElement) === "auto")) { 255.14909 + textareaElement.style.display = displayValueForCopying = originalDisplayValue; 255.14910 + } 255.14911 + 255.14912 + // --------- iframe styles (has to be set before editor styles, otherwise IE9 sets wrong fontFamily on blurStylesHost) --------- 255.14913 + dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.editableArea).andTo(this.blurStylesHost); 255.14914 + 255.14915 + // --------- editor styles --------- 255.14916 + dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.element).andTo(this.blurStylesHost); 255.14917 + 255.14918 + // --------- apply standard rules --------- 255.14919 + dom.insertCSS(ADDITIONAL_CSS_RULES).into(this.element.ownerDocument); 255.14920 + 255.14921 + // --------- :disabled styles --------- 255.14922 + textareaElement.disabled = true; 255.14923 + dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.disabledStylesHost); 255.14924 + dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.disabledStylesHost); 255.14925 + textareaElement.disabled = originalDisabled; 255.14926 + 255.14927 + // --------- :focus styles --------- 255.14928 + textareaElement.style.display = originalDisplayValue; 255.14929 + focusWithoutScrolling(textareaElement); 255.14930 + textareaElement.style.display = displayValueForCopying; 255.14931 + 255.14932 + dom.copyStyles(BOX_FORMATTING).from(textareaElement).to(this.focusStylesHost); 255.14933 + dom.copyStyles(TEXT_FORMATTING).from(textareaElement).to(this.focusStylesHost); 255.14934 + 255.14935 + // reset textarea 255.14936 + textareaElement.style.display = originalDisplayValue; 255.14937 + 255.14938 + dom.copyStyles(["display"]).from(textareaElement).to(this.editableArea); 255.14939 + 255.14940 + // Make sure that we don't change the display style of the iframe when copying styles oblur/onfocus 255.14941 + // this is needed for when the change_view event is fired where the iframe is hidden and then 255.14942 + // the blur event fires and re-displays it 255.14943 + var boxFormattingStyles = wysihtml.lang.array(BOX_FORMATTING).without(["display"]); 255.14944 + 255.14945 + // --------- restore focus --------- 255.14946 + if (originalActiveElement) { 255.14947 + focusWithoutScrolling(originalActiveElement); 255.14948 + } else { 255.14949 + textareaElement.blur(); 255.14950 + } 255.14951 + 255.14952 + // --------- restore placeholder --------- 255.14953 + if (hasPlaceholder) { 255.14954 + textareaElement.setAttribute("placeholder", originalPlaceholder); 255.14955 + } 255.14956 + 255.14957 + // --------- Sync focus/blur styles --------- 255.14958 + this.parent.on("focus:composer", function() { 255.14959 + dom.copyStyles(boxFormattingStyles) .from(that.focusStylesHost).to(that.editableArea); 255.14960 + dom.copyStyles(TEXT_FORMATTING) .from(that.focusStylesHost).to(that.element); 255.14961 + }); 255.14962 + 255.14963 + this.parent.on("blur:composer", function() { 255.14964 + dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea); 255.14965 + dom.copyStyles(TEXT_FORMATTING) .from(that.blurStylesHost).to(that.element); 255.14966 + }); 255.14967 + 255.14968 + this.parent.observe("disable:composer", function() { 255.14969 + dom.copyStyles(boxFormattingStyles) .from(that.disabledStylesHost).to(that.editableArea); 255.14970 + dom.copyStyles(TEXT_FORMATTING) .from(that.disabledStylesHost).to(that.element); 255.14971 + }); 255.14972 + 255.14973 + this.parent.observe("enable:composer", function() { 255.14974 + dom.copyStyles(boxFormattingStyles) .from(that.blurStylesHost).to(that.editableArea); 255.14975 + dom.copyStyles(TEXT_FORMATTING) .from(that.blurStylesHost).to(that.element); 255.14976 + }); 255.14977 + 255.14978 + return this; 255.14979 + }; 255.14980 +})(wysihtml); 255.14981 + 255.14982 +/** 255.14983 + * Taking care of events 255.14984 + * - Simulating 'change' event on contentEditable element 255.14985 + * - Handling drag & drop logic 255.14986 + * - Catch paste events 255.14987 + * - Dispatch proprietary newword:composer event 255.14988 + * - Keyboard shortcuts 255.14989 + */ 255.14990 +(function(wysihtml) { 255.14991 + var dom = wysihtml.dom, 255.14992 + domNode = dom.domNode, 255.14993 + browser = wysihtml.browser, 255.14994 + /** 255.14995 + * Map keyCodes to query commands 255.14996 + */ 255.14997 + shortcuts = { 255.14998 + "66": "bold", // B 255.14999 + "73": "italic", // I 255.15000 + "85": "underline" // U 255.15001 + }; 255.15002 + 255.15003 + var actions = { 255.15004 + 255.15005 + // Adds multiple eventlisteners to target, bound to one callback 255.15006 + // TODO: If needed elsewhere make it part of wysihtml.dom or sth 255.15007 + addListeners: function (target, events, callback) { 255.15008 + for(var i = 0, max = events.length; i < max; i++) { 255.15009 + target.addEventListener(events[i], callback, false); 255.15010 + } 255.15011 + }, 255.15012 + 255.15013 + // Removes multiple eventlisteners from target, bound to one callback 255.15014 + // TODO: If needed elsewhere make it part of wysihtml.dom or sth 255.15015 + removeListeners: function (target, events, callback) { 255.15016 + for(var i = 0, max = events.length; i < max; i++) { 255.15017 + target.removeEventListener(events[i], callback, false); 255.15018 + } 255.15019 + }, 255.15020 + 255.15021 + // Override for giving user ability to delete last line break in table cell 255.15022 + fixLastBrDeletionInTable: function(composer, force) { 255.15023 + if (composer.selection.caretIsInTheEndOfNode()) { 255.15024 + var sel = composer.selection.getSelection(), 255.15025 + aNode = sel.anchorNode; 255.15026 + if (aNode && aNode.nodeType === 1 && (wysihtml.dom.getParentElement(aNode, {query: 'td, th'}, false, composer.element) || force)) { 255.15027 + var nextNode = aNode.childNodes[sel.anchorOffset]; 255.15028 + if (nextNode && nextNode.nodeType === 1 & nextNode.nodeName === "BR") { 255.15029 + nextNode.parentNode.removeChild(nextNode); 255.15030 + return true; 255.15031 + } 255.15032 + } 255.15033 + } 255.15034 + return false; 255.15035 + }, 255.15036 + 255.15037 + // If found an uneditable before caret then notify it before deletion 255.15038 + handleUneditableDeletion: function(composer) { 255.15039 + var before = composer.selection.getBeforeSelection(true); 255.15040 + if (before && (before.type === "element" || before.type === "leafnode") && before.node.nodeType === 1 && before.node.classList.contains(composer.config.classNames.uneditableContainer)) { 255.15041 + if (actions.fixLastBrDeletionInTable(composer, true)) { 255.15042 + return true; 255.15043 + } 255.15044 + try { 255.15045 + var ev = new CustomEvent("wysihtml:uneditable:delete", {bubbles: true, cancelable: false}); 255.15046 + before.node.dispatchEvent(ev); 255.15047 + } catch (err) {} 255.15048 + before.node.parentNode.removeChild(before.node); 255.15049 + return true; 255.15050 + } 255.15051 + return false; 255.15052 + }, 255.15053 + 255.15054 + // Deletion with caret in the beginning of headings and other block elvel elements needs special attention 255.15055 + // Not allways does it concate text to previous block node correctly (browsers do unexpected miracles here especially webkit) 255.15056 + fixDeleteInTheBeginningOfBlock: function(composer) { 255.15057 + var selection = composer.selection, 255.15058 + prevNode = selection.getPreviousNode(); 255.15059 + 255.15060 + if (selection.caretIsFirstInSelection(wysihtml.browser.usesControlRanges()) && prevNode) { 255.15061 + if (prevNode.nodeType === 1 && 255.15062 + wysihtml.dom.domNode(prevNode).is.block() && 255.15063 + !domNode(prevNode).test({ 255.15064 + query: "ol, ul, table, tr, dl" 255.15065 + }) 255.15066 + ) { 255.15067 + if ((/^\s*$/).test(prevNode.textContent || prevNode.innerText)) { 255.15068 + // If heading is empty remove the heading node 255.15069 + prevNode.parentNode.removeChild(prevNode); 255.15070 + return true; 255.15071 + } else { 255.15072 + if (prevNode.lastChild) { 255.15073 + var selNode = prevNode.lastChild, 255.15074 + selectedNode = selection.getSelectedNode(), 255.15075 + commonAncestorNode = domNode(prevNode).commonAncestor(selectedNode, composer.element), 255.15076 + curNode = wysihtml.dom.getParentElement(selectedNode, { 255.15077 + query: "h1, h2, h3, h4, h5, h6, p, pre, div, blockquote" 255.15078 + }, false, commonAncestorNode || composer.element); 255.15079 + 255.15080 + if (curNode) { 255.15081 + domNode(curNode).transferContentTo(prevNode, true); 255.15082 + selection.setAfter(selNode); 255.15083 + return true; 255.15084 + } else if (wysihtml.browser.usesControlRanges()) { 255.15085 + selectedNode = selection.getCaretNode(); 255.15086 + domNode(selectedNode).transferContentTo(prevNode, true); 255.15087 + selection.setAfter(selNode); 255.15088 + return true; 255.15089 + } 255.15090 + } 255.15091 + } 255.15092 + } 255.15093 + } 255.15094 + return false; 255.15095 + }, 255.15096 + 255.15097 + /* In IE when deleting with caret at the begining of LI, list gets broken into half instead of merging the LI with previous */ 255.15098 + /* This does not match other browsers an is less intuitive from UI standpoint, thus has to be fixed */ 255.15099 + fixDeleteInTheBeginningOfLi: function(composer) { 255.15100 + if (wysihtml.browser.hasLiDeletingProblem()) { 255.15101 + var selection = composer.selection.getSelection(), 255.15102 + aNode = selection.anchorNode, 255.15103 + listNode, prevNode, firstNode, 255.15104 + isInBeginnig = composer.selection.caretIsFirstInSelection(), 255.15105 + prevNode, 255.15106 + intermediaryNode; 255.15107 + 255.15108 + // Fix caret at the beginnig of first textNode in LI 255.15109 + if (aNode.nodeType === 3 && selection.anchorOffset === 0 && aNode === aNode.parentNode.firstChild) { 255.15110 + aNode = aNode.parentNode; 255.15111 + isInBeginnig = true; 255.15112 + } 255.15113 + 255.15114 + if (isInBeginnig && aNode && aNode.nodeType === 1 && aNode.nodeName === "LI") { 255.15115 + prevNode = domNode(aNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); 255.15116 + if (!prevNode && aNode.parentNode && (aNode.parentNode.nodeName === "UL" || aNode.parentNode.nodeName === "OL")) { 255.15117 + prevNode = domNode(aNode.parentNode).prev({nodeTypes: [1,3], ignoreBlankTexts: true}); 255.15118 + intermediaryNode = aNode.parentNode; 255.15119 + } 255.15120 + if (prevNode) { 255.15121 + firstNode = aNode.firstChild; 255.15122 + domNode(aNode).transferContentTo(prevNode, true); 255.15123 + 255.15124 + if (intermediaryNode && intermediaryNode.children.length === 0){ 255.15125 + intermediaryNode.remove(); 255.15126 + } 255.15127 + 255.15128 + if (firstNode) { 255.15129 + composer.selection.setBefore(firstNode); 255.15130 + } else if (prevNode) { 255.15131 + if (prevNode.nodeType === 1) { 255.15132 + if (prevNode.lastChild) { 255.15133 + composer.selection.setAfter(prevNode.lastChild); 255.15134 + } else { 255.15135 + composer.selection.selectNode(prevNode); 255.15136 + } 255.15137 + } else { 255.15138 + composer.selection.setAfter(prevNode); 255.15139 + } 255.15140 + } 255.15141 + return true; 255.15142 + } 255.15143 + } 255.15144 + } 255.15145 + return false; 255.15146 + }, 255.15147 + 255.15148 + fixDeleteInTheBeginningOfControlSelection: function(composer) { 255.15149 + var selection = composer.selection, 255.15150 + prevNode = selection.getPreviousNode(), 255.15151 + selectedNode = selection.getSelectedNode(), 255.15152 + afterCaretNode; 255.15153 + 255.15154 + if (selection.caretIsFirstInSelection()) { 255.15155 + if (selectedNode.nodeType === 3) { 255.15156 + selectedNode = selectedNode.parentNode; 255.15157 + } 255.15158 + afterCaretNode = selectedNode.firstChild; 255.15159 + domNode(selectedNode).transferContentTo(prevNode, true); 255.15160 + if (afterCaretNode) { 255.15161 + composer.selection.setBefore(afterCaretNode); 255.15162 + } 255.15163 + return true; 255.15164 + } 255.15165 + return false; 255.15166 + }, 255.15167 + 255.15168 + // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature) 255.15169 + // Returns true if some corrections is applied so events know when to prevent default 255.15170 + doLineBreaksModeEnterWithCaret: function(composer) { 255.15171 + var breakNodes = "p, pre, div, blockquote", 255.15172 + caretInfo, parent, txtNode, 255.15173 + ret = false; 255.15174 + 255.15175 + caretInfo = composer.selection.getNodesNearCaret(); 255.15176 + if (caretInfo) { 255.15177 + 255.15178 + if (caretInfo.caretNode || caretInfo.nextNode) { 255.15179 + parent = dom.getParentElement(caretInfo.caretNode || caretInfo.nextNode, { query: breakNodes }, 2); 255.15180 + if (parent === composer.element) { 255.15181 + parent = undefined; 255.15182 + } 255.15183 + } 255.15184 + 255.15185 + if (parent && caretInfo.caretNode) { 255.15186 + if (domNode(caretInfo.caretNode).is.lineBreak()) { 255.15187 + 255.15188 + if (composer.config.doubleLineBreakEscapesBlock) { 255.15189 + // Double enter (enter on blank line) exits block element in useLineBreaks mode. 255.15190 + ret = true; 255.15191 + caretInfo.caretNode.parentNode.removeChild(caretInfo.caretNode); 255.15192 + 255.15193 + // Ensure surplous line breaks are not added to preceding element 255.15194 + if (domNode(caretInfo.nextNode).is.lineBreak()) { 255.15195 + caretInfo.nextNode.parentNode.removeChild(caretInfo.nextNode); 255.15196 + } 255.15197 + 255.15198 + var brNode = composer.doc.createElement('br'); 255.15199 + if (domNode(caretInfo.nextNode).is.lineBreak() && caretInfo.nextNode === parent.lastChild) { 255.15200 + parent.parentNode.insertBefore(brNode, parent.nextSibling); 255.15201 + } else { 255.15202 + composer.selection.splitElementAtCaret(parent, brNode); 255.15203 + } 255.15204 + 255.15205 + // Ensure surplous blank lines are not added to preceding element 255.15206 + if (caretInfo.nextNode && caretInfo.nextNode.nodeType === 3) { 255.15207 + // Replaces blank lines at the beginning of textnode 255.15208 + caretInfo.nextNode.data = caretInfo.nextNode.data.replace(/^ *[\r\n]+/, ''); 255.15209 + } 255.15210 + composer.selection.setBefore(brNode); 255.15211 + } 255.15212 + 255.15213 + } else if (caretInfo.caretNode.nodeType === 3 && wysihtml.browser.hasCaretBlockElementIssue() && caretInfo.textOffset === caretInfo.caretNode.data.length && !caretInfo.nextNode) { 255.15214 + 255.15215 + // This fixes annoying webkit issue when you press enter at the end of a block then seemingly nothing happens. 255.15216 + // in reality one line break is generated and cursor is reported after it, but when entering something cursor jumps before the br 255.15217 + ret = true; 255.15218 + var br1 = composer.doc.createElement('br'), 255.15219 + br2 = composer.doc.createElement('br'), 255.15220 + f = composer.doc.createDocumentFragment(); 255.15221 + f.appendChild(br1); 255.15222 + f.appendChild(br2); 255.15223 + composer.selection.insertNode(f); 255.15224 + composer.selection.setBefore(br2); 255.15225 + 255.15226 + } 255.15227 + } 255.15228 + } 255.15229 + return ret; 255.15230 + } 255.15231 + }; 255.15232 + 255.15233 + var handleDeleteKeyPress = function(event, composer) { 255.15234 + var selection = composer.selection, 255.15235 + element = composer.element; 255.15236 + 255.15237 + if (selection.isCollapsed()) { 255.15238 + /** 255.15239 + * when the editor is empty in useLineBreaks = false mode, preserve 255.15240 + * the default value in it which is <p><br></p> 255.15241 + */ 255.15242 + if (composer.isEmpty() && !composer.config.useLineBreaks) { 255.15243 + event.preventDefault(); 255.15244 + return; 255.15245 + } 255.15246 + if (actions.handleUneditableDeletion(composer)) { 255.15247 + event.preventDefault(); 255.15248 + return; 255.15249 + } 255.15250 + if (actions.fixDeleteInTheBeginningOfLi(composer)) { 255.15251 + event.preventDefault(); 255.15252 + return; 255.15253 + } 255.15254 + if (actions.fixDeleteInTheBeginningOfBlock(composer)) { 255.15255 + event.preventDefault(); 255.15256 + return; 255.15257 + } 255.15258 + if (actions.fixLastBrDeletionInTable(composer)) { 255.15259 + event.preventDefault(); 255.15260 + return; 255.15261 + } 255.15262 + if (wysihtml.browser.usesControlRanges()) { 255.15263 + if (actions.fixDeleteInTheBeginningOfControlSelection(composer)) { 255.15264 + event.preventDefault(); 255.15265 + return; 255.15266 + } 255.15267 + } 255.15268 + } else { 255.15269 + if (selection.containsUneditable()) { 255.15270 + event.preventDefault(); 255.15271 + selection.deleteContents(); 255.15272 + } 255.15273 + } 255.15274 + }; 255.15275 + 255.15276 + var handleEnterKeyPress = function(event, composer) { 255.15277 + if (composer.config.useLineBreaks && !event.shiftKey && !event.ctrlKey) { 255.15278 + // Fixes some misbehaviours of enters in linebreaks mode (natively a bit unsupported feature) 255.15279 + 255.15280 + var breakNodes = "p, pre, div, blockquote", 255.15281 + caretInfo, parent, txtNode; 255.15282 + 255.15283 + if (composer.selection.isCollapsed()) { 255.15284 + if (actions.doLineBreaksModeEnterWithCaret(composer)) { 255.15285 + event.preventDefault(); 255.15286 + } 255.15287 + } 255.15288 + } 255.15289 + 255.15290 + if (browser.hasCaretAtLinkEndInsertionProblems() && composer.selection.caretIsInTheEndOfNode()) { 255.15291 + var target = composer.selection.getSelectedNode(true), 255.15292 + targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element 255.15293 + invisibleSpace, space; 255.15294 + 255.15295 + if (targetEl && targetEl.closest('a') && target.nodeType === 3 && target === targetEl.lastChild) { 255.15296 + // Seems like enter was pressed and caret was at the end of link node 255.15297 + // This means user wants to escape the link now (caret is last in link node too). 255.15298 + composer.selection.setAfter(targetEl); 255.15299 + } 255.15300 + } 255.15301 + }; 255.15302 + 255.15303 + var handleTabKeyDown = function(composer, element, shiftKey) { 255.15304 + if (!composer.selection.isCollapsed()) { 255.15305 + composer.selection.deleteContents(); 255.15306 + } else if (composer.selection.caretIsInTheBeginnig('li')) { 255.15307 + if (shiftKey) { 255.15308 + if (composer.commands.exec('outdentList')) return; 255.15309 + } else { 255.15310 + if (composer.commands.exec('indentList')) return; 255.15311 + } 255.15312 + } 255.15313 + 255.15314 + // Is   close enough to tab. Could not find enough counter arguments for now. 255.15315 + composer.commands.exec("insertHTML", " "); 255.15316 + }; 255.15317 + 255.15318 + var handleDomNodeRemoved = function(event) { 255.15319 + if (this.domNodeRemovedInterval) { 255.15320 + clearInterval(domNodeRemovedInterval); 255.15321 + } 255.15322 + this.parent.fire("destroy:composer"); 255.15323 + }; 255.15324 + 255.15325 + // Listens to "drop", "paste", "mouseup", "focus", "keyup" events and fires 255.15326 + var handleUserInteraction = function (event) { 255.15327 + this.parent.fire("beforeinteraction", event).fire("beforeinteraction:composer", event); 255.15328 + setTimeout((function() { 255.15329 + this.parent.fire("interaction", event).fire("interaction:composer", event); 255.15330 + }).bind(this), 0); 255.15331 + }; 255.15332 + 255.15333 + var handleFocus = function(event) { 255.15334 + this.parent.fire("focus", event).fire("focus:composer", event); 255.15335 + 255.15336 + // Delay storing of state until all focus handler are fired 255.15337 + // especially the one which resets the placeholder 255.15338 + setTimeout((function() { 255.15339 + this.focusState = this.getValue(false, false); 255.15340 + }).bind(this), 0); 255.15341 + }; 255.15342 + 255.15343 + var handleBlur = function(event) { 255.15344 + if (this.focusState !== this.getValue(false, false)) { 255.15345 + //create change event if supported (all except IE8) 255.15346 + var changeevent = event; 255.15347 + if(typeof Object.create == 'function') { 255.15348 + changeevent = Object.create(event, { type: { value: 'change' } }); 255.15349 + } 255.15350 + this.parent.fire("change", changeevent).fire("change:composer", changeevent); 255.15351 + } 255.15352 + this.parent.fire("blur", event).fire("blur:composer", event); 255.15353 + }; 255.15354 + 255.15355 + var handlePaste = function(event) { 255.15356 + this.parent.fire(event.type, event).fire(event.type + ":composer", event); 255.15357 + if (event.type === "paste") { 255.15358 + setTimeout((function() { 255.15359 + this.parent.fire("newword:composer"); 255.15360 + }).bind(this), 0); 255.15361 + } 255.15362 + }; 255.15363 + 255.15364 + var handleCopy = function(event) { 255.15365 + if (this.config.copyedFromMarking) { 255.15366 + // If supported the copied source can be based directly on selection 255.15367 + // Very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection. 255.15368 + if (wysihtml.browser.supportsModernPaste()) { 255.15369 + event.clipboardData.setData("text/html", this.config.copyedFromMarking + this.selection.getHtml()); 255.15370 + event.clipboardData.setData("text/plain", this.selection.getPlainText()); 255.15371 + event.preventDefault(); 255.15372 + } 255.15373 + this.parent.fire(event.type, event).fire(event.type + ":composer", event); 255.15374 + } 255.15375 + }; 255.15376 + 255.15377 + var handleKeyUp = function(event) { 255.15378 + var keyCode = event.keyCode; 255.15379 + if (keyCode === wysihtml.SPACE_KEY || keyCode === wysihtml.ENTER_KEY) { 255.15380 + this.parent.fire("newword:composer"); 255.15381 + } 255.15382 + }; 255.15383 + 255.15384 + var handleMouseDown = function(event) { 255.15385 + if (!browser.canSelectImagesInContentEditable()) { 255.15386 + // Make sure that images are selected when clicking on them 255.15387 + var target = event.target, 255.15388 + allImages = this.element.querySelectorAll('img'), 255.15389 + notMyImages = this.element.querySelectorAll('.' + this.config.classNames.uneditableContainer + ' img'), 255.15390 + myImages = wysihtml.lang.array(allImages).without(notMyImages); 255.15391 + 255.15392 + if (target.nodeName === "IMG" && wysihtml.lang.array(myImages).contains(target)) { 255.15393 + this.selection.selectNode(target); 255.15394 + } 255.15395 + } 255.15396 + 255.15397 + // Saves mousedown position for IE controlSelect fix 255.15398 + if (wysihtml.browser.usesControlRanges()) { 255.15399 + this.selection.lastMouseDownPos = {x: event.clientX, y: event.clientY}; 255.15400 + setTimeout(function() { 255.15401 + delete this.selection.lastMouseDownPos; 255.15402 + }.bind(this), 0); 255.15403 + } 255.15404 + }; 255.15405 + 255.15406 + // IE has this madness of control selects of overflowed and some other elements (weird box around element on selection and second click selects text) 255.15407 + // This fix handles the second click problem by adding cursor to the right position under cursor inside when controlSelection is made 255.15408 + var handleIEControlSelect = function(event) { 255.15409 + var target = event.target, 255.15410 + pos = this.selection.lastMouseDownPos; 255.15411 + if (pos) { 255.15412 + var caretPosition = document.body.createTextRange(); 255.15413 + setTimeout(function() { 255.15414 + try { 255.15415 + caretPosition.moveToPoint(pos.x, pos.y); 255.15416 + caretPosition.select(); 255.15417 + } catch (e) {} 255.15418 + }.bind(this), 0); 255.15419 + } 255.15420 + }; 255.15421 + 255.15422 + var handleClick = function(event) { 255.15423 + if (this.config.classNames.uneditableContainer) { 255.15424 + // If uneditables is configured, makes clicking on uneditable move caret after clicked element (so it can be deleted like text) 255.15425 + // If uneditable needs text selection itself event.stopPropagation can be used to prevent this behaviour 255.15426 + var uneditable = wysihtml.dom.getParentElement(event.target, { query: "." + this.config.classNames.uneditableContainer }, false, this.element); 255.15427 + if (uneditable) { 255.15428 + this.selection.setAfter(uneditable); 255.15429 + } 255.15430 + } 255.15431 + }; 255.15432 + 255.15433 + var handleDrop = function(event) { 255.15434 + if (!browser.canSelectImagesInContentEditable()) { 255.15435 + // TODO: if I knew how to get dropped elements list from event I could limit it to only IMG element case 255.15436 + setTimeout((function() { 255.15437 + this.selection.getSelection().removeAllRanges(); 255.15438 + }).bind(this), 0); 255.15439 + } 255.15440 + }; 255.15441 + 255.15442 + var handleKeyDown = function(event) { 255.15443 + var keyCode = event.keyCode, 255.15444 + command = shortcuts[keyCode], 255.15445 + target = this.selection.getSelectedNode(true), 255.15446 + targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element 255.15447 + parent; 255.15448 + 255.15449 + // Select all (meta/ctrl + a) 255.15450 + if ((event.ctrlKey || event.metaKey) && !event.altKey && keyCode === 65) { 255.15451 + this.selection.selectAll(); 255.15452 + event.preventDefault(); 255.15453 + return; 255.15454 + } 255.15455 + 255.15456 + // Shortcut logic 255.15457 + if ((event.ctrlKey || event.metaKey) && !event.altKey && command) { 255.15458 + this.commands.exec(command); 255.15459 + event.preventDefault(); 255.15460 + } 255.15461 + 255.15462 + if (keyCode === wysihtml.BACKSPACE_KEY) { 255.15463 + // Delete key override for special cases 255.15464 + handleDeleteKeyPress(event, this); 255.15465 + } 255.15466 + 255.15467 + // Make sure that when pressing backspace/delete on selected images deletes the image and it's anchor 255.15468 + if (keyCode === wysihtml.BACKSPACE_KEY || keyCode === wysihtml.DELETE_KEY) { 255.15469 + if (target && target.nodeName === "IMG") { 255.15470 + event.preventDefault(); 255.15471 + parent = target.parentNode; 255.15472 + parent.removeChild(target);// delete the <img> 255.15473 + // And it's parent <a> too if it hasn't got any other child nodes 255.15474 + if (parent.nodeName === "A" && !parent.firstChild) { 255.15475 + parent.parentNode.removeChild(parent); 255.15476 + } 255.15477 + setTimeout((function() { 255.15478 + wysihtml.quirks.redraw(this.element); 255.15479 + }).bind(this), 0); 255.15480 + } 255.15481 + } 255.15482 + 255.15483 + if (this.config.handleTabKey && keyCode === wysihtml.TAB_KEY) { 255.15484 + // TAB key handling 255.15485 + event.preventDefault(); 255.15486 + handleTabKeyDown(this, this.element, event.shiftKey); 255.15487 + } 255.15488 + 255.15489 + if (keyCode === wysihtml.ENTER_KEY) { 255.15490 + handleEnterKeyPress(event, this); 255.15491 + } 255.15492 + 255.15493 + }; 255.15494 + 255.15495 + var handleKeyPress = function(event) { 255.15496 + 255.15497 + // This block should run only if some character is inserted (nor command keys like delete, backspace, enter, etc.) 255.15498 + if (event.which !== 0) { 255.15499 + 255.15500 + // Test if caret is last in a link in webkit and try to fix webkit problem, 255.15501 + // that all inserted content is added outside of link. 255.15502 + // This issue was added as a not thought through fix for getting caret after link in contenteditable if it is last in editable area. 255.15503 + // Allthough it fixes this minor case it actually introduces a cascade of problems when editing links. 255.15504 + // The standard approachi in other wysiwygs seems as a step backwards - introducing a separate modal for managing links content text. 255.15505 + // I find it to be too big of a tradeoff in terms of expected simple UI flow, thus trying to fight against it. 255.15506 + // Also adds link escaping by double space with caret at the end of link for all browsers 255.15507 + 255.15508 + if (this.selection.caretIsInTheEndOfNode()) { 255.15509 + var target = this.selection.getSelectedNode(true), 255.15510 + targetEl = (target && target.nodeType === 3) ? target.parentNode : target, // target guaranteed to be an Element 255.15511 + invisibleSpace, space; 255.15512 + 255.15513 + if (targetEl && targetEl.closest('a') && target === targetEl.lastChild) { 255.15514 + 255.15515 + if (event.which !== 32 || this.selection.caretIsInTheEndOfNode(true) && browser.hasCaretAtLinkEndInsertionProblems()) { 255.15516 + // Executed if there is no whitespace before caret in textnode in case of pressing space. 255.15517 + // Whitespace before marks that user wants to escape the node by pressing double space. 255.15518 + // Otherwise insert the character in the link not out as it would like to go natively 255.15519 + 255.15520 + invisibleSpace = this.doc.createTextNode(wysihtml.INVISIBLE_SPACE); 255.15521 + this.selection.insertNode(invisibleSpace); 255.15522 + this.selection.setBefore(invisibleSpace); 255.15523 + setTimeout(function() { 255.15524 + 255.15525 + if (invisibleSpace.textContent.length > 1) { 255.15526 + invisibleSpace.textContent = invisibleSpace.textContent.replace(wysihtml.INVISIBLE_SPACE_REG_EXP, ''); 255.15527 + this.selection.setAfter(invisibleSpace); 255.15528 + } else { 255.15529 + invisibleSpace.remove(); 255.15530 + } 255.15531 + 255.15532 + }.bind(this), 0); 255.15533 + } else if (event.which === 32) { 255.15534 + // Seems like space was pressed and there was a space before the caret allready 255.15535 + // This means user wants to escape the link now (caret is last in link node too) so we let the native browser do it-s job and escape. 255.15536 + // But lets move the trailing space too out of link if present 255.15537 + 255.15538 + if (target.nodeType === 3 && (/[\u00A0 ]$/).test(target.textContent)) { 255.15539 + 255.15540 + target.textContent = target.textContent.replace(/[\u00A0 ]$/, ''); 255.15541 + space = this.doc.createTextNode(' '); 255.15542 + targetEl.parentNode.insertBefore(space, targetEl.nextSibling); 255.15543 + this.selection.setAfter(space, false); 255.15544 + event.preventDefault(); 255.15545 + 255.15546 + } 255.15547 + } 255.15548 + } 255.15549 + } 255.15550 + } 255.15551 + } 255.15552 + 255.15553 + var handleIframeFocus = function(event) { 255.15554 + setTimeout((function() { 255.15555 + if (this.doc.querySelector(":focus") !== this.element) { 255.15556 + this.focus(); 255.15557 + } 255.15558 + }).bind(this), 0); 255.15559 + }; 255.15560 + 255.15561 + var handleIframeBlur = function(event) { 255.15562 + setTimeout((function() { 255.15563 + this.selection.getSelection().removeAllRanges(); 255.15564 + }).bind(this), 0); 255.15565 + }; 255.15566 + 255.15567 + // Testing requires actions to be accessible from out of scope 255.15568 + wysihtml.views.Composer.prototype.observeActions = actions; 255.15569 + 255.15570 + wysihtml.views.Composer.prototype.observe = function() { 255.15571 + var that = this, 255.15572 + container = (this.sandbox.getIframe) ? this.sandbox.getIframe() : this.sandbox.getContentEditable(), 255.15573 + element = this.element, 255.15574 + focusBlurElement = (browser.supportsEventsInIframeCorrectly() || this.sandbox.getContentEditable) ? this.element : this.sandbox.getWindow(); 255.15575 + 255.15576 + this.focusState = this.getValue(false, false); 255.15577 + this.actions = actions; 255.15578 + 255.15579 + // --------- destroy:composer event --------- 255.15580 + container.addEventListener(["DOMNodeRemoved"], handleDomNodeRemoved.bind(this), false); 255.15581 + 255.15582 + // DOMNodeRemoved event is not supported in IE 8 255.15583 + // TODO: try to figure out a polyfill style fix, so it could be transferred to polyfills and removed if ie8 is not needed 255.15584 + if (!browser.supportsMutationEvents()) { 255.15585 + this.domNodeRemovedInterval = setInterval(function() { 255.15586 + if (!dom.contains(document.documentElement, container)) { 255.15587 + handleDomNodeRemoved.call(this); 255.15588 + } 255.15589 + }, 250); 255.15590 + } 255.15591 + 255.15592 + actions.addListeners(focusBlurElement, ['drop', 'paste', 'mouseup', 'focus', 'keyup'], handleUserInteraction.bind(this)); 255.15593 + focusBlurElement.addEventListener('focus', handleFocus.bind(this), false); 255.15594 + focusBlurElement.addEventListener('blur', handleBlur.bind(this), false); 255.15595 + 255.15596 + actions.addListeners(this.element, ['drop', 'paste', 'beforepaste'], handlePaste.bind(this), false); 255.15597 + this.element.addEventListener('copy', handleCopy.bind(this), false); 255.15598 + this.element.addEventListener('mousedown', handleMouseDown.bind(this), false); 255.15599 + this.element.addEventListener('click', handleClick.bind(this), false); 255.15600 + this.element.addEventListener('drop', handleDrop.bind(this), false); 255.15601 + this.element.addEventListener('keyup', handleKeyUp.bind(this), false); 255.15602 + this.element.addEventListener('keydown', handleKeyDown.bind(this), false); 255.15603 + this.element.addEventListener('keypress', handleKeyPress.bind(this), false); 255.15604 + 255.15605 + // IE controlselect madness fix 255.15606 + if (wysihtml.browser.usesControlRanges()) { 255.15607 + this.element.addEventListener('mscontrolselect', handleIEControlSelect.bind(this), false); 255.15608 + } 255.15609 + 255.15610 + this.element.addEventListener("dragenter", (function() { 255.15611 + this.parent.fire("unset_placeholder"); 255.15612 + }).bind(this), false); 255.15613 + 255.15614 + }; 255.15615 +})(wysihtml); 255.15616 + 255.15617 +/** 255.15618 + * Class that takes care that the value of the composer and the textarea is always in sync 255.15619 + */ 255.15620 +(function(wysihtml) { 255.15621 + var INTERVAL = 400; 255.15622 + 255.15623 + wysihtml.views.Synchronizer = Base.extend( 255.15624 + /** @scope wysihtml.views.Synchronizer.prototype */ { 255.15625 + 255.15626 + constructor: function(editor, textarea, composer) { 255.15627 + this.editor = editor; 255.15628 + this.textarea = textarea; 255.15629 + this.composer = composer; 255.15630 + 255.15631 + this._observe(); 255.15632 + }, 255.15633 + 255.15634 + /** 255.15635 + * Sync html from composer to textarea 255.15636 + * Takes care of placeholders 255.15637 + * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the textarea 255.15638 + */ 255.15639 + fromComposerToTextarea: function(shouldParseHtml) { 255.15640 + this.textarea.setValue(wysihtml.lang.string(this.composer.getValue(false, false)).trim(), shouldParseHtml); 255.15641 + }, 255.15642 + 255.15643 + /** 255.15644 + * Sync value of textarea to composer 255.15645 + * Takes care of placeholders 255.15646 + * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer 255.15647 + */ 255.15648 + fromTextareaToComposer: function(shouldParseHtml) { 255.15649 + var textareaValue = this.textarea.getValue(false, false); 255.15650 + if (textareaValue) { 255.15651 + this.composer.setValue(textareaValue, shouldParseHtml); 255.15652 + } else { 255.15653 + this.composer.clear(); 255.15654 + this.editor.fire("set_placeholder"); 255.15655 + } 255.15656 + }, 255.15657 + 255.15658 + /** 255.15659 + * Invoke syncing based on view state 255.15660 + * @param {Boolean} shouldParseHtml Whether the html should be sanitized before inserting it into the composer/textarea 255.15661 + */ 255.15662 + sync: function(shouldParseHtml) { 255.15663 + if (this.editor.currentView.name === "textarea") { 255.15664 + this.fromTextareaToComposer(shouldParseHtml); 255.15665 + } else { 255.15666 + this.fromComposerToTextarea(shouldParseHtml); 255.15667 + } 255.15668 + }, 255.15669 + 255.15670 + /** 255.15671 + * Initializes interval-based syncing 255.15672 + * also makes sure that on-submit the composer's content is synced with the textarea 255.15673 + * immediately when the form gets submitted 255.15674 + */ 255.15675 + _observe: function() { 255.15676 + var interval, 255.15677 + that = this, 255.15678 + form = this.textarea.element.form, 255.15679 + startInterval = function() { 255.15680 + interval = setInterval(function() { that.fromComposerToTextarea(); }, INTERVAL); 255.15681 + }, 255.15682 + stopInterval = function() { 255.15683 + clearInterval(interval); 255.15684 + interval = null; 255.15685 + }; 255.15686 + 255.15687 + startInterval(); 255.15688 + 255.15689 + if (form) { 255.15690 + // If the textarea is in a form make sure that after onreset and onsubmit the composer 255.15691 + // has the correct state 255.15692 + wysihtml.dom.observe(form, "submit", function() { 255.15693 + that.sync(true); 255.15694 + }); 255.15695 + wysihtml.dom.observe(form, "reset", function() { 255.15696 + setTimeout(function() { that.fromTextareaToComposer(); }, 0); 255.15697 + }); 255.15698 + } 255.15699 + 255.15700 + this.editor.on("change_view", function(view) { 255.15701 + if (view === "composer" && !interval) { 255.15702 + that.fromTextareaToComposer(true); 255.15703 + startInterval(); 255.15704 + } else if (view === "textarea") { 255.15705 + that.fromComposerToTextarea(true); 255.15706 + stopInterval(); 255.15707 + } 255.15708 + }); 255.15709 + 255.15710 + this.editor.on("destroy:composer", stopInterval); 255.15711 + } 255.15712 + }); 255.15713 +})(wysihtml); 255.15714 + 255.15715 +(function(wysihtml) { 255.15716 + 255.15717 + wysihtml.views.SourceView = Base.extend( 255.15718 + /** @scope wysihtml.views.SourceView.prototype */ { 255.15719 + 255.15720 + constructor: function(editor, composer) { 255.15721 + this.editor = editor; 255.15722 + this.composer = composer; 255.15723 + 255.15724 + this._observe(); 255.15725 + }, 255.15726 + 255.15727 + switchToTextarea: function(shouldParseHtml) { 255.15728 + var composerStyles = this.composer.win.getComputedStyle(this.composer.element), 255.15729 + width = parseFloat(composerStyles.width), 255.15730 + height = Math.max(parseFloat(composerStyles.height), 100); 255.15731 + 255.15732 + if (!this.textarea) { 255.15733 + this.textarea = this.composer.doc.createElement('textarea'); 255.15734 + this.textarea.className = "wysihtml-source-view"; 255.15735 + } 255.15736 + this.textarea.style.width = width + 'px'; 255.15737 + this.textarea.style.height = height + 'px'; 255.15738 + this.textarea.value = this.editor.getValue(shouldParseHtml, true); 255.15739 + this.composer.element.parentNode.insertBefore(this.textarea, this.composer.element); 255.15740 + this.editor.currentView = "source"; 255.15741 + this.composer.element.style.display = 'none'; 255.15742 + }, 255.15743 + 255.15744 + switchToComposer: function(shouldParseHtml) { 255.15745 + var textareaValue = this.textarea.value; 255.15746 + if (textareaValue) { 255.15747 + this.composer.setValue(textareaValue, shouldParseHtml); 255.15748 + } else { 255.15749 + this.composer.clear(); 255.15750 + this.editor.fire("set_placeholder"); 255.15751 + } 255.15752 + this.textarea.parentNode.removeChild(this.textarea); 255.15753 + this.editor.currentView = this.composer; 255.15754 + this.composer.element.style.display = ''; 255.15755 + }, 255.15756 + 255.15757 + _observe: function() { 255.15758 + this.editor.on("change_view", function(view) { 255.15759 + if (view === "composer") { 255.15760 + this.switchToComposer(true); 255.15761 + } else if (view === "textarea") { 255.15762 + this.switchToTextarea(true); 255.15763 + } 255.15764 + }.bind(this)); 255.15765 + } 255.15766 + 255.15767 + }); 255.15768 + 255.15769 +})(wysihtml); 255.15770 + 255.15771 +wysihtml.views.Textarea = wysihtml.views.View.extend( 255.15772 + /** @scope wysihtml.views.Textarea.prototype */ { 255.15773 + name: "textarea", 255.15774 + 255.15775 + constructor: function(parent, textareaElement, config) { 255.15776 + this.base(parent, textareaElement, config); 255.15777 + 255.15778 + this._observe(); 255.15779 + }, 255.15780 + 255.15781 + clear: function() { 255.15782 + this.element.value = ""; 255.15783 + }, 255.15784 + 255.15785 + getValue: function(parse) { 255.15786 + var value = this.isEmpty() ? "" : this.element.value; 255.15787 + if (parse !== false) { 255.15788 + value = this.parent.parse(value); 255.15789 + } 255.15790 + return value; 255.15791 + }, 255.15792 + 255.15793 + setValue: function(html, parse) { 255.15794 + if (parse !== false) { 255.15795 + html = this.parent.parse(html); 255.15796 + } 255.15797 + this.element.value = html; 255.15798 + }, 255.15799 + 255.15800 + cleanUp: function(rules) { 255.15801 + var html = this.parent.parse(this.element.value, undefined, rules); 255.15802 + this.element.value = html; 255.15803 + }, 255.15804 + 255.15805 + hasPlaceholderSet: function() { 255.15806 + var supportsPlaceholder = wysihtml.browser.supportsPlaceholderAttributeOn(this.element), 255.15807 + placeholderText = this.element.getAttribute("placeholder") || null, 255.15808 + value = this.element.value, 255.15809 + isEmpty = !value; 255.15810 + return (supportsPlaceholder && isEmpty) || (value === placeholderText); 255.15811 + }, 255.15812 + 255.15813 + isEmpty: function() { 255.15814 + return !wysihtml.lang.string(this.element.value).trim() || this.hasPlaceholderSet(); 255.15815 + }, 255.15816 + 255.15817 + _observe: function() { 255.15818 + var element = this.element, 255.15819 + parent = this.parent, 255.15820 + eventMapping = { 255.15821 + focusin: "focus", 255.15822 + focusout: "blur" 255.15823 + }, 255.15824 + /** 255.15825 + * Calling focus() or blur() on an element doesn't synchronously trigger the attached focus/blur events 255.15826 + * This is the case for focusin and focusout, so let's use them whenever possible, kkthxbai 255.15827 + */ 255.15828 + events = wysihtml.browser.supportsEvent("focusin") ? ["focusin", "focusout", "change"] : ["focus", "blur", "change"]; 255.15829 + 255.15830 + parent.on("beforeload", function() { 255.15831 + wysihtml.dom.observe(element, events, function(event) { 255.15832 + var eventName = eventMapping[event.type] || event.type; 255.15833 + parent.fire(eventName).fire(eventName + ":textarea"); 255.15834 + }); 255.15835 + 255.15836 + wysihtml.dom.observe(element, ["paste", "drop"], function() { 255.15837 + setTimeout(function() { parent.fire("paste").fire("paste:textarea"); }, 0); 255.15838 + }); 255.15839 + }); 255.15840 + } 255.15841 +}); 255.15842 + 255.15843 +/** 255.15844 + * WYSIHTML Editor 255.15845 + * 255.15846 + * @param {Element} editableElement Reference to the textarea which should be turned into a rich text interface 255.15847 + * @param {Object} [config] See defaults object below for explanation of each individual config option 255.15848 + * 255.15849 + * @events 255.15850 + * load 255.15851 + * beforeload (for internal use only) 255.15852 + * focus 255.15853 + * focus:composer 255.15854 + * focus:textarea 255.15855 + * blur 255.15856 + * blur:composer 255.15857 + * blur:textarea 255.15858 + * change 255.15859 + * change:composer 255.15860 + * change:textarea 255.15861 + * paste 255.15862 + * paste:composer 255.15863 + * paste:textarea 255.15864 + * newword:composer 255.15865 + * destroy:composer 255.15866 + * undo:composer 255.15867 + * redo:composer 255.15868 + * beforecommand:composer 255.15869 + * aftercommand:composer 255.15870 + * enable:composer 255.15871 + * disable:composer 255.15872 + * change_view 255.15873 + */ 255.15874 +(function(wysihtml) { 255.15875 + var undef; 255.15876 + 255.15877 + wysihtml.Editor = wysihtml.lang.Dispatcher.extend({ 255.15878 + /** @scope wysihtml.Editor.prototype */ 255.15879 + defaults: { 255.15880 + // Give the editor a name, the name will also be set as class name on the iframe and on the iframe's body 255.15881 + name: undef, 255.15882 + // Whether the editor should look like the textarea (by adopting styles) 255.15883 + style: true, 255.15884 + // Whether urls, entered by the user should automatically become clickable-links 255.15885 + autoLink: true, 255.15886 + // Tab key inserts tab into text as default behaviour. It can be disabled to regain keyboard navigation 255.15887 + handleTabKey: true, 255.15888 + // Object which includes parser rules to apply when html gets cleaned 255.15889 + // See parser_rules/*.js for examples 255.15890 + parserRules: { tags: { br: {}, span: {}, div: {}, p: {}, b: {}, i: {}, u: {} }, classes: {} }, 255.15891 + // Object which includes parser when the user inserts content via copy & paste. If null parserRules will be used instead 255.15892 + pasteParserRulesets: null, 255.15893 + // Parser method to use when the user inserts content 255.15894 + parser: wysihtml.dom.parse, 255.15895 + // By default wysihtml will insert a <br> for line breaks, set this to false to use <p> 255.15896 + useLineBreaks: true, 255.15897 + // Double enter (enter on blank line) exits block element in useLineBreaks mode. 255.15898 + // It enables a way of escaping out of block elements and splitting block elements 255.15899 + doubleLineBreakEscapesBlock: true, 255.15900 + // Array (or single string) of stylesheet urls to be loaded in the editor's iframe 255.15901 + stylesheets: [], 255.15902 + // Placeholder text to use, defaults to the placeholder attribute on the textarea element 255.15903 + placeholderText: undef, 255.15904 + // Whether the rich text editor should be rendered on touch devices (wysihtml >= 0.3.0 comes with basic support for iOS 5) 255.15905 + supportTouchDevices: true, 255.15906 + // Whether senseless <span> elements (empty or without attributes) should be removed/replaced with their content 255.15907 + cleanUp: true, 255.15908 + // Whether to use div instead of secure iframe 255.15909 + contentEditableMode: false, 255.15910 + classNames: { 255.15911 + // Class name which should be set on the contentEditable element in the created sandbox iframe, can be styled via the 'stylesheets' option 255.15912 + composer: "wysihtml-editor", 255.15913 + // Class name to add to the body when the wysihtml editor is supported 255.15914 + body: "wysihtml-supported", 255.15915 + // classname added to editable area element (iframe/div) on creation 255.15916 + sandbox: "wysihtml-sandbox", 255.15917 + // class on editable area with placeholder 255.15918 + placeholder: "wysihtml-placeholder", 255.15919 + // Classname of container that editor should not touch and pass through 255.15920 + uneditableContainer: "wysihtml-uneditable-container" 255.15921 + }, 255.15922 + // Browsers that support copied source handling will get a marking of the origin of the copied source (for determinig code cleanup rules on paste) 255.15923 + // Also copied source is based directly on selection - 255.15924 + // (very useful for webkit based browsers where copy will otherwise contain a lot of code and styles based on whatever and not actually in selection). 255.15925 + // If falsy value is passed source override is also disabled 255.15926 + copyedFromMarking: '<meta name="copied-from" content="wysihtml">' 255.15927 + }, 255.15928 + 255.15929 + constructor: function(editableElement, config) { 255.15930 + this.editableElement = typeof(editableElement) === "string" ? document.getElementById(editableElement) : editableElement; 255.15931 + this.config = wysihtml.lang.object({}).merge(this.defaults).merge(config).get(); 255.15932 + this._isCompatible = wysihtml.browser.supported(); 255.15933 + 255.15934 + // merge classNames 255.15935 + if (config && config.classNames) { 255.15936 + wysihtml.lang.object(this.config.classNames).merge(config.classNames); 255.15937 + } 255.15938 + 255.15939 + if (this.editableElement.nodeName.toLowerCase() != "textarea") { 255.15940 + this.config.contentEditableMode = true; 255.15941 + this.config.noTextarea = true; 255.15942 + } 255.15943 + if (!this.config.noTextarea) { 255.15944 + this.textarea = new wysihtml.views.Textarea(this, this.editableElement, this.config); 255.15945 + this.currentView = this.textarea; 255.15946 + } 255.15947 + 255.15948 + // Sort out unsupported/unwanted browsers here 255.15949 + if (!this._isCompatible || (!this.config.supportTouchDevices && wysihtml.browser.isTouchDevice())) { 255.15950 + var that = this; 255.15951 + setTimeout(function() { that.fire("beforeload").fire("load"); }, 0); 255.15952 + return; 255.15953 + } 255.15954 + 255.15955 + // Add class name to body, to indicate that the editor is supported 255.15956 + wysihtml.dom.addClass(document.body, this.config.classNames.body); 255.15957 + 255.15958 + this.composer = new wysihtml.views.Composer(this, this.editableElement, this.config); 255.15959 + this.currentView = this.composer; 255.15960 + 255.15961 + if (typeof(this.config.parser) === "function") { 255.15962 + this._initParser(); 255.15963 + } 255.15964 + 255.15965 + this.on("beforeload", this.handleBeforeLoad); 255.15966 + }, 255.15967 + 255.15968 + handleBeforeLoad: function() { 255.15969 + if (!this.config.noTextarea) { 255.15970 + this.synchronizer = new wysihtml.views.Synchronizer(this, this.textarea, this.composer); 255.15971 + } else { 255.15972 + this.sourceView = new wysihtml.views.SourceView(this, this.composer); 255.15973 + } 255.15974 + this.runEditorExtenders(); 255.15975 + }, 255.15976 + 255.15977 + runEditorExtenders: function() { 255.15978 + wysihtml.editorExtenders.forEach(function(extender) { 255.15979 + extender(this); 255.15980 + }.bind(this)); 255.15981 + }, 255.15982 + 255.15983 + isCompatible: function() { 255.15984 + return this._isCompatible; 255.15985 + }, 255.15986 + 255.15987 + clear: function() { 255.15988 + this.currentView.clear(); 255.15989 + return this; 255.15990 + }, 255.15991 + 255.15992 + getValue: function(parse, clearInternals) { 255.15993 + return this.currentView.getValue(parse, clearInternals); 255.15994 + }, 255.15995 + 255.15996 + setValue: function(html, parse) { 255.15997 + this.fire("unset_placeholder"); 255.15998 + 255.15999 + if (!html) { 255.16000 + return this.clear(); 255.16001 + } 255.16002 + 255.16003 + this.currentView.setValue(html, parse); 255.16004 + return this; 255.16005 + }, 255.16006 + 255.16007 + cleanUp: function(rules) { 255.16008 + this.currentView.cleanUp(rules); 255.16009 + }, 255.16010 + 255.16011 + focus: function(setToEnd) { 255.16012 + this.currentView.focus(setToEnd); 255.16013 + return this; 255.16014 + }, 255.16015 + 255.16016 + /** 255.16017 + * Deactivate editor (make it readonly) 255.16018 + */ 255.16019 + disable: function() { 255.16020 + this.currentView.disable(); 255.16021 + return this; 255.16022 + }, 255.16023 + 255.16024 + /** 255.16025 + * Activate editor 255.16026 + */ 255.16027 + enable: function() { 255.16028 + this.currentView.enable(); 255.16029 + return this; 255.16030 + }, 255.16031 + 255.16032 + isEmpty: function() { 255.16033 + return this.currentView.isEmpty(); 255.16034 + }, 255.16035 + 255.16036 + hasPlaceholderSet: function() { 255.16037 + return this.currentView.hasPlaceholderSet(); 255.16038 + }, 255.16039 + 255.16040 + destroy: function() { 255.16041 + if (this.composer && this.composer.sandbox) { 255.16042 + this.composer.sandbox.destroy(); 255.16043 + } 255.16044 + this.fire("destroy:composer"); 255.16045 + this.off(); 255.16046 + }, 255.16047 + 255.16048 + parse: function(htmlOrElement, clearInternals, customRules) { 255.16049 + var parseContext = (this.config.contentEditableMode) ? document : ((this.composer) ? this.composer.sandbox.getDocument() : null); 255.16050 + var returnValue = this.config.parser(htmlOrElement, { 255.16051 + "rules": customRules || this.config.parserRules, 255.16052 + "cleanUp": this.config.cleanUp, 255.16053 + "context": parseContext, 255.16054 + "uneditableClass": this.config.classNames.uneditableContainer, 255.16055 + "clearInternals" : clearInternals 255.16056 + }); 255.16057 + if (typeof(htmlOrElement) === "object") { 255.16058 + wysihtml.quirks.redraw(htmlOrElement); 255.16059 + } 255.16060 + return returnValue; 255.16061 + }, 255.16062 + 255.16063 + /** 255.16064 + * Prepare html parser logic 255.16065 + * - Observes for paste and drop 255.16066 + */ 255.16067 + _initParser: function() { 255.16068 + var oldHtml; 255.16069 + 255.16070 + if (wysihtml.browser.supportsModernPaste()) { 255.16071 + this.on("paste:composer", function(event) { 255.16072 + event.preventDefault(); 255.16073 + oldHtml = wysihtml.dom.getPastedHtml(event); 255.16074 + if (oldHtml) { 255.16075 + this._cleanAndPaste(oldHtml); 255.16076 + } 255.16077 + }.bind(this)); 255.16078 + 255.16079 + } else { 255.16080 + this.on("beforepaste:composer", function(event) { 255.16081 + event.preventDefault(); 255.16082 + var scrollPos = this.composer.getScrollPos(); 255.16083 + 255.16084 + wysihtml.dom.getPastedHtmlWithDiv(this.composer, function(pastedHTML) { 255.16085 + if (pastedHTML) { 255.16086 + this._cleanAndPaste(pastedHTML); 255.16087 + } 255.16088 + this.composer.setScrollPos(scrollPos); 255.16089 + }.bind(this)); 255.16090 + 255.16091 + }.bind(this)); 255.16092 + } 255.16093 + }, 255.16094 + 255.16095 + _cleanAndPaste: function (oldHtml) { 255.16096 + var cleanHtml = wysihtml.quirks.cleanPastedHTML(oldHtml, { 255.16097 + "referenceNode": this.composer.element, 255.16098 + "rules": this.config.pasteParserRulesets || [{"set": this.config.parserRules}], 255.16099 + "uneditableClass": this.config.classNames.uneditableContainer 255.16100 + }); 255.16101 + this.composer.selection.deleteContents(); 255.16102 + this.composer.selection.insertHTML(cleanHtml); 255.16103 + } 255.16104 + }); 255.16105 +})(wysihtml);
256.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 256.2 +++ b/static/wysihtml/wysihtml.toolbar.js Sun Jul 15 14:07:29 2018 +0200 256.3 @@ -0,0 +1,850 @@ 256.4 +/** 256.5 + * Toolbar Dialog 256.6 + * 256.7 + * @param {Element} link The toolbar link which causes the dialog to show up 256.8 + * @param {Element} container The dialog container 256.9 + * 256.10 + * @example 256.11 + * <!-- Toolbar link --> 256.12 + * <a data-wysihtml-command="insertImage">insert an image</a> 256.13 + * 256.14 + * <!-- Dialog --> 256.15 + * <div data-wysihtml-dialog="insertImage" style="display: none;"> 256.16 + * <label> 256.17 + * URL: <input data-wysihtml-dialog-field="src" value="http://"> 256.18 + * </label> 256.19 + * <label> 256.20 + * Alternative text: <input data-wysihtml-dialog-field="alt" value=""> 256.21 + * </label> 256.22 + * </div> 256.23 + * 256.24 + * <script> 256.25 + * var dialog = new wysihtml.toolbar.Dialog( 256.26 + * document.querySelector("[data-wysihtml-command='insertImage']"), 256.27 + * document.querySelector("[data-wysihtml-dialog='insertImage']") 256.28 + * ); 256.29 + * dialog.observe("save", function(attributes) { 256.30 + * // do something 256.31 + * }); 256.32 + * </script> 256.33 + */ 256.34 +(function(wysihtml) { 256.35 + var dom = wysihtml.dom, 256.36 + CLASS_NAME_OPENED = "wysihtml-command-dialog-opened", 256.37 + SELECTOR_FORM_ELEMENTS = "input, select, textarea", 256.38 + SELECTOR_FIELDS = "[data-wysihtml-dialog-field]", 256.39 + ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field"; 256.40 + 256.41 + 256.42 + wysihtml.toolbar.Dialog = wysihtml.lang.Dispatcher.extend( 256.43 + /** @scope wysihtml.toolbar.Dialog.prototype */ { 256.44 + constructor: function(link, container) { 256.45 + this.link = link; 256.46 + this.container = container; 256.47 + }, 256.48 + 256.49 + _observe: function() { 256.50 + if (this._observed) { 256.51 + return; 256.52 + } 256.53 + 256.54 + var that = this, 256.55 + callbackWrapper = function(event) { 256.56 + var attributes = that._serialize(); 256.57 + that.fire("save", attributes); 256.58 + that.hide(); 256.59 + event.preventDefault(); 256.60 + event.stopPropagation(); 256.61 + }; 256.62 + 256.63 + dom.observe(that.link, "click", function() { 256.64 + if (dom.hasClass(that.link, CLASS_NAME_OPENED)) { 256.65 + setTimeout(function() { that.hide(); }, 0); 256.66 + } 256.67 + }); 256.68 + 256.69 + dom.observe(this.container, "keydown", function(event) { 256.70 + var keyCode = event.keyCode; 256.71 + if (keyCode === wysihtml.ENTER_KEY) { 256.72 + callbackWrapper(event); 256.73 + } 256.74 + if (keyCode === wysihtml.ESCAPE_KEY) { 256.75 + that.cancel(); 256.76 + } 256.77 + }); 256.78 + 256.79 + dom.delegate(this.container, "[data-wysihtml-dialog-action=save]", "click", callbackWrapper); 256.80 + 256.81 + dom.delegate(this.container, "[data-wysihtml-dialog-action=cancel]", "click", function(event) { 256.82 + that.cancel(); 256.83 + event.preventDefault(); 256.84 + event.stopPropagation(); 256.85 + }); 256.86 + 256.87 + this._observed = true; 256.88 + }, 256.89 + 256.90 + /** 256.91 + * Grabs all fields in the dialog and puts them in key=>value style in an object which 256.92 + * then gets returned 256.93 + */ 256.94 + _serialize: function() { 256.95 + var data = {}, 256.96 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 256.97 + length = fields.length, 256.98 + i = 0; 256.99 + 256.100 + for (; i<length; i++) { 256.101 + data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value; 256.102 + } 256.103 + return data; 256.104 + }, 256.105 + 256.106 + /** 256.107 + * Takes the attributes of the "elementToChange" 256.108 + * and inserts them in their corresponding dialog input fields 256.109 + * 256.110 + * Assume the "elementToChange" looks like this: 256.111 + * <a href="http://www.google.com" target="_blank">foo</a> 256.112 + * 256.113 + * and we have the following dialog: 256.114 + * <input type="text" data-wysihtml-dialog-field="href" value=""> 256.115 + * <input type="text" data-wysihtml-dialog-field="target" value=""> 256.116 + * 256.117 + * after calling _interpolate() the dialog will look like this 256.118 + * <input type="text" data-wysihtml-dialog-field="href" value="http://www.google.com"> 256.119 + * <input type="text" data-wysihtml-dialog-field="target" value="_blank"> 256.120 + * 256.121 + * Basically it adopted the attribute values into the corresponding input fields 256.122 + * 256.123 + */ 256.124 + _interpolate: function(avoidHiddenFields) { 256.125 + var field, 256.126 + fieldName, 256.127 + newValue, 256.128 + focusedElement = document.querySelector(":focus"), 256.129 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 256.130 + length = fields.length, 256.131 + i = 0; 256.132 + for (; i<length; i++) { 256.133 + field = fields[i]; 256.134 + 256.135 + // Never change elements where the user is currently typing in 256.136 + if (field === focusedElement) { 256.137 + continue; 256.138 + } 256.139 + 256.140 + // Don't update hidden fields 256.141 + // See https://github.com/xing/wysihtml5/pull/14 256.142 + if (avoidHiddenFields && field.type === "hidden") { 256.143 + continue; 256.144 + } 256.145 + 256.146 + fieldName = field.getAttribute(ATTRIBUTE_FIELDS); 256.147 + newValue = (this.elementToChange && typeof(this.elementToChange) !== 'boolean') ? (this.elementToChange.getAttribute(fieldName) || "") : field.defaultValue; 256.148 + field.value = newValue; 256.149 + } 256.150 + }, 256.151 + 256.152 + update: function (elementToChange) { 256.153 + this.elementToChange = elementToChange ? elementToChange : this.elementToChange; 256.154 + this._interpolate(); 256.155 + }, 256.156 + 256.157 + /** 256.158 + * Show the dialog element 256.159 + */ 256.160 + show: function(elementToChange) { 256.161 + var firstField = this.container.querySelector(SELECTOR_FORM_ELEMENTS); 256.162 + 256.163 + this._observe(); 256.164 + this.update(elementToChange); 256.165 + 256.166 + dom.addClass(this.link, CLASS_NAME_OPENED); 256.167 + this.container.style.display = ""; 256.168 + this.isOpen = true; 256.169 + this.fire("show"); 256.170 + 256.171 + if (firstField && !elementToChange) { 256.172 + try { 256.173 + firstField.focus(); 256.174 + } catch(e) {} 256.175 + } 256.176 + }, 256.177 + 256.178 + /** 256.179 + * Hide the dialog element 256.180 + */ 256.181 + _hide: function(focus) { 256.182 + this.elementToChange = null; 256.183 + dom.removeClass(this.link, CLASS_NAME_OPENED); 256.184 + this.container.style.display = "none"; 256.185 + this.isOpen = false; 256.186 + }, 256.187 + 256.188 + hide: function() { 256.189 + this._hide(); 256.190 + this.fire("hide"); 256.191 + }, 256.192 + 256.193 + cancel: function() { 256.194 + this._hide(); 256.195 + this.fire("cancel"); 256.196 + } 256.197 + }); 256.198 +})(wysihtml); //jshint ignore:line 256.199 + 256.200 +(function(wysihtml) { 256.201 + var dom = wysihtml.dom, 256.202 + SELECTOR_FIELDS = "[data-wysihtml-dialog-field]", 256.203 + ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field"; 256.204 + 256.205 + wysihtml.toolbar.Dialog_bgColorStyle = wysihtml.toolbar.Dialog.extend({ 256.206 + multiselect: true, 256.207 + 256.208 + _serialize: function() { 256.209 + var data = {}, 256.210 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 256.211 + length = fields.length, 256.212 + i = 0; 256.213 + 256.214 + for (; i<length; i++) { 256.215 + data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value; 256.216 + } 256.217 + return data; 256.218 + }, 256.219 + 256.220 + _interpolate: function(avoidHiddenFields) { 256.221 + var field, 256.222 + fieldName, 256.223 + newValue, 256.224 + focusedElement = document.querySelector(":focus"), 256.225 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 256.226 + length = fields.length, 256.227 + i = 0, 256.228 + firstElement = (this.elementToChange) ? ((wysihtml.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null, 256.229 + colorStr = (firstElement) ? firstElement.getAttribute('style') : null, 256.230 + color = (colorStr) ? wysihtml.quirks.styleParser.parseColor(colorStr, "background-color") : null; 256.231 + 256.232 + for (; i<length; i++) { 256.233 + field = fields[i]; 256.234 + // Never change elements where the user is currently typing in 256.235 + if (field === focusedElement) { 256.236 + continue; 256.237 + } 256.238 + // Don't update hidden fields3 256.239 + if (avoidHiddenFields && field.type === "hidden") { 256.240 + continue; 256.241 + } 256.242 + if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") { 256.243 + if (color) { 256.244 + if (color[3] && color[3] != 1) { 256.245 + field.value = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ");"; 256.246 + } else { 256.247 + field.value = "rgb(" + color[0] + "," + color[1] + "," + color[2] + ");"; 256.248 + } 256.249 + } else { 256.250 + field.value = "rgb(0,0,0);"; 256.251 + } 256.252 + } 256.253 + } 256.254 + } 256.255 + 256.256 + }); 256.257 +})(wysihtml); 256.258 + 256.259 +(function(wysihtml) { 256.260 + wysihtml.toolbar.Dialog_createTable = wysihtml.toolbar.Dialog.extend({ 256.261 + show: function(elementToChange) { 256.262 + this.base(elementToChange); 256.263 + } 256.264 + }); 256.265 +})(wysihtml); 256.266 + 256.267 +(function(wysihtml) { 256.268 + var dom = wysihtml.dom, 256.269 + SELECTOR_FIELDS = "[data-wysihtml-dialog-field]", 256.270 + ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field"; 256.271 + 256.272 + wysihtml.toolbar.Dialog_fontSizeStyle = wysihtml.toolbar.Dialog.extend({ 256.273 + multiselect: true, 256.274 + 256.275 + _serialize: function() { 256.276 + return {"size" : this.container.querySelector('[data-wysihtml-dialog-field="size"]').value}; 256.277 + }, 256.278 + 256.279 + _interpolate: function(avoidHiddenFields) { 256.280 + var focusedElement = document.querySelector(":focus"), 256.281 + field = this.container.querySelector("[data-wysihtml-dialog-field='size']"), 256.282 + firstElement = (this.elementToChange) ? ((wysihtml.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null, 256.283 + styleStr = (firstElement) ? firstElement.getAttribute('style') : null, 256.284 + size = (styleStr) ? wysihtml.quirks.styleParser.parseFontSize(styleStr) : null; 256.285 + 256.286 + if (field && field !== focusedElement && size && !(/^\s*$/).test(size)) { 256.287 + field.value = size; 256.288 + } 256.289 + } 256.290 + }); 256.291 +})(wysihtml); 256.292 + 256.293 +(function(wysihtml) { 256.294 + var SELECTOR_FIELDS = "[data-wysihtml-dialog-field]", 256.295 + ATTRIBUTE_FIELDS = "data-wysihtml-dialog-field"; 256.296 + 256.297 + wysihtml.toolbar.Dialog_foreColorStyle = wysihtml.toolbar.Dialog.extend({ 256.298 + multiselect: true, 256.299 + 256.300 + _serialize: function() { 256.301 + var data = {}, 256.302 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 256.303 + length = fields.length, 256.304 + i = 0; 256.305 + 256.306 + for (; i<length; i++) { 256.307 + data[fields[i].getAttribute(ATTRIBUTE_FIELDS)] = fields[i].value; 256.308 + } 256.309 + return data; 256.310 + }, 256.311 + 256.312 + _interpolate: function(avoidHiddenFields) { 256.313 + var field, colourMode, 256.314 + styleParser = wysihtml.quirks.styleParser, 256.315 + focusedElement = document.querySelector(":focus"), 256.316 + fields = this.container.querySelectorAll(SELECTOR_FIELDS), 256.317 + length = fields.length, 256.318 + i = 0, 256.319 + firstElement = (this.elementToChange) ? ((wysihtml.lang.object(this.elementToChange).isArray()) ? this.elementToChange[0] : this.elementToChange) : null, 256.320 + colourStr = (firstElement) ? firstElement.getAttribute("style") : null, 256.321 + colour = (colourStr) ? styleParser.parseColor(colourStr, "color") : null; 256.322 + 256.323 + for (; i<length; i++) { 256.324 + field = fields[i]; 256.325 + // Never change elements where the user is currently typing in 256.326 + if (field === focusedElement) { 256.327 + continue; 256.328 + } 256.329 + // Don't update hidden fields3 256.330 + if (avoidHiddenFields && field.type === "hidden") { 256.331 + continue; 256.332 + } 256.333 + if (field.getAttribute(ATTRIBUTE_FIELDS) === "color") { 256.334 + colourMode = (field.dataset.colormode || "rgb").toLowerCase(); 256.335 + colourMode = colourMode === "hex" ? "hash" : colourMode; 256.336 + 256.337 + if (colour) { 256.338 + field.value = styleParser.unparseColor(colour, colourMode); 256.339 + } else { 256.340 + field.value = styleParser.unparseColor([0, 0, 0], colourMode); 256.341 + } 256.342 + } 256.343 + } 256.344 + } 256.345 + 256.346 + }); 256.347 +})(wysihtml); 256.348 + 256.349 +/** 256.350 + * Converts speech-to-text and inserts this into the editor 256.351 + * As of now (2011/03/25) this only is supported in Chrome >= 11 256.352 + * 256.353 + * Note that it sends the recorded audio to the google speech recognition api: 256.354 + * http://stackoverflow.com/questions/4361826/does-chrome-have-buil-in-speech-recognition-for-input-type-text-x-webkit-speec 256.355 + * 256.356 + * Current HTML5 draft can be found here 256.357 + * http://lists.w3.org/Archives/Public/public-xg-htmlspeech/2011Feb/att-0020/api-draft.html 256.358 + * 256.359 + * "Accessing Google Speech API Chrome 11" 256.360 + * http://mikepultz.com/2011/03/accessing-google-speech-api-chrome-11/ 256.361 + */ 256.362 +(function(wysihtml) { 256.363 + var dom = wysihtml.dom; 256.364 + 256.365 + var linkStyles = { 256.366 + position: "relative" 256.367 + }; 256.368 + 256.369 + var wrapperStyles = { 256.370 + left: 0, 256.371 + margin: 0, 256.372 + opacity: 0, 256.373 + overflow: "hidden", 256.374 + padding: 0, 256.375 + position: "absolute", 256.376 + top: 0, 256.377 + zIndex: 1 256.378 + }; 256.379 + 256.380 + var inputStyles = { 256.381 + cursor: "inherit", 256.382 + fontSize: "50px", 256.383 + height: "50px", 256.384 + marginTop: "-25px", 256.385 + outline: 0, 256.386 + padding: 0, 256.387 + position: "absolute", 256.388 + right: "-4px", 256.389 + top: "50%" 256.390 + }; 256.391 + 256.392 + var inputAttributes = { 256.393 + "x-webkit-speech": "", 256.394 + "speech": "" 256.395 + }; 256.396 + 256.397 + wysihtml.toolbar.Speech = function(parent, link) { 256.398 + var input = document.createElement("input"); 256.399 + if (!wysihtml.browser.supportsSpeechApiOn(input)) { 256.400 + link.style.display = "none"; 256.401 + return; 256.402 + } 256.403 + var lang = parent.editor.textarea.element.getAttribute("lang"); 256.404 + if (lang) { 256.405 + inputAttributes.lang = lang; 256.406 + } 256.407 + 256.408 + var wrapper = document.createElement("div"); 256.409 + 256.410 + wysihtml.lang.object(wrapperStyles).merge({ 256.411 + width: link.offsetWidth + "px", 256.412 + height: link.offsetHeight + "px" 256.413 + }); 256.414 + 256.415 + dom.insert(input).into(wrapper); 256.416 + dom.insert(wrapper).into(link); 256.417 + 256.418 + dom.setStyles(inputStyles).on(input); 256.419 + dom.setAttributes(inputAttributes).on(input); 256.420 + 256.421 + dom.setStyles(wrapperStyles).on(wrapper); 256.422 + dom.setStyles(linkStyles).on(link); 256.423 + 256.424 + var eventName = "onwebkitspeechchange" in input ? "webkitspeechchange" : "speechchange"; 256.425 + dom.observe(input, eventName, function() { 256.426 + parent.execCommand("insertText", input.value); 256.427 + input.value = ""; 256.428 + }); 256.429 + 256.430 + dom.observe(input, "click", function(event) { 256.431 + if (dom.hasClass(link, "wysihtml-command-disabled")) { 256.432 + event.preventDefault(); 256.433 + } 256.434 + 256.435 + event.stopPropagation(); 256.436 + }); 256.437 + }; 256.438 +})(wysihtml); 256.439 + 256.440 +/** 256.441 + * Toolbar 256.442 + * 256.443 + * @param {Object} parent Reference to instance of Editor instance 256.444 + * @param {Element} container Reference to the toolbar container element 256.445 + * 256.446 + * @example 256.447 + * <div id="toolbar"> 256.448 + * <a data-wysihtml-command="createLink">insert link</a> 256.449 + * <a data-wysihtml-command="formatBlock" data-wysihtml-command-value="h1">insert h1</a> 256.450 + * </div> 256.451 + * 256.452 + * <script> 256.453 + * var toolbar = new wysihtml.toolbar.Toolbar(editor, document.getElementById("toolbar")); 256.454 + * </script> 256.455 + */ 256.456 +(function(wysihtml) { 256.457 + var CLASS_NAME_COMMAND_DISABLED = "wysihtml-command-disabled", 256.458 + CLASS_NAME_COMMANDS_DISABLED = "wysihtml-commands-disabled", 256.459 + CLASS_NAME_COMMAND_ACTIVE = "wysihtml-command-active", 256.460 + CLASS_NAME_ACTION_ACTIVE = "wysihtml-action-active", 256.461 + dom = wysihtml.dom; 256.462 + 256.463 + wysihtml.toolbar.Toolbar = Base.extend( 256.464 + /** @scope wysihtml.toolbar.Toolbar.prototype */ { 256.465 + constructor: function(editor, container, showOnInit) { 256.466 + this.editor = editor; 256.467 + this.container = typeof(container) === "string" ? document.getElementById(container) : container; 256.468 + this.composer = editor.composer; 256.469 + 256.470 + this._getLinks("command"); 256.471 + this._getLinks("action"); 256.472 + 256.473 + this._observe(); 256.474 + if (showOnInit) { this.show(); } 256.475 + 256.476 + if (editor.config.classNameCommandDisabled != null) { 256.477 + CLASS_NAME_COMMAND_DISABLED = editor.config.classNameCommandDisabled; 256.478 + } 256.479 + if (editor.config.classNameCommandsDisabled != null) { 256.480 + CLASS_NAME_COMMANDS_DISABLED = editor.config.classNameCommandsDisabled; 256.481 + } 256.482 + if (editor.config.classNameCommandActive != null) { 256.483 + CLASS_NAME_COMMAND_ACTIVE = editor.config.classNameCommandActive; 256.484 + } 256.485 + if (editor.config.classNameActionActive != null) { 256.486 + CLASS_NAME_ACTION_ACTIVE = editor.config.classNameActionActive; 256.487 + } 256.488 + 256.489 + var speechInputLinks = this.container.querySelectorAll("[data-wysihtml-command=insertSpeech]"), 256.490 + length = speechInputLinks.length, 256.491 + i = 0; 256.492 + for (; i<length; i++) { 256.493 + new wysihtml.toolbar.Speech(this, speechInputLinks[i]); 256.494 + } 256.495 + }, 256.496 + 256.497 + _getLinks: function(type) { 256.498 + var links = this[type + "Links"] = wysihtml.lang.array(this.container.querySelectorAll("[data-wysihtml-" + type + "]")).get(), 256.499 + length = links.length, 256.500 + i = 0, 256.501 + mapping = this[type + "Mapping"] = {}, 256.502 + link, 256.503 + group, 256.504 + name, 256.505 + value, 256.506 + dialog, 256.507 + tracksBlankValue; 256.508 + 256.509 + for (; i<length; i++) { 256.510 + link = links[i]; 256.511 + name = link.getAttribute("data-wysihtml-" + type); 256.512 + value = link.getAttribute("data-wysihtml-" + type + "-value"); 256.513 + tracksBlankValue = link.getAttribute("data-wysihtml-" + type + "-blank-value"); 256.514 + group = this.container.querySelector("[data-wysihtml-" + type + "-group='" + name + "']"); 256.515 + dialog = this._getDialog(link, name); 256.516 + 256.517 + mapping[name + ":" + value] = { 256.518 + link: link, 256.519 + group: group, 256.520 + name: name, 256.521 + value: value, 256.522 + tracksBlankValue: tracksBlankValue, 256.523 + dialog: dialog, 256.524 + state: false 256.525 + }; 256.526 + } 256.527 + }, 256.528 + 256.529 + _getDialog: function(link, command) { 256.530 + var that = this, 256.531 + dialogElement = this.container.querySelector("[data-wysihtml-dialog='" + command + "']"), 256.532 + dialog, caretBookmark; 256.533 + 256.534 + if (dialogElement) { 256.535 + if (wysihtml.toolbar["Dialog_" + command]) { 256.536 + dialog = new wysihtml.toolbar["Dialog_" + command](link, dialogElement); 256.537 + } else { 256.538 + dialog = new wysihtml.toolbar.Dialog(link, dialogElement); 256.539 + } 256.540 + 256.541 + dialog.on("show", function() { 256.542 + caretBookmark = that.composer.selection.getBookmark(); 256.543 + that.editor.fire("show:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); 256.544 + }); 256.545 + 256.546 + dialog.on("save", function(attributes) { 256.547 + if (caretBookmark) { 256.548 + that.composer.selection.setBookmark(caretBookmark); 256.549 + } 256.550 + that._execCommand(command, attributes); 256.551 + that.editor.fire("save:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); 256.552 + that._hideAllDialogs(); 256.553 + that._preventInstantFocus(); 256.554 + caretBookmark = undefined; 256.555 + 256.556 + }); 256.557 + 256.558 + dialog.on("cancel", function() { 256.559 + if (caretBookmark) { 256.560 + that.composer.selection.setBookmark(caretBookmark); 256.561 + } 256.562 + that.editor.fire("cancel:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); 256.563 + caretBookmark = undefined; 256.564 + that._preventInstantFocus(); 256.565 + }); 256.566 + 256.567 + dialog.on("hide", function() { 256.568 + that.editor.fire("hide:dialog", { command: command, dialogContainer: dialogElement, commandLink: link }); 256.569 + caretBookmark = undefined; 256.570 + }); 256.571 + 256.572 + } 256.573 + return dialog; 256.574 + }, 256.575 + 256.576 + /** 256.577 + * @example 256.578 + * var toolbar = new wysihtml.Toolbar(); 256.579 + * // Insert a <blockquote> element or wrap current selection in <blockquote> 256.580 + * toolbar.execCommand("formatBlock", "blockquote"); 256.581 + */ 256.582 + execCommand: function(command, commandValue) { 256.583 + if (this.commandsDisabled) { 256.584 + return; 256.585 + } 256.586 + 256.587 + this._execCommand(command, commandValue); 256.588 + }, 256.589 + 256.590 + _execCommand: function(command, commandValue) { 256.591 + // Make sure that composer is focussed (false => don't move caret to the end) 256.592 + this.editor.focus(false); 256.593 + 256.594 + this.composer.commands.exec(command, commandValue); 256.595 + this._updateLinkStates(); 256.596 + }, 256.597 + 256.598 + execAction: function(action) { 256.599 + var editor = this.editor; 256.600 + if (action === "change_view") { 256.601 + if (editor.currentView === editor.textarea || editor.currentView === "source") { 256.602 + editor.fire("change_view", "composer"); 256.603 + } else { 256.604 + editor.fire("change_view", "textarea"); 256.605 + } 256.606 + } 256.607 + if (action == "showSource") { 256.608 + editor.fire("showSource"); 256.609 + } 256.610 + }, 256.611 + 256.612 + _observe: function() { 256.613 + var that = this, 256.614 + editor = this.editor, 256.615 + container = this.container, 256.616 + links = this.commandLinks.concat(this.actionLinks), 256.617 + length = links.length, 256.618 + i = 0; 256.619 + 256.620 + for (; i<length; i++) { 256.621 + // 'javascript:;' and unselectable=on Needed for IE, but done in all browsers to make sure that all get the same css applied 256.622 + // (you know, a:link { ... } doesn't match anchors with missing href attribute) 256.623 + if (links[i].nodeName === "A") { 256.624 + dom.setAttributes({ 256.625 + href: "javascript:;", 256.626 + unselectable: "on" 256.627 + }).on(links[i]); 256.628 + } else { 256.629 + dom.setAttributes({ unselectable: "on" }).on(links[i]); 256.630 + } 256.631 + } 256.632 + 256.633 + // Needed for opera and chrome 256.634 + dom.delegate(container, "[data-wysihtml-command], [data-wysihtml-action]", "mousedown", function(event) { event.preventDefault(); }); 256.635 + 256.636 + dom.delegate(container, "[data-wysihtml-command]", "click", function(event) { 256.637 + var state, 256.638 + link = this, 256.639 + command = link.getAttribute("data-wysihtml-command"), 256.640 + commandValue = link.getAttribute("data-wysihtml-command-value"), 256.641 + commandObj = that.commandMapping[command + ":" + commandValue]; 256.642 + 256.643 + if (commandValue || !commandObj.dialog) { 256.644 + that.execCommand(command, commandValue); 256.645 + } else { 256.646 + state = getCommandState(that.composer, commandObj); 256.647 + commandObj.dialog.show(state); 256.648 + } 256.649 + 256.650 + event.preventDefault(); 256.651 + }); 256.652 + 256.653 + dom.delegate(container, "[data-wysihtml-action]", "click", function(event) { 256.654 + var action = this.getAttribute("data-wysihtml-action"); 256.655 + that.execAction(action); 256.656 + event.preventDefault(); 256.657 + }); 256.658 + 256.659 + editor.on("interaction:composer", function(event) { 256.660 + if (!that.preventFocus) { 256.661 + that._updateLinkStates(); 256.662 + } 256.663 + }); 256.664 + 256.665 + this._ownerDocumentClick = function(event) { 256.666 + if (!wysihtml.dom.contains(that.container, event.target) && !wysihtml.dom.contains(that.composer.element, event.target)) { 256.667 + that._updateLinkStates(); 256.668 + that._preventInstantFocus(); 256.669 + } 256.670 + }; 256.671 + 256.672 + this.container.ownerDocument.addEventListener("click", this._ownerDocumentClick, false); 256.673 + this.editor.on("destroy:composer", this.destroy.bind(this)); 256.674 + 256.675 + if (this.editor.config.handleTables) { 256.676 + editor.on("tableselect:composer", function() { 256.677 + that.container.querySelectorAll('[data-wysihtml-hiddentools="table"]')[0].style.display = ""; 256.678 + }); 256.679 + editor.on("tableunselect:composer", function() { 256.680 + that.container.querySelectorAll('[data-wysihtml-hiddentools="table"]')[0].style.display = "none"; 256.681 + }); 256.682 + } 256.683 + 256.684 + editor.on("change_view", function(currentView) { 256.685 + // Set timeout needed in order to let the blur event fire first 256.686 + setTimeout(function() { 256.687 + that.commandsDisabled = (currentView !== "composer"); 256.688 + that._updateLinkStates(); 256.689 + if (that.commandsDisabled) { 256.690 + dom.addClass(container, CLASS_NAME_COMMANDS_DISABLED); 256.691 + } else { 256.692 + dom.removeClass(container, CLASS_NAME_COMMANDS_DISABLED); 256.693 + } 256.694 + }, 0); 256.695 + }); 256.696 + }, 256.697 + 256.698 + destroy: function() { 256.699 + this.container.ownerDocument.removeEventListener("click", this._ownerDocumentClick, false); 256.700 + }, 256.701 + 256.702 + _hideAllDialogs: function() { 256.703 + var commandMapping = this.commandMapping; 256.704 + for (var i in commandMapping) { 256.705 + if (commandMapping[i].dialog) { 256.706 + commandMapping[i].dialog.hide(); 256.707 + } 256.708 + } 256.709 + }, 256.710 + 256.711 + _preventInstantFocus: function() { 256.712 + this.preventFocus = true; 256.713 + setTimeout(function() { 256.714 + this.preventFocus = false; 256.715 + }.bind(this),0); 256.716 + }, 256.717 + 256.718 + _updateLinkStates: function() { 256.719 + 256.720 + var i, state, action, command, displayDialogAttributeValue, 256.721 + commandMapping = this.commandMapping, 256.722 + composer = this.composer, 256.723 + actionMapping = this.actionMapping; 256.724 + // every millisecond counts... this is executed quite often 256.725 + for (i in commandMapping) { 256.726 + command = commandMapping[i]; 256.727 + if (this.commandsDisabled) { 256.728 + state = false; 256.729 + dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE); 256.730 + if (command.group) { 256.731 + dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE); 256.732 + } 256.733 + if (command.dialog) { 256.734 + command.dialog.hide(); 256.735 + } 256.736 + } else { 256.737 + state = this.composer.commands.state(command.name, command.value); 256.738 + dom.removeClass(command.link, CLASS_NAME_COMMAND_DISABLED); 256.739 + if (command.group) { 256.740 + dom.removeClass(command.group, CLASS_NAME_COMMAND_DISABLED); 256.741 + } 256.742 + } 256.743 + if (command.state === state && !command.tracksBlankValue) { 256.744 + continue; 256.745 + } 256.746 + 256.747 + command.state = state; 256.748 + if (state) { 256.749 + if (command.tracksBlankValue) { 256.750 + dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE); 256.751 + } else { 256.752 + dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE); 256.753 + if (command.group) { 256.754 + dom.addClass(command.group, CLASS_NAME_COMMAND_ACTIVE); 256.755 + } 256.756 + // commands with fixed value can not have a dialog. 256.757 + if (command.dialog && (typeof command.value === "undefined" || command.value === null)) { 256.758 + if (state && typeof state === "object") { 256.759 + state = getCommandState(composer, command); 256.760 + command.state = state; 256.761 + 256.762 + // If dialog has dataset.showdialogonselection set as true, 256.763 + // Dialog displays on text state becoming active regardless of clobal showToolbarDialogsOnSelection options value 256.764 + displayDialogAttributeValue = command.dialog.container.dataset ? command.dialog.container.dataset.showdialogonselection : false; 256.765 + 256.766 + if (composer.config.showToolbarDialogsOnSelection || displayDialogAttributeValue) { 256.767 + command.dialog.show(state); 256.768 + } else { 256.769 + command.dialog.update(state); 256.770 + } 256.771 + } else { 256.772 + command.dialog.hide(); 256.773 + } 256.774 + } 256.775 + } 256.776 + } else { 256.777 + if (command.tracksBlankValue) { 256.778 + dom.addClass(command.link, CLASS_NAME_COMMAND_ACTIVE); 256.779 + } else { 256.780 + dom.removeClass(command.link, CLASS_NAME_COMMAND_ACTIVE); 256.781 + if (command.group) { 256.782 + dom.removeClass(command.group, CLASS_NAME_COMMAND_ACTIVE); 256.783 + } 256.784 + // commands with fixed value can not have a dialog. 256.785 + if (command.dialog && !command.value) { 256.786 + command.dialog.hide(); 256.787 + } 256.788 + } 256.789 + } 256.790 + } 256.791 + 256.792 + for (i in actionMapping) { 256.793 + action = actionMapping[i]; 256.794 + 256.795 + if (action.name === "change_view") { 256.796 + action.state = this.editor.currentView === this.editor.textarea || this.editor.currentView === "source"; 256.797 + if (action.state) { 256.798 + dom.addClass(action.link, CLASS_NAME_ACTION_ACTIVE); 256.799 + } else { 256.800 + dom.removeClass(action.link, CLASS_NAME_ACTION_ACTIVE); 256.801 + } 256.802 + } 256.803 + } 256.804 + }, 256.805 + 256.806 + show: function() { 256.807 + this.container.style.display = ""; 256.808 + }, 256.809 + 256.810 + hide: function() { 256.811 + this.container.style.display = "none"; 256.812 + } 256.813 + }); 256.814 + 256.815 + function getCommandState (composer, command) { 256.816 + var state = composer.commands.state(command.name, command.value); 256.817 + 256.818 + // Grab first and only object/element in state array, otherwise convert state into boolean 256.819 + // to avoid showing a dialog for multiple selected elements which may have different attributes 256.820 + // eg. when two links with different href are selected, the state will be an array consisting of both link elements 256.821 + // but the dialog interface can only update one 256.822 + if (!command.dialog.multiselect && wysihtml.lang.object(state).isArray()) { 256.823 + state = state.length === 1 ? state[0] : true; 256.824 + } 256.825 + 256.826 + return state; 256.827 + } 256.828 + 256.829 + // Extend defaults 256.830 + 256.831 + // Id of the toolbar element, pass falsey value if you don't want any toolbar logic 256.832 + wysihtml.Editor.prototype.defaults.toolbar = undefined; 256.833 + 256.834 + // Whether toolbar is displayed after init by script automatically. 256.835 + // Can be set to false if toolobar is set to display only on editable area focus 256.836 + wysihtml.Editor.prototype.defaults.showToolbarAfterInit = true; 256.837 + 256.838 + // With default toolbar it shows dialogs in toolbar when their related text format state becomes active (click on link in text opens link dialogue) 256.839 + wysihtml.Editor.prototype.defaults.showToolbarDialogsOnSelection= true; 256.840 + 256.841 + // Bind toolbar initiation on editor instance creation 256.842 + wysihtml.extendEditor(function(editor) { 256.843 + if (editor.config.toolbar) { 256.844 + editor.toolbar = new wysihtml.toolbar.Toolbar(editor, editor.config.toolbar, editor.config.showToolbarAfterInit); 256.845 + editor.on('destroy:composer', function() { 256.846 + if (editor && editor.toolbar) { 256.847 + editor.toolbar.destroy(); 256.848 + } 256.849 + }); 256.850 + } 256.851 + }); 256.852 + 256.853 +})(wysihtml);
257.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 257.2 +++ b/static/wysihtml/wysihtml_liquidfeedback_rules.js Sun Jul 15 14:07:29 2018 +0200 257.3 @@ -0,0 +1,24 @@ 257.4 +var wysihtmlParserRules = { 257.5 + tags: { 257.6 + b: {}, 257.7 + i: {}, 257.8 + br: {}, 257.9 + p: {}, 257.10 + ul: {}, 257.11 + ol: {}, 257.12 + li: {}, 257.13 + a: { 257.14 + check_attributes: { 257.15 + href: "url" // important to avoid XSS 257.16 + } 257.17 + }, 257.18 + h1: {}, 257.19 + h2: {}, 257.20 + h3: {}, 257.21 + h4: {}, 257.22 + h5: {}, 257.23 + h6: {}, 257.24 + sup: {}, 257.25 + sub: {} 257.26 + } 257.27 +};
258.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 258.2 +++ b/style/mdl/LICENSE Sun Jul 15 14:07:29 2018 +0200 258.3 @@ -0,0 +1,212 @@ 258.4 + 258.5 + Apache License 258.6 + Version 2.0, January 2004 258.7 + http://www.apache.org/licenses/ 258.8 + 258.9 + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 258.10 + 258.11 + 1. Definitions. 258.12 + 258.13 + "License" shall mean the terms and conditions for use, reproduction, 258.14 + and distribution as defined by Sections 1 through 9 of this document. 258.15 + 258.16 + "Licensor" shall mean the copyright owner or entity authorized by 258.17 + the copyright owner that is granting the License. 258.18 + 258.19 + "Legal Entity" shall mean the union of the acting entity and all 258.20 + other entities that control, are controlled by, or are under common 258.21 + control with that entity. For the purposes of this definition, 258.22 + "control" means (i) the power, direct or indirect, to cause the 258.23 + direction or management of such entity, whether by contract or 258.24 + otherwise, or (ii) ownership of fifty percent (50%) or more of the 258.25 + outstanding shares, or (iii) beneficial ownership of such entity. 258.26 + 258.27 + "You" (or "Your") shall mean an individual or Legal Entity 258.28 + exercising permissions granted by this License. 258.29 + 258.30 + "Source" form shall mean the preferred form for making modifications, 258.31 + including but not limited to software source code, documentation 258.32 + source, and configuration files. 258.33 + 258.34 + "Object" form shall mean any form resulting from mechanical 258.35 + transformation or translation of a Source form, including but 258.36 + not limited to compiled object code, generated documentation, 258.37 + and conversions to other media types. 258.38 + 258.39 + "Work" shall mean the work of authorship, whether in Source or 258.40 + Object form, made available under the License, as indicated by a 258.41 + copyright notice that is included in or attached to the work 258.42 + (an example is provided in the Appendix below). 258.43 + 258.44 + "Derivative Works" shall mean any work, whether in Source or Object 258.45 + form, that is based on (or derived from) the Work and for which the 258.46 + editorial revisions, annotations, elaborations, or other modifications 258.47 + represent, as a whole, an original work of authorship. For the purposes 258.48 + of this License, Derivative Works shall not include works that remain 258.49 + separable from, or merely link (or bind by name) to the interfaces of, 258.50 + the Work and Derivative Works thereof. 258.51 + 258.52 + "Contribution" shall mean any work of authorship, including 258.53 + the original version of the Work and any modifications or additions 258.54 + to that Work or Derivative Works thereof, that is intentionally 258.55 + submitted to Licensor for inclusion in the Work by the copyright owner 258.56 + or by an individual or Legal Entity authorized to submit on behalf of 258.57 + the copyright owner. For the purposes of this definition, "submitted" 258.58 + means any form of electronic, verbal, or written communication sent 258.59 + to the Licensor or its representatives, including but not limited to 258.60 + communication on electronic mailing lists, source code control systems, 258.61 + and issue tracking systems that are managed by, or on behalf of, the 258.62 + Licensor for the purpose of discussing and improving the Work, but 258.63 + excluding communication that is conspicuously marked or otherwise 258.64 + designated in writing by the copyright owner as "Not a Contribution." 258.65 + 258.66 + "Contributor" shall mean Licensor and any individual or Legal Entity 258.67 + on behalf of whom a Contribution has been received by Licensor and 258.68 + subsequently incorporated within the Work. 258.69 + 258.70 + 2. Grant of Copyright License. Subject to the terms and conditions of 258.71 + this License, each Contributor hereby grants to You a perpetual, 258.72 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 258.73 + copyright license to reproduce, prepare Derivative Works of, 258.74 + publicly display, publicly perform, sublicense, and distribute the 258.75 + Work and such Derivative Works in Source or Object form. 258.76 + 258.77 + 3. Grant of Patent License. Subject to the terms and conditions of 258.78 + this License, each Contributor hereby grants to You a perpetual, 258.79 + worldwide, non-exclusive, no-charge, royalty-free, irrevocable 258.80 + (except as stated in this section) patent license to make, have made, 258.81 + use, offer to sell, sell, import, and otherwise transfer the Work, 258.82 + where such license applies only to those patent claims licensable 258.83 + by such Contributor that are necessarily infringed by their 258.84 + Contribution(s) alone or by combination of their Contribution(s) 258.85 + with the Work to which such Contribution(s) was submitted. If You 258.86 + institute patent litigation against any entity (including a 258.87 + cross-claim or counterclaim in a lawsuit) alleging that the Work 258.88 + or a Contribution incorporated within the Work constitutes direct 258.89 + or contributory patent infringement, then any patent licenses 258.90 + granted to You under this License for that Work shall terminate 258.91 + as of the date such litigation is filed. 258.92 + 258.93 + 4. Redistribution. You may reproduce and distribute copies of the 258.94 + Work or Derivative Works thereof in any medium, with or without 258.95 + modifications, and in Source or Object form, provided that You 258.96 + meet the following conditions: 258.97 + 258.98 + (a) You must give any other recipients of the Work or 258.99 + Derivative Works a copy of this License; and 258.100 + 258.101 + (b) You must cause any modified files to carry prominent notices 258.102 + stating that You changed the files; and 258.103 + 258.104 + (c) You must retain, in the Source form of any Derivative Works 258.105 + that You distribute, all copyright, patent, trademark, and 258.106 + attribution notices from the Source form of the Work, 258.107 + excluding those notices that do not pertain to any part of 258.108 + the Derivative Works; and 258.109 + 258.110 + (d) If the Work includes a "NOTICE" text file as part of its 258.111 + distribution, then any Derivative Works that You distribute must 258.112 + include a readable copy of the attribution notices contained 258.113 + within such NOTICE file, excluding those notices that do not 258.114 + pertain to any part of the Derivative Works, in at least one 258.115 + of the following places: within a NOTICE text file distributed 258.116 + as part of the Derivative Works; within the Source form or 258.117 + documentation, if provided along with the Derivative Works; or, 258.118 + within a display generated by the Derivative Works, if and 258.119 + wherever such third-party notices normally appear. The contents 258.120 + of the NOTICE file are for informational purposes only and 258.121 + do not modify the License. You may add Your own attribution 258.122 + notices within Derivative Works that You distribute, alongside 258.123 + or as an addendum to the NOTICE text from the Work, provided 258.124 + that such additional attribution notices cannot be construed 258.125 + as modifying the License. 258.126 + 258.127 + You may add Your own copyright statement to Your modifications and 258.128 + may provide additional or different license terms and conditions 258.129 + for use, reproduction, or distribution of Your modifications, or 258.130 + for any such Derivative Works as a whole, provided Your use, 258.131 + reproduction, and distribution of the Work otherwise complies with 258.132 + the conditions stated in this License. 258.133 + 258.134 + 5. Submission of Contributions. Unless You explicitly state otherwise, 258.135 + any Contribution intentionally submitted for inclusion in the Work 258.136 + by You to the Licensor shall be under the terms and conditions of 258.137 + this License, without any additional terms or conditions. 258.138 + Notwithstanding the above, nothing herein shall supersede or modify 258.139 + the terms of any separate license agreement you may have executed 258.140 + with Licensor regarding such Contributions. 258.141 + 258.142 + 6. Trademarks. This License does not grant permission to use the trade 258.143 + names, trademarks, service marks, or product names of the Licensor, 258.144 + except as required for reasonable and customary use in describing the 258.145 + origin of the Work and reproducing the content of the NOTICE file. 258.146 + 258.147 + 7. Disclaimer of Warranty. Unless required by applicable law or 258.148 + agreed to in writing, Licensor provides the Work (and each 258.149 + Contributor provides its Contributions) on an "AS IS" BASIS, 258.150 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 258.151 + implied, including, without limitation, any warranties or conditions 258.152 + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 258.153 + PARTICULAR PURPOSE. You are solely responsible for determining the 258.154 + appropriateness of using or redistributing the Work and assume any 258.155 + risks associated with Your exercise of permissions under this License. 258.156 + 258.157 + 8. Limitation of Liability. In no event and under no legal theory, 258.158 + whether in tort (including negligence), contract, or otherwise, 258.159 + unless required by applicable law (such as deliberate and grossly 258.160 + negligent acts) or agreed to in writing, shall any Contributor be 258.161 + liable to You for damages, including any direct, indirect, special, 258.162 + incidental, or consequential damages of any character arising as a 258.163 + result of this License or out of the use or inability to use the 258.164 + Work (including but not limited to damages for loss of goodwill, 258.165 + work stoppage, computer failure or malfunction, or any and all 258.166 + other commercial damages or losses), even if such Contributor 258.167 + has been advised of the possibility of such damages. 258.168 + 258.169 + 9. Accepting Warranty or Additional Liability. While redistributing 258.170 + the Work or Derivative Works thereof, You may choose to offer, 258.171 + and charge a fee for, acceptance of support, warranty, indemnity, 258.172 + or other liability obligations and/or rights consistent with this 258.173 + License. However, in accepting such obligations, You may act only 258.174 + on Your own behalf and on Your sole responsibility, not on behalf 258.175 + of any other Contributor, and only if You agree to indemnify, 258.176 + defend, and hold each Contributor harmless for any liability 258.177 + incurred by, or claims asserted against, such Contributor by reason 258.178 + of your accepting any such warranty or additional liability. 258.179 + 258.180 + END OF TERMS AND CONDITIONS 258.181 + 258.182 + APPENDIX: How to apply the Apache License to your work. 258.183 + 258.184 + To apply the Apache License to your work, attach the following 258.185 + boilerplate notice, with the fields enclosed by brackets "[]" 258.186 + replaced with your own identifying information. (Don't include 258.187 + the brackets!) The text should be enclosed in the appropriate 258.188 + comment syntax for the file format. We also recommend that a 258.189 + file or class name and description of purpose be included on the 258.190 + same "printed page" as the copyright notice for easier 258.191 + identification within third-party archives. 258.192 + 258.193 + Copyright 2015 Google Inc 258.194 + 258.195 + Licensed under the Apache License, Version 2.0 (the "License"); 258.196 + you may not use this file except in compliance with the License. 258.197 + You may obtain a copy of the License at 258.198 + 258.199 + http://www.apache.org/licenses/LICENSE-2.0 258.200 + 258.201 + Unless required by applicable law or agreed to in writing, software 258.202 + distributed under the License is distributed on an "AS IS" BASIS, 258.203 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 258.204 + See the License for the specific language governing permissions and 258.205 + limitations under the License. 258.206 + 258.207 + All code in any directories or sub-directories that end with *.html or 258.208 + *.css is licensed under the Creative Commons Attribution International 258.209 + 4.0 License, which full text can be found here: 258.210 + https://creativecommons.org/licenses/by/4.0/legalcode. 258.211 + 258.212 + As an exception to this license, all html or css that is generated by 258.213 + the software at the direction of the user is copyright the user. The 258.214 + user has full ownership and control over such content, including 258.215 + whether and how they wish to license it.
259.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 259.2 +++ b/style/mdl/_color-definitions.scss Sun Jul 15 14:07:29 2018 +0200 259.3 @@ -0,0 +1,599 @@ 259.4 +/** 259.5 + * Copyright 2015 Google Inc. All Rights Reserved. 259.6 + * 259.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 259.8 + * you may not use this file except in compliance with the License. 259.9 + * You may obtain a copy of the License at 259.10 + * 259.11 + * http://www.apache.org/licenses/LICENSE-2.0 259.12 + * 259.13 + * Unless required by applicable law or agreed to in writing, software 259.14 + * distributed under the License is distributed on an "AS IS" BASIS, 259.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 259.16 + * See the License for the specific language governing permissions and 259.17 + * limitations under the License. 259.18 + */ 259.19 + 259.20 +/* ========== Color Palettes ========== */ 259.21 + 259.22 +// Color order: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, A100, A200, 259.23 +// A400, A700. 259.24 + 259.25 +$palette-red: 259.26 +"255,235,238" 259.27 +"255,205,210" 259.28 +"239,154,154" 259.29 +"229,115,115" 259.30 +"239,83,80" 259.31 +"244,67,54" 259.32 +"229,57,53" 259.33 +"211,47,47" 259.34 +"198,40,40" 259.35 +"183,28,28" 259.36 +"255,138,128" 259.37 +"255,82,82" 259.38 +"255,23,68" 259.39 +"213,0,0"; 259.40 + 259.41 +$palette-red-50: nth($palette-red, 1); 259.42 +$palette-red-100: nth($palette-red, 2); 259.43 +$palette-red-200: nth($palette-red, 3); 259.44 +$palette-red-300: nth($palette-red, 4); 259.45 +$palette-red-400: nth($palette-red, 5); 259.46 +$palette-red-500: nth($palette-red, 6); 259.47 +$palette-red-600: nth($palette-red, 7); 259.48 +$palette-red-700: nth($palette-red, 8); 259.49 +$palette-red-800: nth($palette-red, 9); 259.50 +$palette-red-900: nth($palette-red, 10); 259.51 +$palette-red-A100: nth($palette-red, 11); 259.52 +$palette-red-A200: nth($palette-red, 12); 259.53 +$palette-red-A400: nth($palette-red, 13); 259.54 +$palette-red-A700: nth($palette-red, 14); 259.55 + 259.56 +$palette-pink: 259.57 +"252,228,236" 259.58 +"248,187,208" 259.59 +"244,143,177" 259.60 +"240,98,146" 259.61 +"236,64,122" 259.62 +"233,30,99" 259.63 +"216,27,96" 259.64 +"194,24,91" 259.65 +"173,20,87" 259.66 +"136,14,79" 259.67 +"255,128,171" 259.68 +"255,64,129" 259.69 +"245,0,87" 259.70 +"197,17,98"; 259.71 + 259.72 +$palette-pink-50: nth($palette-pink, 1); 259.73 +$palette-pink-100: nth($palette-pink, 2); 259.74 +$palette-pink-200: nth($palette-pink, 3); 259.75 +$palette-pink-300: nth($palette-pink, 4); 259.76 +$palette-pink-400: nth($palette-pink, 5); 259.77 +$palette-pink-500: nth($palette-pink, 6); 259.78 +$palette-pink-600: nth($palette-pink, 7); 259.79 +$palette-pink-700: nth($palette-pink, 8); 259.80 +$palette-pink-800: nth($palette-pink, 9); 259.81 +$palette-pink-900: nth($palette-pink, 10); 259.82 +$palette-pink-A100: nth($palette-pink, 11); 259.83 +$palette-pink-A200: nth($palette-pink, 12); 259.84 +$palette-pink-A400: nth($palette-pink, 13); 259.85 +$palette-pink-A700: nth($palette-pink, 14); 259.86 + 259.87 +$palette-purple: 259.88 +"243,229,245" 259.89 +"225,190,231" 259.90 +"206,147,216" 259.91 +"186,104,200" 259.92 +"171,71,188" 259.93 +"156,39,176" 259.94 +"142,36,170" 259.95 +"123,31,162" 259.96 +"106,27,154" 259.97 +"74,20,140" 259.98 +"234,128,252" 259.99 +"224,64,251" 259.100 +"213,0,249" 259.101 +"170,0,255"; 259.102 + 259.103 +$palette-purple-50: nth($palette-purple, 1); 259.104 +$palette-purple-100: nth($palette-purple, 2); 259.105 +$palette-purple-200: nth($palette-purple, 3); 259.106 +$palette-purple-300: nth($palette-purple, 4); 259.107 +$palette-purple-400: nth($palette-purple, 5); 259.108 +$palette-purple-500: nth($palette-purple, 6); 259.109 +$palette-purple-600: nth($palette-purple, 7); 259.110 +$palette-purple-700: nth($palette-purple, 8); 259.111 +$palette-purple-800: nth($palette-purple, 9); 259.112 +$palette-purple-900: nth($palette-purple, 10); 259.113 +$palette-purple-A100: nth($palette-purple, 11); 259.114 +$palette-purple-A200: nth($palette-purple, 12); 259.115 +$palette-purple-A400: nth($palette-purple, 13); 259.116 +$palette-purple-A700: nth($palette-purple, 14); 259.117 + 259.118 +$palette-deep-purple: 259.119 +"237,231,246" 259.120 +"209,196,233" 259.121 +"179,157,219" 259.122 +"149,117,205" 259.123 +"126,87,194" 259.124 +"103,58,183" 259.125 +"94,53,177" 259.126 +"81,45,168" 259.127 +"69,39,160" 259.128 +"49,27,146" 259.129 +"179,136,255" 259.130 +"124,77,255" 259.131 +"101,31,255" 259.132 +"98,0,234"; 259.133 + 259.134 +$palette-deep-purple-50: nth($palette-deep-purple, 1); 259.135 +$palette-deep-purple-100: nth($palette-deep-purple, 2); 259.136 +$palette-deep-purple-200: nth($palette-deep-purple, 3); 259.137 +$palette-deep-purple-300: nth($palette-deep-purple, 4); 259.138 +$palette-deep-purple-400: nth($palette-deep-purple, 5); 259.139 +$palette-deep-purple-500: nth($palette-deep-purple, 6); 259.140 +$palette-deep-purple-600: nth($palette-deep-purple, 7); 259.141 +$palette-deep-purple-700: nth($palette-deep-purple, 8); 259.142 +$palette-deep-purple-800: nth($palette-deep-purple, 9); 259.143 +$palette-deep-purple-900: nth($palette-deep-purple, 10); 259.144 +$palette-deep-purple-A100: nth($palette-deep-purple, 11); 259.145 +$palette-deep-purple-A200: nth($palette-deep-purple, 12); 259.146 +$palette-deep-purple-A400: nth($palette-deep-purple, 13); 259.147 +$palette-deep-purple-A700: nth($palette-deep-purple, 14); 259.148 + 259.149 +$palette-indigo: 259.150 +"232,234,246" 259.151 +"197,202,233" 259.152 +"159,168,218" 259.153 +"121,134,203" 259.154 +"92,107,192" 259.155 +"63,81,181" 259.156 +"57,73,171" 259.157 +"48,63,159" 259.158 +"40,53,147" 259.159 +"26,35,126" 259.160 +"140,158,255" 259.161 +"83,109,254" 259.162 +"61,90,254" 259.163 +"48,79,254"; 259.164 + 259.165 +$palette-indigo-50: nth($palette-indigo, 1); 259.166 +$palette-indigo-100: nth($palette-indigo, 2); 259.167 +$palette-indigo-200: nth($palette-indigo, 3); 259.168 +$palette-indigo-300: nth($palette-indigo, 4); 259.169 +$palette-indigo-400: nth($palette-indigo, 5); 259.170 +$palette-indigo-500: nth($palette-indigo, 6); 259.171 +$palette-indigo-600: nth($palette-indigo, 7); 259.172 +$palette-indigo-700: nth($palette-indigo, 8); 259.173 +$palette-indigo-800: nth($palette-indigo, 9); 259.174 +$palette-indigo-900: nth($palette-indigo, 10); 259.175 +$palette-indigo-A100: nth($palette-indigo, 11); 259.176 +$palette-indigo-A200: nth($palette-indigo, 12); 259.177 +$palette-indigo-A400: nth($palette-indigo, 13); 259.178 +$palette-indigo-A700: nth($palette-indigo, 14); 259.179 + 259.180 +$palette-blue: 259.181 +"227,242,253" 259.182 +"187,222,251" 259.183 +"144,202,249" 259.184 +"100,181,246" 259.185 +"66,165,245" 259.186 +"33,150,243" 259.187 +"30,136,229" 259.188 +"25,118,210" 259.189 +"21,101,192" 259.190 +"13,71,161" 259.191 +"130,177,255" 259.192 +"68,138,255" 259.193 +"41,121,255" 259.194 +"41,98,255"; 259.195 + 259.196 +$palette-blue-50: nth($palette-blue, 1); 259.197 +$palette-blue-100: nth($palette-blue, 2); 259.198 +$palette-blue-200: nth($palette-blue, 3); 259.199 +$palette-blue-300: nth($palette-blue, 4); 259.200 +$palette-blue-400: nth($palette-blue, 5); 259.201 +$palette-blue-500: nth($palette-blue, 6); 259.202 +$palette-blue-600: nth($palette-blue, 7); 259.203 +$palette-blue-700: nth($palette-blue, 8); 259.204 +$palette-blue-800: nth($palette-blue, 9); 259.205 +$palette-blue-900: nth($palette-blue, 10); 259.206 +$palette-blue-A100: nth($palette-blue, 11); 259.207 +$palette-blue-A200: nth($palette-blue, 12); 259.208 +$palette-blue-A400: nth($palette-blue, 13); 259.209 +$palette-blue-A700: nth($palette-blue, 14); 259.210 + 259.211 +$palette-light-blue: 259.212 +"225,245,254" 259.213 +"179,229,252" 259.214 +"129,212,250" 259.215 +"79,195,247" 259.216 +"41,182,246" 259.217 +"3,169,244" 259.218 +"3,155,229" 259.219 +"2,136,209" 259.220 +"2,119,189" 259.221 +"1,87,155" 259.222 +"128,216,255" 259.223 +"64,196,255" 259.224 +"0,176,255" 259.225 +"0,145,234"; 259.226 + 259.227 +$palette-light-blue-50: nth($palette-light-blue, 1); 259.228 +$palette-light-blue-100: nth($palette-light-blue, 2); 259.229 +$palette-light-blue-200: nth($palette-light-blue, 3); 259.230 +$palette-light-blue-300: nth($palette-light-blue, 4); 259.231 +$palette-light-blue-400: nth($palette-light-blue, 5); 259.232 +$palette-light-blue-500: nth($palette-light-blue, 6); 259.233 +$palette-light-blue-600: nth($palette-light-blue, 7); 259.234 +$palette-light-blue-700: nth($palette-light-blue, 8); 259.235 +$palette-light-blue-800: nth($palette-light-blue, 9); 259.236 +$palette-light-blue-900: nth($palette-light-blue, 10); 259.237 +$palette-light-blue-A100: nth($palette-light-blue, 11); 259.238 +$palette-light-blue-A200: nth($palette-light-blue, 12); 259.239 +$palette-light-blue-A400: nth($palette-light-blue, 13); 259.240 +$palette-light-blue-A700: nth($palette-light-blue, 14); 259.241 + 259.242 +$palette-cyan: 259.243 +"224,247,250" 259.244 +"178,235,242" 259.245 +"128,222,234" 259.246 +"77,208,225" 259.247 +"38,198,218" 259.248 +"0,188,212" 259.249 +"0,172,193" 259.250 +"0,151,167" 259.251 +"0,131,143" 259.252 +"0,96,100" 259.253 +"132,255,255" 259.254 +"24,255,255" 259.255 +"0,229,255" 259.256 +"0,184,212"; 259.257 + 259.258 +$palette-cyan-50: nth($palette-cyan, 1); 259.259 +$palette-cyan-100: nth($palette-cyan, 2); 259.260 +$palette-cyan-200: nth($palette-cyan, 3); 259.261 +$palette-cyan-300: nth($palette-cyan, 4); 259.262 +$palette-cyan-400: nth($palette-cyan, 5); 259.263 +$palette-cyan-500: nth($palette-cyan, 6); 259.264 +$palette-cyan-600: nth($palette-cyan, 7); 259.265 +$palette-cyan-700: nth($palette-cyan, 8); 259.266 +$palette-cyan-800: nth($palette-cyan, 9); 259.267 +$palette-cyan-900: nth($palette-cyan, 10); 259.268 +$palette-cyan-A100: nth($palette-cyan, 11); 259.269 +$palette-cyan-A200: nth($palette-cyan, 12); 259.270 +$palette-cyan-A400: nth($palette-cyan, 13); 259.271 +$palette-cyan-A700: nth($palette-cyan, 14); 259.272 + 259.273 +$palette-teal: 259.274 +"224,242,241" 259.275 +"178,223,219" 259.276 +"128,203,196" 259.277 +"77,182,172" 259.278 +"38,166,154" 259.279 +"0,150,136" 259.280 +"0,137,123" 259.281 +"0,121,107" 259.282 +"0,105,92" 259.283 +"0,77,64" 259.284 +"167,255,235" 259.285 +"100,255,218" 259.286 +"29,233,182" 259.287 +"0,191,165"; 259.288 + 259.289 +$palette-teal-50: nth($palette-teal, 1); 259.290 +$palette-teal-100: nth($palette-teal, 2); 259.291 +$palette-teal-200: nth($palette-teal, 3); 259.292 +$palette-teal-300: nth($palette-teal, 4); 259.293 +$palette-teal-400: nth($palette-teal, 5); 259.294 +$palette-teal-500: nth($palette-teal, 6); 259.295 +$palette-teal-600: nth($palette-teal, 7); 259.296 +$palette-teal-700: nth($palette-teal, 8); 259.297 +$palette-teal-800: nth($palette-teal, 9); 259.298 +$palette-teal-900: nth($palette-teal, 10); 259.299 +$palette-teal-A100: nth($palette-teal, 11); 259.300 +$palette-teal-A200: nth($palette-teal, 12); 259.301 +$palette-teal-A400: nth($palette-teal, 13); 259.302 +$palette-teal-A700: nth($palette-teal, 14); 259.303 + 259.304 +$palette-green: 259.305 +"232,245,233" 259.306 +"200,230,201" 259.307 +"165,214,167" 259.308 +"129,199,132" 259.309 +"102,187,106" 259.310 +"76,175,80" 259.311 +"67,160,71" 259.312 +"56,142,60" 259.313 +"46,125,50" 259.314 +"27,94,32" 259.315 +"185,246,202" 259.316 +"105,240,174" 259.317 +"0,230,118" 259.318 +"0,200,83"; 259.319 + 259.320 +$palette-green-50: nth($palette-green, 1); 259.321 +$palette-green-100: nth($palette-green, 2); 259.322 +$palette-green-200: nth($palette-green, 3); 259.323 +$palette-green-300: nth($palette-green, 4); 259.324 +$palette-green-400: nth($palette-green, 5); 259.325 +$palette-green-500: nth($palette-green, 6); 259.326 +$palette-green-600: nth($palette-green, 7); 259.327 +$palette-green-700: nth($palette-green, 8); 259.328 +$palette-green-800: nth($palette-green, 9); 259.329 +$palette-green-900: nth($palette-green, 10); 259.330 +$palette-green-A100: nth($palette-green, 11); 259.331 +$palette-green-A200: nth($palette-green, 12); 259.332 +$palette-green-A400: nth($palette-green, 13); 259.333 +$palette-green-A700: nth($palette-green, 14); 259.334 + 259.335 +$palette-light-green: 259.336 +"241,248,233" 259.337 +"220,237,200" 259.338 +"197,225,165" 259.339 +"174,213,129" 259.340 +"156,204,101" 259.341 +"139,195,74" 259.342 +"124,179,66" 259.343 +"104,159,56" 259.344 +"85,139,47" 259.345 +"51,105,30" 259.346 +"204,255,144" 259.347 +"178,255,89" 259.348 +"118,255,3" 259.349 +"100,221,23"; 259.350 + 259.351 +$palette-light-green-50: nth($palette-light-green, 1); 259.352 +$palette-light-green-100: nth($palette-light-green, 2); 259.353 +$palette-light-green-200: nth($palette-light-green, 3); 259.354 +$palette-light-green-300: nth($palette-light-green, 4); 259.355 +$palette-light-green-400: nth($palette-light-green, 5); 259.356 +$palette-light-green-500: nth($palette-light-green, 6); 259.357 +$palette-light-green-600: nth($palette-light-green, 7); 259.358 +$palette-light-green-700: nth($palette-light-green, 8); 259.359 +$palette-light-green-800: nth($palette-light-green, 9); 259.360 +$palette-light-green-900: nth($palette-light-green, 10); 259.361 +$palette-light-green-A100: nth($palette-light-green, 11); 259.362 +$palette-light-green-A200: nth($palette-light-green, 12); 259.363 +$palette-light-green-A400: nth($palette-light-green, 13); 259.364 +$palette-light-green-A700: nth($palette-light-green, 14); 259.365 + 259.366 +$palette-lime: 259.367 +"249,251,231" 259.368 +"240,244,195" 259.369 +"230,238,156" 259.370 +"220,231,117" 259.371 +"212,225,87" 259.372 +"205,220,57" 259.373 +"192,202,51" 259.374 +"175,180,43" 259.375 +"158,157,36" 259.376 +"130,119,23" 259.377 +"244,255,129" 259.378 +"238,255,65" 259.379 +"198,255,0" 259.380 +"174,234,0"; 259.381 + 259.382 +$palette-lime-50: nth($palette-lime, 1); 259.383 +$palette-lime-100: nth($palette-lime, 2); 259.384 +$palette-lime-200: nth($palette-lime, 3); 259.385 +$palette-lime-300: nth($palette-lime, 4); 259.386 +$palette-lime-400: nth($palette-lime, 5); 259.387 +$palette-lime-500: nth($palette-lime, 6); 259.388 +$palette-lime-600: nth($palette-lime, 7); 259.389 +$palette-lime-700: nth($palette-lime, 8); 259.390 +$palette-lime-800: nth($palette-lime, 9); 259.391 +$palette-lime-900: nth($palette-lime, 10); 259.392 +$palette-lime-A100: nth($palette-lime, 11); 259.393 +$palette-lime-A200: nth($palette-lime, 12); 259.394 +$palette-lime-A400: nth($palette-lime, 13); 259.395 +$palette-lime-A700: nth($palette-lime, 14); 259.396 + 259.397 +$palette-yellow: 259.398 +"255,253,231" 259.399 +"255,249,196" 259.400 +"255,245,157" 259.401 +"255,241,118" 259.402 +"255,238,88" 259.403 +"255,235,59" 259.404 +"253,216,53" 259.405 +"251,192,45" 259.406 +"249,168,37" 259.407 +"245,127,23" 259.408 +"255,255,141" 259.409 +"255,255,0" 259.410 +"255,234,0" 259.411 +"255,214,0"; 259.412 + 259.413 +$palette-yellow-50: nth($palette-yellow, 1); 259.414 +$palette-yellow-100: nth($palette-yellow, 2); 259.415 +$palette-yellow-200: nth($palette-yellow, 3); 259.416 +$palette-yellow-300: nth($palette-yellow, 4); 259.417 +$palette-yellow-400: nth($palette-yellow, 5); 259.418 +$palette-yellow-500: nth($palette-yellow, 6); 259.419 +$palette-yellow-600: nth($palette-yellow, 7); 259.420 +$palette-yellow-700: nth($palette-yellow, 8); 259.421 +$palette-yellow-800: nth($palette-yellow, 9); 259.422 +$palette-yellow-900: nth($palette-yellow, 10); 259.423 +$palette-yellow-A100: nth($palette-yellow, 11); 259.424 +$palette-yellow-A200: nth($palette-yellow, 12); 259.425 +$palette-yellow-A400: nth($palette-yellow, 13); 259.426 +$palette-yellow-A700: nth($palette-yellow, 14); 259.427 + 259.428 +$palette-amber: 259.429 +"255,248,225" 259.430 +"255,236,179" 259.431 +"255,224,130" 259.432 +"255,213,79" 259.433 +"255,202,40" 259.434 +"255,193,7" 259.435 +"255,179,0" 259.436 +"255,160,0" 259.437 +"255,143,0" 259.438 +"255,111,0" 259.439 +"255,229,127" 259.440 +"255,215,64" 259.441 +"255,196,0" 259.442 +"255,171,0"; 259.443 + 259.444 +$palette-amber-50: nth($palette-amber, 1); 259.445 +$palette-amber-100: nth($palette-amber, 2); 259.446 +$palette-amber-200: nth($palette-amber, 3); 259.447 +$palette-amber-300: nth($palette-amber, 4); 259.448 +$palette-amber-400: nth($palette-amber, 5); 259.449 +$palette-amber-500: nth($palette-amber, 6); 259.450 +$palette-amber-600: nth($palette-amber, 7); 259.451 +$palette-amber-700: nth($palette-amber, 8); 259.452 +$palette-amber-800: nth($palette-amber, 9); 259.453 +$palette-amber-900: nth($palette-amber, 10); 259.454 +$palette-amber-A100: nth($palette-amber, 11); 259.455 +$palette-amber-A200: nth($palette-amber, 12); 259.456 +$palette-amber-A400: nth($palette-amber, 13); 259.457 +$palette-amber-A700: nth($palette-amber, 14); 259.458 + 259.459 +$palette-orange: 259.460 +"255,243,224" 259.461 +"255,224,178" 259.462 +"255,204,128" 259.463 +"255,183,77" 259.464 +"255,167,38" 259.465 +"255,152,0" 259.466 +"251,140,0" 259.467 +"245,124,0" 259.468 +"239,108,0" 259.469 +"230,81,0" 259.470 +"255,209,128" 259.471 +"255,171,64" 259.472 +"255,145,0" 259.473 +"255,109,0"; 259.474 + 259.475 +$palette-orange-50: nth($palette-orange, 1); 259.476 +$palette-orange-100: nth($palette-orange, 2); 259.477 +$palette-orange-200: nth($palette-orange, 3); 259.478 +$palette-orange-300: nth($palette-orange, 4); 259.479 +$palette-orange-400: nth($palette-orange, 5); 259.480 +$palette-orange-500: nth($palette-orange, 6); 259.481 +$palette-orange-600: nth($palette-orange, 7); 259.482 +$palette-orange-700: nth($palette-orange, 8); 259.483 +$palette-orange-800: nth($palette-orange, 9); 259.484 +$palette-orange-900: nth($palette-orange, 10); 259.485 +$palette-orange-A100: nth($palette-orange, 11); 259.486 +$palette-orange-A200: nth($palette-orange, 12); 259.487 +$palette-orange-A400: nth($palette-orange, 13); 259.488 +$palette-orange-A700: nth($palette-orange, 14); 259.489 + 259.490 +$palette-deep-orange: 259.491 +"251,233,231" 259.492 +"255,204,188" 259.493 +"255,171,145" 259.494 +"255,138,101" 259.495 +"255,112,67" 259.496 +"255,87,34" 259.497 +"244,81,30" 259.498 +"230,74,25" 259.499 +"216,67,21" 259.500 +"191,54,12" 259.501 +"255,158,128" 259.502 +"255,110,64" 259.503 +"255,61,0" 259.504 +"221,44,0"; 259.505 + 259.506 +$palette-deep-orange-50: nth($palette-deep-orange, 1); 259.507 +$palette-deep-orange-100: nth($palette-deep-orange, 2); 259.508 +$palette-deep-orange-200: nth($palette-deep-orange, 3); 259.509 +$palette-deep-orange-300: nth($palette-deep-orange, 4); 259.510 +$palette-deep-orange-400: nth($palette-deep-orange, 5); 259.511 +$palette-deep-orange-500: nth($palette-deep-orange, 6); 259.512 +$palette-deep-orange-600: nth($palette-deep-orange, 7); 259.513 +$palette-deep-orange-700: nth($palette-deep-orange, 8); 259.514 +$palette-deep-orange-800: nth($palette-deep-orange, 9); 259.515 +$palette-deep-orange-900: nth($palette-deep-orange, 10); 259.516 +$palette-deep-orange-A100: nth($palette-deep-orange, 11); 259.517 +$palette-deep-orange-A200: nth($palette-deep-orange, 12); 259.518 +$palette-deep-orange-A400: nth($palette-deep-orange, 13); 259.519 +$palette-deep-orange-A700: nth($palette-deep-orange, 14); 259.520 + 259.521 + 259.522 +// Color order: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900. 259.523 + 259.524 +$palette-brown: 259.525 +"239,235,233" 259.526 +"215,204,200" 259.527 +"188,170,164" 259.528 +"161,136,127" 259.529 +"141,110,99" 259.530 +"121,85,72" 259.531 +"109,76,65" 259.532 +"93,64,55" 259.533 +"78,52,46" 259.534 +"62,39,35"; 259.535 + 259.536 +$palette-brown-50: nth($palette-brown, 1); 259.537 +$palette-brown-100: nth($palette-brown, 2); 259.538 +$palette-brown-200: nth($palette-brown, 3); 259.539 +$palette-brown-300: nth($palette-brown, 4); 259.540 +$palette-brown-400: nth($palette-brown, 5); 259.541 +$palette-brown-500: nth($palette-brown, 6); 259.542 +$palette-brown-600: nth($palette-brown, 7); 259.543 +$palette-brown-700: nth($palette-brown, 8); 259.544 +$palette-brown-800: nth($palette-brown, 9); 259.545 +$palette-brown-900: nth($palette-brown, 10); 259.546 + 259.547 +$palette-grey: 259.548 +"250,250,250" 259.549 +"245,245,245" 259.550 +"238,238,238" 259.551 +"224,224,224" 259.552 +"189,189,189" 259.553 +"158,158,158" 259.554 +"117,117,117" 259.555 +"97,97,97" 259.556 +"66,66,66" 259.557 +"33,33,33"; 259.558 + 259.559 +$palette-grey-50: nth($palette-grey, 1); 259.560 +$palette-grey-100: nth($palette-grey, 2); 259.561 +$palette-grey-200: nth($palette-grey, 3); 259.562 +$palette-grey-300: nth($palette-grey, 4); 259.563 +$palette-grey-400: nth($palette-grey, 5); 259.564 +$palette-grey-500: nth($palette-grey, 6); 259.565 +$palette-grey-600: nth($palette-grey, 7); 259.566 +$palette-grey-700: nth($palette-grey, 8); 259.567 +$palette-grey-800: nth($palette-grey, 9); 259.568 +$palette-grey-900: nth($palette-grey, 10); 259.569 + 259.570 +$palette-blue-grey: 259.571 +"236,239,241" 259.572 +"207,216,220" 259.573 +"176,190,197" 259.574 +"144,164,174" 259.575 +"120,144,156" 259.576 +"96,125,139" 259.577 +"84,110,122" 259.578 +"69,90,100" 259.579 +"55,71,79" 259.580 +"38,50,56"; 259.581 + 259.582 +$palette-blue-grey-50: nth($palette-blue-grey, 1); 259.583 +$palette-blue-grey-100: nth($palette-blue-grey, 2); 259.584 +$palette-blue-grey-200: nth($palette-blue-grey, 3); 259.585 +$palette-blue-grey-300: nth($palette-blue-grey, 4); 259.586 +$palette-blue-grey-400: nth($palette-blue-grey, 5); 259.587 +$palette-blue-grey-500: nth($palette-blue-grey, 6); 259.588 +$palette-blue-grey-600: nth($palette-blue-grey, 7); 259.589 +$palette-blue-grey-700: nth($palette-blue-grey, 8); 259.590 +$palette-blue-grey-800: nth($palette-blue-grey, 9); 259.591 +$palette-blue-grey-900: nth($palette-blue-grey, 10); 259.592 + 259.593 +$color-black: "0,0,0"; 259.594 +$color-white: "255,255,255"; 259.595 + 259.596 + 259.597 +/* colors.scss */ 259.598 +$styleguide-generate-template: false !default; 259.599 + 259.600 +// The two possible colors for overlayed text. 259.601 +$color-dark-contrast: $color-white !default; 259.602 +$color-light-contrast: $color-black !default;
260.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 260.2 +++ b/style/mdl/_functions.scss Sun Jul 15 14:07:29 2018 +0200 260.3 @@ -0,0 +1,19 @@ 260.4 +/** 260.5 + * Copyright 2015 Google Inc. All Rights Reserved. 260.6 + * 260.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 260.8 + * you may not use this file except in compliance with the License. 260.9 + * You may obtain a copy of the License at 260.10 + * 260.11 + * http://www.apache.org/licenses/LICENSE-2.0 260.12 + * 260.13 + * Unless required by applicable law or agreed to in writing, software 260.14 + * distributed under the License is distributed on an "AS IS" BASIS, 260.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 260.16 + * See the License for the specific language governing permissions and 260.17 + * limitations under the License. 260.18 + */ 260.19 + 260.20 +@function strip-units($number) { 260.21 + @return $number / ($number * 0 + 1); 260.22 +}
261.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 261.2 +++ b/style/mdl/_mixins.scss Sun Jul 15 14:07:29 2018 +0200 261.3 @@ -0,0 +1,301 @@ 261.4 +/** 261.5 + * Copyright 2015 Google Inc. All Rights Reserved. 261.6 + * 261.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 261.8 + * you may not use this file except in compliance with the License. 261.9 + * You may obtain a copy of the License at 261.10 + * 261.11 + * http://www.apache.org/licenses/LICENSE-2.0 261.12 + * 261.13 + * Unless required by applicable law or agreed to in writing, software 261.14 + * distributed under the License is distributed on an "AS IS" BASIS, 261.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 261.16 + * See the License for the specific language governing permissions and 261.17 + * limitations under the License. 261.18 + */ 261.19 + 261.20 +/* Typography */ 261.21 + 261.22 +@mixin typo-preferred-font($usePreferred: true) { 261.23 + @if $usePreferred { 261.24 + font-family: $preferred_font; 261.25 + } 261.26 +} 261.27 + 261.28 +@mixin typo-display-4($colorContrast: false, $usePreferred: true) { 261.29 + @include typo-preferred-font($usePreferred); 261.30 + font-size: 112px; 261.31 + font-weight: 300; 261.32 + line-height: 1; 261.33 + letter-spacing: -0.04em; 261.34 + 261.35 + @if $colorContrast { 261.36 + opacity: 0.54; 261.37 + } 261.38 +} 261.39 + 261.40 +@mixin typo-display-3($colorContrast: false, $usePreferred: true) { 261.41 + @include typo-preferred-font($usePreferred); 261.42 + font-size: 56px; 261.43 + font-weight: 400; 261.44 + line-height: 1.35; 261.45 + letter-spacing: -0.02em; 261.46 + 261.47 + @if $colorContrast { 261.48 + opacity: 0.54; 261.49 + } 261.50 +} 261.51 + 261.52 +@mixin typo-display-2($colorContrast: false, $usePreferred: true) { 261.53 + @include typo-preferred-font($usePreferred); 261.54 + font-size: 45px; 261.55 + font-weight: 400; 261.56 + line-height: 48px; 261.57 + 261.58 + @if $colorContrast { 261.59 + opacity: 0.54; 261.60 + } 261.61 +} 261.62 + 261.63 +@mixin typo-display-1($colorContrast: false, $usePreferred: true) { 261.64 + @include typo-preferred-font($usePreferred); 261.65 + font-size: 34px; 261.66 + font-weight: 400; 261.67 + line-height: 40px; 261.68 + 261.69 + @if $colorContrast { 261.70 + opacity: 0.54; 261.71 + } 261.72 +} 261.73 + 261.74 +@mixin typo-headline($colorContrast: false, $usePreferred: true) { 261.75 + @include typo-preferred-font($usePreferred); 261.76 + font-size: 24px; 261.77 + font-weight: 400; 261.78 + line-height: 32px; 261.79 + -moz-osx-font-smoothing: grayscale; 261.80 + 261.81 + @if $colorContrast { 261.82 + opacity: 0.87; 261.83 + } 261.84 +} 261.85 + 261.86 +@mixin typo-title($colorContrast: false, $usePreferred: true) { 261.87 + @include typo-preferred-font($usePreferred); 261.88 + font-size: 20px; 261.89 + font-weight: 500; 261.90 + line-height: 1; 261.91 + letter-spacing: 0.02em; 261.92 + 261.93 + @if $colorContrast { 261.94 + opacity: 0.87; 261.95 + } 261.96 +} 261.97 + 261.98 +@mixin typo-subhead($colorContrast: false, $usePreferred: true) { 261.99 + @include typo-preferred-font($usePreferred); 261.100 + font-size: 16px; 261.101 + font-weight: 400; 261.102 + line-height: 24px; 261.103 + letter-spacing: 0.04em; 261.104 + 261.105 + @if $colorContrast { 261.106 + opacity: 0.87; 261.107 + } 261.108 +} 261.109 + 261.110 +@mixin typo-subhead-2($colorContrast: false, $usePreferred: true) { 261.111 + @include typo-preferred-font($usePreferred); 261.112 + font-size: 16px; 261.113 + font-weight: 400; 261.114 + line-height: 28px; 261.115 + letter-spacing: 0.04em; 261.116 + 261.117 + @if $colorContrast { 261.118 + opacity: 0.87; 261.119 + } 261.120 +} 261.121 + 261.122 +@mixin typo-body-2($colorContrast: false, $usePreferred: false) { 261.123 + @include typo-preferred-font($usePreferred); 261.124 + font-size: 14px; 261.125 + @if $usePreferred { 261.126 + font-weight: 500; 261.127 + } @else { 261.128 + font-weight: bold; 261.129 + } 261.130 + line-height: 24px; 261.131 + letter-spacing: 0; 261.132 + 261.133 + @if $colorContrast { 261.134 + opacity: 0.87; 261.135 + } 261.136 +} 261.137 + 261.138 +@mixin typo-body-1($colorContrast: false, $usePreferred: false) { 261.139 + @include typo-preferred-font($usePreferred); 261.140 + font-size: 14px; 261.141 + font-weight: 400; 261.142 + line-height: 24px; 261.143 + letter-spacing: 0; 261.144 + 261.145 + @if $colorContrast { 261.146 + opacity: 0.87; 261.147 + } 261.148 +} 261.149 + 261.150 +@mixin typo-caption($colorContrast: false, $usePreferred: false) { 261.151 + @include typo-preferred-font($usePreferred); 261.152 + font-size: 12px; 261.153 + font-weight: 400; 261.154 + line-height: 1; 261.155 + letter-spacing: 0; 261.156 + 261.157 + @if $colorContrast { 261.158 + opacity: 0.54; 261.159 + } 261.160 +} 261.161 + 261.162 +@mixin typo-blockquote($colorContrast: false, $usePreferred: true) { 261.163 + @include typo-preferred-font($usePreferred); 261.164 + position: relative; 261.165 + font-size: 24px; 261.166 + font-weight: 300; 261.167 + font-style: italic; 261.168 + line-height: 1.35; 261.169 + letter-spacing: 0.08em; 261.170 + 261.171 + &:before { 261.172 + position: absolute; 261.173 + left: -0.5em; 261.174 + content: '“'; 261.175 + } 261.176 + 261.177 + &:after { 261.178 + content: '”'; 261.179 + margin-left: -0.05em; 261.180 + } 261.181 + 261.182 + @if $colorContrast { 261.183 + opacity: 0.54; 261.184 + } 261.185 +} 261.186 + 261.187 +@mixin typo-menu($colorContrast: false, $usePreferred: true) { 261.188 + @include typo-preferred-font($usePreferred); 261.189 + font-size: 14px; 261.190 + font-weight: 500; 261.191 + line-height: 1; 261.192 + letter-spacing: 0; 261.193 + 261.194 + @if $colorContrast { 261.195 + opacity: 0.87; 261.196 + } 261.197 +} 261.198 + 261.199 +@mixin typo-button($colorContrast: false, $usePreferred: true) { 261.200 + @include typo-preferred-font($usePreferred); 261.201 + font-size: 14px; 261.202 + font-weight: 500; 261.203 + text-transform: uppercase; 261.204 + line-height: 1; 261.205 + letter-spacing: 0; 261.206 + 261.207 + @if $colorContrast { 261.208 + opacity: 0.87; 261.209 + } 261.210 +} 261.211 + 261.212 +@mixin typo-icon() { 261.213 + font-family: 'Material Icons'; 261.214 + font-weight: normal; 261.215 + font-style: normal; 261.216 + font-size: 24px; 261.217 + line-height: 1; 261.218 + letter-spacing: normal; 261.219 + text-transform: none; 261.220 + display: inline-block; 261.221 + word-wrap: normal; 261.222 + font-feature-settings: 'liga'; 261.223 + -webkit-font-feature-settings: 'liga'; 261.224 + -webkit-font-smoothing: antialiased; 261.225 +} 261.226 + 261.227 +/* Shadows */ 261.228 + 261.229 +// Focus shadow mixin. 261.230 +@mixin focus-shadow() { 261.231 + box-shadow: 0 0 8px rgba(0,0,0,.18),0 8px 16px rgba(0,0,0,.36); 261.232 +} 261.233 + 261.234 +@mixin shadow-2dp() { 261.235 + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, $shadow-key-penumbra-opacity), 261.236 + 0 3px 1px -2px rgba(0, 0, 0, $shadow-key-umbra-opacity), 261.237 + 0 1px 5px 0 rgba(0, 0, 0, $shadow-ambient-shadow-opacity); 261.238 +} 261.239 +@mixin shadow-3dp() { 261.240 + box-shadow: 0 3px 4px 0 rgba(0, 0, 0, $shadow-key-penumbra-opacity), 261.241 + 0 3px 3px -2px rgba(0, 0, 0, $shadow-key-umbra-opacity), 261.242 + 0 1px 8px 0 rgba(0, 0, 0, $shadow-ambient-shadow-opacity); 261.243 +} 261.244 +@mixin shadow-4dp() { 261.245 + box-shadow: 0 4px 5px 0 rgba(0, 0, 0, $shadow-key-penumbra-opacity), 261.246 + 0 1px 10px 0 rgba(0, 0, 0, $shadow-ambient-shadow-opacity), 261.247 + 0 2px 4px -1px rgba(0, 0, 0, $shadow-key-umbra-opacity); 261.248 +} 261.249 +@mixin shadow-6dp() { 261.250 + box-shadow: 0 6px 10px 0 rgba(0, 0, 0, $shadow-key-penumbra-opacity), 261.251 + 0 1px 18px 0 rgba(0, 0, 0, $shadow-ambient-shadow-opacity), 261.252 + 0 3px 5px -1px rgba(0, 0, 0, $shadow-key-umbra-opacity); 261.253 +} 261.254 +@mixin shadow-8dp() { 261.255 + box-shadow: 0 8px 10px 1px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 261.256 + 0 3px 14px 2px rgba(0, 0, 0, $shadow-ambient-shadow-opacity), 261.257 + 0 5px 5px -3px rgba(0, 0, 0, $shadow-key-umbra-opacity); 261.258 +} 261.259 + 261.260 +@mixin shadow-16dp() { 261.261 + box-shadow: 0 16px 24px 2px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 261.262 + 0 6px 30px 5px rgba(0, 0, 0, $shadow-ambient-shadow-opacity), 261.263 + 0 8px 10px -5px rgba(0, 0, 0, $shadow-key-umbra-opacity); 261.264 +} 261.265 + 261.266 +@mixin shadow-24dp() { 261.267 + box-shadow: 0 9px 46px 8px rgba(0, 0, 0, $shadow-key-penumbra-opacity), 261.268 + 0 11px 15px -7px rgba(0, 0, 0, $shadow-ambient-shadow-opacity), 261.269 + 0 24px 38px 3px rgba(0, 0, 0, $shadow-key-umbra-opacity); 261.270 +} 261.271 + 261.272 +/* Animations */ 261.273 + 261.274 +@mixin material-animation-fast-out-slow-in($duration:0.2s) { 261.275 + transition-duration: $duration; 261.276 + transition-timing-function: $animation-curve-fast-out-slow-in; 261.277 +} 261.278 + 261.279 +@mixin material-animation-linear-out-slow-in($duration:0.2s) { 261.280 + transition-duration: $duration; 261.281 + transition-timing-function: $animation-curve-linear-out-slow-in; 261.282 +} 261.283 + 261.284 +@mixin material-animation-fast-out-linear-in($duration:0.2s) { 261.285 + transition-duration: $duration; 261.286 + transition-timing-function: $animation-curve-fast-out-linear-in; 261.287 +} 261.288 + 261.289 +@mixin material-animation-default($duration:0.2s) { 261.290 + transition-duration: $duration; 261.291 + transition-timing-function: $animation-curve-default; 261.292 +} 261.293 + 261.294 +/* Dialog */ 261.295 + 261.296 +@mixin dialog-width($units:5) { 261.297 + @if(type_of($units) != 'number') { 261.298 + @error "The unit given to dialog-width should be a number."; 261.299 + } 261.300 + // 56dp is the base unit width for Dialogs. 261.301 + // With 5 units being the number of units for a mobile device. 261.302 + // https://goo.gl/sK2O5o 261.303 + width: $units * 56px; 261.304 +}
262.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 262.2 +++ b/style/mdl/_variables.scss Sun Jul 15 14:07:29 2018 +0200 262.3 @@ -0,0 +1,592 @@ 262.4 +/** 262.5 + * Copyright 2015 Google Inc. All Rights Reserved. 262.6 + * 262.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 262.8 + * you may not use this file except in compliance with the License. 262.9 + * You may obtain a copy of the License at 262.10 + * 262.11 + * http://www.apache.org/licenses/LICENSE-2.0 262.12 + * 262.13 + * Unless required by applicable law or agreed to in writing, software 262.14 + * distributed under the License is distributed on an "AS IS" BASIS, 262.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 262.16 + * See the License for the specific language governing permissions and 262.17 + * limitations under the License. 262.18 + */ 262.19 + 262.20 +/*------------------------------------*\ 262.21 + $CONTENTS 262.22 +\*------------------------------------*/ 262.23 +/** 262.24 + * STYLE GUIDE VARIABLES------------------Declarations of Sass variables 262.25 + * -----Typography 262.26 + * -----Colors 262.27 + * -----Textfield 262.28 + * -----Switch 262.29 + * -----Spinner 262.30 + * -----Radio 262.31 + * -----Menu 262.32 + * -----List 262.33 + * -----Layout 262.34 + * -----Icon toggles 262.35 + * -----Footer 262.36 + * -----Column 262.37 + * -----Checkbox 262.38 + * -----Card 262.39 + * -----Button 262.40 + * -----Animation 262.41 + * -----Progress 262.42 + * -----Badge 262.43 + * -----Shadows 262.44 + * -----Grid 262.45 + * -----Data table 262.46 + * -----Dialog 262.47 + * -----Snackbar 262.48 + * -----Tooltip 262.49 + * -----Chip 262.50 + * 262.51 + * Even though all variables have the `!default` directive, most of them 262.52 + * should not be changed as they are dependent one another. This can cause 262.53 + * visual distortions (like alignment issues) that are hard to track down 262.54 + * and fix. 262.55 + */ 262.56 + 262.57 + 262.58 +/* ========== TYPOGRAPHY ========== */ 262.59 + 262.60 +/* We're splitting fonts into "preferred" and "performance" in order to optimize 262.61 + page loading. For important text, such as the body, we want it to load 262.62 + immediately and not wait for the web font load, whereas for other sections, 262.63 + such as headers and titles, we're OK with things taking a bit longer to load. 262.64 + We do have some optional classes and parameters in the mixins, in case you 262.65 + definitely want to make sure you're using the preferred font and don't mind 262.66 + the performance hit. 262.67 + We should be able to improve on this once CSS Font Loading L3 becomes more 262.68 + widely available. 262.69 +*/ 262.70 +$preferred_font: 'Roboto', 'Helvetica', 'Arial', sans-serif !default; 262.71 +$performance_font: 'Helvetica', 'Arial', sans-serif !default; 262.72 + 262.73 +/* ========== COLORS ========== */ 262.74 + 262.75 +/** 262.76 +* 262.77 +* Material design color palettes. 262.78 +* @see http://www.google.com/design/spec/style/color.html 262.79 +* 262.80 +**/ 262.81 + 262.82 +@import "color-definitions"; 262.83 +@import "functions"; 262.84 + 262.85 +/* ========== IMAGES ========== */ 262.86 +$image_path: '/static/mdl' !default; 262.87 + 262.88 +/* ========== Color & Themes ========== */ 262.89 + 262.90 +// Define whether individual color palette items should have classes created. 262.91 +// Setting this to true will remove individual color classes for each color in the palettes. 262.92 +// To improve overall performance (assuming they aren't used) by: 262.93 +// * Saving server bandwidth sending the extra classes 262.94 +// * Save client computation against the classes 262.95 +// it is RECOMMENDED you set this to true. 262.96 +$trim-color-classes: false !default; 262.97 + 262.98 +// Use color primarily for emphasis. Choose colors that fit with 262.99 +// your brand and provide good contrast between visual components. 262.100 +$color-primary: $palette-indigo-500 !default; 262.101 +$color-primary-dark: $palette-indigo-700 !default; 262.102 +$color-accent: $palette-pink-A200 !default; 262.103 + 262.104 +// Our primary is dark, so use $color-dark-contrast for overlaid text. 262.105 +$color-primary-contrast: $color-dark-contrast !default; 262.106 +// Our accent is dark, so use $color-dark-contrast for overlaid text. 262.107 +$color-accent-contrast: $color-dark-contrast !default; 262.108 + 262.109 +// Replace all colors with placeholders if we're generating a template. 262.110 +@if $styleguide-generate-template == true { 262.111 + $color-primary: '$color-primary'; 262.112 + $color-primary-dark: '$color-primary-dark'; 262.113 + $color-accent: '$color-accent'; 262.114 + $color-primary-contrast: '$color-primary-contrast'; 262.115 + $color-accent-contrast: '$color-accent-contrast'; 262.116 +} 262.117 + 262.118 +/* ========== Typography ========== */ 262.119 + 262.120 +// We use the following default color styles: text-color-primary and 262.121 +// text-color-secondary. For light themes, use text-color-primary-inverse 262.122 +// and text-color-secondary-inverse. 262.123 + 262.124 +$text-color-primary: unquote("rgba(#{$color-black}, 0.87)") !default; 262.125 +$text-link-color: unquote("rgb(#{$color-accent})") !default; 262.126 + 262.127 +// Define whether to target elements directly for typographic enhancements. 262.128 +// Turning this off means you need to use mdl-* classes more often. 262.129 +// Other components may also fail to adhere to MD without these rules. 262.130 +// It is strongly recommended you leave this as true. 262.131 + 262.132 +$target-elements-directly: true !default; 262.133 + 262.134 +/* ========== Components ========== */ 262.135 + 262.136 +/* ========== Standard Buttons ========== */ 262.137 + 262.138 +// Default button colors. 262.139 +$button-primary-color: unquote("rgba(#{$palette-grey-500}, 0.20)") !default; 262.140 +$button-secondary-color: unquote("rgb(#{$color-black})") !default; 262.141 +$button-hover-color: $button-primary-color !default; 262.142 +$button-active-color: unquote("rgba(#{$palette-grey-500}, 0.40)") !default; 262.143 +$button-focus-color: unquote("rgba(#{$color-black}, 0.12)") !default; 262.144 + 262.145 +// Colored button colors. 262.146 +$button-primary-color-alt: unquote("rgb(#{$color-primary})") !default; 262.147 +$button-secondary-color-alt: unquote("rgb(#{$color-primary-contrast})") !default; 262.148 +$button-hover-color-alt: unquote("rgb(#{$color-primary})") !default; 262.149 +$button-active-color-alt: unquote("rgb(#{$color-primary})") !default; 262.150 +$button-focus-color-alt: $button-focus-color !default; 262.151 + 262.152 +// Ripple color for colored raised buttons. 262.153 +$button-ripple-color-alt: unquote("rgb(#{$color-primary-contrast})") !default; 262.154 + 262.155 +// Disabled button colors. 262.156 +$button-primary-color-disabled: unquote("rgba(#{$color-black}, 0.12)") !default; 262.157 +$button-secondary-color-disabled: unquote("rgba(#{$color-black}, 0.26)") !default; 262.158 + 262.159 +// FAB colors and sizes. 262.160 +$button-fab-color-alt: unquote("rgb(#{$color-accent})") !default; 262.161 +$button-fab-hover-color-alt: unquote("rgb(#{$color-accent})") !default; 262.162 +$button-fab-active-color-alt: unquote("rgb(#{$color-accent})") !default; 262.163 +$button-fab-text-color-alt: unquote("rgb(#{$color-accent-contrast})") !default; 262.164 +$button-fab-ripple-color-alt: unquote("rgb(#{$color-accent-contrast})") !default; 262.165 + 262.166 +// Icon button colors and sizes. 262.167 +$button-icon-color: unquote("rgb(#{$palette-grey-700})") !default; 262.168 +$button-icon-focus-color: $button-focus-color !default; 262.169 + 262.170 +/* ========== Icon Toggles ========== */ 262.171 + 262.172 +$icon-toggle-color: unquote("rgb(#{$palette-grey-700})") !default; 262.173 +$icon-toggle-focus-color: $button-focus-color !default; 262.174 +$icon-toggle-checked-color: unquote("rgb(#{$color-primary})") !default; 262.175 +$icon-toggle-checked-focus-color: unquote("rgba(#{$color-primary}, 0.26)") !default; 262.176 +$icon-toggle-disabled-color: unquote("rgba(#{$color-black}, 0.26)") !default; 262.177 + 262.178 +/* ========== Radio Buttons ========== */ 262.179 + 262.180 +$radio-color: unquote("rgb(#{$color-primary})") !default; 262.181 +$radio-off-color: unquote("rgba(#{$color-black}, 0.54)") !default; 262.182 +$radio-disabled-color: unquote("rgba(#{$color-black}, 0.26)") !default; 262.183 + 262.184 +/* ========== Ripple effect ========== */ 262.185 + 262.186 +$ripple-bg-color: unquote("rgb(#{$color-light-contrast})") !default; 262.187 + 262.188 +/* ========== Layout ========== */ 262.189 + 262.190 +$layout-nav-color: unquote("rgb(#{$palette-grey-300})") !default; 262.191 + 262.192 +// Drawer 262.193 +$layout-drawer-bg-color: unquote("rgb(#{$palette-grey-50})") !default; 262.194 +$layout-drawer-border-color: unquote("rgb(#{$palette-grey-300})") !default; 262.195 +$layout-text-color: unquote("rgb(#{$palette-grey-800})") !default; 262.196 +$layout-drawer-navigation-color: #757575 !default; 262.197 +$layout-drawer-navigation-link-active-background: unquote("rgb(#{$palette-grey-300})") !default; 262.198 +$layout-drawer-navigation-link-active-color: unquote("rgb(#{$color-light-contrast})") !default; 262.199 + 262.200 +// Header 262.201 +$layout-header-bg-color: unquote("rgb(#{$color-primary})") !default; 262.202 +$layout-header-text-color: unquote("rgb(#{$color-primary-contrast})") !default; 262.203 +$layout-header-nav-hover-color: unquote("rgba(#{$palette-grey-700}, 0.6)") !default; 262.204 +$layout-header-tab-text-color: unquote("rgba(#{$color-primary-contrast}, 0.6)") !default; 262.205 + 262.206 +// Tabs 262.207 +$layout-header-tab-highlight: unquote("rgb(#{$color-accent})") !default; 262.208 + 262.209 +/* ========== Content Tabs ========== */ 262.210 + 262.211 +$tab-highlight-color: unquote("rgb(#{$color-primary})") !default; 262.212 +$tab-text-color: unquote("rgba(#{$color-black}, 0.54)") !default; 262.213 +$tab-active-text-color: unquote("rgba(#{$color-black}, 0.87)") !default; 262.214 +$tab-border-color: unquote("rgb(#{$palette-grey-300})") !default; 262.215 + 262.216 +/* ========== Checkboxes ========== */ 262.217 + 262.218 +$checkbox-color: unquote("rgb(#{$color-primary})") !default; 262.219 +$checkbox-off-color: unquote("rgba(#{$color-black}, 0.54)") !default; 262.220 +$checkbox-disabled-color: unquote("rgba(#{$color-black}, 0.26)") !default; 262.221 +$checkbox-focus-color: unquote("rgba(#{$color-primary}, 0.26)") !default; 262.222 +$checkbox-image-path: $image_path; 262.223 + 262.224 +/* ========== Switches ========== */ 262.225 + 262.226 +$switch-color: unquote("rgb(#{$color-primary})") !default; 262.227 +$switch-faded-color: unquote("rgba(#{$color-primary}, 0.26)") !default; 262.228 +$switch-thumb-color: $switch-color !default; 262.229 +$switch-track-color: unquote("rgba(#{$color-primary}, 0.5)") !default; 262.230 + 262.231 +$switch-off-thumb-color: unquote("rgb(#{$palette-grey-50})") !default; 262.232 +$switch-off-track-color: unquote("rgba(#{$color-black}, 0.26)") !default; 262.233 +$switch-disabled-thumb-color: unquote("rgb(#{$palette-grey-400})") !default; 262.234 +$switch-disabled-track-color: unquote("rgba(#{$color-black}, 0.12)") !default; 262.235 + 262.236 +/* ========== Spinner ========== */ 262.237 + 262.238 +$spinner-color-1: unquote("rgb(#{$palette-blue-400})") !default; 262.239 +$spinner-color-2: unquote("rgb(#{$palette-red-500})") !default; 262.240 +$spinner-color-3: unquote("rgb(#{$palette-yellow-600})") !default; 262.241 +$spinner-color-4: unquote("rgb(#{$palette-green-500})") !default; 262.242 + 262.243 +$spinner-single-color: unquote("rgb(#{$color-primary})") !default; 262.244 + 262.245 +/* ========== Text fields ========== */ 262.246 + 262.247 +$input-text-background-color: transparent !default; 262.248 +$input-text-label-color: unquote("rgba(#{$color-black}, 0.26)") !default; 262.249 +$input-text-bottom-border-color: unquote("rgba(#{$color-black}, 0.12)") !default; 262.250 +$input-text-highlight-color: unquote("rgb(#{$color-primary})") !default; 262.251 +$input-text-disabled-color: $input-text-bottom-border-color !default; 262.252 +$input-text-disabled-text-color: $input-text-label-color !default; 262.253 +$input-text-error-color: unquote("rgb(#{$palette-red-A700})") !default; 262.254 + 262.255 +/* ========== Card ========== */ 262.256 + 262.257 +$card-background-color: unquote("rgb(#{$color-white})") !default; 262.258 +$card-text-color: unquote("rgb(#{$color-black})") !default; 262.259 +$card-image-placeholder-color: unquote("rgb(#{$color-accent})") !default; 262.260 +$card-supporting-text-text-color: unquote("rgba(#{$color-black}, 0.54)") !default; 262.261 +$card-border-color: rgba(0,0,0,0.1) !default; 262.262 +$card-subtitle-color: unquote("rgba(#{$color-black}, 0.54)") !default; 262.263 + 262.264 +/* ========== Sliders ========== */ 262.265 + 262.266 +$range-bg-color: unquote("rgba(#{$color-black}, 0.26)") !default; 262.267 +$range-color: unquote("rgb(#{$color-primary})") !default; 262.268 +$range-faded-color: unquote("rgba(#{$color-primary}, 0.26)") !default; 262.269 +$range-bg-focus-color: unquote("rgba(#{$color-black}, 0.12)") !default; 262.270 + 262.271 +/* ========== Progress ========== */ 262.272 +$progress-main-color: unquote("rgb(#{$color-primary})") !default; 262.273 +$progress-secondary-color: unquote("rgba(#{$color-primary-contrast}, 0.7)") !default; 262.274 +$progress-fallback-buffer-color: unquote("rgba(#{$color-primary-contrast}, 0.9)") !default; 262.275 +$progress-image-path: $image_path; 262.276 + 262.277 +/* ========== List ========== */ 262.278 + 262.279 +$list-main-text-text-color: unquote("rgba(#{$color-black}, 0.87)") !default; 262.280 +$list-supporting-text-text-color: unquote("rgba(#{$color-black}, 0.54)") !default; 262.281 +$list-icon-color: unquote("rgb(#{$palette-grey-600})") !default; 262.282 +$list-avatar-color: white !default; 262.283 + 262.284 +/* ========== Item ========== */ 262.285 + 262.286 +// Default Item Colors 262.287 +$default-item-text-color: unquote("rgba(#{$color-black}, 0.87)") !default; 262.288 +$default-item-outline-color: unquote("rgb(#{$palette-grey-400})") !default; 262.289 +$default-item-hover-bg-color: unquote("rgb(#{$palette-grey-200})") !default; 262.290 +$default-item-focus-bg-color: unquote("rgb(#{$palette-grey-200})") !default; 262.291 +$default-item-active-bg-color: unquote("rgb(#{$palette-grey-300})") !default; 262.292 +$default-item-divider-color: unquote("rgba(#{$color-black}, 0.12)") !default; 262.293 + 262.294 +// Disabled Button Colors 262.295 +$disabled-item-text-color: unquote("rgb(#{$palette-grey-400})") !default; 262.296 + 262.297 +/* ========== Dropdown menu ========== */ 262.298 + 262.299 +$default-dropdown-bg-color: unquote("rgb(#{$color-white})") !default; 262.300 + 262.301 +/* ========== Tooltips ========== */ 262.302 + 262.303 +$tooltip-text-color: unquote("rgb(#{$color-white})") !default; 262.304 +$tooltip-background-color: unquote("rgba(#{$palette-grey-700}, 0.9)") !default; 262.305 + 262.306 +/* ========== Footer ========== */ 262.307 + 262.308 +$footer-bg-color: unquote("rgb(#{$palette-grey-800})") !default; 262.309 +$footer-color: unquote("rgb(#{$palette-grey-500})") !default; 262.310 +$footer-heading-color: unquote("rgb(#{$palette-grey-300})") !default; 262.311 +$footer-button-fill-color: $footer-color !default; 262.312 +$footer-underline-color: $footer-color !default; 262.313 + 262.314 + 262.315 +/* TEXTFIELD */ 262.316 + 262.317 +$input-text-font-size: 16px !default; 262.318 +$input-text-width: 100% !default; 262.319 +$input-text-padding: 4px !default; 262.320 +$input-text-vertical-spacing: 20px !default; 262.321 + 262.322 +$input-text-button-size: 32px !default; 262.323 +$input-text-floating-label-fontsize: 12px !default; 262.324 +$input-text-expandable-icon-top: 16px !default; 262.325 + 262.326 + 262.327 +/* SWITCH */ 262.328 + 262.329 +$switch-label-font-size: 16px !default; 262.330 +$switch-label-height: 24px !default; 262.331 +$switch-track-height: 14px !default; 262.332 +$switch-track-length: 36px !default; 262.333 +$switch-thumb-size: 20px !default; 262.334 +$switch-track-top: ($switch-label-height - $switch-track-height) / 2 !default; 262.335 +$switch-thumb-top: ($switch-label-height - $switch-thumb-size) / 2 !default; 262.336 +$switch-ripple-size: $switch-label-height * 2 !default; 262.337 +$switch-helper-size: 8px !default; 262.338 + 262.339 +/* SPINNER */ 262.340 + 262.341 +$spinner-size: 28px !default; 262.342 +$spinner-stroke-width: 3px !default; 262.343 + 262.344 +// Amount of circle the arc takes up. 262.345 +$spinner-arc-size: 270deg !default; 262.346 +// Time it takes to expand and contract arc. 262.347 +$spinner-arc-time: 1333ms !default; 262.348 +// How much the start location of the arc should rotate each time. 262.349 +$spinner-arc-start-rot: 216deg !default; 262.350 + 262.351 +$spinner-duration: 360 * $spinner-arc-time / ( 262.352 + strip-units($spinner-arc-start-rot + (360deg - $spinner-arc-size))); 262.353 + 262.354 + 262.355 +/* RADIO */ 262.356 + 262.357 +$radio-label-font-size: 16px !default; 262.358 +$radio-label-height: 24px !default; 262.359 +$radio-button-size: 16px !default; 262.360 +$radio-inner-margin: $radio-button-size / 4; 262.361 +$radio-padding: 8px !default; 262.362 +$radio-top-offset: ($radio-label-height - $radio-button-size) / 2; 262.363 +$radio-ripple-size: 42px !default; 262.364 + 262.365 + 262.366 +/* MENU */ 262.367 + 262.368 +$menu-expand-duration: 0.3s !default; 262.369 +$menu-fade-duration: 0.2s !default; 262.370 + 262.371 +/* LIST */ 262.372 + 262.373 +$list-border: 8px !default; 262.374 +$list-min-height: 48px !default; 262.375 +$list-min-padding: 16px !default; 262.376 +$list-bottom-padding: 20px !default; 262.377 +$list-avatar-text-left-distance: 72px !default; 262.378 +$list-icon-text-left-distance: 72px !default; 262.379 + 262.380 +$list-avatar-size: 40px !default; 262.381 +$list-icon-size: 24px !default; 262.382 + 262.383 +$list-two-line-height: 72px !default; 262.384 +$list-three-line-height: 88px !default; 262.385 + 262.386 +/* LAYOUT */ 262.387 + 262.388 +$layout-drawer-narrow: 240px !default; 262.389 +$layout-drawer-wide: 456px !default; 262.390 +$layout-drawer-width: $layout-drawer-narrow !default; 262.391 + 262.392 +$layout-header-icon-size: 32px !default; 262.393 +$layout-screen-size-threshold: 1024px !default; 262.394 +$layout-header-icon-margin: 24px !default; 262.395 +$layout-drawer-button-mobile-size: 32px !default; 262.396 +$layout-drawer-button-desktop-size: 48px !default; 262.397 + 262.398 +$layout-header-mobile-row-height: 56px !default; 262.399 +$layout-mobile-header-height: $layout-header-mobile-row-height; 262.400 +$layout-header-desktop-row-height: 64px !default; 262.401 +$layout-desktop-header-height: $layout-header-desktop-row-height; 262.402 + 262.403 +$layout-header-desktop-baseline: 80px !default; 262.404 +$layout-header-mobile-baseline: 72px !default; 262.405 +$layout-header-mobile-indent: 16px !default; 262.406 +$layout-header-desktop-indent: 40px !default; 262.407 + 262.408 +$layout-tab-font-size: 14px !default; 262.409 +$layout-tab-bar-height: 48px !default; 262.410 +$layout-tab-mobile-padding: 12px !default; 262.411 +$layout-tab-desktop-padding: 24px !default; 262.412 +$layout-tab-highlight-thickness: 2px !default; 262.413 + 262.414 + 262.415 +/* ICON TOGGLE */ 262.416 + 262.417 +$icon-toggle-size: 32px !default; 262.418 +$icon-toggle-font-size: 24px !default; 262.419 +$icon-toggle-ripple-size: 36px !default; 262.420 + 262.421 +/* FOOTER */ 262.422 + 262.423 +/*mega-footer*/ 262.424 +$footer-min-padding: 16px !default; 262.425 +$footer-padding-sides: 40px !default; 262.426 +$footer-heading-font-size: 14px !default; 262.427 +$footer-heading-line-height: (1.7 * $footer-heading-font-size) !default; 262.428 +$footer-btn-size: 36px !default; 262.429 + 262.430 +/*mini-footer*/ 262.431 +$padding: 16px !default; 262.432 +$footer-heading-font-size: 24px !default; 262.433 +$footer-heading-line-height: (1.5 * $footer-heading-font-size) !default; 262.434 +$footer-btn-size: 36px !default; 262.435 + 262.436 +/* CHECKBOX */ 262.437 + 262.438 +$checkbox-label-font-size: 16px !default; 262.439 +$checkbox-label-height: 24px !default; 262.440 +$checkbox-button-size: 16px !default; 262.441 +$checkbox-inner-margin: 2px !default; 262.442 +$checkbox-padding: 8px !default; 262.443 +$checkbox-top-offset: 262.444 +($checkbox-label-height - $checkbox-button-size - $checkbox-inner-margin) / 2; 262.445 +$checkbox-ripple-size: $checkbox-label-height * 1.5; 262.446 + 262.447 +/* CARD */ 262.448 + 262.449 +/* Card dimensions */ 262.450 +$card-width: 330px !default; 262.451 +$card-height: 200px !default; 262.452 +$card-font-size: 16px !default; 262.453 +$card-title-font-size: 24px !default; 262.454 +$card-subtitle-font-size: 14px !default; 262.455 +$card-horizontal-padding: 16px !default; 262.456 +$card-vertical-padding: 16px !default; 262.457 + 262.458 +$card-title-perspective-origin-x: 165px !default; 262.459 +$card-title-perspective-origin-y: 56px !default; 262.460 + 262.461 +$card-title-transform-origin-x: 165px !default; 262.462 +$card-title-transform-origin-y: 56px !default; 262.463 + 262.464 +$card-title-text-transform-origin-x: 149px !default; 262.465 +$card-title-text-transform-origin-y: 48px !default; 262.466 + 262.467 +$card-supporting-text-font-size: 1rem !default; 262.468 +$card-supporting-text-line-height: 18px !default; 262.469 + 262.470 +$card-actions-font-size: 16px !default; 262.471 + 262.472 +$card-title-text-font-weight: 300 !default; 262.473 +$card-z-index: 1 !default; 262.474 + 262.475 +/* Cover image */ 262.476 +$card-cover-image-height: 186px !default; 262.477 +$card-background-image-url: '' !default; 262.478 + 262.479 + 262.480 +/* BUTTON */ 262.481 +/** 262.482 + * 262.483 + * Dimensions 262.484 + * 262.485 + */ 262.486 +$button-min-width: 64px !default; 262.487 +$button-height: 36px !default; 262.488 +$button-padding: 16px !default; 262.489 +$button-margin: 4px !default; 262.490 +$button-border-radius: 2px !default; 262.491 + 262.492 +$button-fab-size: 56px !default; 262.493 +$button-fab-size-mini: 40px !default; 262.494 +$button-fab-font-size: 24px !default; 262.495 + 262.496 +$button-icon-size: 32px !default; 262.497 +$button-icon-size-mini: 24px !default; 262.498 + 262.499 + 262.500 +/* ANIMATION */ 262.501 +$animation-curve-fast-out-slow-in: cubic-bezier(0.4, 0, 0.2, 1) !default; 262.502 +$animation-curve-linear-out-slow-in: cubic-bezier(0, 0, 0.2, 1) !default; 262.503 +$animation-curve-fast-out-linear-in: cubic-bezier(0.4, 0, 1, 1) !default; 262.504 + 262.505 +$animation-curve-default: $animation-curve-fast-out-slow-in !default; 262.506 + 262.507 + 262.508 +/* PROGRESS */ 262.509 +$bar-height: 4px !default; 262.510 + 262.511 +/* BADGE */ 262.512 +$badge-font-size: 12px !default; 262.513 +$badge-color: unquote("rgb(#{$color-accent-contrast})") !default; 262.514 +$badge-color-inverse: unquote("rgb(#{$color-accent})") !default; 262.515 +$badge-background: unquote("rgb(#{$color-accent})") !default; 262.516 +$badge-background-inverse: unquote("rgba(#{$color-accent-contrast},0.2)") !default; 262.517 +$badge-size : 22px !default; 262.518 +$badge-padding: 2px !default; 262.519 +$badge-overlap: 12px !default; 262.520 + 262.521 +/* SHADOWS */ 262.522 + 262.523 +$shadow-key-umbra-opacity: 0.2 !default; 262.524 +$shadow-key-penumbra-opacity: 0.14 !default; 262.525 +$shadow-ambient-shadow-opacity: 0.12 !default; 262.526 + 262.527 +/* GRID */ 262.528 + 262.529 +$grid-desktop-columns: 12 !default; 262.530 +$grid-desktop-gutter: 16px !default; 262.531 +$grid-desktop-margin: 16px !default; 262.532 + 262.533 +$grid-desktop-breakpoint: 840px !default; 262.534 + 262.535 +$grid-tablet-columns: 8 !default; 262.536 +$grid-tablet-gutter: $grid-desktop-gutter !default; 262.537 +$grid-tablet-margin: $grid-desktop-margin !default; 262.538 + 262.539 +$grid-tablet-breakpoint: 480px !default; 262.540 + 262.541 +$grid-phone-columns: 4 !default; 262.542 +$grid-phone-gutter: $grid-desktop-gutter !default; 262.543 +$grid-phone-margin: $grid-desktop-margin !default; 262.544 + 262.545 +$grid-cell-default-columns: $grid-phone-columns !default; 262.546 +$grid-max-columns: $grid-desktop-columns !default; 262.547 + 262.548 +/* DATA TABLE */ 262.549 + 262.550 +$data-table-font-size: 13px !default; 262.551 +$data-table-header-font-size: 12px !default; 262.552 +$data-table-header-sort-icon-size: 16px !default; 262.553 + 262.554 +$data-table-header-color: rgba(#000, 0.54) !default; 262.555 +$data-table-header-sorted-color: rgba(#000, 0.87) !default; 262.556 +$data-table-header-sorted-icon-hover-color: rgba(#000, 0.26) !default; 262.557 +$data-table-divider-color: rgba(#000, 0.12) !default; 262.558 + 262.559 +$data-table-hover-color: #eeeeee !default; 262.560 +$data-table-selection-color: #e0e0e0 !default; 262.561 + 262.562 +$data-table-dividers: 1px solid $data-table-divider-color !default; 262.563 + 262.564 +$data-table-row-height: 48px !default; 262.565 +$data-table-last-row-height: 56px !default; 262.566 +$data-table-header-height: 56px !default; 262.567 + 262.568 +$data-table-column-spacing: 36px !default; 262.569 +$data-table-column-padding: $data-table-column-spacing / 2; 262.570 + 262.571 +$data-table-card-header-height: 64px !default; 262.572 +$data-table-card-title-top: 20px !default; 262.573 +$data-table-card-padding: 24px !default; 262.574 +$data-table-button-padding-right: 16px !default; 262.575 +$data-table-cell-top: $data-table-card-padding / 2; 262.576 + 262.577 +/* DIALOG */ 262.578 +$dialog-content-color: $card-supporting-text-text-color; 262.579 + 262.580 +/* SNACKBAR */ 262.581 + 262.582 +// Hard coded since the color is not present in any palette. 262.583 +$snackbar-background-color: #323232 !default; 262.584 +$snackbar-tablet-breakpoint: $grid-tablet-breakpoint; 262.585 +$snackbar-action-color: unquote("rgb(#{$color-accent})") !default; 262.586 + 262.587 +/* TOOLTIP */ 262.588 +$tooltip-font-size: 10px !default; 262.589 +$tooltip-font-size-large: 14px !default; 262.590 + 262.591 +/* CHIP */ 262.592 +$chip-bg-color: rgb(222, 222, 222) !default; 262.593 +$chip-bg-active-color: rgb(214, 214, 214) !default; 262.594 +$chip-height: 32px !default; 262.595 +$chip-font-size: 13px !default;
263.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 263.2 +++ b/style/mdl/animation/_animation.scss Sun Jul 15 14:07:29 2018 +0200 263.3 @@ -0,0 +1,34 @@ 263.4 +/** 263.5 + * Copyright 2015 Google Inc. All Rights Reserved. 263.6 + * 263.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 263.8 + * you may not use this file except in compliance with the License. 263.9 + * You may obtain a copy of the License at 263.10 + * 263.11 + * http://www.apache.org/licenses/LICENSE-2.0 263.12 + * 263.13 + * Unless required by applicable law or agreed to in writing, software 263.14 + * distributed under the License is distributed on an "AS IS" BASIS, 263.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 263.16 + * See the License for the specific language governing permissions and 263.17 + * limitations under the License. 263.18 + */ 263.19 + 263.20 +@import "../variables"; 263.21 + 263.22 + 263.23 +.mdl-animation--default { 263.24 + transition-timing-function: $animation-curve-default; 263.25 +} 263.26 + 263.27 +.mdl-animation--fast-out-slow-in { 263.28 + transition-timing-function: $animation-curve-fast-out-slow-in; 263.29 +} 263.30 + 263.31 +.mdl-animation--linear-out-slow-in { 263.32 + transition-timing-function: $animation-curve-linear-out-slow-in; 263.33 +} 263.34 + 263.35 +.mdl-animation--fast-out-linear-in { 263.36 + transition-timing-function: $animation-curve-fast-out-linear-in; 263.37 +}
264.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 264.2 +++ b/style/mdl/badge/_badge.scss Sun Jul 15 14:07:29 2018 +0200 264.3 @@ -0,0 +1,72 @@ 264.4 +/** 264.5 + * Copyright 2015 Google Inc. All Rights Reserved. 264.6 + * 264.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 264.8 + * you may not use this file except in compliance with the License. 264.9 + * You may obtain a copy of the License at 264.10 + * 264.11 + * http://www.apache.org/licenses/LICENSE-2.0 264.12 + * 264.13 + * Unless required by applicable law or agreed to in writing, software 264.14 + * distributed under the License is distributed on an "AS IS" BASIS, 264.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 264.16 + * See the License for the specific language governing permissions and 264.17 + * limitations under the License. 264.18 + */ 264.19 + 264.20 +@import "../variables"; 264.21 + 264.22 +.mdl-badge { 264.23 + position : relative; 264.24 + white-space: nowrap; 264.25 + margin-right: ($badge-size + $badge-padding); 264.26 + 264.27 + &:not([data-badge]) { 264.28 + margin-right: auto; 264.29 + } 264.30 + 264.31 + &[data-badge]:after { 264.32 + content: attr(data-badge); 264.33 + 264.34 + display: flex; 264.35 + flex-direction: row; 264.36 + flex-wrap: wrap; 264.37 + justify-content: center; 264.38 + align-content: center; 264.39 + align-items: center; 264.40 + 264.41 + position: absolute; 264.42 + top: -($badge-size / 2); 264.43 + right: -($badge-size + $badge-padding); 264.44 + 264.45 + .mdl-button & { 264.46 + top: -10px; 264.47 + right: -5px; 264.48 + } 264.49 + 264.50 + font-family: $preferred_font; 264.51 + font-weight: 600; 264.52 + font-size: $badge-font-size; 264.53 + width: $badge-size; 264.54 + height: $badge-size; 264.55 + border-radius : 50%; 264.56 + 264.57 + background: $badge-background; 264.58 + color: $badge-color; 264.59 + } 264.60 + 264.61 + &.mdl-badge--no-background { 264.62 + &[data-badge]:after { 264.63 + color: $badge-color-inverse; 264.64 + background: $badge-background-inverse; 264.65 + 264.66 + box-shadow: 0 0 1px gray; 264.67 + } 264.68 + } 264.69 + &.mdl-badge--overlap { 264.70 + margin-right: ($badge-size - $badge-overlap); 264.71 + &:after { 264.72 + right: -($badge-size - $badge-overlap); 264.73 + } 264.74 + } 264.75 +}
265.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 265.2 +++ b/style/mdl/button/_button.scss Sun Jul 15 14:07:29 2018 +0200 265.3 @@ -0,0 +1,305 @@ 265.4 +/** 265.5 + * Copyright 2015 Google Inc. All Rights Reserved. 265.6 + * 265.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 265.8 + * you may not use this file except in compliance with the License. 265.9 + * You may obtain a copy of the License at 265.10 + * 265.11 + * http://www.apache.org/licenses/LICENSE-2.0 265.12 + * 265.13 + * Unless required by applicable law or agreed to in writing, software 265.14 + * distributed under the License is distributed on an "AS IS" BASIS, 265.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 265.16 + * See the License for the specific language governing permissions and 265.17 + * limitations under the License. 265.18 + */ 265.19 + 265.20 +@import "../variables"; 265.21 +@import "../mixins"; 265.22 + 265.23 +// The button component. Defaults to a flat button. 265.24 +.mdl-button { 265.25 + background: transparent; 265.26 + border: none; 265.27 + border-radius: $button-border-radius; 265.28 + color: $button-secondary-color; 265.29 + position: relative; 265.30 + height: $button-height; 265.31 + margin: 0; 265.32 + min-width: $button-min-width; 265.33 + padding: 0 $button-padding; 265.34 + display: inline-block; 265.35 + @include typo-button(); 265.36 + overflow: hidden; 265.37 + will-change: box-shadow; 265.38 + transition: box-shadow 0.2s $animation-curve-fast-out-linear-in, 265.39 + background-color 0.2s $animation-curve-default, 265.40 + color 0.2s $animation-curve-default; 265.41 + outline: none; 265.42 + cursor: pointer; 265.43 + text-decoration: none; 265.44 + text-align: center; 265.45 + line-height: $button-height; 265.46 + vertical-align: middle; 265.47 + 265.48 + &::-moz-focus-inner { 265.49 + border: 0; 265.50 + } 265.51 + 265.52 + &:hover { 265.53 + background-color: $button-hover-color; 265.54 + } 265.55 + 265.56 + &:focus:not(:active) { 265.57 + background-color: $button-focus-color; 265.58 + } 265.59 + 265.60 + &:active { 265.61 + background-color: $button-active-color; 265.62 + } 265.63 + 265.64 + &.mdl-button--colored { 265.65 + color: $button-primary-color-alt; 265.66 + 265.67 + &:focus:not(:active) { 265.68 + background-color: $button-focus-color-alt; 265.69 + } 265.70 + } 265.71 +} 265.72 + 265.73 +input.mdl-button[type="submit"] { 265.74 + -webkit-appearance:none; 265.75 +} 265.76 + 265.77 + // Raised buttons 265.78 + .mdl-button--raised { 265.79 + background: $button-primary-color; 265.80 + @include shadow-2dp(); 265.81 + 265.82 + &:active { 265.83 + @include shadow-4dp(); 265.84 + background-color: $button-active-color; 265.85 + } 265.86 + 265.87 + &:focus:not(:active) { 265.88 + @include focus-shadow(); 265.89 + background-color: $button-active-color; 265.90 + } 265.91 + 265.92 + &.mdl-button--colored { 265.93 + background: $button-primary-color-alt; 265.94 + color: $button-secondary-color-alt; 265.95 + 265.96 + &:hover { 265.97 + background-color: $button-hover-color-alt; 265.98 + } 265.99 + 265.100 + &:active { 265.101 + background-color: $button-active-color-alt; 265.102 + } 265.103 + 265.104 + &:focus:not(:active) { 265.105 + background-color: $button-active-color-alt; 265.106 + } 265.107 + 265.108 + & .mdl-ripple { 265.109 + background: $button-ripple-color-alt; 265.110 + } 265.111 + } 265.112 + } 265.113 + 265.114 + 265.115 + // FABs 265.116 + .mdl-button--fab { 265.117 + border-radius: 50%; 265.118 + font-size: $button-fab-font-size; 265.119 + height: $button-fab-size; 265.120 + margin: auto; 265.121 + min-width: $button-fab-size; 265.122 + width: $button-fab-size; 265.123 + padding: 0; 265.124 + overflow: hidden; 265.125 + background: $button-primary-color; 265.126 + box-shadow: 0 1px 1.5px 0 rgba(0,0,0,0.12), 0 1px 1px 0 rgba(0,0,0,0.24); 265.127 + position: relative; 265.128 + line-height: normal; 265.129 + 265.130 + & .material-icons { 265.131 + position: absolute; 265.132 + top: 50%; 265.133 + left: 50%; 265.134 + transform: translate(- $button-fab-font-size / 2, - $button-fab-font-size / 2); 265.135 + line-height: $button-fab-font-size; 265.136 + width: $button-fab-font-size; 265.137 + } 265.138 + 265.139 + &.mdl-button--mini-fab { 265.140 + height: $button-fab-size-mini; 265.141 + min-width: $button-fab-size-mini; 265.142 + width: $button-fab-size-mini; 265.143 + } 265.144 + 265.145 + & .mdl-button__ripple-container { 265.146 + border-radius: 50%; 265.147 + // Fixes clipping bug in Safari. 265.148 + -webkit-mask-image: -webkit-radial-gradient(circle, white, black); 265.149 + } 265.150 + 265.151 + &:active { 265.152 + @include shadow-4dp(); 265.153 + background-color: $button-active-color; 265.154 + } 265.155 + 265.156 + &:focus:not(:active) { 265.157 + @include focus-shadow(); 265.158 + background-color: $button-active-color; 265.159 + } 265.160 + 265.161 + &.mdl-button--colored { 265.162 + background: $button-fab-color-alt; 265.163 + color: $button-fab-text-color-alt; 265.164 + 265.165 + &:hover { 265.166 + background-color: $button-fab-hover-color-alt; 265.167 + } 265.168 + 265.169 + &:focus:not(:active) { 265.170 + background-color: $button-fab-active-color-alt; 265.171 + } 265.172 + 265.173 + &:active { 265.174 + background-color: $button-fab-active-color-alt; 265.175 + } 265.176 + 265.177 + & .mdl-ripple { 265.178 + background: $button-fab-ripple-color-alt; 265.179 + } 265.180 + } 265.181 + } 265.182 + 265.183 + 265.184 + // Icon buttons 265.185 + .mdl-button--icon { 265.186 + border-radius: 50%; 265.187 + font-size: $button-fab-font-size; 265.188 + height: $button-icon-size; 265.189 + margin-left: 0; 265.190 + margin-right: 0; 265.191 + min-width: $button-icon-size; 265.192 + width: $button-icon-size; 265.193 + padding: 0; 265.194 + overflow: hidden; 265.195 + color: inherit; 265.196 + line-height: normal; 265.197 + 265.198 + & .material-icons { 265.199 + position: absolute; 265.200 + top: 50%; 265.201 + left: 50%; 265.202 + transform: translate(- $button-fab-font-size / 2, - $button-fab-font-size / 2); 265.203 + line-height: $button-fab-font-size; 265.204 + width: $button-fab-font-size; 265.205 + } 265.206 + 265.207 + &.mdl-button--mini-icon { 265.208 + height: $button-icon-size-mini; 265.209 + min-width: $button-icon-size-mini; 265.210 + width: $button-icon-size-mini; 265.211 + 265.212 + & .material-icons { 265.213 + top: ($button-icon-size-mini - $button-fab-font-size) / 2; 265.214 + left: ($button-icon-size-mini - $button-fab-font-size) / 2; 265.215 + } 265.216 + } 265.217 + 265.218 + & .mdl-button__ripple-container { 265.219 + border-radius: 50%; 265.220 + // Fixes clipping bug in Safari. 265.221 + -webkit-mask-image: -webkit-radial-gradient(circle, white, black); 265.222 + } 265.223 + } 265.224 + 265.225 + 265.226 + // Ripples 265.227 + .mdl-button__ripple-container { 265.228 + display: block; 265.229 + height: 100%; 265.230 + left: 0px; 265.231 + position: absolute; 265.232 + top: 0px; 265.233 + width: 100%; 265.234 + z-index: 0; 265.235 + overflow: hidden; 265.236 + 265.237 + .mdl-button[disabled] & .mdl-ripple, 265.238 + .mdl-button.mdl-button--disabled & .mdl-ripple { 265.239 + background-color: transparent; 265.240 + } 265.241 + } 265.242 + 265.243 +// Colorized buttons 265.244 + 265.245 +.mdl-button--primary.mdl-button--primary { 265.246 + color: $button-primary-color-alt; 265.247 + & .mdl-ripple { 265.248 + background: $button-secondary-color-alt; 265.249 + } 265.250 + &.mdl-button--raised, &.mdl-button--fab { 265.251 + color: $button-secondary-color-alt; 265.252 + background-color: $button-primary-color-alt; 265.253 + } 265.254 +} 265.255 + 265.256 +.mdl-button--accent.mdl-button--accent { 265.257 + color: $button-fab-color-alt; 265.258 + & .mdl-ripple { 265.259 + background: $button-fab-text-color-alt; 265.260 + } 265.261 + &.mdl-button--raised, &.mdl-button--fab { 265.262 + color: $button-fab-text-color-alt; 265.263 + background-color: $button-fab-color-alt; 265.264 + } 265.265 +} 265.266 + 265.267 +// Disabled buttons 265.268 + 265.269 +.mdl-button { 265.270 + // Bump up specificity by using [disabled] twice. 265.271 + &[disabled][disabled], 265.272 + &.mdl-button--disabled.mdl-button--disabled { 265.273 + color: $button-secondary-color-disabled; 265.274 + cursor: default; 265.275 + background-color: transparent; 265.276 + } 265.277 + 265.278 + &--fab { 265.279 + // Bump up specificity by using [disabled] twice. 265.280 + &[disabled][disabled], 265.281 + &.mdl-button--disabled.mdl-button--disabled { 265.282 + background-color: $button-primary-color-disabled; 265.283 + color: $button-secondary-color-disabled; 265.284 + } 265.285 + } 265.286 + 265.287 + &--raised { 265.288 + // Bump up specificity by using [disabled] twice. 265.289 + &[disabled][disabled], 265.290 + &.mdl-button--disabled.mdl-button--disabled { 265.291 + background-color: $button-primary-color-disabled; 265.292 + color: $button-secondary-color-disabled; 265.293 + box-shadow: none; 265.294 + } 265.295 + } 265.296 + &--colored { 265.297 + // Bump up specificity by using [disabled] twice. 265.298 + &[disabled][disabled], 265.299 + &.mdl-button--disabled.mdl-button--disabled { 265.300 + color: $button-secondary-color-disabled; 265.301 + } 265.302 + } 265.303 +} 265.304 + 265.305 +// Align icons inside buttons with text 265.306 +.mdl-button .material-icons { 265.307 + vertical-align: middle; 265.308 +}
266.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 266.2 +++ b/style/mdl/card/_card.scss Sun Jul 15 14:07:29 2018 +0200 266.3 @@ -0,0 +1,115 @@ 266.4 +/** 266.5 + * Copyright 2015 Google Inc. All Rights Reserved. 266.6 + * 266.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 266.8 + * you may not use this file except in compliance with the License. 266.9 + * You may obtain a copy of the License at 266.10 + * 266.11 + * http://www.apache.org/licenses/LICENSE-2.0 266.12 + * 266.13 + * Unless required by applicable law or agreed to in writing, software 266.14 + * distributed under the License is distributed on an "AS IS" BASIS, 266.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 266.16 + * See the License for the specific language governing permissions and 266.17 + * limitations under the License. 266.18 + */ 266.19 + 266.20 +@import "../variables"; 266.21 + 266.22 +.mdl-card { 266.23 + display: flex; 266.24 + flex-direction: column; 266.25 + font-size: $card-font-size; 266.26 + font-weight: 400; 266.27 + min-height: $card-height; 266.28 + overflow: hidden; 266.29 + width: $card-width; 266.30 + z-index: $card-z-index; 266.31 + position: relative; 266.32 + background: $card-background-color; 266.33 + border-radius: 2px; 266.34 + box-sizing: border-box; 266.35 +} 266.36 + 266.37 +.mdl-card__media { 266.38 + background-color: $card-image-placeholder-color; 266.39 + background-repeat: repeat; 266.40 + background-position: 50% 50%; 266.41 + background-size: cover; 266.42 + background-origin: padding-box; 266.43 + background-attachment: scroll; 266.44 + box-sizing: border-box; 266.45 +} 266.46 + 266.47 +.mdl-card__title { 266.48 + align-items: center; 266.49 + color: $card-text-color; 266.50 + display: block; 266.51 + display: flex; 266.52 + justify-content: stretch; 266.53 + line-height: normal; 266.54 + padding: $card-vertical-padding $card-horizontal-padding; 266.55 + perspective-origin: $card-title-perspective-origin-x $card-title-perspective-origin-y; 266.56 + transform-origin: $card-title-transform-origin-x $card-title-transform-origin-y; 266.57 + box-sizing: border-box; 266.58 + 266.59 + &.mdl-card--border { 266.60 + border-bottom: 1px solid $card-border-color; 266.61 + } 266.62 +} 266.63 + 266.64 +.mdl-card__title-text { 266.65 + align-self: flex-end; 266.66 + color: inherit; 266.67 + display: block; 266.68 + display: flex; 266.69 + font-size: $card-title-font-size; 266.70 + font-weight: $card-title-text-font-weight; 266.71 + line-height: normal; 266.72 + overflow: hidden; 266.73 + transform-origin: $card-title-text-transform-origin-x $card-title-text-transform-origin-y; 266.74 + margin: 0; 266.75 +} 266.76 + 266.77 +.mdl-card__subtitle-text { 266.78 + font-size: $card-subtitle-font-size; 266.79 + color: $card-subtitle-color; 266.80 + margin: 0; 266.81 +} 266.82 + 266.83 +.mdl-card__supporting-text { 266.84 + color: $card-supporting-text-text-color; 266.85 + font-size: $card-supporting-text-font-size; 266.86 + line-height: $card-supporting-text-line-height; 266.87 + overflow: hidden; 266.88 + padding: $card-vertical-padding $card-horizontal-padding; 266.89 + width: 90%; 266.90 + 266.91 + &.mdl-card--border { 266.92 + border-bottom: 1px solid $card-border-color; 266.93 + } 266.94 +} 266.95 + 266.96 +.mdl-card__actions { 266.97 + font-size: $card-actions-font-size; 266.98 + line-height: normal; 266.99 + width: 100%; 266.100 + background-color: rgba(0,0,0,0); 266.101 + padding: 8px; 266.102 + box-sizing: border-box; 266.103 + 266.104 + &.mdl-card--border { 266.105 + border-top: 1px solid $card-border-color; 266.106 + } 266.107 +} 266.108 + 266.109 +.mdl-card--expand { 266.110 + flex-grow: 1; 266.111 +} 266.112 + 266.113 + 266.114 +.mdl-card__menu { 266.115 + position: absolute; 266.116 + right: 16px; 266.117 + top: 16px; 266.118 +}
267.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 267.2 +++ b/style/mdl/checkbox/_checkbox.scss Sun Jul 15 14:07:29 2018 +0200 267.3 @@ -0,0 +1,180 @@ 267.4 +/** 267.5 + * Copyright 2015 Google Inc. All Rights Reserved. 267.6 + * 267.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 267.8 + * you may not use this file except in compliance with the License. 267.9 + * You may obtain a copy of the License at 267.10 + * 267.11 + * http://www.apache.org/licenses/LICENSE-2.0 267.12 + * 267.13 + * Unless required by applicable law or agreed to in writing, software 267.14 + * distributed under the License is distributed on an "AS IS" BASIS, 267.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 267.16 + * See the License for the specific language governing permissions and 267.17 + * limitations under the License. 267.18 + */ 267.19 + 267.20 +@import "../variables"; 267.21 +@import "../mixins"; 267.22 + 267.23 +.mdl-checkbox { 267.24 + position: relative; 267.25 + 267.26 + z-index: 1; 267.27 + 267.28 + vertical-align: middle; 267.29 + 267.30 + display: inline-block; 267.31 + 267.32 + box-sizing: border-box; 267.33 + width: 100%; 267.34 + height: $checkbox-label-height; 267.35 + margin: 0; 267.36 + padding: 0; 267.37 + 267.38 + &.is-upgraded { 267.39 + padding-left: $checkbox-button-size + $checkbox-padding; 267.40 + } 267.41 +} 267.42 + 267.43 +.mdl-checkbox__input { 267.44 + line-height: $checkbox-label-height; 267.45 + 267.46 + .mdl-checkbox.is-upgraded & { 267.47 + // Hide input element, while still making it respond to focus. 267.48 + position: absolute; 267.49 + width: 0; 267.50 + height: 0; 267.51 + margin: 0; 267.52 + padding: 0; 267.53 + opacity: 0; 267.54 + -ms-appearance: none; 267.55 + -moz-appearance: none; 267.56 + -webkit-appearance: none; 267.57 + appearance: none; 267.58 + border: none; 267.59 + } 267.60 +} 267.61 + 267.62 +.mdl-checkbox__box-outline { 267.63 + position: absolute; 267.64 + top: $checkbox-top-offset; 267.65 + left: 0; 267.66 + 267.67 + display: inline-block; 267.68 + 267.69 + box-sizing: border-box; 267.70 + width: $checkbox-button-size; 267.71 + height: $checkbox-button-size; 267.72 + margin: 0; 267.73 + 267.74 + cursor: pointer; 267.75 + overflow: hidden; 267.76 + 267.77 + border: 2px solid $checkbox-off-color; 267.78 + border-radius: 2px; 267.79 + 267.80 + z-index: 2; 267.81 + 267.82 + .mdl-checkbox.is-checked & { 267.83 + border: 2px solid $checkbox-color; 267.84 + } 267.85 + 267.86 + fieldset[disabled] .mdl-checkbox &, 267.87 + .mdl-checkbox.is-disabled & { 267.88 + border: 2px solid $checkbox-disabled-color; 267.89 + cursor: auto; 267.90 + } 267.91 +} 267.92 + 267.93 +.mdl-checkbox__focus-helper { 267.94 + position: absolute; 267.95 + top: $checkbox-top-offset; 267.96 + left: 0; 267.97 + 267.98 + display: inline-block; 267.99 + 267.100 + box-sizing: border-box; 267.101 + width: $checkbox-button-size; 267.102 + height: $checkbox-button-size; 267.103 + border-radius: 50%; 267.104 + 267.105 + background-color: transparent; 267.106 + 267.107 + .mdl-checkbox.is-focused & { 267.108 + box-shadow: 0 0 0px ($checkbox-button-size / 2) rgba(0, 0, 0, 0.1); 267.109 + background-color: rgba(0, 0, 0, 0.1); 267.110 + } 267.111 + 267.112 + .mdl-checkbox.is-focused.is-checked & { 267.113 + box-shadow: 0 0 0px ($checkbox-button-size / 2) $checkbox-focus-color; 267.114 + background-color: $checkbox-focus-color; 267.115 + } 267.116 +} 267.117 + 267.118 +.mdl-checkbox__tick-outline { 267.119 + position: absolute; 267.120 + top: 0; 267.121 + left: 0; 267.122 + height: 100%; 267.123 + width: 100%; 267.124 + mask: url("#{$checkbox-image-path}/tick-mask.svg?embed"); 267.125 + 267.126 + background: transparent; 267.127 + @include material-animation-default(0.28s); 267.128 + transition-property: background; 267.129 + 267.130 + .mdl-checkbox.is-checked & { 267.131 + background: $checkbox-color url("#{$checkbox-image-path}/tick.svg?embed"); 267.132 + } 267.133 + 267.134 + fieldset[disabled] .mdl-checkbox.is-checked &, 267.135 + .mdl-checkbox.is-checked.is-disabled & { 267.136 + background: $checkbox-disabled-color url("#{$checkbox-image-path}/tick.svg?embed"); 267.137 + } 267.138 +} 267.139 + 267.140 +.mdl-checkbox__label { 267.141 + position: relative; 267.142 + cursor: pointer; 267.143 + font-size: $checkbox-label-font-size; 267.144 + line-height: $checkbox-label-height; 267.145 + margin: 0; 267.146 + 267.147 + fieldset[disabled] .mdl-checkbox &, 267.148 + .mdl-checkbox.is-disabled & { 267.149 + color: $checkbox-disabled-color; 267.150 + cursor: auto; 267.151 + } 267.152 +} 267.153 + 267.154 +.mdl-checkbox__ripple-container { 267.155 + position: absolute; 267.156 + z-index: 2; 267.157 + top: -(($checkbox-ripple-size - $checkbox-label-height) / 2); 267.158 + left: -(($checkbox-ripple-size - $checkbox-button-size) / 2); 267.159 + 267.160 + box-sizing: border-box; 267.161 + width: $checkbox-ripple-size; 267.162 + height: $checkbox-ripple-size; 267.163 + border-radius: 50%; 267.164 + 267.165 + cursor: pointer; 267.166 + 267.167 + overflow: hidden; 267.168 + -webkit-mask-image: -webkit-radial-gradient(circle, white, black); 267.169 + 267.170 + & .mdl-ripple { 267.171 + background: $checkbox-color; 267.172 + } 267.173 + 267.174 + fieldset[disabled] .mdl-checkbox &, 267.175 + .mdl-checkbox.is-disabled & { 267.176 + cursor: auto; 267.177 + } 267.178 + 267.179 + fieldset[disabled] .mdl-checkbox & .mdl-ripple, 267.180 + .mdl-checkbox.is-disabled & .mdl-ripple { 267.181 + background: transparent; 267.182 + } 267.183 +}
268.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 268.2 +++ b/style/mdl/chip/_chip.scss Sun Jul 15 14:07:29 2018 +0200 268.3 @@ -0,0 +1,88 @@ 268.4 +/** 268.5 + * Copyright 2015 Google Inc. All Rights Reserved. 268.6 + * 268.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 268.8 + * you may not use this file except in compliance with the License. 268.9 + * You may obtain a copy of the License at 268.10 + * 268.11 + * http://www.apache.org/licenses/LICENSE-2.0 268.12 + * 268.13 + * Unless required by applicable law or agreed to in writing, software 268.14 + * distributed under the License is distributed on an "AS IS" BASIS, 268.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 268.16 + * See the License for the specific language governing permissions and 268.17 + * limitations under the License. 268.18 + */ 268.19 + 268.20 +@import "../variables"; 268.21 +@import "../mixins"; 268.22 + 268.23 +.mdl-chip { 268.24 + height: $chip-height; 268.25 + font-family: $preferred_font; 268.26 + line-height: $chip-height; 268.27 + padding: 0 12px; 268.28 + border: 0; 268.29 + border-radius: $chip-height / 2; 268.30 + background-color: $chip-bg-color; 268.31 + display: inline-block; 268.32 + color: $text-color-primary; 268.33 + margin: 2px 0; 268.34 + font-size: 0; 268.35 + white-space: nowrap; 268.36 + 268.37 + &__text { 268.38 + font-size: $chip-font-size; 268.39 + vertical-align: middle; 268.40 + display: inline-block; 268.41 + } 268.42 + 268.43 + &__action { 268.44 + height: 24px; 268.45 + width: 24px; 268.46 + background: transparent; 268.47 + opacity: 0.54; 268.48 + display: inline-block; 268.49 + cursor: pointer; 268.50 + text-align: center; 268.51 + vertical-align: middle; 268.52 + padding: 0; 268.53 + margin: 0 0 0 4px; 268.54 + font-size: $chip-font-size; 268.55 + text-decoration: none; 268.56 + color: $text-color-primary; 268.57 + border: none; 268.58 + outline: none; 268.59 + overflow: hidden; 268.60 + } 268.61 + 268.62 + &__contact { 268.63 + height: $chip-height; 268.64 + width: $chip-height; 268.65 + border-radius: $chip-height / 2; 268.66 + display: inline-block; 268.67 + vertical-align: middle; 268.68 + margin-right: 8px; 268.69 + overflow: hidden; 268.70 + text-align: center; 268.71 + font-size: 18px; 268.72 + line-height: 32px; 268.73 + } 268.74 + 268.75 + &:focus { 268.76 + outline: 0; 268.77 + @include shadow-2dp(); 268.78 + } 268.79 + 268.80 + &:active { 268.81 + background-color: $chip-bg-active-color; 268.82 + } 268.83 + 268.84 + &--deletable { 268.85 + padding-right: 4px; 268.86 + } 268.87 + 268.88 + &--contact { 268.89 + padding-left: 0; 268.90 + } 268.91 +} 268.92 \ No newline at end of file
269.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 269.2 +++ b/style/mdl/data-table/_data-table.scss Sun Jul 15 14:07:29 2018 +0200 269.3 @@ -0,0 +1,120 @@ 269.4 +/** 269.5 + * Copyright 2015 Google Inc. All Rights Reserved. 269.6 + * 269.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 269.8 + * you may not use this file except in compliance with the License. 269.9 + * You may obtain a copy of the License at 269.10 + * 269.11 + * http://www.apache.org/licenses/LICENSE-2.0 269.12 + * 269.13 + * Unless required by applicable law or agreed to in writing, software 269.14 + * distributed under the License is distributed on an "AS IS" BASIS, 269.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 269.16 + * See the License for the specific language governing permissions and 269.17 + * limitations under the License. 269.18 + */ 269.19 + 269.20 +@import "../variables"; 269.21 +@import "../mixins"; 269.22 + 269.23 +.mdl-data-table { 269.24 + position: relative; 269.25 + border: $data-table-dividers; 269.26 + border-collapse: collapse; 269.27 + white-space: nowrap; 269.28 + font-size: $data-table-font-size; 269.29 + background-color: unquote("rgb(#{$color-white})"); 269.30 + 269.31 + thead { 269.32 + padding-bottom: 3px; 269.33 + 269.34 + .mdl-data-table__select { 269.35 + margin-top: 0; 269.36 + } 269.37 + } 269.38 + 269.39 + tbody { 269.40 + tr { 269.41 + position: relative; 269.42 + height: $data-table-row-height; 269.43 + @include material-animation-default(0.28s); 269.44 + transition-property: background-color; 269.45 + 269.46 + &.is-selected { 269.47 + background-color: $data-table-selection-color; 269.48 + } 269.49 + 269.50 + &:hover { 269.51 + background-color: $data-table-hover-color; 269.52 + } 269.53 + } 269.54 + } 269.55 + 269.56 + td, th { 269.57 + padding: 0 $data-table-column-padding 12px $data-table-column-padding; 269.58 + text-align: right; 269.59 + 269.60 + &:first-of-type { 269.61 + padding-left: 24px; 269.62 + } 269.63 + 269.64 + &:last-of-type { 269.65 + padding-right: 24px; 269.66 + } 269.67 + } 269.68 + 269.69 + td { 269.70 + position: relative; 269.71 + vertical-align: middle; 269.72 + height: $data-table-row-height; 269.73 + border-top: $data-table-dividers; 269.74 + border-bottom: $data-table-dividers; 269.75 + padding-top: $data-table-cell-top; 269.76 + box-sizing: border-box; 269.77 + 269.78 + .mdl-data-table__select { 269.79 + vertical-align: middle; 269.80 + } 269.81 + } 269.82 + 269.83 + th { 269.84 + position: relative; 269.85 + vertical-align: bottom; 269.86 + text-overflow: ellipsis; 269.87 + @include typo-body-2(); 269.88 + height: $data-table-row-height; 269.89 + font-size: $data-table-header-font-size; 269.90 + color: $data-table-header-color; 269.91 + padding-bottom: 8px; 269.92 + box-sizing: border-box; 269.93 + 269.94 + &.mdl-data-table__header--sorted-ascending, 269.95 + &.mdl-data-table__header--sorted-descending { 269.96 + color: $data-table-header-sorted-color; 269.97 + &:before { 269.98 + @include typo-icon; 269.99 + font-size: $data-table-header-sort-icon-size; 269.100 + content: "\e5d8"; 269.101 + margin-right: 5px; 269.102 + vertical-align: sub; 269.103 + } 269.104 + &:hover { 269.105 + cursor: pointer; 269.106 + &:before { 269.107 + color: $data-table-header-sorted-icon-hover-color; 269.108 + } 269.109 + } 269.110 + } 269.111 + &.mdl-data-table__header--sorted-descending:before { 269.112 + content: "\e5db"; 269.113 + } 269.114 + } 269.115 +} 269.116 + 269.117 +.mdl-data-table__select { 269.118 + width: 16px; 269.119 +} 269.120 + 269.121 +.mdl-data-table__cell--non-numeric.mdl-data-table__cell--non-numeric { 269.122 + text-align: left; 269.123 +}
270.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 270.2 +++ b/style/mdl/dialog/_dialog.scss Sun Jul 15 14:07:29 2018 +0200 270.3 @@ -0,0 +1,57 @@ 270.4 +/** 270.5 + * Copyright 2015 Google Inc. All Rights Reserved. 270.6 + * 270.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 270.8 + * you may not use this file except in compliance with the License. 270.9 + * You may obtain a copy of the License at 270.10 + * 270.11 + * http://www.apache.org/licenses/LICENSE-2.0 270.12 + * 270.13 + * Unless required by applicable law or agreed to in writing, software 270.14 + * distributed under the License is distributed on an "AS IS" BASIS, 270.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 270.16 + * See the License for the specific language governing permissions and 270.17 + * limitations under the License. 270.18 + */ 270.19 + 270.20 +@import "../variables"; 270.21 +@import "../mixins"; 270.22 + 270.23 +.mdl-dialog { 270.24 + border: none; 270.25 + @include shadow-24dp; 270.26 + @include dialog-width; 270.27 + 270.28 + &__title { 270.29 + padding: 24px 24px 0; 270.30 + margin: 0; 270.31 + font-size: 2.5rem; 270.32 + } 270.33 + &__actions { 270.34 + padding: 8px 8px 8px 24px; 270.35 + display: flex; 270.36 + flex-direction: row-reverse; 270.37 + flex-wrap: wrap; 270.38 + > * { 270.39 + margin-right: 8px; 270.40 + height: 36px; 270.41 + &:first-child { 270.42 + margin-right: 0; 270.43 + } 270.44 + } 270.45 + &--full-width { 270.46 + padding: 0 0 8px 0; 270.47 + > * { 270.48 + height: 48px; 270.49 + flex: 0 0 100%; 270.50 + padding-right: 16px; 270.51 + margin-right: 0; 270.52 + text-align: right; 270.53 + } 270.54 + } 270.55 + } 270.56 + &__content { 270.57 + padding: 20px 24px 24px 24px; 270.58 + color: $dialog-content-color; 270.59 + } 270.60 +}
271.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 271.2 +++ b/style/mdl/footer/_mega_footer.scss Sun Jul 15 14:07:29 2018 +0200 271.3 @@ -0,0 +1,321 @@ 271.4 +/** 271.5 + * Copyright 2015 Google Inc. All Rights Reserved. 271.6 + * 271.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 271.8 + * you may not use this file except in compliance with the License. 271.9 + * You may obtain a copy of the License at 271.10 + * 271.11 + * http://www.apache.org/licenses/LICENSE-2.0 271.12 + * 271.13 + * Unless required by applicable law or agreed to in writing, software 271.14 + * distributed under the License is distributed on an "AS IS" BASIS, 271.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 271.16 + * See the License for the specific language governing permissions and 271.17 + * limitations under the License. 271.18 + */ 271.19 + 271.20 +@import "../variables"; 271.21 +@import "../mixins"; 271.22 + 271.23 +.mdl-mega-footer { 271.24 + padding: $footer-min-padding $footer-padding-sides; 271.25 + 271.26 + color: $footer-color; 271.27 + background-color: $footer-bg-color; 271.28 +} 271.29 + 271.30 + 271.31 +.mdl-mega-footer--top-section:after, 271.32 +.mdl-mega-footer--middle-section:after, 271.33 +.mdl-mega-footer--bottom-section:after, 271.34 +.mdl-mega-footer__top-section:after, 271.35 +.mdl-mega-footer__middle-section:after, 271.36 +.mdl-mega-footer__bottom-section:after { 271.37 + content: ''; 271.38 + display: block; 271.39 + clear: both; 271.40 +} 271.41 + 271.42 +.mdl-mega-footer--left-section, 271.43 +.mdl-mega-footer__left-section { 271.44 + margin-bottom: $footer-min-padding; 271.45 +} 271.46 + 271.47 +.mdl-mega-footer--right-section, 271.48 +.mdl-mega-footer__right-section { 271.49 + margin-bottom: $footer-min-padding; 271.50 +} 271.51 + 271.52 +.mdl-mega-footer--right-section a, 271.53 +.mdl-mega-footer__right-section a { 271.54 + display: block; 271.55 + 271.56 + margin-bottom: $footer-min-padding; 271.57 + 271.58 + color: inherit; 271.59 + text-decoration: none; 271.60 +} 271.61 + 271.62 +@media screen and (min-width: 760px) { 271.63 + .mdl-mega-footer--left-section, 271.64 + .mdl-mega-footer__left-section { 271.65 + float: left; 271.66 + } 271.67 + 271.68 + .mdl-mega-footer--right-section, 271.69 + .mdl-mega-footer__right-section { 271.70 + float: right; 271.71 + } 271.72 + 271.73 + .mdl-mega-footer--right-section a, 271.74 + .mdl-mega-footer__right-section a { 271.75 + display: inline-block; 271.76 + 271.77 + margin-left: $footer-min-padding; 271.78 + 271.79 + line-height: $footer-btn-size; 271.80 + vertical-align: middle; 271.81 + } 271.82 +} 271.83 + 271.84 +.mdl-mega-footer--social-btn, 271.85 +.mdl-mega-footer__social-btn { 271.86 + width: $footer-btn-size; 271.87 + height: $footer-btn-size; 271.88 + 271.89 + padding: 0; 271.90 + margin: 0; 271.91 + 271.92 + background-color: $footer-button-fill-color; 271.93 + 271.94 + border: none; 271.95 +} 271.96 + 271.97 +.mdl-mega-footer--drop-down-section, 271.98 +.mdl-mega-footer__drop-down-section { 271.99 + display: block; 271.100 + 271.101 + position: relative; 271.102 +} 271.103 + 271.104 +@media screen and (min-width: 760px) { 271.105 + .mdl-mega-footer--drop-down-section, 271.106 + .mdl-mega-footer__drop-down-section { 271.107 + width: 33%; 271.108 + } 271.109 + 271.110 + .mdl-mega-footer--drop-down-section:nth-child(1), 271.111 + .mdl-mega-footer--drop-down-section:nth-child(2), 271.112 + .mdl-mega-footer__drop-down-section:nth-child(1), 271.113 + .mdl-mega-footer__drop-down-section:nth-child(2) { 271.114 + float: left; 271.115 + } 271.116 + 271.117 + .mdl-mega-footer--drop-down-section:nth-child(3), 271.118 + .mdl-mega-footer__drop-down-section:nth-child(3) { 271.119 + float: right; 271.120 + 271.121 + &:after { 271.122 + clear: right; 271.123 + } 271.124 + } 271.125 + 271.126 + .mdl-mega-footer--drop-down-section:nth-child(4), 271.127 + .mdl-mega-footer__drop-down-section:nth-child(4) { 271.128 + clear: right; 271.129 + float: right; 271.130 + } 271.131 + 271.132 + .mdl-mega-footer--middle-section:after, 271.133 + .mdl-mega-footer__middle-section:after { 271.134 + content: ''; 271.135 + 271.136 + display: block; 271.137 + 271.138 + clear: both; 271.139 + } 271.140 + 271.141 + .mdl-mega-footer--bottom-section, 271.142 + .mdl-mega-footer__bottom-section { 271.143 + padding-top: 0; 271.144 + } 271.145 +} 271.146 + 271.147 +@media screen and (min-width: 1024px) { 271.148 + .mdl-mega-footer--drop-down-section, 271.149 + .mdl-mega-footer--drop-down-section:nth-child(3), 271.150 + .mdl-mega-footer--drop-down-section:nth-child(4), 271.151 + .mdl-mega-footer__drop-down-section, 271.152 + .mdl-mega-footer__drop-down-section:nth-child(3), 271.153 + .mdl-mega-footer__drop-down-section:nth-child(4) { 271.154 + width: 24%; 271.155 + 271.156 + float: left; 271.157 + } 271.158 +} 271.159 + 271.160 +.mdl-mega-footer--heading-checkbox, 271.161 +.mdl-mega-footer__heading-checkbox { 271.162 + position: absolute; 271.163 + width: 100%; 271.164 + height: $footer-heading-line-height + ($footer-min-padding * 2); 271.165 + 271.166 + padding: ($footer-min-padding * 2); 271.167 + margin: 0; 271.168 + margin-top: -$footer-min-padding; 271.169 + 271.170 + cursor: pointer; 271.171 + 271.172 + z-index: 1; 271.173 + opacity: 0; 271.174 + 271.175 + & + .mdl-mega-footer--heading:after, 271.176 + & + .mdl-mega-footer__heading:after { 271.177 + font-family: 'Material Icons'; 271.178 + content: '\E5CE' 271.179 + } 271.180 +} 271.181 + 271.182 +.mdl-mega-footer--heading-checkbox:checked, 271.183 +.mdl-mega-footer__heading-checkbox:checked { 271.184 + // WebViews in iOS 9 break the "~" operator, and WebViews in OS X 10.10 271.185 + // break consecutive "+" operators in some cases. Therefore, we need to use 271.186 + // both here to cover all the bases. 271.187 + & ~ .mdl-mega-footer--link-list, 271.188 + & ~ .mdl-mega-footer__link-list, 271.189 + & + .mdl-mega-footer--heading + .mdl-mega-footer--link-list, 271.190 + & + .mdl-mega-footer__heading + .mdl-mega-footer__link-list { 271.191 + display: none; 271.192 + } 271.193 + 271.194 + & + .mdl-mega-footer--heading:after, 271.195 + & + .mdl-mega-footer__heading:after { 271.196 + font-family: 'Material Icons'; 271.197 + content: '\E5CF' 271.198 + } 271.199 +} 271.200 + 271.201 +.mdl-mega-footer--heading, 271.202 +.mdl-mega-footer__heading { 271.203 + position: relative; 271.204 + width: 100%; 271.205 + 271.206 + padding-right: $footer-heading-line-height + $footer-min-padding; 271.207 + margin-bottom: $footer-min-padding; 271.208 + 271.209 + box-sizing:border-box; 271.210 + 271.211 + font-size: $footer-heading-font-size; 271.212 + line-height: $footer-heading-line-height; 271.213 + 271.214 + font-weight: 500; 271.215 + 271.216 + white-space: nowrap; 271.217 + text-overflow: ellipsis; 271.218 + overflow: hidden; 271.219 + 271.220 + color: $footer-heading-color; 271.221 +} 271.222 + 271.223 +.mdl-mega-footer--heading:after, 271.224 +.mdl-mega-footer__heading:after { 271.225 + content: ''; 271.226 + 271.227 + position: absolute; 271.228 + top: 0; 271.229 + right: 0; 271.230 + 271.231 + display: block; 271.232 + 271.233 + width: $footer-heading-line-height; 271.234 + height: $footer-heading-line-height; 271.235 + 271.236 + background-size: cover; 271.237 +} 271.238 + 271.239 +.mdl-mega-footer--link-list, 271.240 +.mdl-mega-footer__link-list { 271.241 + list-style: none; 271.242 + 271.243 + margin: 0; 271.244 + padding: 0; 271.245 + 271.246 + margin-bottom: $footer-min-padding * 2; 271.247 + &:after { 271.248 + clear: both; 271.249 + display: block; 271.250 + content: ''; 271.251 + } 271.252 +} 271.253 + 271.254 +.mdl-mega-footer--link-list li, 271.255 +.mdl-mega-footer__link-list li { 271.256 + @include typo-body-1(); 271.257 + line-height: 20px; 271.258 +} 271.259 + 271.260 +.mdl-mega-footer--link-list a, 271.261 +.mdl-mega-footer__link-list a { 271.262 + color: inherit; 271.263 + text-decoration: none; 271.264 + white-space: nowrap; 271.265 +} 271.266 + 271.267 +@media screen and (min-width: 760px) { 271.268 + .mdl-mega-footer--heading-checkbox, 271.269 + .mdl-mega-footer__heading-checkbox { 271.270 + display: none; 271.271 + 271.272 + & + .mdl-mega-footer--heading:after, 271.273 + & + .mdl-mega-footer__heading:after { 271.274 + content: ''; 271.275 + } 271.276 + } 271.277 + .mdl-mega-footer--heading-checkbox:checked, 271.278 + .mdl-mega-footer__heading-checkbox:checked { 271.279 + // WebViews in iOS 9 break the "~" operator, and WebViews in OS X 10.10 271.280 + // break consecutive "+" operators in some cases. Therefore, we need to use 271.281 + // both here to cover all the bases. 271.282 + & ~ .mdl-mega-footer--link-list, 271.283 + & ~ .mdl-mega-footer__link-list, 271.284 + & + .mdl-mega-footer__heading + .mdl-mega-footer__link-list, 271.285 + & + .mdl-mega-footer--heading + .mdl-mega-footer--link-list { 271.286 + display: block; 271.287 + } 271.288 + 271.289 + & + .mdl-mega-footer--heading:after, 271.290 + & + .mdl-mega-footer__heading:after { 271.291 + content: ''; 271.292 + } 271.293 + } 271.294 +} 271.295 + 271.296 +.mdl-mega-footer--bottom-section, 271.297 +.mdl-mega-footer__bottom-section { 271.298 + padding-top: $footer-min-padding; 271.299 + margin-bottom: $footer-min-padding; 271.300 +} 271.301 + 271.302 +.mdl-logo { 271.303 + margin-bottom: $footer-min-padding; 271.304 + color: white; 271.305 +} 271.306 + 271.307 +.mdl-mega-footer--bottom-section .mdl-mega-footer--link-list li, 271.308 +.mdl-mega-footer__bottom-section .mdl-mega-footer__link-list li { 271.309 + float: left; 271.310 + 271.311 + margin-bottom: 0; 271.312 + margin-right: $footer-min-padding; 271.313 +} 271.314 + 271.315 + 271.316 + 271.317 +@media screen and (min-width: 760px) { 271.318 + .mdl-logo { 271.319 + float: left; 271.320 + 271.321 + margin-bottom: 0; 271.322 + margin-right: $footer-min-padding; 271.323 + } 271.324 +}
272.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 272.2 +++ b/style/mdl/footer/_mini_footer.scss Sun Jul 15 14:07:29 2018 +0200 272.3 @@ -0,0 +1,88 @@ 272.4 +/** 272.5 + * Copyright 2015 Google Inc. All Rights Reserved. 272.6 + * 272.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 272.8 + * you may not use this file except in compliance with the License. 272.9 + * You may obtain a copy of the License at 272.10 + * 272.11 + * http://www.apache.org/licenses/LICENSE-2.0 272.12 + * 272.13 + * Unless required by applicable law or agreed to in writing, software 272.14 + * distributed under the License is distributed on an "AS IS" BASIS, 272.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 272.16 + * See the License for the specific language governing permissions and 272.17 + * limitations under the License. 272.18 + */ 272.19 + 272.20 +@import "../variables"; 272.21 + 272.22 +.mdl-mini-footer { 272.23 + display: flex; 272.24 + flex-flow: row wrap; 272.25 + justify-content: space-between; 272.26 + 272.27 + padding: ($padding * 2) $padding; 272.28 + 272.29 + color: $footer-color; 272.30 + background-color: $footer-bg-color; 272.31 + 272.32 + &:after { 272.33 + content: ''; 272.34 + display: block; 272.35 + } 272.36 + 272.37 + & .mdl-logo { 272.38 + line-height: $footer-btn-size; 272.39 + } 272.40 +} 272.41 + 272.42 +.mdl-mini-footer--link-list, 272.43 +.mdl-mini-footer__link-list { 272.44 + display: flex; 272.45 + flex-flow: row nowrap; 272.46 + 272.47 + list-style: none; 272.48 + 272.49 + margin: 0; 272.50 + padding: 0; 272.51 + 272.52 + & li { 272.53 + margin-bottom: 0; 272.54 + margin-right: $padding; 272.55 + 272.56 + @media screen and (min-width: 760px) { 272.57 + line-height: $footer-btn-size; 272.58 + } 272.59 + } 272.60 + 272.61 + & a { 272.62 + color: inherit; 272.63 + text-decoration: none; 272.64 + white-space: nowrap; 272.65 + } 272.66 +} 272.67 + 272.68 +.mdl-mini-footer--left-section, 272.69 +.mdl-mini-footer__left-section { 272.70 + display: inline-block; 272.71 + order: 0; 272.72 +} 272.73 + 272.74 +.mdl-mini-footer--right-section, 272.75 +.mdl-mini-footer__right-section { 272.76 + display: inline-block; 272.77 + order: 1; 272.78 +} 272.79 + 272.80 +.mdl-mini-footer--social-btn, 272.81 +.mdl-mini-footer__social-btn { 272.82 + width: $footer-btn-size; 272.83 + height: $footer-btn-size; 272.84 + 272.85 + padding: 0; 272.86 + margin: 0; 272.87 + 272.88 + background-color: $footer-button-fill-color; 272.89 + 272.90 + border: none; 272.91 +}
273.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 273.2 +++ b/style/mdl/grid/_grid.scss Sun Jul 15 14:07:29 2018 +0200 273.3 @@ -0,0 +1,231 @@ 273.4 +/** 273.5 + * Copyright 2015 Google Inc. All Rights Reserved. 273.6 + * 273.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 273.8 + * you may not use this file except in compliance with the License. 273.9 + * You may obtain a copy of the License at 273.10 + * 273.11 + * http://www.apache.org/licenses/LICENSE-2.0 273.12 + * 273.13 + * Unless required by applicable law or agreed to in writing, software 273.14 + * distributed under the License is distributed on an "AS IS" BASIS, 273.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 273.16 + * See the License for the specific language governing permissions and 273.17 + * limitations under the License. 273.18 + */ 273.19 + 273.20 +/* 273.21 +* NOTE: Some rules here are applied using duplicate selectors. 273.22 +* This is on purpose to increase their specificity when applied. 273.23 +* For example: `.mdl-cell--1-col-phone.mdl-cell--1-col-phone` 273.24 +*/ 273.25 + 273.26 +@import "../variables"; 273.27 + 273.28 +.mdl-grid { 273.29 + display: flex; 273.30 + flex-flow: row wrap; 273.31 + margin: 0 auto 0 auto; 273.32 + align-items: stretch; 273.33 + 273.34 + &.mdl-grid--no-spacing { 273.35 + padding: 0; 273.36 + } 273.37 +} 273.38 + 273.39 +.mdl-cell { 273.40 + box-sizing: border-box; 273.41 +} 273.42 + 273.43 + 273.44 +.mdl-cell--top { 273.45 + align-self: flex-start; 273.46 +} 273.47 + 273.48 +.mdl-cell--middle { 273.49 + align-self: center; 273.50 +} 273.51 + 273.52 +.mdl-cell--bottom { 273.53 + align-self: flex-end; 273.54 +} 273.55 + 273.56 +.mdl-cell--stretch { 273.57 + align-self: stretch; 273.58 +} 273.59 + 273.60 +.mdl-grid.mdl-grid--no-spacing > .mdl-cell { 273.61 + margin: 0; 273.62 +} 273.63 + 273.64 +// Define order override classes. 273.65 +@for $i from 1 through $grid-max-columns { 273.66 + .mdl-cell--order-#{$i} { 273.67 + order: $i; 273.68 + } 273.69 +} 273.70 + 273.71 + 273.72 +// Mixins for width calculation. 273.73 +@mixin partial-size($size, $columns, $gutter) { 273.74 + width: calc(#{(($size / $columns) * 100)+"%"} - #{$gutter}); 273.75 + 273.76 + .mdl-grid--no-spacing > & { 273.77 + width: #{(($size / $columns) * 100)+"%"}; 273.78 + } 273.79 +} 273.80 + 273.81 +@mixin full-size($gutter) { 273.82 + @include partial-size(1, 1, $gutter); 273.83 +} 273.84 + 273.85 +@mixin offset-size($size, $columns, $gutter) { 273.86 + margin-left: calc(#{(($size / $columns) * 100)+"%"} + #{$gutter / 2}); 273.87 + 273.88 + .mdl-grid.mdl-grid--no-spacing > & { 273.89 + margin-left: #{(($size / $columns) * 100)+"%"}; 273.90 + } 273.91 +} 273.92 + 273.93 + 273.94 + 273.95 +////////// Phone ////////// 273.96 + 273.97 +@media (max-width: $grid-tablet-breakpoint - 1) { 273.98 + .mdl-grid { 273.99 + padding: $grid-phone-margin - ($grid-phone-gutter / 2); 273.100 + } 273.101 + 273.102 + .mdl-cell { 273.103 + margin: $grid-phone-gutter / 2; 273.104 + @include partial-size($grid-cell-default-columns, $grid-phone-columns, 273.105 + $grid-phone-gutter); 273.106 + } 273.107 + 273.108 + .mdl-cell--hide-phone { 273.109 + display: none !important; 273.110 + } 273.111 + 273.112 + // Define order override classes. 273.113 + @for $i from 1 through $grid-max-columns { 273.114 + .mdl-cell--order-#{$i}-phone.mdl-cell--order-#{$i}-phone { 273.115 + order: $i; 273.116 + } 273.117 + } 273.118 + 273.119 + // Define partial sizes for columnNumber < totalColumns. 273.120 + @for $i from 1 through ($grid-phone-columns - 1) { 273.121 + .mdl-cell--#{$i}-col, 273.122 + .mdl-cell--#{$i}-col-phone.mdl-cell--#{$i}-col-phone { 273.123 + @include partial-size($i, $grid-phone-columns, $grid-phone-gutter); 273.124 + } 273.125 + } 273.126 + 273.127 + // Define 100% for everything else. 273.128 + @for $i from $grid-phone-columns through $grid-desktop-columns { 273.129 + .mdl-cell--#{$i}-col, 273.130 + .mdl-cell--#{$i}-col-phone.mdl-cell--#{$i}-col-phone { 273.131 + @include full-size($grid-phone-gutter); 273.132 + } 273.133 + } 273.134 + 273.135 + // Define valid phone offsets. 273.136 + @for $i from 1 through ($grid-phone-columns - 1) { 273.137 + .mdl-cell--#{$i}-offset, 273.138 + .mdl-cell--#{$i}-offset-phone.mdl-cell--#{$i}-offset-phone { 273.139 + @include offset-size($i, $grid-phone-columns, $grid-phone-gutter); 273.140 + } 273.141 + } 273.142 +} 273.143 + 273.144 + 273.145 +////////// Tablet ////////// 273.146 + 273.147 +@media (min-width: $grid-tablet-breakpoint) and (max-width: $grid-desktop-breakpoint - 1) { 273.148 + .mdl-grid { 273.149 + padding: $grid-tablet-margin - ($grid-tablet-gutter / 2); 273.150 + } 273.151 + 273.152 + .mdl-cell { 273.153 + margin: $grid-tablet-gutter / 2; 273.154 + @include partial-size($grid-cell-default-columns, $grid-tablet-columns, 273.155 + $grid-tablet-gutter); 273.156 + } 273.157 + 273.158 + .mdl-cell--hide-tablet { 273.159 + display: none !important; 273.160 + } 273.161 + 273.162 + // Define order override classes. 273.163 + @for $i from 1 through $grid-max-columns { 273.164 + .mdl-cell--order-#{$i}-tablet.mdl-cell--order-#{$i}-tablet { 273.165 + order: $i; 273.166 + } 273.167 + } 273.168 + 273.169 + // Define partial sizes for columnNumber < totalColumns. 273.170 + @for $i from 1 through ($grid-tablet-columns - 1) { 273.171 + .mdl-cell--#{$i}-col, 273.172 + .mdl-cell--#{$i}-col-tablet.mdl-cell--#{$i}-col-tablet { 273.173 + @include partial-size($i, $grid-tablet-columns, $grid-tablet-gutter); 273.174 + } 273.175 + } 273.176 + 273.177 + // Define 100% for everything else. 273.178 + @for $i from $grid-tablet-columns through $grid-desktop-columns { 273.179 + .mdl-cell--#{$i}-col, 273.180 + .mdl-cell--#{$i}-col-tablet.mdl-cell--#{$i}-col-tablet { 273.181 + @include full-size($grid-tablet-gutter); 273.182 + } 273.183 + } 273.184 + 273.185 + // Define valid tablet offsets. 273.186 + @for $i from 1 through ($grid-tablet-columns - 1) { 273.187 + .mdl-cell--#{$i}-offset, 273.188 + .mdl-cell--#{$i}-offset-tablet.mdl-cell--#{$i}-offset-tablet { 273.189 + @include offset-size($i, $grid-tablet-columns, $grid-tablet-gutter); 273.190 + } 273.191 + } 273.192 +} 273.193 + 273.194 + 273.195 +////////// Desktop ////////// 273.196 + 273.197 +@media (min-width: $grid-desktop-breakpoint) { 273.198 + .mdl-grid { 273.199 + padding: $grid-desktop-margin - ($grid-desktop-gutter / 2); 273.200 + } 273.201 + 273.202 + .mdl-cell { 273.203 + margin: $grid-desktop-gutter / 2; 273.204 + @include partial-size($grid-cell-default-columns, $grid-desktop-columns, 273.205 + $grid-desktop-gutter); 273.206 + } 273.207 + 273.208 + .mdl-cell--hide-desktop { 273.209 + display: none !important; 273.210 + } 273.211 + 273.212 + // Define order override classes. 273.213 + @for $i from 1 through $grid-max-columns { 273.214 + .mdl-cell--order-#{$i}-desktop.mdl-cell--order-#{$i}-desktop { 273.215 + order: $i; 273.216 + } 273.217 + } 273.218 + 273.219 + // Define partial sizes for all numbers of columns. 273.220 + @for $i from 1 through $grid-desktop-columns { 273.221 + .mdl-cell--#{$i}-col, 273.222 + .mdl-cell--#{$i}-col-desktop.mdl-cell--#{$i}-col-desktop { 273.223 + @include partial-size($i, $grid-desktop-columns, $grid-desktop-gutter); 273.224 + } 273.225 + } 273.226 + 273.227 + // Define valid desktop offsets. 273.228 + @for $i from 1 through ($grid-desktop-columns - 1) { 273.229 + .mdl-cell--#{$i}-offset, 273.230 + .mdl-cell--#{$i}-offset-desktop.mdl-cell--#{$i}-offset-desktop { 273.231 + @include offset-size($i, $grid-desktop-columns, $grid-desktop-gutter); 273.232 + } 273.233 + } 273.234 +}
274.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 274.2 +++ b/style/mdl/icon-toggle/_icon-toggle.scss Sun Jul 15 14:07:29 2018 +0200 274.3 @@ -0,0 +1,121 @@ 274.4 +/** 274.5 + * Copyright 2015 Google Inc. All Rights Reserved. 274.6 + * 274.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 274.8 + * you may not use this file except in compliance with the License. 274.9 + * You may obtain a copy of the License at 274.10 + * 274.11 + * http://www.apache.org/licenses/LICENSE-2.0 274.12 + * 274.13 + * Unless required by applicable law or agreed to in writing, software 274.14 + * distributed under the License is distributed on an "AS IS" BASIS, 274.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 274.16 + * See the License for the specific language governing permissions and 274.17 + * limitations under the License. 274.18 + */ 274.19 + 274.20 +@import "../variables"; 274.21 + 274.22 +.mdl-icon-toggle { 274.23 + position: relative; 274.24 + 274.25 + z-index: 1; 274.26 + 274.27 + vertical-align: middle; 274.28 + 274.29 + display: inline-block; 274.30 + height: $icon-toggle-size; 274.31 + margin: 0; 274.32 + padding: 0; 274.33 +} 274.34 + 274.35 +.mdl-icon-toggle__input { 274.36 + line-height: $icon-toggle-size; 274.37 + 274.38 + .mdl-icon-toggle.is-upgraded & { 274.39 + // Hide input element, while still making it respond to focus. 274.40 + position: absolute; 274.41 + width: 0; 274.42 + height: 0; 274.43 + margin: 0; 274.44 + padding: 0; 274.45 + opacity: 0; 274.46 + -ms-appearance: none; 274.47 + -moz-appearance: none; 274.48 + -webkit-appearance: none; 274.49 + appearance: none; 274.50 + border: none; 274.51 + } 274.52 +} 274.53 + 274.54 +.mdl-icon-toggle__label { 274.55 + display: inline-block; 274.56 + position: relative; 274.57 + cursor: pointer; 274.58 + height: $icon-toggle-size; 274.59 + width: $icon-toggle-size; 274.60 + min-width: $icon-toggle-size; 274.61 + color: $icon-toggle-color; 274.62 + border-radius: 50%; 274.63 + padding: 0; 274.64 + margin-left: 0; 274.65 + margin-right: 0; 274.66 + text-align: center; 274.67 + background-color: transparent; 274.68 + will-change: background-color; 274.69 + transition: background-color 0.2s $animation-curve-default, 274.70 + color 0.2s $animation-curve-default; 274.71 + 274.72 + &.material-icons { 274.73 + line-height: $icon-toggle-size; 274.74 + font-size: $icon-toggle-font-size; 274.75 + } 274.76 + 274.77 + .mdl-icon-toggle.is-checked & { 274.78 + color: $icon-toggle-checked-color; 274.79 + } 274.80 + 274.81 + .mdl-icon-toggle.is-disabled & { 274.82 + color: $icon-toggle-disabled-color; 274.83 + cursor: auto; 274.84 + transition: none; 274.85 + } 274.86 + 274.87 + .mdl-icon-toggle.is-focused & { 274.88 + background-color: $icon-toggle-focus-color; 274.89 + } 274.90 + 274.91 + .mdl-icon-toggle.is-focused.is-checked & { 274.92 + background-color: $icon-toggle-checked-focus-color; 274.93 + } 274.94 +} 274.95 + 274.96 + 274.97 +.mdl-icon-toggle__ripple-container { 274.98 + position: absolute; 274.99 + z-index: 2; 274.100 + top: -(($icon-toggle-ripple-size - $icon-toggle-size) / 2); 274.101 + left: -(($icon-toggle-ripple-size - $icon-toggle-size) / 2); 274.102 + 274.103 + box-sizing: border-box; 274.104 + width: $icon-toggle-ripple-size; 274.105 + height: $icon-toggle-ripple-size; 274.106 + border-radius: 50%; 274.107 + 274.108 + cursor: pointer; 274.109 + 274.110 + overflow: hidden; 274.111 + -webkit-mask-image: -webkit-radial-gradient(circle, white, black); 274.112 + 274.113 + & .mdl-ripple { 274.114 + background: $icon-toggle-color; 274.115 + } 274.116 + 274.117 + .mdl-icon-toggle.is-disabled & { 274.118 + cursor: auto; 274.119 + } 274.120 + 274.121 + .mdl-icon-toggle.is-disabled & .mdl-ripple { 274.122 + background: transparent; 274.123 + } 274.124 +}
275.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 275.2 +++ b/style/mdl/layout/_layout.scss Sun Jul 15 14:07:29 2018 +0200 275.3 @@ -0,0 +1,662 @@ 275.4 +/** 275.5 + * Copyright 2015 Google Inc. All Rights Reserved. 275.6 + * 275.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 275.8 + * you may not use this file except in compliance with the License. 275.9 + * You may obtain a copy of the License at 275.10 + * 275.11 + * http://www.apache.org/licenses/LICENSE-2.0 275.12 + * 275.13 + * Unless required by applicable law or agreed to in writing, software 275.14 + * distributed under the License is distributed on an "AS IS" BASIS, 275.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 275.16 + * See the License for the specific language governing permissions and 275.17 + * limitations under the License. 275.18 + */ 275.19 + 275.20 +@import "../variables"; 275.21 +@import "../mixins"; 275.22 + 275.23 +// Navigation classes. Only used here for now, but we may at some point move 275.24 +// this to its own component. 275.25 +.mdl-navigation { 275.26 + display: flex; 275.27 + flex-wrap: nowrap; 275.28 + box-sizing: border-box; 275.29 +} 275.30 + 275.31 +.mdl-navigation__link { 275.32 + color: $layout-text-color; 275.33 + text-decoration: none; 275.34 + margin: 0; 275.35 + @include typo-body-1(true); 275.36 + 275.37 + // Align icons inside link with text 275.38 + & .material-icons { 275.39 + vertical-align: middle; 275.40 + } 275.41 +} 275.42 + 275.43 +// Main layout class. 275.44 +.mdl-layout { 275.45 + width: 100%; 275.46 + height: 100%; 275.47 + display: flex; 275.48 + flex-direction: column; 275.49 + overflow-y: auto; 275.50 + overflow-x: hidden; 275.51 + position: relative; 275.52 + -webkit-overflow-scrolling: touch; 275.53 +} 275.54 + 275.55 +// Utility classes for screen sizes. 275.56 +.mdl-layout.is-small-screen .mdl-layout--large-screen-only { 275.57 + display: none; 275.58 +} 275.59 + 275.60 +.mdl-layout:not(.is-small-screen) .mdl-layout--small-screen-only { 275.61 + display: none; 275.62 +} 275.63 + 275.64 +.mdl-layout__container { 275.65 + position: absolute; 275.66 + width: 100%; 275.67 + height: 100%; 275.68 +} 275.69 + 275.70 + 275.71 + // Optional utility classes for formatting special blocks in this component. 275.72 + .mdl-layout__title, 275.73 + .mdl-layout-title { 275.74 + display: block; 275.75 + position: relative; 275.76 + 275.77 + @include typo-title(); 275.78 + font-weight: 400; 275.79 + box-sizing: border-box; 275.80 + } 275.81 + 275.82 + .mdl-layout-spacer { 275.83 + flex-grow: 1; 275.84 + } 275.85 + 275.86 + 275.87 + // Drawer. 275.88 + .mdl-layout__drawer { 275.89 + display: flex; 275.90 + flex-direction: column; 275.91 + flex-wrap: nowrap; 275.92 + 275.93 + width: $layout-drawer-width; 275.94 + height: 100%; 275.95 + max-height: 100%; 275.96 + 275.97 + position: absolute; 275.98 + top: 0; 275.99 + left: 0; 275.100 + 275.101 + @include shadow-2dp(); 275.102 + 275.103 + box-sizing: border-box; 275.104 + border-right: 1px solid $layout-drawer-border-color; 275.105 + background: $layout-drawer-bg-color; 275.106 + 275.107 + // Transform offscreen. 275.108 + transform: translateX(-$layout-drawer-width - 10px); 275.109 + transform-style: preserve-3d; 275.110 + will-change: transform; 275.111 + 275.112 + @include material-animation-default(); 275.113 + transition-property: transform; 275.114 + 275.115 + color: $layout-text-color; 275.116 + 275.117 + overflow: visible; 275.118 + overflow-y: auto; 275.119 + 275.120 + z-index: 5; 275.121 + 275.122 + &.is-visible { 275.123 + transform: translateX(0); 275.124 + & ~ .mdl-layout__content.mdl-layout__content { 275.125 + overflow: hidden; 275.126 + } 275.127 + } 275.128 + 275.129 + & > * { 275.130 + flex-shrink: 0; 275.131 + } 275.132 + 275.133 + & > .mdl-layout__title, 275.134 + & > .mdl-layout-title { 275.135 + line-height: $layout-desktop-header-height; 275.136 + padding-left: $layout-header-desktop-indent; 275.137 + 275.138 + @media screen and (max-width: $layout-screen-size-threshold) { 275.139 + line-height: $layout-mobile-header-height; 275.140 + padding-left: $layout-header-mobile-indent; 275.141 + } 275.142 + } 275.143 + 275.144 + & .mdl-navigation { 275.145 + flex-direction: column; 275.146 + align-items: stretch; 275.147 + padding-top: 16px; 275.148 + 275.149 + & .mdl-navigation__link { 275.150 + display: block; 275.151 + flex-shrink: 0; 275.152 + padding: 16px $layout-header-desktop-indent; 275.153 + margin: 0; 275.154 + color: $layout-drawer-navigation-color; 275.155 + 275.156 + @media screen and (max-width: $layout-screen-size-threshold) { 275.157 + padding: 16px $layout-header-mobile-indent; 275.158 + } 275.159 + 275.160 + &:hover { 275.161 + background-color: $layout-nav-color; 275.162 + } 275.163 + 275.164 + &--current { 275.165 + background-color: $layout-drawer-navigation-link-active-background; 275.166 + color: $layout-drawer-navigation-link-active-color; 275.167 + } 275.168 + } 275.169 + } 275.170 + 275.171 + @media screen and (min-width: $layout-screen-size-threshold + 1px) { 275.172 + .mdl-layout--fixed-drawer > & { 275.173 + transform: translateX(0); 275.174 + } 275.175 + } 275.176 + } 275.177 + 275.178 + 275.179 + // Drawer button. 275.180 + // TODO(sgomes): Replace with an icon button when we have that component. 275.181 + .mdl-layout__drawer-button { 275.182 + display: block; 275.183 + 275.184 + position: absolute; 275.185 + height: $layout-drawer-button-desktop-size; 275.186 + width: $layout-drawer-button-desktop-size; 275.187 + border: 0; 275.188 + 275.189 + flex-shrink: 0; 275.190 + 275.191 + overflow: hidden; 275.192 + text-align: center; 275.193 + cursor: pointer; 275.194 + font-size: 26px; 275.195 + line-height: $layout-mobile-header-height; 275.196 + font-family: Helvetica, Arial, sans-serif; 275.197 + margin: ($layout-mobile-header-height - $layout-drawer-button-desktop-size) 12px; 275.198 + top: 0; 275.199 + left: 0; 275.200 + color: $layout-header-text-color; 275.201 + 275.202 + z-index: 4; 275.203 + 275.204 + .mdl-layout__header & { 275.205 + position: absolute; 275.206 + color: $layout-header-text-color; 275.207 + background-color: inherit; 275.208 + 275.209 + @media screen and (max-width: $layout-screen-size-threshold) { 275.210 + margin: 4px; 275.211 + } 275.212 + } 275.213 + 275.214 + @media screen and (max-width: $layout-screen-size-threshold) { 275.215 + margin: 4px; 275.216 + color: rgba(0, 0, 0, 0.5); 275.217 + } 275.218 + 275.219 + @media screen and (min-width: $layout-screen-size-threshold + 1px) { 275.220 + line-height: 54px; 275.221 + 275.222 + .mdl-layout--no-desktop-drawer-button &, 275.223 + .mdl-layout--fixed-drawer > &, 275.224 + .mdl-layout--no-drawer-button & { 275.225 + display: none; 275.226 + } 275.227 + } 275.228 + } 275.229 + 275.230 + .mdl-layout__header { 275.231 + display: flex; 275.232 + flex-direction: column; 275.233 + flex-wrap: nowrap; 275.234 + justify-content: flex-start; 275.235 + box-sizing: border-box; 275.236 + flex-shrink: 0; 275.237 + 275.238 + width: 100%; 275.239 + margin: 0; 275.240 + padding: 0; 275.241 + border: none; 275.242 + min-height: $layout-desktop-header-height; 275.243 + max-height: 1000px; 275.244 + z-index: 3; 275.245 + 275.246 + background-color: $layout-header-bg-color; 275.247 + color: $layout-header-text-color; 275.248 + 275.249 + @include shadow-2dp(); 275.250 + @include material-animation-default(); 275.251 + transition-property: max-height, box-shadow; 275.252 + 275.253 + @media screen and (max-width: $layout-screen-size-threshold) { 275.254 + min-height: $layout-mobile-header-height; 275.255 + } 275.256 + 275.257 + .mdl-layout--fixed-drawer.is-upgraded:not(.is-small-screen) > & { 275.258 + margin-left: $layout-drawer-width; 275.259 + width: calc(100% - #{$layout-drawer-width}); 275.260 + } 275.261 + 275.262 + @media screen and (min-width: $layout-screen-size-threshold + 1px) { 275.263 + .mdl-layout--fixed-drawer > & { 275.264 + .mdl-layout__header-row { 275.265 + padding-left: 40px; 275.266 + } 275.267 + } 275.268 + } 275.269 + 275.270 + & > .mdl-layout-icon { 275.271 + position: absolute; 275.272 + left: $layout-header-desktop-indent; 275.273 + top: ($layout-desktop-header-height - $layout-header-icon-size) / 2; 275.274 + height: $layout-header-icon-size; 275.275 + width: $layout-header-icon-size; 275.276 + overflow: hidden; 275.277 + z-index: 3; 275.278 + display: block; 275.279 + 275.280 + @media screen and (max-width: $layout-screen-size-threshold) { 275.281 + left: $layout-header-mobile-indent; 275.282 + top: ($layout-mobile-header-height - $layout-header-icon-size) / 2; 275.283 + } 275.284 + } 275.285 + 275.286 + .mdl-layout.has-drawer & > .mdl-layout-icon { 275.287 + display: none; 275.288 + } 275.289 + 275.290 + &.is-compact { 275.291 + max-height: $layout-desktop-header-height; 275.292 + 275.293 + @media screen and (max-width: $layout-screen-size-threshold) { 275.294 + max-height: $layout-mobile-header-height; 275.295 + } 275.296 + } 275.297 + 275.298 + &.is-compact.has-tabs { 275.299 + height: $layout-desktop-header-height + $layout-tab-bar-height; 275.300 + 275.301 + @media screen and (max-width: $layout-screen-size-threshold) { 275.302 + min-height: $layout-mobile-header-height + $layout-tab-bar-height; 275.303 + } 275.304 + } 275.305 + 275.306 + @media screen and (max-width: $layout-screen-size-threshold) { 275.307 + & { 275.308 + display: none; 275.309 + } 275.310 + 275.311 + .mdl-layout--fixed-header > & { 275.312 + display: flex; 275.313 + } 275.314 + } 275.315 + } 275.316 + 275.317 + .mdl-layout__header--transparent.mdl-layout__header--transparent { 275.318 + background-color: transparent; 275.319 + box-shadow: none; 275.320 + } 275.321 + 275.322 + .mdl-layout__header--seamed { 275.323 + box-shadow: none; 275.324 + } 275.325 + 275.326 + .mdl-layout__header--scroll { 275.327 + box-shadow: none; 275.328 + } 275.329 + 275.330 + .mdl-layout__header--waterfall { 275.331 + box-shadow: none; 275.332 + overflow: hidden; 275.333 + 275.334 + &.is-casting-shadow { 275.335 + @include shadow-2dp(); 275.336 + } 275.337 + 275.338 + &.mdl-layout__header--waterfall-hide-top { 275.339 + justify-content: flex-end; 275.340 + } 275.341 + } 275.342 + 275.343 + .mdl-layout__header-row { 275.344 + display: flex; 275.345 + flex-direction: row; 275.346 + flex-wrap: nowrap; 275.347 + flex-shrink: 0; 275.348 + box-sizing: border-box; 275.349 + align-self: stretch; 275.350 + align-items: center; 275.351 + height: $layout-header-desktop-row-height; 275.352 + margin: 0; 275.353 + padding: 0 $layout-header-desktop-indent 0 $layout-header-desktop-baseline; 275.354 + 275.355 + .mdl-layout--no-drawer-button & { 275.356 + padding-left: $layout-header-desktop-indent; 275.357 + } 275.358 + 275.359 + @media screen and (min-width: $layout-screen-size-threshold + 1px) { 275.360 + .mdl-layout--no-desktop-drawer-button & { 275.361 + padding-left: $layout-header-desktop-indent; 275.362 + } 275.363 + } 275.364 + 275.365 + @media screen and (max-width: $layout-screen-size-threshold) { 275.366 + height: $layout-header-mobile-row-height; 275.367 + padding: 0 $layout-header-mobile-indent 0 $layout-header-mobile-baseline; 275.368 + 275.369 + .mdl-layout--no-drawer-button & { 275.370 + padding-left: $layout-header-mobile-indent; 275.371 + } 275.372 + } 275.373 + 275.374 + & > * { 275.375 + flex-shrink: 0; 275.376 + } 275.377 + 275.378 + .mdl-layout__header--scroll & { 275.379 + width: 100%; 275.380 + } 275.381 + 275.382 + & .mdl-navigation { 275.383 + margin: 0; 275.384 + padding: 0; 275.385 + height: $layout-header-desktop-row-height; 275.386 + flex-direction: row; 275.387 + align-items: center; 275.388 + 275.389 + @media screen and (max-width: $layout-screen-size-threshold) { 275.390 + height: $layout-header-mobile-row-height; 275.391 + } 275.392 + } 275.393 + 275.394 + & .mdl-navigation__link { 275.395 + display: block; 275.396 + color: $layout-header-text-color; 275.397 + line-height: $layout-header-desktop-row-height; 275.398 + padding: 0 24px; 275.399 + 275.400 + @media screen and (max-width: $layout-screen-size-threshold) { 275.401 + line-height: $layout-header-mobile-row-height; 275.402 + padding: 0 $layout-header-mobile-indent; 275.403 + } 275.404 + } 275.405 + } 275.406 + 275.407 + // Obfuscator. 275.408 + .mdl-layout__obfuscator { 275.409 + background-color: transparent; 275.410 + position: absolute; 275.411 + top: 0; 275.412 + left: 0; 275.413 + height: 100%; 275.414 + width: 100%; 275.415 + z-index: 4; 275.416 + visibility: hidden; 275.417 + transition-property: background-color; 275.418 + @include material-animation-default(); 275.419 + 275.420 + &.is-visible { 275.421 + background-color: rgba(0, 0, 0, 0.5); 275.422 + visibility: visible; 275.423 + } 275.424 + 275.425 + @supports (pointer-events: auto) { 275.426 + background-color: rgba(0, 0, 0, 0.5); 275.427 + opacity: 0; 275.428 + transition-property: opacity; 275.429 + visibility: visible; 275.430 + pointer-events: none; 275.431 + &.is-visible { 275.432 + pointer-events: auto; 275.433 + opacity: 1; 275.434 + } 275.435 + } 275.436 + } 275.437 + 275.438 + 275.439 + // Content. 275.440 + .mdl-layout__content { 275.441 + // Fix IE10 bug. 275.442 + -ms-flex: 0 1 auto; 275.443 + 275.444 + position: relative; 275.445 + display: inline-block; 275.446 + overflow-y: auto; 275.447 + overflow-x: hidden; 275.448 + flex-grow: 1; 275.449 + z-index: 1; 275.450 + -webkit-overflow-scrolling: touch; 275.451 + 275.452 + .mdl-layout--fixed-drawer > & { 275.453 + margin-left: $layout-drawer-width; 275.454 + } 275.455 + 275.456 + .mdl-layout__container.has-scrolling-header & { 275.457 + overflow: visible; 275.458 + } 275.459 + 275.460 + @media screen and (max-width: $layout-screen-size-threshold) { 275.461 + .mdl-layout--fixed-drawer > & { 275.462 + margin-left: 0; 275.463 + } 275.464 + 275.465 + .mdl-layout__container.has-scrolling-header & { 275.466 + overflow-y: auto; 275.467 + overflow-x: hidden; 275.468 + } 275.469 + } 275.470 + } 275.471 + 275.472 + // Tabs. 275.473 + .mdl-layout__tab-bar { 275.474 + height: $layout-tab-bar-height * 2; 275.475 + margin: 0; 275.476 + width: calc(100% - 275.477 + #{(($layout-header-desktop-baseline - $layout-tab-desktop-padding) * 2)}); 275.478 + padding: 0 0 0 275.479 + ($layout-header-desktop-baseline - $layout-tab-desktop-padding); 275.480 + display: flex; 275.481 + background-color: $layout-header-bg-color; 275.482 + overflow-y: hidden; 275.483 + overflow-x: scroll; 275.484 + 275.485 + &::-webkit-scrollbar { 275.486 + display: none; 275.487 + } 275.488 + 275.489 + .mdl-layout--no-drawer-button & { 275.490 + padding-left: $layout-header-desktop-indent - $layout-tab-desktop-padding; 275.491 + width: calc(100% - 275.492 + #{(($layout-header-desktop-indent - $layout-tab-desktop-padding) * 2)}); 275.493 + } 275.494 + 275.495 + @media screen and (min-width: $layout-screen-size-threshold + 1px) { 275.496 + .mdl-layout--no-desktop-drawer-button & { 275.497 + padding-left: $layout-header-desktop-indent - $layout-tab-desktop-padding; 275.498 + width: calc(100% - 275.499 + #{(($layout-header-desktop-indent - $layout-tab-desktop-padding) * 2)}); 275.500 + } 275.501 + } 275.502 + 275.503 + @media screen and (max-width: $layout-screen-size-threshold) { 275.504 + width: calc(100% - 275.505 + #{($layout-header-mobile-baseline - $layout-tab-mobile-padding)}); 275.506 + padding: 0 0 0 275.507 + ($layout-header-mobile-baseline - $layout-tab-mobile-padding); 275.508 + 275.509 + .mdl-layout--no-drawer-button & { 275.510 + width: calc(100% - 275.511 + #{(($layout-header-mobile-indent - $layout-tab-mobile-padding) * 2)}); 275.512 + padding-left: $layout-header-mobile-indent - $layout-tab-mobile-padding; 275.513 + } 275.514 + } 275.515 + 275.516 + .mdl-layout--fixed-tabs & { 275.517 + padding: 0; 275.518 + overflow: hidden; 275.519 + width: 100%; 275.520 + } 275.521 + } 275.522 + 275.523 + .mdl-layout__tab-bar-container { 275.524 + position: relative; 275.525 + height: $layout-tab-bar-height; 275.526 + width: 100%; 275.527 + border: none; 275.528 + margin: 0; 275.529 + z-index: 2; 275.530 + flex-grow: 0; 275.531 + flex-shrink: 0; 275.532 + overflow: hidden; 275.533 + 275.534 + .mdl-layout__container > & { 275.535 + position: absolute; 275.536 + top: 0; 275.537 + left: 0; 275.538 + } 275.539 + } 275.540 + 275.541 + .mdl-layout__tab-bar-button { 275.542 + display: inline-block; 275.543 + position: absolute; 275.544 + top: 0; 275.545 + height: $layout-tab-bar-height; 275.546 + width: $layout-header-desktop-baseline - $layout-tab-desktop-padding; 275.547 + z-index: 4; 275.548 + text-align: center; 275.549 + background-color: $layout-header-bg-color; 275.550 + color: transparent; 275.551 + cursor: pointer; 275.552 + user-select: none; 275.553 + 275.554 + .mdl-layout--no-desktop-drawer-button &, 275.555 + .mdl-layout--no-drawer-button & { 275.556 + width: $layout-header-desktop-indent - $layout-tab-desktop-padding; 275.557 + 275.558 + & .material-icons { 275.559 + position: relative; 275.560 + left: ($layout-header-desktop-indent - $layout-tab-desktop-padding - 24px) / 2; 275.561 + } 275.562 + } 275.563 + 275.564 + @media screen and (max-width: $layout-screen-size-threshold) { 275.565 + width: $layout-header-mobile-baseline - $layout-tab-mobile-padding; 275.566 + } 275.567 + 275.568 + .mdl-layout--fixed-tabs & { 275.569 + display: none; 275.570 + } 275.571 + 275.572 + & .material-icons { 275.573 + line-height: $layout-tab-bar-height; 275.574 + } 275.575 + 275.576 + &.is-active { 275.577 + color: $layout-header-text-color; 275.578 + } 275.579 + } 275.580 + 275.581 + .mdl-layout__tab-bar-left-button { 275.582 + left: 0; 275.583 + } 275.584 + 275.585 + .mdl-layout__tab-bar-right-button { 275.586 + right: 0; 275.587 + } 275.588 + 275.589 + .mdl-layout__tab { 275.590 + margin: 0; 275.591 + border: none; 275.592 + padding: 0 $layout-tab-desktop-padding 0 $layout-tab-desktop-padding; 275.593 + 275.594 + float: left; 275.595 + position: relative; 275.596 + display: block; 275.597 + flex-grow: 0; 275.598 + flex-shrink: 0; 275.599 + 275.600 + text-decoration: none; 275.601 + height: $layout-tab-bar-height; 275.602 + line-height: $layout-tab-bar-height; 275.603 + 275.604 + text-align: center; 275.605 + font-weight: 500; 275.606 + font-size: $layout-tab-font-size; 275.607 + text-transform: uppercase; 275.608 + 275.609 + color: $layout-header-tab-text-color; 275.610 + overflow: hidden; 275.611 + 275.612 + @media screen and (max-width: $layout-screen-size-threshold) { 275.613 + padding: 0 $layout-tab-mobile-padding 0 $layout-tab-mobile-padding; 275.614 + } 275.615 + 275.616 + .mdl-layout--fixed-tabs & { 275.617 + float: none; 275.618 + flex-grow: 1; 275.619 + padding: 0; 275.620 + } 275.621 + 275.622 + .mdl-layout.is-upgraded &.is-active { 275.623 + color: $layout-header-text-color; 275.624 + } 275.625 + 275.626 + .mdl-layout.is-upgraded &.is-active::after { 275.627 + height: $layout-tab-highlight-thickness; 275.628 + width: 100%; 275.629 + display: block; 275.630 + content: " "; 275.631 + bottom: 0; 275.632 + left: 0; 275.633 + position: absolute; 275.634 + background: $layout-header-tab-highlight; 275.635 + animation: border-expand 0.2s cubic-bezier(0.4, 0.0, 0.4, 1) 0.01s alternate forwards; 275.636 + transition: all 1s cubic-bezier(0.4, 0.0, 1, 1); 275.637 + } 275.638 + 275.639 + & .mdl-layout__tab-ripple-container { 275.640 + display: block; 275.641 + position: absolute; 275.642 + height: 100%; 275.643 + width: 100%; 275.644 + left: 0; 275.645 + top: 0; 275.646 + z-index: 1; 275.647 + overflow: hidden; 275.648 + 275.649 + & .mdl-ripple { 275.650 + background-color: $layout-header-text-color; 275.651 + } 275.652 + } 275.653 + } 275.654 + 275.655 + .mdl-layout__tab-panel { 275.656 + display: block; 275.657 + 275.658 + .mdl-layout.is-upgraded & { 275.659 + display: none; 275.660 + } 275.661 + 275.662 + .mdl-layout.is-upgraded &.is-active { 275.663 + display: block; 275.664 + } 275.665 + }
276.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 276.2 +++ b/style/mdl/list/_list.scss Sun Jul 15 14:07:29 2018 +0200 276.3 @@ -0,0 +1,157 @@ 276.4 +/** 276.5 + * Copyright 2015 Google Inc. All Rights Reserved. 276.6 + * 276.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 276.8 + * you may not use this file except in compliance with the License. 276.9 + * You may obtain a copy of the License at 276.10 + * 276.11 + * http://www.apache.org/licenses/LICENSE-2.0 276.12 + * 276.13 + * Unless required by applicable law or agreed to in writing, software 276.14 + * distributed under the License is distributed on an "AS IS" BASIS, 276.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 276.16 + * See the License for the specific language governing permissions and 276.17 + * limitations under the License. 276.18 + */ 276.19 + 276.20 +@import "../variables"; 276.21 +@import "../mixins"; 276.22 + 276.23 + 276.24 +.mdl-list { 276.25 + display: block; 276.26 + padding: $list-border 0; 276.27 + list-style: none; 276.28 +} 276.29 + 276.30 +.mdl-list__item { 276.31 + @include typo-subhead(); 276.32 + line-height: 1; 276.33 + display: flex; 276.34 + min-height: $list-min-height; 276.35 + box-sizing: border-box; 276.36 + flex-direction: row; 276.37 + flex-wrap: nowrap; 276.38 + align-items: center; 276.39 + padding: $list-min-padding; 276.40 + cursor: default; 276.41 + color: $list-main-text-text-color; 276.42 + overflow: hidden; 276.43 + 276.44 + & .mdl-list__item-primary-content { 276.45 + order: 0; 276.46 + flex-grow: 2; 276.47 + text-decoration: none; 276.48 + box-sizing: border-box; 276.49 + display: flex; 276.50 + align-items: center; 276.51 + 276.52 + & .mdl-list__item-icon { 276.53 + margin-right: $list-icon-text-left-distance - $list-icon-size - $list-min-padding; 276.54 + } 276.55 + 276.56 + & .mdl-list__item-avatar { 276.57 + margin-right: $list-avatar-text-left-distance - $list-avatar-size - $list-min-padding; 276.58 + } 276.59 + } 276.60 + 276.61 + & .mdl-list__item-secondary-content { 276.62 + display: flex; 276.63 + flex-flow: column; 276.64 + align-items: flex-end; 276.65 + margin-left: $list-min-padding; 276.66 + 276.67 + & .mdl-list__item-secondary-action label { display: inline; } 276.68 + & .mdl-list__item-secondary-info { 276.69 + @include typo-caption(); 276.70 + color: $list-supporting-text-text-color; 276.71 + } 276.72 + & .mdl-list__item-sub-header { 276.73 + padding: 0 0 0 $list-min-padding; 276.74 + } 276.75 + } 276.76 +} 276.77 + 276.78 +.mdl-list__item-icon, 276.79 +.mdl-list__item-icon.material-icons { 276.80 + height: $list-icon-size; 276.81 + width: $list-icon-size; 276.82 + font-size: $list-icon-size; 276.83 + box-sizing: border-box; 276.84 + color: $list-icon-color; 276.85 +} 276.86 + 276.87 +.mdl-list__item-avatar, 276.88 +.mdl-list__item-avatar.material-icons { 276.89 + height: $list-avatar-size; 276.90 + width: $list-avatar-size; 276.91 + box-sizing: border-box; 276.92 + border-radius: 50%; 276.93 + // Set a background colour in case the user doesn't provide an image. 276.94 + background-color: $list-icon-color; 276.95 + // Set a font size and color in case the user provides a Material Icon. 276.96 + font-size: $list-avatar-size; 276.97 + color: $list-avatar-color; 276.98 +} 276.99 + 276.100 +.mdl-list__item--two-line { 276.101 + height: $list-two-line-height; 276.102 + 276.103 + & .mdl-list__item-primary-content { 276.104 + height: $list-two-line-height - $list-min-padding - $list-bottom-padding; 276.105 + line-height: 20px; 276.106 + display: block; 276.107 + 276.108 + & .mdl-list__item-avatar{ 276.109 + float: left; 276.110 + } 276.111 + 276.112 + & .mdl-list__item-icon { 276.113 + float: left; 276.114 + // Icons are aligned to center of text in a two line list. 276.115 + margin-top: 276.116 + ($list-two-line-height - $list-min-padding - $list-bottom-padding - 276.117 + $list-icon-size) / 2; 276.118 + } 276.119 + 276.120 + & .mdl-list__item-secondary-content { 276.121 + height: $list-two-line-height - $list-min-padding - $list-bottom-padding; 276.122 + } 276.123 + 276.124 + & .mdl-list__item-sub-title { 276.125 + @include typo-body-1(); 276.126 + line-height: 18px; 276.127 + color: $list-supporting-text-text-color; 276.128 + display: block; 276.129 + padding: 0; 276.130 + } 276.131 + } 276.132 +} 276.133 + 276.134 +.mdl-list__item--three-line { 276.135 + height: $list-three-line-height; 276.136 + 276.137 + & .mdl-list__item-primary-content { 276.138 + height: $list-three-line-height - $list-min-padding - $list-bottom-padding; 276.139 + line-height: 20px; 276.140 + display: block; 276.141 + 276.142 + & .mdl-list__item-avatar, 276.143 + & .mdl-list__item-icon { 276.144 + float: left; 276.145 + } 276.146 + } 276.147 + 276.148 + & .mdl-list__item-secondary-content { 276.149 + height: $list-three-line-height - $list-min-padding - $list-bottom-padding; 276.150 + } 276.151 + 276.152 + & .mdl-list__item-text-body { 276.153 + @include typo-body-1(); 276.154 + line-height: 18px; 276.155 + height: $list-three-line-height - $list-min-padding - $list-bottom-padding; 276.156 + color: $list-supporting-text-text-color; 276.157 + display: block; 276.158 + padding: 0; 276.159 + } 276.160 +}
277.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 277.2 +++ b/style/mdl/material-design-lite-grid.scss Sun Jul 15 14:07:29 2018 +0200 277.3 @@ -0,0 +1,21 @@ 277.4 +/** 277.5 + * Copyright 2015 Google Inc. All Rights Reserved. 277.6 + * 277.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 277.8 + * you may not use this file except in compliance with the License. 277.9 + * You may obtain a copy of the License at 277.10 + * 277.11 + * http://www.apache.org/licenses/LICENSE-2.0 277.12 + * 277.13 + * Unless required by applicable law or agreed to in writing, software 277.14 + * distributed under the License is distributed on an "AS IS" BASIS, 277.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 277.16 + * See the License for the specific language governing permissions and 277.17 + * limitations under the License. 277.18 + */ 277.19 + 277.20 +/* Material Design Lite Grid*/ 277.21 + 277.22 +@import "variables"; 277.23 +@import "mixins"; 277.24 +@import "grid/grid"; 277.25 \ No newline at end of file
278.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 278.2 +++ b/style/mdl/material-design-lite.scss Sun Jul 15 14:07:29 2018 +0200 278.3 @@ -0,0 +1,54 @@ 278.4 +/** 278.5 + * Copyright 2015 Google Inc. All Rights Reserved. 278.6 + * 278.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 278.8 + * you may not use this file except in compliance with the License. 278.9 + * You may obtain a copy of the License at 278.10 + * 278.11 + * http://www.apache.org/licenses/LICENSE-2.0 278.12 + * 278.13 + * Unless required by applicable law or agreed to in writing, software 278.14 + * distributed under the License is distributed on an "AS IS" BASIS, 278.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 278.16 + * See the License for the specific language governing permissions and 278.17 + * limitations under the License. 278.18 + */ 278.19 + 278.20 +/* Material Design Lite */ 278.21 + 278.22 +// Variables and mixins 278.23 +@import "variables"; 278.24 +@import "mixins"; 278.25 + 278.26 +// Resets and dependencies 278.27 +@import "resets/resets"; 278.28 +@import "typography/typography"; 278.29 + 278.30 +// Components 278.31 +@import "palette/palette"; 278.32 +@import "ripple/ripple"; 278.33 +@import "animation/animation"; 278.34 +@import "badge/badge"; 278.35 +@import "button/button"; 278.36 +@import "card/card"; 278.37 +@import "checkbox/checkbox"; 278.38 +@import "chip/chip"; 278.39 +@import "data-table/data-table"; 278.40 +@import "dialog/dialog"; 278.41 +@import "footer/mega_footer"; 278.42 +@import "footer/mini_footer"; 278.43 +@import "icon-toggle/icon-toggle"; 278.44 +@import "list/list"; 278.45 +@import "menu/menu"; 278.46 +@import "progress/progress"; 278.47 +@import "layout/layout"; 278.48 +@import "radio/radio"; 278.49 +@import "slider/slider"; 278.50 +@import "snackbar/snackbar"; 278.51 +@import "spinner/spinner"; 278.52 +@import "switch/switch"; 278.53 +@import "tabs/tabs"; 278.54 +@import "textfield/textfield"; 278.55 +@import "tooltip/tooltip"; 278.56 +@import "shadow/shadow"; 278.57 +@import "grid/grid";
279.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 279.2 +++ b/style/mdl/menu/_menu.scss Sun Jul 15 14:07:29 2018 +0200 279.3 @@ -0,0 +1,200 @@ 279.4 +/** 279.5 + * Copyright 2015 Google Inc. All Rights Reserved. 279.6 + * 279.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 279.8 + * you may not use this file except in compliance with the License. 279.9 + * You may obtain a copy of the License at 279.10 + * 279.11 + * http://www.apache.org/licenses/LICENSE-2.0 279.12 + * 279.13 + * Unless required by applicable law or agreed to in writing, software 279.14 + * distributed under the License is distributed on an "AS IS" BASIS, 279.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 279.16 + * See the License for the specific language governing permissions and 279.17 + * limitations under the License. 279.18 + */ 279.19 + 279.20 +@import "../variables"; 279.21 +@import "../mixins"; 279.22 + 279.23 +.mdl-menu__container { 279.24 + display: block; 279.25 + margin: 0; 279.26 + padding: 0; 279.27 + border: none; 279.28 + position: absolute; 279.29 + overflow: visible; 279.30 + height: 0; 279.31 + width: 0; 279.32 + visibility: hidden; 279.33 + z-index: -1; 279.34 + 279.35 + &.is-visible, 279.36 + &.is-animating { 279.37 + z-index: 999; 279.38 + visibility: visible; 279.39 + } 279.40 +} 279.41 + 279.42 +.mdl-menu__outline { 279.43 + display: block; 279.44 + background: $default-dropdown-bg-color; 279.45 + margin: 0; 279.46 + padding: 0; 279.47 + border: none; 279.48 + border-radius: 2px; 279.49 + position: absolute; 279.50 + top: 0; 279.51 + left: 0; 279.52 + overflow: hidden; 279.53 + opacity: 0; 279.54 + transform: scale(0); 279.55 + transform-origin: 0 0; 279.56 + @include shadow-2dp(); 279.57 + will-change: transform; 279.58 + transition: transform $menu-expand-duration $animation-curve-default, 279.59 + opacity $menu-fade-duration $animation-curve-default; 279.60 + z-index: -1; 279.61 + 279.62 + .mdl-menu__container.is-visible & { 279.63 + opacity: 1; 279.64 + transform: scale(1); 279.65 + z-index: 999; 279.66 + } 279.67 + 279.68 + &.mdl-menu--bottom-right { 279.69 + transform-origin: 100% 0; 279.70 + } 279.71 + 279.72 + &.mdl-menu--top-left { 279.73 + transform-origin: 0 100%; 279.74 + } 279.75 + 279.76 + &.mdl-menu--top-right { 279.77 + transform-origin: 100% 100%; 279.78 + } 279.79 +} 279.80 + 279.81 +.mdl-menu { 279.82 + position: absolute; 279.83 + list-style: none; 279.84 + top: 0; 279.85 + left: 0; 279.86 + height: auto; 279.87 + width: auto; 279.88 + min-width: 124px; 279.89 + padding: 8px 0; 279.90 + margin: 0; 279.91 + opacity: 0; 279.92 + clip: rect(0 0 0 0); 279.93 + z-index: -1; 279.94 + 279.95 + .mdl-menu__container.is-visible & { 279.96 + opacity: 1; 279.97 + z-index: 999; 279.98 + } 279.99 + 279.100 + &.is-animating { 279.101 + transition: opacity $menu-fade-duration $animation-curve-default, 279.102 + clip $menu-expand-duration $animation-curve-default; 279.103 + } 279.104 + 279.105 + &.mdl-menu--bottom-right { 279.106 + left: auto; 279.107 + right: 0; 279.108 + } 279.109 + 279.110 + &.mdl-menu--top-left { 279.111 + top: auto; 279.112 + bottom: 0; 279.113 + } 279.114 + 279.115 + &.mdl-menu--top-right { 279.116 + top: auto; 279.117 + left: auto; 279.118 + bottom: 0; 279.119 + right: 0; 279.120 + } 279.121 + 279.122 + &.mdl-menu--unaligned { 279.123 + top: auto; 279.124 + left: auto; 279.125 + } 279.126 +} 279.127 + 279.128 +.mdl-menu__item { 279.129 + display: block; 279.130 + border: none; 279.131 + color: $default-item-text-color; 279.132 + background-color: transparent; 279.133 + text-align: left; 279.134 + margin: 0; 279.135 + padding: 0 16px; 279.136 + outline-color: $default-item-outline-color; 279.137 + position: relative; 279.138 + overflow: hidden; 279.139 + @include typo-body-1(); 279.140 + text-decoration: none; 279.141 + cursor: pointer; 279.142 + height: 48px; 279.143 + line-height: 48px; 279.144 + white-space: nowrap; 279.145 + opacity: 0; 279.146 + transition: opacity $menu-fade-duration $animation-curve-default; 279.147 + user-select: none; 279.148 + 279.149 + .mdl-menu__container.is-visible & { 279.150 + opacity: 1; 279.151 + } 279.152 + 279.153 + &::-moz-focus-inner { 279.154 + border: 0; 279.155 + } 279.156 + 279.157 + &--full-bleed-divider { 279.158 + border-bottom: 1px solid $default-item-divider-color; 279.159 + } 279.160 + 279.161 + &[disabled], &[data-mdl-disabled] { 279.162 + color: $disabled-item-text-color; 279.163 + background-color: transparent; 279.164 + cursor: auto; 279.165 + 279.166 + &:hover { 279.167 + background-color: transparent; 279.168 + } 279.169 + 279.170 + &:focus { 279.171 + background-color: transparent; 279.172 + } 279.173 + 279.174 + & .mdl-ripple { 279.175 + background: transparent; 279.176 + } 279.177 + } 279.178 + 279.179 + &:hover { 279.180 + background-color: $default-item-hover-bg-color; 279.181 + } 279.182 + 279.183 + &:focus { 279.184 + outline: none; 279.185 + background-color: $default-item-focus-bg-color; 279.186 + } 279.187 + 279.188 + &:active { 279.189 + background-color: $default-item-active-bg-color; 279.190 + } 279.191 +} 279.192 + 279.193 + 279.194 +.mdl-menu__item--ripple-container { 279.195 + display: block; 279.196 + height: 100%; 279.197 + left: 0px; 279.198 + position: absolute; 279.199 + top: 0px; 279.200 + width: 100%; 279.201 + z-index: 0; 279.202 + overflow: hidden; 279.203 +}
280.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 280.2 +++ b/style/mdl/palette/_palette.scss Sun Jul 15 14:07:29 2018 +0200 280.3 @@ -0,0 +1,2303 @@ 280.4 +/** 280.5 + * Copyright 2015 Google Inc. All Rights Reserved. 280.6 + * 280.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 280.8 + * you may not use this file except in compliance with the License. 280.9 + * You may obtain a copy of the License at 280.10 + * 280.11 + * http://www.apache.org/licenses/LICENSE-2.0 280.12 + * 280.13 + * Unless required by applicable law or agreed to in writing, software 280.14 + * distributed under the License is distributed on an "AS IS" BASIS, 280.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 280.16 + * See the License for the specific language governing permissions and 280.17 + * limitations under the License. 280.18 + */ 280.19 + 280.20 +@import "../variables"; 280.21 + 280.22 +@if $trim-color-classes == false { 280.23 + // Red 280.24 + 280.25 + .mdl-color-text--red { 280.26 + color: unquote("rgb(#{$palette-red-500})") !important; 280.27 + } 280.28 + 280.29 + .mdl-color--red { 280.30 + background-color: unquote("rgb(#{$palette-red-500})") !important; 280.31 + } 280.32 + 280.33 + .mdl-color-text--red-50 { 280.34 + color: unquote("rgb(#{$palette-red-50})") !important; 280.35 + } 280.36 + 280.37 + .mdl-color--red-50 { 280.38 + background-color: unquote("rgb(#{$palette-red-50})") !important; 280.39 + } 280.40 + 280.41 + .mdl-color-text--red-100 { 280.42 + color: unquote("rgb(#{$palette-red-100})") !important; 280.43 + } 280.44 + 280.45 + .mdl-color--red-100 { 280.46 + background-color: unquote("rgb(#{$palette-red-100})") !important; 280.47 + } 280.48 + 280.49 + .mdl-color-text--red-200 { 280.50 + color: unquote("rgb(#{$palette-red-200})") !important; 280.51 + } 280.52 + 280.53 + .mdl-color--red-200 { 280.54 + background-color: unquote("rgb(#{$palette-red-200})") !important; 280.55 + } 280.56 + 280.57 + .mdl-color-text--red-300 { 280.58 + color: unquote("rgb(#{$palette-red-300})") !important; 280.59 + } 280.60 + 280.61 + .mdl-color--red-300 { 280.62 + background-color: unquote("rgb(#{$palette-red-300})") !important; 280.63 + } 280.64 + 280.65 + .mdl-color-text--red-400 { 280.66 + color: unquote("rgb(#{$palette-red-400})") !important; 280.67 + } 280.68 + 280.69 + .mdl-color--red-400 { 280.70 + background-color: unquote("rgb(#{$palette-red-400})") !important; 280.71 + } 280.72 + 280.73 + .mdl-color-text--red-500 { 280.74 + color: unquote("rgb(#{$palette-red-500})") !important; 280.75 + } 280.76 + 280.77 + .mdl-color--red-500 { 280.78 + background-color: unquote("rgb(#{$palette-red-500})") !important; 280.79 + } 280.80 + 280.81 + .mdl-color-text--red-600 { 280.82 + color: unquote("rgb(#{$palette-red-600})") !important; 280.83 + } 280.84 + 280.85 + .mdl-color--red-600 { 280.86 + background-color: unquote("rgb(#{$palette-red-600})") !important; 280.87 + } 280.88 + 280.89 + .mdl-color-text--red-700 { 280.90 + color: unquote("rgb(#{$palette-red-700})") !important; 280.91 + } 280.92 + 280.93 + .mdl-color--red-700 { 280.94 + background-color: unquote("rgb(#{$palette-red-700})") !important; 280.95 + } 280.96 + 280.97 + .mdl-color-text--red-800 { 280.98 + color: unquote("rgb(#{$palette-red-800})") !important; 280.99 + } 280.100 + 280.101 + .mdl-color--red-800 { 280.102 + background-color: unquote("rgb(#{$palette-red-800})") !important; 280.103 + } 280.104 + 280.105 + .mdl-color-text--red-900 { 280.106 + color: unquote("rgb(#{$palette-red-900})") !important; 280.107 + } 280.108 + 280.109 + .mdl-color--red-900 { 280.110 + background-color: unquote("rgb(#{$palette-red-900})") !important; 280.111 + } 280.112 + 280.113 + .mdl-color-text--red-A100 { 280.114 + color: unquote("rgb(#{$palette-red-A100})") !important; 280.115 + } 280.116 + 280.117 + .mdl-color--red-A100 { 280.118 + background-color: unquote("rgb(#{$palette-red-A100})") !important; 280.119 + } 280.120 + 280.121 + .mdl-color-text--red-A200 { 280.122 + color: unquote("rgb(#{$palette-red-A200})") !important; 280.123 + } 280.124 + 280.125 + .mdl-color--red-A200 { 280.126 + background-color: unquote("rgb(#{$palette-red-A200})") !important; 280.127 + } 280.128 + 280.129 + .mdl-color-text--red-A400 { 280.130 + color: unquote("rgb(#{$palette-red-A400})") !important; 280.131 + } 280.132 + 280.133 + .mdl-color--red-A400 { 280.134 + background-color: unquote("rgb(#{$palette-red-A400})") !important; 280.135 + } 280.136 + 280.137 + .mdl-color-text--red-A700 { 280.138 + color: unquote("rgb(#{$palette-red-A700})") !important; 280.139 + } 280.140 + 280.141 + .mdl-color--red-A700 { 280.142 + background-color: unquote("rgb(#{$palette-red-A700})") !important; 280.143 + } 280.144 + 280.145 + // Pink 280.146 + 280.147 + .mdl-color-text--pink { 280.148 + color: unquote("rgb(#{$palette-pink-500})") !important; 280.149 + } 280.150 + 280.151 + .mdl-color--pink { 280.152 + background-color: unquote("rgb(#{$palette-pink-500})") !important; 280.153 + } 280.154 + 280.155 + .mdl-color-text--pink-50 { 280.156 + color: unquote("rgb(#{$palette-pink-50})") !important; 280.157 + } 280.158 + 280.159 + .mdl-color--pink-50 { 280.160 + background-color: unquote("rgb(#{$palette-pink-50})") !important; 280.161 + } 280.162 + 280.163 + .mdl-color-text--pink-100 { 280.164 + color: unquote("rgb(#{$palette-pink-100})") !important; 280.165 + } 280.166 + 280.167 + .mdl-color--pink-100 { 280.168 + background-color: unquote("rgb(#{$palette-pink-100})") !important; 280.169 + } 280.170 + 280.171 + .mdl-color-text--pink-200 { 280.172 + color: unquote("rgb(#{$palette-pink-200})") !important; 280.173 + } 280.174 + 280.175 + .mdl-color--pink-200 { 280.176 + background-color: unquote("rgb(#{$palette-pink-200})") !important; 280.177 + } 280.178 + 280.179 + .mdl-color-text--pink-300 { 280.180 + color: unquote("rgb(#{$palette-pink-300})") !important; 280.181 + } 280.182 + 280.183 + .mdl-color--pink-300 { 280.184 + background-color: unquote("rgb(#{$palette-pink-300})") !important; 280.185 + } 280.186 + 280.187 + .mdl-color-text--pink-400 { 280.188 + color: unquote("rgb(#{$palette-pink-400})") !important; 280.189 + } 280.190 + 280.191 + .mdl-color--pink-400 { 280.192 + background-color: unquote("rgb(#{$palette-pink-400})") !important; 280.193 + } 280.194 + 280.195 + .mdl-color-text--pink-500 { 280.196 + color: unquote("rgb(#{$palette-pink-500})") !important; 280.197 + } 280.198 + 280.199 + .mdl-color--pink-500 { 280.200 + background-color: unquote("rgb(#{$palette-pink-500})") !important; 280.201 + } 280.202 + 280.203 + .mdl-color-text--pink-600 { 280.204 + color: unquote("rgb(#{$palette-pink-600})") !important; 280.205 + } 280.206 + 280.207 + .mdl-color--pink-600 { 280.208 + background-color: unquote("rgb(#{$palette-pink-600})") !important; 280.209 + } 280.210 + 280.211 + .mdl-color-text--pink-700 { 280.212 + color: unquote("rgb(#{$palette-pink-700})") !important; 280.213 + } 280.214 + 280.215 + .mdl-color--pink-700 { 280.216 + background-color: unquote("rgb(#{$palette-pink-700})") !important; 280.217 + } 280.218 + 280.219 + .mdl-color-text--pink-800 { 280.220 + color: unquote("rgb(#{$palette-pink-800})") !important; 280.221 + } 280.222 + 280.223 + .mdl-color--pink-800 { 280.224 + background-color: unquote("rgb(#{$palette-pink-800})") !important; 280.225 + } 280.226 + 280.227 + .mdl-color-text--pink-900 { 280.228 + color: unquote("rgb(#{$palette-pink-900})") !important; 280.229 + } 280.230 + 280.231 + .mdl-color--pink-900 { 280.232 + background-color: unquote("rgb(#{$palette-pink-900})") !important; 280.233 + } 280.234 + 280.235 + .mdl-color-text--pink-A100 { 280.236 + color: unquote("rgb(#{$palette-pink-A100})") !important; 280.237 + } 280.238 + 280.239 + .mdl-color--pink-A100 { 280.240 + background-color: unquote("rgb(#{$palette-pink-A100})") !important; 280.241 + } 280.242 + 280.243 + .mdl-color-text--pink-A200 { 280.244 + color: unquote("rgb(#{$palette-pink-A200})") !important; 280.245 + } 280.246 + 280.247 + .mdl-color--pink-A200 { 280.248 + background-color: unquote("rgb(#{$palette-pink-A200})") !important; 280.249 + } 280.250 + 280.251 + .mdl-color-text--pink-A400 { 280.252 + color: unquote("rgb(#{$palette-pink-A400})") !important; 280.253 + } 280.254 + 280.255 + .mdl-color--pink-A400 { 280.256 + background-color: unquote("rgb(#{$palette-pink-A400})") !important; 280.257 + } 280.258 + 280.259 + .mdl-color-text--pink-A700 { 280.260 + color: unquote("rgb(#{$palette-pink-A700})") !important; 280.261 + } 280.262 + 280.263 + .mdl-color--pink-A700 { 280.264 + background-color: unquote("rgb(#{$palette-pink-A700})") !important; 280.265 + } 280.266 + 280.267 + // Purple 280.268 + 280.269 + .mdl-color-text--purple { 280.270 + color: unquote("rgb(#{$palette-purple-500})") !important; 280.271 + } 280.272 + 280.273 + .mdl-color--purple { 280.274 + background-color: unquote("rgb(#{$palette-purple-500})") !important; 280.275 + } 280.276 + 280.277 + .mdl-color-text--purple-50 { 280.278 + color: unquote("rgb(#{$palette-purple-50})") !important; 280.279 + } 280.280 + 280.281 + .mdl-color--purple-50 { 280.282 + background-color: unquote("rgb(#{$palette-purple-50})") !important; 280.283 + } 280.284 + 280.285 + .mdl-color-text--purple-100 { 280.286 + color: unquote("rgb(#{$palette-purple-100})") !important; 280.287 + } 280.288 + 280.289 + .mdl-color--purple-100 { 280.290 + background-color: unquote("rgb(#{$palette-purple-100})") !important; 280.291 + } 280.292 + 280.293 + .mdl-color-text--purple-200 { 280.294 + color: unquote("rgb(#{$palette-purple-200})") !important; 280.295 + } 280.296 + 280.297 + .mdl-color--purple-200 { 280.298 + background-color: unquote("rgb(#{$palette-purple-200})") !important; 280.299 + } 280.300 + 280.301 + .mdl-color-text--purple-300 { 280.302 + color: unquote("rgb(#{$palette-purple-300})") !important; 280.303 + } 280.304 + 280.305 + .mdl-color--purple-300 { 280.306 + background-color: unquote("rgb(#{$palette-purple-300})") !important; 280.307 + } 280.308 + 280.309 + .mdl-color-text--purple-400 { 280.310 + color: unquote("rgb(#{$palette-purple-400})") !important; 280.311 + } 280.312 + 280.313 + .mdl-color--purple-400 { 280.314 + background-color: unquote("rgb(#{$palette-purple-400})") !important; 280.315 + } 280.316 + 280.317 + .mdl-color-text--purple-500 { 280.318 + color: unquote("rgb(#{$palette-purple-500})") !important; 280.319 + } 280.320 + 280.321 + .mdl-color--purple-500 { 280.322 + background-color: unquote("rgb(#{$palette-purple-500})") !important; 280.323 + } 280.324 + 280.325 + .mdl-color-text--purple-600 { 280.326 + color: unquote("rgb(#{$palette-purple-600})") !important; 280.327 + } 280.328 + 280.329 + .mdl-color--purple-600 { 280.330 + background-color: unquote("rgb(#{$palette-purple-600})") !important; 280.331 + } 280.332 + 280.333 + .mdl-color-text--purple-700 { 280.334 + color: unquote("rgb(#{$palette-purple-700})") !important; 280.335 + } 280.336 + 280.337 + .mdl-color--purple-700 { 280.338 + background-color: unquote("rgb(#{$palette-purple-700})") !important; 280.339 + } 280.340 + 280.341 + .mdl-color-text--purple-800 { 280.342 + color: unquote("rgb(#{$palette-purple-800})") !important; 280.343 + } 280.344 + 280.345 + .mdl-color--purple-800 { 280.346 + background-color: unquote("rgb(#{$palette-purple-800})") !important; 280.347 + } 280.348 + 280.349 + .mdl-color-text--purple-900 { 280.350 + color: unquote("rgb(#{$palette-purple-900})") !important; 280.351 + } 280.352 + 280.353 + .mdl-color--purple-900 { 280.354 + background-color: unquote("rgb(#{$palette-purple-900})") !important; 280.355 + } 280.356 + 280.357 + .mdl-color-text--purple-A100 { 280.358 + color: unquote("rgb(#{$palette-purple-A100})") !important; 280.359 + } 280.360 + 280.361 + .mdl-color--purple-A100 { 280.362 + background-color: unquote("rgb(#{$palette-purple-A100})") !important; 280.363 + } 280.364 + 280.365 + .mdl-color-text--purple-A200 { 280.366 + color: unquote("rgb(#{$palette-purple-A200})") !important; 280.367 + } 280.368 + 280.369 + .mdl-color--purple-A200 { 280.370 + background-color: unquote("rgb(#{$palette-purple-A200})") !important; 280.371 + } 280.372 + 280.373 + .mdl-color-text--purple-A400 { 280.374 + color: unquote("rgb(#{$palette-purple-A400})") !important; 280.375 + } 280.376 + 280.377 + .mdl-color--purple-A400 { 280.378 + background-color: unquote("rgb(#{$palette-purple-A400})") !important; 280.379 + } 280.380 + 280.381 + .mdl-color-text--purple-A700 { 280.382 + color: unquote("rgb(#{$palette-purple-A700})") !important; 280.383 + } 280.384 + 280.385 + .mdl-color--purple-A700 { 280.386 + background-color: unquote("rgb(#{$palette-purple-A700})") !important; 280.387 + } 280.388 + 280.389 + // Deep Purple. 280.390 + 280.391 + .mdl-color-text--deep-purple { 280.392 + color: unquote("rgb(#{$palette-deep-purple-500})") !important; 280.393 + } 280.394 + 280.395 + .mdl-color--deep-purple { 280.396 + background-color: unquote("rgb(#{$palette-deep-purple-500})") !important; 280.397 + } 280.398 + 280.399 + .mdl-color-text--deep-purple-50 { 280.400 + color: unquote("rgb(#{$palette-deep-purple-50})") !important; 280.401 + } 280.402 + 280.403 + .mdl-color--deep-purple-50 { 280.404 + background-color: unquote("rgb(#{$palette-deep-purple-50})") !important; 280.405 + } 280.406 + 280.407 + .mdl-color-text--deep-purple-100 { 280.408 + color: unquote("rgb(#{$palette-deep-purple-100})") !important; 280.409 + } 280.410 + 280.411 + .mdl-color--deep-purple-100 { 280.412 + background-color: unquote("rgb(#{$palette-deep-purple-100})") !important; 280.413 + } 280.414 + 280.415 + .mdl-color-text--deep-purple-200 { 280.416 + color: unquote("rgb(#{$palette-deep-purple-200})") !important; 280.417 + } 280.418 + 280.419 + .mdl-color--deep-purple-200 { 280.420 + background-color: unquote("rgb(#{$palette-deep-purple-200})") !important; 280.421 + } 280.422 + 280.423 + .mdl-color-text--deep-purple-300 { 280.424 + color: unquote("rgb(#{$palette-deep-purple-300})") !important; 280.425 + } 280.426 + 280.427 + .mdl-color--deep-purple-300 { 280.428 + background-color: unquote("rgb(#{$palette-deep-purple-300})") !important; 280.429 + } 280.430 + 280.431 + .mdl-color-text--deep-purple-400 { 280.432 + color: unquote("rgb(#{$palette-deep-purple-400})") !important; 280.433 + } 280.434 + 280.435 + .mdl-color--deep-purple-400 { 280.436 + background-color: unquote("rgb(#{$palette-deep-purple-400})") !important; 280.437 + } 280.438 + 280.439 + .mdl-color-text--deep-purple-500 { 280.440 + color: unquote("rgb(#{$palette-deep-purple-500})") !important; 280.441 + } 280.442 + 280.443 + .mdl-color--deep-purple-500 { 280.444 + background-color: unquote("rgb(#{$palette-deep-purple-500})") !important; 280.445 + } 280.446 + 280.447 + .mdl-color-text--deep-purple-600 { 280.448 + color: unquote("rgb(#{$palette-deep-purple-600})") !important; 280.449 + } 280.450 + 280.451 + .mdl-color--deep-purple-600 { 280.452 + background-color: unquote("rgb(#{$palette-deep-purple-600})") !important; 280.453 + } 280.454 + 280.455 + .mdl-color-text--deep-purple-700 { 280.456 + color: unquote("rgb(#{$palette-deep-purple-700})") !important; 280.457 + } 280.458 + 280.459 + .mdl-color--deep-purple-700 { 280.460 + background-color: unquote("rgb(#{$palette-deep-purple-700})") !important; 280.461 + } 280.462 + 280.463 + .mdl-color-text--deep-purple-800 { 280.464 + color: unquote("rgb(#{$palette-deep-purple-800})") !important; 280.465 + } 280.466 + 280.467 + .mdl-color--deep-purple-800 { 280.468 + background-color: unquote("rgb(#{$palette-deep-purple-800})") !important; 280.469 + } 280.470 + 280.471 + .mdl-color-text--deep-purple-900 { 280.472 + color: unquote("rgb(#{$palette-deep-purple-900})") !important; 280.473 + } 280.474 + 280.475 + .mdl-color--deep-purple-900 { 280.476 + background-color: unquote("rgb(#{$palette-deep-purple-900})") !important; 280.477 + } 280.478 + 280.479 + .mdl-color-text--deep-purple-A100 { 280.480 + color: unquote("rgb(#{$palette-deep-purple-A100})") !important; 280.481 + } 280.482 + 280.483 + .mdl-color--deep-purple-A100 { 280.484 + background-color: unquote("rgb(#{$palette-deep-purple-A100})") !important; 280.485 + } 280.486 + 280.487 + .mdl-color-text--deep-purple-A200 { 280.488 + color: unquote("rgb(#{$palette-deep-purple-A200})") !important; 280.489 + } 280.490 + 280.491 + .mdl-color--deep-purple-A200 { 280.492 + background-color: unquote("rgb(#{$palette-deep-purple-A200})") !important; 280.493 + } 280.494 + 280.495 + .mdl-color-text--deep-purple-A400 { 280.496 + color: unquote("rgb(#{$palette-deep-purple-A400})") !important; 280.497 + } 280.498 + 280.499 + .mdl-color--deep-purple-A400 { 280.500 + background-color: unquote("rgb(#{$palette-deep-purple-A400})") !important; 280.501 + } 280.502 + 280.503 + .mdl-color-text--deep-purple-A700 { 280.504 + color: unquote("rgb(#{$palette-deep-purple-A700})") !important; 280.505 + } 280.506 + 280.507 + .mdl-color--deep-purple-A700 { 280.508 + background-color: unquote("rgb(#{$palette-deep-purple-A700})") !important; 280.509 + } 280.510 + 280.511 + // Indigo 280.512 + 280.513 + .mdl-color-text--indigo { 280.514 + color: unquote("rgb(#{$palette-indigo-500})") !important; 280.515 + } 280.516 + 280.517 + .mdl-color--indigo { 280.518 + background-color: unquote("rgb(#{$palette-indigo-500})") !important; 280.519 + } 280.520 + 280.521 + .mdl-color-text--indigo-50 { 280.522 + color: unquote("rgb(#{$palette-indigo-50})") !important; 280.523 + } 280.524 + 280.525 + .mdl-color--indigo-50 { 280.526 + background-color: unquote("rgb(#{$palette-indigo-50})") !important; 280.527 + } 280.528 + 280.529 + .mdl-color-text--indigo-100 { 280.530 + color: unquote("rgb(#{$palette-indigo-100})") !important; 280.531 + } 280.532 + 280.533 + .mdl-color--indigo-100 { 280.534 + background-color: unquote("rgb(#{$palette-indigo-100})") !important; 280.535 + } 280.536 + 280.537 + .mdl-color-text--indigo-200 { 280.538 + color: unquote("rgb(#{$palette-indigo-200})") !important; 280.539 + } 280.540 + 280.541 + .mdl-color--indigo-200 { 280.542 + background-color: unquote("rgb(#{$palette-indigo-200})") !important; 280.543 + } 280.544 + 280.545 + .mdl-color-text--indigo-300 { 280.546 + color: unquote("rgb(#{$palette-indigo-300})") !important; 280.547 + } 280.548 + 280.549 + .mdl-color--indigo-300 { 280.550 + background-color: unquote("rgb(#{$palette-indigo-300})") !important; 280.551 + } 280.552 + 280.553 + .mdl-color-text--indigo-400 { 280.554 + color: unquote("rgb(#{$palette-indigo-400})") !important; 280.555 + } 280.556 + 280.557 + .mdl-color--indigo-400 { 280.558 + background-color: unquote("rgb(#{$palette-indigo-400})") !important; 280.559 + } 280.560 + 280.561 + .mdl-color-text--indigo-500 { 280.562 + color: unquote("rgb(#{$palette-indigo-500})") !important; 280.563 + } 280.564 + 280.565 + .mdl-color--indigo-500 { 280.566 + background-color: unquote("rgb(#{$palette-indigo-500})") !important; 280.567 + } 280.568 + 280.569 + .mdl-color-text--indigo-600 { 280.570 + color: unquote("rgb(#{$palette-indigo-600})") !important; 280.571 + } 280.572 + 280.573 + .mdl-color--indigo-600 { 280.574 + background-color: unquote("rgb(#{$palette-indigo-600})") !important; 280.575 + } 280.576 + 280.577 + .mdl-color-text--indigo-700 { 280.578 + color: unquote("rgb(#{$palette-indigo-700})") !important; 280.579 + } 280.580 + 280.581 + .mdl-color--indigo-700 { 280.582 + background-color: unquote("rgb(#{$palette-indigo-700})") !important; 280.583 + } 280.584 + 280.585 + .mdl-color-text--indigo-800 { 280.586 + color: unquote("rgb(#{$palette-indigo-800})") !important; 280.587 + } 280.588 + 280.589 + .mdl-color--indigo-800 { 280.590 + background-color: unquote("rgb(#{$palette-indigo-800})") !important; 280.591 + } 280.592 + 280.593 + .mdl-color-text--indigo-900 { 280.594 + color: unquote("rgb(#{$palette-indigo-900})") !important; 280.595 + } 280.596 + 280.597 + .mdl-color--indigo-900 { 280.598 + background-color: unquote("rgb(#{$palette-indigo-900})") !important; 280.599 + } 280.600 + 280.601 + .mdl-color-text--indigo-A100 { 280.602 + color: unquote("rgb(#{$palette-indigo-A100})") !important; 280.603 + } 280.604 + 280.605 + .mdl-color--indigo-A100 { 280.606 + background-color: unquote("rgb(#{$palette-indigo-A100})") !important; 280.607 + } 280.608 + 280.609 + .mdl-color-text--indigo-A200 { 280.610 + color: unquote("rgb(#{$palette-indigo-A200})") !important; 280.611 + } 280.612 + 280.613 + .mdl-color--indigo-A200 { 280.614 + background-color: unquote("rgb(#{$palette-indigo-A200})") !important; 280.615 + } 280.616 + 280.617 + .mdl-color-text--indigo-A400 { 280.618 + color: unquote("rgb(#{$palette-indigo-A400})") !important; 280.619 + } 280.620 + 280.621 + .mdl-color--indigo-A400 { 280.622 + background-color: unquote("rgb(#{$palette-indigo-A400})") !important; 280.623 + } 280.624 + 280.625 + .mdl-color-text--indigo-A700 { 280.626 + color: unquote("rgb(#{$palette-indigo-A700})") !important; 280.627 + } 280.628 + 280.629 + .mdl-color--indigo-A700 { 280.630 + background-color: unquote("rgb(#{$palette-indigo-A700})") !important; 280.631 + } 280.632 + 280.633 + // Blue 280.634 + 280.635 + .mdl-color-text--blue { 280.636 + color: unquote("rgb(#{$palette-blue-500})") !important; 280.637 + } 280.638 + 280.639 + .mdl-color--blue { 280.640 + background-color: unquote("rgb(#{$palette-blue-500})") !important; 280.641 + } 280.642 + 280.643 + .mdl-color-text--blue-50 { 280.644 + color: unquote("rgb(#{$palette-blue-50})") !important; 280.645 + } 280.646 + 280.647 + .mdl-color--blue-50 { 280.648 + background-color: unquote("rgb(#{$palette-blue-50})") !important; 280.649 + } 280.650 + 280.651 + .mdl-color-text--blue-100 { 280.652 + color: unquote("rgb(#{$palette-blue-100})") !important; 280.653 + } 280.654 + 280.655 + .mdl-color--blue-100 { 280.656 + background-color: unquote("rgb(#{$palette-blue-100})") !important; 280.657 + } 280.658 + 280.659 + .mdl-color-text--blue-200 { 280.660 + color: unquote("rgb(#{$palette-blue-200})") !important; 280.661 + } 280.662 + 280.663 + .mdl-color--blue-200 { 280.664 + background-color: unquote("rgb(#{$palette-blue-200})") !important; 280.665 + } 280.666 + 280.667 + .mdl-color-text--blue-300 { 280.668 + color: unquote("rgb(#{$palette-blue-300})") !important; 280.669 + } 280.670 + 280.671 + .mdl-color--blue-300 { 280.672 + background-color: unquote("rgb(#{$palette-blue-300})") !important; 280.673 + } 280.674 + 280.675 + .mdl-color-text--blue-400 { 280.676 + color: unquote("rgb(#{$palette-blue-400})") !important; 280.677 + } 280.678 + 280.679 + .mdl-color--blue-400 { 280.680 + background-color: unquote("rgb(#{$palette-blue-400})") !important; 280.681 + } 280.682 + 280.683 + .mdl-color-text--blue-500 { 280.684 + color: unquote("rgb(#{$palette-blue-500})") !important; 280.685 + } 280.686 + 280.687 + .mdl-color--blue-500 { 280.688 + background-color: unquote("rgb(#{$palette-blue-500})") !important; 280.689 + } 280.690 + 280.691 + .mdl-color-text--blue-600 { 280.692 + color: unquote("rgb(#{$palette-blue-600})") !important; 280.693 + } 280.694 + 280.695 + .mdl-color--blue-600 { 280.696 + background-color: unquote("rgb(#{$palette-blue-600})") !important; 280.697 + } 280.698 + 280.699 + .mdl-color-text--blue-700 { 280.700 + color: unquote("rgb(#{$palette-blue-700})") !important; 280.701 + } 280.702 + 280.703 + .mdl-color--blue-700 { 280.704 + background-color: unquote("rgb(#{$palette-blue-700})") !important; 280.705 + } 280.706 + 280.707 + .mdl-color-text--blue-800 { 280.708 + color: unquote("rgb(#{$palette-blue-800})") !important; 280.709 + } 280.710 + 280.711 + .mdl-color--blue-800 { 280.712 + background-color: unquote("rgb(#{$palette-blue-800})") !important; 280.713 + } 280.714 + 280.715 + .mdl-color-text--blue-900 { 280.716 + color: unquote("rgb(#{$palette-blue-900})") !important; 280.717 + } 280.718 + 280.719 + .mdl-color--blue-900 { 280.720 + background-color: unquote("rgb(#{$palette-blue-900})") !important; 280.721 + } 280.722 + 280.723 + .mdl-color-text--blue-A100 { 280.724 + color: unquote("rgb(#{$palette-blue-A100})") !important; 280.725 + } 280.726 + 280.727 + .mdl-color--blue-A100 { 280.728 + background-color: unquote("rgb(#{$palette-blue-A100})") !important; 280.729 + } 280.730 + 280.731 + .mdl-color-text--blue-A200 { 280.732 + color: unquote("rgb(#{$palette-blue-A200})") !important; 280.733 + } 280.734 + 280.735 + .mdl-color--blue-A200 { 280.736 + background-color: unquote("rgb(#{$palette-blue-A200})") !important; 280.737 + } 280.738 + 280.739 + .mdl-color-text--blue-A400 { 280.740 + color: unquote("rgb(#{$palette-blue-A400})") !important; 280.741 + } 280.742 + 280.743 + .mdl-color--blue-A400 { 280.744 + background-color: unquote("rgb(#{$palette-blue-A400})") !important; 280.745 + } 280.746 + 280.747 + .mdl-color-text--blue-A700 { 280.748 + color: unquote("rgb(#{$palette-blue-A700})") !important; 280.749 + } 280.750 + 280.751 + .mdl-color--blue-A700 { 280.752 + background-color: unquote("rgb(#{$palette-blue-A700})") !important; 280.753 + } 280.754 + 280.755 + // Light Blue 280.756 + 280.757 + .mdl-color-text--light-blue { 280.758 + color: unquote("rgb(#{$palette-light-blue-500})") !important; 280.759 + } 280.760 + 280.761 + .mdl-color--light-blue { 280.762 + background-color: unquote("rgb(#{$palette-light-blue-500})") !important; 280.763 + } 280.764 + 280.765 + .mdl-color-text--light-blue-50 { 280.766 + color: unquote("rgb(#{$palette-light-blue-50})") !important; 280.767 + } 280.768 + 280.769 + .mdl-color--light-blue-50 { 280.770 + background-color: unquote("rgb(#{$palette-light-blue-50})") !important; 280.771 + } 280.772 + 280.773 + .mdl-color-text--light-blue-100 { 280.774 + color: unquote("rgb(#{$palette-light-blue-100})") !important; 280.775 + } 280.776 + 280.777 + .mdl-color--light-blue-100 { 280.778 + background-color: unquote("rgb(#{$palette-light-blue-100})") !important; 280.779 + } 280.780 + 280.781 + .mdl-color-text--light-blue-200 { 280.782 + color: unquote("rgb(#{$palette-light-blue-200})") !important; 280.783 + } 280.784 + 280.785 + .mdl-color--light-blue-200 { 280.786 + background-color: unquote("rgb(#{$palette-light-blue-200})") !important; 280.787 + } 280.788 + 280.789 + .mdl-color-text--light-blue-300 { 280.790 + color: unquote("rgb(#{$palette-light-blue-300})") !important; 280.791 + } 280.792 + 280.793 + .mdl-color--light-blue-300 { 280.794 + background-color: unquote("rgb(#{$palette-light-blue-300})") !important; 280.795 + } 280.796 + 280.797 + .mdl-color-text--light-blue-400 { 280.798 + color: unquote("rgb(#{$palette-light-blue-400})") !important; 280.799 + } 280.800 + 280.801 + .mdl-color--light-blue-400 { 280.802 + background-color: unquote("rgb(#{$palette-light-blue-400})") !important; 280.803 + } 280.804 + 280.805 + .mdl-color-text--light-blue-500 { 280.806 + color: unquote("rgb(#{$palette-light-blue-500})") !important; 280.807 + } 280.808 + 280.809 + .mdl-color--light-blue-500 { 280.810 + background-color: unquote("rgb(#{$palette-light-blue-500})") !important; 280.811 + } 280.812 + 280.813 + .mdl-color-text--light-blue-600 { 280.814 + color: unquote("rgb(#{$palette-light-blue-600})") !important; 280.815 + } 280.816 + 280.817 + .mdl-color--light-blue-600 { 280.818 + background-color: unquote("rgb(#{$palette-light-blue-600})") !important; 280.819 + } 280.820 + 280.821 + .mdl-color-text--light-blue-700 { 280.822 + color: unquote("rgb(#{$palette-light-blue-700})") !important; 280.823 + } 280.824 + 280.825 + .mdl-color--light-blue-700 { 280.826 + background-color: unquote("rgb(#{$palette-light-blue-700})") !important; 280.827 + } 280.828 + 280.829 + .mdl-color-text--light-blue-800 { 280.830 + color: unquote("rgb(#{$palette-light-blue-800})") !important; 280.831 + } 280.832 + 280.833 + .mdl-color--light-blue-800 { 280.834 + background-color: unquote("rgb(#{$palette-light-blue-800})") !important; 280.835 + } 280.836 + 280.837 + .mdl-color-text--light-blue-900 { 280.838 + color: unquote("rgb(#{$palette-light-blue-900})") !important; 280.839 + } 280.840 + 280.841 + .mdl-color--light-blue-900 { 280.842 + background-color: unquote("rgb(#{$palette-light-blue-900})") !important; 280.843 + } 280.844 + 280.845 + .mdl-color-text--light-blue-A100 { 280.846 + color: unquote("rgb(#{$palette-light-blue-A100})") !important; 280.847 + } 280.848 + 280.849 + .mdl-color--light-blue-A100 { 280.850 + background-color: unquote("rgb(#{$palette-light-blue-A100})") !important; 280.851 + } 280.852 + 280.853 + .mdl-color-text--light-blue-A200 { 280.854 + color: unquote("rgb(#{$palette-light-blue-A200})") !important; 280.855 + } 280.856 + 280.857 + .mdl-color--light-blue-A200 { 280.858 + background-color: unquote("rgb(#{$palette-light-blue-A200})") !important; 280.859 + } 280.860 + 280.861 + .mdl-color-text--light-blue-A400 { 280.862 + color: unquote("rgb(#{$palette-light-blue-A400})") !important; 280.863 + } 280.864 + 280.865 + .mdl-color--light-blue-A400 { 280.866 + background-color: unquote("rgb(#{$palette-light-blue-A400})") !important; 280.867 + } 280.868 + 280.869 + .mdl-color-text--light-blue-A700 { 280.870 + color: unquote("rgb(#{$palette-light-blue-A700})") !important; 280.871 + } 280.872 + 280.873 + .mdl-color--light-blue-A700 { 280.874 + background-color: unquote("rgb(#{$palette-light-blue-A700})") !important; 280.875 + } 280.876 + 280.877 + // Cyan 280.878 + 280.879 + .mdl-color-text--cyan { 280.880 + color: unquote("rgb(#{$palette-cyan-500})") !important; 280.881 + } 280.882 + 280.883 + .mdl-color--cyan { 280.884 + background-color: unquote("rgb(#{$palette-cyan-500})") !important; 280.885 + } 280.886 + 280.887 + .mdl-color-text--cyan-50 { 280.888 + color: unquote("rgb(#{$palette-cyan-50})") !important; 280.889 + } 280.890 + 280.891 + .mdl-color--cyan-50 { 280.892 + background-color: unquote("rgb(#{$palette-cyan-50})") !important; 280.893 + } 280.894 + 280.895 + .mdl-color-text--cyan-100 { 280.896 + color: unquote("rgb(#{$palette-cyan-100})") !important; 280.897 + } 280.898 + 280.899 + .mdl-color--cyan-100 { 280.900 + background-color: unquote("rgb(#{$palette-cyan-100})") !important; 280.901 + } 280.902 + 280.903 + .mdl-color-text--cyan-200 { 280.904 + color: unquote("rgb(#{$palette-cyan-200})") !important; 280.905 + } 280.906 + 280.907 + .mdl-color--cyan-200 { 280.908 + background-color: unquote("rgb(#{$palette-cyan-200})") !important; 280.909 + } 280.910 + 280.911 + .mdl-color-text--cyan-300 { 280.912 + color: unquote("rgb(#{$palette-cyan-300})") !important; 280.913 + } 280.914 + 280.915 + .mdl-color--cyan-300 { 280.916 + background-color: unquote("rgb(#{$palette-cyan-300})") !important; 280.917 + } 280.918 + 280.919 + .mdl-color-text--cyan-400 { 280.920 + color: unquote("rgb(#{$palette-cyan-400})") !important; 280.921 + } 280.922 + 280.923 + .mdl-color--cyan-400 { 280.924 + background-color: unquote("rgb(#{$palette-cyan-400})") !important; 280.925 + } 280.926 + 280.927 + .mdl-color-text--cyan-500 { 280.928 + color: unquote("rgb(#{$palette-cyan-500})") !important; 280.929 + } 280.930 + 280.931 + .mdl-color--cyan-500 { 280.932 + background-color: unquote("rgb(#{$palette-cyan-500})") !important; 280.933 + } 280.934 + 280.935 + .mdl-color-text--cyan-600 { 280.936 + color: unquote("rgb(#{$palette-cyan-600})") !important; 280.937 + } 280.938 + 280.939 + .mdl-color--cyan-600 { 280.940 + background-color: unquote("rgb(#{$palette-cyan-600})") !important; 280.941 + } 280.942 + 280.943 + .mdl-color-text--cyan-700 { 280.944 + color: unquote("rgb(#{$palette-cyan-700})") !important; 280.945 + } 280.946 + 280.947 + .mdl-color--cyan-700 { 280.948 + background-color: unquote("rgb(#{$palette-cyan-700})") !important; 280.949 + } 280.950 + 280.951 + .mdl-color-text--cyan-800 { 280.952 + color: unquote("rgb(#{$palette-cyan-800})") !important; 280.953 + } 280.954 + 280.955 + .mdl-color--cyan-800 { 280.956 + background-color: unquote("rgb(#{$palette-cyan-800})") !important; 280.957 + } 280.958 + 280.959 + .mdl-color-text--cyan-900 { 280.960 + color: unquote("rgb(#{$palette-cyan-900})") !important; 280.961 + } 280.962 + 280.963 + .mdl-color--cyan-900 { 280.964 + background-color: unquote("rgb(#{$palette-cyan-900})") !important; 280.965 + } 280.966 + 280.967 + .mdl-color-text--cyan-A100 { 280.968 + color: unquote("rgb(#{$palette-cyan-A100})") !important; 280.969 + } 280.970 + 280.971 + .mdl-color--cyan-A100 { 280.972 + background-color: unquote("rgb(#{$palette-cyan-A100})") !important; 280.973 + } 280.974 + 280.975 + .mdl-color-text--cyan-A200 { 280.976 + color: unquote("rgb(#{$palette-cyan-A200})") !important; 280.977 + } 280.978 + 280.979 + .mdl-color--cyan-A200 { 280.980 + background-color: unquote("rgb(#{$palette-cyan-A200})") !important; 280.981 + } 280.982 + 280.983 + .mdl-color-text--cyan-A400 { 280.984 + color: unquote("rgb(#{$palette-cyan-A400})") !important; 280.985 + } 280.986 + 280.987 + .mdl-color--cyan-A400 { 280.988 + background-color: unquote("rgb(#{$palette-cyan-A400})") !important; 280.989 + } 280.990 + 280.991 + .mdl-color-text--cyan-A700 { 280.992 + color: unquote("rgb(#{$palette-cyan-A700})") !important; 280.993 + } 280.994 + 280.995 + .mdl-color--cyan-A700 { 280.996 + background-color: unquote("rgb(#{$palette-cyan-A700})") !important; 280.997 + } 280.998 + 280.999 + // Teal 280.1000 + 280.1001 + .mdl-color-text--teal { 280.1002 + color: unquote("rgb(#{$palette-teal-500})") !important; 280.1003 + } 280.1004 + 280.1005 + .mdl-color--teal { 280.1006 + background-color: unquote("rgb(#{$palette-teal-500})") !important; 280.1007 + } 280.1008 + 280.1009 + .mdl-color-text--teal-50 { 280.1010 + color: unquote("rgb(#{$palette-teal-50})") !important; 280.1011 + } 280.1012 + 280.1013 + .mdl-color--teal-50 { 280.1014 + background-color: unquote("rgb(#{$palette-teal-50})") !important; 280.1015 + } 280.1016 + 280.1017 + .mdl-color-text--teal-100 { 280.1018 + color: unquote("rgb(#{$palette-teal-100})") !important; 280.1019 + } 280.1020 + 280.1021 + .mdl-color--teal-100 { 280.1022 + background-color: unquote("rgb(#{$palette-teal-100})") !important; 280.1023 + } 280.1024 + 280.1025 + .mdl-color-text--teal-200 { 280.1026 + color: unquote("rgb(#{$palette-teal-200})") !important; 280.1027 + } 280.1028 + 280.1029 + .mdl-color--teal-200 { 280.1030 + background-color: unquote("rgb(#{$palette-teal-200})") !important; 280.1031 + } 280.1032 + 280.1033 + .mdl-color-text--teal-300 { 280.1034 + color: unquote("rgb(#{$palette-teal-300})") !important; 280.1035 + } 280.1036 + 280.1037 + .mdl-color--teal-300 { 280.1038 + background-color: unquote("rgb(#{$palette-teal-300})") !important; 280.1039 + } 280.1040 + 280.1041 + .mdl-color-text--teal-400 { 280.1042 + color: unquote("rgb(#{$palette-teal-400})") !important; 280.1043 + } 280.1044 + 280.1045 + .mdl-color--teal-400 { 280.1046 + background-color: unquote("rgb(#{$palette-teal-400})") !important; 280.1047 + } 280.1048 + 280.1049 + .mdl-color-text--teal-500 { 280.1050 + color: unquote("rgb(#{$palette-teal-500})") !important; 280.1051 + } 280.1052 + 280.1053 + .mdl-color--teal-500 { 280.1054 + background-color: unquote("rgb(#{$palette-teal-500})") !important; 280.1055 + } 280.1056 + 280.1057 + .mdl-color-text--teal-600 { 280.1058 + color: unquote("rgb(#{$palette-teal-600})") !important; 280.1059 + } 280.1060 + 280.1061 + .mdl-color--teal-600 { 280.1062 + background-color: unquote("rgb(#{$palette-teal-600})") !important; 280.1063 + } 280.1064 + 280.1065 + .mdl-color-text--teal-700 { 280.1066 + color: unquote("rgb(#{$palette-teal-700})") !important; 280.1067 + } 280.1068 + 280.1069 + .mdl-color--teal-700 { 280.1070 + background-color: unquote("rgb(#{$palette-teal-700})") !important; 280.1071 + } 280.1072 + 280.1073 + .mdl-color-text--teal-800 { 280.1074 + color: unquote("rgb(#{$palette-teal-800})") !important; 280.1075 + } 280.1076 + 280.1077 + .mdl-color--teal-800 { 280.1078 + background-color: unquote("rgb(#{$palette-teal-800})") !important; 280.1079 + } 280.1080 + 280.1081 + .mdl-color-text--teal-900 { 280.1082 + color: unquote("rgb(#{$palette-teal-900})") !important; 280.1083 + } 280.1084 + 280.1085 + .mdl-color--teal-900 { 280.1086 + background-color: unquote("rgb(#{$palette-teal-900})") !important; 280.1087 + } 280.1088 + 280.1089 + .mdl-color-text--teal-A100 { 280.1090 + color: unquote("rgb(#{$palette-teal-A100})") !important; 280.1091 + } 280.1092 + 280.1093 + .mdl-color--teal-A100 { 280.1094 + background-color: unquote("rgb(#{$palette-teal-A100})") !important; 280.1095 + } 280.1096 + 280.1097 + .mdl-color-text--teal-A200 { 280.1098 + color: unquote("rgb(#{$palette-teal-A200})") !important; 280.1099 + } 280.1100 + 280.1101 + .mdl-color--teal-A200 { 280.1102 + background-color: unquote("rgb(#{$palette-teal-A200})") !important; 280.1103 + } 280.1104 + 280.1105 + .mdl-color-text--teal-A400 { 280.1106 + color: unquote("rgb(#{$palette-teal-A400})") !important; 280.1107 + } 280.1108 + 280.1109 + .mdl-color--teal-A400 { 280.1110 + background-color: unquote("rgb(#{$palette-teal-A400})") !important; 280.1111 + } 280.1112 + 280.1113 + .mdl-color-text--teal-A700 { 280.1114 + color: unquote("rgb(#{$palette-teal-A700})") !important; 280.1115 + } 280.1116 + 280.1117 + .mdl-color--teal-A700 { 280.1118 + background-color: unquote("rgb(#{$palette-teal-A700})") !important; 280.1119 + } 280.1120 + 280.1121 + // Green 280.1122 + 280.1123 + .mdl-color-text--green { 280.1124 + color: unquote("rgb(#{$palette-green-500})") !important; 280.1125 + } 280.1126 + 280.1127 + .mdl-color--green { 280.1128 + background-color: unquote("rgb(#{$palette-green-500})") !important; 280.1129 + } 280.1130 + 280.1131 + .mdl-color-text--green-50 { 280.1132 + color: unquote("rgb(#{$palette-green-50})") !important; 280.1133 + } 280.1134 + 280.1135 + .mdl-color--green-50 { 280.1136 + background-color: unquote("rgb(#{$palette-green-50})") !important; 280.1137 + } 280.1138 + 280.1139 + .mdl-color-text--green-100 { 280.1140 + color: unquote("rgb(#{$palette-green-100})") !important; 280.1141 + } 280.1142 + 280.1143 + .mdl-color--green-100 { 280.1144 + background-color: unquote("rgb(#{$palette-green-100})") !important; 280.1145 + } 280.1146 + 280.1147 + .mdl-color-text--green-200 { 280.1148 + color: unquote("rgb(#{$palette-green-200})") !important; 280.1149 + } 280.1150 + 280.1151 + .mdl-color--green-200 { 280.1152 + background-color: unquote("rgb(#{$palette-green-200})") !important; 280.1153 + } 280.1154 + 280.1155 + .mdl-color-text--green-300 { 280.1156 + color: unquote("rgb(#{$palette-green-300})") !important; 280.1157 + } 280.1158 + 280.1159 + .mdl-color--green-300 { 280.1160 + background-color: unquote("rgb(#{$palette-green-300})") !important; 280.1161 + } 280.1162 + 280.1163 + .mdl-color-text--green-400 { 280.1164 + color: unquote("rgb(#{$palette-green-400})") !important; 280.1165 + } 280.1166 + 280.1167 + .mdl-color--green-400 { 280.1168 + background-color: unquote("rgb(#{$palette-green-400})") !important; 280.1169 + } 280.1170 + 280.1171 + .mdl-color-text--green-500 { 280.1172 + color: unquote("rgb(#{$palette-green-500})") !important; 280.1173 + } 280.1174 + 280.1175 + .mdl-color--green-500 { 280.1176 + background-color: unquote("rgb(#{$palette-green-500})") !important; 280.1177 + } 280.1178 + 280.1179 + .mdl-color-text--green-600 { 280.1180 + color: unquote("rgb(#{$palette-green-600})") !important; 280.1181 + } 280.1182 + 280.1183 + .mdl-color--green-600 { 280.1184 + background-color: unquote("rgb(#{$palette-green-600})") !important; 280.1185 + } 280.1186 + 280.1187 + .mdl-color-text--green-700 { 280.1188 + color: unquote("rgb(#{$palette-green-700})") !important; 280.1189 + } 280.1190 + 280.1191 + .mdl-color--green-700 { 280.1192 + background-color: unquote("rgb(#{$palette-green-700})") !important; 280.1193 + } 280.1194 + 280.1195 + .mdl-color-text--green-800 { 280.1196 + color: unquote("rgb(#{$palette-green-800})") !important; 280.1197 + } 280.1198 + 280.1199 + .mdl-color--green-800 { 280.1200 + background-color: unquote("rgb(#{$palette-green-800})") !important; 280.1201 + } 280.1202 + 280.1203 + .mdl-color-text--green-900 { 280.1204 + color: unquote("rgb(#{$palette-green-900})") !important; 280.1205 + } 280.1206 + 280.1207 + .mdl-color--green-900 { 280.1208 + background-color: unquote("rgb(#{$palette-green-900})") !important; 280.1209 + } 280.1210 + 280.1211 + .mdl-color-text--green-A100 { 280.1212 + color: unquote("rgb(#{$palette-green-A100})") !important; 280.1213 + } 280.1214 + 280.1215 + .mdl-color--green-A100 { 280.1216 + background-color: unquote("rgb(#{$palette-green-A100})") !important; 280.1217 + } 280.1218 + 280.1219 + .mdl-color-text--green-A200 { 280.1220 + color: unquote("rgb(#{$palette-green-A200})") !important; 280.1221 + } 280.1222 + 280.1223 + .mdl-color--green-A200 { 280.1224 + background-color: unquote("rgb(#{$palette-green-A200})") !important; 280.1225 + } 280.1226 + 280.1227 + .mdl-color-text--green-A400 { 280.1228 + color: unquote("rgb(#{$palette-green-A400})") !important; 280.1229 + } 280.1230 + 280.1231 + .mdl-color--green-A400 { 280.1232 + background-color: unquote("rgb(#{$palette-green-A400})") !important; 280.1233 + } 280.1234 + 280.1235 + .mdl-color-text--green-A700 { 280.1236 + color: unquote("rgb(#{$palette-green-A700})") !important; 280.1237 + } 280.1238 + 280.1239 + .mdl-color--green-A700 { 280.1240 + background-color: unquote("rgb(#{$palette-green-A700})") !important; 280.1241 + } 280.1242 + 280.1243 + // Light Green 280.1244 + 280.1245 + .mdl-color-text--light-green { 280.1246 + color: unquote("rgb(#{$palette-light-green-500})") !important; 280.1247 + } 280.1248 + 280.1249 + .mdl-color--light-green { 280.1250 + background-color: unquote("rgb(#{$palette-light-green-500})") !important; 280.1251 + } 280.1252 + 280.1253 + .mdl-color-text--light-green-50 { 280.1254 + color: unquote("rgb(#{$palette-light-green-50})") !important; 280.1255 + } 280.1256 + 280.1257 + .mdl-color--light-green-50 { 280.1258 + background-color: unquote("rgb(#{$palette-light-green-50})") !important; 280.1259 + } 280.1260 + 280.1261 + .mdl-color-text--light-green-100 { 280.1262 + color: unquote("rgb(#{$palette-light-green-100})") !important; 280.1263 + } 280.1264 + 280.1265 + .mdl-color--light-green-100 { 280.1266 + background-color: unquote("rgb(#{$palette-light-green-100})") !important; 280.1267 + } 280.1268 + 280.1269 + .mdl-color-text--light-green-200 { 280.1270 + color: unquote("rgb(#{$palette-light-green-200})") !important; 280.1271 + } 280.1272 + 280.1273 + .mdl-color--light-green-200 { 280.1274 + background-color: unquote("rgb(#{$palette-light-green-200})") !important; 280.1275 + } 280.1276 + 280.1277 + .mdl-color-text--light-green-300 { 280.1278 + color: unquote("rgb(#{$palette-light-green-300})") !important; 280.1279 + } 280.1280 + 280.1281 + .mdl-color--light-green-300 { 280.1282 + background-color: unquote("rgb(#{$palette-light-green-300})") !important; 280.1283 + } 280.1284 + 280.1285 + .mdl-color-text--light-green-400 { 280.1286 + color: unquote("rgb(#{$palette-light-green-400})") !important; 280.1287 + } 280.1288 + 280.1289 + .mdl-color--light-green-400 { 280.1290 + background-color: unquote("rgb(#{$palette-light-green-400})") !important; 280.1291 + } 280.1292 + 280.1293 + .mdl-color-text--light-green-500 { 280.1294 + color: unquote("rgb(#{$palette-light-green-500})") !important; 280.1295 + } 280.1296 + 280.1297 + .mdl-color--light-green-500 { 280.1298 + background-color: unquote("rgb(#{$palette-light-green-500})") !important; 280.1299 + } 280.1300 + 280.1301 + .mdl-color-text--light-green-600 { 280.1302 + color: unquote("rgb(#{$palette-light-green-600})") !important; 280.1303 + } 280.1304 + 280.1305 + .mdl-color--light-green-600 { 280.1306 + background-color: unquote("rgb(#{$palette-light-green-600})") !important; 280.1307 + } 280.1308 + 280.1309 + .mdl-color-text--light-green-700 { 280.1310 + color: unquote("rgb(#{$palette-light-green-700})") !important; 280.1311 + } 280.1312 + 280.1313 + .mdl-color--light-green-700 { 280.1314 + background-color: unquote("rgb(#{$palette-light-green-700})") !important; 280.1315 + } 280.1316 + 280.1317 + .mdl-color-text--light-green-800 { 280.1318 + color: unquote("rgb(#{$palette-light-green-800})") !important; 280.1319 + } 280.1320 + 280.1321 + .mdl-color--light-green-800 { 280.1322 + background-color: unquote("rgb(#{$palette-light-green-800})") !important; 280.1323 + } 280.1324 + 280.1325 + .mdl-color-text--light-green-900 { 280.1326 + color: unquote("rgb(#{$palette-light-green-900})") !important; 280.1327 + } 280.1328 + 280.1329 + .mdl-color--light-green-900 { 280.1330 + background-color: unquote("rgb(#{$palette-light-green-900})") !important; 280.1331 + } 280.1332 + 280.1333 + .mdl-color-text--light-green-A100 { 280.1334 + color: unquote("rgb(#{$palette-light-green-A100})") !important; 280.1335 + } 280.1336 + 280.1337 + .mdl-color--light-green-A100 { 280.1338 + background-color: unquote("rgb(#{$palette-light-green-A100})") !important; 280.1339 + } 280.1340 + 280.1341 + .mdl-color-text--light-green-A200 { 280.1342 + color: unquote("rgb(#{$palette-light-green-A200})") !important; 280.1343 + } 280.1344 + 280.1345 + .mdl-color--light-green-A200 { 280.1346 + background-color: unquote("rgb(#{$palette-light-green-A200})") !important; 280.1347 + } 280.1348 + 280.1349 + .mdl-color-text--light-green-A400 { 280.1350 + color: unquote("rgb(#{$palette-light-green-A400})") !important; 280.1351 + } 280.1352 + 280.1353 + .mdl-color--light-green-A400 { 280.1354 + background-color: unquote("rgb(#{$palette-light-green-A400})") !important; 280.1355 + } 280.1356 + 280.1357 + .mdl-color-text--light-green-A700 { 280.1358 + color: unquote("rgb(#{$palette-light-green-A700})") !important; 280.1359 + } 280.1360 + 280.1361 + .mdl-color--light-green-A700 { 280.1362 + background-color: unquote("rgb(#{$palette-light-green-A700})") !important; 280.1363 + } 280.1364 + 280.1365 + // Lime 280.1366 + 280.1367 + .mdl-color-text--lime { 280.1368 + color: unquote("rgb(#{$palette-lime-500})") !important; 280.1369 + } 280.1370 + 280.1371 + .mdl-color--lime { 280.1372 + background-color: unquote("rgb(#{$palette-lime-500})") !important; 280.1373 + } 280.1374 + 280.1375 + .mdl-color-text--lime-50 { 280.1376 + color: unquote("rgb(#{$palette-lime-50})") !important; 280.1377 + } 280.1378 + 280.1379 + .mdl-color--lime-50 { 280.1380 + background-color: unquote("rgb(#{$palette-lime-50})") !important; 280.1381 + } 280.1382 + 280.1383 + .mdl-color-text--lime-100 { 280.1384 + color: unquote("rgb(#{$palette-lime-100})") !important; 280.1385 + } 280.1386 + 280.1387 + .mdl-color--lime-100 { 280.1388 + background-color: unquote("rgb(#{$palette-lime-100})") !important; 280.1389 + } 280.1390 + 280.1391 + .mdl-color-text--lime-200 { 280.1392 + color: unquote("rgb(#{$palette-lime-200})") !important; 280.1393 + } 280.1394 + 280.1395 + .mdl-color--lime-200 { 280.1396 + background-color: unquote("rgb(#{$palette-lime-200})") !important; 280.1397 + } 280.1398 + 280.1399 + .mdl-color-text--lime-300 { 280.1400 + color: unquote("rgb(#{$palette-lime-300})") !important; 280.1401 + } 280.1402 + 280.1403 + .mdl-color--lime-300 { 280.1404 + background-color: unquote("rgb(#{$palette-lime-300})") !important; 280.1405 + } 280.1406 + 280.1407 + .mdl-color-text--lime-400 { 280.1408 + color: unquote("rgb(#{$palette-lime-400})") !important; 280.1409 + } 280.1410 + 280.1411 + .mdl-color--lime-400 { 280.1412 + background-color: unquote("rgb(#{$palette-lime-400})") !important; 280.1413 + } 280.1414 + 280.1415 + .mdl-color-text--lime-500 { 280.1416 + color: unquote("rgb(#{$palette-lime-500})") !important; 280.1417 + } 280.1418 + 280.1419 + .mdl-color--lime-500 { 280.1420 + background-color: unquote("rgb(#{$palette-lime-500})") !important; 280.1421 + } 280.1422 + 280.1423 + .mdl-color-text--lime-600 { 280.1424 + color: unquote("rgb(#{$palette-lime-600})") !important; 280.1425 + } 280.1426 + 280.1427 + .mdl-color--lime-600 { 280.1428 + background-color: unquote("rgb(#{$palette-lime-600})") !important; 280.1429 + } 280.1430 + 280.1431 + .mdl-color-text--lime-700 { 280.1432 + color: unquote("rgb(#{$palette-lime-700})") !important; 280.1433 + } 280.1434 + 280.1435 + .mdl-color--lime-700 { 280.1436 + background-color: unquote("rgb(#{$palette-lime-700})") !important; 280.1437 + } 280.1438 + 280.1439 + .mdl-color-text--lime-800 { 280.1440 + color: unquote("rgb(#{$palette-lime-800})") !important; 280.1441 + } 280.1442 + 280.1443 + .mdl-color--lime-800 { 280.1444 + background-color: unquote("rgb(#{$palette-lime-800})") !important; 280.1445 + } 280.1446 + 280.1447 + .mdl-color-text--lime-900 { 280.1448 + color: unquote("rgb(#{$palette-lime-900})") !important; 280.1449 + } 280.1450 + 280.1451 + .mdl-color--lime-900 { 280.1452 + background-color: unquote("rgb(#{$palette-lime-900})") !important; 280.1453 + } 280.1454 + 280.1455 + .mdl-color-text--lime-A100 { 280.1456 + color: unquote("rgb(#{$palette-lime-A100})") !important; 280.1457 + } 280.1458 + 280.1459 + .mdl-color--lime-A100 { 280.1460 + background-color: unquote("rgb(#{$palette-lime-A100})") !important; 280.1461 + } 280.1462 + 280.1463 + .mdl-color-text--lime-A200 { 280.1464 + color: unquote("rgb(#{$palette-lime-A200})") !important; 280.1465 + } 280.1466 + 280.1467 + .mdl-color--lime-A200 { 280.1468 + background-color: unquote("rgb(#{$palette-lime-A200})") !important; 280.1469 + } 280.1470 + 280.1471 + .mdl-color-text--lime-A400 { 280.1472 + color: unquote("rgb(#{$palette-lime-A400})") !important; 280.1473 + } 280.1474 + 280.1475 + .mdl-color--lime-A400 { 280.1476 + background-color: unquote("rgb(#{$palette-lime-A400})") !important; 280.1477 + } 280.1478 + 280.1479 + .mdl-color-text--lime-A700 { 280.1480 + color: unquote("rgb(#{$palette-lime-A700})") !important; 280.1481 + } 280.1482 + 280.1483 + .mdl-color--lime-A700 { 280.1484 + background-color: unquote("rgb(#{$palette-lime-A700})") !important; 280.1485 + } 280.1486 + 280.1487 + // Yellow 280.1488 + 280.1489 + .mdl-color-text--yellow { 280.1490 + color: unquote("rgb(#{$palette-yellow-500})") !important; 280.1491 + } 280.1492 + 280.1493 + .mdl-color--yellow { 280.1494 + background-color: unquote("rgb(#{$palette-yellow-500})") !important; 280.1495 + } 280.1496 + 280.1497 + .mdl-color-text--yellow-50 { 280.1498 + color: unquote("rgb(#{$palette-yellow-50})") !important; 280.1499 + } 280.1500 + 280.1501 + .mdl-color--yellow-50 { 280.1502 + background-color: unquote("rgb(#{$palette-yellow-50})") !important; 280.1503 + } 280.1504 + 280.1505 + .mdl-color-text--yellow-100 { 280.1506 + color: unquote("rgb(#{$palette-yellow-100})") !important; 280.1507 + } 280.1508 + 280.1509 + .mdl-color--yellow-100 { 280.1510 + background-color: unquote("rgb(#{$palette-yellow-100})") !important; 280.1511 + } 280.1512 + 280.1513 + .mdl-color-text--yellow-200 { 280.1514 + color: unquote("rgb(#{$palette-yellow-200})") !important; 280.1515 + } 280.1516 + 280.1517 + .mdl-color--yellow-200 { 280.1518 + background-color: unquote("rgb(#{$palette-yellow-200})") !important; 280.1519 + } 280.1520 + 280.1521 + .mdl-color-text--yellow-300 { 280.1522 + color: unquote("rgb(#{$palette-yellow-300})") !important; 280.1523 + } 280.1524 + 280.1525 + .mdl-color--yellow-300 { 280.1526 + background-color: unquote("rgb(#{$palette-yellow-300})") !important; 280.1527 + } 280.1528 + 280.1529 + .mdl-color-text--yellow-400 { 280.1530 + color: unquote("rgb(#{$palette-yellow-400})") !important; 280.1531 + } 280.1532 + 280.1533 + .mdl-color--yellow-400 { 280.1534 + background-color: unquote("rgb(#{$palette-yellow-400})") !important; 280.1535 + } 280.1536 + 280.1537 + .mdl-color-text--yellow-500 { 280.1538 + color: unquote("rgb(#{$palette-yellow-500})") !important; 280.1539 + } 280.1540 + 280.1541 + .mdl-color--yellow-500 { 280.1542 + background-color: unquote("rgb(#{$palette-yellow-500})") !important; 280.1543 + } 280.1544 + 280.1545 + .mdl-color-text--yellow-600 { 280.1546 + color: unquote("rgb(#{$palette-yellow-600})") !important; 280.1547 + } 280.1548 + 280.1549 + .mdl-color--yellow-600 { 280.1550 + background-color: unquote("rgb(#{$palette-yellow-600})") !important; 280.1551 + } 280.1552 + 280.1553 + .mdl-color-text--yellow-700 { 280.1554 + color: unquote("rgb(#{$palette-yellow-700})") !important; 280.1555 + } 280.1556 + 280.1557 + .mdl-color--yellow-700 { 280.1558 + background-color: unquote("rgb(#{$palette-yellow-700})") !important; 280.1559 + } 280.1560 + 280.1561 + .mdl-color-text--yellow-800 { 280.1562 + color: unquote("rgb(#{$palette-yellow-800})") !important; 280.1563 + } 280.1564 + 280.1565 + .mdl-color--yellow-800 { 280.1566 + background-color: unquote("rgb(#{$palette-yellow-800})") !important; 280.1567 + } 280.1568 + 280.1569 + .mdl-color-text--yellow-900 { 280.1570 + color: unquote("rgb(#{$palette-yellow-900})") !important; 280.1571 + } 280.1572 + 280.1573 + .mdl-color--yellow-900 { 280.1574 + background-color: unquote("rgb(#{$palette-yellow-900})") !important; 280.1575 + } 280.1576 + 280.1577 + .mdl-color-text--yellow-A100 { 280.1578 + color: unquote("rgb(#{$palette-yellow-A100})") !important; 280.1579 + } 280.1580 + 280.1581 + .mdl-color--yellow-A100 { 280.1582 + background-color: unquote("rgb(#{$palette-yellow-A100})") !important; 280.1583 + } 280.1584 + 280.1585 + .mdl-color-text--yellow-A200 { 280.1586 + color: unquote("rgb(#{$palette-yellow-A200})") !important; 280.1587 + } 280.1588 + 280.1589 + .mdl-color--yellow-A200 { 280.1590 + background-color: unquote("rgb(#{$palette-yellow-A200})") !important; 280.1591 + } 280.1592 + 280.1593 + .mdl-color-text--yellow-A400 { 280.1594 + color: unquote("rgb(#{$palette-yellow-A400})") !important; 280.1595 + } 280.1596 + 280.1597 + .mdl-color--yellow-A400 { 280.1598 + background-color: unquote("rgb(#{$palette-yellow-A400})") !important; 280.1599 + } 280.1600 + 280.1601 + .mdl-color-text--yellow-A700 { 280.1602 + color: unquote("rgb(#{$palette-yellow-A700})") !important; 280.1603 + } 280.1604 + 280.1605 + .mdl-color--yellow-A700 { 280.1606 + background-color: unquote("rgb(#{$palette-yellow-A700})") !important; 280.1607 + } 280.1608 + 280.1609 + // Amber 280.1610 + 280.1611 + .mdl-color-text--amber { 280.1612 + color: unquote("rgb(#{$palette-amber-500})") !important; 280.1613 + } 280.1614 + 280.1615 + .mdl-color--amber { 280.1616 + background-color: unquote("rgb(#{$palette-amber-500})") !important; 280.1617 + } 280.1618 + 280.1619 + .mdl-color-text--amber-50 { 280.1620 + color: unquote("rgb(#{$palette-amber-50})") !important; 280.1621 + } 280.1622 + 280.1623 + .mdl-color--amber-50 { 280.1624 + background-color: unquote("rgb(#{$palette-amber-50})") !important; 280.1625 + } 280.1626 + 280.1627 + .mdl-color-text--amber-100 { 280.1628 + color: unquote("rgb(#{$palette-amber-100})") !important; 280.1629 + } 280.1630 + 280.1631 + .mdl-color--amber-100 { 280.1632 + background-color: unquote("rgb(#{$palette-amber-100})") !important; 280.1633 + } 280.1634 + 280.1635 + .mdl-color-text--amber-200 { 280.1636 + color: unquote("rgb(#{$palette-amber-200})") !important; 280.1637 + } 280.1638 + 280.1639 + .mdl-color--amber-200 { 280.1640 + background-color: unquote("rgb(#{$palette-amber-200})") !important; 280.1641 + } 280.1642 + 280.1643 + .mdl-color-text--amber-300 { 280.1644 + color: unquote("rgb(#{$palette-amber-300})") !important; 280.1645 + } 280.1646 + 280.1647 + .mdl-color--amber-300 { 280.1648 + background-color: unquote("rgb(#{$palette-amber-300})") !important; 280.1649 + } 280.1650 + 280.1651 + .mdl-color-text--amber-400 { 280.1652 + color: unquote("rgb(#{$palette-amber-400})") !important; 280.1653 + } 280.1654 + 280.1655 + .mdl-color--amber-400 { 280.1656 + background-color: unquote("rgb(#{$palette-amber-400})") !important; 280.1657 + } 280.1658 + 280.1659 + .mdl-color-text--amber-500 { 280.1660 + color: unquote("rgb(#{$palette-amber-500})") !important; 280.1661 + } 280.1662 + 280.1663 + .mdl-color--amber-500 { 280.1664 + background-color: unquote("rgb(#{$palette-amber-500})") !important; 280.1665 + } 280.1666 + 280.1667 + .mdl-color-text--amber-600 { 280.1668 + color: unquote("rgb(#{$palette-amber-600})") !important; 280.1669 + } 280.1670 + 280.1671 + .mdl-color--amber-600 { 280.1672 + background-color: unquote("rgb(#{$palette-amber-600})") !important; 280.1673 + } 280.1674 + 280.1675 + .mdl-color-text--amber-700 { 280.1676 + color: unquote("rgb(#{$palette-amber-700})") !important; 280.1677 + } 280.1678 + 280.1679 + .mdl-color--amber-700 { 280.1680 + background-color: unquote("rgb(#{$palette-amber-700})") !important; 280.1681 + } 280.1682 + 280.1683 + .mdl-color-text--amber-800 { 280.1684 + color: unquote("rgb(#{$palette-amber-800})") !important; 280.1685 + } 280.1686 + 280.1687 + .mdl-color--amber-800 { 280.1688 + background-color: unquote("rgb(#{$palette-amber-800})") !important; 280.1689 + } 280.1690 + 280.1691 + .mdl-color-text--amber-900 { 280.1692 + color: unquote("rgb(#{$palette-amber-900})") !important; 280.1693 + } 280.1694 + 280.1695 + .mdl-color--amber-900 { 280.1696 + background-color: unquote("rgb(#{$palette-amber-900})") !important; 280.1697 + } 280.1698 + 280.1699 + .mdl-color-text--amber-A100 { 280.1700 + color: unquote("rgb(#{$palette-amber-A100})") !important; 280.1701 + } 280.1702 + 280.1703 + .mdl-color--amber-A100 { 280.1704 + background-color: unquote("rgb(#{$palette-amber-A100})") !important; 280.1705 + } 280.1706 + 280.1707 + .mdl-color-text--amber-A200 { 280.1708 + color: unquote("rgb(#{$palette-amber-A200})") !important; 280.1709 + } 280.1710 + 280.1711 + .mdl-color--amber-A200 { 280.1712 + background-color: unquote("rgb(#{$palette-amber-A200})") !important; 280.1713 + } 280.1714 + 280.1715 + .mdl-color-text--amber-A400 { 280.1716 + color: unquote("rgb(#{$palette-amber-A400})") !important; 280.1717 + } 280.1718 + 280.1719 + .mdl-color--amber-A400 { 280.1720 + background-color: unquote("rgb(#{$palette-amber-A400})") !important; 280.1721 + } 280.1722 + 280.1723 + .mdl-color-text--amber-A700 { 280.1724 + color: unquote("rgb(#{$palette-amber-A700})") !important; 280.1725 + } 280.1726 + 280.1727 + .mdl-color--amber-A700 { 280.1728 + background-color: unquote("rgb(#{$palette-amber-A700})") !important; 280.1729 + } 280.1730 + 280.1731 + // Orange 280.1732 + 280.1733 + .mdl-color-text--orange { 280.1734 + color: unquote("rgb(#{$palette-orange-500})") !important; 280.1735 + } 280.1736 + 280.1737 + .mdl-color--orange { 280.1738 + background-color: unquote("rgb(#{$palette-orange-500})") !important; 280.1739 + } 280.1740 + 280.1741 + .mdl-color-text--orange-50 { 280.1742 + color: unquote("rgb(#{$palette-orange-50})") !important; 280.1743 + } 280.1744 + 280.1745 + .mdl-color--orange-50 { 280.1746 + background-color: unquote("rgb(#{$palette-orange-50})") !important; 280.1747 + } 280.1748 + 280.1749 + .mdl-color-text--orange-100 { 280.1750 + color: unquote("rgb(#{$palette-orange-100})") !important; 280.1751 + } 280.1752 + 280.1753 + .mdl-color--orange-100 { 280.1754 + background-color: unquote("rgb(#{$palette-orange-100})") !important; 280.1755 + } 280.1756 + 280.1757 + .mdl-color-text--orange-200 { 280.1758 + color: unquote("rgb(#{$palette-orange-200})") !important; 280.1759 + } 280.1760 + 280.1761 + .mdl-color--orange-200 { 280.1762 + background-color: unquote("rgb(#{$palette-orange-200})") !important; 280.1763 + } 280.1764 + 280.1765 + .mdl-color-text--orange-300 { 280.1766 + color: unquote("rgb(#{$palette-orange-300})") !important; 280.1767 + } 280.1768 + 280.1769 + .mdl-color--orange-300 { 280.1770 + background-color: unquote("rgb(#{$palette-orange-300})") !important; 280.1771 + } 280.1772 + 280.1773 + .mdl-color-text--orange-400 { 280.1774 + color: unquote("rgb(#{$palette-orange-400})") !important; 280.1775 + } 280.1776 + 280.1777 + .mdl-color--orange-400 { 280.1778 + background-color: unquote("rgb(#{$palette-orange-400})") !important; 280.1779 + } 280.1780 + 280.1781 + .mdl-color-text--orange-500 { 280.1782 + color: unquote("rgb(#{$palette-orange-500})") !important; 280.1783 + } 280.1784 + 280.1785 + .mdl-color--orange-500 { 280.1786 + background-color: unquote("rgb(#{$palette-orange-500})") !important; 280.1787 + } 280.1788 + 280.1789 + .mdl-color-text--orange-600 { 280.1790 + color: unquote("rgb(#{$palette-orange-600})") !important; 280.1791 + } 280.1792 + 280.1793 + .mdl-color--orange-600 { 280.1794 + background-color: unquote("rgb(#{$palette-orange-600})") !important; 280.1795 + } 280.1796 + 280.1797 + .mdl-color-text--orange-700 { 280.1798 + color: unquote("rgb(#{$palette-orange-700})") !important; 280.1799 + } 280.1800 + 280.1801 + .mdl-color--orange-700 { 280.1802 + background-color: unquote("rgb(#{$palette-orange-700})") !important; 280.1803 + } 280.1804 + 280.1805 + .mdl-color-text--orange-800 { 280.1806 + color: unquote("rgb(#{$palette-orange-800})") !important; 280.1807 + } 280.1808 + 280.1809 + .mdl-color--orange-800 { 280.1810 + background-color: unquote("rgb(#{$palette-orange-800})") !important; 280.1811 + } 280.1812 + 280.1813 + .mdl-color-text--orange-900 { 280.1814 + color: unquote("rgb(#{$palette-orange-900})") !important; 280.1815 + } 280.1816 + 280.1817 + .mdl-color--orange-900 { 280.1818 + background-color: unquote("rgb(#{$palette-orange-900})") !important; 280.1819 + } 280.1820 + 280.1821 + .mdl-color-text--orange-A100 { 280.1822 + color: unquote("rgb(#{$palette-orange-A100})") !important; 280.1823 + } 280.1824 + 280.1825 + .mdl-color--orange-A100 { 280.1826 + background-color: unquote("rgb(#{$palette-orange-A100})") !important; 280.1827 + } 280.1828 + 280.1829 + .mdl-color-text--orange-A200 { 280.1830 + color: unquote("rgb(#{$palette-orange-A200})") !important; 280.1831 + } 280.1832 + 280.1833 + .mdl-color--orange-A200 { 280.1834 + background-color: unquote("rgb(#{$palette-orange-A200})") !important; 280.1835 + } 280.1836 + 280.1837 + .mdl-color-text--orange-A400 { 280.1838 + color: unquote("rgb(#{$palette-orange-A400})") !important; 280.1839 + } 280.1840 + 280.1841 + .mdl-color--orange-A400 { 280.1842 + background-color: unquote("rgb(#{$palette-orange-A400})") !important; 280.1843 + } 280.1844 + 280.1845 + .mdl-color-text--orange-A700 { 280.1846 + color: unquote("rgb(#{$palette-orange-A700})") !important; 280.1847 + } 280.1848 + 280.1849 + .mdl-color--orange-A700 { 280.1850 + background-color: unquote("rgb(#{$palette-orange-A700})") !important; 280.1851 + } 280.1852 + 280.1853 + // Deep Orange 280.1854 + 280.1855 + .mdl-color-text--deep-orange { 280.1856 + color: unquote("rgb(#{$palette-deep-orange-500})") !important; 280.1857 + } 280.1858 + 280.1859 + .mdl-color--deep-orange { 280.1860 + background-color: unquote("rgb(#{$palette-deep-orange-500})") !important; 280.1861 + } 280.1862 + 280.1863 + .mdl-color-text--deep-orange-50 { 280.1864 + color: unquote("rgb(#{$palette-deep-orange-50})") !important; 280.1865 + } 280.1866 + 280.1867 + .mdl-color--deep-orange-50 { 280.1868 + background-color: unquote("rgb(#{$palette-deep-orange-50})") !important; 280.1869 + } 280.1870 + 280.1871 + .mdl-color-text--deep-orange-100 { 280.1872 + color: unquote("rgb(#{$palette-deep-orange-100})") !important; 280.1873 + } 280.1874 + 280.1875 + .mdl-color--deep-orange-100 { 280.1876 + background-color: unquote("rgb(#{$palette-deep-orange-100})") !important; 280.1877 + } 280.1878 + 280.1879 + .mdl-color-text--deep-orange-200 { 280.1880 + color: unquote("rgb(#{$palette-deep-orange-200})") !important; 280.1881 + } 280.1882 + 280.1883 + .mdl-color--deep-orange-200 { 280.1884 + background-color: unquote("rgb(#{$palette-deep-orange-200})") !important; 280.1885 + } 280.1886 + 280.1887 + .mdl-color-text--deep-orange-300 { 280.1888 + color: unquote("rgb(#{$palette-deep-orange-300})") !important; 280.1889 + } 280.1890 + 280.1891 + .mdl-color--deep-orange-300 { 280.1892 + background-color: unquote("rgb(#{$palette-deep-orange-300})") !important; 280.1893 + } 280.1894 + 280.1895 + .mdl-color-text--deep-orange-400 { 280.1896 + color: unquote("rgb(#{$palette-deep-orange-400})") !important; 280.1897 + } 280.1898 + 280.1899 + .mdl-color--deep-orange-400 { 280.1900 + background-color: unquote("rgb(#{$palette-deep-orange-400})") !important; 280.1901 + } 280.1902 + 280.1903 + .mdl-color-text--deep-orange-500 { 280.1904 + color: unquote("rgb(#{$palette-deep-orange-500})") !important; 280.1905 + } 280.1906 + 280.1907 + .mdl-color--deep-orange-500 { 280.1908 + background-color: unquote("rgb(#{$palette-deep-orange-500})") !important; 280.1909 + } 280.1910 + 280.1911 + .mdl-color-text--deep-orange-600 { 280.1912 + color: unquote("rgb(#{$palette-deep-orange-600})") !important; 280.1913 + } 280.1914 + 280.1915 + .mdl-color--deep-orange-600 { 280.1916 + background-color: unquote("rgb(#{$palette-deep-orange-600})") !important; 280.1917 + } 280.1918 + 280.1919 + .mdl-color-text--deep-orange-700 { 280.1920 + color: unquote("rgb(#{$palette-deep-orange-700})") !important; 280.1921 + } 280.1922 + 280.1923 + .mdl-color--deep-orange-700 { 280.1924 + background-color: unquote("rgb(#{$palette-deep-orange-700})") !important; 280.1925 + } 280.1926 + 280.1927 + .mdl-color-text--deep-orange-800 { 280.1928 + color: unquote("rgb(#{$palette-deep-orange-800})") !important; 280.1929 + } 280.1930 + 280.1931 + .mdl-color--deep-orange-800 { 280.1932 + background-color: unquote("rgb(#{$palette-deep-orange-800})") !important; 280.1933 + } 280.1934 + 280.1935 + .mdl-color-text--deep-orange-900 { 280.1936 + color: unquote("rgb(#{$palette-deep-orange-900})") !important; 280.1937 + } 280.1938 + 280.1939 + .mdl-color--deep-orange-900 { 280.1940 + background-color: unquote("rgb(#{$palette-deep-orange-900})") !important; 280.1941 + } 280.1942 + 280.1943 + .mdl-color-text--deep-orange-A100 { 280.1944 + color: unquote("rgb(#{$palette-deep-orange-A100})") !important; 280.1945 + } 280.1946 + 280.1947 + .mdl-color--deep-orange-A100 { 280.1948 + background-color: unquote("rgb(#{$palette-deep-orange-A100})") !important; 280.1949 + } 280.1950 + 280.1951 + .mdl-color-text--deep-orange-A200 { 280.1952 + color: unquote("rgb(#{$palette-deep-orange-A200})") !important; 280.1953 + } 280.1954 + 280.1955 + .mdl-color--deep-orange-A200 { 280.1956 + background-color: unquote("rgb(#{$palette-deep-orange-A200})") !important; 280.1957 + } 280.1958 + 280.1959 + .mdl-color-text--deep-orange-A400 { 280.1960 + color: unquote("rgb(#{$palette-deep-orange-A400})") !important; 280.1961 + } 280.1962 + 280.1963 + .mdl-color--deep-orange-A400 { 280.1964 + background-color: unquote("rgb(#{$palette-deep-orange-A400})") !important; 280.1965 + } 280.1966 + 280.1967 + .mdl-color-text--deep-orange-A700 { 280.1968 + color: unquote("rgb(#{$palette-deep-orange-A700})") !important; 280.1969 + } 280.1970 + 280.1971 + .mdl-color--deep-orange-A700 { 280.1972 + background-color: unquote("rgb(#{$palette-deep-orange-A700})") !important; 280.1973 + } 280.1974 + 280.1975 + // Brown 280.1976 + 280.1977 + .mdl-color-text--brown { 280.1978 + color: unquote("rgb(#{$palette-brown-500})") !important; 280.1979 + } 280.1980 + 280.1981 + .mdl-color--brown { 280.1982 + background-color: unquote("rgb(#{$palette-brown-500})") !important; 280.1983 + } 280.1984 + 280.1985 + .mdl-color-text--brown-50 { 280.1986 + color: unquote("rgb(#{$palette-brown-50})") !important; 280.1987 + } 280.1988 + 280.1989 + .mdl-color--brown-50 { 280.1990 + background-color: unquote("rgb(#{$palette-brown-50})") !important; 280.1991 + } 280.1992 + 280.1993 + .mdl-color-text--brown-100 { 280.1994 + color: unquote("rgb(#{$palette-brown-100})") !important; 280.1995 + } 280.1996 + 280.1997 + .mdl-color--brown-100 { 280.1998 + background-color: unquote("rgb(#{$palette-brown-100})") !important; 280.1999 + } 280.2000 + 280.2001 + .mdl-color-text--brown-200 { 280.2002 + color: unquote("rgb(#{$palette-brown-200})") !important; 280.2003 + } 280.2004 + 280.2005 + .mdl-color--brown-200 { 280.2006 + background-color: unquote("rgb(#{$palette-brown-200})") !important; 280.2007 + } 280.2008 + 280.2009 + .mdl-color-text--brown-300 { 280.2010 + color: unquote("rgb(#{$palette-brown-300})") !important; 280.2011 + } 280.2012 + 280.2013 + .mdl-color--brown-300 { 280.2014 + background-color: unquote("rgb(#{$palette-brown-300})") !important; 280.2015 + } 280.2016 + 280.2017 + .mdl-color-text--brown-400 { 280.2018 + color: unquote("rgb(#{$palette-brown-400})") !important; 280.2019 + } 280.2020 + 280.2021 + .mdl-color--brown-400 { 280.2022 + background-color: unquote("rgb(#{$palette-brown-400})") !important; 280.2023 + } 280.2024 + 280.2025 + .mdl-color-text--brown-500 { 280.2026 + color: unquote("rgb(#{$palette-brown-500})") !important; 280.2027 + } 280.2028 + 280.2029 + .mdl-color--brown-500 { 280.2030 + background-color: unquote("rgb(#{$palette-brown-500})") !important; 280.2031 + } 280.2032 + 280.2033 + .mdl-color-text--brown-600 { 280.2034 + color: unquote("rgb(#{$palette-brown-600})") !important; 280.2035 + } 280.2036 + 280.2037 + .mdl-color--brown-600 { 280.2038 + background-color: unquote("rgb(#{$palette-brown-600})") !important; 280.2039 + } 280.2040 + 280.2041 + .mdl-color-text--brown-700 { 280.2042 + color: unquote("rgb(#{$palette-brown-700})") !important; 280.2043 + } 280.2044 + 280.2045 + .mdl-color--brown-700 { 280.2046 + background-color: unquote("rgb(#{$palette-brown-700})") !important; 280.2047 + } 280.2048 + 280.2049 + .mdl-color-text--brown-800 { 280.2050 + color: unquote("rgb(#{$palette-brown-800})") !important; 280.2051 + } 280.2052 + 280.2053 + .mdl-color--brown-800 { 280.2054 + background-color: unquote("rgb(#{$palette-brown-800})") !important; 280.2055 + } 280.2056 + 280.2057 + .mdl-color-text--brown-900 { 280.2058 + color: unquote("rgb(#{$palette-brown-900})") !important; 280.2059 + } 280.2060 + 280.2061 + .mdl-color--brown-900 { 280.2062 + background-color: unquote("rgb(#{$palette-brown-900})") !important; 280.2063 + } 280.2064 + 280.2065 + // Grey 280.2066 + 280.2067 + .mdl-color-text--grey { 280.2068 + color: unquote("rgb(#{$palette-grey-500})") !important; 280.2069 + } 280.2070 + 280.2071 + .mdl-color--grey { 280.2072 + background-color: unquote("rgb(#{$palette-grey-500})") !important; 280.2073 + } 280.2074 + 280.2075 + .mdl-color-text--grey-50 { 280.2076 + color: unquote("rgb(#{$palette-grey-50})") !important; 280.2077 + } 280.2078 + 280.2079 + .mdl-color--grey-50 { 280.2080 + background-color: unquote("rgb(#{$palette-grey-50})") !important; 280.2081 + } 280.2082 + 280.2083 + .mdl-color-text--grey-100 { 280.2084 + color: unquote("rgb(#{$palette-grey-100})") !important; 280.2085 + } 280.2086 + 280.2087 + .mdl-color--grey-100 { 280.2088 + background-color: unquote("rgb(#{$palette-grey-100})") !important; 280.2089 + } 280.2090 + 280.2091 + .mdl-color-text--grey-200 { 280.2092 + color: unquote("rgb(#{$palette-grey-200})") !important; 280.2093 + } 280.2094 + 280.2095 + .mdl-color--grey-200 { 280.2096 + background-color: unquote("rgb(#{$palette-grey-200})") !important; 280.2097 + } 280.2098 + 280.2099 + .mdl-color-text--grey-300 { 280.2100 + color: unquote("rgb(#{$palette-grey-300})") !important; 280.2101 + } 280.2102 + 280.2103 + .mdl-color--grey-300 { 280.2104 + background-color: unquote("rgb(#{$palette-grey-300})") !important; 280.2105 + } 280.2106 + 280.2107 + .mdl-color-text--grey-400 { 280.2108 + color: unquote("rgb(#{$palette-grey-400})") !important; 280.2109 + } 280.2110 + 280.2111 + .mdl-color--grey-400 { 280.2112 + background-color: unquote("rgb(#{$palette-grey-400})") !important; 280.2113 + } 280.2114 + 280.2115 + .mdl-color-text--grey-500 { 280.2116 + color: unquote("rgb(#{$palette-grey-500})") !important; 280.2117 + } 280.2118 + 280.2119 + .mdl-color--grey-500 { 280.2120 + background-color: unquote("rgb(#{$palette-grey-500})") !important; 280.2121 + } 280.2122 + 280.2123 + .mdl-color-text--grey-600 { 280.2124 + color: unquote("rgb(#{$palette-grey-600})") !important; 280.2125 + } 280.2126 + 280.2127 + .mdl-color--grey-600 { 280.2128 + background-color: unquote("rgb(#{$palette-grey-600})") !important; 280.2129 + } 280.2130 + 280.2131 + .mdl-color-text--grey-700 { 280.2132 + color: unquote("rgb(#{$palette-grey-700})") !important; 280.2133 + } 280.2134 + 280.2135 + .mdl-color--grey-700 { 280.2136 + background-color: unquote("rgb(#{$palette-grey-700})") !important; 280.2137 + } 280.2138 + 280.2139 + .mdl-color-text--grey-800 { 280.2140 + color: unquote("rgb(#{$palette-grey-800})") !important; 280.2141 + } 280.2142 + 280.2143 + .mdl-color--grey-800 { 280.2144 + background-color: unquote("rgb(#{$palette-grey-800})") !important; 280.2145 + } 280.2146 + 280.2147 + .mdl-color-text--grey-900 { 280.2148 + color: unquote("rgb(#{$palette-grey-900})") !important; 280.2149 + } 280.2150 + 280.2151 + .mdl-color--grey-900 { 280.2152 + background-color: unquote("rgb(#{$palette-grey-900})") !important; 280.2153 + } 280.2154 + 280.2155 + // Blue Grey 280.2156 + 280.2157 + .mdl-color-text--blue-grey { 280.2158 + color: unquote("rgb(#{$palette-blue-grey-500})") !important; 280.2159 + } 280.2160 + 280.2161 + .mdl-color--blue-grey { 280.2162 + background-color: unquote("rgb(#{$palette-blue-grey-500})") !important; 280.2163 + } 280.2164 + 280.2165 + .mdl-color-text--blue-grey-50 { 280.2166 + color: unquote("rgb(#{$palette-blue-grey-50})") !important; 280.2167 + } 280.2168 + 280.2169 + .mdl-color--blue-grey-50 { 280.2170 + background-color: unquote("rgb(#{$palette-blue-grey-50})") !important; 280.2171 + } 280.2172 + 280.2173 + .mdl-color-text--blue-grey-100 { 280.2174 + color: unquote("rgb(#{$palette-blue-grey-100})") !important; 280.2175 + } 280.2176 + 280.2177 + .mdl-color--blue-grey-100 { 280.2178 + background-color: unquote("rgb(#{$palette-blue-grey-100})") !important; 280.2179 + } 280.2180 + 280.2181 + .mdl-color-text--blue-grey-200 { 280.2182 + color: unquote("rgb(#{$palette-blue-grey-200})") !important; 280.2183 + } 280.2184 + 280.2185 + .mdl-color--blue-grey-200 { 280.2186 + background-color: unquote("rgb(#{$palette-blue-grey-200})") !important; 280.2187 + } 280.2188 + 280.2189 + .mdl-color-text--blue-grey-300 { 280.2190 + color: unquote("rgb(#{$palette-blue-grey-300})") !important; 280.2191 + } 280.2192 + 280.2193 + .mdl-color--blue-grey-300 { 280.2194 + background-color: unquote("rgb(#{$palette-blue-grey-300})") !important; 280.2195 + } 280.2196 + 280.2197 + .mdl-color-text--blue-grey-400 { 280.2198 + color: unquote("rgb(#{$palette-blue-grey-400})") !important; 280.2199 + } 280.2200 + 280.2201 + .mdl-color--blue-grey-400 { 280.2202 + background-color: unquote("rgb(#{$palette-blue-grey-400})") !important; 280.2203 + } 280.2204 + 280.2205 + .mdl-color-text--blue-grey-500 { 280.2206 + color: unquote("rgb(#{$palette-blue-grey-500})") !important; 280.2207 + } 280.2208 + 280.2209 + .mdl-color--blue-grey-500 { 280.2210 + background-color: unquote("rgb(#{$palette-blue-grey-500})") !important; 280.2211 + } 280.2212 + 280.2213 + .mdl-color-text--blue-grey-600 { 280.2214 + color: unquote("rgb(#{$palette-blue-grey-600})") !important; 280.2215 + } 280.2216 + 280.2217 + .mdl-color--blue-grey-600 { 280.2218 + background-color: unquote("rgb(#{$palette-blue-grey-600})") !important; 280.2219 + } 280.2220 + 280.2221 + .mdl-color-text--blue-grey-700 { 280.2222 + color: unquote("rgb(#{$palette-blue-grey-700})") !important; 280.2223 + } 280.2224 + 280.2225 + .mdl-color--blue-grey-700 { 280.2226 + background-color: unquote("rgb(#{$palette-blue-grey-700})") !important; 280.2227 + } 280.2228 + 280.2229 + .mdl-color-text--blue-grey-800 { 280.2230 + color: unquote("rgb(#{$palette-blue-grey-800})") !important; 280.2231 + } 280.2232 + 280.2233 + .mdl-color--blue-grey-800 { 280.2234 + background-color: unquote("rgb(#{$palette-blue-grey-800})") !important; 280.2235 + } 280.2236 + 280.2237 + .mdl-color-text--blue-grey-900 { 280.2238 + color: unquote("rgb(#{$palette-blue-grey-900})") !important; 280.2239 + } 280.2240 + 280.2241 + .mdl-color--blue-grey-900 { 280.2242 + background-color: unquote("rgb(#{$palette-blue-grey-900})") !important; 280.2243 + } 280.2244 + 280.2245 + // Black 280.2246 + 280.2247 + .mdl-color--black { 280.2248 + background-color: unquote("rgb(#{$color-black})") !important; 280.2249 + } 280.2250 + 280.2251 + .mdl-color-text--black { 280.2252 + color: unquote("rgb(#{$color-black})") !important; 280.2253 + } 280.2254 + 280.2255 + // White 280.2256 + 280.2257 + .mdl-color--white { 280.2258 + background-color: unquote("rgb(#{$color-white})") !important; 280.2259 + } 280.2260 + 280.2261 + .mdl-color-text--white { 280.2262 + color: unquote("rgb(#{$color-white})") !important; 280.2263 + } 280.2264 +} 280.2265 + 280.2266 +// Primary and accent 280.2267 + 280.2268 +.mdl-color--primary { 280.2269 + background-color: unquote("rgb(#{$color-primary})") !important; 280.2270 +} 280.2271 + 280.2272 +.mdl-color--primary-contrast { 280.2273 + background-color: unquote("rgb(#{$color-primary-contrast})") !important; 280.2274 +} 280.2275 + 280.2276 +.mdl-color--primary-dark { 280.2277 + background-color: unquote("rgb(#{$color-primary-dark})") !important; 280.2278 +} 280.2279 + 280.2280 +.mdl-color--accent { 280.2281 + background-color: unquote("rgb(#{$color-accent})") !important; 280.2282 +} 280.2283 + 280.2284 +.mdl-color--accent-contrast { 280.2285 + background-color: unquote("rgb(#{$color-accent-contrast})") !important; 280.2286 +} 280.2287 + 280.2288 +.mdl-color-text--primary { 280.2289 + color: unquote("rgb(#{$color-primary})") !important; 280.2290 +} 280.2291 + 280.2292 +.mdl-color-text--primary-contrast { 280.2293 + color: unquote("rgb(#{$color-primary-contrast})") !important; 280.2294 +} 280.2295 + 280.2296 +.mdl-color-text--primary-dark { 280.2297 + color: unquote("rgb(#{$color-primary-dark})") !important; 280.2298 +} 280.2299 + 280.2300 +.mdl-color-text--accent { 280.2301 + color: unquote("rgb(#{$color-accent})") !important; 280.2302 +} 280.2303 + 280.2304 +.mdl-color-text--accent-contrast { 280.2305 + color: unquote("rgb(#{$color-accent-contrast})") !important; 280.2306 +}
281.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 281.2 +++ b/style/mdl/progress/_progress.scss Sun Jul 15 14:07:29 2018 +0200 281.3 @@ -0,0 +1,120 @@ 281.4 +/** 281.5 + * Copyright 2015 Google Inc. All Rights Reserved. 281.6 + * 281.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 281.8 + * you may not use this file except in compliance with the License. 281.9 + * You may obtain a copy of the License at 281.10 + * 281.11 + * http://www.apache.org/licenses/LICENSE-2.0 281.12 + * 281.13 + * Unless required by applicable law or agreed to in writing, software 281.14 + * distributed under the License is distributed on an "AS IS" BASIS, 281.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 281.16 + * See the License for the specific language governing permissions and 281.17 + * limitations under the License. 281.18 + */ 281.19 + 281.20 +@import "../variables"; 281.21 + 281.22 +.mdl-progress { 281.23 + display: block; 281.24 + position: relative; 281.25 + height: $bar-height; 281.26 + width: 500px; 281.27 + max-width: 100%; 281.28 +} 281.29 + 281.30 +.mdl-progress > .bar { 281.31 + display: block; 281.32 + position: absolute; 281.33 + top: 0; 281.34 + bottom: 0; 281.35 + width: 0%; 281.36 + transition: width 0.2s $animation-curve-default; 281.37 +} 281.38 + 281.39 +.mdl-progress > .progressbar { 281.40 + background-color: $progress-main-color; 281.41 + z-index: 1; 281.42 + left: 0; 281.43 +} 281.44 + 281.45 +.mdl-progress > .bufferbar { 281.46 + background-image: linear-gradient(to right, $progress-secondary-color, $progress-secondary-color), 281.47 + linear-gradient(to right, $progress-main-color, $progress-main-color); 281.48 + z-index: 0; 281.49 + left: 0; 281.50 +} 281.51 + 281.52 +.mdl-progress > .auxbar { 281.53 + right: 0; 281.54 +} 281.55 + 281.56 +// Webkit only 281.57 +@supports (-webkit-appearance:none) { 281.58 + .mdl-progress:not(.mdl-progress--indeterminate):not(.mdl-progress--indeterminate) > .auxbar, 281.59 + .mdl-progress:not(.mdl-progress__indeterminate):not(.mdl-progress__indeterminate) > .auxbar { 281.60 + background-image: linear-gradient(to right, $progress-secondary-color, $progress-secondary-color), 281.61 + linear-gradient(to right, $progress-main-color, $progress-main-color); 281.62 + mask: url('#{$progress-image-path}/buffer.svg?embed'); 281.63 + } 281.64 +} 281.65 + 281.66 +.mdl-progress:not(.mdl-progress--indeterminate) > .auxbar, 281.67 +.mdl-progress:not(.mdl-progress__indeterminate) > .auxbar { 281.68 + background-image: linear-gradient(to right, $progress-fallback-buffer-color, $progress-fallback-buffer-color), 281.69 + linear-gradient(to right, $progress-main-color, $progress-main-color); 281.70 +} 281.71 + 281.72 +.mdl-progress.mdl-progress--indeterminate > .bar1, 281.73 +.mdl-progress.mdl-progress__indeterminate > .bar1 { 281.74 + background-color: $progress-main-color; 281.75 + animation-name: indeterminate1; 281.76 + animation-duration: 2s; 281.77 + animation-iteration-count: infinite; 281.78 + animation-timing-function: linear; 281.79 +} 281.80 + 281.81 +.mdl-progress.mdl-progress--indeterminate > .bar3, 281.82 +.mdl-progress.mdl-progress__indeterminate > .bar3 { 281.83 + background-image: none; 281.84 + background-color: $progress-main-color; 281.85 + animation-name: indeterminate2; 281.86 + animation-duration: 2s; 281.87 + animation-iteration-count: infinite; 281.88 + animation-timing-function: linear; 281.89 +} 281.90 + 281.91 +@keyframes indeterminate1 { 281.92 + 0% { 281.93 + left: 0%; 281.94 + width: 0%; 281.95 + } 281.96 + 50% { 281.97 + left: 25%; 281.98 + width: 75%; 281.99 + } 281.100 + 75% { 281.101 + left: 100%; 281.102 + width: 0%; 281.103 + } 281.104 +} 281.105 + 281.106 +@keyframes indeterminate2 { 281.107 + 0% { 281.108 + left: 0%; 281.109 + width: 0%; 281.110 + } 281.111 + 50% { 281.112 + left: 0%; 281.113 + width: 0%; 281.114 + } 281.115 + 75% { 281.116 + left: 0%; 281.117 + width: 25%; 281.118 + } 281.119 + 100% { 281.120 + left: 100%; 281.121 + width: 0%; 281.122 + } 281.123 +}
282.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 282.2 +++ b/style/mdl/radio/_radio.scss Sun Jul 15 14:07:29 2018 +0200 282.3 @@ -0,0 +1,160 @@ 282.4 +/** 282.5 + * Copyright 2015 Google Inc. All Rights Reserved. 282.6 + * 282.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 282.8 + * you may not use this file except in compliance with the License. 282.9 + * You may obtain a copy of the License at 282.10 + * 282.11 + * http://www.apache.org/licenses/LICENSE-2.0 282.12 + * 282.13 + * Unless required by applicable law or agreed to in writing, software 282.14 + * distributed under the License is distributed on an "AS IS" BASIS, 282.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 282.16 + * See the License for the specific language governing permissions and 282.17 + * limitations under the License. 282.18 + */ 282.19 + 282.20 +@import "../variables"; 282.21 +@import "../mixins"; 282.22 + 282.23 +.mdl-radio { 282.24 + position: relative; 282.25 + 282.26 + font-size: $radio-label-font-size; 282.27 + line-height: $radio-label-height; 282.28 + 282.29 + display: inline-block; 282.30 + 282.31 + box-sizing: border-box; 282.32 + margin: 0; 282.33 + padding-left: 0; 282.34 + 282.35 + &.is-upgraded { 282.36 + padding-left: $radio-button-size + $radio-padding; 282.37 + } 282.38 +} 282.39 + 282.40 +.mdl-radio__button { 282.41 + line-height: $radio-label-height; 282.42 + 282.43 + .mdl-radio.is-upgraded & { 282.44 + // Hide input element, while still making it respond to focus. 282.45 + position: absolute; 282.46 + width: 0; 282.47 + height: 0; 282.48 + margin: 0; 282.49 + padding: 0; 282.50 + opacity: 0; 282.51 + -ms-appearance: none; 282.52 + -moz-appearance: none; 282.53 + -webkit-appearance: none; 282.54 + appearance: none; 282.55 + border: none; 282.56 + } 282.57 +} 282.58 + 282.59 +.mdl-radio__outer-circle { 282.60 + position: absolute; 282.61 + top: $radio-top-offset; 282.62 + left: 0; 282.63 + 282.64 + display: inline-block; 282.65 + 282.66 + box-sizing: border-box; 282.67 + width: $radio-button-size; 282.68 + height: $radio-button-size; 282.69 + margin: 0; 282.70 + 282.71 + cursor: pointer; 282.72 + 282.73 + border: 2px solid $radio-off-color; 282.74 + border-radius: 50%; 282.75 + 282.76 + z-index: 2; 282.77 + 282.78 + .mdl-radio.is-checked & { 282.79 + border: 2px solid $radio-color; 282.80 + } 282.81 + 282.82 + fieldset[disabled] .mdl-radio, 282.83 + .mdl-radio.is-disabled & { 282.84 + border: 2px solid $radio-disabled-color; 282.85 + cursor: auto; 282.86 + } 282.87 +} 282.88 + 282.89 +.mdl-radio__inner-circle { 282.90 + position: absolute; 282.91 + z-index: 1; 282.92 + margin: 0; 282.93 + top: $radio-top-offset + $radio-inner-margin; 282.94 + left: $radio-inner-margin; 282.95 + 282.96 + box-sizing: border-box; 282.97 + width: $radio-button-size - ($radio-inner-margin * 2); 282.98 + height: $radio-button-size - ($radio-inner-margin * 2); 282.99 + 282.100 + cursor: pointer; 282.101 + 282.102 + @include material-animation-default(0.28s); 282.103 + transition-property: transform; 282.104 + transform: scale3d(0, 0, 0); 282.105 + 282.106 + border-radius: 50%; 282.107 + background: $radio-color; 282.108 + 282.109 + .mdl-radio.is-checked & { 282.110 + transform: scale3d(1, 1, 1); 282.111 + } 282.112 + 282.113 + fieldset[disabled] .mdl-radio &, 282.114 + .mdl-radio.is-disabled & { 282.115 + background: $radio-disabled-color; 282.116 + cursor: auto; 282.117 + } 282.118 + 282.119 + .mdl-radio.is-focused & { 282.120 + box-shadow: 0 0 0px 10px rgba(0, 0, 0, 0.1); 282.121 + } 282.122 +} 282.123 + 282.124 +.mdl-radio__label { 282.125 + cursor: pointer; 282.126 + 282.127 + fieldset[disabled] .mdl-radio &, 282.128 + .mdl-radio.is-disabled & { 282.129 + color: $radio-disabled-color; 282.130 + cursor: auto; 282.131 + } 282.132 +} 282.133 + 282.134 +.mdl-radio__ripple-container { 282.135 + position: absolute; 282.136 + z-index: 2; 282.137 + top: -(($radio-ripple-size - $radio-label-height) / 2); 282.138 + left: -(($radio-ripple-size - $radio-button-size) / 2); 282.139 + 282.140 + box-sizing: border-box; 282.141 + width: $radio-ripple-size; 282.142 + height: $radio-ripple-size; 282.143 + border-radius: 50%; 282.144 + 282.145 + cursor: pointer; 282.146 + 282.147 + overflow: hidden; 282.148 + -webkit-mask-image: -webkit-radial-gradient(circle, white, black); 282.149 + 282.150 + & .mdl-ripple { 282.151 + background: $radio-color; 282.152 + } 282.153 + 282.154 + fieldset[disabled] .mdl-radio &, 282.155 + .mdl-radio.is-disabled & { 282.156 + cursor: auto; 282.157 + } 282.158 + 282.159 + fieldset[disabled] .mdl-radio & .mdl-ripple, 282.160 + .mdl-radio.is-disabled & .mdl-ripple { 282.161 + background: transparent; 282.162 + } 282.163 +}
283.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 283.2 +++ b/style/mdl/resets/_h5bp.scss Sun Jul 15 14:07:29 2018 +0200 283.3 @@ -0,0 +1,289 @@ 283.4 +/** 283.5 + * Copyright 2015 Google Inc. All Rights Reserved. 283.6 + * 283.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 283.8 + * you may not use this file except in compliance with the License. 283.9 + * You may obtain a copy of the License at 283.10 + * 283.11 + * http://www.apache.org/licenses/LICENSE-2.0 283.12 + * 283.13 + * Unless required by applicable law or agreed to in writing, software 283.14 + * distributed under the License is distributed on an "AS IS" BASIS, 283.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 283.16 + * See the License for the specific language governing permissions and 283.17 + * limitations under the License. 283.18 + */ 283.19 + 283.20 +@import "../variables"; 283.21 + 283.22 +/* 283.23 + * What follows is the result of much research on cross-browser styling. 283.24 + * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 283.25 + * Kroc Camen, and the H5BP dev community and team. 283.26 + */ 283.27 + 283.28 +/* ========================================================================== 283.29 + Base styles: opinionated defaults 283.30 + ========================================================================== */ 283.31 + 283.32 +html { 283.33 + color: $text-color-primary; 283.34 + font-size: 1em; 283.35 + line-height: 1.4; 283.36 +} 283.37 + 283.38 +/* 283.39 + * Remove text-shadow in selection highlight: 283.40 + * https://twitter.com/miketaylr/status/12228805301 283.41 + * 283.42 + * These selection rule sets have to be separate. 283.43 + * Customize the background color to match your design. 283.44 + */ 283.45 + 283.46 +::selection { 283.47 + background: #b3d4fc; 283.48 + text-shadow: none; 283.49 +} 283.50 + 283.51 +/* 283.52 + * A better looking default horizontal rule 283.53 + */ 283.54 + 283.55 +hr { 283.56 + display: block; 283.57 + height: 1px; 283.58 + border: 0; 283.59 + border-top: 1px solid #ccc; 283.60 + margin: 1em 0; 283.61 + padding: 0; 283.62 +} 283.63 + 283.64 +/* 283.65 + * Remove the gap between audio, canvas, iframes, 283.66 + * images, videos and the bottom of their containers: 283.67 + * https://github.com/h5bp/html5-boilerplate/issues/440 283.68 + */ 283.69 + 283.70 +audio, 283.71 +canvas, 283.72 +iframe, 283.73 +img, 283.74 +svg, 283.75 +video { 283.76 + vertical-align: middle; 283.77 +} 283.78 + 283.79 +/* 283.80 + * Remove default fieldset styles. 283.81 + */ 283.82 + 283.83 +fieldset { 283.84 + border: 0; 283.85 + margin: 0; 283.86 + padding: 0; 283.87 +} 283.88 + 283.89 +/* 283.90 + * Allow only vertical resizing of textareas. 283.91 + */ 283.92 + 283.93 +textarea { 283.94 + resize: vertical; 283.95 +} 283.96 + 283.97 +/* ========================================================================== 283.98 + Browser Upgrade Prompt 283.99 + ========================================================================== */ 283.100 + 283.101 +.browserupgrade { 283.102 + margin: 0.2em 0; 283.103 + background: #ccc; 283.104 + color: #000; 283.105 + padding: 0.2em 0; 283.106 +} 283.107 + 283.108 +/* ========================================================================== 283.109 + Author's custom styles 283.110 + ========================================================================== */ 283.111 + 283.112 + 283.113 + 283.114 + 283.115 + 283.116 + 283.117 + 283.118 + 283.119 + 283.120 + 283.121 + 283.122 + 283.123 + 283.124 + 283.125 + 283.126 + 283.127 + 283.128 +/* ========================================================================== 283.129 + Helper classes 283.130 + ========================================================================== */ 283.131 + 283.132 +/* 283.133 + * Hide visually and from screen readers: 283.134 + */ 283.135 + 283.136 +.hidden { 283.137 + display: none !important; 283.138 +} 283.139 + 283.140 +/* 283.141 + * Hide only visually, but have it available for screen readers: 283.142 + * http://snook.ca/archives/html_and_css/hiding-content-for-accessibility 283.143 + */ 283.144 + 283.145 +.visuallyhidden { 283.146 + border: 0; 283.147 + clip: rect(0 0 0 0); 283.148 + height: 1px; 283.149 + margin: -1px; 283.150 + overflow: hidden; 283.151 + padding: 0; 283.152 + position: absolute; 283.153 + width: 1px; 283.154 +} 283.155 + 283.156 +/* 283.157 + * Extends the .visuallyhidden class to allow the element 283.158 + * to be focusable when navigated to via the keyboard: 283.159 + * https://www.drupal.org/node/897638 283.160 + */ 283.161 + 283.162 +.visuallyhidden.focusable:active, 283.163 +.visuallyhidden.focusable:focus { 283.164 + clip: auto; 283.165 + height: auto; 283.166 + margin: 0; 283.167 + overflow: visible; 283.168 + position: static; 283.169 + width: auto; 283.170 +} 283.171 + 283.172 +/* 283.173 + * Hide visually and from screen readers, but maintain layout 283.174 + */ 283.175 + 283.176 +.invisible { 283.177 + visibility: hidden; 283.178 +} 283.179 + 283.180 +/* 283.181 + * Clearfix: contain floats 283.182 + * 283.183 + * For modern browsers 283.184 + * 1. The space content is one way to avoid an Opera bug when the 283.185 + * `contenteditable` attribute is included anywhere else in the document. 283.186 + * Otherwise it causes space to appear at the top and bottom of elements 283.187 + * that receive the `clearfix` class. 283.188 + * 2. The use of `table` rather than `block` is only necessary if using 283.189 + * `:before` to contain the top-margins of child elements. 283.190 + */ 283.191 + 283.192 +.clearfix:before, 283.193 +.clearfix:after { 283.194 + content: " "; /* 1 */ 283.195 + display: table; /* 2 */ 283.196 +} 283.197 + 283.198 +.clearfix:after { 283.199 + clear: both; 283.200 +} 283.201 + 283.202 +/* ========================================================================== 283.203 + EXAMPLE Media Queries for Responsive Design. 283.204 + These examples override the primary ('mobile first') styles. 283.205 + Modify as content requires. 283.206 + ========================================================================== */ 283.207 + 283.208 +@media only screen and (min-width: 35em) { 283.209 + /* Style adjustments for viewports that meet the condition */ 283.210 +} 283.211 + 283.212 +@media print, 283.213 + (min-resolution: 1.25dppx), 283.214 + (min-resolution: 120dpi) { 283.215 + /* Style adjustments for high resolution devices */ 283.216 +} 283.217 + 283.218 +/* ========================================================================== 283.219 + Print styles. 283.220 + Inlined to avoid the additional HTTP request: 283.221 + http://www.phpied.com/delay-loading-your-print-css/ 283.222 + ========================================================================== */ 283.223 + 283.224 +@media print { 283.225 + *, 283.226 + *:before, 283.227 + *:after, 283.228 + *:first-letter { 283.229 + background: transparent !important; 283.230 + color: #000 !important; /* Black prints faster: http://www.sanbeiji.com/archives/953 */ 283.231 + box-shadow: none !important; 283.232 + } 283.233 + 283.234 + a, 283.235 + a:visited { 283.236 + text-decoration: underline; 283.237 + } 283.238 + 283.239 + a[href]:after { 283.240 + content: " (" attr(href) ")"; 283.241 + } 283.242 + 283.243 + abbr[title]:after { 283.244 + content: " (" attr(title) ")"; 283.245 + } 283.246 + 283.247 + /* 283.248 + * Don't show links that are fragment identifiers, 283.249 + * or use the `javascript:` pseudo protocol 283.250 + */ 283.251 + 283.252 + a[href^="#"]:after, 283.253 + a[href^="javascript:"]:after { 283.254 + content: ""; 283.255 + } 283.256 + 283.257 + pre, 283.258 + blockquote { 283.259 + border: 1px solid #999; 283.260 + page-break-inside: avoid; 283.261 + } 283.262 + 283.263 + /* 283.264 + * Printing Tables: 283.265 + * http://css-discuss.incutio.com/wiki/Printing_Tables 283.266 + */ 283.267 + 283.268 + thead { 283.269 + display: table-header-group; 283.270 + } 283.271 + 283.272 + tr, 283.273 + img { 283.274 + page-break-inside: avoid; 283.275 + } 283.276 + 283.277 + img { 283.278 + max-width: 100% !important; 283.279 + } 283.280 + 283.281 + p, 283.282 + h2, 283.283 + h3 { 283.284 + orphans: 3; 283.285 + widows: 3; 283.286 + } 283.287 + 283.288 + h2, 283.289 + h3 { 283.290 + page-break-after: avoid; 283.291 + } 283.292 +}
284.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 284.2 +++ b/style/mdl/resets/_mobile.scss Sun Jul 15 14:07:29 2018 +0200 284.3 @@ -0,0 +1,25 @@ 284.4 +/** 284.5 + * Copyright 2015 Google Inc. All Rights Reserved. 284.6 + * 284.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 284.8 + * you may not use this file except in compliance with the License. 284.9 + * You may obtain a copy of the License at 284.10 + * 284.11 + * http://www.apache.org/licenses/LICENSE-2.0 284.12 + * 284.13 + * Unless required by applicable law or agreed to in writing, software 284.14 + * distributed under the License is distributed on an "AS IS" BASIS, 284.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 284.16 + * See the License for the specific language governing permissions and 284.17 + * limitations under the License. 284.18 + */ 284.19 + 284.20 + 284.21 +/* Remove the unwanted box around FAB buttons */ 284.22 +/* More info: http://goo.gl/IPwKi */ 284.23 +a, .mdl-accordion, .mdl-button, .mdl-card, .mdl-checkbox, .mdl-dropdown-menu, 284.24 +.mdl-icon-toggle, .mdl-item, .mdl-radio, .mdl-slider, .mdl-switch, .mdl-tabs__tab { 284.25 + 284.26 + -webkit-tap-highlight-color: transparent; 284.27 + -webkit-tap-highlight-color: rgba(255, 255, 255, 0); 284.28 +}
285.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 285.2 +++ b/style/mdl/resets/_resets.scss Sun Jul 15 14:07:29 2018 +0200 285.3 @@ -0,0 +1,55 @@ 285.4 +/** 285.5 + * Copyright 2015 Google Inc. All Rights Reserved. 285.6 + * 285.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 285.8 + * you may not use this file except in compliance with the License. 285.9 + * You may obtain a copy of the License at 285.10 + * 285.11 + * http://www.apache.org/licenses/LICENSE-2.0 285.12 + * 285.13 + * Unless required by applicable law or agreed to in writing, software 285.14 + * distributed under the License is distributed on an "AS IS" BASIS, 285.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 285.16 + * See the License for the specific language governing permissions and 285.17 + * limitations under the License. 285.18 + */ 285.19 + 285.20 +@import "h5bp"; 285.21 +@import "mobile"; 285.22 + 285.23 +/* 285.24 + * Make html take up the entire screen 285.25 + * Then set touch-action to avoid touch delay on mobile IE 285.26 + */ 285.27 +html { 285.28 + width: 100%; 285.29 + height: 100%; 285.30 + -ms-touch-action: manipulation; 285.31 + touch-action: manipulation; 285.32 +} 285.33 + 285.34 +/* 285.35 +* Make body take up the entire screen 285.36 +* Remove body margin so layout containers don't cause extra overflow. 285.37 +*/ 285.38 +body { 285.39 + width: 100%; 285.40 + min-height: 100%; 285.41 + margin: 0; 285.42 +} 285.43 + 285.44 +/* 285.45 + * Main display reset for IE support. 285.46 + * Source: http://weblog.west-wind.com/posts/2015/Jan/12/main-HTML5-Tag-not-working-in-Internet-Explorer-91011 285.47 + */ 285.48 +main { 285.49 + display: block; 285.50 +} 285.51 + 285.52 +/* 285.53 +* Apply no display to elements with the hidden attribute. 285.54 +* IE 9 and 10 support. 285.55 +*/ 285.56 +*[hidden] { 285.57 + display: none !important; 285.58 +}
286.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 286.2 +++ b/style/mdl/ripple/_ripple.scss Sun Jul 15 14:07:29 2018 +0200 286.3 @@ -0,0 +1,42 @@ 286.4 +/** 286.5 + * Copyright 2015 Google Inc. All Rights Reserved. 286.6 + * 286.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 286.8 + * you may not use this file except in compliance with the License. 286.9 + * You may obtain a copy of the License at 286.10 + * 286.11 + * http://www.apache.org/licenses/LICENSE-2.0 286.12 + * 286.13 + * Unless required by applicable law or agreed to in writing, software 286.14 + * distributed under the License is distributed on an "AS IS" BASIS, 286.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 286.16 + * See the License for the specific language governing permissions and 286.17 + * limitations under the License. 286.18 + */ 286.19 + 286.20 +@import "../variables"; 286.21 + 286.22 +.mdl-ripple { 286.23 + background : $ripple-bg-color; 286.24 + border-radius : 50%; 286.25 + height : 50px; 286.26 + left : 0; 286.27 + opacity : 0; 286.28 + pointer-events : none; 286.29 + position : absolute; 286.30 + top : 0; 286.31 + transform : translate(-50%, -50%); 286.32 + width : 50px; 286.33 + overflow : hidden; 286.34 + 286.35 + &.is-animating { 286.36 + transition: transform 0.3s $animation-curve-linear-out-slow-in, 286.37 + width 0.3s $animation-curve-linear-out-slow-in, 286.38 + height 0.3s $animation-curve-linear-out-slow-in, 286.39 + opacity 0.6s $animation-curve-linear-out-slow-in; 286.40 + } 286.41 + 286.42 + &.is-visible { 286.43 + opacity: 0.3; 286.44 + } 286.45 +}
287.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 287.2 +++ b/style/mdl/shadow/_shadow.scss Sun Jul 15 14:07:29 2018 +0200 287.3 @@ -0,0 +1,46 @@ 287.4 +/** 287.5 + * Copyright 2015 Google Inc. All Rights Reserved. 287.6 + * 287.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 287.8 + * you may not use this file except in compliance with the License. 287.9 + * You may obtain a copy of the License at 287.10 + * 287.11 + * http://www.apache.org/licenses/LICENSE-2.0 287.12 + * 287.13 + * Unless required by applicable law or agreed to in writing, software 287.14 + * distributed under the License is distributed on an "AS IS" BASIS, 287.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 287.16 + * See the License for the specific language governing permissions and 287.17 + * limitations under the License. 287.18 + */ 287.19 + 287.20 +@import "../variables"; 287.21 +@import "../mixins"; 287.22 + 287.23 +.mdl-shadow--2dp { 287.24 + @include shadow-2dp(); 287.25 +} 287.26 + 287.27 +.mdl-shadow--3dp { 287.28 + @include shadow-3dp(); 287.29 +} 287.30 + 287.31 +.mdl-shadow--4dp { 287.32 + @include shadow-4dp(); 287.33 +} 287.34 + 287.35 +.mdl-shadow--6dp { 287.36 + @include shadow-6dp(); 287.37 +} 287.38 + 287.39 +.mdl-shadow--8dp { 287.40 + @include shadow-8dp(); 287.41 +} 287.42 + 287.43 +.mdl-shadow--16dp { 287.44 + @include shadow-16dp(); 287.45 +} 287.46 + 287.47 +.mdl-shadow--24dp { 287.48 + @include shadow-24dp(); 287.49 +}
288.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 288.2 +++ b/style/mdl/slider/_slider.scss Sun Jul 15 14:07:29 2018 +0200 288.3 @@ -0,0 +1,397 @@ 288.4 +/** 288.5 + * Copyright 2015 Google Inc. All Rights Reserved. 288.6 + * 288.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 288.8 + * you may not use this file except in compliance with the License. 288.9 + * You may obtain a copy of the License at 288.10 + * 288.11 + * http://www.apache.org/licenses/LICENSE-2.0 288.12 + * 288.13 + * Unless required by applicable law or agreed to in writing, software 288.14 + * distributed under the License is distributed on an "AS IS" BASIS, 288.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 288.16 + * See the License for the specific language governing permissions and 288.17 + * limitations under the License. 288.18 + */ 288.19 + 288.20 +@import "../variables"; 288.21 + 288.22 +// Some CSS magic to target only IE. 288.23 +_:-ms-input-placeholder, :root .mdl-slider.mdl-slider.is-upgraded { 288.24 + -ms-appearance: none; 288.25 + // The thumb can't overflow the track or the rest of the control in IE, so we 288.26 + // need to make it tall enough to contain the largest version of the thumb. 288.27 + height: 32px; 288.28 + margin: 0; 288.29 +} 288.30 + 288.31 +// Slider component (styled input[type=range]). 288.32 +.mdl-slider { 288.33 + width: calc(100% - 40px); 288.34 + margin: 0 20px; 288.35 + 288.36 + &.is-upgraded { 288.37 + -webkit-appearance: none; 288.38 + -moz-appearance: none; 288.39 + appearance: none; 288.40 + height: 2px; 288.41 + background: transparent; 288.42 + -webkit-user-select: none; 288.43 + -moz-user-select: none; 288.44 + user-select: none; 288.45 + outline: 0; 288.46 + padding: 0; 288.47 + color: $range-color; 288.48 + align-self: center; 288.49 + z-index: 1; 288.50 + cursor: pointer; 288.51 + 288.52 + 288.53 + // Disable default focus on Firefox. 288.54 + &::-moz-focus-outer { 288.55 + border: 0; 288.56 + } 288.57 + 288.58 + // Disable tooltip on IE. 288.59 + &::-ms-tooltip { 288.60 + display: none; 288.61 + } 288.62 + 288.63 + 288.64 + /**************************** Tracks ****************************/ 288.65 + &::-webkit-slider-runnable-track { 288.66 + background: transparent; 288.67 + } 288.68 + 288.69 + &::-moz-range-track { 288.70 + background: transparent; 288.71 + border: none; 288.72 + } 288.73 + 288.74 + &::-ms-track { 288.75 + background: none; 288.76 + color: transparent; 288.77 + height: 2px; 288.78 + width: 100%; 288.79 + border: none; 288.80 + } 288.81 + 288.82 + &::-ms-fill-lower { 288.83 + padding: 0; 288.84 + // Margin on -ms-track doesn't work right, so we use gradients on the 288.85 + // fills. 288.86 + background: linear-gradient(to right, 288.87 + transparent, 288.88 + transparent 16px, 288.89 + $range-color 16px, 288.90 + $range-color 0); 288.91 + } 288.92 + 288.93 + &::-ms-fill-upper { 288.94 + padding: 0; 288.95 + // Margin on -ms-track doesn't work right, so we use gradients on the 288.96 + // fills. 288.97 + background: linear-gradient(to left, 288.98 + transparent, 288.99 + transparent 16px, 288.100 + $range-bg-color 16px, 288.101 + $range-bg-color 0); 288.102 + } 288.103 + 288.104 + 288.105 + /**************************** Thumbs ****************************/ 288.106 + &::-webkit-slider-thumb { 288.107 + -webkit-appearance: none; 288.108 + width: 12px; 288.109 + height: 12px; 288.110 + box-sizing: border-box; 288.111 + border-radius: 50%; 288.112 + background: $range-color; 288.113 + border: none; 288.114 + transition: transform 0.18s $animation-curve-default, 288.115 + border 0.18s $animation-curve-default, 288.116 + box-shadow 0.18s $animation-curve-default, 288.117 + background 0.28s $animation-curve-default; 288.118 + } 288.119 + 288.120 + &::-moz-range-thumb { 288.121 + -moz-appearance: none; 288.122 + width: 12px; 288.123 + height: 12px; 288.124 + box-sizing: border-box; 288.125 + border-radius: 50%; 288.126 + background-image: none; 288.127 + background: $range-color; 288.128 + border: none; 288.129 + // -moz-range-thumb doesn't currently support transitions. 288.130 + } 288.131 + 288.132 + &:focus:not(:active)::-webkit-slider-thumb { 288.133 + box-shadow: 0 0 0 10px $range-faded-color; 288.134 + } 288.135 + 288.136 + &:focus:not(:active)::-moz-range-thumb { 288.137 + box-shadow: 0 0 0 10px $range-faded-color; 288.138 + } 288.139 + 288.140 + &:active::-webkit-slider-thumb { 288.141 + background-image: none; 288.142 + background: $range-color; 288.143 + transform: scale(1.5); 288.144 + } 288.145 + 288.146 + &:active::-moz-range-thumb { 288.147 + background-image: none; 288.148 + background: $range-color; 288.149 + transform: scale(1.5); 288.150 + } 288.151 + 288.152 + &::-ms-thumb { 288.153 + width: 32px; 288.154 + height: 32px; 288.155 + border: none; 288.156 + border-radius: 50%; 288.157 + background: $range-color; 288.158 + transform: scale(0.375); 288.159 + // -ms-thumb doesn't currently support transitions, but leaving this here 288.160 + // in case support ever gets added. 288.161 + transition: transform 0.18s $animation-curve-default, 288.162 + background 0.28s $animation-curve-default; 288.163 + } 288.164 + 288.165 + &:focus:not(:active)::-ms-thumb { 288.166 + background: radial-gradient(circle closest-side, 288.167 + $range-color 0%, 288.168 + $range-color 37.5%, 288.169 + $range-faded-color 37.5%, 288.170 + $range-faded-color 100%); 288.171 + transform: scale(1); 288.172 + } 288.173 + 288.174 + &:active::-ms-thumb { 288.175 + background: $range-color; 288.176 + transform: scale(0.5625); 288.177 + } 288.178 + 288.179 + 288.180 + /**************************** 0-value ****************************/ 288.181 + &.is-lowest-value::-webkit-slider-thumb { 288.182 + border: 2px solid $range-bg-color; 288.183 + background: transparent; 288.184 + } 288.185 + 288.186 + &.is-lowest-value::-moz-range-thumb { 288.187 + border: 2px solid $range-bg-color; 288.188 + background: transparent; 288.189 + } 288.190 + 288.191 + &.is-lowest-value + 288.192 + .mdl-slider__background-flex > .mdl-slider__background-upper { 288.193 + left: 6px; 288.194 + } 288.195 + 288.196 + &.is-lowest-value:focus:not(:active)::-webkit-slider-thumb { 288.197 + box-shadow: 0 0 0 10px $range-bg-focus-color; 288.198 + background: $range-bg-focus-color; 288.199 + } 288.200 + 288.201 + &.is-lowest-value:focus:not(:active)::-moz-range-thumb { 288.202 + box-shadow: 0 0 0 10px $range-bg-focus-color; 288.203 + background: $range-bg-focus-color; 288.204 + } 288.205 + 288.206 + &.is-lowest-value:active::-webkit-slider-thumb { 288.207 + border: 1.6px solid $range-bg-color; 288.208 + transform: scale(1.5); 288.209 + } 288.210 + 288.211 + &.is-lowest-value:active + 288.212 + .mdl-slider__background-flex > .mdl-slider__background-upper { 288.213 + left: 9px; 288.214 + } 288.215 + 288.216 + &.is-lowest-value:active::-moz-range-thumb { 288.217 + border: 1.5px solid $range-bg-color; 288.218 + transform: scale(1.5); 288.219 + } 288.220 + 288.221 + &.is-lowest-value::-ms-thumb { 288.222 + background: radial-gradient(circle closest-side, 288.223 + transparent 0%, 288.224 + transparent 66.67%, 288.225 + $range-bg-color 66.67%, 288.226 + $range-bg-color 100%); 288.227 + } 288.228 + 288.229 + &.is-lowest-value:focus:not(:active)::-ms-thumb { 288.230 + background: radial-gradient(circle closest-side, 288.231 + $range-bg-focus-color 0%, 288.232 + $range-bg-focus-color 25%, 288.233 + $range-bg-color 25%, 288.234 + $range-bg-color 37.5%, 288.235 + $range-bg-focus-color 37.5%, 288.236 + $range-bg-focus-color 100%); 288.237 + transform: scale(1); 288.238 + } 288.239 + 288.240 + &.is-lowest-value:active::-ms-thumb { 288.241 + transform: scale(0.5625); 288.242 + background: radial-gradient(circle closest-side, 288.243 + transparent 0%, 288.244 + transparent 77.78%, 288.245 + $range-bg-color 77.78%, 288.246 + $range-bg-color 100%); 288.247 + } 288.248 + 288.249 + &.is-lowest-value::-ms-fill-lower { 288.250 + background: transparent; 288.251 + } 288.252 + 288.253 + &.is-lowest-value::-ms-fill-upper { 288.254 + margin-left: 6px; 288.255 + } 288.256 + 288.257 + &.is-lowest-value:active::-ms-fill-upper { 288.258 + margin-left: 9px; 288.259 + } 288.260 + 288.261 + /**************************** Disabled ****************************/ 288.262 + 288.263 + &:disabled:focus::-webkit-slider-thumb, 288.264 + &:disabled:active::-webkit-slider-thumb, 288.265 + &:disabled::-webkit-slider-thumb { 288.266 + transform: scale(0.667); 288.267 + background: $range-bg-color; 288.268 + } 288.269 + 288.270 + &:disabled:focus::-moz-range-thumb, 288.271 + &:disabled:active::-moz-range-thumb, 288.272 + &:disabled::-moz-range-thumb { 288.273 + transform: scale(0.667); 288.274 + background: $range-bg-color; 288.275 + } 288.276 + 288.277 + &:disabled + 288.278 + .mdl-slider__background-flex > .mdl-slider__background-lower { 288.279 + background-color: $range-bg-color; 288.280 + left: -6px; 288.281 + } 288.282 + 288.283 + &:disabled + 288.284 + .mdl-slider__background-flex > .mdl-slider__background-upper { 288.285 + left: 6px; 288.286 + } 288.287 + 288.288 + &.is-lowest-value:disabled:focus::-webkit-slider-thumb, 288.289 + &.is-lowest-value:disabled:active::-webkit-slider-thumb, 288.290 + &.is-lowest-value:disabled::-webkit-slider-thumb { 288.291 + border: 3px solid $range-bg-color; 288.292 + background: transparent; 288.293 + transform: scale(0.667); 288.294 + } 288.295 + 288.296 + &.is-lowest-value:disabled:focus::-moz-range-thumb, 288.297 + &.is-lowest-value:disabled:active::-moz-range-thumb, 288.298 + &.is-lowest-value:disabled::-moz-range-thumb { 288.299 + border: 3px solid $range-bg-color; 288.300 + background: transparent; 288.301 + transform: scale(0.667); 288.302 + } 288.303 + 288.304 + &.is-lowest-value:disabled:active + 288.305 + .mdl-slider__background-flex > .mdl-slider__background-upper { 288.306 + left: 6px; 288.307 + } 288.308 + 288.309 + &:disabled:focus::-ms-thumb, 288.310 + &:disabled:active::-ms-thumb, 288.311 + &:disabled::-ms-thumb { 288.312 + transform: scale(0.25); 288.313 + background: $range-bg-color; 288.314 + } 288.315 + 288.316 + &.is-lowest-value:disabled:focus::-ms-thumb, 288.317 + &.is-lowest-value:disabled:active::-ms-thumb, 288.318 + &.is-lowest-value:disabled::-ms-thumb { 288.319 + transform: scale(0.25); 288.320 + background: radial-gradient(circle closest-side, 288.321 + transparent 0%, 288.322 + transparent 50%, 288.323 + $range-bg-color 50%, 288.324 + $range-bg-color 100%); 288.325 + } 288.326 + 288.327 + &:disabled::-ms-fill-lower { 288.328 + margin-right: 6px; 288.329 + background: linear-gradient(to right, 288.330 + transparent, 288.331 + transparent 25px, 288.332 + $range-bg-color 25px, 288.333 + $range-bg-color 0); 288.334 + } 288.335 + 288.336 + &:disabled::-ms-fill-upper { 288.337 + margin-left: 6px; 288.338 + } 288.339 + 288.340 + &.is-lowest-value:disabled:active::-ms-fill-upper { 288.341 + margin-left: 6px; 288.342 + } 288.343 + } 288.344 +} 288.345 + 288.346 + // Since we need to specify a height of 32px in IE, we add a class here for a 288.347 + // container that brings it back to a reasonable height. 288.348 + .mdl-slider__ie-container { 288.349 + height: 18px; 288.350 + overflow: visible; 288.351 + border: none; 288.352 + margin: none; 288.353 + padding: none; 288.354 + } 288.355 + 288.356 + // We use a set of divs behind the track to style it in all non-IE browsers. 288.357 + // This one contains both the background and the slider. 288.358 + .mdl-slider__container { 288.359 + height: 18px; 288.360 + position: relative; 288.361 + background: none; 288.362 + display: flex; 288.363 + flex-direction: row; 288.364 + } 288.365 + 288.366 + // This one sets up a flex box for the styled upper and lower portions of the 288.367 + // the slider track. 288.368 + .mdl-slider__background-flex { 288.369 + background: transparent; 288.370 + position: absolute; 288.371 + height: 2px; 288.372 + width: calc(100% - 52px); 288.373 + top: 50%; 288.374 + left: 0; 288.375 + margin: 0 26px; 288.376 + display: flex; 288.377 + overflow: hidden; 288.378 + border: 0; 288.379 + padding: 0; 288.380 + transform: translate(0, -1px); 288.381 + } 288.382 + 288.383 + // This one styles the lower part of the slider track. 288.384 + .mdl-slider__background-lower { 288.385 + background: $range-color; 288.386 + flex: 0; 288.387 + position: relative; 288.388 + border: 0; 288.389 + padding: 0; 288.390 + } 288.391 + 288.392 + // This one styles the upper part of the slider track. 288.393 + .mdl-slider__background-upper { 288.394 + background: $range-bg-color; 288.395 + flex: 0; 288.396 + position: relative; 288.397 + border: 0; 288.398 + padding: 0; 288.399 + transition: left 0.18s $animation-curve-default 288.400 + }
289.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 289.2 +++ b/style/mdl/snackbar/_snackbar.scss Sun Jul 15 14:07:29 2018 +0200 289.3 @@ -0,0 +1,89 @@ 289.4 +/** 289.5 + * Copyright 2015 Google Inc. All Rights Reserved. 289.6 + * 289.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 289.8 + * you may not use this file except in compliance with the License. 289.9 + * You may obtain a copy of the License at 289.10 + * 289.11 + * http://www.apache.org/licenses/LICENSE-2.0 289.12 + * 289.13 + * Unless required by applicable law or agreed to in writing, software 289.14 + * distributed under the License is distributed on an "AS IS" BASIS, 289.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 289.16 + * See the License for the specific language governing permissions and 289.17 + * limitations under the License. 289.18 + */ 289.19 + 289.20 +@import "../variables"; 289.21 +@import "../mixins"; 289.22 + 289.23 +.mdl-snackbar { 289.24 + position: fixed; 289.25 + bottom: 0; 289.26 + left: 50%; 289.27 + cursor: default; 289.28 + background-color: $snackbar-background-color; 289.29 + z-index: 3; 289.30 + display: block; 289.31 + display: flex; 289.32 + justify-content: space-between; 289.33 + font-family: $preferred_font; 289.34 + will-change: transform; 289.35 + transform: translate(0, 80px); 289.36 + transition: transform 0.25s $animation-curve-fast-out-linear-in; 289.37 + pointer-events: none; 289.38 + @media(max-width: $snackbar-tablet-breakpoint - 1) { 289.39 + width: 100%; 289.40 + left: 0; 289.41 + min-height: 48px; 289.42 + max-height: 80px; 289.43 + } 289.44 + @media(min-width: $snackbar-tablet-breakpoint) { 289.45 + min-width: 288px; 289.46 + max-width: 568px; 289.47 + border-radius: 2px; 289.48 + transform: translate(-50%, 80px); 289.49 + } 289.50 + &--active { 289.51 + transform: translate(0, 0); 289.52 + pointer-events: auto; 289.53 + transition: transform 0.25s $animation-curve-linear-out-slow-in; 289.54 + 289.55 + @media(min-width: $snackbar-tablet-breakpoint) { 289.56 + transform: translate(-50%, 0); 289.57 + } 289.58 + } 289.59 + 289.60 + &__text { 289.61 + padding: 14px 12px 14px 24px; 289.62 + vertical-align: middle; 289.63 + color: white; 289.64 + float: left; 289.65 + } 289.66 + 289.67 + &__action { 289.68 + background: transparent; 289.69 + border: none; 289.70 + color: $snackbar-action-color; 289.71 + float: right; 289.72 + text-transform: uppercase; 289.73 + padding: 14px 24px 14px 12px; 289.74 + @include typo-button(); 289.75 + overflow: hidden; 289.76 + outline: none; 289.77 + opacity: 0; 289.78 + pointer-events: none; 289.79 + cursor: pointer; 289.80 + text-decoration: none; 289.81 + text-align: center; 289.82 + align-self: center; 289.83 + 289.84 + &::-moz-focus-inner { 289.85 + border: 0; 289.86 + } 289.87 + &:not([aria-hidden]) { 289.88 + opacity: 1; 289.89 + pointer-events: auto; 289.90 + } 289.91 + } 289.92 +}
290.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 290.2 +++ b/style/mdl/spinner/_spinner.scss Sun Jul 15 14:07:29 2018 +0200 290.3 @@ -0,0 +1,248 @@ 290.4 +/** 290.5 + * Copyright 2015 Google Inc. All Rights Reserved. 290.6 + * 290.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 290.8 + * you may not use this file except in compliance with the License. 290.9 + * You may obtain a copy of the License at 290.10 + * 290.11 + * http://www.apache.org/licenses/LICENSE-2.0 290.12 + * 290.13 + * Unless required by applicable law or agreed to in writing, software 290.14 + * distributed under the License is distributed on an "AS IS" BASIS, 290.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 290.16 + * See the License for the specific language governing permissions and 290.17 + * limitations under the License. 290.18 + */ 290.19 + 290.20 +@import "../variables"; 290.21 + 290.22 + 290.23 +.mdl-spinner { 290.24 + display: inline-block; 290.25 + position: relative; 290.26 + width: $spinner-size; 290.27 + height: $spinner-size; 290.28 + 290.29 + &:not(.is-upgraded).is-active:after { 290.30 + content: "Loading..."; 290.31 + } 290.32 + 290.33 + &.is-upgraded.is-active { 290.34 + animation: mdl-spinner__container-rotate $spinner-duration linear infinite; 290.35 + } 290.36 +} 290.37 + 290.38 +@keyframes mdl-spinner__container-rotate { 290.39 + to { transform: rotate(360deg) } 290.40 +} 290.41 + 290.42 +.mdl-spinner__layer { 290.43 + position: absolute; 290.44 + width: 100%; 290.45 + height: 100%; 290.46 + opacity: 0; 290.47 +} 290.48 + 290.49 +.mdl-spinner__layer-1 { 290.50 + border-color: $spinner-color-1; 290.51 + 290.52 + .mdl-spinner--single-color & { 290.53 + border-color: $spinner-single-color; 290.54 + } 290.55 + 290.56 + .mdl-spinner.is-active & { 290.57 + animation: 290.58 + mdl-spinner__fill-unfill-rotate (4 * $spinner-arc-time) 290.59 + $animation-curve-fast-out-slow-in infinite both, 290.60 + mdl-spinner__layer-1-fade-in-out (4 * $spinner-arc-time) 290.61 + $animation-curve-fast-out-slow-in infinite both; 290.62 + } 290.63 +} 290.64 + 290.65 +.mdl-spinner__layer-2 { 290.66 + border-color: $spinner-color-2; 290.67 + 290.68 + .mdl-spinner--single-color & { 290.69 + border-color: $spinner-single-color; 290.70 + } 290.71 + 290.72 + .mdl-spinner.is-active & { 290.73 + animation: 290.74 + mdl-spinner__fill-unfill-rotate (4 * $spinner-arc-time) 290.75 + $animation-curve-fast-out-slow-in infinite both, 290.76 + mdl-spinner__layer-2-fade-in-out (4 * $spinner-arc-time) 290.77 + $animation-curve-fast-out-slow-in infinite both; 290.78 + } 290.79 +} 290.80 + 290.81 +.mdl-spinner__layer-3 { 290.82 + border-color: $spinner-color-3; 290.83 + 290.84 + .mdl-spinner--single-color & { 290.85 + border-color: $spinner-single-color; 290.86 + } 290.87 + 290.88 + .mdl-spinner.is-active & { 290.89 + animation: 290.90 + mdl-spinner__fill-unfill-rotate (4 * $spinner-arc-time) 290.91 + $animation-curve-fast-out-slow-in infinite both, 290.92 + mdl-spinner__layer-3-fade-in-out (4 * $spinner-arc-time) 290.93 + $animation-curve-fast-out-slow-in infinite both; 290.94 + } 290.95 +} 290.96 + 290.97 +.mdl-spinner__layer-4 { 290.98 + border-color: $spinner-color-4; 290.99 + 290.100 + .mdl-spinner--single-color & { 290.101 + border-color: $spinner-single-color; 290.102 + } 290.103 + 290.104 + .mdl-spinner.is-active & { 290.105 + animation: 290.106 + mdl-spinner__fill-unfill-rotate (4 * $spinner-arc-time) 290.107 + $animation-curve-fast-out-slow-in infinite both, 290.108 + mdl-spinner__layer-4-fade-in-out (4 * $spinner-arc-time) 290.109 + $animation-curve-fast-out-slow-in infinite both; 290.110 + } 290.111 +} 290.112 + 290.113 +@keyframes mdl-spinner__fill-unfill-rotate { 290.114 + 12.5% { transform: rotate(0.5 * $spinner-arc-size); } 290.115 + 25% { transform: rotate($spinner-arc-size); } 290.116 + 37.5% { transform: rotate(1.5 * $spinner-arc-size); } 290.117 + 50% { transform: rotate(2 * $spinner-arc-size); } 290.118 + 62.5% { transform: rotate(2.5 * $spinner-arc-size); } 290.119 + 75% { transform: rotate(3 * $spinner-arc-size); } 290.120 + 87.5% { transform: rotate(3.5 * $spinner-arc-size); } 290.121 + to { transform: rotate(4 * $spinner-arc-size); } 290.122 +} 290.123 + 290.124 +/** 290.125 +* HACK: Even though the intention is to have the current .mdl-spinner__layer-N 290.126 +* at `opacity: 1`, we set it to `opacity: 0.99` instead since this forces Chrome 290.127 +* to do proper subpixel rendering for the elements being animated. This is 290.128 +* especially visible in Chrome 39 on Ubuntu 14.04. See: 290.129 +* 290.130 +* - https://github.com/Polymer/paper-spinner/issues/9 290.131 +* - https://code.google.com/p/chromium/issues/detail?id=436255 290.132 +*/ 290.133 +@keyframes mdl-spinner__layer-1-fade-in-out { 290.134 + from { opacity: 0.99; } 290.135 + 25% { opacity: 0.99; } 290.136 + 26% { opacity: 0; } 290.137 + 89% { opacity: 0; } 290.138 + 90% { opacity: 0.99; } 290.139 + 100% { opacity: 0.99; } 290.140 +} 290.141 + 290.142 +@keyframes mdl-spinner__layer-2-fade-in-out { 290.143 + from { opacity: 0; } 290.144 + 15% { opacity: 0; } 290.145 + 25% { opacity: 0.99; } 290.146 + 50% { opacity: 0.99; } 290.147 + 51% { opacity: 0; } 290.148 +} 290.149 + 290.150 +@keyframes mdl-spinner__layer-3-fade-in-out { 290.151 + from { opacity: 0; } 290.152 + 40% { opacity: 0; } 290.153 + 50% { opacity: 0.99; } 290.154 + 75% { opacity: 0.99; } 290.155 + 76% { opacity: 0; } 290.156 +} 290.157 + 290.158 +@keyframes mdl-spinner__layer-4-fade-in-out { 290.159 + from { opacity: 0; } 290.160 + 65% { opacity: 0; } 290.161 + 75% { opacity: 0.99; } 290.162 + 90% { opacity: 0.99; } 290.163 + 100% { opacity: 0; } 290.164 +} 290.165 + 290.166 +/** 290.167 +* Patch the gap that appear between the two adjacent 290.168 +* div.mdl-spinner__circle-clipper while the spinner is rotating 290.169 +* (appears on Chrome 38, Safari 7.1, and IE 11). 290.170 +* 290.171 +* Update: the gap no longer appears on Chrome when .mdl-spinner__layer-N's 290.172 +* opacity is 0.99, but still does on Safari and IE. 290.173 +*/ 290.174 +.mdl-spinner__gap-patch { 290.175 + position: absolute; 290.176 + box-sizing: border-box; 290.177 + top: 0; 290.178 + left: 45%; 290.179 + width: 10%; 290.180 + height: 100%; 290.181 + overflow: hidden; 290.182 + border-color: inherit; 290.183 + 290.184 + & .mdl-spinner__circle { 290.185 + width: 1000%; 290.186 + left: -450%; 290.187 + } 290.188 +} 290.189 + 290.190 +.mdl-spinner__circle-clipper { 290.191 + display: inline-block; 290.192 + position: relative; 290.193 + width: 50%; 290.194 + height: 100%; 290.195 + overflow: hidden; 290.196 + border-color: inherit; 290.197 + 290.198 + & .mdl-spinner__circle { 290.199 + width: 200%; 290.200 + } 290.201 +} 290.202 + 290.203 +.mdl-spinner__circle { 290.204 + box-sizing: border-box; 290.205 + height: 100%; 290.206 + border-width: $spinner-stroke-width; 290.207 + border-style: solid; 290.208 + border-color: inherit; 290.209 + border-bottom-color: transparent !important; 290.210 + border-radius: 50%; 290.211 + animation: none; 290.212 + 290.213 + position: absolute; 290.214 + top: 0; 290.215 + right: 0; 290.216 + bottom: 0; 290.217 + left: 0; 290.218 + 290.219 + .mdl-spinner__left & { 290.220 + border-right-color: transparent !important; 290.221 + transform: rotate(129deg); 290.222 + 290.223 + .mdl-spinner.is-active & { 290.224 + animation: mdl-spinner__left-spin $spinner-arc-time 290.225 + $animation-curve-fast-out-slow-in infinite both; 290.226 + } 290.227 + } 290.228 + 290.229 + .mdl-spinner__right & { 290.230 + left: -100%; 290.231 + border-left-color: transparent !important; 290.232 + transform: rotate(-129deg); 290.233 + 290.234 + .mdl-spinner.is-active & { 290.235 + animation: mdl-spinner__right-spin $spinner-arc-time 290.236 + $animation-curve-fast-out-slow-in infinite both; 290.237 + } 290.238 + } 290.239 +} 290.240 + 290.241 +@keyframes mdl-spinner__left-spin { 290.242 + from { transform: rotate(130deg); } 290.243 + 50% { transform: rotate(-5deg); } 290.244 + to { transform: rotate(130deg); } 290.245 +} 290.246 + 290.247 +@keyframes mdl-spinner__right-spin { 290.248 + from { transform: rotate(-130deg); } 290.249 + 50% { transform: rotate(5deg); } 290.250 + to { transform: rotate(-130deg); } 290.251 +}
291.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 291.2 +++ b/style/mdl/styleguide.scss Sun Jul 15 14:07:29 2018 +0200 291.3 @@ -0,0 +1,101 @@ 291.4 +/** 291.5 + * Copyright 2015 Google Inc. All Rights Reserved. 291.6 + * 291.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 291.8 + * you may not use this file except in compliance with the License. 291.9 + * You may obtain a copy of the License at 291.10 + * 291.11 + * http://www.apache.org/licenses/LICENSE-2.0 291.12 + * 291.13 + * Unless required by applicable law or agreed to in writing, software 291.14 + * distributed under the License is distributed on an "AS IS" BASIS, 291.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 291.16 + * See the License for the specific language governing permissions and 291.17 + * limitations under the License. 291.18 + */ 291.19 + 291.20 +@import "material-design-lite"; 291.21 + 291.22 +$padding: 24px; 291.23 + 291.24 +body { 291.25 + margin: 0px; 291.26 +} 291.27 + 291.28 +.styleguide-demo h1 { 291.29 + margin: ($padding * 2) $padding 0 $padding; 291.30 +} 291.31 + 291.32 +.styleguide-demo h1:after { 291.33 + content: ''; 291.34 + 291.35 + display: block; 291.36 + width: 100%; 291.37 + 291.38 + border-bottom: 1px solid rgba(0,0,0,0.5); 291.39 + margin-top: $padding; 291.40 +} 291.41 + 291.42 +.styleguide-demo { 291.43 + opacity: 0; 291.44 + 291.45 + transition: opacity 0.6s ease; 291.46 +} 291.47 + 291.48 +.styleguide-masthead { 291.49 + height: 256px; 291.50 + background: unquote("rgb(#{nth($palette-grey, 10)})"); 291.51 + padding: 115px 16px 0; 291.52 +} 291.53 + 291.54 +.styleguide-container { 291.55 + position: relative; 291.56 + max-width: 960px; 291.57 + width: 100%; 291.58 +} 291.59 + 291.60 +.styleguide-title { 291.61 + color: #fff; 291.62 + bottom: auto; 291.63 + position: relative; 291.64 + font-size: 56px; 291.65 + font-weight: 300; 291.66 + line-height: 1; 291.67 + letter-spacing: -0.02em; 291.68 + 291.69 + &:after { 291.70 + border-bottom: 0px; 291.71 + } 291.72 + 291.73 + span { 291.74 + font-weight: 300; 291.75 + } 291.76 +} 291.77 + 291.78 +.mdl-styleguide .mdl-layout__drawer .mdl-navigation__link { 291.79 + padding: 10px 24px; 291.80 +} 291.81 + 291.82 +.demosLoaded .styleguide-demo { 291.83 + opacity: 1; 291.84 +} 291.85 + 291.86 +iframe { 291.87 + display: block; 291.88 + 291.89 + width: 100%; 291.90 + 291.91 + border: none; 291.92 +} 291.93 + 291.94 +iframe.heightSet { 291.95 + overflow: hidden; 291.96 +} 291.97 + 291.98 +.demo-wrapper { 291.99 + margin: $padding; 291.100 + 291.101 + iframe { 291.102 + border: 1px solid rgba(0,0,0,0.5); 291.103 + } 291.104 +}
292.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 292.2 +++ b/style/mdl/switch/_switch.scss Sun Jul 15 14:07:29 2018 +0200 292.3 @@ -0,0 +1,203 @@ 292.4 +/** 292.5 + * Copyright 2015 Google Inc. All Rights Reserved. 292.6 + * 292.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 292.8 + * you may not use this file except in compliance with the License. 292.9 + * You may obtain a copy of the License at 292.10 + * 292.11 + * http://www.apache.org/licenses/LICENSE-2.0 292.12 + * 292.13 + * Unless required by applicable law or agreed to in writing, software 292.14 + * distributed under the License is distributed on an "AS IS" BASIS, 292.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 292.16 + * See the License for the specific language governing permissions and 292.17 + * limitations under the License. 292.18 + */ 292.19 + 292.20 + 292.21 +@import "../variables"; 292.22 +@import "../mixins"; 292.23 + 292.24 +.mdl-switch { 292.25 + position: relative; 292.26 + 292.27 + z-index: 1; 292.28 + 292.29 + vertical-align: middle; 292.30 + 292.31 + display: inline-block; 292.32 + 292.33 + box-sizing: border-box; 292.34 + width: 100%; 292.35 + height: $switch-label-height; 292.36 + margin: 0; 292.37 + padding: 0; 292.38 + 292.39 + overflow: visible; 292.40 + 292.41 + &.is-upgraded { 292.42 + padding-left: $switch-track-length - 8px; 292.43 + } 292.44 + 292.45 + // avoids blue box around switch 292.46 + -webkit-touch-callout: none; 292.47 + -webkit-user-select: none; 292.48 + -moz-user-select: none; 292.49 + -ms-user-select: none; 292.50 + user-select: none; 292.51 +} 292.52 + 292.53 +.mdl-switch__input { 292.54 + line-height: $switch-label-height; 292.55 + 292.56 + .mdl-switch.is-upgraded & { 292.57 + // Hide input element, while still making it respond to focus. 292.58 + position: absolute; 292.59 + width: 0; 292.60 + height: 0; 292.61 + margin: 0; 292.62 + padding: 0; 292.63 + opacity: 0; 292.64 + -ms-appearance: none; 292.65 + -moz-appearance: none; 292.66 + -webkit-appearance: none; 292.67 + appearance: none; 292.68 + border: none; 292.69 + } 292.70 +} 292.71 + 292.72 +.mdl-switch__track { 292.73 + background: $switch-off-track-color; 292.74 + position: absolute; 292.75 + left: 0; 292.76 + top: $switch-track-top; 292.77 + height: $switch-track-height; 292.78 + width: $switch-track-length; 292.79 + border-radius: $switch-track-height; 292.80 + 292.81 + cursor: pointer; 292.82 + 292.83 + .mdl-switch.is-checked & { 292.84 + background: $switch-track-color; 292.85 + } 292.86 + 292.87 + fieldset[disabled] .mdl-switch, 292.88 + .mdl-switch.is-disabled & { 292.89 + background: $switch-disabled-track-color; 292.90 + cursor: auto; 292.91 + } 292.92 +} 292.93 + 292.94 +.mdl-switch__thumb { 292.95 + background: $switch-off-thumb-color; 292.96 + position: absolute; 292.97 + left: 0; 292.98 + top: $switch-thumb-top; 292.99 + height: $switch-thumb-size; 292.100 + width: $switch-thumb-size; 292.101 + border-radius: 50%; 292.102 + 292.103 + cursor: pointer; 292.104 + 292.105 + @include shadow-2dp(); 292.106 + 292.107 + @include material-animation-default(0.28s); 292.108 + transition-property: left; 292.109 + 292.110 + .mdl-switch.is-checked & { 292.111 + background: $switch-thumb-color; 292.112 + left: $switch-track-length - $switch-thumb-size; 292.113 + 292.114 + @include shadow-3dp(); 292.115 + } 292.116 + 292.117 + fieldset[disabled] .mdl-switch, 292.118 + .mdl-switch.is-disabled & { 292.119 + background: $switch-disabled-thumb-color; 292.120 + cursor: auto; 292.121 + } 292.122 +} 292.123 + 292.124 +.mdl-switch__focus-helper { 292.125 + position: absolute; 292.126 + top: 50%; 292.127 + left: 50%; 292.128 + 292.129 + transform: translate(-$switch-helper-size / 2, -$switch-helper-size / 2); 292.130 + 292.131 + display: inline-block; 292.132 + 292.133 + box-sizing: border-box; 292.134 + width: $switch-helper-size; 292.135 + height: $switch-helper-size; 292.136 + border-radius: 50%; 292.137 + 292.138 + background-color: transparent; 292.139 + 292.140 + .mdl-switch.is-focused & { 292.141 + box-shadow: 0 0 0px (($switch-ripple-size - $switch-helper-size) / 2) 292.142 + rgba(0, 0, 0, 0.1); 292.143 + background-color: rgba(0, 0, 0, 0.1); 292.144 + } 292.145 + 292.146 + .mdl-switch.is-focused.is-checked & { 292.147 + box-shadow: 0 0 0px (($switch-ripple-size - $switch-helper-size) / 2) 292.148 + $switch-faded-color; 292.149 + background-color: $switch-faded-color; 292.150 + } 292.151 +} 292.152 + 292.153 +.mdl-switch__label { 292.154 + position: relative; 292.155 + cursor: pointer; 292.156 + font-size: $switch-label-font-size; 292.157 + line-height: $switch-label-height; 292.158 + margin: 0; 292.159 + left: 24px; 292.160 + 292.161 + fieldset[disabled] .mdl-switch, 292.162 + .mdl-switch.is-disabled & { 292.163 + color: $switch-disabled-thumb-color; 292.164 + cursor: auto; 292.165 + } 292.166 +} 292.167 + 292.168 +.mdl-switch__ripple-container { 292.169 + position: absolute; 292.170 + z-index: 2; 292.171 + top: -($switch-ripple-size - $switch-label-height) / 2; 292.172 + left: $switch-thumb-size / 2 - $switch-ripple-size / 2; 292.173 + 292.174 + box-sizing: border-box; 292.175 + width: $switch-ripple-size; 292.176 + height: $switch-ripple-size; 292.177 + border-radius: 50%; 292.178 + 292.179 + cursor: pointer; 292.180 + 292.181 + overflow: hidden; 292.182 + -webkit-mask-image: -webkit-radial-gradient(circle, white, black); 292.183 + 292.184 + transition-duration: 0.40s; 292.185 + transition-timing-function: step-end; 292.186 + transition-property: left; 292.187 + 292.188 + & .mdl-ripple { 292.189 + background: $switch-color; 292.190 + } 292.191 + 292.192 + fieldset[disabled] .mdl-switch, 292.193 + .mdl-switch.is-disabled & { 292.194 + cursor: auto; 292.195 + } 292.196 + 292.197 + fieldset[disabled] .mdl-switch & .mdl-ripple, 292.198 + .mdl-switch.is-disabled & .mdl-ripple { 292.199 + background: transparent; 292.200 + } 292.201 + 292.202 + .mdl-switch.is-checked & { 292.203 + left: $switch-track-length - $switch-ripple-size / 2 - 292.204 + $switch-thumb-size / 2; 292.205 + } 292.206 +}
293.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 293.2 +++ b/style/mdl/tabs/_tabs.scss Sun Jul 15 14:07:29 2018 +0200 293.3 @@ -0,0 +1,114 @@ 293.4 +/** 293.5 + * Copyright 2015 Google Inc. All Rights Reserved. 293.6 + * 293.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 293.8 + * you may not use this file except in compliance with the License. 293.9 + * You may obtain a copy of the License at 293.10 + * 293.11 + * http://www.apache.org/licenses/LICENSE-2.0 293.12 + * 293.13 + * Unless required by applicable law or agreed to in writing, software 293.14 + * distributed under the License is distributed on an "AS IS" BASIS, 293.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 293.16 + * See the License for the specific language governing permissions and 293.17 + * limitations under the License. 293.18 + */ 293.19 + 293.20 +@import "../variables"; 293.21 + 293.22 + 293.23 +.mdl-tabs { 293.24 + display: block; 293.25 + width: 100%; 293.26 +} 293.27 + 293.28 +.mdl-tabs__tab-bar { 293.29 + display : flex; 293.30 + flex-direction : row; 293.31 + justify-content : center; // ⇾ 293.32 + align-content : space-between; // || 293.33 + align-items : flex-start; // ↓ 293.34 + 293.35 + height : 48px; 293.36 + padding : 0 0 0 0; 293.37 + margin : 0; 293.38 + border-bottom : 1px solid $tab-border-color; 293.39 +} 293.40 + 293.41 +.mdl-tabs__tab { 293.42 + margin: 0; 293.43 + border: none; 293.44 + padding: 0 24px 0 24px; 293.45 + 293.46 + float: left; 293.47 + position: relative; 293.48 + display: block; 293.49 + 293.50 + text-decoration: none; 293.51 + height: 48px; 293.52 + line-height: 48px; 293.53 + 293.54 + text-align: center; 293.55 + font-weight: 500; 293.56 + font-size: $layout-tab-font-size; 293.57 + text-transform: uppercase; 293.58 + 293.59 + color: $tab-text-color; 293.60 + overflow: hidden; 293.61 + 293.62 + .mdl-tabs.is-upgraded &.is-active { 293.63 + color: $tab-active-text-color; 293.64 + } 293.65 + 293.66 + .mdl-tabs.is-upgraded &.is-active:after { 293.67 + height: 2px; 293.68 + width: 100%; 293.69 + display: block; 293.70 + content: " "; 293.71 + bottom: 0px; 293.72 + left: 0px; 293.73 + position: absolute; 293.74 + background: $tab-highlight-color; 293.75 + animation: border-expand 0.2s cubic-bezier(0.4, 0.0, 0.4, 1) 0.01s alternate forwards; 293.76 + transition: all 1s cubic-bezier(0.4, 0.0, 1, 1); 293.77 + } 293.78 + 293.79 + & .mdl-tabs__ripple-container { 293.80 + display: block; 293.81 + position: absolute; 293.82 + height: 100%; 293.83 + width: 100%; 293.84 + left: 0px; 293.85 + top: 0px; 293.86 + z-index: 1; 293.87 + overflow: hidden; 293.88 + 293.89 + & .mdl-ripple { 293.90 + background: $tab-highlight-color; 293.91 + } 293.92 + } 293.93 +} 293.94 + 293.95 +.mdl-tabs__panel { 293.96 + display: block; 293.97 + 293.98 + .mdl-tabs.is-upgraded & { 293.99 + display: none; 293.100 + } 293.101 + 293.102 + .mdl-tabs.is-upgraded &.is-active { 293.103 + display: block; 293.104 + } 293.105 +} 293.106 + 293.107 +@keyframes border-expand { 293.108 + 0% { 293.109 + opacity: 0; 293.110 + width: 0; 293.111 + } 293.112 + 293.113 + 100% { 293.114 + opacity: 1; 293.115 + width: 100%; 293.116 + } 293.117 +}
294.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 294.2 +++ b/style/mdl/template.scss Sun Jul 15 14:07:29 2018 +0200 294.3 @@ -0,0 +1,21 @@ 294.4 +/** 294.5 + * Copyright 2015 Google Inc. All Rights Reserved. 294.6 + * 294.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 294.8 + * you may not use this file except in compliance with the License. 294.9 + * You may obtain a copy of the License at 294.10 + * 294.11 + * http://www.apache.org/licenses/LICENSE-2.0 294.12 + * 294.13 + * Unless required by applicable law or agreed to in writing, software 294.14 + * distributed under the License is distributed on an "AS IS" BASIS, 294.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 294.16 + * See the License for the specific language governing permissions and 294.17 + * limitations under the License. 294.18 + */ 294.19 + 294.20 +/* Material Design Lite */ 294.21 + 294.22 +$styleguide-generate-template: true; 294.23 + 294.24 +@import "styleguide";
295.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 295.2 +++ b/style/mdl/textfield/_textfield.scss Sun Jul 15 14:07:29 2018 +0200 295.3 @@ -0,0 +1,223 @@ 295.4 +/** 295.5 + * Copyright 2015 Google Inc. All Rights Reserved. 295.6 + * 295.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 295.8 + * you may not use this file except in compliance with the License. 295.9 + * You may obtain a copy of the License at 295.10 + * 295.11 + * http://www.apache.org/licenses/LICENSE-2.0 295.12 + * 295.13 + * Unless required by applicable law or agreed to in writing, software 295.14 + * distributed under the License is distributed on an "AS IS" BASIS, 295.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 295.16 + * See the License for the specific language governing permissions and 295.17 + * limitations under the License. 295.18 + */ 295.19 + 295.20 +@import "../variables"; 295.21 +@import "../mixins"; 295.22 + 295.23 +// The container for the whole component. 295.24 +.mdl-textfield { 295.25 + position: relative; 295.26 + font-size: $input-text-font-size; 295.27 + display: inline-block; 295.28 + box-sizing: border-box; 295.29 + width: 300px; 295.30 + max-width: 100%; 295.31 + margin: 0; 295.32 + padding: $input-text-vertical-spacing 0; 295.33 + 295.34 + // Align buttons, if used. 295.35 + & .mdl-button { 295.36 + position: absolute; 295.37 + bottom: $input-text-vertical-spacing; 295.38 + } 295.39 +} 295.40 + 295.41 +// Optional class to align right. 295.42 +.mdl-textfield--align-right { 295.43 + text-align: right; 295.44 +} 295.45 + 295.46 +// Optional class to display at full width. 295.47 +.mdl-textfield--full-width { 295.48 + width: 100%; 295.49 +} 295.50 + 295.51 +// Optional class to make the text field expandable. 295.52 +.mdl-textfield--expandable { 295.53 + min-width: $input-text-button-size; 295.54 + width: auto; 295.55 + min-height: $input-text-button-size; 295.56 + 295.57 + // Align icon button 295.58 + .mdl-button--icon { 295.59 + top: $input-text-expandable-icon-top; 295.60 + } 295.61 +} 295.62 + 295.63 +// Styling for the input element. 295.64 +.mdl-textfield__input { 295.65 + border: none; 295.66 + border-bottom: 1px solid $input-text-bottom-border-color; 295.67 + display: block; 295.68 + font-size: $input-text-font-size; 295.69 + font-family: $performance_font; 295.70 + margin: 0; 295.71 + padding: $input-text-padding 0; 295.72 + width: $input-text-width; 295.73 + background: none; 295.74 + text-align: left; 295.75 + color: inherit; 295.76 + 295.77 + &[type="number"] { 295.78 + -moz-appearance: textfield; 295.79 + } 295.80 + 295.81 + &[type="number"]::-webkit-inner-spin-button, 295.82 + &[type="number"]::-webkit-outer-spin-button { 295.83 + -webkit-appearance: none; 295.84 + margin: 0; 295.85 + } 295.86 + 295.87 + .mdl-textfield.is-focused & { 295.88 + outline: none; 295.89 + } 295.90 + 295.91 + .mdl-textfield.is-invalid & { 295.92 + border-color: $input-text-error-color; 295.93 + box-shadow: none; 295.94 + } 295.95 + 295.96 + fieldset[disabled] .mdl-textfield &, 295.97 + .mdl-textfield.is-disabled & { 295.98 + background-color: transparent; 295.99 + border-bottom: 1px dotted $input-text-disabled-color; 295.100 + color: $input-text-disabled-text-color; 295.101 + } 295.102 +} 295.103 + 295.104 +.mdl-textfield textarea.mdl-textfield__input { 295.105 + display: block; 295.106 +} 295.107 + 295.108 +// Styling for the label / floating label. 295.109 +.mdl-textfield__label { 295.110 + bottom: 0; 295.111 + color: $input-text-label-color; 295.112 + font-size: $input-text-font-size; 295.113 + left: 0; 295.114 + right: 0; 295.115 + pointer-events: none; 295.116 + position: absolute; 295.117 + display: block; 295.118 + top: ($input-text-padding + $input-text-vertical-spacing); 295.119 + width: 100%; 295.120 + overflow: hidden; 295.121 + white-space: nowrap; 295.122 + text-align: left; 295.123 + 295.124 + .mdl-textfield.is-dirty &, 295.125 + .mdl-textfield.has-placeholder & { 295.126 + visibility: hidden; 295.127 + } 295.128 + 295.129 + // Floating Label 295.130 + .mdl-textfield--floating-label & { 295.131 + @include material-animation-default(); 295.132 + } 295.133 + 295.134 + .mdl-textfield--floating-label.has-placeholder & { 295.135 + transition: none; 295.136 + } 295.137 + 295.138 + fieldset[disabled] .mdl-textfield &, 295.139 + .mdl-textfield.is-disabled.is-disabled & { 295.140 + color: $input-text-disabled-text-color; 295.141 + } 295.142 + 295.143 + .mdl-textfield--floating-label.is-focused &, 295.144 + .mdl-textfield--floating-label.is-dirty &, 295.145 + .mdl-textfield--floating-label.has-placeholder & { 295.146 + color: $input-text-highlight-color; 295.147 + font-size : $input-text-floating-label-fontsize; 295.148 + top: $input-text-vertical-spacing - ($input-text-floating-label-fontsize + $input-text-padding); 295.149 + visibility: visible; 295.150 + } 295.151 + 295.152 + .mdl-textfield--floating-label.is-focused .mdl-textfield__expandable-holder &, 295.153 + .mdl-textfield--floating-label.is-dirty .mdl-textfield__expandable-holder &, 295.154 + .mdl-textfield--floating-label.has-placeholder .mdl-textfield__expandable-holder & { 295.155 + top: -($input-text-floating-label-fontsize + $input-text-padding); 295.156 + } 295.157 + 295.158 + .mdl-textfield--floating-label.is-invalid & { 295.159 + color: $input-text-error-color; 295.160 + font-size: $input-text-floating-label-fontsize; 295.161 + } 295.162 + 295.163 + // The after label is the colored underline for the TextField. 295.164 + &:after { 295.165 + background-color: $input-text-highlight-color; 295.166 + bottom: $input-text-vertical-spacing; 295.167 + content: ''; 295.168 + height: 2px; 295.169 + left: 45%; 295.170 + position: absolute; 295.171 + @include material-animation-default(); 295.172 + visibility: hidden; 295.173 + width: 10px; 295.174 + } 295.175 + 295.176 + .mdl-textfield.is-focused &:after { 295.177 + left: 0; 295.178 + visibility: visible; 295.179 + width: 100%; 295.180 + } 295.181 + 295.182 + .mdl-textfield.is-invalid &:after { 295.183 + background-color: $input-text-error-color; 295.184 + } 295.185 +} 295.186 + 295.187 +// TextField Error. 295.188 +.mdl-textfield__error { 295.189 + color: $input-text-error-color; 295.190 + position: absolute; 295.191 + font-size: $input-text-floating-label-fontsize; 295.192 + margin-top: 3px; 295.193 + visibility: hidden; 295.194 + display: block; 295.195 + 295.196 + .mdl-textfield.is-invalid & { 295.197 + visibility: visible; 295.198 + } 295.199 +} 295.200 + 295.201 +// Expandable Holder. 295.202 +.mdl-textfield__expandable-holder { 295.203 + display: inline-block; 295.204 + position: relative; 295.205 + margin-left: $input-text-button-size; 295.206 + 295.207 + @include material-animation-default(); 295.208 + display: inline-block; 295.209 + 295.210 + // Safari (possibly others) need to be convinced that this field is actually 295.211 + // visible, otherwise it cannot be tabbed to nor focused via a <label>. 295.212 + // TODO: In some cases (Retina displays), this is big enough to render the 295.213 + // inner element :( 295.214 + max-width: 0.1px; 295.215 + 295.216 + .mdl-textfield.is-focused &, .mdl-textfield.is-dirty & { 295.217 + // This is an unfortunate hack. Animating between widths in percent (%) 295.218 + // in many browsers (Chrome, Firefox) only animates the inner visual style 295.219 + // of the input - the outer bounding box still 'jumps'. 295.220 + // Thus assume a sensible maximum, and animate to/from that value. 295.221 + max-width: 600px; 295.222 + } 295.223 + .mdl-textfield__label:after { 295.224 + bottom: 0; 295.225 + } 295.226 +}
296.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 296.2 +++ b/style/mdl/tooltip/_tooltip.scss Sun Jul 15 14:07:29 2018 +0200 296.3 @@ -0,0 +1,65 @@ 296.4 +/** 296.5 + * Copyright 2015 Google Inc. All Rights Reserved. 296.6 + * 296.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 296.8 + * you may not use this file except in compliance with the License. 296.9 + * You may obtain a copy of the License at 296.10 + * 296.11 + * http://www.apache.org/licenses/LICENSE-2.0 296.12 + * 296.13 + * Unless required by applicable law or agreed to in writing, software 296.14 + * distributed under the License is distributed on an "AS IS" BASIS, 296.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 296.16 + * See the License for the specific language governing permissions and 296.17 + * limitations under the License. 296.18 + */ 296.19 + 296.20 +@import "../variables"; 296.21 + 296.22 +.mdl-tooltip { 296.23 + transform: scale(0); 296.24 + transform-origin: top center; 296.25 + z-index: 999; 296.26 + background: $tooltip-background-color; 296.27 + border-radius: 2px; 296.28 + color: $tooltip-text-color; 296.29 + display: inline-block; 296.30 + font-size: $tooltip-font-size; 296.31 + font-weight: 500; 296.32 + line-height: 14px; 296.33 + max-width: 170px; 296.34 + position: fixed; 296.35 + top: -500px; 296.36 + left: -500px; 296.37 + padding: 8px; 296.38 + text-align: center; 296.39 +} 296.40 +.mdl-tooltip.is-active { 296.41 + animation: pulse 200ms $animation-curve-linear-out-slow-in forwards; 296.42 +} 296.43 + 296.44 +.mdl-tooltip--large { 296.45 + line-height: 14px; 296.46 + font-size: $tooltip-font-size-large; 296.47 + padding: 16px; 296.48 +} 296.49 + 296.50 +@keyframes pulse { 296.51 + 0% { 296.52 + transform: scale(0); 296.53 + opacity: 0; 296.54 + } 296.55 + 50% { 296.56 + // Fixes a weird bug with the interaction between Safari and the result of 296.57 + // the SASS compilation for the animation. 296.58 + // Essentially, we need to make sure that "50%" and "100%" don't get merged 296.59 + // into a single "50%, 100%" entry, so we need to avoid them having any 296.60 + // matching properties. 296.61 + transform: scale(0.99); 296.62 + } 296.63 + 100% { 296.64 + transform: scale(1); 296.65 + opacity: 1; 296.66 + visibility: visible; 296.67 + } 296.68 +}
297.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 297.2 +++ b/style/mdl/typography/_typography.scss Sun Jul 15 14:07:29 2018 +0200 297.3 @@ -0,0 +1,301 @@ 297.4 +/** 297.5 + * Copyright 2015 Google Inc. All Rights Reserved. 297.6 + * 297.7 + * Licensed under the Apache License, Version 2.0 (the "License"); 297.8 + * you may not use this file except in compliance with the License. 297.9 + * You may obtain a copy of the License at 297.10 + * 297.11 + * http://www.apache.org/licenses/LICENSE-2.0 297.12 + * 297.13 + * Unless required by applicable law or agreed to in writing, software 297.14 + * distributed under the License is distributed on an "AS IS" BASIS, 297.15 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 297.16 + * See the License for the specific language governing permissions and 297.17 + * limitations under the License. 297.18 + */ 297.19 + 297.20 +@import "../variables"; 297.21 +@import "../mixins"; 297.22 + 297.23 +@if $target-elements-directly == true { 297.24 + html, body { 297.25 + font-family: $performance_font; 297.26 + font-size: 14px; 297.27 + font-weight: 400; 297.28 + line-height: 20px; 297.29 + } 297.30 + 297.31 + h1, h2, h3, h4, h5, h6, p { 297.32 + margin: 0; 297.33 + padding: 0; 297.34 + } 297.35 + 297.36 + /** 297.37 + * Styles for HTML elements 297.38 + */ 297.39 + 297.40 + h1 small, h2 small, h3 small, h4 small, h5 small, h6 small { 297.41 + @include typo-display-3($colorContrast: true); 297.42 + 297.43 + font-size: 0.6em; 297.44 + } 297.45 + 297.46 + h1 { 297.47 + @include typo-display-3; 297.48 + 297.49 + margin-top: 24px; 297.50 + margin-bottom: 24px; 297.51 + } 297.52 + 297.53 + h2 { 297.54 + @include typo-display-2; 297.55 + 297.56 + margin-top: 24px; 297.57 + margin-bottom: 24px; 297.58 + } 297.59 + 297.60 + h3 { 297.61 + @include typo-display-1; 297.62 + 297.63 + margin-top: 24px; 297.64 + margin-bottom: 24px; 297.65 + } 297.66 + 297.67 + h4 { 297.68 + @include typo-headline; 297.69 + 297.70 + margin-top: 24px; 297.71 + margin-bottom: 16px; 297.72 + } 297.73 + 297.74 + h5 { 297.75 + @include typo-title; 297.76 + 297.77 + margin-top: 24px; 297.78 + margin-bottom: 16px; 297.79 + } 297.80 + 297.81 + h6 { 297.82 + @include typo-subhead; 297.83 + 297.84 + margin-top: 24px; 297.85 + margin-bottom: 16px; 297.86 + } 297.87 + 297.88 + p { 297.89 + @include typo-body-1; 297.90 + 297.91 + margin-bottom: 16px; 297.92 + } 297.93 + 297.94 + a { 297.95 + color: $text-link-color; 297.96 + font-weight: 500; 297.97 + } 297.98 + 297.99 + blockquote { 297.100 + @include typo-blockquote; 297.101 + } 297.102 + 297.103 + mark { 297.104 + background-color: #f4ff81; 297.105 + } 297.106 + 297.107 + dt { 297.108 + font-weight: 700; 297.109 + } 297.110 + 297.111 + address { 297.112 + @include typo-caption; 297.113 + 297.114 + font-style: normal; 297.115 + } 297.116 + 297.117 + ul, ol { 297.118 + @include typo-body-1; 297.119 + } 297.120 +} 297.121 + 297.122 +/** 297.123 + * Class Name Styles 297.124 + */ 297.125 + 297.126 +.mdl-typography--display-4 { 297.127 + @include typo-display-4; 297.128 +} 297.129 + 297.130 +.mdl-typography--display-4-color-contrast { 297.131 + @include typo-display-4($colorContrast: true); 297.132 +} 297.133 + 297.134 +.mdl-typography--display-3 { 297.135 + @include typo-display-3; 297.136 +} 297.137 + 297.138 +.mdl-typography--display-3-color-contrast { 297.139 + @include typo-display-3($colorContrast: true); 297.140 +} 297.141 + 297.142 +.mdl-typography--display-2 { 297.143 + @include typo-display-2; 297.144 +} 297.145 + 297.146 +.mdl-typography--display-2-color-contrast { 297.147 + @include typo-display-2($colorContrast: true); 297.148 +} 297.149 + 297.150 +.mdl-typography--display-1 { 297.151 + @include typo-display-1; 297.152 +} 297.153 + 297.154 +.mdl-typography--display-1-color-contrast { 297.155 + @include typo-display-1($colorContrast: true); 297.156 +} 297.157 + 297.158 +.mdl-typography--headline { 297.159 + @include typo-headline; 297.160 +} 297.161 + 297.162 +.mdl-typography--headline-color-contrast { 297.163 + @include typo-headline($colorContrast: true); 297.164 +} 297.165 + 297.166 +.mdl-typography--title { 297.167 + @include typo-title; 297.168 +} 297.169 + 297.170 +.mdl-typography--title-color-contrast { 297.171 + @include typo-title($colorContrast: true); 297.172 +} 297.173 + 297.174 +.mdl-typography--subhead { 297.175 + @include typo-subhead; 297.176 +} 297.177 + 297.178 +.mdl-typography--subhead-color-contrast { 297.179 + @include typo-subhead($colorContrast: true); 297.180 +} 297.181 + 297.182 +.mdl-typography--body-2 { 297.183 + @include typo-body-2; 297.184 +} 297.185 + 297.186 +.mdl-typography--body-2-color-contrast { 297.187 + @include typo-body-2($colorContrast: true); 297.188 +} 297.189 + 297.190 +.mdl-typography--body-1 { 297.191 + @include typo-body-1; 297.192 +} 297.193 + 297.194 +.mdl-typography--body-1-color-contrast { 297.195 + @include typo-body-1($colorContrast: true); 297.196 +} 297.197 + 297.198 +.mdl-typography--body-2-force-preferred-font { 297.199 + @include typo-body-2($usePreferred: true); 297.200 +} 297.201 + 297.202 +.mdl-typography--body-2-force-preferred-font-color-contrast { 297.203 + @include typo-body-2($colorContrast: true, $usePreferred: true); 297.204 +} 297.205 + 297.206 +.mdl-typography--body-1-force-preferred-font { 297.207 + @include typo-body-1($usePreferred: true); 297.208 +} 297.209 + 297.210 +.mdl-typography--body-1-force-preferred-font-color-contrast { 297.211 + @include typo-body-1($colorContrast: true, $usePreferred: true); 297.212 +} 297.213 + 297.214 +.mdl-typography--caption { 297.215 + @include typo-caption; 297.216 +} 297.217 + 297.218 +.mdl-typography--caption-force-preferred-font { 297.219 + @include typo-caption($usePreferred: true); 297.220 +} 297.221 + 297.222 +.mdl-typography--caption-color-contrast { 297.223 + @include typo-caption($colorContrast: true); 297.224 +} 297.225 + 297.226 +.mdl-typography--caption-force-preferred-font-color-contrast { 297.227 + @include typo-caption($colorContrast: true, $usePreferred: true); 297.228 +} 297.229 + 297.230 +.mdl-typography--menu { 297.231 + @include typo-menu; 297.232 +} 297.233 + 297.234 +.mdl-typography--menu-color-contrast { 297.235 + @include typo-menu($colorContrast: true); 297.236 +} 297.237 + 297.238 +.mdl-typography--button { 297.239 + @include typo-button; 297.240 +} 297.241 + 297.242 +.mdl-typography--button-color-contrast { 297.243 + @include typo-button($colorContrast: true); 297.244 +} 297.245 + 297.246 +.mdl-typography--text-left { 297.247 + text-align: left; 297.248 +} 297.249 + 297.250 +.mdl-typography--text-right { 297.251 + text-align: right; 297.252 +} 297.253 + 297.254 +.mdl-typography--text-center { 297.255 + text-align: center; 297.256 +} 297.257 + 297.258 +.mdl-typography--text-justify { 297.259 + text-align: justify; 297.260 +} 297.261 + 297.262 +.mdl-typography--text-nowrap { 297.263 + white-space: nowrap; 297.264 +} 297.265 + 297.266 +.mdl-typography--text-lowercase { 297.267 + text-transform: lowercase; 297.268 +} 297.269 + 297.270 +.mdl-typography--text-uppercase { 297.271 + text-transform: uppercase; 297.272 +} 297.273 + 297.274 +.mdl-typography--text-capitalize { 297.275 + text-transform: capitalize; 297.276 +} 297.277 + 297.278 +.mdl-typography--font-thin { 297.279 + font-weight: 200 !important; 297.280 +} 297.281 + 297.282 +.mdl-typography--font-light { 297.283 + font-weight: 300 !important; 297.284 +} 297.285 + 297.286 +.mdl-typography--font-regular { 297.287 + font-weight: 400 !important; 297.288 +} 297.289 + 297.290 +.mdl-typography--font-medium { 297.291 + font-weight: 500 !important; 297.292 +} 297.293 + 297.294 +.mdl-typography--font-bold { 297.295 + font-weight: 700 !important; 297.296 +} 297.297 + 297.298 +.mdl-typography--font-black { 297.299 + font-weight: 900 !important; 297.300 +} 297.301 + 297.302 +.material-icons { 297.303 + @include typo-icon; 297.304 +}