liquid_feedback_frontend

changeset 1309:32cc544d5a5b

Cumulative patch for upcoming frontend version 4
author bsw/jbe
date Sun Jul 15 14:07:29 2018 +0200 (2018-07-15)
parents 7ea154c9238a
children 5b5f8de48b3d
files LICENSE app/main/_filter/20_session.lua app/main/_filter/21_auth.lua app/main/_filter_view/30_navigation.lua app/main/_filter_view/34_stylesheet.lua app/main/_layout/default.html app/main/_layout/system_error.html app/main/_prefork/10_init.lua app/main/admin/_action/area_update.lua app/main/admin/_action/member_update.lua app/main/admin/_action/policy_update.lua app/main/admin/_filter/90_admin.lua app/main/admin/area_show.lua app/main/admin/invite_list.lua app/main/admin/invite_pdf.lua app/main/admin/member_edit.lua app/main/admin/policy_show.lua app/main/admin/unit_edit.lua app/main/admin/verification.lua app/main/admin/verification_list.lua app/main/agent/_action/accept.lua app/main/agent/show.lua app/main/api/_filter/30_auth.lua app/main/api/_issue.lua app/main/api/_member.lua app/main/api/_profile.lua app/main/api/_settings.lua app/main/api/application.lua app/main/api/event.lua app/main/api/info.lua app/main/api/instance.lua app/main/api/member.lua app/main/api/navigation.lua app/main/api/notify_email.lua app/main/api/profile.lua app/main/api/profile_info.lua app/main/api/settings.lua app/main/api/settings_info.lua app/main/api/style.lua app/main/area/_head.lua app/main/area/_sidebar_members.lua app/main/area/_sidebar_whatcanido.lua app/main/area/show.lua app/main/contact/list.lua app/main/delegation/_action/update.lua app/main/delegation/_info.lua app/main/delegation/show.lua app/main/draft/_action/add.lua app/main/draft/_show.lua app/main/draft/diff.lua app/main/draft/new.lua app/main/draft/show.lua app/main/help/introduction.lua app/main/http_options.lua app/main/index/403.lua app/main/index/404.lua app/main/index/405.lua app/main/index/_action/login.lua app/main/index/_action/logout.lua app/main/index/_action/register.lua app/main/index/_drawer.lua app/main/index/_head.lua app/main/index/_lang_chooser.lua app/main/index/_sidebar_members.lua app/main/index/_sidebar_motd.lua app/main/index/_sidebar_motd_intern.lua app/main/index/_sidebar_motd_public.lua app/main/index/_sidebar_notifications.lua app/main/index/_sidebar_units.lua app/main/index/_sidebar_whatcanido.lua app/main/index/about.lua app/main/index/document.lua app/main/index/document_file.lua app/main/index/download.lua app/main/index/download_file.lua app/main/index/index.lua app/main/index/login.lua app/main/index/register.lua app/main/index/reset_password.lua app/main/index/search.lua app/main/index/send_login.lua app/main/initiative/_action/add_initiator.lua app/main/initiative/_action/add_support.lua app/main/initiative/_action/create.lua app/main/initiative/_action/reject_initiator_invitation.lua app/main/initiative/_action/remove_initiator.lua app/main/initiative/_action/revoke.lua app/main/initiative/_bargraph.lua app/main/initiative/_head.lua app/main/initiative/_list.lua app/main/initiative/_list_element.lua app/main/initiative/_sidebar_history.lua app/main/initiative/_sidebar_state.lua app/main/initiative/_suggestions.lua app/main/initiative/history.lua app/main/initiative/new.lua app/main/initiative/remove_initiator.lua app/main/initiative/show.lua app/main/interest/_action/update.lua app/main/interest/show_incoming.lua app/main/issue/_filters.lua app/main/issue/_head.lua app/main/issue/_list.lua app/main/issue/_sidebar.lua app/main/issue/_sidebar_issue.lua app/main/issue/_sidebar_members.lua app/main/issue/_sidebar_state.lua app/main/issue/_sidebar_whatcanido.lua app/main/issue/history.lua app/main/issue/show.lua app/main/member/_action/remove_application.lua app/main/member/_action/update.lua app/main/member/_action/update_agent.lua app/main/member/_action/update_email.lua app/main/member/_action/update_login.lua app/main/member/_action/update_name.lua app/main/member/_action/update_password.lua app/main/member/_agent_menu.lua app/main/member/_list.lua app/main/member/_menu.lua app/main/member/_profile.lua app/main/member/_settings_list.lua app/main/member/_show_thumb.lua app/main/member/_sidebar_contacts.lua app/main/member/_sidebar_whatcanido.lua app/main/member/_timeline.lua app/main/member/edit.lua app/main/member/edit_images.lua app/main/member/history.lua app/main/member/list.lua app/main/member/settings.lua app/main/member/settings_agent.lua app/main/member/settings_applications.lua app/main/member/settings_email.lua app/main/member/settings_login.lua app/main/member/settings_name.lua app/main/member/settings_notification.lua app/main/member/settings_password.lua app/main/member/show.lua app/main/member_image/_show.lua app/main/oauth2/_action/accept_scope.lua app/main/oauth2/_authorization.lua app/main/oauth2/_filter_view/10_transaction.lua app/main/oauth2/authorization.lua app/main/oauth2/register.lua app/main/oauth2/session.lua app/main/oauth2/token.lua app/main/oauth2/validate.lua app/main/opinion/_action/update.lua app/main/policy/_list.lua app/main/registration/_action/register.lua app/main/registration/_action/register_pin.lua app/main/registration/_action/update_vote.lua app/main/registration/_check_fiscal_code.lua app/main/registration/_register_form.lua app/main/registration/register.lua app/main/registration/register_completed.lua app/main/registration/register_enter_pin.lua app/main/registration/register_manual_check_needed.lua app/main/registration/register_rejected_age.lua app/main/registration_admin/_action/update_role_verification.lua app/main/registration_admin/_action/update_verification.lua app/main/registration_admin/_filter/90_admin.lua app/main/registration_admin/_role_verification_list.lua app/main/registration_admin/_verification_list.lua app/main/registration_admin/index.lua app/main/registration_admin/role_verification.lua app/main/registration_admin/role_verification_accredited.lua app/main/registration_admin/role_verification_cancelled.lua app/main/registration_admin/role_verification_rejected.lua app/main/registration_admin/role_verification_requests.lua app/main/registration_admin/verification.lua app/main/registration_admin/verification_accredited.lua app/main/registration_admin/verification_cancelled.lua app/main/registration_admin/verification_rejected.lua app/main/registration_admin/verification_requests.lua app/main/role/_action/request.lua app/main/role/_action/switch.lua app/main/role/_request_form.lua app/main/role/request.lua app/main/style/_style.lua app/main/style/style.css.lua app/main/suggestion/_action/add.lua app/main/unit/_head.lua app/main/unit/_sidebar.lua app/main/unit/_sidebar_whatcanido.lua app/main/unit/show.lua app/main/util/_unit_area_filter.lua app/main/vote/_action/update.lua app/main/vote/list.lua app/main/vote/show_incoming.lua config/example.lua env/format/interval_text.lua env/lf4rcs/commit.lua env/lf4rcs/init.lua env/model/has_rendered_content.lua env/request/router.lua env/ui/bargraph.lua env/ui/cell_full.lua env/ui/cell_main.lua env/ui/cell_sidebar.lua env/ui/field/location.lua env/ui/field/wysihtml.lua env/ui/filters.lua env/ui/grid.lua env/ui/map.lua env/ui/supporter_count.lua env/ui/title.lua env/util/api_error.lua env/util/get_access_token.lua env/util/html_is_safe.lua env/util/html_to_text.lua env/util/initiative_pie.lua env/util/is_profile_field_locked.lua env/util/micro_avatar.lua env/util/scope_name.lua env/util/wysihtml_preproc.lua lib/ontomap/ontomap.lua locale/translations.de.lua model/agent.lua model/direct_voter.lua model/draft.lua model/dynamic_application_scope.lua model/event.lua model/event_processed.lua model/initiative.lua model/issue.lua model/member.lua model/member_application.lua model/member_profile.lua model/member_settings.lua model/member_useterms.lua model/role_verification.lua model/session.lua model/suggestion.lua model/system_application.lua model/system_application_redirect_uri.lua model/token.lua model/token_scope.lua model/unit.lua model/verification.lua static/font/Apache_License.txt static/js/dragdrop.js static/mdl/LICENSE static/mdl/buffer.svg static/mdl/material.js static/mdl/material.min.js static/mdl/tick-mask.svg static/mdl/tick.svg static/wysihtml/advanced.js static/wysihtml/example.html static/wysihtml/jquery.1.10.2.js static/wysihtml/simple.js static/wysihtml/wysihtml.all-commands.js static/wysihtml/wysihtml.js static/wysihtml/wysihtml.toolbar.js static/wysihtml/wysihtml_liquidfeedback_rules.js style/mdl/LICENSE style/mdl/_color-definitions.scss style/mdl/_functions.scss style/mdl/_mixins.scss style/mdl/_variables.scss style/mdl/animation/_animation.scss style/mdl/badge/_badge.scss style/mdl/button/_button.scss style/mdl/card/_card.scss style/mdl/checkbox/_checkbox.scss style/mdl/chip/_chip.scss style/mdl/data-table/_data-table.scss style/mdl/dialog/_dialog.scss style/mdl/footer/_mega_footer.scss style/mdl/footer/_mini_footer.scss style/mdl/grid/_grid.scss style/mdl/icon-toggle/_icon-toggle.scss style/mdl/layout/_layout.scss style/mdl/list/_list.scss style/mdl/material-design-lite-grid.scss style/mdl/material-design-lite.scss style/mdl/menu/_menu.scss style/mdl/palette/_palette.scss style/mdl/progress/_progress.scss style/mdl/radio/_radio.scss style/mdl/resets/_h5bp.scss style/mdl/resets/_mobile.scss style/mdl/resets/_resets.scss style/mdl/ripple/_ripple.scss style/mdl/shadow/_shadow.scss style/mdl/slider/_slider.scss style/mdl/snackbar/_snackbar.scss style/mdl/spinner/_spinner.scss style/mdl/styleguide.scss style/mdl/switch/_switch.scss style/mdl/tabs/_tabs.scss style/mdl/template.scss style/mdl/textfield/_textfield.scss style/mdl/tooltip/_tooltip.scss style/mdl/typography/_typography.scss
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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
   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(" &nbsp; ")
   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(" &nbsp; ")
  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(" &middot; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
   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(" &nbsp; ")
   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(" &nbsp; ")
  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(" &nbsp; ")
  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 />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;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(" &nbsp; ")
  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("&nbsp;&nbsp;")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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("&nbsp;")
  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("&nbsp;")
  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(" &nbsp; ")
   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("&nbsp;&nbsp;")
   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(" &nbsp; ")
   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(" &nbsp; ")
  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("&nbsp;")
  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 ( "&nbsp;" )
  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(" &nbsp;&nbsp;&nbsp; ")
  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(" &nbsp;&nbsp;&nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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("&nbsp;")
  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 ( "&nbsp;" )
  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(" &middot; ")
  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(" &middot; ")
  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(" &middot; ")
  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(" &middot; ")
  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("&nbsp;") 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("&nbsp;") 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(" &nbsp; ")
 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(" &nbsp; ")
 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(" &nbsp; ")
 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(" &nbsp; ")
  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(" &nbsp;")
  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(" &nbsp; ")
  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(" &nbsp; ")
 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(" &nbsp; ")
 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(" &nbsp; ")
 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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
  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(" &nbsp; ")
 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(" &nbsp; ")
 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(" &nbsp; ")
 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(" &nbsp; ")
 167.110 +              ui.submit{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, value = "Save role data" }
 167.111 +              slot.put(" &nbsp; ")
 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(" &nbsp; ")
 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(" &nbsp; ")
  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(" &nbsp; ")
 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(" &nbsp; ")
 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(" &nbsp; ")
 172.109 +              ui.submit{ attr = { class = "mdl-button mdl-js-button mdl-button--raised" }, value = "Save personal data" }
 172.110 +              slot.put(" &nbsp; ")
 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(" &nbsp; ")
 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(" &nbsp; ")
  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("&nbsp;")
 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("&nbsp;")
 190.500 +                                  end
 190.501                                  }
 190.502 -                                slot.put("&nbsp;")
 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(" &nbsp; ")
 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(" &nbsp; ")
 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>&nbsp;<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, "<", "&lt;")
  212.81 +  str = string.gsub(str, ">", "&gt;")
  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, "&nbsp;", " ")
   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: '&#xE5D2;',
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:"&#xE5D2;",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>&nbsp;<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>&nbsp;<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 +        '&': '&amp;',
255.7214 +        '<': '&lt;',
255.7215 +        '>': '&gt;',
255.7216 +        '"': "&quot;",
255.7217 +        '\t':"&nbsp; "
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&lt;br&gt;"
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, "&nbsp; ");
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>&nbsp;</p>" ||
255.10329 +          innerHTML == "<p>&nbsp;</p><p>&nbsp;</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 &emsp; close enough to tab. Could not find enough counter arguments for now.
255.15315 +    composer.commands.exec("insertHTML", "&emsp;");
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 +}

Impressum / About Us