changeset 0:7ecd1a4ef557

add content
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 21 Mar 2019 19:15:52 -0600
parents
children 75262552dd35
files classpath.sh clearCache.sh conf/Init.luan conf/serve_nabble.luan lib/activation.jar lib/commons-fileupload-1.3.jar lib/commons-io-2.4.jar lib/commons-logging-1.1.3.jar lib/commons-primitives-1.0.jar lib/fschmidt.jar lib/gson-2.2.4.jar lib/jasypt-1.9.0.jar lib/jdom.jar lib/jetty-continuation-7.6.0.v20120127.jar lib/jetty-http-7.6.0.v20120127.jar lib/jetty-io-7.6.0.v20120127.jar lib/jetty-security-7.6.0.v20120127.jar lib/jetty-server-7.6.0.v20120127.jar lib/jetty-servlet-7.6.0.v20120127.jar lib/jetty-servlets-7.6.0.v20120127.jar lib/jetty-util-7.6.0.v20120127.jar lib/log4j-1.2.16.jar lib/luan.jar lib/lucene-analyzers-3.6.2.jar lib/lucene-core-3.6.2.jar lib/lucene-highlighter-3.6.2.jar lib/lucene-memory-3.6.2.jar lib/lucene-queries-3.6.2.jar lib/mail.jar lib/mstor.jar lib/postgresql-9.1-901.jdbc4.jar lib/servlet-api-2.5.jar lib/slf4j-api-1.6.4.jar lib/slf4j-log4j12-1.6.4.jar lib/yjp-controller-api-redist.jar luan.sh serve.sh src/cachingfilter/BufferInputStream.java src/cachingfilter/CachedDir.java src/cachingfilter/CachedFile.java src/cachingfilter/CachedPage.java src/cachingfilter/CachingFilter.java src/cachingfilter/CachingRequestWrapper.java src/cachingfilter/CachingResponseWrapper.java src/cachingfilter/FileHandler.java src/cachingfilter/Locker.java src/cachingfilter/ProxyServletOutputStream.java src/cachingfilter/ResponseAction.java src/global/GlobalJetty.java src/global/HtmlGlobalUtils.java src/global/HtmlGlobalUtils.jtp src/global/Server.java src/global/Site.java src/global/UrlMapperImpl.java src/global/WebCache.java src/global/web/ContactUs.java src/global/web/ContactUs.jtp src/global/web/Index.java src/global/web/Index.jtp src/global/web/PoweredBy.java src/global/web/PoweredBy.jtp src/global/web/PrivacyPolicy.java src/global/web/PrivacyPolicy.jtp src/global/web/RootForums.java src/global/web/RootForums.jtp src/global/web/Sitemap.java src/global/web/Terms.java src/global/web/Terms.jtp src/global/web/UserSites.java src/global/web/UserSites.jtp src/global/web/assets/font-awesome/css/font-awesome.css src/global/web/assets/font-awesome/css/font-awesome.min.css src/global/web/assets/font-awesome/fonts/FontAwesome.otf src/global/web/assets/font-awesome/fonts/fontawesome-webfont.eot src/global/web/assets/font-awesome/fonts/fontawesome-webfont.svg src/global/web/assets/font-awesome/fonts/fontawesome-webfont.ttf src/global/web/assets/font-awesome/fonts/fontawesome-webfont.woff src/global/web/assets/font-awesome/fonts/fontawesome-webfont.woff2 src/global/web/assets/global.css src/global/web/assets/images/debian.png src/global/web/assets/images/exim.png src/global/web/assets/images/gray.png src/global/web/assets/images/home.png src/global/web/assets/images/java.gif src/global/web/assets/images/jetty.gif src/global/web/assets/images/logo_nabble_home.png src/global/web/assets/images/postgres.gif src/global/web/assets/jquery/jquery-1.9.1.min.js src/global/web/nabble.css src/global/web/tools/Index.java src/global/web/tools/Index.jtp src/global/web/tools/Reindex.java src/global/web/tools/Reindex.jtp src/global/web/tools/Search.java src/global/web/tools/Search.jtp src/global/web/tools/run.luan src/global/web/tools/shell.luan src/jdbcpgbackup/CachingDBOFactory.java src/jdbcpgbackup/Constraint.java src/jdbcpgbackup/DBOFactory.java src/jdbcpgbackup/DataFilter.java src/jdbcpgbackup/DbBackupObject.java src/jdbcpgbackup/Index.java src/jdbcpgbackup/JdbcPgBackup.java src/jdbcpgbackup/Schema.java src/jdbcpgbackup/Sequence.java src/jdbcpgbackup/Table.java src/jdbcpgbackup/View.java src/jdbcpgbackup/ZipBackup.java src/nabble/data/global.schema src/nabble/data/site.schema src/nabble/model/AbstractType.java src/nabble/model/Anonymous.java src/nabble/model/Batch.java src/nabble/model/CursorNodeIterator.java src/nabble/model/DailyNumber.java src/nabble/model/Db.java src/nabble/model/DbGlobalUpdater.java src/nabble/model/DbGlobalUpdater.jmp src/nabble/model/DbParamSetter.java src/nabble/model/DbSiteCreator.java src/nabble/model/DbSiteUpdater.java src/nabble/model/DbSiteUpdater.jmp src/nabble/model/DbUpdater.java src/nabble/model/Executors.java src/nabble/model/ExtensionFactory.java src/nabble/model/FileUpload.java src/nabble/model/FilteredNodeIterator.java src/nabble/model/Init.java src/nabble/model/ListServer.java src/nabble/model/Lucene.java src/nabble/model/MailMessageFormat.java src/nabble/model/MailSubsystem.java src/nabble/model/MailingList.java src/nabble/model/MailingListImpl.java src/nabble/model/MailingLists.java src/nabble/model/Message.java src/nabble/model/MessageFormatImpls.java src/nabble/model/MessageUtils.java src/nabble/model/ModelException.java src/nabble/model/ModelHome.java src/nabble/model/Node.java src/nabble/model/NodeImpl.java src/nabble/model/NodeIterator.java src/nabble/model/NodeSearcher.java src/nabble/model/Person.java src/nabble/model/PersonImpl.java src/nabble/model/PostByEmail.java src/nabble/model/Site.java src/nabble/model/SiteGlobal.java src/nabble/model/SiteImpl.java src/nabble/model/SiteKey.java src/nabble/model/Subscription.java src/nabble/model/SubscriptionImpl.java src/nabble/model/SystemProperties.java src/nabble/model/TagImpl.java src/nabble/model/UpdatingException.java src/nabble/model/User.java src/nabble/model/UserImpl.java src/nabble/model/ViewCount.java src/nabble/model/export/AbstractImportImpl.java src/nabble/model/export/Export.java src/nabble/model/export/Import.java src/nabble/model/export/ImportImpl.java src/nabble/model/export/ImportServer.java src/nabble/model/export/ImportServerImpl.java src/nabble/model/export/NodeData.java src/nabble/model/lucene/HitCollector.java src/nabble/model/lucene/IndexCache.java src/nabble/model/lucene/LuceneSearcher.java src/nabble/model/lucene/LuceneSearcherImpl.java src/nabble/modules/ModuleManager.java src/nabble/modules/NamlModule.java src/nabble/modules/ad/Ad.java src/nabble/modules/ad/AdModule.java src/nabble/modules/ad/BaseNamespaceExt.java src/nabble/modules/ad/Javascript.java src/nabble/modules/ad/Javascript.jmp src/nabble/modules/ad/SetAdType.java src/nabble/modules/ad/SetAdType.jtp src/nabble/modules/ad/ad.naml src/nabble/modules/hacks/HacksModule.java src/nabble/modules/hacks/UserHack.java src/nabble/modules/hacks/UserNamespaceExt.java src/nabble/modules/naml/ads.naml src/nabble/modules/naml/ads_auto.naml src/nabble/modules/naml/ads_manual.naml src/nabble/modules/naml/adsnew.naml src/nabble/modules/naml/animeron.naml src/nabble/modules/naml/banhax.naml src/nabble/modules/naml/blackcow.naml src/nabble/modules/naml/content_news_summary.naml src/nabble/modules/naml/content_noindex.naml src/nabble/modules/naml/content_open_links_in_new_window.naml src/nabble/modules/naml/content_smart_cache.naml src/nabble/modules/naml/email_registration_notification.naml src/nabble/modules/naml/expire_old_threads.naml src/nabble/modules/naml/forexschoolonline.naml src/nabble/modules/naml/forum_avatars.naml src/nabble/modules/naml/invite_subscribers.naml src/nabble/modules/naml/jonasbirrexblog.naml src/nabble/modules/naml/jonaspm.naml src/nabble/modules/naml/lang_arabic.naml src/nabble/modules/naml/lang_ch_si.naml src/nabble/modules/naml/lang_ch_tr.naml src/nabble/modules/naml/lang_cs_cz.naml src/nabble/modules/naml/lang_de.naml src/nabble/modules/naml/lang_ell.naml src/nabble/modules/naml/lang_es.naml src/nabble/modules/naml/lang_fr_fr.naml src/nabble/modules/naml/lang_pl.naml src/nabble/modules/naml/lang_pt_br.naml src/nabble/modules/naml/lang_rus_ru.naml src/nabble/modules/naml/lang_sv.naml src/nabble/modules/naml/lang_tu.naml src/nabble/modules/naml/mobile.naml src/nabble/modules/naml/more_ads.naml src/nabble/modules/naml/pedxing.naml src/nabble/modules/naml/ppc.naml src/nabble/modules/naml/responsive.naml src/nabble/modules/naml/social_dropdown_links.naml src/nabble/modules/naml/social_facebook_like.naml src/nabble/modules/naml/social_google_plus_one.naml src/nabble/modules/naml/social_tweet.naml src/nabble/modules/naml/spam_searcher.naml src/nabble/modules/naml/thread_navigation.naml src/nabble/modules/naml/zimbaroo.naml src/nabble/modules/poll/NodeNamespaceExt.java src/nabble/modules/poll/Poll.java src/nabble/modules/poll/PollModule.java src/nabble/modules/poll/poll.naml src/nabble/modules/workgroup/Assignment.java src/nabble/modules/workgroup/NodeNamespaceExt.java src/nabble/modules/workgroup/WorkgroupModule.java src/nabble/modules/workgroup/workgroup.naml src/nabble/naml/compiler/BlockWrapper.java src/nabble/naml/compiler/BooleanFormatException.java src/nabble/naml/compiler/Call.java src/nabble/naml/compiler/Chunk.java src/nabble/naml/compiler/Command.java src/nabble/naml/compiler/CommandSpec.java src/nabble/naml/compiler/CompileException.java src/nabble/naml/compiler/CompileMethodException.java src/nabble/naml/compiler/Compiler.java src/nabble/naml/compiler/Encoder.java src/nabble/naml/compiler/ExitException.java src/nabble/naml/compiler/GenericNamespace.java src/nabble/naml/compiler/IPrintWriter.java src/nabble/naml/compiler/Interpreter.java src/nabble/naml/compiler/InterpreterImpl.java src/nabble/naml/compiler/JavaCall.java src/nabble/naml/compiler/JavaCommand.java src/nabble/naml/compiler/JavaNamespace.java src/nabble/naml/compiler/Macro.java src/nabble/naml/compiler/MacroNamespace.java src/nabble/naml/compiler/MacroScope.java src/nabble/naml/compiler/Meaning.java src/nabble/naml/compiler/Module.java src/nabble/naml/compiler/Namespace.java src/nabble/naml/compiler/NamespaceExtension.java src/nabble/naml/compiler/NamlNullPointerException.java src/nabble/naml/compiler/ParamChunk.java src/nabble/naml/compiler/ParamMeaning.java src/nabble/naml/compiler/Primitive.java src/nabble/naml/compiler/PrintWriter.java src/nabble/naml/compiler/Program.java src/nabble/naml/compiler/RunState.java src/nabble/naml/compiler/RunStateImpl.java src/nabble/naml/compiler/ScopedInterpreter.java src/nabble/naml/compiler/ScopedInterpreterImpl.java src/nabble/naml/compiler/Source.java src/nabble/naml/compiler/StackTrace.java src/nabble/naml/compiler/StackTraceElement.java src/nabble/naml/compiler/StringChunk.java src/nabble/naml/compiler/Template.java src/nabble/naml/compiler/TemplatePrintWriter.java src/nabble/naml/compiler/TemplateRuntimeException.java src/nabble/naml/compiler/Usage.java src/nabble/naml/dom/Attribute.java src/nabble/naml/dom/Cdata.java src/nabble/naml/dom/Comment.java src/nabble/naml/dom/Container.java src/nabble/naml/dom/Element.java src/nabble/naml/dom/ElementName.java src/nabble/naml/dom/EmptyElement.java src/nabble/naml/dom/Naml.java src/nabble/naml/dom/ParseException.java src/nabble/naml/dom/Parser.java src/nabble/naml/dom/ParserImpl.java src/nabble/naml/dom/Text.java src/nabble/naml/namespaces/BasicNamespace.java src/nabble/naml/namespaces/CommandDoc.java src/nabble/naml/namespaces/IntegerNamespace.java src/nabble/naml/namespaces/ListSequence.java src/nabble/naml/namespaces/RegexNamespace.java src/nabble/naml/namespaces/Sequence.java src/nabble/naml/namespaces/StringList.java src/nabble/naml/namespaces/TemplateException.java src/nabble/utils/Jetty.java src/nabble/utils/Log4j.java src/nabble/utils/LuanServlet.java src/nabble/utils/Utils.luan src/nabble/utils/init.luan src/nabble/utils/luan/Http.luan src/nabble/utils/luan/HttpServicer.java src/nabble/view/lib/Cache.java src/nabble/view/lib/ChangeEmailMail.java src/nabble/view/lib/ChangeEmailMail.jtp src/nabble/view/lib/ClearCache.java src/nabble/view/lib/EmbedUtils.java src/nabble/view/lib/EmbedUtils.jmp src/nabble/view/lib/HtmlViewUtils.java src/nabble/view/lib/HtmlViewUtils.jtp src/nabble/view/lib/Jtp.java src/nabble/view/lib/JtpContextServlet.java src/nabble/view/lib/MinorServletException.java src/nabble/view/lib/MyJtpServlet.java src/nabble/view/lib/NabbleConnectionLimitFilter.java src/nabble/view/lib/NabbleErrorFilter.java src/nabble/view/lib/NabbleErrorHandler.java src/nabble/view/lib/NabbleErrorHandler.jtp src/nabble/view/lib/NewSiteMail.java src/nabble/view/lib/NewSiteMail.jtp src/nabble/view/lib/Permissions.java src/nabble/view/lib/Recaptcha.java src/nabble/view/lib/Shared.java src/nabble/view/lib/Shared.jtp src/nabble/view/lib/SiteDeleteMail.java src/nabble/view/lib/SiteDeleteMail.jtp src/nabble/view/lib/SubscribeDefaultsMail.java src/nabble/view/lib/SubscribeDefaultsMail.jtp src/nabble/view/lib/Test.java src/nabble/view/lib/TopicView.java src/nabble/view/lib/UrlMappable.java src/nabble/view/lib/UrlMapperImpl.java src/nabble/view/lib/ViewUtils.java src/nabble/view/lib/help/Help.java src/nabble/view/lib/help/Help.jmp src/nabble/view/naml/action_row.naml src/nabble/view/naml/adv_search.naml src/nabble/view/naml/amazon_payments.naml src/nabble/view/naml/app.naml src/nabble/view/naml/backup.naml src/nabble/view/naml/ban_user.naml src/nabble/view/naml/change_app_type.naml src/nabble/view/naml/change_appearance.naml src/nabble/view/naml/change_permissions.naml src/nabble/view/naml/change_post_date.naml src/nabble/view/naml/change_title_and_meta_tags.naml src/nabble/view/naml/change_user_groups.naml src/nabble/view/naml/columns.naml src/nabble/view/naml/compile_all.naml src/nabble/view/naml/configurations.naml src/nabble/view/naml/create_sub_app.naml src/nabble/view/naml/date.naml src/nabble/view/naml/delete_app.naml src/nabble/view/naml/delete_node.naml src/nabble/view/naml/doc.naml src/nabble/view/naml/dropdown.naml src/nabble/view/naml/edit_app.naml src/nabble/view/naml/edit_post.naml src/nabble/view/naml/edit_profile.naml src/nabble/view/naml/edit_signature.naml src/nabble/view/naml/email.naml src/nabble/view/naml/feeds.naml src/nabble/view/naml/forgot_password.naml src/nabble/view/naml/javascript_library.naml src/nabble/view/naml/js_page.naml src/nabble/view/naml/layout.naml src/nabble/view/naml/login.naml src/nabble/view/naml/logout.naml src/nabble/view/naml/macro_viewer.naml src/nabble/view/naml/mailing_list.naml src/nabble/view/naml/mailing_list_settings.naml src/nabble/view/naml/manage_banned_users.naml src/nabble/view/naml/manage_subscribers.naml src/nabble/view/naml/manage_users_and_groups.naml src/nabble/view/naml/message.naml src/nabble/view/naml/move_node.naml src/nabble/view/naml/new_topic.naml src/nabble/view/naml/people.naml src/nabble/view/naml/permissions.naml src/nabble/view/naml/post_by_email.naml src/nabble/view/naml/post_by_email_page.naml src/nabble/view/naml/print_post.naml src/nabble/view/naml/register.naml src/nabble/view/naml/reply.naml src/nabble/view/naml/rest_group_control.naml src/nabble/view/naml/search.naml src/nabble/view/naml/show_macro.naml src/nabble/view/naml/static.naml src/nabble/view/naml/subapps.naml src/nabble/view/naml/subscribe.naml src/nabble/view/naml/text_editor.naml src/nabble/view/naml/tools.naml src/nabble/view/naml/topic.naml src/nabble/view/naml/trkg.naml src/nabble/view/naml/ui_components.naml src/nabble/view/naml/unauthorized.naml src/nabble/view/naml/unban_user.naml src/nabble/view/naml/unsubscribe.naml src/nabble/view/naml/url_mapper.naml src/nabble/view/naml/use_google_analytics.naml src/nabble/view/naml/user.naml src/nabble/view/naml/user_nodes.naml src/nabble/view/naml/user_profile.naml src/nabble/view/naml/utilities.naml src/nabble/view/naml/view_blog.naml src/nabble/view/naml/view_board.naml src/nabble/view/naml/view_category.naml src/nabble/view/naml/view_gallery.naml src/nabble/view/naml/view_mixed.naml src/nabble/view/naml/view_news.naml src/nabble/view/naml/view_standard.naml src/nabble/view/naml/view_topics.naml src/nabble/view/naml/widget.naml src/nabble/view/web/Index.java src/nabble/view/web/Index.jtp src/nabble/view/web/Javascript.java src/nabble/view/web/Javascript.jtp src/nabble/view/web/ads/ads.css src/nabble/view/web/ads/ads.js src/nabble/view/web/app/Addons.java src/nabble/view/web/app/Addons.jtp src/nabble/view/web/app/Languages.java src/nabble/view/web/app/Languages.jtp src/nabble/view/web/assets/bootstrap/css/bootstrap.css src/nabble/view/web/assets/bootstrap/css/bootstrap.min.css src/nabble/view/web/assets/bootstrap/js/bootstrap.js src/nabble/view/web/assets/bootstrap/js/bootstrap.min.js src/nabble/view/web/assets/font-awesome/.gitignore src/nabble/view/web/assets/font-awesome/css/font-awesome-ie7.min.css src/nabble/view/web/assets/font-awesome/css/font-awesome.css src/nabble/view/web/assets/font-awesome/css/font-awesome.min.css src/nabble/view/web/assets/font-awesome/font/FontAwesome.otf src/nabble/view/web/assets/font-awesome/font/fontawesome-webfont.eot src/nabble/view/web/assets/font-awesome/font/fontawesome-webfont.svg src/nabble/view/web/assets/font-awesome/font/fontawesome-webfont.ttf src/nabble/view/web/assets/font-awesome/font/fontawesome-webfont.woff src/nabble/view/web/assets/jquery/jquery-1.9.1.min.js src/nabble/view/web/catalog/ChangeParent.java src/nabble/view/web/catalog/ChangeParent.jtp src/nabble/view/web/catalog/ChangePinOrder.java src/nabble/view/web/catalog/ChangePinOrder.jtp src/nabble/view/web/catalog/ExportConfirmation.java src/nabble/view/web/catalog/ExportConfirmation.jtp src/nabble/view/web/catalog/SetPin.java src/nabble/view/web/embed/EmbedInfo.java src/nabble/view/web/embed/EmbedInfo.jtp src/nabble/view/web/embed/EmbedOptions.java src/nabble/view/web/embed/EmbedOptions.jtp src/nabble/view/web/embed/JsEmbed.java src/nabble/view/web/embed/JsEmbed.jtp src/nabble/view/web/embed/NabbleEmbed.java src/nabble/view/web/embed/NabbleEmbed.jtp src/nabble/view/web/end.luan src/nabble/view/web/favicon.ico src/nabble/view/web/forum/AttachmentDownload.java src/nabble/view/web/forum/ChangeDomainName.java src/nabble/view/web/forum/ChangeDomainName.jtp src/nabble/view/web/forum/ClearDeleteDate.java src/nabble/view/web/forum/FileDownload.java src/nabble/view/web/forum/NodeEditorNamespace.java src/nabble/view/web/forum/Permalink.java src/nabble/view/web/forum/SearchNamespace.java src/nabble/view/web/forum/Thumbnail.java src/nabble/view/web/forum/UploadFile.java src/nabble/view/web/forum/UploadFile.jtp src/nabble/view/web/forum/UploadFile2.java src/nabble/view/web/forum/UploadFile2.jtp src/nabble/view/web/forum/UploadImage.java src/nabble/view/web/forum/UploadImage.jtp src/nabble/view/web/forum/UploadImage2.java src/nabble/view/web/forum/UploadImage2.jtp src/nabble/view/web/google5f301677badb6983.java src/nabble/view/web/google5f301677badb6983.jtp src/nabble/view/web/help/Answer.java src/nabble/view/web/help/Answer.jtp src/nabble/view/web/help/DNSConfiguration.java src/nabble/view/web/help/DNSConfiguration.jtp src/nabble/view/web/help/Index.java src/nabble/view/web/help/Index.jtp src/nabble/view/web/help/SearchHelp.java src/nabble/view/web/help/SearchHelp.jtp src/nabble/view/web/images/abuse_email.png src/nabble/view/web/images/account.png src/nabble/view/web/images/add.png src/nabble/view/web/images/arrow.png src/nabble/view/web/images/arrowdown.png src/nabble/view/web/images/arrowleft.png src/nabble/view/web/images/arrowup.png src/nabble/view/web/images/avatar100.png src/nabble/view/web/images/avatar24.png src/nabble/view/web/images/avatar_no.png src/nabble/view/web/images/avatar_yes.png src/nabble/view/web/images/bold.png src/nabble/view/web/images/box.png src/nabble/view/web/images/btn_bg.gif src/nabble/view/web/images/check.png src/nabble/view/web/images/close.png src/nabble/view/web/images/collapse.gif src/nabble/view/web/images/colors.png src/nabble/view/web/images/connect-end.gif src/nabble/view/web/images/connect-line.gif src/nabble/view/web/images/connect-node-closed.gif src/nabble/view/web/images/connect-node.gif src/nabble/view/web/images/credits.png src/nabble/view/web/images/debian.png src/nabble/view/web/images/edit_sm.png src/nabble/view/web/images/embed.png src/nabble/view/web/images/embedded/atwtmb.png src/nabble/view/web/images/embedded/carrot2.png src/nabble/view/web/images/embedded/netty.png src/nabble/view/web/images/embedded/plone.png src/nabble/view/web/images/embedded/ruralmailtalk.png src/nabble/view/web/images/exim.png src/nabble/view/web/images/eye.png src/nabble/view/web/images/feeds.png src/nabble/view/web/images/file.png src/nabble/view/web/images/file_modified.png src/nabble/view/web/images/flag_gray.png src/nabble/view/web/images/forum.png src/nabble/view/web/images/forum_pin.png src/nabble/view/web/images/forum_sm.png src/nabble/view/web/images/forum_star_sm.png src/nabble/view/web/images/gear.png src/nabble/view/web/images/green.gif src/nabble/view/web/images/grey.gif src/nabble/view/web/images/grip.png src/nabble/view/web/images/hand.png src/nabble/view/web/images/help/application_nodes.png src/nabble/view/web/images/help/distributed.png src/nabble/view/web/images/help/help_embed.png src/nabble/view/web/images/help/help_embed_default.png src/nabble/view/web/images/help/help_embed_example.png src/nabble/view/web/images/help/help_embed_permalink.png src/nabble/view/web/images/help/help_embed_permalink2.png src/nabble/view/web/images/help/help_node_structure.png src/nabble/view/web/images/help/help_style_easy.png src/nabble/view/web/images/help/help_style_tab.png src/nabble/view/web/images/help/help_sub_forums.png src/nabble/view/web/images/help/members_only.png src/nabble/view/web/images/help/mixed_lengths.png src/nabble/view/web/images/help/registered_only.png src/nabble/view/web/images/help/subscribe.png src/nabble/view/web/images/help/subscribe2.png src/nabble/view/web/images/hide.gif src/nabble/view/web/images/homepage/archive.png src/nabble/view/web/images/homepage/blog.png src/nabble/view/web/images/homepage/blog_md.png src/nabble/view/web/images/homepage/blog_sm.png src/nabble/view/web/images/homepage/forum.png src/nabble/view/web/images/homepage/forum_md.png src/nabble/view/web/images/homepage/gallery.png src/nabble/view/web/images/homepage/gallery_md.png src/nabble/view/web/images/homepage/gallery_sm.png src/nabble/view/web/images/homepage/gray.png src/nabble/view/web/images/homepage/mailing-list.png src/nabble/view/web/images/homepage/news_sm.png src/nabble/view/web/images/homepage/newspaper.png src/nabble/view/web/images/homepage/newspaper_md.png src/nabble/view/web/images/homepage/separator.png src/nabble/view/web/images/icon_alert.png src/nabble/view/web/images/icon_alert_sm.png src/nabble/view/web/images/icon_attachment.gif src/nabble/view/web/images/icon_blocked.png src/nabble/view/web/images/icon_blocked_blue.png src/nabble/view/web/images/icon_blocked_gray.png src/nabble/view/web/images/icon_blocked_red.png src/nabble/view/web/images/icon_down.png src/nabble/view/web/images/icon_first.png src/nabble/view/web/images/icon_first_disabled.png src/nabble/view/web/images/icon_happy.png src/nabble/view/web/images/icon_info.png src/nabble/view/web/images/icon_last.png src/nabble/view/web/images/icon_last_disabled.png src/nabble/view/web/images/icon_message.png src/nabble/view/web/images/icon_minus.png src/nabble/view/web/images/icon_next.png src/nabble/view/web/images/icon_next_disabled.png src/nabble/view/web/images/icon_orphan.png src/nabble/view/web/images/icon_pending.png src/nabble/view/web/images/icon_plus.png src/nabble/view/web/images/icon_post_message.png src/nabble/view/web/images/icon_prev.png src/nabble/view/web/images/icon_prev_disabled.png src/nabble/view/web/images/icon_read_only_forum.png src/nabble/view/web/images/icon_star_blue.png src/nabble/view/web/images/icon_star_grey.png src/nabble/view/web/images/icon_star_red.png src/nabble/view/web/images/icon_starred.png src/nabble/view/web/images/icon_tree_forum.png src/nabble/view/web/images/icon_tree_post.png src/nabble/view/web/images/icon_tri.png src/nabble/view/web/images/icon_unstarred.png src/nabble/view/web/images/icon_up.png src/nabble/view/web/images/image.png src/nabble/view/web/images/info.png src/nabble/view/web/images/italic.png src/nabble/view/web/images/java.gif src/nabble/view/web/images/jetty.gif src/nabble/view/web/images/jscolor/arrow.gif src/nabble/view/web/images/jscolor/cross.gif src/nabble/view/web/images/jscolor/hs.png src/nabble/view/web/images/jscolor/hv.png src/nabble/view/web/images/left.png src/nabble/view/web/images/link.png src/nabble/view/web/images/loading.png src/nabble/view/web/images/lock.png src/nabble/view/web/images/lock_sm.png src/nabble/view/web/images/logo.gif src/nabble/view/web/images/logo2.gif src/nabble/view/web/images/logo_blogs.png src/nabble/view/web/images/logo_forums.png src/nabble/view/web/images/logo_gallery.png src/nabble/view/web/images/logo_nabble_forums.png src/nabble/view/web/images/logo_nabble_home.png src/nabble/view/web/images/logo_news.png src/nabble/view/web/images/mail.png src/nabble/view/web/images/maintenance_anim.gif src/nabble/view/web/images/more.gif src/nabble/view/web/images/more.png src/nabble/view/web/images/naml.png src/nabble/view/web/images/nop.gif src/nabble/view/web/images/offline.png src/nabble/view/web/images/online.png src/nabble/view/web/images/paperclip.png src/nabble/view/web/images/people.png src/nabble/view/web/images/people_sm.png src/nabble/view/web/images/pin.png src/nabble/view/web/images/plus.png src/nabble/view/web/images/postgres.gif src/nabble/view/web/images/promo/anonymous.png src/nabble/view/web/images/promo/ban.png src/nabble/view/web/images/promo/embed.png src/nabble/view/web/images/promo/format.png src/nabble/view/web/images/promo/pin.png src/nabble/view/web/images/promo/private.png src/nabble/view/web/images/promo/simple.png src/nabble/view/web/images/promo/thread.png src/nabble/view/web/images/promo/views.png src/nabble/view/web/images/quote.png src/nabble/view/web/images/remove_sm.png src/nabble/view/web/images/revert.png src/nabble/view/web/images/right.png src/nabble/view/web/images/search.png src/nabble/view/web/images/shadow.png src/nabble/view/web/images/show.gif src/nabble/view/web/images/small_square.gif src/nabble/view/web/images/smiley/anim_blbl.gif src/nabble/view/web/images/smiley/anim_claps.gif src/nabble/view/web/images/smiley/anim_confused.gif src/nabble/view/web/images/smiley/anim_crazy.gif src/nabble/view/web/images/smiley/anim_drunk.gif src/nabble/view/web/images/smiley/anim_handshake.gif src/nabble/view/web/images/smiley/anim_jump.gif src/nabble/view/web/images/smiley/anim_rules.gif src/nabble/view/web/images/smiley/anim_working.gif src/nabble/view/web/images/smiley/argue.gif src/nabble/view/web/images/smiley/bear_hug.gif src/nabble/view/web/images/smiley/big_grin.png src/nabble/view/web/images/smiley/birthday.gif src/nabble/view/web/images/smiley/blbl.gif src/nabble/view/web/images/smiley/bouquet.gif src/nabble/view/web/images/smiley/clap.gif src/nabble/view/web/images/smiley/compute.gif src/nabble/view/web/images/smiley/confused.png src/nabble/view/web/images/smiley/cry.png src/nabble/view/web/images/smiley/dont_know.gif src/nabble/view/web/images/smiley/drink.gif src/nabble/view/web/images/smiley/grin.gif src/nabble/view/web/images/smiley/heart.gif src/nabble/view/web/images/smiley/joy.gif src/nabble/view/web/images/smiley/laugh.gif src/nabble/view/web/images/smiley/oh.png src/nabble/view/web/images/smiley/pig.png src/nabble/view/web/images/smiley/respect.gif src/nabble/view/web/images/smiley/rules.gif src/nabble/view/web/images/smiley/smile.png src/nabble/view/web/images/smiley/smiley_angry.gif src/nabble/view/web/images/smiley/smiley_angry2.gif src/nabble/view/web/images/smiley/smiley_argh.gif src/nabble/view/web/images/smiley/smiley_beam.gif src/nabble/view/web/images/smiley/smiley_blush.gif src/nabble/view/web/images/smiley/smiley_cool.gif src/nabble/view/web/images/smiley/smiley_cry.gif src/nabble/view/web/images/smiley/smiley_evil.gif src/nabble/view/web/images/smiley/smiley_good.gif src/nabble/view/web/images/smiley/smiley_grin.gif src/nabble/view/web/images/smiley/smiley_happy.gif src/nabble/view/web/images/smiley/smiley_hurt.gif src/nabble/view/web/images/smiley/smiley_music.gif src/nabble/view/web/images/smiley/smiley_mustach.gif src/nabble/view/web/images/smiley/smiley_ninja.gif src/nabble/view/web/images/smiley/smiley_oh.gif src/nabble/view/web/images/smiley/smiley_oh_no.gif src/nabble/view/web/images/smiley/smiley_pirate.gif src/nabble/view/web/images/smiley/smiley_sad.gif src/nabble/view/web/images/smiley/smiley_scared.gif src/nabble/view/web/images/smiley/smiley_sleep.gif src/nabble/view/web/images/smiley/smiley_super.gif src/nabble/view/web/images/smiley/smiley_teeth.gif src/nabble/view/web/images/smiley/smiley_thinking.gif src/nabble/view/web/images/smiley/smiley_tongue.gif src/nabble/view/web/images/smiley/smiley_uh.gif src/nabble/view/web/images/smiley/smiley_unhappy.gif src/nabble/view/web/images/smiley/smiley_what.gif src/nabble/view/web/images/smiley/smiley_whistling.gif src/nabble/view/web/images/smiley/smiley_wink.gif src/nabble/view/web/images/smiley/sunglasses.png src/nabble/view/web/images/smiley/tongue.png src/nabble/view/web/images/smiley/unhappy.png src/nabble/view/web/images/smiley/wink.png src/nabble/view/web/images/social/delicious.png src/nabble/view/web/images/social/digg.png src/nabble/view/web/images/social/facebook.png src/nabble/view/web/images/social/google.png src/nabble/view/web/images/social/linkedin.png src/nabble/view/web/images/social/stumbleupon.png src/nabble/view/web/images/social/twitter.png src/nabble/view/web/images/styles/connect-end.gif src/nabble/view/web/images/styles/connect-line.gif src/nabble/view/web/images/styles/connect-node.gif src/nabble/view/web/images/styles/glossy-dark-highlight.gif src/nabble/view/web/images/styles/glossy-dark.gif src/nabble/view/web/images/styles/glossy-highlight.gif src/nabble/view/web/images/styles/glossy-light-highlight.gif src/nabble/view/web/images/styles/glossy-light.gif src/nabble/view/web/images/styles/pink-shaded.png src/nabble/view/web/images/styles/pink.png src/nabble/view/web/images/styles/preview_bluesky.png src/nabble/view/web/images/styles/preview_darkness.png src/nabble/view/web/images/styles/preview_desert.png src/nabble/view/web/images/styles/preview_glossy.png src/nabble/view/web/images/styles/preview_moonlight.png src/nabble/view/web/images/styles/preview_pink.png src/nabble/view/web/images/styles/preview_rainy.png src/nabble/view/web/images/styles/rainy-shaded.png src/nabble/view/web/images/styles/rainy.png src/nabble/view/web/images/submenu.gif src/nabble/view/web/images/success.png src/nabble/view/web/images/support.png src/nabble/view/web/images/terms_email.png src/nabble/view/web/images/thread.png src/nabble/view/web/images/thread_sm.png src/nabble/view/web/images/tool.png src/nabble/view/web/images/tool_big.png src/nabble/view/web/images/top-dropdown.png src/nabble/view/web/images/topShadow.png src/nabble/view/web/images/unpin.png src/nabble/view/web/images/user_group.png src/nabble/view/web/images/view-classic.gif src/nabble/view/web/images/view-list.gif src/nabble/view/web/images/view-threaded.gif src/nabble/view/web/images/views_category.png src/nabble/view/web/images/views_children.png src/nabble/view/web/images/views_nabble.png src/nabble/view/web/images/views_standard.png src/nabble/view/web/images/world.png src/nabble/view/web/images/zip.png src/nabble/view/web/mailing_list/MailingListNamespace.java src/nabble/view/web/mailing_list/MailingListOptions.java src/nabble/view/web/mailing_list/MailingListOptions.jtp src/nabble/view/web/mailing_list/Subscribe.java src/nabble/view/web/mailing_list/Subscribe.jtp src/nabble/view/web/mailing_list/Subscribe2.java src/nabble/view/web/mailing_list/Subscribe2.jtp src/nabble/view/web/mailing_list/SubscribeDefaults.java src/nabble/view/web/mailing_list/SubscribeDefaults.jtp src/nabble/view/web/mailing_list/SubscribeToMailingList.java src/nabble/view/web/mailing_list/SubscribeToMailingList.jtp src/nabble/view/web/mailing_list/Unsubscribe.java src/nabble/view/web/mailing_list/Unsubscribe.jtp src/nabble/view/web/mailing_list/Unsubscribe2.java src/nabble/view/web/mailing_list/Unsubscribe2.jtp src/nabble/view/web/mailing_list/UnsubscribeFromMailingList.java src/nabble/view/web/mailing_list/UnsubscribeFromMailingList.jtp src/nabble/view/web/more/Forum.java src/nabble/view/web/more/Forum.jtp src/nabble/view/web/more/ForumStart.java src/nabble/view/web/more/ForumStart.jtp src/nabble/view/web/more/MailingListRequest.java src/nabble/view/web/more/MailingListRequest.jtp src/nabble/view/web/nabble.css src/nabble/view/web/seo/WidgetRedir.java src/nabble/view/web/seo/WidgetRedir.jtp src/nabble/view/web/template/CacheNamespace.java src/nabble/view/web/template/CalendarWidget.java src/nabble/view/web/template/CalendarWidget.jtp src/nabble/view/web/template/CompileTest.java src/nabble/view/web/template/DateNamespace.java src/nabble/view/web/template/DocNamespace.java src/nabble/view/web/template/EmailNamespace.java src/nabble/view/web/template/ErrorNamespace.java src/nabble/view/web/template/FieldNamespace.java src/nabble/view/web/template/HtmlListNamespace.java src/nabble/view/web/template/HtmlNamespace.java src/nabble/view/web/template/InstantMailNamespace.java src/nabble/view/web/template/MacroEditorNamespace.java src/nabble/view/web/template/MacroSourceNamespace.java src/nabble/view/web/template/MessageNamespace.java src/nabble/view/web/template/MonthlyArchivesNamespace.java src/nabble/view/web/template/NabbleNamespace.java src/nabble/view/web/template/NamespaceUtils.java src/nabble/view/web/template/NamlConfigurationNamespace.java src/nabble/view/web/template/NamlDownload.java src/nabble/view/web/template/NamlEditor.java src/nabble/view/web/template/NamlEditor.jtp src/nabble/view/web/template/NamlLogger.java src/nabble/view/web/template/NamlServlet.java src/nabble/view/web/template/NamlTest.java src/nabble/view/web/template/NodeList.java src/nabble/view/web/template/NodeNamespace.java src/nabble/view/web/template/NodePageNamespace.java src/nabble/view/web/template/PagingNamespace.java src/nabble/view/web/template/RegistrationNamespace.java src/nabble/view/web/template/RequestNamespace.java src/nabble/view/web/template/ServletNamespace.java src/nabble/view/web/template/ServletNamespaceUtils.java src/nabble/view/web/template/SubscriptionNamespace.java src/nabble/view/web/template/TagArgs.java src/nabble/view/web/template/UrlMapperNamespace.java src/nabble/view/web/template/UserNamespace.java src/nabble/view/web/template/UserPageNamespace.java src/nabble/view/web/tools/Admin.java src/nabble/view/web/tools/Admin.jtp src/nabble/view/web/tools/AdminNotice.java src/nabble/view/web/tools/AdminNotice.jtp src/nabble/view/web/tools/Index.java src/nabble/view/web/tools/Index.jtp src/nabble/view/web/tools/NamlEditor.java src/nabble/view/web/tools/OnlineUsers.java src/nabble/view/web/tools/OnlineUsers.jtp src/nabble/view/web/tools/Profile.java src/nabble/view/web/tools/Profile.jtp src/nabble/view/web/tools/SendMail.java src/nabble/view/web/tools/SendMail.jtp src/nabble/view/web/tools/ShellHelp.java src/nabble/view/web/tools/ShellHelp.jtp src/nabble/view/web/tools/SpamRules.java src/nabble/view/web/tools/SpamRules.jtp src/nabble/view/web/tools/SpamSearch.java src/nabble/view/web/tools/SpamSearch.jtp src/nabble/view/web/tools/TestMacro.java src/nabble/view/web/tools/TestMacro.jtp src/nabble/view/web/tools/UploadMbox.java src/nabble/view/web/tools/UploadMbox.jtp src/nabble/view/web/tools/UploadMbox2.java src/nabble/view/web/tools/UploadMbox2.jtp src/nabble/view/web/tools/run.luan src/nabble/view/web/tools/shell.luan src/nabble/view/web/user/Advanced.java src/nabble/view/web/user/Advanced.jtp src/nabble/view/web/user/Advanced2.java src/nabble/view/web/user/Advanced2.jtp src/nabble/view/web/user/ChangeAvatar.java src/nabble/view/web/user/ChangeAvatar.jtp src/nabble/view/web/user/ChangeAvatar2.java src/nabble/view/web/user/ChangeAvatar2.jtp src/nabble/view/web/user/ChangeEmail.java src/nabble/view/web/user/ChangeEmail.jtp src/nabble/view/web/user/ChangeEmail2.java src/nabble/view/web/user/ChangeEmail2.jtp src/nabble/view/web/user/ChangeEmail3.java src/nabble/view/web/user/ChangeEmail3.jtp src/nabble/view/web/user/EditProfile.java src/nabble/view/web/user/EditProfile.jtp src/nabble/view/web/user/OnlineStatus.java src/nabble/view/web/user/RemoveAccount.java src/nabble/view/web/user/RemoveAccount.jtp src/nabble/view/web/user/ResetPassword.java src/nabble/view/web/user/ResetPassword.jtp src/nabble/view/web/user/SendEmail.java src/nabble/view/web/user/SendEmail.jtp src/nabble/view/web/user/SendEmail2.java src/nabble/view/web/user/SendEmail2.jtp src/nabble/view/web/user/UserEditorNamespace.java src/nabble/view/web/user/UserPendingNodes.java src/nabble/view/web/user/UserPendingNodes.jtp src/nabble/view/web/util/Empty.java src/nabble/view/web/util/Empty.jtp src/nabble/view/web/util/GradientImage.java src/nabble/view/web/util/JsHolder.java src/nabble/view/web/util/Redirect.java src/nabble/view/web/util/Rmi.java src/nabble/view/web/util/RoundedCorner.java src/nabble/view/web/util/SessionService.java src/nabble/view/web/util/VisitCounter.java src/nabble/view/web/util/codemirror/LICENSE src/nabble/view/web/util/codemirror/css/docs.css src/nabble/view/web/util/codemirror/css/font.js src/nabble/view/web/util/codemirror/css/xmlcolors.css src/nabble/view/web/util/codemirror/js/codemirror.js src/nabble/view/web/util/codemirror/js/editor.js src/nabble/view/web/util/codemirror/js/highlight.js src/nabble/view/web/util/codemirror/js/mirrorframe.js src/nabble/view/web/util/codemirror/js/parsecss.js src/nabble/view/web/util/codemirror/js/parsedummy.js src/nabble/view/web/util/codemirror/js/parsehtmlmixed.js src/nabble/view/web/util/codemirror/js/parsejavascript.js src/nabble/view/web/util/codemirror/js/parsesparql.js src/nabble/view/web/util/codemirror/js/parsexml.js src/nabble/view/web/util/codemirror/js/select.js src/nabble/view/web/util/codemirror/js/stringstream.js src/nabble/view/web/util/codemirror/js/tokenize.js src/nabble/view/web/util/codemirror/js/tokenizejavascript.js src/nabble/view/web/util/codemirror/js/undo.js src/nabble/view/web/util/codemirror/js/unittests.js src/nabble/view/web/util/codemirror/js/util.js src/nabble/view/web/util/dropdown_menu_hack.js src/nabble/view/web/util/image-crop.js src/nabble/view/web/util/jquery-1.7.2.js src/nabble/view/web/util/jquery-1.7.2.pack.js src/nabble/view/web/util/jscolor/jscolor.js src/nabble/view/web/util/jscolor/jscolor.pack.js src/nabble/view/web/util/minmax.js src/nabble/view/web/util/nabbledropdown-2.4.1.js src/nabble/view/web/util/nabblegallery-1.2.js src/nabble/view/web/util/nabbletabs.css src/nabble/view/web/util/nabbletabs.js src/nabble/view/web/util/tablesorter/asc.gif src/nabble/view/web/util/tablesorter/bg.gif src/nabble/view/web/util/tablesorter/desc.gif src/nabble/view/web/util/tablesorter/jquery.tablesorter.min.js src/nabble/view/web/util/tablesorter/style.css src/nabble/view/web/w3c/P3PXML.java src/nabble/view/web/w3c/P3PXML.jtp src/nabble/view/web/w3c/PolicyHTML.java src/nabble/view/web/w3c/PolicyHTML.jtp src/nabble/view/web/w3c/PolicyXML.java src/nabble/view/web/w3c/PolicyXML.jtp src/nabble/view/web/y_key_11a47ceb3359dacc.java src/nabble/view/web/y_key_11a47ceb3359dacc.jtp
diffstat 931 files changed, 134149 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/classpath.sh	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,9 @@
+NABBLE_HOME=$(pwd);
+
+CLASSPATH=$NABBLE_HOME/src
+
+for file in $NABBLE_HOME/lib/*; do
+  CLASSPATH=$CLASSPATH:$file;
+done
+
+export CLASSPATH
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clearCache.sh	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,11 @@
+#!/bin/bash
+ 
+set -e
+
+cd `dirname $0`
+
+mkdir -p local/cache
+mv local/cache local/cache.old
+rm -rf local/cache.old &
+
+echo "cache cleared";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/conf/Init.luan	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,82 @@
+java()
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local MailHome = require "java:fschmidt.util.mail.MailHome"
+local System = require "java:java.lang.System"
+local Logging = require "luan:logging/Logging.luan"
+local logger = Logging.logger "Init_default"
+
+
+local Init = {}
+
+System.setProperty("mail.smtp.host", "127.0.0.1")
+System.setProperty("mail.mime.base64.ignoreerrors", "true")
+System.setProperty("mail.mime.address.strict", "false")
+System.setProperty("mail.mime.decodetext.strict", "false")
+
+Init.dbUrl = "jdbc:postgresql://localhost:5432/nabble"
+Init.dbUser = "administrator"
+Init.dbPassword = ""
+
+Init.poolSize = 130
+
+Init.local_dir = "local/"
+
+-- for now
+local smtp = MailHome.getSmtpServer("mail.smtp2go.com","dev@singlesushi.com","FB4dating")
+smtp.setPort(2525)
+MailHome.setDefaultSmtpServer(smtp)
+
+Init.homeContextUrl = "http://me.nabble.com:8081"
+
+Init.deleteInactiveSites = false
+Init.nabbleHost = "me.nabble.com:8080"
+
+
+Init.defaultHost = "me.nabble.com:8080"
+Init.domain = Init.defaultHost
+--Init.mailDomain = Init.defaultHost
+Init.nabbleHost = Init.defaultHost
+
+--[=[
+local function getPop3Server(addr, pwd)
+	local server = MailHome.getPop3Server(popMailServer, addr, pwd)
+	server.useSsl()
+	return server
+end
+
+local pop3 = {}  -- fill in
+
+local pop3Servers = {
+	lists = 'mailingListArchivePop3Server'
+	fwd = 'fwdPop3Server'
+	ml = 'subscriptionsPop3Server'
+	sb = 'subscriptionBouncesPop3Server'
+}
+for name, password in pairs(pop3) do
+	Init[pop3Servers[name]] = getPop3Server( name.."@"..Init.mailDomain, password )
+end
+]=]
+
+--local HashSet = require "java:java.util.HashSet"
+--Init.sysadmins = HashSet.new {}  -- list of emails
+
+--Init.monitor_emails = {}  -- list of emails
+
+
+-- for serve_nabble.luan
+
+function Init.fix_serve(Serve)
+	if Serve.is_www then
+		Serve.is_forums = false
+	else
+		Serve.www_port = 8081
+		Serve.is_www = true
+	end
+end
+
+function Init.add_filters(context)
+end
+
+
+return Init
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/conf/serve_nabble.luan	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,148 @@
+java()
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local ipairs = Luan.ipairs or error()
+local Io = require "luan:Io.luan"
+local String = require "luan:String.luan"
+local LoggerFactory = require "java:org.slf4j.LoggerFactory"
+local Log4j = require "java:nabble.utils.Log4j"
+local Init = require "file:conf/Init.luan"
+
+
+local Serve = {}
+
+Serve.username = "nabble"
+Serve.password = "password"
+
+Serve.is_www = false
+Serve.is_forums = true
+
+Serve.www_port = 8080
+Serve.forums_port = 8080
+
+local log_to_console = false
+
+for _, arg in ipairs{...} do
+	if arg == "console" then
+		log_to_console = true
+	end
+	if arg == "www" then
+		Serve.is_www = true
+	end
+end
+
+Init.fix_serve(Serve)
+
+if log_to_console then
+	Log4j.initForConsole()
+else
+	Log4j.initBasicFiles("logs/info.log", "logs/warn.log", "logs/error.log", "5MB")
+	Log4j.initForClass("nabble.view.web.embed.JsEmbed", "logs/embed.log", "1MB")
+end
+
+local logger = LoggerFactory.getLogger("serve_nabble");
+
+local function add_contexts(jetty)
+	local context = jetty.newTools2Context()
+	jetty.authenticate(context, "/*", Serve.username, Serve.password)
+	
+	-- /logs folder
+	context = jetty.newFolderContext("/logs", "logs", { "/*" }, true);
+	jetty.authenticate(context, "/*", Serve.username, Serve.password)
+	
+	-- /conf folder
+	context = jetty.newFolderContext("/conf", "conf", { "/*" }, true);
+	jetty.authenticate(context, "/*", Serve.username, Serve.password)
+end
+
+
+if Serve.is_www then
+
+	-- jetty
+
+	local GlobalJetty = require "java:global.GlobalJetty"
+	
+	local jetty = GlobalJetty.new();
+	local context = jetty.newWebContext();
+	
+	jetty.authenticate(context, "/tools/*", Serve.username, Serve.password)
+	
+	add_contexts(jetty)
+	
+	-- /html folder
+	context = jetty.newFolderContext("/html", "html", { "/*" }, true);
+	jetty.authenticate(context,"/*", Serve.username, Serve.password);
+	
+	local log = jetty.newNCSARequestLog()
+	local server = jetty.newServer(Serve.www_port, log);
+	server.start();
+	
+	logger.error("www server started");
+end
+
+
+if Serve.is_forums then
+	local Jetty = require "java:nabble.utils.Jetty"
+	
+	local jettyConfig = {
+		cache = "true"
+		timeLimit = "60000"
+		errorCacheSize = "1000"
+		ipListSize = "5"
+		exportDir = "export/"
+	}
+	
+	local jetty = Jetty.new();
+	local context = jetty.newWebContext(jettyConfig);
+	
+	context.setMaxFormContentSize(500000);
+	jetty.authenticate(context, "/tools/*", Serve.username, Serve.password);
+	
+	
+	-- Start Filters
+	
+	Init.add_filters(context)
+
+	jetty.addNabbleErrorFilter(context);
+	
+	jetty.addNabbleConnectionLimitFilter(context, { max = "115", queueSize = "1000", timeoutDelay = "45000" });
+	
+	jetty.addBadBotFilter(context, { max = "10" });
+	
+	jetty.addCachingFilter(context, { dir = Init.local_dir.."cache", hasDelayedDelete = "true", acceptEncoding = "gzip" });
+	local FileHandler = require "java:cachingfilter.FileHandler"
+	FileHandler.factory = FileHandler.mappedOrIoFile;
+	
+	jetty.addGzipFilter(context);
+	
+	add_contexts(jetty)
+	
+	-- /backups folder
+	context = jetty.newFolderContext("/backups", Init.local_dir.."schemas", { "/*" }, false);
+	
+	local log
+	if log_to_console then
+		log = jetty.newNCSARequestLog()
+	else
+		Io.uri("file:logs/daily").mkdir()
+		log = jetty.newNCSARequestLog("logs/daily/yyyy_mm_dd.log")
+		log.setRetainDays(30)
+	end
+	
+	local server = jetty.newServer(Serve.forums_port, log);
+	jetty.setThreadPool(server);
+
+	local DbGlobalUpdater = require "java:nabble.model.DbGlobalUpdater"
+	DbGlobalUpdater.updateGlobal()
+	logger.info "db update done"
+
+	local ClearCache = require "java:nabble.view.lib.ClearCache"
+	ClearCache.run()
+	--logger.info "db cache cleared"
+
+	server.start();
+	
+	logger.error("nabble server started");
+	
+	jetty.addShutdownHook(server);
+end_if
Binary file lib/activation.jar has changed
Binary file lib/commons-fileupload-1.3.jar has changed
Binary file lib/commons-io-2.4.jar has changed
Binary file lib/commons-logging-1.1.3.jar has changed
Binary file lib/commons-primitives-1.0.jar has changed
Binary file lib/fschmidt.jar has changed
Binary file lib/gson-2.2.4.jar has changed
Binary file lib/jasypt-1.9.0.jar has changed
Binary file lib/jdom.jar has changed
Binary file lib/jetty-continuation-7.6.0.v20120127.jar has changed
Binary file lib/jetty-http-7.6.0.v20120127.jar has changed
Binary file lib/jetty-io-7.6.0.v20120127.jar has changed
Binary file lib/jetty-security-7.6.0.v20120127.jar has changed
Binary file lib/jetty-server-7.6.0.v20120127.jar has changed
Binary file lib/jetty-servlet-7.6.0.v20120127.jar has changed
Binary file lib/jetty-servlets-7.6.0.v20120127.jar has changed
Binary file lib/jetty-util-7.6.0.v20120127.jar has changed
Binary file lib/log4j-1.2.16.jar has changed
Binary file lib/luan.jar has changed
Binary file lib/lucene-analyzers-3.6.2.jar has changed
Binary file lib/lucene-core-3.6.2.jar has changed
Binary file lib/lucene-highlighter-3.6.2.jar has changed
Binary file lib/lucene-memory-3.6.2.jar has changed
Binary file lib/lucene-queries-3.6.2.jar has changed
Binary file lib/mail.jar has changed
Binary file lib/mstor.jar has changed
Binary file lib/postgresql-9.1-901.jdbc4.jar has changed
Binary file lib/servlet-api-2.5.jar has changed
Binary file lib/slf4j-api-1.6.4.jar has changed
Binary file lib/slf4j-log4j12-1.6.4.jar has changed
Binary file lib/yjp-controller-api-redist.jar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/luan.sh	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,2 @@
+. classpath.sh
+java -classpath $CLASSPATH luan.Luan "$@"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/serve.sh	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+set -e
+
+./clearCache.sh
+
+./luan.sh conf/serve_nabble.luan console $* 2>&1 | tee serve.err
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/BufferInputStream.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,41 @@
+package cachingfilter;
+
+import java.io.InputStream;
+import org.eclipse.jetty.io.Buffer;
+
+
+public final class BufferInputStream extends InputStream {
+	private final Buffer buffer;
+
+	public BufferInputStream(Buffer buffer) {
+		this.buffer = buffer;
+	}
+
+	public int read() {
+		return buffer.length()==0 ? -1 : buffer.get();
+	}
+
+	public int read(byte b[], int off, int len) {
+		return buffer.get(b,off,len);
+	}
+
+	public long skip(long n) {
+		return buffer.skip( (int)n );
+	}
+
+	public int available() {
+		return buffer.length();
+	}
+
+	public void mark(int readlimit) {
+		buffer.mark();
+	}
+
+	public void reset() {
+		buffer.reset();
+	}
+
+	public boolean markSupported() {
+		return true;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/CachedDir.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,99 @@
+package cachingfilter;
+
+import java.io.File;
+import java.io.IOException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+final class CachedDir implements CachedPage {
+	private static final Logger logger = LoggerFactory.getLogger(CachedDir.class);
+	private static final long tooLong = 1000L*60*10;  // 10 minutes
+	private final File dir;
+	private File newFile;
+
+	CachedDir( File parent, String child ) {
+		this.dir = new File(parent,child);
+	}
+
+	public String name() {
+		return dir.getName();
+	}
+
+	public boolean exists() {
+		if( !dir.exists() )
+			return false;
+		File[] files = dir.listFiles();
+		if( files == null )
+			throw new RuntimeException("error doing listFiles of "+dir);
+		return files.length >= 1;
+	}
+
+	public File lastFile() {
+		File[] files = dir.listFiles();
+		if( files.length == 1 ) {
+			return files[0];
+		}
+		int n = 0;
+		File lastFile = null;
+		for( File file : files ) {
+			String name = file.getName();
+			int i = Integer.parseInt(name);
+			if( n < i ) {
+				if( lastFile != null )
+					lastFile.delete();
+				n = i;
+				lastFile = file;
+			} else {
+				file.delete();
+			}
+		}
+		if( lastFile==null )
+			throw new RuntimeException(dir.toString());
+		return lastFile;
+	}
+
+	public File newFile() throws IOException {
+		if( !dir.exists() && !dir.mkdir() )
+			throw new RuntimeException("couldn't create "+dir);
+		int n = 0;
+		for( File file : dir.listFiles() ) {
+			String name = file.getName();
+			int i = Integer.parseInt(name);
+			if( n < i )
+				n = i;
+			file.delete();
+		}
+		newFile = new File( dir, Integer.toString( n + 1 ) );
+		if( !newFile.createNewFile() )
+			throw new RuntimeException("couldn't create "+newFile);
+		return newFile;
+	}
+
+	public void deleteNewFile() {
+		if( !newFile.delete() )
+			throw new RuntimeException("couldn't delete new file "+newFile);
+	}
+
+	public boolean delete() {
+		File die = new File(dir.getParentFile(),"~die");
+		if( die.exists() ) {
+			for( File f : die.listFiles() ) {
+				if( !f.delete() ) {
+					logger.error("couldn't delete "+f);
+					return false;
+				}
+			}
+			if( !die.delete() ) {
+				logger.error("couldn't delete "+die);
+				return false;
+			}
+		}
+		return dir.renameTo(die);
+	}
+
+	public String toString() {
+		return dir.toString();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/CachedFile.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,52 @@
+package cachingfilter;
+
+import java.io.File;
+import java.io.IOException;
+
+import java.io.File;
+import java.io.IOException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+final class CachedFile implements CachedPage {
+	private static final Logger logger = LoggerFactory.getLogger(CachedFile.class);
+	private final File file;
+
+	CachedFile( File parent, String child ) {
+		this.file = new File(parent,child);
+	}
+
+	public String name() {
+		return file.getName();
+	}
+
+	public boolean exists() {
+		return file.exists();
+	}
+
+	public File lastFile() {
+		return file;
+	}
+
+	public File newFile() throws IOException {
+		delete();
+		if( !file.createNewFile() )
+			throw new RuntimeException("couldn't create "+file);
+		return file;
+	}
+
+	public void deleteNewFile() {
+		if( !delete() )
+			throw new RuntimeException("couldn't delete "+file);
+	}
+
+	public boolean delete() {
+		return file.delete();
+	}
+
+	public String toString() {
+		return file.toString();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/CachedPage.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,14 @@
+package cachingfilter;
+
+import java.io.File;
+import java.io.IOException;
+
+
+interface CachedPage {
+	public String name();
+	public boolean exists();
+	public File lastFile();
+	public File newFile() throws IOException;
+	public void deleteNewFile();
+	public boolean delete();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/CachingFilter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,147 @@
+package cachingfilter;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.BufferedOutputStream;
+import java.io.FileOutputStream;
+import java.io.ObjectOutputStream;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public final class CachingFilter implements Filter { 
+	private static final Logger logger = LoggerFactory.getLogger(CachingFilter.class);
+
+	static final Locker<String> locker = new Locker<String>();
+
+//	private static final long sweepFreqSeconds = 60*60*24;
+//	private static final long expiryPeriodMillis = 1000L*60*60*24*10;
+	private static final long sweepFreqSeconds = 60*60;
+	private static final long expiryPeriodMillis = 1000L*60*60*24;
+
+	static final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
+
+	private File dir;
+	private boolean hasDelayedDelete;
+	private final Set<String> encodings = new HashSet<String>();
+
+	public void init(FilterConfig filterConfig)
+		throws ServletException
+	{
+		String dirS = filterConfig.getInitParameter("dir");
+		if( dirS == null )
+			throw new ServletException("'dir' init parameter must be set");
+		dir = new File(dirS);
+		dir.mkdirs();
+
+		String acceptEncoding = filterConfig.getInitParameter("acceptEncoding");
+		if( acceptEncoding == null )
+			throw new ServletException("'acceptEncoding' init parameter must be set");
+		for( String encoding : acceptEncoding.split(",") ) {
+			encoding = encoding.trim();
+			if( encoding.length() > 0 )
+				encodings.add(encoding);
+		}
+
+		String hasDelayedDeleteS = filterConfig.getInitParameter("hasDelayedDelete");
+		if( hasDelayedDeleteS == null )
+			throw new ServletException("'hasDelayedDelete' init parameter must be set");
+		hasDelayedDelete = Boolean.parseBoolean(hasDelayedDeleteS);
+
+		Runnable sweeper = new Runnable() {
+			public void run() {
+				try {
+					logger.error("starting sweep");
+					final int[] total = new int[1];
+					final long expired = System.currentTimeMillis() - expiryPeriodMillis;
+					File[] files = dir.listFiles(new FileFilter(){
+						public boolean accept(File file) {
+							total[0]++;
+							return file.lastModified() < expired && file.getName().charAt(0) != '~';
+						}
+					});
+					int n = 0;
+					for( File file : files ) {
+						String name = file.getName();
+						locker.lock(name);
+						try {
+							if( newCachedPage(name).delete() )
+								n++;
+						} finally {
+							locker.unlock(name);
+						}
+					}
+					logger.error("finished sweep, deleted "+n+" pages out of "+files.length+" candidates, "+total[0]+" total");
+				} catch(RuntimeException e) {
+					logger.error("exception in sweeper",e);
+					throw e;
+				} catch(Error e) {
+					logger.error("error in sweeper",e);
+					System.exit(-1);
+			}
+			}
+		};
+		executor.scheduleWithFixedDelay( sweeper, sweepFreqSeconds, sweepFreqSeconds, TimeUnit.SECONDS );
+	}
+
+	public void destroy() {}
+
+	public static void shutdown() {}
+
+	CachedPage newCachedPage(String name) {
+		if( name.length() == 0 )
+			throw new RuntimeException("empty filenames not allowed");
+		if( name.charAt(0) == '~' )
+			throw new RuntimeException("invalid filename: "+name);
+		return hasDelayedDelete ? new CachedFile(dir,name) : new CachedDir(dir,name);
+	}
+
+	public void doFilter(
+		ServletRequest req,
+		ServletResponse res,
+		FilterChain chain
+	)
+		throws IOException, ServletException
+	{
+		HttpServletRequest request = (HttpServletRequest)req;
+		HttpServletResponse response = (HttpServletResponse)res;
+		if( !"GET".equals( request.getMethod() ) ) {
+			chain.doFilter(request,response);
+			return;
+		}
+		CachingRequestWrapper wrappedRequest = new CachingRequestWrapper(request);
+		CachingResponseWrapper wrappedResponse = new CachingResponseWrapper(this,wrappedRequest,response);
+		boolean isDone = false;
+		try {
+			chain.doFilter(wrappedRequest,wrappedResponse);
+			isDone = true;
+		} finally {
+			wrappedResponse.finish(isDone);
+		}
+		
+	}
+
+	public Set<String> getEncodings() {
+		return encodings;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/CachingRequestWrapper.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,119 @@
+package cachingfilter;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Enumeration;
+import java.util.TimeZone;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.HashMap;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+
+
+final class CachingRequestWrapper extends HttpServletRequestWrapper {
+	private static final Logger logger = LoggerFactory.getLogger(CachingRequestWrapper.class);
+
+	static final String IF_MODIFIED_SINCE = "If-Modified-Since".toLowerCase();
+	static final String IF_NONE_MATCH = "If-None-Match".toLowerCase();
+	static final String ACCEPT_ENCODING = "Accept-Encoding".toLowerCase();
+
+	final Map<String,Object> headerMap = new HashMap<String,Object>();
+
+	CachingRequestWrapper(HttpServletRequest request) throws IOException {
+		super(request);
+		checkRequest(request);
+	}
+
+	private static void checkRequest(HttpServletRequest request) throws IOException {
+		AbstractHttpConnection c = ((org.eclipse.jetty.server.Request) request).getConnection();
+		if (c.getRequestFields().containsKey("Host")) {
+			for (Enumeration<String> e2 = c.getRequestFields().getValues("Host"); e2.hasMoreElements();) {
+				if (e2.nextElement().trim().endsWith(":")) {
+					throw new IOException("Bad 'Host' request header (ends with colon)");
+				}
+			}
+		}
+	}
+
+	public String getHeader(String name) {
+/*
+		String v = getHeader2(name);
+		logger.trace("getHeader "+name+" = "+v);
+		return v;
+	}
+	public String getHeader2(String name) {
+*/
+		String key = name.toLowerCase();
+		if( headerMap.containsKey(key) ) {
+			Object val = headerMap.get(key);
+			if( val instanceof Long ) {
+				long date = (Long)val;
+				if( date == -1 )
+					return null;
+				return formatDate(date);
+			}
+			return (String)val;
+		}
+		return super.getHeader(name);
+	}
+
+	public long getDateHeader(String name) {
+/*
+		long v = getDateHeader2(name);
+		logger.trace("getDateHeader "+name+" = "+v);
+		return v;
+	}
+	public long getDateHeader2(String name) {
+*/
+		String key = name.toLowerCase();
+		if( headerMap.containsKey(key) )
+			return (Long)headerMap.get(key);
+		return super.getDateHeader(name);
+	}
+
+	boolean isCacheable() {
+		boolean isCacheable = false;
+		Long val = (Long)headerMap.get(IF_MODIFIED_SINCE);
+		if( val!=null ) {
+			try {
+				long cachedLastModified = val;
+				if( cachedLastModified != -1 ) {
+					long ifModifiedSince = super.getDateHeader(IF_MODIFIED_SINCE);
+					//logger.trace("cachedLastModified = "+cachedLastModified+"  ifModifiedSince = "+ifModifiedSince);
+					if( cachedLastModified > ifModifiedSince )
+						return false;
+					isCacheable = true;
+				}
+			} catch(IllegalArgumentException e) {
+				logger.warn("bad date, user-agent="+getHeader("user-agent"),e);
+			}
+		}
+		String etag = (String)headerMap.get(IF_NONE_MATCH);
+		if( etag != null ) {
+			String ifNoneMatch = super.getHeader(IF_NONE_MATCH);
+			if( !etag.equals(ifNoneMatch) )
+				return false;
+			isCacheable = true;
+		}
+		return isCacheable;
+	}
+
+
+	private static ThreadLocal<DateFormat> dateFormat = new ThreadLocal<DateFormat>() {
+		protected synchronized DateFormat initialValue() {
+			DateFormat httpDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+			httpDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+			return httpDateFormat;
+		}
+	};
+
+	private String formatDate(long date) {
+		return dateFormat.get().format(new Date(date));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/CachingResponseWrapper.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,528 @@
+package cachingfilter;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.ObjectOutputStream;
+import java.io.ObjectInputStream;
+import java.net.URLEncoder;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Collection;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+final class CachingResponseWrapper extends HttpServletResponseWrapper {
+	private static final Logger logger = LoggerFactory.getLogger(CachingResponseWrapper.class);
+
+	private static Set<String> cacheableHeaders = new HashSet<String>();
+	static {
+		for( String header : new String[]{
+			"Last-Modified",
+			"Etag",
+			"Content-Type",
+			"Cache-Control",
+			"Content-Encoding",
+		} ) {
+			cacheableHeaders.add( header.toLowerCase() );
+		}
+	}
+
+	private final CachingFilter cachingFilter;
+	private final CachingRequestWrapper request;
+	private final String fullName;
+	private boolean isCachingToFile;
+	private CachedPage cachingFile;
+	private ServletOutputStream outputStream;
+	private PrintWriter writer;
+	private int status = SC_OK;
+	private Long lastModified = null;
+	private String etag = null;
+	private FileHandler fileHandler = null;
+	private ObjectInputStream ois = null;
+	private final List<ResponseAction> actions = new ArrayList<ResponseAction>();
+	private boolean isLocked = false;  // for debugging
+	private StringBuilder log = new StringBuilder();
+
+	private void log(String msg) {
+		log.append(msg).append('\n');
+	}
+
+	CachingResponseWrapper( CachingFilter cachingFilter, CachingRequestWrapper request, HttpServletResponse response ) {
+		super(response);
+		try {
+			this.cachingFilter = cachingFilter;
+			this.request = request;
+			this.fullName = getFullName();
+			boolean ok = false;
+			try {
+				if( !openObjectInputStream() ) {  // calls lock()
+					ok = true;
+					return;
+				}
+				Long cachedLastModified = (Long)ois.readObject();
+				if( cachedLastModified==null )
+					cachedLastModified = -1L;
+				request.headerMap.put( CachingRequestWrapper.IF_MODIFIED_SINCE, cachedLastModified );
+				String cachedEtag = (String)ois.readObject();
+				request.headerMap.put( CachingRequestWrapper.IF_NONE_MATCH, cachedEtag );
+				ok = true;
+			} finally {
+				if( !ok && cachingFile != null )
+					unlock();
+			}
+		} catch(ClassNotFoundException e) {
+			throw new RuntimeException(e);
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private String getFullName()
+		throws IOException
+	{
+		StringBuffer url = request.getRequestURL();
+		String queryString = request.getQueryString();
+		if( queryString != null ) {
+			url.append( '?' );
+			url.append( queryString );
+		}
+		log("url = "+url);
+		String fullUrl = URLEncoder.encode(url.toString(),"UTF-8");
+		String acceptEncoding = request.getHeader("Accept-Encoding");
+		logger.trace("acceptEncoding = "+acceptEncoding);
+		if( acceptEncoding == null )
+			return fullUrl;
+		Set<String> knownEncodings = cachingFilter.getEncodings();
+		List<String> list = new ArrayList<String>();
+		for( String encoding : acceptEncoding.split(",") ) {
+			encoding = encoding.trim();
+			if( knownEncodings.contains(encoding) )
+				list.add(encoding);
+		}
+		if( list.isEmpty() ) {
+			request.headerMap.put( CachingRequestWrapper.ACCEPT_ENCODING, null );
+			return fullUrl;
+		}
+		String encodings = join( list, "," );
+		request.headerMap.put( CachingRequestWrapper.ACCEPT_ENCODING, encodings );
+		return fullUrl + '~' + encodings;
+	}
+
+	private void lock() {
+		CachingFilter.locker.lock(cachingFile.name());
+		log("lock");
+		isLocked = true;
+	}
+
+	private boolean unlock() {
+		boolean wasLocked = CachingFilter.locker.unlock(cachingFile.name());
+		log("unlock "+wasLocked);
+		if( isLocked != wasLocked )
+			logger.error("isLocked="+isLocked+" wasLocked="+wasLocked,new Exception());
+		isLocked = false;
+		return wasLocked;
+	}
+
+	private static String join(Collection<?> col,String separator) {
+		if( col.isEmpty() )
+			return "";
+		StringBuilder sb = new StringBuilder();
+		Iterator<?> iter = col.iterator();
+		sb.append( iter.next() );
+		while( iter.hasNext() ) {
+			sb.append( separator ).append( iter.next() );
+		}
+		return sb.toString();
+	}
+
+	private static long hashCode(String s) {
+		final int len = s.length();
+		long h = 0;
+        for( int i = 0; i < len; i++ ) {
+            h = 31*h + s.charAt(i);
+        }
+		return h;
+	}
+
+	private boolean openObjectInputStream() {
+		for( long hash = hashCode(fullName); true; hash++ ) {
+			String s = Long.toHexString(hash);
+			cachingFile = cachingFilter.newCachedPage(s);
+			lock();
+			if( !cachingFile.exists() ) {
+				logger.trace("couldn't find "+cachingFile);
+				return false;
+			}
+			try {
+				FileHandler fileHandler = FileHandler.factory.newInstance(cachingFile.lastFile());
+				ObjectInputStream ois = new ObjectInputStream(fileHandler.getInputStream());
+				if( ois.readUTF().equals(fullName) ) {
+					logger.trace("found file = "+cachingFile);
+					this.fileHandler = fileHandler;
+					this.ois = ois;
+					return true;
+				}
+				fileHandler.close();
+			} catch(IOException e) {
+				logger.error("couldn't read "+cachingFile+" length="+cachingFile.lastFile().length(),e);
+				if( !cachingFile.delete() )
+					logger.error("couldn't delete "+cachingFile);
+				return false;
+			}
+			unlock();
+		}
+	}
+
+	public void setContentType(String ct)
+	{
+		logger.trace("setContentType "+ct);
+		super.setContentType(ct);
+		actions.add( new ResponseAction.SetHeader("Content-Type",ct) );
+	}
+
+	public void setStatus(int sc, String sm)
+	{
+		logger.trace("setStatus2");
+		super.setStatus(sc,sm);
+		this.status = sc;
+	}
+	
+	public void setStatus(int sc)
+	{
+		logger.trace("setStatus "+sc);
+		super.setStatus(sc);
+		this.status = sc;
+	}
+
+	public void setHeader(String name, String value) {
+		logger.trace("setHeader "+name+" = "+value);
+		super.setHeader(name,value);
+		if( "Etag".equalsIgnoreCase(name) )
+			etag = value;
+		if( "Last-Modified".equalsIgnoreCase(name) ) {
+			if( value==null )
+				lastModified = null;
+			else
+				logger.error("unsupported",new Exception());
+		}
+		if( cacheableHeaders.contains(name.toLowerCase()) )
+			actions.add( new ResponseAction.SetHeader(name,value) );
+	}
+
+	public void addHeader(String name, String value) {
+		logger.trace("addHeader "+name+" = "+value);
+		super.addHeader(name,value);
+		if( cacheableHeaders.contains(name.toLowerCase()) )
+			actions.add( new ResponseAction.AddHeader(name,value) );
+	}
+
+	public void setIntHeader(String name, int value) {
+		logger.trace("setIntHeader "+name);
+		super.setIntHeader(name,value);
+		if( cacheableHeaders.contains(name.toLowerCase()) )
+			actions.add( new ResponseAction.SetIntHeader(name,value) );
+	}
+
+	public void addIntHeader(String name, int value) {
+		logger.trace("addIntHeader "+name);
+		super.addIntHeader(name,value);
+		if( cacheableHeaders.contains(name.toLowerCase()) )
+			actions.add( new ResponseAction.AddIntHeader(name,value) );
+	}
+
+	public void setDateHeader(String name, long value) {
+		logger.trace("setDateHeader "+name);
+		super.setDateHeader(name,value);
+		value = value / 1000 * 1000;  // round to seconds
+		if( "Last-Modified".equalsIgnoreCase(name) )
+			lastModified = value;
+		if( cacheableHeaders.contains(name.toLowerCase()) )
+			actions.add( new ResponseAction.SetDateHeader(name,value) );
+	}
+
+	public void addDateHeader(String name, long value) {
+		logger.trace("addDateHeader "+name);
+		super.setDateHeader(name,value);
+		if( cacheableHeaders.contains(name.toLowerCase()) )
+			actions.add( new ResponseAction.AddDateHeader(name,value) );
+	}
+
+	public void reset()
+	{
+		logger.trace("reset");
+		super.reset();
+		resetOutput();
+		status = SC_OK;
+		lastModified = null;
+		etag = null;
+		actions.clear();
+	}
+
+	public void resetBuffer()
+	{
+		logger.trace("resetBuffer");
+		super.resetBuffer();
+		resetOutput();
+	}
+
+	private void resetOutput() {
+		if( isCachingToFile ) {
+			try {
+				outputStream.close();
+			} catch(IOException e) {
+				logger.error("resetOutput",e);
+			}
+			cachingFile.deleteNewFile();
+			isCachingToFile = false;
+		}
+		outputStream = null;
+		writer = null;
+	}
+
+	public void sendError(int sc, String msg) throws IOException
+	{
+		logger.trace("sendError2");
+		this.status = sc;
+		resetBuffer();
+		if( shouldSendFile() ) {
+			sendFile();
+		} else {
+			super.sendError(sc,msg);
+		}
+	}
+
+	public void sendError(int sc) throws IOException
+	{
+		logger.trace("sendError");
+		this.status = sc;
+		resetBuffer();
+		if( shouldSendFile() ) {
+			sendFile();
+		} else {
+			super.sendError(sc);
+		}
+	}
+
+	public void sendRedirect(String location) throws IOException
+	{
+		logger.trace("sendRedirect");
+		this.status = SC_MOVED_TEMPORARILY;
+		resetBuffer();
+		super.sendRedirect(location);
+	}
+
+	public void flushBuffer() throws IOException
+	{
+		logger.trace("flushBuffer "+isCommitted());
+		if( writer != null )
+			writer.flush();
+		if( outputStream != null )
+			outputStream.flush();
+		else if( shouldSendFile() )
+			sendFile();
+		else
+			getResponse().flushBuffer();
+	}
+
+	private boolean shouldSendFile() {
+		if( !(status==SC_NOT_MODIFIED && ois!=null) )
+			return false;
+		if( request.isCacheable() )
+			return false;  // no need
+		return true;
+	}
+
+	private void sendFile() throws IOException {
+		CachingResponseWrapper.super.setHeader("Via","cache-yes");
+		sendFile2();
+	}
+
+	private void sendFile2() throws IOException {
+		logger.trace("sendFile");
+		unlock();
+		setStatus(SC_OK);
+		HttpServletResponse response = (HttpServletResponse)getResponse();
+		try {
+			@SuppressWarnings("unchecked")
+			List<ResponseAction> cachedActions = (List<ResponseAction>)ois.readObject();
+			for( ResponseAction cachedAction : cachedActions ) {
+				cachedAction.apply(response);
+			}
+		} catch(ClassNotFoundException e) {
+			throw new RuntimeException(e);
+		}
+		ServletOutputStream out = response.getOutputStream();
+		fileHandler.writeTo(out);
+	}
+
+	public ServletOutputStream getOutputStream()
+	{
+		logger.trace("getOutputStream");
+		if (outputStream==null) {
+			 newOutputStream();
+		} else if (writer!=null)
+			throw new IllegalStateException("getWriter() called");
+		
+		return outputStream;
+	}
+
+	public PrintWriter getWriter() throws IOException
+	{
+		logger.trace("getWriter");
+		if (writer==null)
+		{ 
+			if (outputStream!=null)
+				throw new IllegalStateException("getOutputStream() called");
+			
+			newOutputStream();
+			String encoding = getCharacterEncoding();
+			writer = encoding==null ? new PrintWriter(outputStream)
+				: new PrintWriter(new OutputStreamWriter(outputStream,encoding));
+		}
+		return writer;
+	}
+
+	private boolean isCacheable() {
+		if( getResponse().isCommitted() ) {
+			logger.trace("!isCacheable - isCommitted");
+			return false;
+		}
+		if( status != SC_OK ) {
+			logger.trace("!isCacheable - status="+status);
+			return false;
+		}
+		if( lastModified==null && etag==null ) {
+			logger.trace("!isCacheable - no lastModified,etag");
+			return false;
+		}
+		return true;
+	}
+
+	private void newOutputStream() {
+		outputStream = new ProxyServletOutputStream() {
+			protected OutputStream newOutputStream()
+				throws IOException
+			{
+				CachingResponseWrapper.super.setHeader("Via","cache-no");
+				ServletOutputStream out = getResponse().getOutputStream();
+				if( !isCacheable() ) {
+					unlock();
+					logger.trace("return getResponse().getOutputStream() "+out.getClass());
+					return out;
+				}
+				try {
+					File newFile = cachingFile.newFile();
+					logger.trace("write to cache");
+					isCachingToFile = true;
+					OutputStream outFile = new BufferedOutputStream(new FileOutputStream(newFile));
+					ObjectOutputStream oos = new ObjectOutputStream(outFile);
+					oos.writeUTF(fullName);
+					oos.writeObject(lastModified);
+					oos.writeObject(etag);
+					oos.writeObject(actions);
+					oos.flush();
+					return outFile;
+				} catch(IOException e) {
+					throw new RuntimeException(e);
+				}
+			}
+		};
+	}
+
+	void finish(boolean isDone) throws IOException {
+		try {
+			log("finish a");
+			if( fileHandler != null ) {
+				fileHandler.close();
+				logger.trace("closed fileHandler");
+			}
+			if( outputStream == null || !isDone && !isCachingToFile )
+				unlock();
+			if( isDone ) {
+				if( writer != null )
+					writer.flush();
+				if( outputStream != null ) {
+					try {
+						outputStream.flush();
+					} catch(IOException e) {
+						logger.trace("",e);
+						isDone = false;
+					}
+				}
+			}
+			if( isCachingToFile ) {
+				log("finish b");
+				try {
+					outputStream.close();
+				} catch(IOException e) {
+					logger.trace("",e);
+					isDone = false;
+				}
+				if( isDone ) {
+					log("finish c");
+					try {
+						log("file size = "+cachingFile.lastFile().length());
+						fileHandler = FileHandler.factory.newInstance(cachingFile.lastFile());
+						ois = new ObjectInputStream(fileHandler.getInputStream());
+						ois.readUTF();  // full name
+						ois.readObject();  // lastModified
+						ois.readObject();  // etag
+					} catch(ClassNotFoundException e) {
+						throw new RuntimeException(e);
+					} catch(IOException e) {
+						cachingFile.deleteNewFile();
+						throw new RuntimeException(e);
+					}
+					CachingResponseWrapper.super.setHeader("Via","cache-write");
+					try {
+						log("finish d");
+						sendFile2();
+						log("finish e");
+					} catch(IOException e) {
+						unlock();
+						throw e;
+					}
+					fileHandler.close();
+				} else {
+					cachingFile.deleteNewFile();
+					unlock();
+				}
+			}
+			log("finish z");
+		} catch(RuntimeException e) {
+			log("finish RuntimeException");
+			logger.error("RuntimeException in finish()",e);
+			throw e;
+		} catch(Error e) {
+			log("finish Error");
+			logger.error("Error in finish()",e);
+			throw e;
+		} finally {
+			if( unlock() ) {
+				logger.error("still locked isDone="+isDone+" isCachingToFile="+isCachingToFile+" outputStream="+(outputStream!=null));
+				logger.error("log:\n"+log);
+			}
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/FileHandler.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,138 @@
+package cachingfilter;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import javax.servlet.ServletOutputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.eclipse.jetty.io.nio.DirectNIOBuffer;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+
+
+public abstract class FileHandler {
+	abstract void close();
+	abstract InputStream getInputStream();
+	abstract void writeTo(ServletOutputStream out) throws IOException;
+
+	public interface Factory {
+		public FileHandler newInstance(File file) throws IOException;
+	}
+
+	private static final Logger logger = LoggerFactory.getLogger(FileHandler.class);
+
+	private static abstract class BufferedFactory implements Factory {
+		abstract DirectNIOBuffer getDirectNIOBuffer(File file) throws IOException;
+
+		public FileHandler newInstance(File file) throws IOException {
+			final DirectNIOBuffer fileBuffer = getDirectNIOBuffer(file);
+
+			return new FileHandler() {
+
+				void close() {}
+
+				InputStream getInputStream() {
+					return new BufferInputStream(fileBuffer);
+				}
+
+				void writeTo(ServletOutputStream out) throws IOException {
+					if (out instanceof AbstractHttpConnection.Output) {
+						logger.trace("sendFileContent using AbstractHttpConnection.Output");
+						((AbstractHttpConnection.Output)out).sendContent(fileBuffer);
+					} else {
+						fileBuffer.writeTo(out);
+					}
+				}
+			};
+		}
+	}
+
+	public static final Factory mappedFile = new BufferedFactory() {
+
+		private MappedByteBuffer map(File file) throws IOException {
+			FileInputStream fis = new FileInputStream(file);
+			try {
+				return fis.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length());
+			} finally {
+				fis.close();
+			}
+		}
+
+		DirectNIOBuffer getDirectNIOBuffer(File file) throws IOException {
+			return new DirectNIOBuffer(map(file),true);
+		}
+
+	};
+
+	public static final Factory memFile = new BufferedFactory() {
+
+		DirectNIOBuffer getDirectNIOBuffer(File file) throws IOException {
+			int len = (int)file.length();
+			DirectNIOBuffer buffer = new DirectNIOBuffer(len);
+			InputStream is = new FileInputStream(file);
+			buffer.readFrom(is,len);
+			is.close();
+			return buffer;
+		}
+
+	};
+
+	public static final Factory ioFile = new Factory() {
+
+		public FileHandler newInstance(File file) throws IOException {
+			final InputStream in = new BufferedInputStream(new FileInputStream(file));
+
+			return new FileHandler() {
+
+				void close() {
+					try {
+						in.close();
+					} catch(IOException e) {
+						logger.error("",e);
+					}
+				}
+
+				InputStream getInputStream() {
+					return in;
+				}
+
+				void writeTo(ServletOutputStream out) throws IOException {
+					byte[] a = new byte[8192];
+					while(true) {
+						int n;
+						try {
+							n = in.read(a);
+						} catch(IOException e) {
+							throw new RuntimeException(e);
+						}
+						if( n == -1 )
+							break;
+						out.write(a,0,n);
+					}
+				}
+			};
+		}
+
+	};
+
+	public static final Factory mappedOrIoFile = new Factory() {
+
+		public FileHandler newInstance(File file) throws IOException {
+			try {
+				return mappedFile.newInstance(file);
+			} catch(IOException e) {
+				if( !"Operation not permitted".equals(e.getMessage()) )
+					throw e;
+				logger.warn("couldn't map "+file+" length="+file.length(),e);
+				return ioFile.newInstance(file);
+			}
+		}
+
+	};
+
+	public static Factory factory = mappedFile;  // change for other implementations
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/Locker.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,50 @@
+package cachingfilter;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+
+public final class Locker<T> {
+
+	private static class Tracker {
+		final ReentrantLock lock = new ReentrantLock();
+		int waiters = 0;
+	}
+
+	private final Map<T,Tracker> map = new HashMap<T,Tracker>();
+
+	public void lock(T obj) {
+		Tracker t;
+		synchronized(map) {
+			t = map.get(obj);
+			if( t == null ) {
+				t = new Tracker();
+				map.put(obj,t);
+			}
+			t.waiters++;
+		}
+		try {
+			t.lock.lock();
+		} finally {
+			synchronized(map) {
+				t.waiters--;
+			}
+		}
+	}
+
+	public boolean unlock(T obj) {
+		synchronized(map) {
+			Tracker t = map.get(obj);
+			if( t == null || !t.lock.isHeldByCurrentThread() )
+				return false;
+			t.lock.unlock();
+			if( t.waiters==0 ) {
+				map.remove(obj);
+			}
+			return true;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/ProxyServletOutputStream.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,72 @@
+package cachingfilter;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.ByteArrayOutputStream;
+import javax.servlet.ServletOutputStream;
+
+
+public abstract class ProxyServletOutputStream extends ServletOutputStream {
+	private static final int BUFFER_SIZE = 100;
+
+	private OutputStream out = new OutputStream() {
+		private final ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+		public void write(int b) throws IOException {
+			buf.write(b);
+			check();
+		}
+		
+		public void write(byte b[]) throws IOException {
+			buf.write(b);
+			check();
+		}
+		
+		public void write(byte b[], int off, int len) throws IOException {
+			buf.write(b,off,len);
+			check();
+		}
+		
+		public void flush() throws IOException {
+			setOut();
+			out.flush();
+		}
+		
+		public void close() throws IOException {
+			setOut();
+			out.close();
+		}
+
+		void check() throws IOException {
+			if( buf.size() >= BUFFER_SIZE )
+				setOut();
+		}
+
+		private void setOut() throws IOException {
+			out = newOutputStream();
+			out.write(buf.toByteArray());
+		}
+	};
+
+	protected abstract OutputStream newOutputStream() throws IOException;
+	
+	public void write(int b) throws IOException {
+		out.write(b);
+	}
+	
+	public void write(byte b[]) throws IOException {
+		out.write(b);
+	}
+	
+	public void write(byte b[], int off, int len) throws IOException {
+		out.write(b,off,len);
+	}
+	
+	public void flush() throws IOException {
+		out.flush();
+	}
+	
+	public void close() throws IOException {
+		out.close();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cachingfilter/ResponseAction.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,135 @@
+package cachingfilter;
+
+import java.io.Serializable;
+import javax.servlet.http.HttpServletResponse;
+
+
+abstract class ResponseAction implements Serializable {
+	final String name;
+
+	ResponseAction(String name) {
+		this.name = name;
+	}
+
+	abstract void apply(HttpServletResponse response);
+
+	public abstract String toString();
+
+	static abstract class StringHeader extends ResponseAction {
+		final String value;
+
+		StringHeader(String name,String value) {
+			super(name);
+			this.value = value;
+		}
+	}
+
+	static abstract class DateHeader extends ResponseAction {
+		final long value;
+
+		DateHeader(String name,long value) {
+			super(name);
+			this.value = value;
+		}
+	}
+
+	static abstract class IntHeader extends ResponseAction {
+		final int value;
+
+		IntHeader(String name,int value) {
+			super(name);
+			this.value = value;
+		}
+	}
+
+	static class SetHeader extends StringHeader {
+
+		SetHeader(String name,String value) {
+			super(name,value);
+		}
+
+		void apply(HttpServletResponse response) {
+			response.setHeader(name,value);
+		}
+
+		public String toString() {
+			return "ResponseAction setHeader(\""+name+"\",\""+value+"\")";
+		}
+	}
+
+	static class AddHeader extends StringHeader {
+
+		AddHeader(String name,String value) {
+			super(name,value);
+		}
+
+		void apply(HttpServletResponse response) {
+			response.addHeader(name,value);
+		}
+
+		public String toString() {
+			return "ResponseAction addHeader(\""+name+"\",\""+value+"\")";
+		}
+	}
+
+	static class SetDateHeader extends DateHeader {
+
+		SetDateHeader(String name,long value) {
+			super(name,value);
+		}
+
+		void apply(HttpServletResponse response) {
+			response.setDateHeader(name,value);
+		}
+
+		public String toString() {
+			return "ResponseAction setDateHeader(\""+name+"\","+value+")";
+		}
+	}
+
+	static class AddDateHeader extends DateHeader {
+
+		AddDateHeader(String name,long value) {
+			super(name,value);
+		}
+
+		void apply(HttpServletResponse response) {
+			response.addDateHeader(name,value);
+		}
+
+		public String toString() {
+			return "ResponseAction addDateHeader(\""+name+"\","+value+")";
+		}
+	}
+
+	static class SetIntHeader extends IntHeader {
+
+		SetIntHeader(String name,int value) {
+			super(name,value);
+		}
+
+		void apply(HttpServletResponse response) {
+			response.setIntHeader(name,value);
+		}
+
+		public String toString() {
+			return "ResponseAction setIntHeader(\""+name+"\","+value+")";
+		}
+	}
+
+	static class AddIntHeader extends IntHeader {
+
+		AddIntHeader(String name,int value) {
+			super(name,value);
+		}
+
+		void apply(HttpServletResponse response) {
+			response.addIntHeader(name,value);
+		}
+
+		public String toString() {
+			return "ResponseAction addIntHeader(\""+name+"\","+value+")";
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/GlobalJetty.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,32 @@
+package global;
+
+import nabble.view.lib.JtpContextServlet;
+import nabble.utils.Jetty;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import nabble.utils.LuanServlet;
+
+import java.net.MalformedURLException;
+
+public class GlobalJetty extends Jetty {
+
+	public ServletContextHandler newWebContext()
+			throws MalformedURLException
+	{
+		ServletContextHandler context = newContext("/", "global/web/Index.class");
+
+		newServletHolder( new LuanServlet("classpath:global/web"), context, new String[]{"*.luan"} );
+
+		JtpContextServlet jtpContext = new JtpContextServlet();
+		jtpContext.setBase("global.web");
+		jtpContext.setUrlMapper( UrlMapperImpl.INSTANCE );
+		jtpContext.setHttpCache( WebCache.INSTANCE );
+
+		ServletHolder sh = newServletHolder(jtpContext, context, new String[] { "/", "*.jtp" });
+		sh.setInitParameter("cache","false");
+		addDefaultServlet(context);
+
+		return context;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/HtmlGlobalUtils.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,67 @@
+
+package global;
+
+import global.web.Terms;
+import nabble.view.lib.HtmlViewUtils;
+import nabble.model.Init;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Calendar;
+
+
+public class HtmlGlobalUtils {
+
+	public static final String nabbleHost = (String)Init.get("nabbleHost");
+	public static final String nabbleContextUrl = "http://" + nabbleHost;
+
+	private static final int cssVersion = 13;
+
+	public static final String nabbleSupportUrl = "http://support.nabble.com/";
+
+	public static void head(HttpServletRequest request, HttpServletResponse response, String title)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<meta charset=\"utf-8\">\r\n        <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\r\n<title>Nabble &bull; " );
+		out.print( (title) );
+		out.print( "</title>\r\n<link href='https://fonts.googleapis.com/css?family=Lato|Oswald' rel='stylesheet' type='text/css'>\r\n<link rel=\"stylesheet\" href=\"/assets/font-awesome/css/font-awesome.min.css?" );
+		out.print( (cssVersion) );
+		out.print( "\">\r\n<link rel=\"stylesheet\" href=\"/assets/global.css?" );
+		out.print( (cssVersion) );
+		out.print( "\">\r\n" );
+
+		HtmlViewUtils.googleAnalytics(out);
+	}
+
+	public static void header(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<div header center>\r\n	<a href=\"/\"><img src=\"/assets/images/logo_nabble_home.png\" width=\"236\" height=\"50\" alt=\"Nabble\"/></a>\r\n</div>\r\n" );
+
+	}
+
+	public static void footer(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<div dark full marginTop center footer>\r\n	<div content>\r\n		<a href=\"" );
+		out.print( (HtmlGlobalUtils.nabbleContextUrl) );
+		out.print( "/help/Index.jtp\" title=\"Nabble help articles\">Help</a>\r\n		<a href=\"/PoweredBy.jtp\" rel=\"nofollow\">Powered by</a>\r\n		<a href=\"" );
+		out.print( (Terms.path(false)) );
+		out.print( "\" rel=\"nofollow\">Terms of Use</a>\r\n		<a href=\"/PrivacyPolicy.jtp\" rel=\"nofollow\">Privacy Policy</a>\r\n		<a href=\"/ContactUs.jtp\">Contact Us</a>\r\n		<div>&copy; 2005-" );
+		out.print( (Calendar.getInstance().get(Calendar.YEAR)) );
+		out.print( " Nabble, LLC.</div>\r\n	</div>\r\n</div>\r\n" );
+
+	}
+
+	private HtmlGlobalUtils() {}  // never
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/HtmlGlobalUtils.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,71 @@
+<%
+package global;
+
+import global.web.Terms;
+import nabble.view.lib.HtmlViewUtils;
+import nabble.model.Init;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Calendar;
+
+
+public class HtmlGlobalUtils {
+
+	public static final String nabbleHost = (String)Init.get("nabbleHost");
+	public static final String nabbleContextUrl = "http://" + nabbleHost;
+
+	private static final int cssVersion = 13;
+
+	public static final String nabbleSupportUrl = "http://support.nabble.com/";
+
+	public static void head(HttpServletRequest request, HttpServletResponse response, String title)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<meta charset="utf-8">
+        <meta name="viewport" content="width=device-width,initial-scale=1">
+		<title>Nabble &bull; <%=title%></title>
+		<link href='https://fonts.googleapis.com/css?family=Lato|Oswald' rel='stylesheet' type='text/css'>
+		<link rel="stylesheet" href="/assets/font-awesome/css/font-awesome.min.css?<%=cssVersion%>">
+		<link rel="stylesheet" href="/assets/global.css?<%=cssVersion%>">
+		<%
+		HtmlViewUtils.googleAnalytics(out);
+	}
+
+	public static void header(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<div header center>
+			<a href="/"><img src="/assets/images/logo_nabble_home.png" width="236" height="50" alt="Nabble"/></a>
+		</div>
+		<%
+	}
+
+	public static void footer(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<div dark full marginTop center footer>
+			<div content>
+				<a href="<%=HtmlGlobalUtils.nabbleContextUrl%>/help/Index.jtp" title="Nabble help articles">Help</a>
+				<a href="/PoweredBy.jtp" rel="nofollow">Powered by</a>
+				<a href="<%=Terms.path(false)%>" rel="nofollow">Terms of Use</a>
+				<a href="/PrivacyPolicy.jtp" rel="nofollow">Privacy Policy</a>
+				<a href="/ContactUs.jtp">Contact Us</a>
+				<div>&copy; 2005-<%=Calendar.getInstance().get(Calendar.YEAR)%> Nabble, LLC.</div>
+			</div>
+		</div>
+		<%
+	}
+
+	private HtmlGlobalUtils() {}  // never
+}
+
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/Server.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,51 @@
+package global;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Properties;
+
+
+public final class Server {
+
+	private static final Map<String,Server> map = new LinkedHashMap<String,Server>();
+
+	// call from beanshell
+	public static void add(String name,String dbUrl,String dbUser,String dbPassword,String host) throws ClassNotFoundException {
+		map.put( name, new Server(name,dbUrl,dbUser,dbPassword,host) );
+	}
+
+	static Collection<Server> getServers() {
+		return map.values();
+	}
+
+	public static Server getServer(String name) {
+		return map.get(name);
+	}
+
+	public final String name;
+	private final String dbUrl;
+	private final Properties dbProperties = new Properties();
+	public final String host;
+
+	private Server(String name,String dbUrl,String dbUser,String dbPassword,String host)
+		throws ClassNotFoundException
+	{
+		this.name = name;
+		Class.forName("org.postgresql.Driver");
+		this.dbUrl = dbUrl;
+		dbProperties.setProperty("user",dbUser);
+		dbProperties.setProperty("password",dbPassword);
+		this.host = host;
+	}
+
+	Connection getConnection()
+		throws SQLException
+	{
+		return DriverManager.getConnection(dbUrl,dbProperties);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/Site.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,450 @@
+package global;
+
+import fschmidt.util.java.HtmlUtils;
+import nabble.view.lib.ViewUtils;
+import nabble.model.Init;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.snowball.SnowballAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.NumericField;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.util.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+
+public final class Site {
+	private static final Logger logger = LoggerFactory.getLogger(Site.class);
+
+	public static volatile String status = "";
+
+	public static final String SERVER_FLD = "server";
+	public static final String SITE_FLD = "site";
+	public static final String DOMAIN_FLD = "domain";
+	public static final String SUBJECT_FLD = "subject";
+	public static final String MESSAGE_FLD = "message";
+	public static final String TYPE_FLD = "type";
+	public static final String ACTIVITY_FLD = "activity";
+	public static final String EMBARRASSING_FLD = "embarrassing";
+	public static final String PRIVATE_FLD = "private";
+	public static final String OWNER_EMAIL_FLD = "owner_email";
+	public static final String OWNER_EMAIL_DOMAIN_FLD = "owner_email_domain";
+	public static final String NODE_COUNT_FLD = "node_count";
+	public static final String WHEN_CREATED_FLD = "when_created";
+	public static final String TWEAKS_FLD = "tweaks";
+	public static final String HAS_TWEAKS_FLD = "has_tweaks";
+	public static final String FILE_COUNT_FLD = "file_count";
+	public static final String FILE_NODE_RATIO_FLD = "file_node_ratio";
+	public static final String MONTHLY_VIEWS_FLD = "monthly_views";
+	public static final String VALUE_FLD = "value";
+
+	public static final Sort SORT_BY_ACTIVITY = new Sort(new SortField(ACTIVITY_FLD, SortField.INT, true));
+	public static final Sort SORT_BY_FILE_NODE_RATIO = new Sort(new SortField(FILE_NODE_RATIO_FLD, SortField.INT, true));
+	public static final Sort SORT_BY_VALUE = new Sort(
+		new SortField(VALUE_FLD, SortField.INT, true),
+		new SortField(NODE_COUNT_FLD, SortField.INT, true)
+	);
+
+	public static final Analyzer analyzer = new SnowballAnalyzer(Version.LUCENE_CURRENT,"English");
+	private static final FSDirectory dir1;
+	private static final FSDirectory dir2;
+	private static volatile Thread thread = null;
+	private static final boolean skipReindex1 = Init.get("skipReindex1",false);
+
+	static {
+		try {
+			String localDir = (String)Init.get("local_dir");
+			dir1 = FSDirectory.open(new File(localDir+"lucene_raw"));
+			dir2 = FSDirectory.open(new File(localDir+"lucene_global"));
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static FSDirectory dir() {
+		return dir2;
+	}
+/*
+	static synchronized void clear()
+		throws IOException
+	{
+		new IndexWriter(dir,analyzer,true,IndexWriter.MaxFieldLength.LIMITED).close();
+	}
+*/
+	public static boolean isReindexing() {
+		return thread != null && thread.isAlive();
+	}
+
+	public static void startReindexing(){
+		status = "starting reindex";
+		thread = new Thread(new Runnable(){public void run(){
+			try {
+				reindex();
+			} catch(SQLException e) {
+				logger.error("",e);
+				status = "Error: " + e.getMessage();
+			} catch(IOException e) {
+				logger.error("",e);
+				status = "Error: " + e.getMessage();
+			} catch(RuntimeException e) {
+				logger.error("",e);
+				status = "Error: " + e.getMessage();
+			}
+		}},"reindex");
+		thread.start();
+	}
+
+	static synchronized void reindex()
+		throws SQLException, IOException
+	{
+		if( !skipReindex1 )
+			reindex1();
+		status = "done indexing servers";
+		logger.info("reindex2");
+		reindex2();
+		logger.info("done reindexing");
+		status = "done reindexing";
+	}
+
+	private static void reindex1()
+		throws SQLException, IOException
+	{
+		IndexWriter indexWriter = new IndexWriter(dir1,analyzer,true,IndexWriter.MaxFieldLength.LIMITED);
+		try {
+			for( Server server : Server.getServers() ) {
+				reindex1(server,indexWriter);
+			}
+		} finally {
+			indexWriter.close();
+		}
+	}
+/*
+	public static synchronized void removeBySubject(String subject)
+		throws IOException
+	{
+		Term term = new Term( SUBJECT_FLD, subject );
+		IndexWriter indexWriter = new IndexWriter(dir2,analyzer,IndexWriter.MaxFieldLength.LIMITED);
+		indexWriter.deleteDocuments(term);
+		indexWriter.close();
+	}
+*/
+	private static synchronized void reindex1(Server server,IndexWriter indexWriter)
+		throws SQLException, IOException
+	{
+		logger.info("reindex "+server.name);
+		status = "reindexing "+server.name;
+
+		List<Long> siteIds = new ArrayList<Long>();
+		{
+			Connection con = server.getConnection();
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"select site_id from global.site_global"
+			);
+			while( rs.next() ) {
+				long siteId = rs.getLong("site_id");
+				siteIds.add(siteId);
+			}
+			rs.close();
+			stmt.close();
+			con.close();
+		}
+		final int n = siteIds.size();
+		int count = 0;
+		for( long siteId : siteIds ) {
+			update(indexWriter,server,siteId);
+			count++;
+			logger.info("reindexed "+count+" of "+n+" sites on "+server.name);
+			status = "reindexed "+count+" of "+n+" sites on "+server.name;
+		}
+	}
+
+	private static void update(IndexWriter indexWriter,Server server,long siteId)
+		throws IOException, SQLException
+	{
+		Connection con = server.getConnection();
+		try {
+			String schema = "s" + siteId;
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"select site_global.*, site.*, node.*, node_msg.message, priv.label as priv, user_.email, tweak.tweaks"
+				+", (select count(*) from " + schema + ".file_node) as file_node_count"
+				+" from global.site_global"
+					+", " + schema + ".site"
+					+" join " + schema + ".node on site.root_node_id = node.node_id"
+					+" join " + schema + ".node_msg on site.root_node_id = node_msg.node_id"
+					+" left join " + schema + ".tag as priv on site.root_node_id = priv.node_id and priv.user_id is null and priv.label='permission:View'"
+					+" left join " + schema + ".user_ on node.owner_id = user_.user_id"
+					+" , (select string_agg(content,' ') as tweaks from " + schema + ".tweak) as tweak"
+					+" where site_global.site_id = " + siteId
+			);
+			if( !rs.next() ) {
+				logger.error("site not found: "+siteId);
+				return;
+			}
+			Document doc = new Document();
+			doc.add( new Field(SERVER_FLD, server.name, Field.Store.YES, Field.Index.NO) );
+			doc.add( new Field(SITE_FLD, Long.toString(siteId), Field.Store.YES, Field.Index.NO) );
+			String subject = rs.getString("subject");
+			String domain = rs.getString("custom_domain");
+			if( domain == null )
+				domain = ViewUtils.getDefaultBaseUrl(siteId,subject,server.host);
+			doc.add( new Field(DOMAIN_FLD, domain, Field.Store.YES, Field.Index.NO) );
+			doc.add( new Field(SUBJECT_FLD, subject, Field.Store.YES, Field.Index.NO) );
+			String message = rs.getString("message");
+			doc.add( new Field(MESSAGE_FLD, message, Field.Store.YES, Field.Index.NO) );
+			String type = "" + rs.getString("type");
+			doc.add( new Field(TYPE_FLD, type, Field.Store.YES, Field.Index.NO) );
+			int activity = rs.getInt("activity");
+			doc.add( new NumericField(ACTIVITY_FLD,Field.Store.YES,false).setIntValue(activity) );
+			String embarrassing = Boolean.toString( rs.getBoolean("is_embarrassing") );
+			doc.add( new Field(EMBARRASSING_FLD, embarrassing, Field.Store.YES, Field.Index.NO) );
+			String privS = rs.getString("priv");
+			String priv = Boolean.toString( privS != null );
+			doc.add( new Field(PRIVATE_FLD, priv, Field.Store.YES, Field.Index.NO) );
+			String email = rs.getString("email");
+			if( email != null ) {
+				doc.add( new Field(OWNER_EMAIL_FLD, email, Field.Store.YES, Field.Index.NO) );
+			}
+			int nodeCount = rs.getInt("node_count");
+			doc.add( new NumericField(NODE_COUNT_FLD,Field.Store.YES,false).setIntValue(nodeCount) );
+			Date whenCreated = rs.getTimestamp("when_created");
+			doc.add( new NumericField(WHEN_CREATED_FLD,Field.Store.YES,false).setLongValue(whenCreated.getTime()) );
+			String tweaks = rs.getString("tweaks");
+			if( tweaks != null ) {
+				doc.add( new Field(TWEAKS_FLD, tweaks, Field.Store.YES, Field.Index.NO) );
+			}
+			int fileCount = rs.getInt("file_node_count");
+			doc.add( new NumericField(FILE_COUNT_FLD,Field.Store.YES,false).setIntValue(fileCount) );
+			int monthlyViews = rs.getInt("monthly_views");
+			doc.add( new NumericField(MONTHLY_VIEWS_FLD,Field.Store.YES,false).setIntValue(monthlyViews) );
+			rs.close();
+			stmt.close();
+			indexWriter.addDocument(doc);
+		} catch(SQLException e) {
+			logger.error("failed to index site "+siteId,e);
+			String msg = e.getMessage();
+			if( !(msg.contains("schema") && msg.contains("does not exist")) )
+				throw e;
+		} finally {
+			con.close();
+		}
+	}
+
+	private static final Pattern ptn = Pattern.compile("([^,]+),(\\d+|\"[0-9,]+\")");
+
+	private static Map<String,Integer> domainMap()
+		throws IOException
+	{
+		Map<String,Integer> map = new HashMap<String,Integer>();
+		File siteFile = new File("data/sites.csv");
+		if( !siteFile.exists() )
+			return map;
+		BufferedReader in = new BufferedReader(new FileReader(siteFile));
+		String line = in.readLine();
+		try {
+			while (!ptn.matcher(line).matches())
+				line = in.readLine();
+			while (true) {
+				if (line == null)
+					break;
+				if (line.length() > 0) {
+					if( line.startsWith(",") )
+						break;
+					Matcher m = ptn.matcher(line);
+					if (!m.matches())
+						throw new RuntimeException(line);
+					String domain = m.group(1);
+					String amt = m.group(2);
+					amt = amt.replaceAll("[,\"]", "");
+					int sessions = Integer.parseInt(amt);
+					map.put(domain, sessions);
+				}
+				line = in.readLine();
+			}
+			in.close();
+		} catch (RuntimeException e) {
+			logger.error("Error in line: " + line, e);
+			throw e;
+		}
+		return map;
+	}
+
+	private static void reindex2()
+		throws IOException
+	{
+		Map<String,Integer> domainMap = domainMap();
+		IndexReader reader = IndexReader.open(dir1);
+		IndexWriter indexWriter = new IndexWriter(dir2,analyzer,true,IndexWriter.MaxFieldLength.LIMITED);
+		try {
+			int n = reader.numDocs();
+			if( n != reader.maxDoc() )
+				throw new RuntimeException();
+			for( int i=0; i<n; i++ ) {
+				Document data = reader.document(i);
+				Document doc = new Document();
+				doc.add( new Field(SERVER_FLD, data.get(SERVER_FLD), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS) );
+				doc.add( new Field(SITE_FLD, data.get(SITE_FLD), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS) );
+				String domain = data.get(DOMAIN_FLD);
+				doc.add( new Field(DOMAIN_FLD, domain, Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS) );
+				doc.add( new Field(SUBJECT_FLD, data.get(SUBJECT_FLD), Field.Store.YES, Field.Index.ANALYZED) );
+				doc.add( new Field(MESSAGE_FLD, data.get(MESSAGE_FLD), Field.Store.YES, Field.Index.ANALYZED) );
+				doc.add( new Field(TYPE_FLD, data.get(TYPE_FLD), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS) );
+				int activity = ((NumericField)data.getFieldable(ACTIVITY_FLD)).getNumericValue().intValue();
+				doc.add( new NumericField(ACTIVITY_FLD,Field.Store.YES,true).setIntValue(activity) );
+				doc.add( new Field(EMBARRASSING_FLD, data.get(EMBARRASSING_FLD), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS) );
+				doc.add( new Field(PRIVATE_FLD, data.get(PRIVATE_FLD), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS) );
+				String email = data.get(OWNER_EMAIL_FLD);
+				if( email != null ) {
+					doc.add( new Field(OWNER_EMAIL_FLD, email, Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS) );
+					String emailDomain = email.substring( email.indexOf('@') + 1 );
+					doc.add( new Field(OWNER_EMAIL_DOMAIN_FLD, emailDomain, Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS) );
+				}
+				int nodeCount = ((NumericField)data.getFieldable(NODE_COUNT_FLD)).getNumericValue().intValue();
+				doc.add( new NumericField(NODE_COUNT_FLD,Field.Store.YES,true).setIntValue(nodeCount) );
+				long whenCreated = ((NumericField)data.getFieldable(WHEN_CREATED_FLD)).getNumericValue().longValue();
+				doc.add( new NumericField(WHEN_CREATED_FLD,Field.Store.YES,false).setLongValue(whenCreated) );
+				String tweaks = data.get(TWEAKS_FLD);
+				if( tweaks != null ) {
+					doc.add( new Field(TWEAKS_FLD, tweaks, Field.Store.YES, Field.Index.NO) );
+					doc.add( new Field(HAS_TWEAKS_FLD, "true", Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS) );
+				}
+				int fileCount = ((NumericField)data.getFieldable(FILE_COUNT_FLD)).getNumericValue().intValue();
+				doc.add( new NumericField(FILE_COUNT_FLD,Field.Store.YES,false).setIntValue(fileCount) );
+				if( nodeCount > 0 ) {
+					int fileNodeRatio = fileCount*1000/nodeCount;
+					doc.add( new NumericField(FILE_NODE_RATIO_FLD,Field.Store.NO,true).setIntValue(fileNodeRatio) );
+				}
+				int monthlyViews = ((NumericField)data.getFieldable(MONTHLY_VIEWS_FLD)).getNumericValue().intValue();
+				doc.add( new NumericField(MONTHLY_VIEWS_FLD,Field.Store.YES,true).setIntValue(monthlyViews) );
+				Integer sessions = domainMap.get(domain);
+				if( sessions == null )
+					sessions = 0;
+				doc.add( new NumericField(VALUE_FLD,Field.Store.YES,true).setIntValue(sessions) );
+				indexWriter.addDocument(doc);
+			}
+		} finally {
+			indexWriter.close();
+			reader.close();
+		}
+	}
+
+	// class here
+
+	private final Document doc;
+
+	public Site(Document doc) {
+		this.doc = doc;
+	}
+
+	public String serverName() {
+		return doc.get(SERVER_FLD);
+	}
+
+	public Server server() {
+		return Server.getServer(serverName());
+	}
+
+	public String id() {
+		return doc.get(SITE_FLD);
+	}
+
+	private static final Set https = new HashSet( Arrays.asList(
+		"www.postgresql-archive.org",
+		"ffq.38.me.nabble.com"
+	) );
+
+	public String url() {
+		String domain = doc.get(DOMAIN_FLD);
+		String scheme = https.contains(domain) ? "https" : "http";
+		return scheme + "://" + domain + "/";
+	}
+
+	public String subject() {
+		return doc.get(SUBJECT_FLD);
+	}
+
+	public String subjectHtml() {
+		return HtmlUtils.htmlEncode(subject());
+	}
+
+	public String link() {
+		return "<a href=\"" + url() + "\">" + subjectHtml() + "</a>";
+	}
+
+	public String message() {
+		return doc.get(MESSAGE_FLD);
+	}
+
+	public String type() {
+		return doc.get(TYPE_FLD);
+	}
+
+	public int activity() {
+		NumericField fld = (NumericField)doc.getFieldable(ACTIVITY_FLD);
+		return fld.getNumericValue().intValue();
+	}
+
+	public int nodeCount() {
+		NumericField fld = (NumericField)doc.getFieldable(NODE_COUNT_FLD);
+		return fld.getNumericValue().intValue();
+	}
+
+	public Date whenCreated() {
+		NumericField fld = (NumericField)doc.getFieldable(WHEN_CREATED_FLD);
+		return new Date(fld.getNumericValue().longValue());
+	}
+
+	public boolean isEmbarrassing() {
+		return Boolean.parseBoolean( doc.get(EMBARRASSING_FLD) );
+	}
+
+	public String tweaks() {
+		return doc.get(TWEAKS_FLD);
+	}
+
+	public int fileCount() {
+		NumericField fld = (NumericField)doc.getFieldable(FILE_COUNT_FLD);
+		return fld.getNumericValue().intValue();
+	}
+
+	public String ownerEmail() {
+		return doc.get(OWNER_EMAIL_FLD);
+	}
+
+	public int monthlyViews() {
+		NumericField fld = (NumericField)doc.getFieldable(MONTHLY_VIEWS_FLD);
+		return fld.getNumericValue().intValue();
+	}
+
+	public int value() {
+		NumericField fld = (NumericField)doc.getFieldable(VALUE_FLD);
+		return fld.getNumericValue().intValue();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/UrlMapperImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,41 @@
+package global;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.servlet.http.HttpServletRequest;
+import fschmidt.util.servlet.UrlMapper;
+import fschmidt.util.servlet.UrlMapping;
+
+
+enum UrlMapperImpl implements UrlMapper {
+	INSTANCE;
+
+	private static final Pattern ROOT_FORUMS_PATTERN = Pattern.compile("/free-apps/page(\\d+)\\.html$");
+
+	public UrlMapping getUrlMapping(HttpServletRequest request) {
+		String path = request.getServletPath();
+
+		if( path.equals("/") )
+			return new UrlMapping( global.web.Index.class, Collections.<String,String[]>emptyMap() );
+
+		if( path.equals("/tools/") )
+			return new UrlMapping( global.web.tools.Index.class, Collections.<String,String[]>emptyMap() );
+
+		Matcher m = ROOT_FORUMS_PATTERN.matcher(path);
+		if( m.find() ) {
+			Map<String,String[]> params = new HashMap<String,String[]>();
+			String s = m.group(1);
+			if( s != null )
+				params.put( "page", new String[]{s} );
+			return new UrlMapping( global.web.RootForums.class, params );
+		}
+
+		if( path.equals("/sitemap.txt") )
+			return new UrlMapping( global.web.Sitemap.class, Collections.<String,String[]>emptyMap() );
+
+		return null;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/WebCache.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,38 @@
+package global;
+
+import fschmidt.util.servlet.HttpCache;
+import nabble.model.Init;
+
+
+enum WebCache implements HttpCache {
+	INSTANCE;
+
+	private static final long hour = 1000L*60*60;
+	private static final long cacheTime = Init.get("cacheTime",hour);
+
+	private long mod = now();
+
+	private static long now() {
+		return System.currentTimeMillis()/1000*1000;
+	}
+
+	public synchronized long[] lastModifieds(String[] modifyingEvents) {
+		long now = now();
+		if( now - mod > cacheTime )
+			mod = now;
+		long[] rtn = new long[modifyingEvents.length];
+		for( int i=0; i<modifyingEvents.length; i++ ) {
+			rtn[i] = mod;
+		}
+		return rtn;
+	}
+
+	public void modified(String modifyingEvent) {
+		throw new UnsupportedOperationException();
+	}
+
+	public synchronized void clear() {
+		mod = now();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/ContactUs.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,39 @@
+
+package global.web;
+
+import fschmidt.util.servlet.JtpContext;
+import global.HtmlGlobalUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Calendar;
+
+
+public final class ContactUs extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext) getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+
+		out.print( "\n<!DOCTYPE html>\n<html lang=\"en\">\n	<head>\n		" );
+ HtmlGlobalUtils.head(request, response, "Contact Us"); 
+		out.print( "\n		<style>p{max-width:500px;margin:0 auto 3em}</style>\n	</head>\n	<body lato>\n		" );
+ HtmlGlobalUtils.header(request,response); 
+		out.print( "\n		<div content center paddingTop>\n			<h2 oswald>Nabble Support Forum</h2>\n			<p>\n				<a href=\"" );
+		out.print( (HtmlGlobalUtils.nabbleSupportUrl) );
+		out.print( "\">Visit the Support Forum</a><br/>\n				For help with technical questions related to your Nabble apps, embedding, customizations, NAML language, bugs and other issues.\n			</p>\n\n			<h2 oswald>Report Abuse</h2>\n			<p>\n				Communications related to privacy, violation of our <a href=\"" );
+		out.print( (Terms.path(false)) );
+		out.print( "\">Terms of Use</a> or other abuse must be submitted by email to\n				abuse<span invisible>[please remove </span><span invisible>this part]</span>&#64;nabble&#46;com.\n			</p>\n\n			<h2 oswald>DMCA Policy</h2>\n			<p>\n				Copyright owners who believe that a Nabble user has posted infringing content may provide notice in compliance with the requirements\n				of the Digital Millennium Copyright Act to Weizhen Lin, 1568 Grackle Way, Sunnyvale CA 94087, email\n				DMCA<span invisible>[please remove </span><span invisible>this part]</span>&#64;nabble&#46;com.\n				<b>There will be no response to communications that do not involve copyright infringement.</b>\n			</p>\n		</div>\n		" );
+ HtmlGlobalUtils.footer(request,response); 
+		out.print( "\n	</body>\n</html>\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/ContactUs.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,60 @@
+<%
+package global.web;
+
+import fschmidt.util.servlet.JtpContext;
+import global.HtmlGlobalUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Calendar;
+
+
+public final class ContactUs extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext) getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+%>
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<% HtmlGlobalUtils.head(request, response, "Contact Us"); %>
+		<style>p{max-width:500px;margin:0 auto 3em}</style>
+	</head>
+	<body lato>
+		<% HtmlGlobalUtils.header(request,response); %>
+		<div content center paddingTop>
+			<h2 oswald>Nabble Support Forum</h2>
+			<p>
+				<a href="<%=HtmlGlobalUtils.nabbleSupportUrl%>">Visit the Support Forum</a><br/>
+				For help with technical questions related to your Nabble apps, embedding, customizations, NAML language, bugs and other issues.
+			</p>
+
+			<h2 oswald>Report Abuse</h2>
+			<p>
+				Communications related to privacy, violation of our <a href="<%=Terms.path(false)%>">Terms of Use</a> or other abuse must be submitted by email to
+				abuse<span invisible>[please remove </span><span invisible>this part]</span>&#64;nabble&#46;com.
+			</p>
+
+			<h2 oswald>DMCA Policy</h2>
+			<p>
+				Copyright owners who believe that a Nabble user has posted infringing content may provide notice in compliance with the requirements
+				of the Digital Millennium Copyright Act to Weizhen Lin, 1568 Grackle Way, Sunnyvale CA 94087, email
+				DMCA<span invisible>[please remove </span><span invisible>this part]</span>&#64;nabble&#46;com.
+				<b>There will be no response to communications that do not involve copyright infringement.</b>
+			</p>
+		</div>
+		<% HtmlGlobalUtils.footer(request,response); %>
+	</body>
+</html>
+<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/Index.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,115 @@
+
+package global.web;
+
+import fschmidt.util.servlet.JtpContext;
+import fschmidt.util.servlet.ServletUtils;
+import global.HtmlGlobalUtils;
+import global.Site;
+import nabble.view.web.more.ForumStart;
+import nabble.view.web.more.MailingListRequest;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TopDocs;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class Index extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(Index.class);
+
+//	private static boolean hasJobsAtNabble = Init.get("hasJobsAtNabble",false);
+
+	public static String path() {
+		return "/";
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response,"x");
+		PrintWriter out = response.getWriter();
+
+		out.print( "\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n	<head>\r\n		" );
+ HtmlGlobalUtils.head(request, response, "Free Forum &bull; Embeddable Web Apps"); 
+		out.print( "\r\n		<link rel=\"canonical\" href=\"https://www.nabble.com/\">\r\n		<script src=\"/assets/jquery/jquery-1.9.1.min.js\"></script>\r\n		<script src=\"/trk5.js\" async=\"true\"></script>\r\n		<meta name=\"description\" content=\"Create a free forum online in less than one minute. All forums are embeddable and fully customizable with scripting language. Choose a unique style and build a discussion board for your community.\" />\r\n		<meta name=\"keywords\" content=\"free forum, free mailing list, mailing list archive, free photo gallery, free newspaper, free blog, best forum, free message board, message board hosting, bulletin board, customizable, private forum, phpBB, vBulletin, hosted, communities\">\r\n		<meta name=\"google-site-verification\" content=\"SUurO4gVJ46SZyzANkH4pJBGH8q-6Bv5P-ZgRBH8Cck\" />\r\n		<style>\r\n		div[actions] i.fa.fa-chevron-right{color:#DDD;float:right}\r\n		a[fixed]{margin-top:-3em;position:absolute;right:1em;text-decoration:none;background-color:#555;border-radius:.5em;padding:.5em;color:#D0D0D0;text-shadow:0px 1px 1px black;border-bottom:1px dotted #757474}\r\n		@media (max-width: 950px) {\r\n			div[footer]{padding-bottom:5em}\r\n		}\r\n		</style>\r\n		<script>\r\n			$(document).ready(function() {\r\n				var $c1 = $('div[col1]');\r\n				var $c2 = $('div[col2]');\r\n				function centerAlign() {\r\n					var width = $c1.width() + $c2.width();\r\n					var margin = ($c1.parent().width() - width)/2;\r\n					margin = margin > 0? margin : 0;\r\n					$c1.css('margin-left',margin+'px');\r\n				};\r\n				centerAlign();\r\n				$(window).resize(centerAlign);\r\n			});\r\n\r\n			if (document.cookie.indexOf('st=1') >= 0)\r\n				$(window).focus(function(){try{window.close()}catch(err){}top.location='about:blank';});\r\n		</script>\r\n	</head>\r\n	<body lato>\r\n		" );
+ HtmlGlobalUtils.header(request,response); 
+		out.print( "\r\n		<div full>\r\n			<div content>\r\n				<div col1 center>\r\n					<h1 oswald>Free Forum Hosting &amp; Online Web Apps!</h1>\r\n					<h2 lato gray>Clean Look <span light>&bull;</span> Embeddable <span light>&bull;</span> Customizable</h2>\r\n					<img src=\"/assets/images/home.png\" alt=\"Free forum hosting and online embeddable apps\"/>\r\n				</div>\r\n				<div col2 actions>\r\n					<ul>\r\n						<li><a href=\"" );
+		out.print( (HtmlGlobalUtils.nabbleContextUrl) );
+		out.print( (ForumStart.path("forum")) );
+		out.print( "\" title=\"Click to create a free forum\">Create Free Forum</a> <i class=\"fa fa-chevron-right\"></i></li>\r\n						<li><a href=\"" );
+		out.print( (HtmlGlobalUtils.nabbleContextUrl) );
+		out.print( (MailingListRequest.path()) );
+		out.print( "\" title=\"Click to archive a mailing list\">Archive Mailing List</a> <i class=\"fa fa-chevron-right\"></i></li>\r\n						<li><a href=\"" );
+		out.print( (HtmlGlobalUtils.nabbleContextUrl) );
+		out.print( (ForumStart.path("gallery")) );
+		out.print( "\" title=\"Click to create a free photo gallery\">Create Photo Gallery</a> <i class=\"fa fa-chevron-right\"></i></li>\r\n						<li><a href=\"" );
+		out.print( (HtmlGlobalUtils.nabbleContextUrl) );
+		out.print( (ForumStart.path("newspaper")) );
+		out.print( "\" title=\"Click to create a free newspaper\">Create News Site</a> <i class=\"fa fa-chevron-right\"></i></li>\r\n						<li><a href=\"" );
+		out.print( (HtmlGlobalUtils.nabbleContextUrl) );
+		out.print( (ForumStart.path("blog")) );
+		out.print( "\" title=\"Click to create a free blog\">Create Blog</a> <i class=\"fa fa-chevron-right\"></i></li>\r\n						<li><a clck href=\"https://www.super-resume.com/\" title=\"Click to build a free resume\">Resume Builder</a> <i class=\"fa fa-chevron-right\"></i></li>\r\n					</ul>\r\n				</div>\r\n			</div>\r\n			<div content paddingTop>\r\n				<div col33 center>\r\n					<h2 oswald>Multi Language</h2>\r\n					<ul floating>\r\n						<li>English</li>\r\n						<li>Čeština (Czech Republic)</li>\r\n						<li>Español</li>\r\n						<li>Français</li>\r\n						<li>Polski</li>\r\n						<li>Português (Brasil)</li>\r\n						<li>Svenska</li>\r\n						<li>Türkçe</li>\r\n						<li>Русский</li>\r\n						<li>Ελληνικά</li>\r\n						<li>中文 (简体)</li>\r\n						<li><a href=\"http://support.nabble.com/Nabble-Translations-f6669344.html\">Translate to other languages</a> &raquo;</li>\r\n					</ul>\r\n				</div>\r\n				<div col33 center>\r\n					<h2 oswald>Embed into any Website</h2>\r\n					<p lineHeight marginHorizontal>All Nabble apps are naturally embeddable, which means that they can be easily displayed inside any web page.\r\n					</p>\r\n				</div>\r\n				<div col33 center>\r\n					<h2 oswald>Fully Customizable</h2>\r\n					<p lineHeight marginHorizontal>All Nabble apps are built with NAML, a scripting language that gives you full control over the app pages.</p>\r\n				</div>\r\n			</div>\r\n			<div content center paddingTop>\r\n				<h2 oswald>Browse Active Nabble Apps</h2>\r\n				" );
+ topSites(out); 
+		out.print( "\r\n			</div>\r\n		</div>\r\n		" );
+ HtmlGlobalUtils.footer(request,response); 
+		out.print( "\r\n" );
+/*
+		out.print( "\r\n		<a fixed href=\"http://www.blasma.com\">Help design a new forum <i class=\"fa fa-chevron-right\"></i></a>\r\n" );
+*/
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+
+	static Query query = new TermQuery(new Term(Site.EMBARRASSING_FLD,"false"));
+
+	private static void topSites(PrintWriter out)
+		throws ServletException, IOException
+	{
+		IndexSearcher searcher;
+		TopDocs hits;
+		try {
+			searcher = new IndexSearcher(Site.dir());
+			hits = searcher.search( query, 60, Site.SORT_BY_VALUE );
+		} catch(IOException e) {
+			logger.error("Index error", e);
+			
+		out.print( "[Rebuilding Index]" );
+
+			return;
+		}
+		try {
+			
+		out.print( "\r\n<ul floating center>\r\n" );
+
+			for( ScoreDoc sd : hits.scoreDocs ) {
+				Site site = new Site( searcher.doc(sd.doc) );
+				
+		out.print( "\r\n<li>" );
+		out.print( (site.link()) );
+		out.print( "</li>\r\n" );
+
+			}
+				
+		out.print( "\r\n</ul>\r\n<div style=\"padding-top:.5em;clear:both\">\r\n<a bold href=\"" );
+		out.print( (RootForums.path()) );
+		out.print( "\" title=\"View more active forums and apps\">View More</a></b> <i class=\"fa fa-chevron-right\"></i>\r\n</div>\r\n" );
+
+		} finally {
+			searcher.close();
+		}
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/Index.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,178 @@
+<%
+package global.web;
+
+import fschmidt.util.servlet.JtpContext;
+import fschmidt.util.servlet.ServletUtils;
+import global.HtmlGlobalUtils;
+import global.Site;
+import nabble.view.web.more.ForumStart;
+import nabble.view.web.more.MailingListRequest;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TopDocs;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class Index extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(Index.class);
+
+//	private static boolean hasJobsAtNabble = Init.get("hasJobsAtNabble",false);
+
+	public static String path() {
+		return "/";
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response,"x");
+		PrintWriter out = response.getWriter();
+%>
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<% HtmlGlobalUtils.head(request, response, "Free Forum &bull; Embeddable Web Apps"); %>
+		<link rel="canonical" href="https://www.nabble.com/">
+		<script src="/assets/jquery/jquery-1.9.1.min.js"></script>
+		<script src="/trk5.js" async="true"></script>
+		<meta name="description" content="Create a free forum online in less than one minute. All forums are embeddable and fully customizable with scripting language. Choose a unique style and build a discussion board for your community." />
+		<meta name="keywords" content="free forum, free mailing list, mailing list archive, free photo gallery, free newspaper, free blog, best forum, free message board, message board hosting, bulletin board, customizable, private forum, phpBB, vBulletin, hosted, communities">
+		<meta name="google-site-verification" content="SUurO4gVJ46SZyzANkH4pJBGH8q-6Bv5P-ZgRBH8Cck" />
+		<style>
+		div[actions] i.fa.fa-chevron-right{color:#DDD;float:right}
+		a[fixed]{margin-top:-3em;position:absolute;right:1em;text-decoration:none;background-color:#555;border-radius:.5em;padding:.5em;color:#D0D0D0;text-shadow:0px 1px 1px black;border-bottom:1px dotted #757474}
+		@media (max-width: 950px) {
+			div[footer]{padding-bottom:5em}
+		}
+		</style>
+		<script>
+			$(document).ready(function() {
+				var $c1 = $('div[col1]');
+				var $c2 = $('div[col2]');
+				function centerAlign() {
+					var width = $c1.width() + $c2.width();
+					var margin = ($c1.parent().width() - width)/2;
+					margin = margin > 0? margin : 0;
+					$c1.css('margin-left',margin+'px');
+				};
+				centerAlign();
+				$(window).resize(centerAlign);
+			});
+
+			if (document.cookie.indexOf('st=1') >= 0)
+				$(window).focus(function(){try{window.close()}catch(err){}top.location='about:blank';});
+		</script>
+	</head>
+	<body lato>
+		<% HtmlGlobalUtils.header(request,response); %>
+		<div full>
+			<div content>
+				<div col1 center>
+					<h1 oswald>Free Forum Hosting &amp; Online Web Apps!</h1>
+					<h2 lato gray>Clean Look <span light>&bull;</span> Embeddable <span light>&bull;</span> Customizable</h2>
+					<img src="/assets/images/home.png" alt="Free forum hosting and online embeddable apps"/>
+				</div>
+				<div col2 actions>
+					<ul>
+						<li><a href="<%=HtmlGlobalUtils.nabbleContextUrl%><%=ForumStart.path("forum")%>" title="Click to create a free forum">Create Free Forum</a> <i class="fa fa-chevron-right"></i></li>
+						<li><a href="<%=HtmlGlobalUtils.nabbleContextUrl%><%=MailingListRequest.path()%>" title="Click to archive a mailing list">Archive Mailing List</a> <i class="fa fa-chevron-right"></i></li>
+						<li><a href="<%=HtmlGlobalUtils.nabbleContextUrl%><%=ForumStart.path("gallery")%>" title="Click to create a free photo gallery">Create Photo Gallery</a> <i class="fa fa-chevron-right"></i></li>
+						<li><a href="<%=HtmlGlobalUtils.nabbleContextUrl%><%=ForumStart.path("newspaper")%>" title="Click to create a free newspaper">Create News Site</a> <i class="fa fa-chevron-right"></i></li>
+						<li><a href="<%=HtmlGlobalUtils.nabbleContextUrl%><%=ForumStart.path("blog")%>" title="Click to create a free blog">Create Blog</a> <i class="fa fa-chevron-right"></i></li>
+						<li><a clck href="https://www.super-resume.com/" title="Click to build a free resume">Resume Builder</a> <i class="fa fa-chevron-right"></i></li>
+					</ul>
+				</div>
+			</div>
+			<div content paddingTop>
+				<div col33 center>
+					<h2 oswald>Multi Language</h2>
+					<ul floating>
+						<li>English</li>
+						<li>Čeština (Czech Republic)</li>
+						<li>Español</li>
+						<li>Français</li>
+						<li>Polski</li>
+						<li>Português (Brasil)</li>
+						<li>Svenska</li>
+						<li>Türkçe</li>
+						<li>Русский</li>
+						<li>Ελληνικά</li>
+						<li>中文 (简体)</li>
+						<li><a href="http://support.nabble.com/Nabble-Translations-f6669344.html">Translate to other languages</a> &raquo;</li>
+					</ul>
+				</div>
+				<div col33 center>
+					<h2 oswald>Embed into any Website</h2>
+					<p lineHeight marginHorizontal>All Nabble apps are naturally embeddable, which means that they can be easily displayed inside any web page.
+					</p>
+				</div>
+				<div col33 center>
+					<h2 oswald>Fully Customizable</h2>
+					<p lineHeight marginHorizontal>All Nabble apps are built with NAML, a scripting language that gives you full control over the app pages.</p>
+				</div>
+			</div>
+			<div content center paddingTop>
+				<h2 oswald>Browse Active Nabble Apps</h2>
+				<% topSites(out); %>
+			</div>
+		</div>
+		<% HtmlGlobalUtils.footer(request,response); %>
+<%/*%>
+		<a fixed href="http://www.blasma.com">Help design a new forum <i class="fa fa-chevron-right"></i></a>
+<%*/%>
+	</body>
+</html>
+<%
+	}
+
+	static Query query = new TermQuery(new Term(Site.EMBARRASSING_FLD,"false"));
+
+	private static void topSites(PrintWriter out)
+		throws ServletException, IOException
+	{
+		IndexSearcher searcher;
+		TopDocs hits;
+		try {
+			searcher = new IndexSearcher(Site.dir());
+			hits = searcher.search( query, 60, Site.SORT_BY_VALUE );
+		} catch(IOException e) {
+			logger.error("Index error", e);
+			%>[Rebuilding Index]<%
+			return;
+		}
+		try {
+			%>
+			<ul floating center>
+			<%
+			for( ScoreDoc sd : hits.scoreDocs ) {
+				Site site = new Site( searcher.doc(sd.doc) );
+				%>
+				<li><%=site.link()%></li>
+				<%
+			}
+				%>
+			</ul>
+			<div style="padding-top:.5em;clear:both">
+				<a bold href="<%=RootForums.path()%>" title="View more active forums and apps">View More</a></b> <i class="fa fa-chevron-right"></i>
+			</div>
+			<%
+		} finally {
+			searcher.close();
+		}
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/PoweredBy.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,31 @@
+
+package global.web;
+
+import global.HtmlGlobalUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class PoweredBy extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+
+		out.print( "\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n	<head>\r\n		" );
+ HtmlGlobalUtils.head(request, response, "Powered By"); 
+		out.print( "\r\n		<style>\r\n		table{width:30em}\r\n		@media (max-width: 950px) {\r\n			td{display:block}\r\n			table{width:auto}\r\n		}\r\n		</style>\r\n	</head>\r\n	<body lato>\r\n		" );
+ HtmlGlobalUtils.header(request,response); 
+		out.print( "\r\n		<div content center paddingTop>\r\n			<h1 oswald>Nabble is powered by the following:</h1>\r\n			<table align=\"center\">\r\n				<tr valign=\"top\">\r\n					<td align=\"center\">\r\n						<p><a href=\"http://java.com/en/download/\"><img src=\"/assets/images/java.gif\" border=\"0\" /></a></p>\r\n						<p><a href=\"http://jetty.mortbay.org/\"><img src=\"/assets/images/jetty.gif\" border=\"0\" /></a></p>\r\n						<p><a href=\"http://www.postgresql.org/\"><img src=\"/assets/images/postgres.gif\" border=\"0\" /></a></p>\r\n						<p><a href=\"http://www.exim.org/\"><img src=\"/assets/images/exim.png\" border=\"0\" /></a></p>\r\n						<p><a href=\"http://www.debian.org/\"><img src=\"/assets/images/debian.png\" border=\"0\" /></a></p>\r\n					</td>\r\n					<td align=\"center\">\r\n						<p><a href=\"http://lucene.apache.org/java/\">Lucene</a></p>\r\n						<p><a href=\"http://jquery.com/\">jQuery</a></p>\r\n						<p><a href=\"http://subversion.tigris.org/\">Subversion</a></p>\r\n						<p><a href=\"https://code.google.com/p/luan-java/\">Luan</a></p>\r\n						<p><a href=\"http://logging.apache.org/log4j/\">Log4j</a></p>\r\n						<p><a href=\"http://www.slf4j.org/\">SLF4J</a></p>\r\n						<p><a href=\"http://mstor.sourceforge.net/\">mstor</a></p>\r\n						<p><a href=\"http://gif-plugin.dev.java.net/\">gif-plugin</a></p>\r\n						<p><a href=\"http://jakarta.apache.org/commons/fileupload/\">FileUpload</a></p>\r\n						<p><a href=\"http://code.google.com/p/caching-filter/\">caching-filter</a></p>\r\n						<p><a href=\"https://github.com/tig100/JdbcPgBackup\">JdbcPgBackup</a></p>\r\n					</td>\r\n				</tr>\r\n			</table>\r\n		</div>\r\n		" );
+ HtmlGlobalUtils.footer(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/PoweredBy.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,68 @@
+<%
+package global.web;
+
+import global.HtmlGlobalUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class PoweredBy extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+%>
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<% HtmlGlobalUtils.head(request, response, "Powered By"); %>
+		<style>
+		table{width:30em}
+		@media (max-width: 950px) {
+			td{display:block}
+			table{width:auto}
+		}
+		</style>
+	</head>
+	<body lato>
+		<% HtmlGlobalUtils.header(request,response); %>
+		<div content center paddingTop>
+			<h1 oswald>Nabble is powered by the following:</h1>
+			<table align="center">
+				<tr valign="top">
+					<td align="center">
+						<p><a href="http://java.com/en/download/"><img src="/assets/images/java.gif" border="0" /></a></p>
+						<p><a href="http://jetty.mortbay.org/"><img src="/assets/images/jetty.gif" border="0" /></a></p>
+						<p><a href="http://www.postgresql.org/"><img src="/assets/images/postgres.gif" border="0" /></a></p>
+						<p><a href="http://www.exim.org/"><img src="/assets/images/exim.png" border="0" /></a></p>
+						<p><a href="http://www.debian.org/"><img src="/assets/images/debian.png" border="0" /></a></p>
+					</td>
+					<td align="center">
+						<p><a href="http://lucene.apache.org/java/">Lucene</a></p>
+						<p><a href="http://jquery.com/">jQuery</a></p>
+						<p><a href="http://subversion.tigris.org/">Subversion</a></p>
+						<p><a href="https://code.google.com/p/luan-java/">Luan</a></p>
+						<p><a href="http://logging.apache.org/log4j/">Log4j</a></p>
+						<p><a href="http://www.slf4j.org/">SLF4J</a></p>
+						<p><a href="http://mstor.sourceforge.net/">mstor</a></p>
+						<p><a href="http://gif-plugin.dev.java.net/">gif-plugin</a></p>
+						<p><a href="http://jakarta.apache.org/commons/fileupload/">FileUpload</a></p>
+						<p><a href="http://code.google.com/p/caching-filter/">caching-filter</a></p>
+						<p><a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a></p>
+					</td>
+				</tr>
+			</table>
+		</div>
+		<% HtmlGlobalUtils.footer(request,response); %>
+	</body>
+</html>
+<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/PrivacyPolicy.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,34 @@
+
+package global.web;
+
+import fschmidt.util.servlet.JtpContext;
+import global.HtmlGlobalUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class PrivacyPolicy extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+
+		out.print( "\n<!DOCTYPE html>\n<html lang=\"en\">\n	<head>\n		" );
+ HtmlGlobalUtils.head(request, response, "Privacy Policy"); 
+		out.print( "\n	</head>\n	<body lato>\n		" );
+ HtmlGlobalUtils.header(request,response); 
+		out.print( "\n		<div content center paddingTop>\n			<h1 oswald>Privacy Policy</h1>\n\n			<div class=\"terms\" style=\"margin: 2em 1em .83em;text-align: justify\">\n				<p class=\"first-para\">\n					This Privacy Policy specifies the manner in which Nabble collects, stores, uses,\n					and discloses information about visitors. By using Nabble, you understand and\n					accept the most recent version of this policy, which will be periodically updated.\n					This document was last updated on April 03, 2015.\n				</p>\n\n				<h2 oswald>Information we collect</h2>\n\n				<p class=\"first-para\">\n					We collect information to provide better services to all of our users. We collect information in two ways:\n				</p>\n				<ul>\n					<li>\n						<strong>Information you give us.</strong>\n						Nabble retains private and personal information only when it is voluntarily submitted by visitors via forms and emails.\n					</li>\n					<li>\n						<strong>Information we get from your use of our services.</strong>\n						We may collect information about the services that you use and how you use them. This includes:\n						<ul>\n							<li>\n								<strong>Cookies and anonymous identifiers.</strong>\n								This site uses some unobtrusive cookies to store information on your computer. Some cookies\n								on this site are essential, and the site won't work as expected without them. These cookies are\n								set when you submit a form, login or interact with the site by doing something that goes beyond\n								clicking on simple links. We also use some non-essential cookies to anonymously track visitors\n								or enhance your experience of the site.\n							</li>\n							<li>\n								<strong>Log information.</strong>\n								Nabble may collect and store certain information in server logs, including details of how you used our service,\n								system activity, hardware settings, browser type, browser language, IP address, the date and time of your request and referral URL.\n							</li>\n						</ul>\n					</li>\n				</ul>\n\n				<h2 oswald>How we use the information we collect</h2>\n\n				<p>\n					The information we collect is used to improve your user experience and the overall quality of our services.\n					This basically includes verification of identity, maintaining user preferences, monitoring web traffic,\n					investigation of bugs and statistical analysis with the purpose of improving the user experience.\n				</p>\n\n				<h2 oswald>Anonymous Usage</h2>\n\n				<p>\n					You may use Nabble without divulging any information beyond what is technologically required to retrieve a web page.\n					Such usage, however, will limit the availability of certain features, including maintaining an account.\n				</p>\n\n				<h2 oswald>Site Owners</h2>\n				<p>\n					A Nabble \"site\" is a forum, blog, or any other application created on Nabble with its own subdomain.\n					The user who creates a site owns it and can make the content public or private as he/she sees fit.\n					This user may also download backups of the data for the site, and this will include any data\n					that you have submitted to this site (except passwords, which are encrypted and truncated for security reasons).\n				</p>\n\n				<h2 oswald>Third Parties</h2>\n				<p>\n					Nabble utilizes certain third parties for the normal operation of our service.\n					This includes, but is not limited to, providers of hosting, payment gateways, statistical analysis software and advertising networks.\n					We allow third-party companies to collect certain information when you visit our web site. These companies may utilize cookies, pixels or\n					other technologies to collect and use non-personally identifiable information (e.g., hashed data, click stream information, browser type,\n					time and date, subject of advertisements clicked or scrolled over) during your visits to this and other web sites in order to provide\n					advertisements about goods and services likely to be of greater interest to you. To learn more about this behavioral advertising practice or\n					to opt-out of this type of advertising, you can visit the websites of the Digital Advertising Alliance at\n					<a href=\"http://www.aboutads.info\">www.aboutads.info</a> and\n					Networking Advertising Initiative at <a href=\"http://www.networkadvertising.org/choices/\">www.networkadvertising.org/choices/</a>.\n				</p>\n				<p>\n					Personal information we collect is not shared with third parties except when approved by you or necessary for the delivery of the service.\n					Information may likewise be divulged to aid in the investigation of illegal or forbidden activities and as required by law.\n					Information may be transferred in the event that Nabble is acquired by another organization.\n				</p>\n			</div>\n		</div>\n		" );
+ HtmlGlobalUtils.footer(request,response); 
+		out.print( "\n	</body>\n</html>\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/PrivacyPolicy.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,121 @@
+<%
+package global.web;
+
+import fschmidt.util.servlet.JtpContext;
+import global.HtmlGlobalUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class PrivacyPolicy extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+%>
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<% HtmlGlobalUtils.head(request, response, "Privacy Policy"); %>
+	</head>
+	<body lato>
+		<% HtmlGlobalUtils.header(request,response); %>
+		<div content center paddingTop>
+			<h1 oswald>Privacy Policy</h1>
+
+			<div class="terms" style="margin: 2em 1em .83em;text-align: justify">
+				<p class="first-para">
+					This Privacy Policy specifies the manner in which Nabble collects, stores, uses,
+					and discloses information about visitors. By using Nabble, you understand and
+					accept the most recent version of this policy, which will be periodically updated.
+					This document was last updated on April 03, 2015.
+				</p>
+
+				<h2 oswald>Information we collect</h2>
+
+				<p class="first-para">
+					We collect information to provide better services to all of our users. We collect information in two ways:
+				</p>
+				<ul>
+					<li>
+						<strong>Information you give us.</strong>
+						Nabble retains private and personal information only when it is voluntarily submitted by visitors via forms and emails.
+					</li>
+					<li>
+						<strong>Information we get from your use of our services.</strong>
+						We may collect information about the services that you use and how you use them. This includes:
+						<ul>
+							<li>
+								<strong>Cookies and anonymous identifiers.</strong>
+								This site uses some unobtrusive cookies to store information on your computer. Some cookies
+								on this site are essential, and the site won't work as expected without them. These cookies are
+								set when you submit a form, login or interact with the site by doing something that goes beyond
+								clicking on simple links. We also use some non-essential cookies to anonymously track visitors
+								or enhance your experience of the site.
+							</li>
+							<li>
+								<strong>Log information.</strong>
+								Nabble may collect and store certain information in server logs, including details of how you used our service,
+								system activity, hardware settings, browser type, browser language, IP address, the date and time of your request and referral URL.
+							</li>
+						</ul>
+					</li>
+				</ul>
+
+				<h2 oswald>How we use the information we collect</h2>
+
+				<p>
+					The information we collect is used to improve your user experience and the overall quality of our services.
+					This basically includes verification of identity, maintaining user preferences, monitoring web traffic,
+					investigation of bugs and statistical analysis with the purpose of improving the user experience.
+				</p>
+
+				<h2 oswald>Anonymous Usage</h2>
+
+				<p>
+					You may use Nabble without divulging any information beyond what is technologically required to retrieve a web page.
+					Such usage, however, will limit the availability of certain features, including maintaining an account.
+				</p>
+
+				<h2 oswald>Site Owners</h2>
+				<p>
+					A Nabble "site" is a forum, blog, or any other application created on Nabble with its own subdomain.
+					The user who creates a site owns it and can make the content public or private as he/she sees fit.
+					This user may also download backups of the data for the site, and this will include any data
+					that you have submitted to this site (except passwords, which are encrypted and truncated for security reasons).
+				</p>
+
+				<h2 oswald>Third Parties</h2>
+				<p>
+					Nabble utilizes certain third parties for the normal operation of our service.
+					This includes, but is not limited to, providers of hosting, payment gateways, statistical analysis software and advertising networks.
+					We allow third-party companies to collect certain information when you visit our web site. These companies may utilize cookies, pixels or
+					other technologies to collect and use non-personally identifiable information (e.g., hashed data, click stream information, browser type,
+					time and date, subject of advertisements clicked or scrolled over) during your visits to this and other web sites in order to provide
+					advertisements about goods and services likely to be of greater interest to you. To learn more about this behavioral advertising practice or
+					to opt-out of this type of advertising, you can visit the websites of the Digital Advertising Alliance at
+					<a href="http://www.aboutads.info">www.aboutads.info</a> and
+					Networking Advertising Initiative at <a href="http://www.networkadvertising.org/choices/">www.networkadvertising.org/choices/</a>.
+				</p>
+				<p>
+					Personal information we collect is not shared with third parties except when approved by you or necessary for the delivery of the service.
+					Information may likewise be divulged to aid in the investigation of illegal or forbidden activities and as required by law.
+					Information may be transferred in the event that Nabble is acquired by another organization.
+				</p>
+			</div>
+		</div>
+		<% HtmlGlobalUtils.footer(request,response); %>
+	</body>
+</html>
+<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/RootForums.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,113 @@
+
+package global.web;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import global.HtmlGlobalUtils;
+import global.Site;
+import nabble.model.MessageUtils;
+import nabble.model.NodeSearcher;
+import nabble.view.lib.HtmlViewUtils;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopDocs;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class RootForums extends HttpServlet {
+
+	public static final int PAGES = 200;
+	private static final int ROWS_PER_PAGE = 50;
+
+	static String path() {
+		return path(1);
+	}
+
+	static String path(int page) {
+		return "/free-apps/page" + page + ".html";
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response,"x");
+
+		PrintWriter out = response.getWriter();
+
+		String title = "Browse Apps";
+
+		String pageS = request.getParameter("page");
+		int page = Integer.parseInt(pageS);
+		int iRec = (page-1)*ROWS_PER_PAGE;
+
+		IndexSearcher searcher;
+		TopDocs hits;
+		try {
+			searcher = new IndexSearcher(Site.dir());
+			hits = searcher.search( Index.query, iRec+ROWS_PER_PAGE, Site.SORT_BY_VALUE );
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+		try {
+			HtmlViewUtils.PagingPath pagingPath = new HtmlViewUtils.PagingPath() {
+				public String path(int row) {
+					return RootForums.path(1+row/ROWS_PER_PAGE);
+				}
+			};
+
+
+		out.print( "\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n	<head>\r\n		" );
+ HtmlGlobalUtils.head(request, response, title + " | Page " + page); 
+		out.print( "\r\n		<link rel=\"canonical\" href=\"https://www.nabble.com" );
+		out.print( (path(page)) );
+		out.print( "\">\r\n		<style>\r\n			h2{margin-bottom:.2em}\r\n			div[sites] > div{min-height:70px}\r\n		</style>\r\n	</head>\r\n	<body lato>\r\n		" );
+ HtmlGlobalUtils.header(request,response); 
+		out.print( "\r\n		<div content center paddingTop>\r\n			<h1 oswald>" );
+		out.print( (title) );
+		out.print( "</h1>\r\n		</div>\r\n		<div gray content marginBottom>\r\n			Page " );
+		out.print( (page) );
+		out.print( " of " );
+		out.print( (PAGES) );
+		out.print( "\r\n			" );
+ HtmlViewUtils.genericPaging(request, response, PAGES*ROWS_PER_PAGE, iRec, ROWS_PER_PAGE, pagingPath, "0 4em .5em", true, 10); 
+		out.print( "\r\n		</div>\r\n		<div content paddingTop sites>\r\n			" );
+
+			for( int i = iRec; i<hits.scoreDocs.length; i++ ) {
+				Site site = new Site(searcher.doc(hits.scoreDocs[i].doc));
+				String message = site.message();
+				message = MessageUtils.getTextWithoutQuotes(message);
+				message = NodeSearcher.getStartingFragment(message,200,"...");
+				message = MessageUtils.hideAllEmails(message);
+				message = HtmlUtils.htmlEncode(message);
+				String what = site.type();
+				if (what == null || what.matches("mixed|category|board|null"))
+					what = "forum";
+				
+		out.print( "\r\n	<div marginBottom>\r\n		<h2>" );
+		out.print( (site.link()) );
+		out.print( "</h2>\r\n		<div gray>\r\n			" );
+		out.print( (message) );
+		out.print( "\r\n		</div>\r\n	</div>\r\n" );
+
+			}
+			
+		out.print( "\r\n" );
+ HtmlViewUtils.genericPaging(request, response, PAGES*ROWS_PER_PAGE, iRec, ROWS_PER_PAGE, pagingPath, ".4em 4em", true, 10); 
+		out.print( "\r\n</div>\r\n" );
+ HtmlGlobalUtils.footer(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+		} finally {
+			searcher.close();
+		}
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/RootForums.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,119 @@
+<%
+package global.web;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import global.HtmlGlobalUtils;
+import global.Site;
+import nabble.model.MessageUtils;
+import nabble.model.NodeSearcher;
+import nabble.view.lib.HtmlViewUtils;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopDocs;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class RootForums extends HttpServlet {
+
+	public static final int PAGES = 200;
+	private static final int ROWS_PER_PAGE = 50;
+
+	static String path() {
+		return path(1);
+	}
+
+	static String path(int page) {
+		return "/free-apps/page" + page + ".html";
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response,"x");
+
+		PrintWriter out = response.getWriter();
+
+		String title = "Browse Apps";
+
+		String pageS = request.getParameter("page");
+		int page = Integer.parseInt(pageS);
+		int iRec = (page-1)*ROWS_PER_PAGE;
+
+		IndexSearcher searcher;
+		TopDocs hits;
+		try {
+			searcher = new IndexSearcher(Site.dir());
+			hits = searcher.search( Index.query, iRec+ROWS_PER_PAGE, Site.SORT_BY_VALUE );
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+		try {
+			HtmlViewUtils.PagingPath pagingPath = new HtmlViewUtils.PagingPath() {
+				public String path(int row) {
+					return RootForums.path(1+row/ROWS_PER_PAGE);
+				}
+			};
+
+%>
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<% HtmlGlobalUtils.head(request, response, title + " | Page " + page); %>
+		<link rel="canonical" href="https://www.nabble.com<%=path(page)%>">
+		<style>
+			h2{margin-bottom:.2em}
+			div[sites] > div{min-height:70px}
+		</style>
+	</head>
+	<body lato>
+		<% HtmlGlobalUtils.header(request,response); %>
+		<div content center paddingTop>
+			<h1 oswald><%=title%></h1>
+		</div>
+		<div gray content marginBottom>
+			Page <%=page%> of <%=PAGES%>
+			<% HtmlViewUtils.genericPaging(request, response, PAGES*ROWS_PER_PAGE, iRec, ROWS_PER_PAGE, pagingPath, "0 4em .5em", true, 10); %>
+		</div>
+		<div content paddingTop sites>
+			<%
+			for( int i = iRec; i<hits.scoreDocs.length; i++ ) {
+				Site site = new Site(searcher.doc(hits.scoreDocs[i].doc));
+				String message = site.message();
+				message = MessageUtils.getTextWithoutQuotes(message);
+				message = NodeSearcher.getStartingFragment(message,200,"...");
+				message = MessageUtils.hideAllEmails(message);
+				message = HtmlUtils.htmlEncode(message);
+				String what = site.type();
+				if (what == null || what.matches("mixed|category|board|null"))
+					what = "forum";
+				%>
+					<div marginBottom>
+						<h2><%=site.link()%></h2>
+						<div gray>
+							<%=message%>
+						</div>
+					</div>
+				<%
+			}
+			%>
+			<% HtmlViewUtils.genericPaging(request, response, PAGES*ROWS_PER_PAGE, iRec, ROWS_PER_PAGE, pagingPath, ".4em 4em", true, 10); %>
+		</div>
+		<% HtmlGlobalUtils.footer(request,response); %>
+	</body>
+</html>
+<%
+		} finally {
+			searcher.close();
+		}
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/Sitemap.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,33 @@
+package global.web;
+
+import global.HtmlGlobalUtils;
+
+import java.io.PrintWriter;
+import java.io.IOException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+public final class Sitemap extends HttpServlet {
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		response.setContentType("text/plain");
+		response.setCharacterEncoding("UTF-8");
+		PrintWriter out = response.getWriter();
+
+		String home = "https://www.nabble.com";
+		out.println(home + "/");
+		out.println(home + "/ContactUs.jtp");
+		out.println(home + "/PoweredBy.jtp");
+		out.println(home + "/PrivacyPolicy.jtp");
+		out.println(home + "/Terms.jtp");
+		out.println(home + "/BackEnd.jtp");
+
+		for (int page = 1; page <= RootForums.PAGES; page++) {
+			out.println( home + RootForums.path(page) );
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/Terms.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,46 @@
+
+package global.web;
+
+import fschmidt.util.servlet.JtpContext;
+import global.HtmlGlobalUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class Terms extends HttpServlet {
+
+	public static String path(boolean back) {
+		return "/Terms.jtp";
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+
+		out.print( "\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n	<head>\r\n		" );
+ HtmlGlobalUtils.head(request, response, "Terms of Use"); 
+		out.print( "\r\n		<meta name=\"robots\" content=\"noindex,nofollow\"/>\r\n	</head>\r\n	<body lato>\r\n		" );
+ HtmlGlobalUtils.header(request,response); 
+		out.print( "\r\n		<div content center paddingTop>\r\n			<h1 oswald>Terms of Use</h1>\r\n\r\n			<div class=\"terms\" style=\"margin: .83em 1em;text-align: justify\">\r\n				<h2 oswald>This is a Contract and License</h2>\r\n				<p class=\"first-para\">\r\n					By using Nabble, you accept the terms of this license and contract between you and Nabble, LLC.\r\n					This document was last updated on August 17, 2012.\r\n				</p>\r\n\r\n				<h2 oswald>Your Content in our Services</h2>\r\n				<p class=\"first-para\">\r\n					Nabble agrees that you retain ownership of any intellectual property rights that you hold in content that you submit to us.\r\n					In short, what belongs to you stays yours.\r\n					You are responsible for your content, and you may edit or delete your content.\r\n					Nabble does not moderate sites and does not review, approve or endorse any content.\r\n				</p>\r\n\r\n				<p>\r\n					When you upload or otherwise submit content to our servers, you give Nabble a worldwide license to use, host, store,\r\n					reproduce, modify, communicate and publish such content. Your content can be public or private depending on the configuration\r\n					of the subdomain where it was submitted. This configuration is controlled by other users who administer that subdomain, and they can freely make your\r\n					content public or private at any time.\r\n					Nabble does not make private content public. If you own a Nabble site (subdomain) and want to keep your content private, it will stay private.\r\n				</p>\r\n\r\n				<p>\r\n					Since it is possible that a user may post offensive content or content that is harmful to minors,\r\n					Nabble wants to make sure that you are aware that filtering software and services are available to prevent access to such content.\r\n					This feature is available in Internet security software made by Symantec, Inc. and McAfee, Inc. as well as from major Internet Service Providers.\r\n					In order to see a list of current vendors, please use an Internet search engine to search for \"parental control software\" or \"filtering software.\"\r\n				</p>\r\n\r\n				<h2 oswald>Privacy and Copyright Protection</h2>\r\n\r\n				<p class=\"first-para\">\r\n					Nabble’s <a href=\"/PrivacyPolicy.jtp\">privacy policy</a> explains how we treat your personal data and protect your privacy when you use our services.\r\n					By using our services, you agree that Nabble can use such data in accordance with our privacy policy.\r\n				</p>\r\n\r\n				<p>\r\n					Copyright owners who believe that a Nabble user has posted infringing content may provide notice in compliance with the requirements\r\n					of the Digital Millennium Copyright Act to Weizhen Lin, 1568 Grackle Way, Sunnyvale CA 94087, Telephone:\r\n					408-464-4439, email " );
+		out.print( (encodeNabbleEmail("DMCA")) );
+		out.print( ".\r\n					<b>There will be no response to communications that do not involve copyright infringement.</b>\r\n					Communications related to privacy, violation of our Terms of Use or other abuse must be submitted by email to\r\n					" );
+		out.print( (encodeNabbleEmail("abuse")) );
+		out.print( ".\r\n				</p>\r\n\r\n				<h2 oswald>Deletion of Content/Termination of Account</h2>\r\n				<p class=\"first-para\">Nabble reserves the right to delete or block access to any content that you post if Nabble determines that your content is inappropriate for any reason, including Nabble's determination that the content infringes on another's intellectual property rights.  Nabble provides the following examples of inappropriate content as a guideline, but this list is not all inclusive and it is subject to change:</p>\r\n\r\n				<ul>\r\n				<li>Content that violates the laws of California or the United States;</li>\r\n				<li>Content that infringes intellectual property rights;</li>\r\n				<li>Content that violates the privacy rights of others;</li>\r\n				<li>Mass advertising (e.g., spam);</li>\r\n				<li>Viruses and the like;</li>\r\n				<li>Statements intended to perpetrate a fraud; and,</li>\r\n				<li>Illegal pornography.</li>\r\n				</ul>\r\n\r\n				<p>Posting of inappropriate content may lead to the termination of your account.  Repeated copyright infringement will lead to the termination of your account.</p>\r\n\r\n				<p>Nabble reserves the right to delete accounts that it determines are inactive without notice.</p>\r\n\r\n				<h2 oswald>Limited Warranty and Disclaimer of Warranties.</h2>\r\n				<p class=\"first-para\">NABBLE MAKES NO EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, OR ARISING FROM A COURSE OF DEALING, TRADE USAGE OR TRADE PRACTICE.</p>\r\n\r\n				<h2 oswald>Limitation of Liability</h2>\r\n				<p class=\"first-para\">In no event will Nabble be liable for any lost revenue, profit or data, or for special, indirect, consequential, incidental, or punitive damages however caused and regardless of the theory of liability arising out of the use of or inability to use Nabble or arising out of any content posted on Nabble even if Nabble has been advised of the possibility of such damages.</p>\r\n\r\n				<h2 oswald>Trademarks/Service Marks</h2>\r\n				<p class=\"first-para\">Nabble is a service mark of Nabble, LLC.  Nothing contained on this Web site should be construed as granting any license or right to use any Nabble trademark or service mark without the express written permission of Nabble, LLC.</p>\r\n\r\n				<h2 oswald>Choice of Law and Forum</h2>\r\n				<p class=\"first-para\">Any action related to this Agreement will be governed by California law and controlling U.S. federal law. Any legal action brought concerning this Agreement or any dispute hereunder shall be brought only in the courts of the State of California, in the City and County of San Francisco or in the federal courts located in such state and county. Both parties submit to venue and jurisdiction in these courts.</p>\r\n\r\n				<h2 oswald>Entire Agreement: Severability</h2>\r\n				<p class=\"first-para\">These Terms of Use constitute the entire agreement with respect to your access to and use of this Web site.  If any provision of these Terms and Conditions is unlawful, void or unenforceable, then that provision shall be deemed severable from the remaining provisions and shall not affect their validity and enforceability.</p>\r\n\r\n				<h2 oswald>Changes to Terms of Use</h2>\r\n				<p class=\"first-para\">This Web site and these Terms of Use may be changed by Nabble with or without notice. Please review this page regularly for changes.  Continued use of this Web site following any change constitutes your acceptance of the change.</p>\r\n\r\n				<p><strong>BY USING NABBLE, YOU ACCEPT OUR TERMS OF USE.  IF YOU DO NOT AGREE, THEN DO NOT USE NABBLE.</strong></p>\r\n			</div>\r\n		</div>\r\n		" );
+ HtmlGlobalUtils.footer(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+
+	private static String encodeNabbleEmail(String alias) {
+		return alias + "<span class=\"invisible\">[please remove </span><span class=\"invisible\">this part]</span>&#64;nabble&#46;com";
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/Terms.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,133 @@
+<%
+package global.web;
+
+import fschmidt.util.servlet.JtpContext;
+import global.HtmlGlobalUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class Terms extends HttpServlet {
+
+	public static String path(boolean back) {
+		return "/Terms.jtp";
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+%>
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<% HtmlGlobalUtils.head(request, response, "Terms of Use"); %>
+		<meta name="robots" content="noindex,nofollow"/>
+	</head>
+	<body lato>
+		<% HtmlGlobalUtils.header(request,response); %>
+		<div content center paddingTop>
+			<h1 oswald>Terms of Use</h1>
+
+			<div class="terms" style="margin: .83em 1em;text-align: justify">
+				<h2 oswald>This is a Contract and License</h2>
+				<p class="first-para">
+					By using Nabble, you accept the terms of this license and contract between you and Nabble, LLC.
+					This document was last updated on August 17, 2012.
+				</p>
+
+				<h2 oswald>Your Content in our Services</h2>
+				<p class="first-para">
+					Nabble agrees that you retain ownership of any intellectual property rights that you hold in content that you submit to us.
+					In short, what belongs to you stays yours.
+					You are responsible for your content, and you may edit or delete your content.
+					Nabble does not moderate sites and does not review, approve or endorse any content.
+				</p>
+
+				<p>
+					When you upload or otherwise submit content to our servers, you give Nabble a worldwide license to use, host, store,
+					reproduce, modify, communicate and publish such content. Your content can be public or private depending on the configuration
+					of the subdomain where it was submitted. This configuration is controlled by other users who administer that subdomain, and they can freely make your
+					content public or private at any time.
+					Nabble does not make private content public. If you own a Nabble site (subdomain) and want to keep your content private, it will stay private.
+				</p>
+
+				<p>
+					Since it is possible that a user may post offensive content or content that is harmful to minors,
+					Nabble wants to make sure that you are aware that filtering software and services are available to prevent access to such content.
+					This feature is available in Internet security software made by Symantec, Inc. and McAfee, Inc. as well as from major Internet Service Providers.
+					In order to see a list of current vendors, please use an Internet search engine to search for "parental control software" or "filtering software."
+				</p>
+
+				<h2 oswald>Privacy and Copyright Protection</h2>
+
+				<p class="first-para">
+					Nabble’s <a href="/PrivacyPolicy.jtp">privacy policy</a> explains how we treat your personal data and protect your privacy when you use our services.
+					By using our services, you agree that Nabble can use such data in accordance with our privacy policy.
+				</p>
+
+				<p>
+					Copyright owners who believe that a Nabble user has posted infringing content may provide notice in compliance with the requirements
+					of the Digital Millennium Copyright Act to Weizhen Lin, 1568 Grackle Way, Sunnyvale CA 94087, Telephone:
+					408-464-4439, email <%=encodeNabbleEmail("DMCA")%>.
+					<b>There will be no response to communications that do not involve copyright infringement.</b>
+					Communications related to privacy, violation of our Terms of Use or other abuse must be submitted by email to
+					<%=encodeNabbleEmail("abuse")%>.
+				</p>
+
+				<h2 oswald>Deletion of Content/Termination of Account</h2>
+				<p class="first-para">Nabble reserves the right to delete or block access to any content that you post if Nabble determines that your content is inappropriate for any reason, including Nabble's determination that the content infringes on another's intellectual property rights.  Nabble provides the following examples of inappropriate content as a guideline, but this list is not all inclusive and it is subject to change:</p>
+
+				<ul>
+				<li>Content that violates the laws of California or the United States;</li>
+				<li>Content that infringes intellectual property rights;</li>
+				<li>Content that violates the privacy rights of others;</li>
+				<li>Mass advertising (e.g., spam);</li>
+				<li>Viruses and the like;</li>
+				<li>Statements intended to perpetrate a fraud; and,</li>
+				<li>Illegal pornography.</li>
+				</ul>
+
+				<p>Posting of inappropriate content may lead to the termination of your account.  Repeated copyright infringement will lead to the termination of your account.</p>
+
+				<p>Nabble reserves the right to delete accounts that it determines are inactive without notice.</p>
+
+				<h2 oswald>Limited Warranty and Disclaimer of Warranties.</h2>
+				<p class="first-para">NABBLE MAKES NO EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, OR ARISING FROM A COURSE OF DEALING, TRADE USAGE OR TRADE PRACTICE.</p>
+
+				<h2 oswald>Limitation of Liability</h2>
+				<p class="first-para">In no event will Nabble be liable for any lost revenue, profit or data, or for special, indirect, consequential, incidental, or punitive damages however caused and regardless of the theory of liability arising out of the use of or inability to use Nabble or arising out of any content posted on Nabble even if Nabble has been advised of the possibility of such damages.</p>
+
+				<h2 oswald>Trademarks/Service Marks</h2>
+				<p class="first-para">Nabble is a service mark of Nabble, LLC.  Nothing contained on this Web site should be construed as granting any license or right to use any Nabble trademark or service mark without the express written permission of Nabble, LLC.</p>
+
+				<h2 oswald>Choice of Law and Forum</h2>
+				<p class="first-para">Any action related to this Agreement will be governed by California law and controlling U.S. federal law. Any legal action brought concerning this Agreement or any dispute hereunder shall be brought only in the courts of the State of California, in the City and County of San Francisco or in the federal courts located in such state and county. Both parties submit to venue and jurisdiction in these courts.</p>
+
+				<h2 oswald>Entire Agreement: Severability</h2>
+				<p class="first-para">These Terms of Use constitute the entire agreement with respect to your access to and use of this Web site.  If any provision of these Terms and Conditions is unlawful, void or unenforceable, then that provision shall be deemed severable from the remaining provisions and shall not affect their validity and enforceability.</p>
+
+				<h2 oswald>Changes to Terms of Use</h2>
+				<p class="first-para">This Web site and these Terms of Use may be changed by Nabble with or without notice. Please review this page regularly for changes.  Continued use of this Web site following any change constitutes your acceptance of the change.</p>
+
+				<p><strong>BY USING NABBLE, YOU ACCEPT OUR TERMS OF USE.  IF YOU DO NOT AGREE, THEN DO NOT USE NABBLE.</strong></p>
+			</div>
+		</div>
+		<% HtmlGlobalUtils.footer(request,response); %>
+	</body>
+</html>
+<%
+	}
+
+	private static String encodeNabbleEmail(String alias) {
+		return alias + "<span class=\"invisible\">[please remove </span><span class=\"invisible\">this part]</span>&#64;nabble&#46;com";
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/UserSites.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,116 @@
+
+package global.web;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import global.Site;
+import global.HtmlGlobalUtils;
+
+
+public final class UserSites extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		boolean isValid = true;
+		boolean isSent = false;
+		String email = request.getParameter("email");
+		if( email == null ) {
+			email = "";
+		} else {
+			email = email.trim();
+			MailAddress to = new MailAddress(email);
+			isValid = to.isValid();
+			if( isValid ) {
+				Mail mail = MailHome.newMail();
+				mail.setTo(to);
+				mail.setFrom( new MailAddress("no-reply@nabble.com","Nabble") );
+				mail.setSubject("Your Nabble Apps");
+				mail.setContent(new PlainTextContent(text(email)));
+				MailHome.getDefaultSmtpServer().send(mail);
+				isSent = true;
+			}
+		}
+
+		out.print( "\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n	<head>\r\n		" );
+ HtmlGlobalUtils.head(request, response, "Your Apps"); 
+		out.print( "\r\n	</head>\r\n	<body lato>\r\n		" );
+ HtmlGlobalUtils.header(request,response); 
+		out.print( "\r\n		<div content center paddingTop>\r\n			<h1 oswald>Your Nabble Apps</h1>\r\n			<p>\r\n				Nabble can send you a list with all apps that you currently own.\r\n			</p>\r\n			" );
+
+			if (!isSent) {
+				
+		out.print( "\r\n<form action=\"UserSites.jtp\">\r\n	<p>\r\n		<input name=\"email\" type=\"text\" value=\"" );
+		out.print( (email) );
+		out.print( "\" size=\"30\" placeholder=\"Email address\"/>\r\n		<input type=\"submit\" value=\"Submit\"/>\r\n	</p>\r\n</form>\r\n" );
+
+				if( !isValid ) {
+					
+		out.print( "<p>invalid email address</p>" );
+
+				}
+			} else {
+				
+		out.print( "<div class=\"info-message\" style=\"padding:.5em\">An email has been sent to you.</div>" );
+
+			}
+			
+		out.print( "\r\n</div>\r\n" );
+ HtmlGlobalUtils.footer(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+
+    private static String text(String email) {
+		try {
+			IndexSearcher searcher = new IndexSearcher(Site.dir());
+			Query q = new TermQuery(new Term(Site.OWNER_EMAIL_FLD,email));
+			TopDocs hits = searcher.search( q, 500 );
+			try {
+				StringWriter buf = new StringWriter();
+				PrintWriter out = new PrintWriter(buf);
+
+		out.print( "\r\nDear Nabble user,\r\n" );
+ if( hits.totalHits == 0 ){ 
+		out.print( "\r\nYou don't have any sites on Nabble.\r\n" );
+ } else { 
+		out.print( "\r\nHere are your Nabble apps:\r\n" );
+ for( ScoreDoc sd : hits.scoreDocs ) {
+Site site = new Site( searcher.doc(sd.doc) );
+
+		out.print( "\r\n" );
+		out.print( (site.url()) );
+		out.print( "\r\n" );
+
+}
+}
+
+		out.print( "\r\nRegards,\r\nThe Nabble team\r\n" );
+
+				out.flush();
+				return buf.toString();
+			} finally {
+				searcher.close();
+			}
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/UserSites.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,122 @@
+<%
+package global.web;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import global.Site;
+import global.HtmlGlobalUtils;
+
+
+public final class UserSites extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		boolean isValid = true;
+		boolean isSent = false;
+		String email = request.getParameter("email");
+		if( email == null ) {
+			email = "";
+		} else {
+			email = email.trim();
+			MailAddress to = new MailAddress(email);
+			isValid = to.isValid();
+			if( isValid ) {
+				Mail mail = MailHome.newMail();
+				mail.setTo(to);
+				mail.setFrom( new MailAddress("no-reply@nabble.com","Nabble") );
+				mail.setSubject("Your Nabble Apps");
+				mail.setContent(new PlainTextContent(text(email)));
+				MailHome.getDefaultSmtpServer().send(mail);
+				isSent = true;
+			}
+		}
+%>
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<% HtmlGlobalUtils.head(request, response, "Your Apps"); %>
+	</head>
+	<body lato>
+		<% HtmlGlobalUtils.header(request,response); %>
+		<div content center paddingTop>
+			<h1 oswald>Your Nabble Apps</h1>
+			<p>
+				Nabble can send you a list with all apps that you currently own.
+			</p>
+			<%
+			if (!isSent) {
+				%>
+				<form action="UserSites.jtp">
+					<p>
+						<input name="email" type="text" value="<%=email%>" size="30" placeholder="Email address"/>
+						<input type="submit" value="Submit"/>
+					</p>
+				</form>
+				<%
+				if( !isValid ) {
+					%><p>invalid email address</p><%
+				}
+			} else {
+				%><div class="info-message" style="padding:.5em">An email has been sent to you.</div><%
+			}
+			%>
+		</div>
+		<% HtmlGlobalUtils.footer(request,response); %>
+	</body>
+</html>
+		<%
+	}
+
+    private static String text(String email) {
+		try {
+			IndexSearcher searcher = new IndexSearcher(Site.dir());
+			Query q = new TermQuery(new Term(Site.OWNER_EMAIL_FLD,email));
+			TopDocs hits = searcher.search( q, 500 );
+			try {
+				StringWriter buf = new StringWriter();
+				PrintWriter out = new PrintWriter(buf);
+%>
+Dear Nabble user,
+<% if( hits.totalHits == 0 ){ %>
+You don't have any sites on Nabble.
+<% } else { %>
+Here are your Nabble apps:
+<% for( ScoreDoc sd : hits.scoreDocs ) {
+Site site = new Site( searcher.doc(sd.doc) );
+%>
+<%=site.url()%>
+<%
+}
+}
+%>
+Regards,
+The Nabble team
+<%
+				out.flush();
+				return buf.toString();
+			} finally {
+				searcher.close();
+			}
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/assets/font-awesome/css/font-awesome.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,2026 @@
+/*!
+ *  Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome
+ *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */
+/* FONT PATH
+ * -------------------------- */
+@font-face {
+  font-family: 'FontAwesome';
+  src: url('../fonts/fontawesome-webfont.eot?v=4.4.0');
+  src: url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'), url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'), url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'), url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'), url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');
+  font-weight: normal;
+  font-style: normal;
+}
+.fa {
+  display: inline-block;
+  font: normal normal normal 14px/1 FontAwesome;
+  font-size: inherit;
+  text-rendering: auto;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+/* makes the font 33% larger relative to the icon container */
+.fa-lg {
+  font-size: 1.33333333em;
+  line-height: 0.75em;
+  vertical-align: -15%;
+}
+.fa-2x {
+  font-size: 2em;
+}
+.fa-3x {
+  font-size: 3em;
+}
+.fa-4x {
+  font-size: 4em;
+}
+.fa-5x {
+  font-size: 5em;
+}
+.fa-fw {
+  width: 1.28571429em;
+  text-align: center;
+}
+.fa-ul {
+  padding-left: 0;
+  margin-left: 2.14285714em;
+  list-style-type: none;
+}
+.fa-ul > li {
+  position: relative;
+}
+.fa-li {
+  position: absolute;
+  left: -2.14285714em;
+  width: 2.14285714em;
+  top: 0.14285714em;
+  text-align: center;
+}
+.fa-li.fa-lg {
+  left: -1.85714286em;
+}
+.fa-border {
+  padding: .2em .25em .15em;
+  border: solid 0.08em #eeeeee;
+  border-radius: .1em;
+}
+.fa-pull-left {
+  float: left;
+}
+.fa-pull-right {
+  float: right;
+}
+.fa.fa-pull-left {
+  margin-right: .3em;
+}
+.fa.fa-pull-right {
+  margin-left: .3em;
+}
+/* Deprecated as of 4.4.0 */
+.pull-right {
+  float: right;
+}
+.pull-left {
+  float: left;
+}
+.fa.pull-left {
+  margin-right: .3em;
+}
+.fa.pull-right {
+  margin-left: .3em;
+}
+.fa-spin {
+  -webkit-animation: fa-spin 2s infinite linear;
+  animation: fa-spin 2s infinite linear;
+}
+.fa-pulse {
+  -webkit-animation: fa-spin 1s infinite steps(8);
+  animation: fa-spin 1s infinite steps(8);
+}
+@-webkit-keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+@keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(359deg);
+    transform: rotate(359deg);
+  }
+}
+.fa-rotate-90 {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
+  -webkit-transform: rotate(90deg);
+  -ms-transform: rotate(90deg);
+  transform: rotate(90deg);
+}
+.fa-rotate-180 {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
+  -webkit-transform: rotate(180deg);
+  -ms-transform: rotate(180deg);
+  transform: rotate(180deg);
+}
+.fa-rotate-270 {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
+  -webkit-transform: rotate(270deg);
+  -ms-transform: rotate(270deg);
+  transform: rotate(270deg);
+}
+.fa-flip-horizontal {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);
+  -webkit-transform: scale(-1, 1);
+  -ms-transform: scale(-1, 1);
+  transform: scale(-1, 1);
+}
+.fa-flip-vertical {
+  filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);
+  -webkit-transform: scale(1, -1);
+  -ms-transform: scale(1, -1);
+  transform: scale(1, -1);
+}
+:root .fa-rotate-90,
+:root .fa-rotate-180,
+:root .fa-rotate-270,
+:root .fa-flip-horizontal,
+:root .fa-flip-vertical {
+  filter: none;
+}
+.fa-stack {
+  position: relative;
+  display: inline-block;
+  width: 2em;
+  height: 2em;
+  line-height: 2em;
+  vertical-align: middle;
+}
+.fa-stack-1x,
+.fa-stack-2x {
+  position: absolute;
+  left: 0;
+  width: 100%;
+  text-align: center;
+}
+.fa-stack-1x {
+  line-height: inherit;
+}
+.fa-stack-2x {
+  font-size: 2em;
+}
+.fa-inverse {
+  color: #ffffff;
+}
+/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
+   readers do not read off random characters that represent icons */
+.fa-glass:before {
+  content: "\f000";
+}
+.fa-music:before {
+  content: "\f001";
+}
+.fa-search:before {
+  content: "\f002";
+}
+.fa-envelope-o:before {
+  content: "\f003";
+}
+.fa-heart:before {
+  content: "\f004";
+}
+.fa-star:before {
+  content: "\f005";
+}
+.fa-star-o:before {
+  content: "\f006";
+}
+.fa-user:before {
+  content: "\f007";
+}
+.fa-film:before {
+  content: "\f008";
+}
+.fa-th-large:before {
+  content: "\f009";
+}
+.fa-th:before {
+  content: "\f00a";
+}
+.fa-th-list:before {
+  content: "\f00b";
+}
+.fa-check:before {
+  content: "\f00c";
+}
+.fa-remove:before,
+.fa-close:before,
+.fa-times:before {
+  content: "\f00d";
+}
+.fa-search-plus:before {
+  content: "\f00e";
+}
+.fa-search-minus:before {
+  content: "\f010";
+}
+.fa-power-off:before {
+  content: "\f011";
+}
+.fa-signal:before {
+  content: "\f012";
+}
+.fa-gear:before,
+.fa-cog:before {
+  content: "\f013";
+}
+.fa-trash-o:before {
+  content: "\f014";
+}
+.fa-home:before {
+  content: "\f015";
+}
+.fa-file-o:before {
+  content: "\f016";
+}
+.fa-clock-o:before {
+  content: "\f017";
+}
+.fa-road:before {
+  content: "\f018";
+}
+.fa-download:before {
+  content: "\f019";
+}
+.fa-arrow-circle-o-down:before {
+  content: "\f01a";
+}
+.fa-arrow-circle-o-up:before {
+  content: "\f01b";
+}
+.fa-inbox:before {
+  content: "\f01c";
+}
+.fa-play-circle-o:before {
+  content: "\f01d";
+}
+.fa-rotate-right:before,
+.fa-repeat:before {
+  content: "\f01e";
+}
+.fa-refresh:before {
+  content: "\f021";
+}
+.fa-list-alt:before {
+  content: "\f022";
+}
+.fa-lock:before {
+  content: "\f023";
+}
+.fa-flag:before {
+  content: "\f024";
+}
+.fa-headphones:before {
+  content: "\f025";
+}
+.fa-volume-off:before {
+  content: "\f026";
+}
+.fa-volume-down:before {
+  content: "\f027";
+}
+.fa-volume-up:before {
+  content: "\f028";
+}
+.fa-qrcode:before {
+  content: "\f029";
+}
+.fa-barcode:before {
+  content: "\f02a";
+}
+.fa-tag:before {
+  content: "\f02b";
+}
+.fa-tags:before {
+  content: "\f02c";
+}
+.fa-book:before {
+  content: "\f02d";
+}
+.fa-bookmark:before {
+  content: "\f02e";
+}
+.fa-print:before {
+  content: "\f02f";
+}
+.fa-camera:before {
+  content: "\f030";
+}
+.fa-font:before {
+  content: "\f031";
+}
+.fa-bold:before {
+  content: "\f032";
+}
+.fa-italic:before {
+  content: "\f033";
+}
+.fa-text-height:before {
+  content: "\f034";
+}
+.fa-text-width:before {
+  content: "\f035";
+}
+.fa-align-left:before {
+  content: "\f036";
+}
+.fa-align-center:before {
+  content: "\f037";
+}
+.fa-align-right:before {
+  content: "\f038";
+}
+.fa-align-justify:before {
+  content: "\f039";
+}
+.fa-list:before {
+  content: "\f03a";
+}
+.fa-dedent:before,
+.fa-outdent:before {
+  content: "\f03b";
+}
+.fa-indent:before {
+  content: "\f03c";
+}
+.fa-video-camera:before {
+  content: "\f03d";
+}
+.fa-photo:before,
+.fa-image:before,
+.fa-picture-o:before {
+  content: "\f03e";
+}
+.fa-pencil:before {
+  content: "\f040";
+}
+.fa-map-marker:before {
+  content: "\f041";
+}
+.fa-adjust:before {
+  content: "\f042";
+}
+.fa-tint:before {
+  content: "\f043";
+}
+.fa-edit:before,
+.fa-pencil-square-o:before {
+  content: "\f044";
+}
+.fa-share-square-o:before {
+  content: "\f045";
+}
+.fa-check-square-o:before {
+  content: "\f046";
+}
+.fa-arrows:before {
+  content: "\f047";
+}
+.fa-step-backward:before {
+  content: "\f048";
+}
+.fa-fast-backward:before {
+  content: "\f049";
+}
+.fa-backward:before {
+  content: "\f04a";
+}
+.fa-play:before {
+  content: "\f04b";
+}
+.fa-pause:before {
+  content: "\f04c";
+}
+.fa-stop:before {
+  content: "\f04d";
+}
+.fa-forward:before {
+  content: "\f04e";
+}
+.fa-fast-forward:before {
+  content: "\f050";
+}
+.fa-step-forward:before {
+  content: "\f051";
+}
+.fa-eject:before {
+  content: "\f052";
+}
+.fa-chevron-left:before {
+  content: "\f053";
+}
+.fa-chevron-right:before {
+  content: "\f054";
+}
+.fa-plus-circle:before {
+  content: "\f055";
+}
+.fa-minus-circle:before {
+  content: "\f056";
+}
+.fa-times-circle:before {
+  content: "\f057";
+}
+.fa-check-circle:before {
+  content: "\f058";
+}
+.fa-question-circle:before {
+  content: "\f059";
+}
+.fa-info-circle:before {
+  content: "\f05a";
+}
+.fa-crosshairs:before {
+  content: "\f05b";
+}
+.fa-times-circle-o:before {
+  content: "\f05c";
+}
+.fa-check-circle-o:before {
+  content: "\f05d";
+}
+.fa-ban:before {
+  content: "\f05e";
+}
+.fa-arrow-left:before {
+  content: "\f060";
+}
+.fa-arrow-right:before {
+  content: "\f061";
+}
+.fa-arrow-up:before {
+  content: "\f062";
+}
+.fa-arrow-down:before {
+  content: "\f063";
+}
+.fa-mail-forward:before,
+.fa-share:before {
+  content: "\f064";
+}
+.fa-expand:before {
+  content: "\f065";
+}
+.fa-compress:before {
+  content: "\f066";
+}
+.fa-plus:before {
+  content: "\f067";
+}
+.fa-minus:before {
+  content: "\f068";
+}
+.fa-asterisk:before {
+  content: "\f069";
+}
+.fa-exclamation-circle:before {
+  content: "\f06a";
+}
+.fa-gift:before {
+  content: "\f06b";
+}
+.fa-leaf:before {
+  content: "\f06c";
+}
+.fa-fire:before {
+  content: "\f06d";
+}
+.fa-eye:before {
+  content: "\f06e";
+}
+.fa-eye-slash:before {
+  content: "\f070";
+}
+.fa-warning:before,
+.fa-exclamation-triangle:before {
+  content: "\f071";
+}
+.fa-plane:before {
+  content: "\f072";
+}
+.fa-calendar:before {
+  content: "\f073";
+}
+.fa-random:before {
+  content: "\f074";
+}
+.fa-comment:before {
+  content: "\f075";
+}
+.fa-magnet:before {
+  content: "\f076";
+}
+.fa-chevron-up:before {
+  content: "\f077";
+}
+.fa-chevron-down:before {
+  content: "\f078";
+}
+.fa-retweet:before {
+  content: "\f079";
+}
+.fa-shopping-cart:before {
+  content: "\f07a";
+}
+.fa-folder:before {
+  content: "\f07b";
+}
+.fa-folder-open:before {
+  content: "\f07c";
+}
+.fa-arrows-v:before {
+  content: "\f07d";
+}
+.fa-arrows-h:before {
+  content: "\f07e";
+}
+.fa-bar-chart-o:before,
+.fa-bar-chart:before {
+  content: "\f080";
+}
+.fa-twitter-square:before {
+  content: "\f081";
+}
+.fa-facebook-square:before {
+  content: "\f082";
+}
+.fa-camera-retro:before {
+  content: "\f083";
+}
+.fa-key:before {
+  content: "\f084";
+}
+.fa-gears:before,
+.fa-cogs:before {
+  content: "\f085";
+}
+.fa-comments:before {
+  content: "\f086";
+}
+.fa-thumbs-o-up:before {
+  content: "\f087";
+}
+.fa-thumbs-o-down:before {
+  content: "\f088";
+}
+.fa-star-half:before {
+  content: "\f089";
+}
+.fa-heart-o:before {
+  content: "\f08a";
+}
+.fa-sign-out:before {
+  content: "\f08b";
+}
+.fa-linkedin-square:before {
+  content: "\f08c";
+}
+.fa-thumb-tack:before {
+  content: "\f08d";
+}
+.fa-external-link:before {
+  content: "\f08e";
+}
+.fa-sign-in:before {
+  content: "\f090";
+}
+.fa-trophy:before {
+  content: "\f091";
+}
+.fa-github-square:before {
+  content: "\f092";
+}
+.fa-upload:before {
+  content: "\f093";
+}
+.fa-lemon-o:before {
+  content: "\f094";
+}
+.fa-phone:before {
+  content: "\f095";
+}
+.fa-square-o:before {
+  content: "\f096";
+}
+.fa-bookmark-o:before {
+  content: "\f097";
+}
+.fa-phone-square:before {
+  content: "\f098";
+}
+.fa-twitter:before {
+  content: "\f099";
+}
+.fa-facebook-f:before,
+.fa-facebook:before {
+  content: "\f09a";
+}
+.fa-github:before {
+  content: "\f09b";
+}
+.fa-unlock:before {
+  content: "\f09c";
+}
+.fa-credit-card:before {
+  content: "\f09d";
+}
+.fa-feed:before,
+.fa-rss:before {
+  content: "\f09e";
+}
+.fa-hdd-o:before {
+  content: "\f0a0";
+}
+.fa-bullhorn:before {
+  content: "\f0a1";
+}
+.fa-bell:before {
+  content: "\f0f3";
+}
+.fa-certificate:before {
+  content: "\f0a3";
+}
+.fa-hand-o-right:before {
+  content: "\f0a4";
+}
+.fa-hand-o-left:before {
+  content: "\f0a5";
+}
+.fa-hand-o-up:before {
+  content: "\f0a6";
+}
+.fa-hand-o-down:before {
+  content: "\f0a7";
+}
+.fa-arrow-circle-left:before {
+  content: "\f0a8";
+}
+.fa-arrow-circle-right:before {
+  content: "\f0a9";
+}
+.fa-arrow-circle-up:before {
+  content: "\f0aa";
+}
+.fa-arrow-circle-down:before {
+  content: "\f0ab";
+}
+.fa-globe:before {
+  content: "\f0ac";
+}
+.fa-wrench:before {
+  content: "\f0ad";
+}
+.fa-tasks:before {
+  content: "\f0ae";
+}
+.fa-filter:before {
+  content: "\f0b0";
+}
+.fa-briefcase:before {
+  content: "\f0b1";
+}
+.fa-arrows-alt:before {
+  content: "\f0b2";
+}
+.fa-group:before,
+.fa-users:before {
+  content: "\f0c0";
+}
+.fa-chain:before,
+.fa-link:before {
+  content: "\f0c1";
+}
+.fa-cloud:before {
+  content: "\f0c2";
+}
+.fa-flask:before {
+  content: "\f0c3";
+}
+.fa-cut:before,
+.fa-scissors:before {
+  content: "\f0c4";
+}
+.fa-copy:before,
+.fa-files-o:before {
+  content: "\f0c5";
+}
+.fa-paperclip:before {
+  content: "\f0c6";
+}
+.fa-save:before,
+.fa-floppy-o:before {
+  content: "\f0c7";
+}
+.fa-square:before {
+  content: "\f0c8";
+}
+.fa-navicon:before,
+.fa-reorder:before,
+.fa-bars:before {
+  content: "\f0c9";
+}
+.fa-list-ul:before {
+  content: "\f0ca";
+}
+.fa-list-ol:before {
+  content: "\f0cb";
+}
+.fa-strikethrough:before {
+  content: "\f0cc";
+}
+.fa-underline:before {
+  content: "\f0cd";
+}
+.fa-table:before {
+  content: "\f0ce";
+}
+.fa-magic:before {
+  content: "\f0d0";
+}
+.fa-truck:before {
+  content: "\f0d1";
+}
+.fa-pinterest:before {
+  content: "\f0d2";
+}
+.fa-pinterest-square:before {
+  content: "\f0d3";
+}
+.fa-google-plus-square:before {
+  content: "\f0d4";
+}
+.fa-google-plus:before {
+  content: "\f0d5";
+}
+.fa-money:before {
+  content: "\f0d6";
+}
+.fa-caret-down:before {
+  content: "\f0d7";
+}
+.fa-caret-up:before {
+  content: "\f0d8";
+}
+.fa-caret-left:before {
+  content: "\f0d9";
+}
+.fa-caret-right:before {
+  content: "\f0da";
+}
+.fa-columns:before {
+  content: "\f0db";
+}
+.fa-unsorted:before,
+.fa-sort:before {
+  content: "\f0dc";
+}
+.fa-sort-down:before,
+.fa-sort-desc:before {
+  content: "\f0dd";
+}
+.fa-sort-up:before,
+.fa-sort-asc:before {
+  content: "\f0de";
+}
+.fa-envelope:before {
+  content: "\f0e0";
+}
+.fa-linkedin:before {
+  content: "\f0e1";
+}
+.fa-rotate-left:before,
+.fa-undo:before {
+  content: "\f0e2";
+}
+.fa-legal:before,
+.fa-gavel:before {
+  content: "\f0e3";
+}
+.fa-dashboard:before,
+.fa-tachometer:before {
+  content: "\f0e4";
+}
+.fa-comment-o:before {
+  content: "\f0e5";
+}
+.fa-comments-o:before {
+  content: "\f0e6";
+}
+.fa-flash:before,
+.fa-bolt:before {
+  content: "\f0e7";
+}
+.fa-sitemap:before {
+  content: "\f0e8";
+}
+.fa-umbrella:before {
+  content: "\f0e9";
+}
+.fa-paste:before,
+.fa-clipboard:before {
+  content: "\f0ea";
+}
+.fa-lightbulb-o:before {
+  content: "\f0eb";
+}
+.fa-exchange:before {
+  content: "\f0ec";
+}
+.fa-cloud-download:before {
+  content: "\f0ed";
+}
+.fa-cloud-upload:before {
+  content: "\f0ee";
+}
+.fa-user-md:before {
+  content: "\f0f0";
+}
+.fa-stethoscope:before {
+  content: "\f0f1";
+}
+.fa-suitcase:before {
+  content: "\f0f2";
+}
+.fa-bell-o:before {
+  content: "\f0a2";
+}
+.fa-coffee:before {
+  content: "\f0f4";
+}
+.fa-cutlery:before {
+  content: "\f0f5";
+}
+.fa-file-text-o:before {
+  content: "\f0f6";
+}
+.fa-building-o:before {
+  content: "\f0f7";
+}
+.fa-hospital-o:before {
+  content: "\f0f8";
+}
+.fa-ambulance:before {
+  content: "\f0f9";
+}
+.fa-medkit:before {
+  content: "\f0fa";
+}
+.fa-fighter-jet:before {
+  content: "\f0fb";
+}
+.fa-beer:before {
+  content: "\f0fc";
+}
+.fa-h-square:before {
+  content: "\f0fd";
+}
+.fa-plus-square:before {
+  content: "\f0fe";
+}
+.fa-angle-double-left:before {
+  content: "\f100";
+}
+.fa-angle-double-right:before {
+  content: "\f101";
+}
+.fa-angle-double-up:before {
+  content: "\f102";
+}
+.fa-angle-double-down:before {
+  content: "\f103";
+}
+.fa-angle-left:before {
+  content: "\f104";
+}
+.fa-angle-right:before {
+  content: "\f105";
+}
+.fa-angle-up:before {
+  content: "\f106";
+}
+.fa-angle-down:before {
+  content: "\f107";
+}
+.fa-desktop:before {
+  content: "\f108";
+}
+.fa-laptop:before {
+  content: "\f109";
+}
+.fa-tablet:before {
+  content: "\f10a";
+}
+.fa-mobile-phone:before,
+.fa-mobile:before {
+  content: "\f10b";
+}
+.fa-circle-o:before {
+  content: "\f10c";
+}
+.fa-quote-left:before {
+  content: "\f10d";
+}
+.fa-quote-right:before {
+  content: "\f10e";
+}
+.fa-spinner:before {
+  content: "\f110";
+}
+.fa-circle:before {
+  content: "\f111";
+}
+.fa-mail-reply:before,
+.fa-reply:before {
+  content: "\f112";
+}
+.fa-github-alt:before {
+  content: "\f113";
+}
+.fa-folder-o:before {
+  content: "\f114";
+}
+.fa-folder-open-o:before {
+  content: "\f115";
+}
+.fa-smile-o:before {
+  content: "\f118";
+}
+.fa-frown-o:before {
+  content: "\f119";
+}
+.fa-meh-o:before {
+  content: "\f11a";
+}
+.fa-gamepad:before {
+  content: "\f11b";
+}
+.fa-keyboard-o:before {
+  content: "\f11c";
+}
+.fa-flag-o:before {
+  content: "\f11d";
+}
+.fa-flag-checkered:before {
+  content: "\f11e";
+}
+.fa-terminal:before {
+  content: "\f120";
+}
+.fa-code:before {
+  content: "\f121";
+}
+.fa-mail-reply-all:before,
+.fa-reply-all:before {
+  content: "\f122";
+}
+.fa-star-half-empty:before,
+.fa-star-half-full:before,
+.fa-star-half-o:before {
+  content: "\f123";
+}
+.fa-location-arrow:before {
+  content: "\f124";
+}
+.fa-crop:before {
+  content: "\f125";
+}
+.fa-code-fork:before {
+  content: "\f126";
+}
+.fa-unlink:before,
+.fa-chain-broken:before {
+  content: "\f127";
+}
+.fa-question:before {
+  content: "\f128";
+}
+.fa-info:before {
+  content: "\f129";
+}
+.fa-exclamation:before {
+  content: "\f12a";
+}
+.fa-superscript:before {
+  content: "\f12b";
+}
+.fa-subscript:before {
+  content: "\f12c";
+}
+.fa-eraser:before {
+  content: "\f12d";
+}
+.fa-puzzle-piece:before {
+  content: "\f12e";
+}
+.fa-microphone:before {
+  content: "\f130";
+}
+.fa-microphone-slash:before {
+  content: "\f131";
+}
+.fa-shield:before {
+  content: "\f132";
+}
+.fa-calendar-o:before {
+  content: "\f133";
+}
+.fa-fire-extinguisher:before {
+  content: "\f134";
+}
+.fa-rocket:before {
+  content: "\f135";
+}
+.fa-maxcdn:before {
+  content: "\f136";
+}
+.fa-chevron-circle-left:before {
+  content: "\f137";
+}
+.fa-chevron-circle-right:before {
+  content: "\f138";
+}
+.fa-chevron-circle-up:before {
+  content: "\f139";
+}
+.fa-chevron-circle-down:before {
+  content: "\f13a";
+}
+.fa-html5:before {
+  content: "\f13b";
+}
+.fa-css3:before {
+  content: "\f13c";
+}
+.fa-anchor:before {
+  content: "\f13d";
+}
+.fa-unlock-alt:before {
+  content: "\f13e";
+}
+.fa-bullseye:before {
+  content: "\f140";
+}
+.fa-ellipsis-h:before {
+  content: "\f141";
+}
+.fa-ellipsis-v:before {
+  content: "\f142";
+}
+.fa-rss-square:before {
+  content: "\f143";
+}
+.fa-play-circle:before {
+  content: "\f144";
+}
+.fa-ticket:before {
+  content: "\f145";
+}
+.fa-minus-square:before {
+  content: "\f146";
+}
+.fa-minus-square-o:before {
+  content: "\f147";
+}
+.fa-level-up:before {
+  content: "\f148";
+}
+.fa-level-down:before {
+  content: "\f149";
+}
+.fa-check-square:before {
+  content: "\f14a";
+}
+.fa-pencil-square:before {
+  content: "\f14b";
+}
+.fa-external-link-square:before {
+  content: "\f14c";
+}
+.fa-share-square:before {
+  content: "\f14d";
+}
+.fa-compass:before {
+  content: "\f14e";
+}
+.fa-toggle-down:before,
+.fa-caret-square-o-down:before {
+  content: "\f150";
+}
+.fa-toggle-up:before,
+.fa-caret-square-o-up:before {
+  content: "\f151";
+}
+.fa-toggle-right:before,
+.fa-caret-square-o-right:before {
+  content: "\f152";
+}
+.fa-euro:before,
+.fa-eur:before {
+  content: "\f153";
+}
+.fa-gbp:before {
+  content: "\f154";
+}
+.fa-dollar:before,
+.fa-usd:before {
+  content: "\f155";
+}
+.fa-rupee:before,
+.fa-inr:before {
+  content: "\f156";
+}
+.fa-cny:before,
+.fa-rmb:before,
+.fa-yen:before,
+.fa-jpy:before {
+  content: "\f157";
+}
+.fa-ruble:before,
+.fa-rouble:before,
+.fa-rub:before {
+  content: "\f158";
+}
+.fa-won:before,
+.fa-krw:before {
+  content: "\f159";
+}
+.fa-bitcoin:before,
+.fa-btc:before {
+  content: "\f15a";
+}
+.fa-file:before {
+  content: "\f15b";
+}
+.fa-file-text:before {
+  content: "\f15c";
+}
+.fa-sort-alpha-asc:before {
+  content: "\f15d";
+}
+.fa-sort-alpha-desc:before {
+  content: "\f15e";
+}
+.fa-sort-amount-asc:before {
+  content: "\f160";
+}
+.fa-sort-amount-desc:before {
+  content: "\f161";
+}
+.fa-sort-numeric-asc:before {
+  content: "\f162";
+}
+.fa-sort-numeric-desc:before {
+  content: "\f163";
+}
+.fa-thumbs-up:before {
+  content: "\f164";
+}
+.fa-thumbs-down:before {
+  content: "\f165";
+}
+.fa-youtube-square:before {
+  content: "\f166";
+}
+.fa-youtube:before {
+  content: "\f167";
+}
+.fa-xing:before {
+  content: "\f168";
+}
+.fa-xing-square:before {
+  content: "\f169";
+}
+.fa-youtube-play:before {
+  content: "\f16a";
+}
+.fa-dropbox:before {
+  content: "\f16b";
+}
+.fa-stack-overflow:before {
+  content: "\f16c";
+}
+.fa-instagram:before {
+  content: "\f16d";
+}
+.fa-flickr:before {
+  content: "\f16e";
+}
+.fa-adn:before {
+  content: "\f170";
+}
+.fa-bitbucket:before {
+  content: "\f171";
+}
+.fa-bitbucket-square:before {
+  content: "\f172";
+}
+.fa-tumblr:before {
+  content: "\f173";
+}
+.fa-tumblr-square:before {
+  content: "\f174";
+}
+.fa-long-arrow-down:before {
+  content: "\f175";
+}
+.fa-long-arrow-up:before {
+  content: "\f176";
+}
+.fa-long-arrow-left:before {
+  content: "\f177";
+}
+.fa-long-arrow-right:before {
+  content: "\f178";
+}
+.fa-apple:before {
+  content: "\f179";
+}
+.fa-windows:before {
+  content: "\f17a";
+}
+.fa-android:before {
+  content: "\f17b";
+}
+.fa-linux:before {
+  content: "\f17c";
+}
+.fa-dribbble:before {
+  content: "\f17d";
+}
+.fa-skype:before {
+  content: "\f17e";
+}
+.fa-foursquare:before {
+  content: "\f180";
+}
+.fa-trello:before {
+  content: "\f181";
+}
+.fa-female:before {
+  content: "\f182";
+}
+.fa-male:before {
+  content: "\f183";
+}
+.fa-gittip:before,
+.fa-gratipay:before {
+  content: "\f184";
+}
+.fa-sun-o:before {
+  content: "\f185";
+}
+.fa-moon-o:before {
+  content: "\f186";
+}
+.fa-archive:before {
+  content: "\f187";
+}
+.fa-bug:before {
+  content: "\f188";
+}
+.fa-vk:before {
+  content: "\f189";
+}
+.fa-weibo:before {
+  content: "\f18a";
+}
+.fa-renren:before {
+  content: "\f18b";
+}
+.fa-pagelines:before {
+  content: "\f18c";
+}
+.fa-stack-exchange:before {
+  content: "\f18d";
+}
+.fa-arrow-circle-o-right:before {
+  content: "\f18e";
+}
+.fa-arrow-circle-o-left:before {
+  content: "\f190";
+}
+.fa-toggle-left:before,
+.fa-caret-square-o-left:before {
+  content: "\f191";
+}
+.fa-dot-circle-o:before {
+  content: "\f192";
+}
+.fa-wheelchair:before {
+  content: "\f193";
+}
+.fa-vimeo-square:before {
+  content: "\f194";
+}
+.fa-turkish-lira:before,
+.fa-try:before {
+  content: "\f195";
+}
+.fa-plus-square-o:before {
+  content: "\f196";
+}
+.fa-space-shuttle:before {
+  content: "\f197";
+}
+.fa-slack:before {
+  content: "\f198";
+}
+.fa-envelope-square:before {
+  content: "\f199";
+}
+.fa-wordpress:before {
+  content: "\f19a";
+}
+.fa-openid:before {
+  content: "\f19b";
+}
+.fa-institution:before,
+.fa-bank:before,
+.fa-university:before {
+  content: "\f19c";
+}
+.fa-mortar-board:before,
+.fa-graduation-cap:before {
+  content: "\f19d";
+}
+.fa-yahoo:before {
+  content: "\f19e";
+}
+.fa-google:before {
+  content: "\f1a0";
+}
+.fa-reddit:before {
+  content: "\f1a1";
+}
+.fa-reddit-square:before {
+  content: "\f1a2";
+}
+.fa-stumbleupon-circle:before {
+  content: "\f1a3";
+}
+.fa-stumbleupon:before {
+  content: "\f1a4";
+}
+.fa-delicious:before {
+  content: "\f1a5";
+}
+.fa-digg:before {
+  content: "\f1a6";
+}
+.fa-pied-piper:before {
+  content: "\f1a7";
+}
+.fa-pied-piper-alt:before {
+  content: "\f1a8";
+}
+.fa-drupal:before {
+  content: "\f1a9";
+}
+.fa-joomla:before {
+  content: "\f1aa";
+}
+.fa-language:before {
+  content: "\f1ab";
+}
+.fa-fax:before {
+  content: "\f1ac";
+}
+.fa-building:before {
+  content: "\f1ad";
+}
+.fa-child:before {
+  content: "\f1ae";
+}
+.fa-paw:before {
+  content: "\f1b0";
+}
+.fa-spoon:before {
+  content: "\f1b1";
+}
+.fa-cube:before {
+  content: "\f1b2";
+}
+.fa-cubes:before {
+  content: "\f1b3";
+}
+.fa-behance:before {
+  content: "\f1b4";
+}
+.fa-behance-square:before {
+  content: "\f1b5";
+}
+.fa-steam:before {
+  content: "\f1b6";
+}
+.fa-steam-square:before {
+  content: "\f1b7";
+}
+.fa-recycle:before {
+  content: "\f1b8";
+}
+.fa-automobile:before,
+.fa-car:before {
+  content: "\f1b9";
+}
+.fa-cab:before,
+.fa-taxi:before {
+  content: "\f1ba";
+}
+.fa-tree:before {
+  content: "\f1bb";
+}
+.fa-spotify:before {
+  content: "\f1bc";
+}
+.fa-deviantart:before {
+  content: "\f1bd";
+}
+.fa-soundcloud:before {
+  content: "\f1be";
+}
+.fa-database:before {
+  content: "\f1c0";
+}
+.fa-file-pdf-o:before {
+  content: "\f1c1";
+}
+.fa-file-word-o:before {
+  content: "\f1c2";
+}
+.fa-file-excel-o:before {
+  content: "\f1c3";
+}
+.fa-file-powerpoint-o:before {
+  content: "\f1c4";
+}
+.fa-file-photo-o:before,
+.fa-file-picture-o:before,
+.fa-file-image-o:before {
+  content: "\f1c5";
+}
+.fa-file-zip-o:before,
+.fa-file-archive-o:before {
+  content: "\f1c6";
+}
+.fa-file-sound-o:before,
+.fa-file-audio-o:before {
+  content: "\f1c7";
+}
+.fa-file-movie-o:before,
+.fa-file-video-o:before {
+  content: "\f1c8";
+}
+.fa-file-code-o:before {
+  content: "\f1c9";
+}
+.fa-vine:before {
+  content: "\f1ca";
+}
+.fa-codepen:before {
+  content: "\f1cb";
+}
+.fa-jsfiddle:before {
+  content: "\f1cc";
+}
+.fa-life-bouy:before,
+.fa-life-buoy:before,
+.fa-life-saver:before,
+.fa-support:before,
+.fa-life-ring:before {
+  content: "\f1cd";
+}
+.fa-circle-o-notch:before {
+  content: "\f1ce";
+}
+.fa-ra:before,
+.fa-rebel:before {
+  content: "\f1d0";
+}
+.fa-ge:before,
+.fa-empire:before {
+  content: "\f1d1";
+}
+.fa-git-square:before {
+  content: "\f1d2";
+}
+.fa-git:before {
+  content: "\f1d3";
+}
+.fa-y-combinator-square:before,
+.fa-yc-square:before,
+.fa-hacker-news:before {
+  content: "\f1d4";
+}
+.fa-tencent-weibo:before {
+  content: "\f1d5";
+}
+.fa-qq:before {
+  content: "\f1d6";
+}
+.fa-wechat:before,
+.fa-weixin:before {
+  content: "\f1d7";
+}
+.fa-send:before,
+.fa-paper-plane:before {
+  content: "\f1d8";
+}
+.fa-send-o:before,
+.fa-paper-plane-o:before {
+  content: "\f1d9";
+}
+.fa-history:before {
+  content: "\f1da";
+}
+.fa-circle-thin:before {
+  content: "\f1db";
+}
+.fa-header:before {
+  content: "\f1dc";
+}
+.fa-paragraph:before {
+  content: "\f1dd";
+}
+.fa-sliders:before {
+  content: "\f1de";
+}
+.fa-share-alt:before {
+  content: "\f1e0";
+}
+.fa-share-alt-square:before {
+  content: "\f1e1";
+}
+.fa-bomb:before {
+  content: "\f1e2";
+}
+.fa-soccer-ball-o:before,
+.fa-futbol-o:before {
+  content: "\f1e3";
+}
+.fa-tty:before {
+  content: "\f1e4";
+}
+.fa-binoculars:before {
+  content: "\f1e5";
+}
+.fa-plug:before {
+  content: "\f1e6";
+}
+.fa-slideshare:before {
+  content: "\f1e7";
+}
+.fa-twitch:before {
+  content: "\f1e8";
+}
+.fa-yelp:before {
+  content: "\f1e9";
+}
+.fa-newspaper-o:before {
+  content: "\f1ea";
+}
+.fa-wifi:before {
+  content: "\f1eb";
+}
+.fa-calculator:before {
+  content: "\f1ec";
+}
+.fa-paypal:before {
+  content: "\f1ed";
+}
+.fa-google-wallet:before {
+  content: "\f1ee";
+}
+.fa-cc-visa:before {
+  content: "\f1f0";
+}
+.fa-cc-mastercard:before {
+  content: "\f1f1";
+}
+.fa-cc-discover:before {
+  content: "\f1f2";
+}
+.fa-cc-amex:before {
+  content: "\f1f3";
+}
+.fa-cc-paypal:before {
+  content: "\f1f4";
+}
+.fa-cc-stripe:before {
+  content: "\f1f5";
+}
+.fa-bell-slash:before {
+  content: "\f1f6";
+}
+.fa-bell-slash-o:before {
+  content: "\f1f7";
+}
+.fa-trash:before {
+  content: "\f1f8";
+}
+.fa-copyright:before {
+  content: "\f1f9";
+}
+.fa-at:before {
+  content: "\f1fa";
+}
+.fa-eyedropper:before {
+  content: "\f1fb";
+}
+.fa-paint-brush:before {
+  content: "\f1fc";
+}
+.fa-birthday-cake:before {
+  content: "\f1fd";
+}
+.fa-area-chart:before {
+  content: "\f1fe";
+}
+.fa-pie-chart:before {
+  content: "\f200";
+}
+.fa-line-chart:before {
+  content: "\f201";
+}
+.fa-lastfm:before {
+  content: "\f202";
+}
+.fa-lastfm-square:before {
+  content: "\f203";
+}
+.fa-toggle-off:before {
+  content: "\f204";
+}
+.fa-toggle-on:before {
+  content: "\f205";
+}
+.fa-bicycle:before {
+  content: "\f206";
+}
+.fa-bus:before {
+  content: "\f207";
+}
+.fa-ioxhost:before {
+  content: "\f208";
+}
+.fa-angellist:before {
+  content: "\f209";
+}
+.fa-cc:before {
+  content: "\f20a";
+}
+.fa-shekel:before,
+.fa-sheqel:before,
+.fa-ils:before {
+  content: "\f20b";
+}
+.fa-meanpath:before {
+  content: "\f20c";
+}
+.fa-buysellads:before {
+  content: "\f20d";
+}
+.fa-connectdevelop:before {
+  content: "\f20e";
+}
+.fa-dashcube:before {
+  content: "\f210";
+}
+.fa-forumbee:before {
+  content: "\f211";
+}
+.fa-leanpub:before {
+  content: "\f212";
+}
+.fa-sellsy:before {
+  content: "\f213";
+}
+.fa-shirtsinbulk:before {
+  content: "\f214";
+}
+.fa-simplybuilt:before {
+  content: "\f215";
+}
+.fa-skyatlas:before {
+  content: "\f216";
+}
+.fa-cart-plus:before {
+  content: "\f217";
+}
+.fa-cart-arrow-down:before {
+  content: "\f218";
+}
+.fa-diamond:before {
+  content: "\f219";
+}
+.fa-ship:before {
+  content: "\f21a";
+}
+.fa-user-secret:before {
+  content: "\f21b";
+}
+.fa-motorcycle:before {
+  content: "\f21c";
+}
+.fa-street-view:before {
+  content: "\f21d";
+}
+.fa-heartbeat:before {
+  content: "\f21e";
+}
+.fa-venus:before {
+  content: "\f221";
+}
+.fa-mars:before {
+  content: "\f222";
+}
+.fa-mercury:before {
+  content: "\f223";
+}
+.fa-intersex:before,
+.fa-transgender:before {
+  content: "\f224";
+}
+.fa-transgender-alt:before {
+  content: "\f225";
+}
+.fa-venus-double:before {
+  content: "\f226";
+}
+.fa-mars-double:before {
+  content: "\f227";
+}
+.fa-venus-mars:before {
+  content: "\f228";
+}
+.fa-mars-stroke:before {
+  content: "\f229";
+}
+.fa-mars-stroke-v:before {
+  content: "\f22a";
+}
+.fa-mars-stroke-h:before {
+  content: "\f22b";
+}
+.fa-neuter:before {
+  content: "\f22c";
+}
+.fa-genderless:before {
+  content: "\f22d";
+}
+.fa-facebook-official:before {
+  content: "\f230";
+}
+.fa-pinterest-p:before {
+  content: "\f231";
+}
+.fa-whatsapp:before {
+  content: "\f232";
+}
+.fa-server:before {
+  content: "\f233";
+}
+.fa-user-plus:before {
+  content: "\f234";
+}
+.fa-user-times:before {
+  content: "\f235";
+}
+.fa-hotel:before,
+.fa-bed:before {
+  content: "\f236";
+}
+.fa-viacoin:before {
+  content: "\f237";
+}
+.fa-train:before {
+  content: "\f238";
+}
+.fa-subway:before {
+  content: "\f239";
+}
+.fa-medium:before {
+  content: "\f23a";
+}
+.fa-yc:before,
+.fa-y-combinator:before {
+  content: "\f23b";
+}
+.fa-optin-monster:before {
+  content: "\f23c";
+}
+.fa-opencart:before {
+  content: "\f23d";
+}
+.fa-expeditedssl:before {
+  content: "\f23e";
+}
+.fa-battery-4:before,
+.fa-battery-full:before {
+  content: "\f240";
+}
+.fa-battery-3:before,
+.fa-battery-three-quarters:before {
+  content: "\f241";
+}
+.fa-battery-2:before,
+.fa-battery-half:before {
+  content: "\f242";
+}
+.fa-battery-1:before,
+.fa-battery-quarter:before {
+  content: "\f243";
+}
+.fa-battery-0:before,
+.fa-battery-empty:before {
+  content: "\f244";
+}
+.fa-mouse-pointer:before {
+  content: "\f245";
+}
+.fa-i-cursor:before {
+  content: "\f246";
+}
+.fa-object-group:before {
+  content: "\f247";
+}
+.fa-object-ungroup:before {
+  content: "\f248";
+}
+.fa-sticky-note:before {
+  content: "\f249";
+}
+.fa-sticky-note-o:before {
+  content: "\f24a";
+}
+.fa-cc-jcb:before {
+  content: "\f24b";
+}
+.fa-cc-diners-club:before {
+  content: "\f24c";
+}
+.fa-clone:before {
+  content: "\f24d";
+}
+.fa-balance-scale:before {
+  content: "\f24e";
+}
+.fa-hourglass-o:before {
+  content: "\f250";
+}
+.fa-hourglass-1:before,
+.fa-hourglass-start:before {
+  content: "\f251";
+}
+.fa-hourglass-2:before,
+.fa-hourglass-half:before {
+  content: "\f252";
+}
+.fa-hourglass-3:before,
+.fa-hourglass-end:before {
+  content: "\f253";
+}
+.fa-hourglass:before {
+  content: "\f254";
+}
+.fa-hand-grab-o:before,
+.fa-hand-rock-o:before {
+  content: "\f255";
+}
+.fa-hand-stop-o:before,
+.fa-hand-paper-o:before {
+  content: "\f256";
+}
+.fa-hand-scissors-o:before {
+  content: "\f257";
+}
+.fa-hand-lizard-o:before {
+  content: "\f258";
+}
+.fa-hand-spock-o:before {
+  content: "\f259";
+}
+.fa-hand-pointer-o:before {
+  content: "\f25a";
+}
+.fa-hand-peace-o:before {
+  content: "\f25b";
+}
+.fa-trademark:before {
+  content: "\f25c";
+}
+.fa-registered:before {
+  content: "\f25d";
+}
+.fa-creative-commons:before {
+  content: "\f25e";
+}
+.fa-gg:before {
+  content: "\f260";
+}
+.fa-gg-circle:before {
+  content: "\f261";
+}
+.fa-tripadvisor:before {
+  content: "\f262";
+}
+.fa-odnoklassniki:before {
+  content: "\f263";
+}
+.fa-odnoklassniki-square:before {
+  content: "\f264";
+}
+.fa-get-pocket:before {
+  content: "\f265";
+}
+.fa-wikipedia-w:before {
+  content: "\f266";
+}
+.fa-safari:before {
+  content: "\f267";
+}
+.fa-chrome:before {
+  content: "\f268";
+}
+.fa-firefox:before {
+  content: "\f269";
+}
+.fa-opera:before {
+  content: "\f26a";
+}
+.fa-internet-explorer:before {
+  content: "\f26b";
+}
+.fa-tv:before,
+.fa-television:before {
+  content: "\f26c";
+}
+.fa-contao:before {
+  content: "\f26d";
+}
+.fa-500px:before {
+  content: "\f26e";
+}
+.fa-amazon:before {
+  content: "\f270";
+}
+.fa-calendar-plus-o:before {
+  content: "\f271";
+}
+.fa-calendar-minus-o:before {
+  content: "\f272";
+}
+.fa-calendar-times-o:before {
+  content: "\f273";
+}
+.fa-calendar-check-o:before {
+  content: "\f274";
+}
+.fa-industry:before {
+  content: "\f275";
+}
+.fa-map-pin:before {
+  content: "\f276";
+}
+.fa-map-signs:before {
+  content: "\f277";
+}
+.fa-map-o:before {
+  content: "\f278";
+}
+.fa-map:before {
+  content: "\f279";
+}
+.fa-commenting:before {
+  content: "\f27a";
+}
+.fa-commenting-o:before {
+  content: "\f27b";
+}
+.fa-houzz:before {
+  content: "\f27c";
+}
+.fa-vimeo:before {
+  content: "\f27d";
+}
+.fa-black-tie:before {
+  content: "\f27e";
+}
+.fa-fonticons:before {
+  content: "\f280";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/assets/font-awesome/css/font-awesome.min.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,4 @@
+/*!
+ *  Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome
+ *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}
Binary file src/global/web/assets/font-awesome/fonts/FontAwesome.otf has changed
Binary file src/global/web/assets/font-awesome/fonts/fontawesome-webfont.eot has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/assets/font-awesome/fonts/fontawesome-webfont.svg	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,640 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
+<metadata></metadata>
+<defs>
+<font id="fontawesomeregular" horiz-adv-x="1536" >
+<font-face units-per-em="1792" ascent="1536" descent="-256" />
+<missing-glyph horiz-adv-x="448" />
+<glyph unicode=" "  horiz-adv-x="448" />
+<glyph unicode="&#x09;" horiz-adv-x="448" />
+<glyph unicode="&#xa0;" horiz-adv-x="448" />
+<glyph unicode="&#xa8;" horiz-adv-x="1792" />
+<glyph unicode="&#xa9;" horiz-adv-x="1792" />
+<glyph unicode="&#xae;" horiz-adv-x="1792" />
+<glyph unicode="&#xb4;" horiz-adv-x="1792" />
+<glyph unicode="&#xc6;" horiz-adv-x="1792" />
+<glyph unicode="&#xd8;" horiz-adv-x="1792" />
+<glyph unicode="&#x2000;" horiz-adv-x="768" />
+<glyph unicode="&#x2001;" horiz-adv-x="1537" />
+<glyph unicode="&#x2002;" horiz-adv-x="768" />
+<glyph unicode="&#x2003;" horiz-adv-x="1537" />
+<glyph unicode="&#x2004;" horiz-adv-x="512" />
+<glyph unicode="&#x2005;" horiz-adv-x="384" />
+<glyph unicode="&#x2006;" horiz-adv-x="256" />
+<glyph unicode="&#x2007;" horiz-adv-x="256" />
+<glyph unicode="&#x2008;" horiz-adv-x="192" />
+<glyph unicode="&#x2009;" horiz-adv-x="307" />
+<glyph unicode="&#x200a;" horiz-adv-x="85" />
+<glyph unicode="&#x202f;" horiz-adv-x="307" />
+<glyph unicode="&#x205f;" horiz-adv-x="384" />
+<glyph unicode="&#x2122;" horiz-adv-x="1792" />
+<glyph unicode="&#x221e;" horiz-adv-x="1792" />
+<glyph unicode="&#x2260;" horiz-adv-x="1792" />
+<glyph unicode="&#x25fc;" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="&#xf000;" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" />
+<glyph unicode="&#xf001;" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf002;" horiz-adv-x="1664" d="M1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5 t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
+<glyph unicode="&#xf003;" horiz-adv-x="1792" d="M1664 32v768q-32 -36 -69 -66q-268 -206 -426 -338q-51 -43 -83 -67t-86.5 -48.5t-102.5 -24.5h-1h-1q-48 0 -102.5 24.5t-86.5 48.5t-83 67q-158 132 -426 338q-37 30 -69 66v-768q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1664 1083v11v13.5t-0.5 13 t-3 12.5t-5.5 9t-9 7.5t-14 2.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5q0 -168 147 -284q193 -152 401 -317q6 -5 35 -29.5t46 -37.5t44.5 -31.5t50.5 -27.5t43 -9h1h1q20 0 43 9t50.5 27.5t44.5 31.5t46 37.5t35 29.5q208 165 401 317q54 43 100.5 115.5t46.5 131.5z M1792 1120v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf004;" horiz-adv-x="1792" d="M896 -128q-26 0 -44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600 q-18 -18 -44 -18z" />
+<glyph unicode="&#xf005;" horiz-adv-x="1664" d="M1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -21 -10.5 -35.5t-30.5 -14.5q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455 l502 -73q56 -9 56 -46z" />
+<glyph unicode="&#xf006;" horiz-adv-x="1664" d="M1137 532l306 297l-422 62l-189 382l-189 -382l-422 -62l306 -297l-73 -421l378 199l377 -199zM1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -50 -41 -50q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500 l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46z" />
+<glyph unicode="&#xf007;" horiz-adv-x="1408" d="M1408 131q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q9 0 42 -21.5t74.5 -48t108 -48t133.5 -21.5t133.5 21.5t108 48t74.5 48t42 21.5q61 0 111.5 -20t85.5 -53.5t62 -81 t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf008;" horiz-adv-x="1920" d="M384 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 320v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 704v128q0 26 -19 45t-45 19h-128 q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 -64v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM384 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45 t45 -19h128q26 0 45 19t19 45zM1792 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 704v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1792 320v128 q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 704v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19 t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1920 1248v-1344q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1344q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf009;" horiz-adv-x="1664" d="M768 512v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM768 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 512v-384q0 -52 -38 -90t-90 -38 h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf00a;" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 288v-192q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf00b;" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h960q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf00c;" horiz-adv-x="1792" d="M1671 970q0 -40 -28 -68l-724 -724l-136 -136q-28 -28 -68 -28t-68 28l-136 136l-362 362q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -295l656 657q28 28 68 28t68 -28l136 -136q28 -28 28 -68z" />
+<glyph unicode="&#xf00d;" horiz-adv-x="1408" d="M1298 214q0 -40 -28 -68l-136 -136q-28 -28 -68 -28t-68 28l-294 294l-294 -294q-28 -28 -68 -28t-68 28l-136 136q-28 28 -28 68t28 68l294 294l-294 294q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -294l294 294q28 28 68 28t68 -28l136 -136q28 -28 28 -68 t-28 -68l-294 -294l294 -294q28 -28 28 -68z" />
+<glyph unicode="&#xf00e;" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-224q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v224h-224q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h224v224q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-224h224 q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5 t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
+<glyph unicode="&#xf010;" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-576q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h576q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5z M1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z " />
+<glyph unicode="&#xf011;" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61t-298 61t-245 164t-164 245t-61 298q0 182 80.5 343t226.5 270q43 32 95.5 25t83.5 -50q32 -42 24.5 -94.5t-49.5 -84.5q-98 -74 -151.5 -181t-53.5 -228q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5 t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5q0 121 -53.5 228t-151.5 181q-42 32 -49.5 84.5t24.5 94.5q31 43 84 50t95 -25q146 -109 226.5 -270t80.5 -343zM896 1408v-640q0 -52 -38 -90t-90 -38t-90 38t-38 90v640q0 52 38 90t90 38t90 -38t38 -90z" />
+<glyph unicode="&#xf012;" horiz-adv-x="1792" d="M256 96v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 224v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 480v-576q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1408 864v-960q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1376v-1472q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1472q0 14 9 23t23 9h192q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf013;" d="M1024 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1536 749v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108q-44 -23 -91 -38 q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5v222q0 12 8 23t19 13 l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10q129 -119 165 -170q7 -8 7 -22 q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5z" />
+<glyph unicode="&#xf014;" horiz-adv-x="1408" d="M512 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM768 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1024 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1152 76v948h-896v-948q0 -22 7 -40.5t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832 q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf015;" horiz-adv-x="1664" d="M1408 544v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6zM1631 613l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5t11 21.5 l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5z" />
+<glyph unicode="&#xf016;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z " />
+<glyph unicode="&#xf017;" d="M896 992v-448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf018;" horiz-adv-x="1920" d="M1111 540v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20zM1870 73q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256 q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116z" />
+<glyph unicode="&#xf019;" horiz-adv-x="1664" d="M1280 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 416v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h465l135 -136 q58 -56 136 -56t136 56l136 136h464q40 0 68 -28t28 -68zM1339 985q17 -41 -14 -70l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39z" />
+<glyph unicode="&#xf01a;" d="M1120 608q0 -12 -10 -24l-319 -319q-11 -9 -23 -9t-23 9l-320 320q-15 16 -7 35q8 20 30 20h192v352q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-352h192q14 0 23 -9t9 -23zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273 t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01b;" d="M1118 660q-8 -20 -30 -20h-192v-352q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v352h-192q-14 0 -23 9t-9 23q0 12 10 24l319 319q11 9 23 9t23 -9l320 -320q15 -16 7 -35zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198 t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01c;" d="M1023 576h316q-1 3 -2.5 8t-2.5 8l-212 496h-708l-212 -496q-1 -2 -2.5 -8t-2.5 -8h316l95 -192h320zM1536 546v-482q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v482q0 62 25 123l238 552q10 25 36.5 42t52.5 17h832q26 0 52.5 -17t36.5 -42l238 -552 q25 -61 25 -123z" />
+<glyph unicode="&#xf01d;" d="M1184 640q0 -37 -32 -55l-544 -320q-15 -9 -32 -9q-16 0 -32 8q-32 19 -32 56v640q0 37 32 56q33 18 64 -1l544 -320q32 -18 32 -55zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01e;" d="M1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l138 138q-148 137 -349 137q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5q119 0 225 52t179 147q7 10 23 12q14 0 25 -9 l137 -138q9 -8 9.5 -20.5t-7.5 -22.5q-109 -132 -264 -204.5t-327 -72.5q-156 0 -298 61t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q147 0 284.5 -55.5t244.5 -156.5l130 129q29 31 70 14q39 -17 39 -59z" />
+<glyph unicode="&#xf021;" d="M1511 480q0 -5 -1 -7q-64 -268 -268 -434.5t-478 -166.5q-146 0 -282.5 55t-243.5 157l-129 -129q-19 -19 -45 -19t-45 19t-19 45v448q0 26 19 45t45 19h448q26 0 45 -19t19 -45t-19 -45l-137 -137q71 -66 161 -102t187 -36q134 0 250 65t186 179q11 17 53 117 q8 23 30 23h192q13 0 22.5 -9.5t9.5 -22.5zM1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-26 0 -45 19t-19 45t19 45l138 138q-148 137 -349 137q-134 0 -250 -65t-186 -179q-11 -17 -53 -117q-8 -23 -30 -23h-199q-13 0 -22.5 9.5t-9.5 22.5v7q65 268 270 434.5t480 166.5 q146 0 284 -55.5t245 -156.5l130 129q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf022;" horiz-adv-x="1792" d="M384 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M384 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1536 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5z M1536 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5zM1536 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5 t9.5 -22.5zM1664 160v832q0 13 -9.5 22.5t-22.5 9.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1792 1248v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47 t47 -113z" />
+<glyph unicode="&#xf023;" horiz-adv-x="1152" d="M320 768h512v192q0 106 -75 181t-181 75t-181 -75t-75 -181v-192zM1152 672v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v192q0 184 132 316t316 132t316 -132t132 -316v-192h32q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf024;" horiz-adv-x="1792" d="M320 1280q0 -72 -64 -110v-1266q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v1266q-64 38 -64 110q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -25 -12.5 -38.5t-39.5 -27.5q-215 -116 -369 -116q-61 0 -123.5 22t-108.5 48 t-115.5 48t-142.5 22q-192 0 -464 -146q-17 -9 -33 -9q-26 0 -45 19t-19 45v742q0 32 31 55q21 14 79 43q236 120 421 120q107 0 200 -29t219 -88q38 -19 88 -19q54 0 117.5 21t110 47t88 47t54.5 21q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf025;" horiz-adv-x="1664" d="M1664 650q0 -166 -60 -314l-20 -49l-185 -33q-22 -83 -90.5 -136.5t-156.5 -53.5v-32q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-32q71 0 130 -35.5t93 -95.5l68 12q29 95 29 193q0 148 -88 279t-236.5 209t-315.5 78 t-315.5 -78t-236.5 -209t-88 -279q0 -98 29 -193l68 -12q34 60 93 95.5t130 35.5v32q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v32q-88 0 -156.5 53.5t-90.5 136.5l-185 33l-20 49q-60 148 -60 314q0 151 67 291t179 242.5 t266 163.5t320 61t320 -61t266 -163.5t179 -242.5t67 -291z" />
+<glyph unicode="&#xf026;" horiz-adv-x="768" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf027;" horiz-adv-x="1152" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142z" />
+<glyph unicode="&#xf028;" horiz-adv-x="1664" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142zM1408 640q0 -153 -85 -282.5t-225 -188.5q-13 -5 -25 -5q-27 0 -46 19t-19 45q0 39 39 59q56 29 76 44q74 54 115.5 135.5t41.5 173.5t-41.5 173.5 t-115.5 135.5q-20 15 -76 44q-39 20 -39 59q0 26 19 45t45 19q13 0 26 -5q140 -59 225 -188.5t85 -282.5zM1664 640q0 -230 -127 -422.5t-338 -283.5q-13 -5 -26 -5q-26 0 -45 19t-19 45q0 36 39 59q7 4 22.5 10.5t22.5 10.5q46 25 82 51q123 91 192 227t69 289t-69 289 t-192 227q-36 26 -82 51q-7 4 -22.5 10.5t-22.5 10.5q-39 23 -39 59q0 26 19 45t45 19q13 0 26 -5q211 -91 338 -283.5t127 -422.5z" />
+<glyph unicode="&#xf029;" horiz-adv-x="1408" d="M384 384v-128h-128v128h128zM384 1152v-128h-128v128h128zM1152 1152v-128h-128v128h128zM128 129h384v383h-384v-383zM128 896h384v384h-384v-384zM896 896h384v384h-384v-384zM640 640v-640h-640v640h640zM1152 128v-128h-128v128h128zM1408 128v-128h-128v128h128z M1408 640v-384h-384v128h-128v-384h-128v640h384v-128h128v128h128zM640 1408v-640h-640v640h640zM1408 1408v-640h-640v640h640z" />
+<glyph unicode="&#xf02a;" horiz-adv-x="1792" d="M63 0h-63v1408h63v-1408zM126 1h-32v1407h32v-1407zM220 1h-31v1407h31v-1407zM377 1h-31v1407h31v-1407zM534 1h-62v1407h62v-1407zM660 1h-31v1407h31v-1407zM723 1h-31v1407h31v-1407zM786 1h-31v1407h31v-1407zM943 1h-63v1407h63v-1407zM1100 1h-63v1407h63v-1407z M1226 1h-63v1407h63v-1407zM1352 1h-63v1407h63v-1407zM1446 1h-63v1407h63v-1407zM1635 1h-94v1407h94v-1407zM1698 1h-32v1407h32v-1407zM1792 0h-63v1408h63v-1408z" />
+<glyph unicode="&#xf02b;" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91z" />
+<glyph unicode="&#xf02c;" horiz-adv-x="1920" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91zM1899 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-36 0 -59 14t-53 45l470 470q37 37 37 90q0 52 -37 91l-715 714q-38 38 -102 64.5t-117 26.5h224q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91z" />
+<glyph unicode="&#xf02d;" horiz-adv-x="1664" d="M1639 1058q40 -57 18 -129l-275 -906q-19 -64 -76.5 -107.5t-122.5 -43.5h-923q-77 0 -148.5 53.5t-99.5 131.5q-24 67 -2 127q0 4 3 27t4 37q1 8 -3 21.5t-3 19.5q2 11 8 21t16.5 23.5t16.5 23.5q23 38 45 91.5t30 91.5q3 10 0.5 30t-0.5 28q3 11 17 28t17 23 q21 36 42 92t25 90q1 9 -2.5 32t0.5 28q4 13 22 30.5t22 22.5q19 26 42.5 84.5t27.5 96.5q1 8 -3 25.5t-2 26.5q2 8 9 18t18 23t17 21q8 12 16.5 30.5t15 35t16 36t19.5 32t26.5 23.5t36 11.5t47.5 -5.5l-1 -3q38 9 51 9h761q74 0 114 -56t18 -130l-274 -906 q-36 -119 -71.5 -153.5t-128.5 -34.5h-869q-27 0 -38 -15q-11 -16 -1 -43q24 -70 144 -70h923q29 0 56 15.5t35 41.5l300 987q7 22 5 57q38 -15 59 -43zM575 1056q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5 t-16.5 -22.5zM492 800q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5z" />
+<glyph unicode="&#xf02e;" horiz-adv-x="1280" d="M1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
+<glyph unicode="&#xf02f;" horiz-adv-x="1664" d="M384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1536 576q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 576v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68 v160h-224q-13 0 -22.5 9.5t-9.5 22.5v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5z" />
+<glyph unicode="&#xf030;" horiz-adv-x="1920" d="M960 864q119 0 203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5t84.5 203.5t203.5 84.5zM1664 1280q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181v896q0 106 75 181t181 75h224l51 136 q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224zM960 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf031;" horiz-adv-x="1664" d="M725 977l-170 -450q33 0 136.5 -2t160.5 -2q19 0 57 2q-87 253 -184 452zM0 -128l2 79q23 7 56 12.5t57 10.5t49.5 14.5t44.5 29t31 50.5l237 616l280 724h75h53q8 -14 11 -21l205 -480q33 -78 106 -257.5t114 -274.5q15 -34 58 -144.5t72 -168.5q20 -45 35 -57 q19 -15 88 -29.5t84 -20.5q6 -38 6 -57q0 -4 -0.5 -13t-0.5 -13q-63 0 -190 8t-191 8q-76 0 -215 -7t-178 -8q0 43 4 78l131 28q1 0 12.5 2.5t15.5 3.5t14.5 4.5t15 6.5t11 8t9 11t2.5 14q0 16 -31 96.5t-72 177.5t-42 100l-450 2q-26 -58 -76.5 -195.5t-50.5 -162.5 q0 -22 14 -37.5t43.5 -24.5t48.5 -13.5t57 -8.5t41 -4q1 -19 1 -58q0 -9 -2 -27q-58 0 -174.5 10t-174.5 10q-8 0 -26.5 -4t-21.5 -4q-80 -14 -188 -14z" />
+<glyph unicode="&#xf032;" horiz-adv-x="1408" d="M555 15q74 -32 140 -32q376 0 376 335q0 114 -41 180q-27 44 -61.5 74t-67.5 46.5t-80.5 25t-84 10.5t-94.5 2q-73 0 -101 -10q0 -53 -0.5 -159t-0.5 -158q0 -8 -1 -67.5t-0.5 -96.5t4.5 -83.5t12 -66.5zM541 761q42 -7 109 -7q82 0 143 13t110 44.5t74.5 89.5t25.5 142 q0 70 -29 122.5t-79 82t-108 43.5t-124 14q-50 0 -130 -13q0 -50 4 -151t4 -152q0 -27 -0.5 -80t-0.5 -79q0 -46 1 -69zM0 -128l2 94q15 4 85 16t106 27q7 12 12.5 27t8.5 33.5t5.5 32.5t3 37.5t0.5 34v35.5v30q0 982 -22 1025q-4 8 -22 14.5t-44.5 11t-49.5 7t-48.5 4.5 t-30.5 3l-4 83q98 2 340 11.5t373 9.5q23 0 68.5 -0.5t67.5 -0.5q70 0 136.5 -13t128.5 -42t108 -71t74 -104.5t28 -137.5q0 -52 -16.5 -95.5t-39 -72t-64.5 -57.5t-73 -45t-84 -40q154 -35 256.5 -134t102.5 -248q0 -100 -35 -179.5t-93.5 -130.5t-138 -85.5t-163.5 -48.5 t-176 -14q-44 0 -132 3t-132 3q-106 0 -307 -11t-231 -12z" />
+<glyph unicode="&#xf033;" horiz-adv-x="1024" d="M0 -126l17 85q6 2 81.5 21.5t111.5 37.5q28 35 41 101q1 7 62 289t114 543.5t52 296.5v25q-24 13 -54.5 18.5t-69.5 8t-58 5.5l19 103q33 -2 120 -6.5t149.5 -7t120.5 -2.5q48 0 98.5 2.5t121 7t98.5 6.5q-5 -39 -19 -89q-30 -10 -101.5 -28.5t-108.5 -33.5 q-8 -19 -14 -42.5t-9 -40t-7.5 -45.5t-6.5 -42q-27 -148 -87.5 -419.5t-77.5 -355.5q-2 -9 -13 -58t-20 -90t-16 -83.5t-6 -57.5l1 -18q17 -4 185 -31q-3 -44 -16 -99q-11 0 -32.5 -1.5t-32.5 -1.5q-29 0 -87 10t-86 10q-138 2 -206 2q-51 0 -143 -9t-121 -11z" />
+<glyph unicode="&#xf034;" horiz-adv-x="1792" d="M1744 128q33 0 42 -18.5t-11 -44.5l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80zM81 1407l54 -27q12 -5 211 -5q44 0 132 2 t132 2q36 0 107.5 -0.5t107.5 -0.5h293q6 0 21 -0.5t20.5 0t16 3t17.5 9t15 17.5l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 48t-14.5 73.5t-7.5 35.5q-6 8 -12 12.5t-15.5 6t-13 2.5t-18 0.5t-16.5 -0.5 q-17 0 -66.5 0.5t-74.5 0.5t-64 -2t-71 -6q-9 -81 -8 -136q0 -94 2 -388t2 -455q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27 q19 42 19 383q0 101 -3 303t-3 303v117q0 2 0.5 15.5t0.5 25t-1 25.5t-3 24t-5 14q-11 12 -162 12q-33 0 -93 -12t-80 -26q-19 -13 -34 -72.5t-31.5 -111t-42.5 -53.5q-42 26 -56 44v383z" />
+<glyph unicode="&#xf035;" d="M81 1407l54 -27q12 -5 211 -5q44 0 132 2t132 2q70 0 246.5 1t304.5 0.5t247 -4.5q33 -1 56 31l42 1q4 0 14 -0.5t14 -0.5q2 -112 2 -336q0 -80 -5 -109q-39 -14 -68 -18q-25 44 -54 128q-3 9 -11 47.5t-15 73.5t-7 36q-10 13 -27 19q-5 2 -66 2q-30 0 -93 1t-103 1 t-94 -2t-96 -7q-9 -81 -8 -136l1 -152v52q0 -55 1 -154t1.5 -180t0.5 -153q0 -16 -2.5 -71.5t0 -91.5t12.5 -69q40 -21 124 -42.5t120 -37.5q5 -40 5 -50q0 -14 -3 -29l-34 -1q-76 -2 -218 8t-207 10q-50 0 -151 -9t-152 -9q-3 51 -3 52v9q17 27 61.5 43t98.5 29t78 27 q7 16 11.5 74t6 145.5t1.5 155t-0.5 153.5t-0.5 89q0 7 -2.5 21.5t-2.5 22.5q0 7 0.5 44t1 73t0 76.5t-3 67.5t-6.5 32q-11 12 -162 12q-41 0 -163 -13.5t-138 -24.5q-19 -12 -34 -71.5t-31.5 -111.5t-42.5 -54q-42 26 -56 44v383zM1310 125q12 0 42 -19.5t57.5 -41.5 t59.5 -49t36 -30q26 -21 26 -49t-26 -49q-4 -3 -36 -30t-59.5 -49t-57.5 -41.5t-42 -19.5q-13 0 -20.5 10.5t-10 28.5t-2.5 33.5t1.5 33t1.5 19.5h-1024q0 -2 1.5 -19.5t1.5 -33t-2.5 -33.5t-10 -28.5t-20.5 -10.5q-12 0 -42 19.5t-57.5 41.5t-59.5 49t-36 30q-26 21 -26 49 t26 49q4 3 36 30t59.5 49t57.5 41.5t42 19.5q13 0 20.5 -10.5t10 -28.5t2.5 -33.5t-1.5 -33t-1.5 -19.5h1024q0 2 -1.5 19.5t-1.5 33t2.5 33.5t10 28.5t20.5 10.5z" />
+<glyph unicode="&#xf036;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf037;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45t-45 -19 h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf038;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf039;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf03a;" horiz-adv-x="1792" d="M256 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM256 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5 t9.5 -22.5zM256 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344 q13 0 22.5 -9.5t9.5 -22.5zM256 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192 q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03b;" horiz-adv-x="1792" d="M384 992v-576q0 -13 -9.5 -22.5t-22.5 -9.5q-14 0 -23 9l-288 288q-9 9 -9 23t9 23l288 288q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03c;" horiz-adv-x="1792" d="M352 704q0 -14 -9 -23l-288 -288q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v576q0 13 9.5 22.5t22.5 9.5q14 0 23 -9l288 -288q9 -9 9 -23zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03d;" horiz-adv-x="1792" d="M1792 1184v-1088q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-403 403v-166q0 -119 -84.5 -203.5t-203.5 -84.5h-704q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h704q119 0 203.5 -84.5t84.5 -203.5v-165l403 402q18 19 45 19q12 0 25 -5 q39 -17 39 -59z" />
+<glyph unicode="&#xf03e;" horiz-adv-x="1920" d="M640 960q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 576v-448h-1408v192l320 320l160 -160l512 512zM1760 1280h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216 q0 13 -9.5 22.5t-22.5 9.5zM1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf040;" d="M363 0l91 91l-235 235l-91 -91v-107h128v-128h107zM886 928q0 22 -22 22q-10 0 -17 -7l-542 -542q-7 -7 -7 -17q0 -22 22 -22q10 0 17 7l542 542q7 7 7 17zM832 1120l416 -416l-832 -832h-416v416zM1515 1024q0 -53 -37 -90l-166 -166l-416 416l166 165q36 38 90 38 q53 0 91 -38l235 -234q37 -39 37 -91z" />
+<glyph unicode="&#xf041;" horiz-adv-x="1024" d="M768 896q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1024 896q0 -109 -33 -179l-364 -774q-16 -33 -47.5 -52t-67.5 -19t-67.5 19t-46.5 52l-365 774q-33 70 -33 179q0 212 150 362t362 150t362 -150t150 -362z" />
+<glyph unicode="&#xf042;" d="M768 96v1088q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf043;" horiz-adv-x="1024" d="M512 384q0 36 -20 69q-1 1 -15.5 22.5t-25.5 38t-25 44t-21 50.5q-4 16 -21 16t-21 -16q-7 -23 -21 -50.5t-25 -44t-25.5 -38t-15.5 -22.5q-20 -33 -20 -69q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 512q0 -212 -150 -362t-362 -150t-362 150t-150 362 q0 145 81 275q6 9 62.5 90.5t101 151t99.5 178t83 201.5q9 30 34 47t51 17t51.5 -17t33.5 -47q28 -93 83 -201.5t99.5 -178t101 -151t62.5 -90.5q81 -127 81 -275z" />
+<glyph unicode="&#xf044;" horiz-adv-x="1792" d="M888 352l116 116l-152 152l-116 -116v-56h96v-96h56zM1328 1072q-16 16 -33 -1l-350 -350q-17 -17 -1 -33t33 1l350 350q17 17 1 33zM1408 478v-190q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-14 -14 -32 -8q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v126q0 13 9 22l64 64q15 15 35 7t20 -29zM1312 1216l288 -288l-672 -672h-288v288zM1756 1084l-92 -92 l-288 288l92 92q28 28 68 28t68 -28l152 -152q28 -28 28 -68t-28 -68z" />
+<glyph unicode="&#xf045;" horiz-adv-x="1664" d="M1408 547v-259q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h255v0q13 0 22.5 -9.5t9.5 -22.5q0 -27 -26 -32q-77 -26 -133 -60q-10 -4 -16 -4h-112q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832 q66 0 113 47t47 113v214q0 19 18 29q28 13 54 37q16 16 35 8q21 -9 21 -29zM1645 1043l-384 -384q-18 -19 -45 -19q-12 0 -25 5q-39 17 -39 59v192h-160q-323 0 -438 -131q-119 -137 -74 -473q3 -23 -20 -34q-8 -2 -12 -2q-16 0 -26 13q-10 14 -21 31t-39.5 68.5t-49.5 99.5 t-38.5 114t-17.5 122q0 49 3.5 91t14 90t28 88t47 81.5t68.5 74t94.5 61.5t124.5 48.5t159.5 30.5t196.5 11h160v192q0 42 39 59q13 5 25 5q26 0 45 -19l384 -384q19 -19 19 -45t-19 -45z" />
+<glyph unicode="&#xf046;" horiz-adv-x="1664" d="M1408 606v-318q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-10 -10 -23 -10q-3 0 -9 2q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832 q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v254q0 13 9 22l64 64q10 10 23 10q6 0 12 -3q20 -8 20 -29zM1639 1095l-814 -814q-24 -24 -57 -24t-57 24l-430 430q-24 24 -24 57t24 57l110 110q24 24 57 24t57 -24l263 -263l647 647q24 24 57 24t57 -24l110 -110 q24 -24 24 -57t-24 -57z" />
+<glyph unicode="&#xf047;" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-384v-384h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v384h-384v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45 t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h384v384h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-384h384v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf048;" horiz-adv-x="1024" d="M979 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19z" />
+<glyph unicode="&#xf049;" horiz-adv-x="1792" d="M1747 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710 q19 19 32 13t13 -32v-710q4 11 13 19z" />
+<glyph unicode="&#xf04a;" horiz-adv-x="1664" d="M1619 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-8 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-19 19 -19 45t19 45l710 710q19 19 32 13t13 -32v-710q5 11 13 19z" />
+<glyph unicode="&#xf04b;" horiz-adv-x="1408" d="M1384 609l-1328 -738q-23 -13 -39.5 -3t-16.5 36v1472q0 26 16.5 36t39.5 -3l1328 -738q23 -13 23 -31t-23 -31z" />
+<glyph unicode="&#xf04c;" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45zM640 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf04d;" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf04e;" horiz-adv-x="1664" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q19 -19 19 -45t-19 -45l-710 -710q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
+<glyph unicode="&#xf050;" horiz-adv-x="1792" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710 q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
+<glyph unicode="&#xf051;" horiz-adv-x="1024" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19z" />
+<glyph unicode="&#xf052;" horiz-adv-x="1538" d="M14 557l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13t13 32zM1473 0h-1408q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19z" />
+<glyph unicode="&#xf053;" horiz-adv-x="1280" d="M1171 1235l-531 -531l531 -531q19 -19 19 -45t-19 -45l-166 -166q-19 -19 -45 -19t-45 19l-742 742q-19 19 -19 45t19 45l742 742q19 19 45 19t45 -19l166 -166q19 -19 19 -45t-19 -45z" />
+<glyph unicode="&#xf054;" horiz-adv-x="1280" d="M1107 659l-742 -742q-19 -19 -45 -19t-45 19l-166 166q-19 19 -19 45t19 45l531 531l-531 531q-19 19 -19 45t19 45l166 166q19 19 45 19t45 -19l742 -742q19 -19 19 -45t-19 -45z" />
+<glyph unicode="&#xf055;" d="M1216 576v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf056;" d="M1216 576v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" />
+<glyph unicode="&#xf057;" d="M1149 414q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45q0 -27 19 -46l90 -90q19 -19 46 -19 q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19l90 90q19 19 19 46zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf058;" d="M1284 802q0 28 -18 46l-91 90q-19 19 -45 19t-45 -19l-408 -407l-226 226q-19 19 -45 19t-45 -19l-91 -90q-18 -18 -18 -46q0 -27 18 -45l362 -362q19 -19 45 -19q27 0 46 19l543 543q18 18 18 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf059;" d="M896 160v192q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h192q14 0 23 9t9 23zM1152 832q0 88 -55.5 163t-138.5 116t-170 41q-243 0 -371 -213q-15 -24 8 -42l132 -100q7 -6 19 -6q16 0 25 12q53 68 86 92q34 24 86 24q48 0 85.5 -26t37.5 -59 q0 -38 -20 -61t-68 -45q-63 -28 -115.5 -86.5t-52.5 -125.5v-36q0 -14 9 -23t23 -9h192q14 0 23 9t9 23q0 19 21.5 49.5t54.5 49.5q32 18 49 28.5t46 35t44.5 48t28 60.5t12.5 81zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05a;" d="M1024 160v160q0 14 -9 23t-23 9h-96v512q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h96v-320h-96q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23t23 -9h448q14 0 23 9t9 23zM896 1056v160q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-160q0 -14 9 -23 t23 -9h192q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05b;" d="M1197 512h-109q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h109q-32 108 -112.5 188.5t-188.5 112.5v-109q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v109q-108 -32 -188.5 -112.5t-112.5 -188.5h109q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-109 q32 -108 112.5 -188.5t188.5 -112.5v109q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-109q108 32 188.5 112.5t112.5 188.5zM1536 704v-128q0 -26 -19 -45t-45 -19h-143q-37 -161 -154.5 -278.5t-278.5 -154.5v-143q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v143 q-161 37 -278.5 154.5t-154.5 278.5h-143q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h143q37 161 154.5 278.5t278.5 154.5v143q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-143q161 -37 278.5 -154.5t154.5 -278.5h143q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf05c;" d="M1097 457l-146 -146q-10 -10 -23 -10t-23 10l-137 137l-137 -137q-10 -10 -23 -10t-23 10l-146 146q-10 10 -10 23t10 23l137 137l-137 137q-10 10 -10 23t10 23l146 146q10 10 23 10t23 -10l137 -137l137 137q10 10 23 10t23 -10l146 -146q10 -10 10 -23t-10 -23 l-137 -137l137 -137q10 -10 10 -23t-10 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5 t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05d;" d="M1171 723l-422 -422q-19 -19 -45 -19t-45 19l-294 294q-19 19 -19 45t19 45l102 102q19 19 45 19t45 -19l147 -147l275 275q19 19 45 19t45 -19l102 -102q19 -19 19 -45t-19 -45zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198 t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05e;" d="M1312 643q0 161 -87 295l-754 -753q137 -89 297 -89q111 0 211.5 43.5t173.5 116.5t116 174.5t43 212.5zM313 344l755 754q-135 91 -300 91q-148 0 -273 -73t-198 -199t-73 -274q0 -162 89 -299zM1536 643q0 -157 -61 -300t-163.5 -246t-245 -164t-298.5 -61t-298.5 61 t-245 164t-163.5 246t-61 300t61 299.5t163.5 245.5t245 164t298.5 61t298.5 -61t245 -164t163.5 -245.5t61 -299.5z" />
+<glyph unicode="&#xf060;" d="M1536 640v-128q0 -53 -32.5 -90.5t-84.5 -37.5h-704l293 -294q38 -36 38 -90t-38 -90l-75 -76q-37 -37 -90 -37q-52 0 -91 37l-651 652q-37 37 -37 90q0 52 37 91l651 650q38 38 91 38q52 0 90 -38l75 -74q38 -38 38 -91t-38 -91l-293 -293h704q52 0 84.5 -37.5 t32.5 -90.5z" />
+<glyph unicode="&#xf061;" d="M1472 576q0 -54 -37 -91l-651 -651q-39 -37 -91 -37q-51 0 -90 37l-75 75q-38 38 -38 91t38 91l293 293h-704q-52 0 -84.5 37.5t-32.5 90.5v128q0 53 32.5 90.5t84.5 37.5h704l-293 294q-38 36 -38 90t38 90l75 75q38 38 90 38q53 0 91 -38l651 -651q37 -35 37 -90z" />
+<glyph unicode="&#xf062;" horiz-adv-x="1664" d="M1611 565q0 -51 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-294 293v-704q0 -52 -37.5 -84.5t-90.5 -32.5h-128q-53 0 -90.5 32.5t-37.5 84.5v704l-294 -293q-36 -38 -90 -38t-90 38l-75 75q-38 38 -38 90q0 53 38 91l651 651q35 37 90 37q54 0 91 -37l651 -651 q37 -39 37 -91z" />
+<glyph unicode="&#xf063;" horiz-adv-x="1664" d="M1611 704q0 -53 -37 -90l-651 -652q-39 -37 -91 -37q-53 0 -90 37l-651 652q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l294 -294v704q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-704l294 294q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
+<glyph unicode="&#xf064;" horiz-adv-x="1792" d="M1792 896q0 -26 -19 -45l-512 -512q-19 -19 -45 -19t-45 19t-19 45v256h-224q-98 0 -175.5 -6t-154 -21.5t-133 -42.5t-105.5 -69.5t-80 -101t-48.5 -138.5t-17.5 -181q0 -55 5 -123q0 -6 2.5 -23.5t2.5 -26.5q0 -15 -8.5 -25t-23.5 -10q-16 0 -28 17q-7 9 -13 22 t-13.5 30t-10.5 24q-127 285 -127 451q0 199 53 333q162 403 875 403h224v256q0 26 19 45t45 19t45 -19l512 -512q19 -19 19 -45z" />
+<glyph unicode="&#xf065;" d="M755 480q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23zM1536 1344v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332 q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf066;" d="M768 576v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45zM1523 1248q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45 t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23z" />
+<glyph unicode="&#xf067;" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-416v-416q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v416h-416q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h416v416q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-416h416q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf068;" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-1216q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h1216q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf069;" horiz-adv-x="1664" d="M1482 486q46 -26 59.5 -77.5t-12.5 -97.5l-64 -110q-26 -46 -77.5 -59.5t-97.5 12.5l-266 153v-307q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v307l-266 -153q-46 -26 -97.5 -12.5t-77.5 59.5l-64 110q-26 46 -12.5 97.5t59.5 77.5l266 154l-266 154 q-46 26 -59.5 77.5t12.5 97.5l64 110q26 46 77.5 59.5t97.5 -12.5l266 -153v307q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-307l266 153q46 26 97.5 12.5t77.5 -59.5l64 -110q26 -46 12.5 -97.5t-59.5 -77.5l-266 -154z" />
+<glyph unicode="&#xf06a;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM896 161v190q0 14 -9 23.5t-22 9.5h-192q-13 0 -23 -10t-10 -23v-190q0 -13 10 -23t23 -10h192 q13 0 22 9.5t9 23.5zM894 505l18 621q0 12 -10 18q-10 8 -24 8h-220q-14 0 -24 -8q-10 -6 -10 -18l17 -621q0 -10 10 -17.5t24 -7.5h185q14 0 23.5 7.5t10.5 17.5z" />
+<glyph unicode="&#xf06b;" d="M928 180v56v468v192h-320v-192v-468v-56q0 -25 18 -38.5t46 -13.5h192q28 0 46 13.5t18 38.5zM472 1024h195l-126 161q-26 31 -69 31q-40 0 -68 -28t-28 -68t28 -68t68 -28zM1160 1120q0 40 -28 68t-68 28q-43 0 -69 -31l-125 -161h194q40 0 68 28t28 68zM1536 864v-320 q0 -14 -9 -23t-23 -9h-96v-416q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v416h-96q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h440q-93 0 -158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5q107 0 168 -77l128 -165l128 165q61 77 168 77q93 0 158.5 -65.5t65.5 -158.5 t-65.5 -158.5t-158.5 -65.5h440q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf06c;" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19q-172 0 -318 -49.5t-259.5 -134t-235.5 -219.5q-19 -21 -19 -45q0 -26 19 -45t45 -19q24 0 45 19q27 24 74 71t67 66q137 124 268.5 176t313.5 52q26 0 45 19t19 45zM1792 1030q0 -95 -20 -193q-46 -224 -184.5 -383t-357.5 -268 q-214 -108 -438 -108q-148 0 -286 47q-15 5 -88 42t-96 37q-16 0 -39.5 -32t-45 -70t-52.5 -70t-60 -32q-30 0 -51 11t-31 24t-27 42q-2 4 -6 11t-5.5 10t-3 9.5t-1.5 13.5q0 35 31 73.5t68 65.5t68 56t31 48q0 4 -14 38t-16 44q-9 51 -9 104q0 115 43.5 220t119 184.5 t170.5 139t204 95.5q55 18 145 25.5t179.5 9t178.5 6t163.5 24t113.5 56.5l29.5 29.5t29.5 28t27 20t36.5 16t43.5 4.5q39 0 70.5 -46t47.5 -112t24 -124t8 -96z" />
+<glyph unicode="&#xf06d;" horiz-adv-x="1408" d="M1408 -160v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1152 896q0 -78 -24.5 -144t-64 -112.5t-87.5 -88t-96 -77.5t-87.5 -72t-64 -81.5t-24.5 -96.5q0 -96 67 -224l-4 1l1 -1 q-90 41 -160 83t-138.5 100t-113.5 122.5t-72.5 150.5t-27.5 184q0 78 24.5 144t64 112.5t87.5 88t96 77.5t87.5 72t64 81.5t24.5 96.5q0 94 -66 224l3 -1l-1 1q90 -41 160 -83t138.5 -100t113.5 -122.5t72.5 -150.5t27.5 -184z" />
+<glyph unicode="&#xf06e;" horiz-adv-x="1792" d="M1664 576q-152 236 -381 353q61 -104 61 -225q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 121 61 225q-229 -117 -381 -353q133 -205 333.5 -326.5t434.5 -121.5t434.5 121.5t333.5 326.5zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5 t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1792 576q0 -34 -20 -69q-140 -230 -376.5 -368.5t-499.5 -138.5t-499.5 139t-376.5 368q-20 35 -20 69t20 69q140 229 376.5 368t499.5 139t499.5 -139t376.5 -368q20 -35 20 -69z" />
+<glyph unicode="&#xf070;" horiz-adv-x="1792" d="M555 201l78 141q-87 63 -136 159t-49 203q0 121 61 225q-229 -117 -381 -353q167 -258 427 -375zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1307 1151q0 -7 -1 -9 q-105 -188 -315 -566t-316 -567l-49 -89q-10 -16 -28 -16q-12 0 -134 70q-16 10 -16 28q0 12 44 87q-143 65 -263.5 173t-208.5 245q-20 31 -20 69t20 69q153 235 380 371t496 136q89 0 180 -17l54 97q10 16 28 16q5 0 18 -6t31 -15.5t33 -18.5t31.5 -18.5t19.5 -11.5 q16 -10 16 -27zM1344 704q0 -139 -79 -253.5t-209 -164.5l280 502q8 -45 8 -84zM1792 576q0 -35 -20 -69q-39 -64 -109 -145q-150 -172 -347.5 -267t-419.5 -95l74 132q212 18 392.5 137t301.5 307q-115 179 -282 294l63 112q95 -64 182.5 -153t144.5 -184q20 -34 20 -69z " />
+<glyph unicode="&#xf071;" horiz-adv-x="1792" d="M1024 161v190q0 14 -9.5 23.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -23.5v-190q0 -14 9.5 -23.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 23.5zM1022 535l18 459q0 12 -10 19q-13 11 -24 11h-220q-11 0 -24 -11q-10 -7 -10 -21l17 -457q0 -10 10 -16.5t24 -6.5h185 q14 0 23.5 6.5t10.5 16.5zM1008 1469l768 -1408q35 -63 -2 -126q-17 -29 -46.5 -46t-63.5 -17h-1536q-34 0 -63.5 17t-46.5 46q-37 63 -2 126l768 1408q17 31 47 49t65 18t65 -18t47 -49z" />
+<glyph unicode="&#xf072;" horiz-adv-x="1408" d="M1376 1376q44 -52 12 -148t-108 -172l-161 -161l160 -696q5 -19 -12 -33l-128 -96q-7 -6 -19 -6q-4 0 -7 1q-15 3 -21 16l-279 508l-259 -259l53 -194q5 -17 -8 -31l-96 -96q-9 -9 -23 -9h-2q-15 2 -24 13l-189 252l-252 189q-11 7 -13 23q-1 13 9 25l96 97q9 9 23 9 q6 0 8 -1l194 -53l259 259l-508 279q-14 8 -17 24q-2 16 9 27l128 128q14 13 30 8l665 -159l160 160q76 76 172 108t148 -12z" />
+<glyph unicode="&#xf073;" horiz-adv-x="1664" d="M128 -128h288v288h-288v-288zM480 -128h320v288h-320v-288zM128 224h288v320h-288v-320zM480 224h320v320h-320v-320zM128 608h288v288h-288v-288zM864 -128h320v288h-320v-288zM480 608h320v288h-320v-288zM1248 -128h288v288h-288v-288zM864 224h320v320h-320v-320z M512 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1248 224h288v320h-288v-320zM864 608h320v288h-320v-288zM1248 608h288v288h-288v-288zM1280 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64 q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47 h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf074;" horiz-adv-x="1792" d="M666 1055q-60 -92 -137 -273q-22 45 -37 72.5t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q250 0 410 -225zM1792 256q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192q-32 0 -85 -0.5t-81 -1t-73 1 t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1792 1152q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5 v192h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111 t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
+<glyph unicode="&#xf075;" horiz-adv-x="1792" d="M1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281 q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="&#xf076;" d="M1536 704v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5t-98.5 362v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384 q26 0 45 -19t19 -45zM512 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45zM1536 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf077;" horiz-adv-x="1792" d="M1683 205l-166 -165q-19 -19 -45 -19t-45 19l-531 531l-531 -531q-19 -19 -45 -19t-45 19l-166 165q-19 19 -19 45.5t19 45.5l742 741q19 19 45 19t45 -19l742 -741q19 -19 19 -45.5t-19 -45.5z" />
+<glyph unicode="&#xf078;" horiz-adv-x="1792" d="M1683 728l-742 -741q-19 -19 -45 -19t-45 19l-742 741q-19 19 -19 45.5t19 45.5l166 165q19 19 45 19t45 -19l531 -531l531 531q19 19 45 19t45 -19l166 -165q19 -19 19 -45.5t-19 -45.5z" />
+<glyph unicode="&#xf079;" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " />
+<glyph unicode="&#xf07a;" horiz-adv-x="1664" d="M640 0q0 -52 -38 -90t-90 -38t-90 38t-38 90t38 90t90 38t90 -38t38 -90zM1536 0q0 -52 -38 -90t-90 -38t-90 38t-38 90t38 90t90 38t90 -38t38 -90zM1664 1088v-512q0 -24 -16.5 -42.5t-40.5 -21.5l-1044 -122q13 -60 13 -70q0 -16 -24 -64h920q26 0 45 -19t19 -45 t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 11 8 31.5t16 36t21.5 40t15.5 29.5l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t19.5 -15.5t13 -24.5t8 -26t5.5 -29.5t4.5 -26h1201q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf07b;" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf07c;" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf07d;" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf07e;" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf080;" horiz-adv-x="2048" d="M640 640v-512h-256v512h256zM1024 1152v-1024h-256v1024h256zM2048 0v-128h-2048v1536h128v-1408h1920zM1408 896v-768h-256v768h256zM1792 1280v-1152h-256v1152h256z" />
+<glyph unicode="&#xf081;" d="M1280 926q-56 -25 -121 -34q68 40 93 117q-65 -38 -134 -51q-61 66 -153 66q-87 0 -148.5 -61.5t-61.5 -148.5q0 -29 5 -48q-129 7 -242 65t-192 155q-29 -50 -29 -106q0 -114 91 -175q-47 1 -100 26v-2q0 -75 50 -133.5t123 -72.5q-29 -8 -51 -8q-13 0 -39 4 q21 -63 74.5 -104t121.5 -42q-116 -90 -261 -90q-26 0 -50 3q148 -94 322 -94q112 0 210 35.5t168 95t120.5 137t75 162t24.5 168.5q0 18 -1 27q63 45 105 109zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5 t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf082;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-188v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-532q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960z" />
+<glyph unicode="&#xf083;" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf084;" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" />
+<glyph unicode="&#xf085;" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" />
+<glyph unicode="&#xf086;" horiz-adv-x="1792" d="M1408 768q0 -139 -94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224 q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257zM1792 512q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7 q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230z" />
+<glyph unicode="&#xf087;" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 768q0 51 -39 89.5t-89 38.5h-352q0 58 48 159.5t48 160.5q0 98 -32 145t-128 47q-26 -26 -38 -85t-30.5 -125.5t-59.5 -109.5q-22 -23 -77 -91q-4 -5 -23 -30t-31.5 -41t-34.5 -42.5 t-40 -44t-38.5 -35.5t-40 -27t-35.5 -9h-32v-640h32q13 0 31.5 -3t33 -6.5t38 -11t35 -11.5t35.5 -12.5t29 -10.5q211 -73 342 -73h121q192 0 192 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5q32 1 53.5 47t21.5 81zM1536 769 q0 -89 -49 -163q9 -33 9 -69q0 -77 -38 -144q3 -21 3 -43q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5h-36h-93q-96 0 -189.5 22.5t-216.5 65.5q-116 40 -138 40h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h274q36 24 137 155q58 75 107 128 q24 25 35.5 85.5t30.5 126.5t62 108q39 37 90 37q84 0 151 -32.5t102 -101.5t35 -186q0 -93 -48 -192h176q104 0 180 -76t76 -179z" />
+<glyph unicode="&#xf088;" d="M256 1088q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 512q0 35 -21.5 81t-53.5 47q15 17 25 47.5t10 55.5q0 69 -53 119q18 32 18 69t-17.5 73.5t-47.5 52.5q5 30 5 56q0 85 -49 126t-136 41h-128q-131 0 -342 -73q-5 -2 -29 -10.5 t-35.5 -12.5t-35 -11.5t-38 -11t-33 -6.5t-31.5 -3h-32v-640h32q16 0 35.5 -9t40 -27t38.5 -35.5t40 -44t34.5 -42.5t31.5 -41t23 -30q55 -68 77 -91q41 -43 59.5 -109.5t30.5 -125.5t38 -85q96 0 128 47t32 145q0 59 -48 160.5t-48 159.5h352q50 0 89 38.5t39 89.5z M1536 511q0 -103 -76 -179t-180 -76h-176q48 -99 48 -192q0 -118 -35 -186q-35 -69 -102 -101.5t-151 -32.5q-51 0 -90 37q-34 33 -54 82t-25.5 90.5t-17.5 84.5t-31 64q-48 50 -107 127q-101 131 -137 155h-274q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5 h288q22 0 138 40q128 44 223 66t200 22h112q140 0 226.5 -79t85.5 -216v-5q60 -77 60 -178q0 -22 -3 -43q38 -67 38 -144q0 -36 -9 -69q49 -74 49 -163z" />
+<glyph unicode="&#xf089;" horiz-adv-x="896" d="M832 1504v-1339l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41z" />
+<glyph unicode="&#xf08a;" horiz-adv-x="1792" d="M1664 940q0 81 -21.5 143t-55 98.5t-81.5 59.5t-94 31t-98 8t-112 -25.5t-110.5 -64t-86.5 -72t-60 -61.5q-18 -22 -49 -22t-49 22q-24 28 -60 61.5t-86.5 72t-110.5 64t-112 25.5t-98 -8t-94 -31t-81.5 -59.5t-55 -98.5t-21.5 -143q0 -168 187 -355l581 -560l580 559 q188 188 188 356zM1792 940q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5 q224 0 351 -124t127 -344z" />
+<glyph unicode="&#xf08b;" horiz-adv-x="1664" d="M640 96q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h320q13 0 22.5 -9.5t9.5 -22.5q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-66 0 -113 -47t-47 -113v-704 q0 -66 47 -113t113 -47h288h11h13t11.5 -1t11.5 -3t8 -5.5t7 -9t2 -13.5zM1568 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45z" />
+<glyph unicode="&#xf08c;" d="M237 122h231v694h-231v-694zM483 1030q-1 52 -36 86t-93 34t-94.5 -34t-36.5 -86q0 -51 35.5 -85.5t92.5 -34.5h1q59 0 95 34.5t36 85.5zM1068 122h231v398q0 154 -73 233t-193 79q-136 0 -209 -117h2v101h-231q3 -66 0 -694h231v388q0 38 7 56q15 35 45 59.5t74 24.5 q116 0 116 -157v-371zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf08d;" horiz-adv-x="1152" d="M480 672v448q0 14 -9 23t-23 9t-23 -9t-9 -23v-448q0 -14 9 -23t23 -9t23 9t9 23zM1152 320q0 -26 -19 -45t-45 -19h-429l-51 -483q-2 -12 -10.5 -20.5t-20.5 -8.5h-1q-27 0 -32 27l-76 485h-404q-26 0 -45 19t-19 45q0 123 78.5 221.5t177.5 98.5v512q-52 0 -90 38 t-38 90t38 90t90 38h640q52 0 90 -38t38 -90t-38 -90t-90 -38v-512q99 0 177.5 -98.5t78.5 -221.5z" />
+<glyph unicode="&#xf08e;" horiz-adv-x="1792" d="M1408 608v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1792 1472v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf090;" d="M1184 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45zM1536 992v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5 q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf091;" horiz-adv-x="1664" d="M458 653q-74 162 -74 371h-256v-96q0 -78 94.5 -162t235.5 -113zM1536 928v96h-256q0 -209 -74 -371q141 29 235.5 113t94.5 162zM1664 1056v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91 t97.5 -37q75 0 133.5 -45.5t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143v128q0 40 28 68t68 28h288v96 q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf092;" d="M394 184q-8 -9 -20 3q-13 11 -4 19q8 9 20 -3q12 -11 4 -19zM352 245q9 -12 0 -19q-8 -6 -17 7t0 18q9 7 17 -6zM291 305q-5 -7 -13 -2q-10 5 -7 12q3 5 13 2q10 -5 7 -12zM322 271q-6 -7 -16 3q-9 11 -2 16q6 6 16 -3q9 -11 2 -16zM451 159q-4 -12 -19 -6q-17 4 -13 15 t19 7q16 -5 13 -16zM514 154q0 -11 -16 -11q-17 -2 -17 11q0 11 16 11q17 2 17 -11zM572 164q2 -10 -14 -14t-18 8t14 15q16 2 18 -9zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-224q-16 0 -24.5 1t-19.5 5t-16 14.5t-5 27.5v239q0 97 -52 142q57 6 102.5 18t94 39 t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103 q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -103t0.5 -68q0 -22 -11 -33.5t-22 -13t-33 -1.5 h-224q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf093;" horiz-adv-x="1664" d="M1280 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 288v-320q0 -40 -28 -68t-68 -28h-1472q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h427q21 -56 70.5 -92 t110.5 -36h256q61 0 110.5 36t70.5 92h427q40 0 68 -28t28 -68zM1339 936q-17 -40 -59 -40h-256v-448q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-42 0 -59 40q-17 39 14 69l448 448q18 19 45 19t45 -19l448 -448q31 -30 14 -69z" />
+<glyph unicode="&#xf094;" d="M1407 710q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5 q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275zM1535 712q0 -165 -70 -327.5t-196 -288t-281 -180.5q-124 -44 -326 -44 q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5 q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5z" />
+<glyph unicode="&#xf095;" horiz-adv-x="1408" d="M1408 296q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5 q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235t235 -174 q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21z" />
+<glyph unicode="&#xf096;" horiz-adv-x="1408" d="M1120 1280h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf097;" horiz-adv-x="1280" d="M1152 1280h-1024v-1242l423 406l89 85l89 -85l423 -406v1242zM1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289 q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
+<glyph unicode="&#xf098;" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf099;" horiz-adv-x="1664" d="M1620 1128q-67 -98 -162 -167q1 -14 1 -42q0 -130 -38 -259.5t-115.5 -248.5t-184.5 -210.5t-258 -146t-323 -54.5q-271 0 -496 145q35 -4 78 -4q225 0 401 138q-105 2 -188 64.5t-114 159.5q33 -5 61 -5q43 0 85 11q-112 23 -185.5 111.5t-73.5 205.5v4q68 -38 146 -41 q-66 44 -105 115t-39 154q0 88 44 163q121 -149 294.5 -238.5t371.5 -99.5q-8 38 -8 74q0 134 94.5 228.5t228.5 94.5q140 0 236 -102q109 21 205 78q-37 -115 -142 -178q93 10 186 50z" />
+<glyph unicode="&#xf09a;" horiz-adv-x="1024" d="M959 1524v-264h-157q-86 0 -116 -36t-30 -108v-189h293l-39 -296h-254v-759h-306v759h-255v296h255v218q0 186 104 288.5t277 102.5q147 0 228 -12z" />
+<glyph unicode="&#xf09b;" d="M1536 640q0 -251 -146.5 -451.5t-378.5 -277.5q-27 -5 -39.5 7t-12.5 30v211q0 97 -52 142q57 6 102.5 18t94 39t81 66.5t53 105t20.5 150.5q0 121 -79 206q37 91 -8 204q-28 9 -81 -11t-92 -44l-38 -24q-93 26 -192 26t-192 -26q-16 11 -42.5 27t-83.5 38.5t-86 13.5 q-44 -113 -7 -204q-79 -85 -79 -206q0 -85 20.5 -150t52.5 -105t80.5 -67t94 -39t102.5 -18q-40 -36 -49 -103q-21 -10 -45 -15t-57 -5t-65.5 21.5t-55.5 62.5q-19 32 -48.5 52t-49.5 24l-20 3q-21 0 -29 -4.5t-5 -11.5t9 -14t13 -12l7 -5q22 -10 43.5 -38t31.5 -51l10 -23 q13 -38 44 -61.5t67 -30t69.5 -7t55.5 3.5l23 4q0 -38 0.5 -89t0.5 -54q0 -18 -13 -30t-40 -7q-232 77 -378.5 277.5t-146.5 451.5q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf09c;" horiz-adv-x="1664" d="M1664 960v-256q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5 t316.5 -131.5t131.5 -316.5z" />
+<glyph unicode="&#xf09d;" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" />
+<glyph unicode="&#xf09e;" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" />
+<glyph unicode="&#xf0a0;" d="M1040 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1296 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1408 160v320q0 13 -9.5 22.5t-22.5 9.5 h-1216q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM1536 480v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113v320q0 25 16 75 l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75z" />
+<glyph unicode="&#xf0a1;" horiz-adv-x="1792" d="M1664 896q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5 t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384zM1536 292v954q-394 -302 -768 -343v-270q377 -42 768 -341z" />
+<glyph unicode="&#xf0a2;" horiz-adv-x="1792" d="M912 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM246 128h1300q-266 300 -266 832q0 51 -24 105t-69 103t-121.5 80.5t-169.5 31.5t-169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -532 -266 -832z M1728 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q190 -28 307 -158.5 t117 -282.5q0 -139 19.5 -260t50 -206t74.5 -158.5t85 -119.5t91 -88z" />
+<glyph unicode="&#xf0a3;" d="M1376 640l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53q-41 -12 -70 19q-31 29 -19 70 l53 186l-188 48q-40 10 -52 51q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70 l-53 -186l188 -48q40 -10 52 -51q10 -42 -20 -70z" />
+<glyph unicode="&#xf0a4;" horiz-adv-x="1792" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 768q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106 q-69 -57 -140 -57h-32v-640h32q72 0 167 -32t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90zM1792 769q0 -105 -75.5 -181t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5 t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179z" />
+<glyph unicode="&#xf0a5;" horiz-adv-x="1792" d="M1376 128h32v640h-32q-35 0 -67.5 12t-62.5 37t-50 46t-49 54q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576 q-50 0 -89 -38.5t-39 -89.5q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32zM1664 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45 t45 -19t45 19t19 45zM1792 768v-640q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181q0 103 76 179t180 76h374q-22 60 -22 128 q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf0a6;" d="M1280 -64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 700q0 189 -167 189q-26 0 -56 -5q-16 30 -52.5 47.5t-73.5 17.5t-69 -18q-50 53 -119 53q-25 0 -55.5 -10t-47.5 -25v331q0 52 -38 90t-90 38q-51 0 -89.5 -39t-38.5 -89v-576 q-20 0 -48.5 15t-55 33t-68 33t-84.5 15q-67 0 -97.5 -44.5t-30.5 -115.5q0 -24 139 -90q44 -24 65 -37q64 -40 145 -112q81 -71 106 -101q57 -69 57 -140v-32h640v32q0 72 32 167t64 193.5t32 179.5zM1536 705q0 -133 -69 -322q-59 -164 -59 -223v-288q0 -53 -37.5 -90.5 t-90.5 -37.5h-640q-53 0 -90.5 37.5t-37.5 90.5v288q0 10 -4.5 21.5t-14 23.5t-18 22.5t-22.5 24t-21.5 20.5t-21.5 19t-17 14q-74 65 -129 100q-21 13 -62 33t-72 37t-63 40.5t-49.5 55t-17.5 69.5q0 125 67 206.5t189 81.5q68 0 128 -22v374q0 104 76 180t179 76 q105 0 181 -75.5t76 -180.5v-169q62 -4 119 -37q21 3 43 3q101 0 178 -60q139 1 219.5 -85t80.5 -227z" />
+<glyph unicode="&#xf0a7;" d="M1408 576q0 84 -32 183t-64 194t-32 167v32h-640v-32q0 -35 -12 -67.5t-37 -62.5t-46 -50t-54 -49q-9 -8 -14 -12q-81 -72 -145 -112q-22 -14 -68 -38q-3 -1 -22.5 -10.5t-36 -18.5t-35.5 -20t-30.5 -21.5t-11.5 -18.5q0 -71 30.5 -115.5t97.5 -44.5q43 0 84.5 15t68 33 t55 33t48.5 15v-576q0 -50 38.5 -89t89.5 -39q52 0 90 38t38 90v331q46 -35 103 -35q69 0 119 53q32 -18 69 -18t73.5 17.5t52.5 47.5q24 -4 56 -4q85 0 126 48.5t41 135.5zM1280 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 580 q0 -142 -77.5 -230t-217.5 -87l-5 1q-76 -61 -178 -61q-22 0 -43 3q-54 -30 -119 -37v-169q0 -105 -76 -180.5t-181 -75.5q-103 0 -179 76t-76 180v374q-54 -22 -128 -22q-121 0 -188.5 81.5t-67.5 206.5q0 38 17.5 69.5t49.5 55t63 40.5t72 37t62 33q55 35 129 100 q3 2 17 14t21.5 19t21.5 20.5t22.5 24t18 22.5t14 23.5t4.5 21.5v288q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-288q0 -59 59 -223q69 -190 69 -317z" />
+<glyph unicode="&#xf0a8;" d="M1280 576v128q0 26 -19 45t-45 19h-502l189 189q19 19 19 45t-19 45l-91 91q-18 18 -45 18t-45 -18l-362 -362l-91 -91q-18 -18 -18 -45t18 -45l91 -91l362 -362q18 -18 45 -18t45 18l91 91q18 18 18 45t-18 45l-189 189h502q26 0 45 19t19 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0a9;" d="M1285 640q0 27 -18 45l-91 91l-362 362q-18 18 -45 18t-45 -18l-91 -91q-18 -18 -18 -45t18 -45l189 -189h-502q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h502l-189 -189q-19 -19 -19 -45t19 -45l91 -91q18 -18 45 -18t45 18l362 362l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0aa;" d="M1284 641q0 27 -18 45l-362 362l-91 91q-18 18 -45 18t-45 -18l-91 -91l-362 -362q-18 -18 -18 -45t18 -45l91 -91q18 -18 45 -18t45 18l189 189v-502q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v502l189 -189q19 -19 45 -19t45 19l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0ab;" d="M1284 639q0 27 -18 45l-91 91q-18 18 -45 18t-45 -18l-189 -189v502q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-502l-189 189q-19 19 -45 19t-45 -19l-91 -91q-18 -18 -18 -45t18 -45l362 -362l91 -91q18 -18 45 -18t45 18l91 91l362 362q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0ac;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1042 887q-2 -1 -9.5 -9.5t-13.5 -9.5q2 0 4.5 5t5 11t3.5 7q6 7 22 15q14 6 52 12q34 8 51 -11 q-2 2 9.5 13t14.5 12q3 2 15 4.5t15 7.5l2 22q-12 -1 -17.5 7t-6.5 21q0 -2 -6 -8q0 7 -4.5 8t-11.5 -1t-9 -1q-10 3 -15 7.5t-8 16.5t-4 15q-2 5 -9.5 10.5t-9.5 10.5q-1 2 -2.5 5.5t-3 6.5t-4 5.5t-5.5 2.5t-7 -5t-7.5 -10t-4.5 -5q-3 2 -6 1.5t-4.5 -1t-4.5 -3t-5 -3.5 q-3 -2 -8.5 -3t-8.5 -2q15 5 -1 11q-10 4 -16 3q9 4 7.5 12t-8.5 14h5q-1 4 -8.5 8.5t-17.5 8.5t-13 6q-8 5 -34 9.5t-33 0.5q-5 -6 -4.5 -10.5t4 -14t3.5 -12.5q1 -6 -5.5 -13t-6.5 -12q0 -7 14 -15.5t10 -21.5q-3 -8 -16 -16t-16 -12q-5 -8 -1.5 -18.5t10.5 -16.5 q2 -2 1.5 -4t-3.5 -4.5t-5.5 -4t-6.5 -3.5l-3 -2q-11 -5 -20.5 6t-13.5 26q-7 25 -16 30q-23 8 -29 -1q-5 13 -41 26q-25 9 -58 4q6 1 0 15q-7 15 -19 12q3 6 4 17.5t1 13.5q3 13 12 23q1 1 7 8.5t9.5 13.5t0.5 6q35 -4 50 11q5 5 11.5 17t10.5 17q9 6 14 5.5t14.5 -5.5 t14.5 -5q14 -1 15.5 11t-7.5 20q12 -1 3 17q-5 7 -8 9q-12 4 -27 -5q-8 -4 2 -8q-1 1 -9.5 -10.5t-16.5 -17.5t-16 5q-1 1 -5.5 13.5t-9.5 13.5q-8 0 -16 -15q3 8 -11 15t-24 8q19 12 -8 27q-7 4 -20.5 5t-19.5 -4q-5 -7 -5.5 -11.5t5 -8t10.5 -5.5t11.5 -4t8.5 -3 q14 -10 8 -14q-2 -1 -8.5 -3.5t-11.5 -4.5t-6 -4q-3 -4 0 -14t-2 -14q-5 5 -9 17.5t-7 16.5q7 -9 -25 -6l-10 1q-4 0 -16 -2t-20.5 -1t-13.5 8q-4 8 0 20q1 4 4 2q-4 3 -11 9.5t-10 8.5q-46 -15 -94 -41q6 -1 12 1q5 2 13 6.5t10 5.5q34 14 42 7l5 5q14 -16 20 -25 q-7 4 -30 1q-20 -6 -22 -12q7 -12 5 -18q-4 3 -11.5 10t-14.5 11t-15 5q-16 0 -22 -1q-146 -80 -235 -222q7 -7 12 -8q4 -1 5 -9t2.5 -11t11.5 3q9 -8 3 -19q1 1 44 -27q19 -17 21 -21q3 -11 -10 -18q-1 2 -9 9t-9 4q-3 -5 0.5 -18.5t10.5 -12.5q-7 0 -9.5 -16t-2.5 -35.5 t-1 -23.5l2 -1q-3 -12 5.5 -34.5t21.5 -19.5q-13 -3 20 -43q6 -8 8 -9q3 -2 12 -7.5t15 -10t10 -10.5q4 -5 10 -22.5t14 -23.5q-2 -6 9.5 -20t10.5 -23q-1 0 -2.5 -1t-2.5 -1q3 -7 15.5 -14t15.5 -13q1 -3 2 -10t3 -11t8 -2q2 20 -24 62q-15 25 -17 29q-3 5 -5.5 15.5 t-4.5 14.5q2 0 6 -1.5t8.5 -3.5t7.5 -4t2 -3q-3 -7 2 -17.5t12 -18.5t17 -19t12 -13q6 -6 14 -19.5t0 -13.5q9 0 20 -10t17 -20q5 -8 8 -26t5 -24q2 -7 8.5 -13.5t12.5 -9.5l16 -8t13 -7q5 -2 18.5 -10.5t21.5 -11.5q10 -4 16 -4t14.5 2.5t13.5 3.5q15 2 29 -15t21 -21 q36 -19 55 -11q-2 -1 0.5 -7.5t8 -15.5t9 -14.5t5.5 -8.5q5 -6 18 -15t18 -15q6 4 7 9q-3 -8 7 -20t18 -10q14 3 14 32q-31 -15 -49 18q0 1 -2.5 5.5t-4 8.5t-2.5 8.5t0 7.5t5 3q9 0 10 3.5t-2 12.5t-4 13q-1 8 -11 20t-12 15q-5 -9 -16 -8t-16 9q0 -1 -1.5 -5.5t-1.5 -6.5 q-13 0 -15 1q1 3 2.5 17.5t3.5 22.5q1 4 5.5 12t7.5 14.5t4 12.5t-4.5 9.5t-17.5 2.5q-19 -1 -26 -20q-1 -3 -3 -10.5t-5 -11.5t-9 -7q-7 -3 -24 -2t-24 5q-13 8 -22.5 29t-9.5 37q0 10 2.5 26.5t3 25t-5.5 24.5q3 2 9 9.5t10 10.5q2 1 4.5 1.5t4.5 0t4 1.5t3 6q-1 1 -4 3 q-3 3 -4 3q7 -3 28.5 1.5t27.5 -1.5q15 -11 22 2q0 1 -2.5 9.5t-0.5 13.5q5 -27 29 -9q3 -3 15.5 -5t17.5 -5q3 -2 7 -5.5t5.5 -4.5t5 0.5t8.5 6.5q10 -14 12 -24q11 -40 19 -44q7 -3 11 -2t4.5 9.5t0 14t-1.5 12.5l-1 8v18l-1 8q-15 3 -18.5 12t1.5 18.5t15 18.5q1 1 8 3.5 t15.5 6.5t12.5 8q21 19 15 35q7 0 11 9q-1 0 -5 3t-7.5 5t-4.5 2q9 5 2 16q5 3 7.5 11t7.5 10q9 -12 21 -2q7 8 1 16q5 7 20.5 10.5t18.5 9.5q7 -2 8 2t1 12t3 12q4 5 15 9t13 5l17 11q3 4 0 4q18 -2 31 11q10 11 -6 20q3 6 -3 9.5t-15 5.5q3 1 11.5 0.5t10.5 1.5 q15 10 -7 16q-17 5 -43 -12zM879 10q206 36 351 189q-3 3 -12.5 4.5t-12.5 3.5q-18 7 -24 8q1 7 -2.5 13t-8 9t-12.5 8t-11 7q-2 2 -7 6t-7 5.5t-7.5 4.5t-8.5 2t-10 -1l-3 -1q-3 -1 -5.5 -2.5t-5.5 -3t-4 -3t0 -2.5q-21 17 -36 22q-5 1 -11 5.5t-10.5 7t-10 1.5t-11.5 -7 q-5 -5 -6 -15t-2 -13q-7 5 0 17.5t2 18.5q-3 6 -10.5 4.5t-12 -4.5t-11.5 -8.5t-9 -6.5t-8.5 -5.5t-8.5 -7.5q-3 -4 -6 -12t-5 -11q-2 4 -11.5 6.5t-9.5 5.5q2 -10 4 -35t5 -38q7 -31 -12 -48q-27 -25 -29 -40q-4 -22 12 -26q0 -7 -8 -20.5t-7 -21.5q0 -6 2 -16z" />
+<glyph unicode="&#xf0ad;" horiz-adv-x="1664" d="M384 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1028 484l-682 -682q-37 -37 -90 -37q-52 0 -91 37l-106 108q-38 36 -38 90q0 53 38 91l681 681q39 -98 114.5 -173.5t173.5 -114.5zM1662 919q0 -39 -23 -106q-47 -134 -164.5 -217.5 t-258.5 -83.5q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q58 0 121.5 -16.5t107.5 -46.5q16 -11 16 -28t-16 -28l-293 -169v-224l193 -107q5 3 79 48.5t135.5 81t70.5 35.5q15 0 23.5 -10t8.5 -25z" />
+<glyph unicode="&#xf0ae;" horiz-adv-x="1792" d="M1024 128h640v128h-640v-128zM640 640h1024v128h-1024v-128zM1280 1152h384v128h-384v-128zM1792 320v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 832v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19 t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0b0;" horiz-adv-x="1408" d="M1403 1241q17 -41 -14 -70l-493 -493v-742q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-256 256q-19 19 -19 45v486l-493 493q-31 29 -14 70q17 39 59 39h1280q42 0 59 -39z" />
+<glyph unicode="&#xf0b1;" horiz-adv-x="1792" d="M640 1280h512v128h-512v-128zM1792 640v-480q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v480h672v-160q0 -26 19 -45t45 -19h320q26 0 45 19t19 45v160h672zM1024 640v-128h-256v128h256zM1792 1120v-384h-1792v384q0 66 47 113t113 47h352v160q0 40 28 68 t68 28h576q40 0 68 -28t28 -68v-160h352q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf0b2;" d="M1283 995l-355 -355l355 -355l144 144q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l144 144l-355 355l-355 -355l144 -144q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l144 -144 l355 355l-355 355l-144 -144q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v448q0 26 19 45t45 19h448q42 0 59 -40q17 -39 -14 -69l-144 -144l355 -355l355 355l-144 144q-31 30 -14 69q17 40 59 40h448q26 0 45 -19t19 -45v-448q0 -42 -39 -59q-13 -5 -25 -5q-26 0 -45 19z " />
+<glyph unicode="&#xf0c0;" horiz-adv-x="1920" d="M593 640q-162 -5 -265 -128h-134q-82 0 -138 40.5t-56 118.5q0 353 124 353q6 0 43.5 -21t97.5 -42.5t119 -21.5q67 0 133 23q-5 -37 -5 -66q0 -139 81 -256zM1664 3q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q10 0 43 -21.5t73 -48t107 -48t135 -21.5t135 21.5t107 48t73 48t43 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM640 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75 t75 -181zM1344 896q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5zM1920 671q0 -78 -56 -118.5t-138 -40.5h-134q-103 123 -265 128q81 117 81 256q0 29 -5 66q66 -23 133 -23q59 0 119 21.5t97.5 42.5 t43.5 21q124 0 124 -353zM1792 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181z" />
+<glyph unicode="&#xf0c1;" horiz-adv-x="1664" d="M1456 320q0 40 -28 68l-208 208q-28 28 -68 28q-42 0 -72 -32q3 -3 19 -18.5t21.5 -21.5t15 -19t13 -25.5t3.5 -27.5q0 -40 -28 -68t-68 -28q-15 0 -27.5 3.5t-25.5 13t-19 15t-21.5 21.5t-18.5 19q-33 -31 -33 -73q0 -40 28 -68l206 -207q27 -27 68 -27q40 0 68 26 l147 146q28 28 28 67zM753 1025q0 40 -28 68l-206 207q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l208 -208q27 -27 68 -27q42 0 72 31q-3 3 -19 18.5t-21.5 21.5t-15 19t-13 25.5t-3.5 27.5q0 40 28 68t68 28q15 0 27.5 -3.5t25.5 -13t19 -15 t21.5 -21.5t18.5 -19q33 31 33 73zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-206 207q-83 83 -83 203q0 123 88 209l-88 88q-86 -88 -208 -88q-120 0 -204 84l-208 208q-84 84 -84 204t85 203l147 146q83 83 203 83q121 0 204 -85l206 -207 q83 -83 83 -203q0 -123 -88 -209l88 -88q86 88 208 88q120 0 204 -84l208 -208q84 -84 84 -204z" />
+<glyph unicode="&#xf0c2;" horiz-adv-x="1920" d="M1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5q0 132 71 241.5t187 163.5q-2 28 -2 43q0 212 150 362t362 150q158 0 286.5 -88t187.5 -230q70 62 166 62q106 0 181 -75t75 -181q0 -75 -41 -138q129 -30 213 -134.5t84 -239.5z " />
+<glyph unicode="&#xf0c3;" horiz-adv-x="1664" d="M1527 88q56 -89 21.5 -152.5t-140.5 -63.5h-1152q-106 0 -140.5 63.5t21.5 152.5l503 793v399h-64q-26 0 -45 19t-19 45t19 45t45 19h512q26 0 45 -19t19 -45t-19 -45t-45 -19h-64v-399zM748 813l-272 -429h712l-272 429l-20 31v37v399h-128v-399v-37z" />
+<glyph unicode="&#xf0c4;" horiz-adv-x="1792" d="M960 640q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM1260 576l507 -398q28 -20 25 -56q-5 -35 -35 -51l-128 -64q-13 -7 -29 -7q-17 0 -31 8l-690 387l-110 -66q-8 -4 -12 -5q14 -49 10 -97q-7 -77 -56 -147.5t-132 -123.5q-132 -84 -277 -84 q-136 0 -222 78q-90 84 -79 207q7 76 56 147t131 124q132 84 278 84q83 0 151 -31q9 13 22 22l122 73l-122 73q-13 9 -22 22q-68 -31 -151 -31q-146 0 -278 84q-82 53 -131 124t-56 147q-5 59 15.5 113t63.5 93q85 79 222 79q145 0 277 -84q83 -52 132 -123t56 -148 q4 -48 -10 -97q4 -1 12 -5l110 -66l690 387q14 8 31 8q16 0 29 -7l128 -64q30 -16 35 -51q3 -36 -25 -56zM579 836q46 42 21 108t-106 117q-92 59 -192 59q-74 0 -113 -36q-46 -42 -21 -108t106 -117q92 -59 192 -59q74 0 113 36zM494 91q81 51 106 117t-21 108 q-39 36 -113 36q-100 0 -192 -59q-81 -51 -106 -117t21 -108q39 -36 113 -36q100 0 192 59zM672 704l96 -58v11q0 36 33 56l14 8l-79 47l-26 -26q-3 -3 -10 -11t-12 -12q-2 -2 -4 -3.5t-3 -2.5zM896 480l96 -32l736 576l-128 64l-768 -431v-113l-160 -96l9 -8q2 -2 7 -6 q4 -4 11 -12t11 -12l26 -26zM1600 64l128 64l-520 408l-177 -138q-2 -3 -13 -7z" />
+<glyph unicode="&#xf0c5;" horiz-adv-x="1792" d="M1696 1152q40 0 68 -28t28 -68v-1216q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v288h-544q-40 0 -68 28t-28 68v672q0 40 20 88t48 76l408 408q28 28 76 48t88 20h416q40 0 68 -28t28 -68v-328q68 40 128 40h416zM1152 939l-299 -299h299v299zM512 1323l-299 -299 h299v299zM708 676l316 316v416h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h512v256q0 40 20 88t48 76zM1664 -128v1152h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h896z" />
+<glyph unicode="&#xf0c6;" horiz-adv-x="1408" d="M1404 151q0 -117 -79 -196t-196 -79q-135 0 -235 100l-777 776q-113 115 -113 271q0 159 110 270t269 111q158 0 273 -113l605 -606q10 -10 10 -22q0 -16 -30.5 -46.5t-46.5 -30.5q-13 0 -23 10l-606 607q-79 77 -181 77q-106 0 -179 -75t-73 -181q0 -105 76 -181 l776 -777q63 -63 145 -63q64 0 106 42t42 106q0 82 -63 145l-581 581q-26 24 -60 24q-29 0 -48 -19t-19 -48q0 -32 25 -59l410 -410q10 -10 10 -22q0 -16 -31 -47t-47 -31q-12 0 -22 10l-410 410q-63 61 -63 149q0 82 57 139t139 57q88 0 149 -63l581 -581q100 -98 100 -235 z" />
+<glyph unicode="&#xf0c7;" d="M384 0h768v384h-768v-384zM1280 0h128v896q0 14 -10 38.5t-20 34.5l-281 281q-10 10 -34 20t-39 10v-416q0 -40 -28 -68t-68 -28h-576q-40 0 -68 28t-28 68v416h-128v-1280h128v416q0 40 28 68t68 28h832q40 0 68 -28t28 -68v-416zM896 928v320q0 13 -9.5 22.5t-22.5 9.5 h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1536 896v-928q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h928q40 0 88 -20t76 -48l280 -280q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf0c8;" d="M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf0c9;" d="M1536 192v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 704v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 1216v-128q0 -26 -19 -45 t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0ca;" horiz-adv-x="1792" d="M384 128q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 640q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1152q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z M1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf0cb;" horiz-adv-x="1792" d="M381 -84q0 -80 -54.5 -126t-135.5 -46q-106 0 -172 66l57 88q49 -45 106 -45q29 0 50.5 14.5t21.5 42.5q0 64 -105 56l-26 56q8 10 32.5 43.5t42.5 54t37 38.5v1q-16 0 -48.5 -1t-48.5 -1v-53h-106v152h333v-88l-95 -115q51 -12 81 -49t30 -88zM383 543v-159h-362 q-6 36 -6 54q0 51 23.5 93t56.5 68t66 47.5t56.5 43.5t23.5 45q0 25 -14.5 38.5t-39.5 13.5q-46 0 -81 -58l-85 59q24 51 71.5 79.5t105.5 28.5q73 0 123 -41.5t50 -112.5q0 -50 -34 -91.5t-75 -64.5t-75.5 -50.5t-35.5 -52.5h127v60h105zM1792 224v-192q0 -13 -9.5 -22.5 t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1123v-99h-335v99h107q0 41 0.5 122t0.5 121v12h-2q-8 -17 -50 -54l-71 76l136 127h106v-404h108zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5 t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf0cc;" horiz-adv-x="1792" d="M1760 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1728q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h1728zM483 704q-28 35 -51 80q-48 97 -48 188q0 181 134 309q133 127 393 127q50 0 167 -19q66 -12 177 -48q10 -38 21 -118q14 -123 14 -183q0 -18 -5 -45l-12 -3l-84 6 l-14 2q-50 149 -103 205q-88 91 -210 91q-114 0 -182 -59q-67 -58 -67 -146q0 -73 66 -140t279 -129q69 -20 173 -66q58 -28 95 -52h-743zM990 448h411q7 -39 7 -92q0 -111 -41 -212q-23 -55 -71 -104q-37 -35 -109 -81q-80 -48 -153 -66q-80 -21 -203 -21q-114 0 -195 23 l-140 40q-57 16 -72 28q-8 8 -8 22v13q0 108 -2 156q-1 30 0 68l2 37v44l102 2q15 -34 30 -71t22.5 -56t12.5 -27q35 -57 80 -94q43 -36 105 -57q59 -22 132 -22q64 0 139 27q77 26 122 86q47 61 47 129q0 84 -81 157q-34 29 -137 71z" />
+<glyph unicode="&#xf0cd;" d="M48 1313q-37 2 -45 4l-3 88q13 1 40 1q60 0 112 -4q132 -7 166 -7q86 0 168 3q116 4 146 5q56 0 86 2l-1 -14l2 -64v-9q-60 -9 -124 -9q-60 0 -79 -25q-13 -14 -13 -132q0 -13 0.5 -32.5t0.5 -25.5l1 -229l14 -280q6 -124 51 -202q35 -59 96 -92q88 -47 177 -47 q104 0 191 28q56 18 99 51q48 36 65 64q36 56 53 114q21 73 21 229q0 79 -3.5 128t-11 122.5t-13.5 159.5l-4 59q-5 67 -24 88q-34 35 -77 34l-100 -2l-14 3l2 86h84l205 -10q76 -3 196 10l18 -2q6 -38 6 -51q0 -7 -4 -31q-45 -12 -84 -13q-73 -11 -79 -17q-15 -15 -15 -41 q0 -7 1.5 -27t1.5 -31q8 -19 22 -396q6 -195 -15 -304q-15 -76 -41 -122q-38 -65 -112 -123q-75 -57 -182 -89q-109 -33 -255 -33q-167 0 -284 46q-119 47 -179 122q-61 76 -83 195q-16 80 -16 237v333q0 188 -17 213q-25 36 -147 39zM1536 -96v64q0 14 -9 23t-23 9h-1472 q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h1472q14 0 23 9t9 23z" />
+<glyph unicode="&#xf0ce;" horiz-adv-x="1664" d="M512 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23 v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 160v192 q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192 q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1664 1248v-1088q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1344q66 0 113 -47t47 -113 z" />
+<glyph unicode="&#xf0d0;" horiz-adv-x="1664" d="M1190 955l293 293l-107 107l-293 -293zM1637 1248q0 -27 -18 -45l-1286 -1286q-18 -18 -45 -18t-45 18l-198 198q-18 18 -18 45t18 45l1286 1286q18 18 45 18t45 -18l198 -198q18 -18 18 -45zM286 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM636 1276 l196 -60l-196 -60l-60 -196l-60 196l-196 60l196 60l60 196zM1566 798l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM926 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98z" />
+<glyph unicode="&#xf0d1;" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d2;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0d3;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" />
+<glyph unicode="&#xf0d4;" d="M829 318q0 -76 -58.5 -112.5t-139.5 -36.5q-41 0 -80.5 9.5t-75.5 28.5t-58 53t-22 78q0 46 25 80t65.5 51.5t82 25t84.5 7.5q20 0 31 -2q2 -1 23 -16.5t26 -19t23 -18t24.5 -22t19 -22.5t17 -26t9 -26.5t4.5 -31.5zM755 863q0 -60 -33 -99.5t-92 -39.5q-53 0 -93 42.5 t-57.5 96.5t-17.5 106q0 61 32 104t92 43q53 0 93.5 -45t58 -101t17.5 -107zM861 1120l88 64h-265q-85 0 -161 -32t-127.5 -98t-51.5 -153q0 -93 64.5 -154.5t158.5 -61.5q22 0 43 3q-13 -29 -13 -54q0 -44 40 -94q-175 -12 -257 -63q-47 -29 -75.5 -73t-28.5 -95 q0 -43 18.5 -77.5t48.5 -56.5t69 -37t77.5 -21t76.5 -6q60 0 120.5 15.5t113.5 46t86 82.5t33 117q0 49 -20 89.5t-49 66.5t-58 47.5t-49 44t-20 44.5t15.5 42.5t37.5 39.5t44 42t37.5 59.5t15.5 82.5q0 60 -22.5 99.5t-72.5 90.5h83zM1152 672h128v64h-128v128h-64v-128 h-128v-64h128v-160h64v160zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf0d5;" horiz-adv-x="1664" d="M735 740q0 -36 32 -70.5t77.5 -68t90.5 -73.5t77 -104t32 -142q0 -90 -48 -173q-72 -122 -211 -179.5t-298 -57.5q-132 0 -246.5 41.5t-171.5 137.5q-37 60 -37 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 42 -47.5 74t-15.5 73q0 36 21 85q-46 -4 -68 -4 q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q77 66 182.5 98t217.5 32h418l-138 -88h-131q74 -63 112 -133t38 -160q0 -72 -24.5 -129.5t-59 -93t-69.5 -65t-59.5 -61.5t-24.5 -66zM589 836q38 0 78 16.5t66 43.5q53 57 53 159q0 58 -17 125t-48.5 129.5 t-84.5 103.5t-117 41q-42 0 -82.5 -19.5t-65.5 -52.5q-47 -59 -47 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26zM591 -37q58 0 111.5 13t99 39t73 73t27.5 109q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -48 2 q-53 0 -105 -7t-107.5 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -70 35 -123.5t91.5 -83t119 -44t127.5 -14.5zM1401 839h213v-108h-213v-219h-105v219h-212v108h212v217h105v-217z" />
+<glyph unicode="&#xf0d6;" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d7;" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d8;" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0d9;" horiz-adv-x="640" d="M640 1088v-896q0 -26 -19 -45t-45 -19t-45 19l-448 448q-19 19 -19 45t19 45l448 448q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf0da;" horiz-adv-x="640" d="M576 640q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19t-19 45v896q0 26 19 45t45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0db;" horiz-adv-x="1664" d="M160 0h608v1152h-640v-1120q0 -13 9.5 -22.5t22.5 -9.5zM1536 32v1120h-640v-1152h608q13 0 22.5 9.5t9.5 22.5zM1664 1248v-1216q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1344q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf0dc;" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45zM1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0dd;" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0de;" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0e0;" horiz-adv-x="1792" d="M1792 826v-794q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v794q44 -49 101 -87q362 -246 497 -345q57 -42 92.5 -65.5t94.5 -48t110 -24.5h1h1q51 0 110 24.5t94.5 48t92.5 65.5q170 123 498 345q57 39 100 87zM1792 1120q0 -79 -49 -151t-122 -123 q-376 -261 -468 -325q-10 -7 -42.5 -30.5t-54 -38t-52 -32.5t-57.5 -27t-50 -9h-1h-1q-23 0 -50 9t-57.5 27t-52 32.5t-54 38t-42.5 30.5q-91 64 -262 182.5t-205 142.5q-62 42 -117 115.5t-55 136.5q0 78 41.5 130t118.5 52h1472q65 0 112.5 -47t47.5 -113z" />
+<glyph unicode="&#xf0e1;" d="M349 911v-991h-330v991h330zM370 1217q1 -73 -50.5 -122t-135.5 -49h-2q-82 0 -132 49t-50 122q0 74 51.5 122.5t134.5 48.5t133 -48.5t51 -122.5zM1536 488v-568h-329v530q0 105 -40.5 164.5t-126.5 59.5q-63 0 -105.5 -34.5t-63.5 -85.5q-11 -30 -11 -81v-553h-329 q2 399 2 647t-1 296l-1 48h329v-144h-2q20 32 41 56t56.5 52t87 43.5t114.5 15.5q171 0 275 -113.5t104 -332.5z" />
+<glyph unicode="&#xf0e2;" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298z" />
+<glyph unicode="&#xf0e3;" horiz-adv-x="1792" d="M1771 0q0 -53 -37 -90l-107 -108q-39 -37 -91 -37q-53 0 -90 37l-363 364q-38 36 -38 90q0 53 43 96l-256 256l-126 -126q-14 -14 -34 -14t-34 14q2 -2 12.5 -12t12.5 -13t10 -11.5t10 -13.5t6 -13.5t5.5 -16.5t1.5 -18q0 -38 -28 -68q-3 -3 -16.5 -18t-19 -20.5 t-18.5 -16.5t-22 -15.5t-22 -9t-26 -4.5q-40 0 -68 28l-408 408q-28 28 -28 68q0 13 4.5 26t9 22t15.5 22t16.5 18.5t20.5 19t18 16.5q30 28 68 28q10 0 18 -1.5t16.5 -5.5t13.5 -6t13.5 -10t11.5 -10t13 -12.5t12 -12.5q-14 14 -14 34t14 34l348 348q14 14 34 14t34 -14 q-2 2 -12.5 12t-12.5 13t-10 11.5t-10 13.5t-6 13.5t-5.5 16.5t-1.5 18q0 38 28 68q3 3 16.5 18t19 20.5t18.5 16.5t22 15.5t22 9t26 4.5q40 0 68 -28l408 -408q28 -28 28 -68q0 -13 -4.5 -26t-9 -22t-15.5 -22t-16.5 -18.5t-20.5 -19t-18 -16.5q-30 -28 -68 -28 q-10 0 -18 1.5t-16.5 5.5t-13.5 6t-13.5 10t-11.5 10t-13 12.5t-12 12.5q14 -14 14 -34t-14 -34l-126 -126l256 -256q43 43 96 43q52 0 91 -37l363 -363q37 -39 37 -91z" />
+<glyph unicode="&#xf0e4;" horiz-adv-x="1792" d="M384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM576 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1004 351l101 382q6 26 -7.5 48.5t-38.5 29.5 t-48 -6.5t-30 -39.5l-101 -382q-60 -5 -107 -43.5t-63 -98.5q-20 -77 20 -146t117 -89t146 20t89 117q16 60 -6 117t-72 91zM1664 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 1024q0 53 -37.5 90.5 t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1472 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 384q0 -261 -141 -483q-19 -29 -54 -29h-1402q-35 0 -54 29 q-141 221 -141 483q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf0e5;" horiz-adv-x="1792" d="M896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640 q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 174 120 321.5 t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="&#xf0e6;" horiz-adv-x="1792" d="M704 1152q-153 0 -286 -52t-211.5 -141t-78.5 -191q0 -82 53 -158t149 -132l97 -56l-35 -84q34 20 62 39l44 31l53 -10q78 -14 153 -14q153 0 286 52t211.5 141t78.5 191t-78.5 191t-211.5 141t-286 52zM704 1280q191 0 353.5 -68.5t256.5 -186.5t94 -257t-94 -257 t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224q0 139 94 257t256.5 186.5 t353.5 68.5zM1526 111q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129 q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5z" />
+<glyph unicode="&#xf0e7;" horiz-adv-x="896" d="M885 970q18 -20 7 -44l-540 -1157q-13 -25 -42 -25q-4 0 -14 2q-17 5 -25.5 19t-4.5 30l197 808l-406 -101q-4 -1 -12 -1q-18 0 -31 11q-18 15 -13 39l201 825q4 14 16 23t28 9h328q19 0 32 -12.5t13 -29.5q0 -8 -5 -18l-171 -463l396 98q8 2 12 2q19 0 34 -15z" />
+<glyph unicode="&#xf0e8;" horiz-adv-x="1792" d="M1792 288v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192q0 52 38 90t90 38h512v192h-96q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-96v-192h512q52 0 90 -38t38 -90v-192h96q40 0 68 -28t28 -68 z" />
+<glyph unicode="&#xf0e9;" horiz-adv-x="1664" d="M896 708v-580q0 -104 -76 -180t-180 -76t-180 76t-76 180q0 26 19 45t45 19t45 -19t19 -45q0 -50 39 -89t89 -39t89 39t39 89v580q33 11 64 11t64 -11zM1664 681q0 -13 -9.5 -22.5t-22.5 -9.5q-11 0 -23 10q-49 46 -93 69t-102 23q-68 0 -128 -37t-103 -97 q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -28 -17q-18 0 -29 17q-4 6 -14.5 24t-17.5 28q-43 60 -102.5 97t-127.5 37t-127.5 -37t-102.5 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -29 -17q-17 0 -28 17q-4 6 -14.5 24t-17.5 28q-43 60 -103 97t-128 37q-58 0 -102 -23t-93 -69 q-12 -10 -23 -10q-13 0 -22.5 9.5t-9.5 22.5q0 5 1 7q45 183 172.5 319.5t298 204.5t360.5 68q140 0 274.5 -40t246.5 -113.5t194.5 -187t115.5 -251.5q1 -2 1 -7zM896 1408v-98q-42 2 -64 2t-64 -2v98q0 26 19 45t45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf0ea;" horiz-adv-x="1792" d="M768 -128h896v640h-416q-40 0 -68 28t-28 68v416h-384v-1152zM1024 1312v64q0 13 -9.5 22.5t-22.5 9.5h-704q-13 0 -22.5 -9.5t-9.5 -22.5v-64q0 -13 9.5 -22.5t22.5 -9.5h704q13 0 22.5 9.5t9.5 22.5zM1280 640h299l-299 299v-299zM1792 512v-672q0 -40 -28 -68t-68 -28 h-960q-40 0 -68 28t-28 68v160h-544q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1088q40 0 68 -28t28 -68v-328q21 -13 36 -28l408 -408q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf0eb;" horiz-adv-x="1024" d="M736 960q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5q0 46 -54 71t-106 25q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5q50 0 99.5 -16t87 -54t37.5 -90zM896 960q0 72 -34.5 134t-90 101.5t-123 62t-136.5 22.5t-136.5 -22.5t-123 -62t-90 -101.5t-34.5 -134 q0 -101 68 -180q10 -11 30.5 -33t30.5 -33q128 -153 141 -298h228q13 145 141 298q10 11 30.5 33t30.5 33q68 79 68 180zM1024 960q0 -155 -103 -268q-45 -49 -74.5 -87t-59.5 -95.5t-34 -107.5q47 -28 47 -82q0 -37 -25 -64q25 -27 25 -64q0 -52 -45 -81q13 -23 13 -47 q0 -46 -31.5 -71t-77.5 -25q-20 -44 -60 -70t-87 -26t-87 26t-60 70q-46 0 -77.5 25t-31.5 71q0 24 13 47q-45 29 -45 81q0 37 25 64q-25 27 -25 64q0 54 47 82q-4 50 -34 107.5t-59.5 95.5t-74.5 87q-103 113 -103 268q0 99 44.5 184.5t117 142t164 89t186.5 32.5 t186.5 -32.5t164 -89t117 -142t44.5 -184.5z" />
+<glyph unicode="&#xf0ec;" horiz-adv-x="1792" d="M1792 352v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5q-12 0 -24 10l-319 320q-9 9 -9 22q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h1376q13 0 22.5 -9.5t9.5 -22.5zM1792 896q0 -14 -9 -23l-320 -320q-9 -9 -23 -9 q-13 0 -22.5 9.5t-9.5 22.5v192h-1376q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1376v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
+<glyph unicode="&#xf0ed;" horiz-adv-x="1920" d="M1280 608q0 14 -9 23t-23 9h-224v352q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-352h-224q-13 0 -22.5 -9.5t-9.5 -22.5q0 -14 9 -23l352 -352q9 -9 23 -9t23 9l351 351q10 12 10 24zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
+<glyph unicode="&#xf0ee;" horiz-adv-x="1920" d="M1280 672q0 14 -9 23l-352 352q-9 9 -23 9t-23 -9l-351 -351q-10 -12 -10 -24q0 -14 9 -23t23 -9h224v-352q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v352h224q13 0 22.5 9.5t9.5 22.5zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
+<glyph unicode="&#xf0f0;" horiz-adv-x="1408" d="M384 192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56 t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68 t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5t81 -103t47.5 -132.5t24 -138t5.5 -131zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf0f1;" horiz-adv-x="1408" d="M1280 832q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 832q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5t-131.5 271.5v132q-164 20 -274 128t-110 252v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48 t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252 t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136z" />
+<glyph unicode="&#xf0f2;" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM288 1152v-1280h-64q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h64zM1408 1152v-1280h-1024v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128zM1792 928v-832q0 -92 -66 -158t-158 -66h-64v1280h64q92 0 158 -66 t66 -158z" />
+<glyph unicode="&#xf0f3;" horiz-adv-x="1792" d="M912 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1728 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q50 42 91 88t85 119.5t74.5 158.5 t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q190 -28 307 -158.5t117 -282.5q0 -139 19.5 -260t50 -206t74.5 -158.5t85 -119.5t91 -88z" />
+<glyph unicode="&#xf0f4;" horiz-adv-x="1920" d="M1664 896q0 80 -56 136t-136 56h-64v-384h64q80 0 136 56t56 136zM0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM1856 896q0 -159 -112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158v736q0 26 19 45 t45 19h1152q159 0 271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf0f5;" horiz-adv-x="1408" d="M640 1472v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45 t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45zM1408 1472v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f6;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M384 736q0 14 9 23t23 9h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64zM1120 512q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704zM1120 256q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704 q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704z" />
+<glyph unicode="&#xf0f7;" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1536h-1152v-1536h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM1408 1472v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f8;" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1152h-256v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM896 1056v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5 t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1408 1088v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1280q0 26 19 45t45 19h320 v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f9;" horiz-adv-x="1920" d="M640 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM1536 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1664 800v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM1920 1344v-1152 q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128q-26 0 -45 19t-19 45t19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0fa;" horiz-adv-x="1792" d="M1280 416v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM640 1152h512v128h-512v-128zM256 1152v-1280h-32 q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h32zM1440 1152v-1280h-1088v1280h160v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h160zM1792 928v-832q0 -92 -66 -158t-158 -66h-32v1280h32q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf0fb;" horiz-adv-x="1920" d="M1920 576q-1 -32 -288 -96l-352 -32l-224 -64h-64l-293 -352h69q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-96h-160h-64v32h64v416h-160l-192 -224h-96l-32 32v192h32v32h128v8l-192 24v128l192 24v8h-128v32h-32v192l32 32h96l192 -224h160v416h-64v32h64h160h96 q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-69l293 -352h64l224 -64l352 -32q261 -58 287 -93z" />
+<glyph unicode="&#xf0fc;" horiz-adv-x="1664" d="M640 640v384h-256v-256q0 -53 37.5 -90.5t90.5 -37.5h128zM1664 192v-192h-1152v192l128 192h-128q-159 0 -271.5 112.5t-112.5 271.5v320l-64 64l32 128h480l32 128h960l32 -192l-64 -32v-800z" />
+<glyph unicode="&#xf0fd;" d="M1280 192v896q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-512v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-896q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h512v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf0fe;" d="M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf100;" horiz-adv-x="1024" d="M627 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23zM1011 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23z" />
+<glyph unicode="&#xf101;" horiz-adv-x="1024" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM979 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23 l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf102;" horiz-adv-x="1152" d="M1075 224q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM1075 608q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393 q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf103;" horiz-adv-x="1152" d="M1075 672q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23zM1075 1056q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf104;" horiz-adv-x="640" d="M627 992q0 -13 -10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf105;" horiz-adv-x="640" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf106;" horiz-adv-x="1152" d="M1075 352q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf107;" horiz-adv-x="1152" d="M1075 800q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf108;" horiz-adv-x="1920" d="M1792 544v832q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1376v-1088q0 -66 -47 -113t-113 -47h-544q0 -37 16 -77.5t32 -71t16 -43.5q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19 t-19 45q0 14 16 44t32 70t16 78h-544q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf109;" horiz-adv-x="1920" d="M416 256q-66 0 -113 47t-47 113v704q0 66 47 113t113 47h1088q66 0 113 -47t47 -113v-704q0 -66 -47 -113t-113 -47h-1088zM384 1120v-704q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5z M1760 192h160v-96q0 -40 -47 -68t-113 -28h-1600q-66 0 -113 28t-47 68v96h160h1600zM1040 96q16 0 16 16t-16 16h-160q-16 0 -16 -16t16 -16h160z" />
+<glyph unicode="&#xf10a;" horiz-adv-x="1152" d="M640 128q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1024 288v960q0 13 -9.5 22.5t-22.5 9.5h-832q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h832q13 0 22.5 9.5t9.5 22.5zM1152 1248v-1088q0 -66 -47 -113t-113 -47h-832 q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h832q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf10b;" horiz-adv-x="768" d="M464 128q0 33 -23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5t56.5 23.5t23.5 56.5zM672 288v704q0 13 -9.5 22.5t-22.5 9.5h-512q-13 0 -22.5 -9.5t-9.5 -22.5v-704q0 -13 9.5 -22.5t22.5 -9.5h512q13 0 22.5 9.5t9.5 22.5zM480 1136 q0 16 -16 16h-160q-16 0 -16 -16t16 -16h160q16 0 16 16zM768 1152v-1024q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v1024q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf10c;" d="M768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103 t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf10d;" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" />
+<glyph unicode="&#xf10e;" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" />
+<glyph unicode="&#xf110;" horiz-adv-x="1792" d="M526 142q0 -53 -37.5 -90.5t-90.5 -37.5q-52 0 -90 38t-38 90q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1024 -64q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -53 -37.5 -90.5t-90.5 -37.5 t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1522 142q0 -52 -38 -90t-90 -38q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM558 1138q0 -66 -47 -113t-113 -47t-113 47t-47 113t47 113t113 47t113 -47t47 -113z M1728 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1088 1344q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1618 1138q0 -93 -66 -158.5t-158 -65.5q-93 0 -158.5 65.5t-65.5 158.5 q0 92 65.5 158t158.5 66q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf111;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf112;" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" />
+<glyph unicode="&#xf113;" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" />
+<glyph unicode="&#xf114;" horiz-adv-x="1664" d="M1536 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-960q0 -40 28 -68t68 -28h1216q40 0 68 28t28 68zM1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320 q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf115;" horiz-adv-x="1920" d="M1781 605q0 35 -53 35h-1088q-40 0 -85.5 -21.5t-71.5 -52.5l-294 -363q-18 -24 -18 -40q0 -35 53 -35h1088q40 0 86 22t71 53l294 363q18 22 18 39zM640 768h768v160q0 40 -28 68t-68 28h-576q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68 v-853l256 315q44 53 116 87.5t140 34.5zM1909 605q0 -62 -46 -120l-295 -363q-43 -53 -116 -87.5t-140 -34.5h-1088q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h192q54 0 99 -24.5t67 -70.5q15 -32 15 -68z " />
+<glyph unicode="&#xf116;" horiz-adv-x="1792" />
+<glyph unicode="&#xf117;" horiz-adv-x="1792" />
+<glyph unicode="&#xf118;" d="M1134 461q-37 -121 -138 -195t-228 -74t-228 74t-138 195q-8 25 4 48.5t38 31.5q25 8 48.5 -4t31.5 -38q25 -80 92.5 -129.5t151.5 -49.5t151.5 49.5t92.5 129.5q8 26 32 38t49 4t37 -31.5t4 -48.5zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5 t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf119;" d="M1134 307q8 -25 -4 -48.5t-37 -31.5t-49 4t-32 38q-25 80 -92.5 129.5t-151.5 49.5t-151.5 -49.5t-92.5 -129.5q-8 -26 -31.5 -38t-48.5 -4q-26 8 -38 31.5t-4 48.5q37 121 138 195t228 74t228 -74t138 -195zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204 t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf11a;" d="M1152 448q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h640q26 0 45 -19t19 -45zM640 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1152 896q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf11b;" horiz-adv-x="1920" d="M832 448v128q0 14 -9 23t-23 9h-192v192q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-192h-192q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h192v-192q0 -14 9 -23t23 -9h128q14 0 23 9t9 23v192h192q14 0 23 9t9 23zM1408 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1920 512q0 -212 -150 -362t-362 -150q-192 0 -338 128h-220q-146 -128 -338 -128q-212 0 -362 150 t-150 362t150 362t362 150h896q212 0 362 -150t150 -362z" />
+<glyph unicode="&#xf11c;" horiz-adv-x="1920" d="M384 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM512 624v-96q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h224q16 0 16 -16zM384 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 368v-96q0 -16 -16 -16 h-864q-16 0 -16 16v96q0 16 16 16h864q16 0 16 -16zM768 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM640 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1024 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16 h96q16 0 16 -16zM896 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1280 624v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 368v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1152 880v-96 q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1408 880v-96q0 -16 -16 -16h-96q-16 0 -16 16v96q0 16 16 16h96q16 0 16 -16zM1664 880v-352q0 -16 -16 -16h-224q-16 0 -16 16v96q0 16 16 16h112v240q0 16 16 16h96q16 0 16 -16zM1792 128v896h-1664v-896 h1664zM1920 1024v-896q0 -53 -37.5 -90.5t-90.5 -37.5h-1664q-53 0 -90.5 37.5t-37.5 90.5v896q0 53 37.5 90.5t90.5 37.5h1664q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf11d;" horiz-adv-x="1792" d="M1664 491v616q-169 -91 -306 -91q-82 0 -145 32q-100 49 -184 76.5t-178 27.5q-173 0 -403 -127v-599q245 113 433 113q55 0 103.5 -7.5t98 -26t77 -31t82.5 -39.5l28 -14q44 -22 101 -22q120 0 293 92zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9 h-64q-14 0 -23 9t-9 23v1266q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102 q-15 -9 -33 -9q-16 0 -32 8q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
+<glyph unicode="&#xf11e;" horiz-adv-x="1792" d="M832 536v192q-181 -16 -384 -117v-185q205 96 384 110zM832 954v197q-172 -8 -384 -126v-189q215 111 384 118zM1664 491v184q-235 -116 -384 -71v224q-20 6 -39 15q-5 3 -33 17t-34.5 17t-31.5 15t-34.5 15.5t-32.5 13t-36 12.5t-35 8.5t-39.5 7.5t-39.5 4t-44 2 q-23 0 -49 -3v-222h19q102 0 192.5 -29t197.5 -82q19 -9 39 -15v-188q42 -17 91 -17q120 0 293 92zM1664 918v189q-169 -91 -306 -91q-45 0 -78 8v-196q148 -42 384 90zM320 1280q0 -35 -17.5 -64t-46.5 -46v-1266q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v1266 q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -39 -35 -57q-10 -5 -17 -9q-218 -116 -369 -116q-88 0 -158 35l-28 14q-64 33 -99 48t-91 29t-114 14q-102 0 -235.5 -44t-228.5 -102q-15 -9 -33 -9q-16 0 -32 8 q-32 19 -32 56v742q0 35 31 55q35 21 78.5 42.5t114 52t152.5 49.5t155 19q112 0 209 -31t209 -86q38 -19 89 -19q122 0 310 112q22 12 31 17q31 16 62 -2q31 -20 31 -55z" />
+<glyph unicode="&#xf120;" horiz-adv-x="1664" d="M585 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23zM1664 96v-64q0 -14 -9 -23t-23 -9h-960q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h960q14 0 23 -9 t9 -23z" />
+<glyph unicode="&#xf121;" horiz-adv-x="1920" d="M617 137l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23zM1208 1204l-373 -1291q-4 -13 -15.5 -19.5t-23.5 -2.5l-62 17q-13 4 -19.5 15.5t-2.5 24.5 l373 1291q4 13 15.5 19.5t23.5 2.5l62 -17q13 -4 19.5 -15.5t2.5 -24.5zM1865 553l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23t-10 -23z" />
+<glyph unicode="&#xf122;" horiz-adv-x="1792" d="M640 454v-70q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-69l-397 -398q-19 -19 -19 -45t19 -45zM1792 416q0 -58 -17 -133.5t-38.5 -138t-48 -125t-40.5 -90.5l-20 -40q-8 -17 -28 -17q-6 0 -9 1 q-25 8 -23 34q43 400 -106 565q-64 71 -170.5 110.5t-267.5 52.5v-251q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-512 512q-19 19 -19 45t19 45l512 512q29 31 70 14q39 -17 39 -59v-262q411 -28 599 -221q169 -173 169 -509z" />
+<glyph unicode="&#xf123;" horiz-adv-x="1664" d="M1186 579l257 250l-356 52l-66 10l-30 60l-159 322v-963l59 -31l318 -168l-60 355l-12 66zM1638 841l-363 -354l86 -500q5 -33 -6 -51.5t-34 -18.5q-17 0 -40 12l-449 236l-449 -236q-23 -12 -40 -12q-23 0 -34 18.5t-6 51.5l86 500l-364 354q-32 32 -23 59.5t54 34.5 l502 73l225 455q20 41 49 41q28 0 49 -41l225 -455l502 -73q45 -7 54 -34.5t-24 -59.5z" />
+<glyph unicode="&#xf124;" horiz-adv-x="1408" d="M1401 1187l-640 -1280q-17 -35 -57 -35q-5 0 -15 2q-22 5 -35.5 22.5t-13.5 39.5v576h-576q-22 0 -39.5 13.5t-22.5 35.5t4 42t29 30l1280 640q13 7 29 7q27 0 45 -19q15 -14 18.5 -34.5t-6.5 -39.5z" />
+<glyph unicode="&#xf125;" horiz-adv-x="1664" d="M557 256h595v595zM512 301l595 595h-595v-595zM1664 224v-192q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v224h-864q-14 0 -23 9t-9 23v864h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224v224q0 14 9 23t23 9h192q14 0 23 -9t9 -23 v-224h851l246 247q10 9 23 9t23 -9q9 -10 9 -23t-9 -23l-247 -246v-851h224q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf126;" horiz-adv-x="1024" d="M288 64q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM288 1216q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM928 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1024 1088q0 -52 -26 -96.5t-70 -69.5 q-2 -287 -226 -414q-68 -38 -203 -81q-128 -40 -169.5 -71t-41.5 -100v-26q44 -25 70 -69.5t26 -96.5q0 -80 -56 -136t-136 -56t-136 56t-56 136q0 52 26 96.5t70 69.5v820q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136q0 -52 -26 -96.5t-70 -69.5v-497 q54 26 154 57q55 17 87.5 29.5t70.5 31t59 39.5t40.5 51t28 69.5t8.5 91.5q-44 25 -70 69.5t-26 96.5q0 80 56 136t136 56t136 -56t56 -136z" />
+<glyph unicode="&#xf127;" horiz-adv-x="1664" d="M439 265l-256 -256q-10 -9 -23 -9q-12 0 -23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23zM608 224v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM384 448q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23t9 23t23 9h320 q14 0 23 -9t9 -23zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-334 335q-21 21 -42 56l239 18l273 -274q27 -27 68 -27.5t68 26.5l147 146q28 28 28 67q0 40 -28 68l-274 275l18 239q35 -21 56 -42l336 -336q84 -86 84 -204zM1031 1044l-239 -18 l-273 274q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l274 -274l-18 -240q-35 21 -56 42l-336 336q-84 86 -84 204q0 120 85 203l147 146q83 83 203 83q121 0 204 -85l334 -335q21 -21 42 -56zM1664 960q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9 t-9 23t9 23t23 9h320q14 0 23 -9t9 -23zM1120 1504v-320q0 -14 -9 -23t-23 -9t-23 9t-9 23v320q0 14 9 23t23 9t23 -9t9 -23zM1527 1353l-256 -256q-11 -9 -23 -9t-23 9q-9 10 -9 23t9 23l256 256q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" />
+<glyph unicode="&#xf128;" horiz-adv-x="1024" d="M704 280v-240q0 -16 -12 -28t-28 -12h-240q-16 0 -28 12t-12 28v240q0 16 12 28t28 12h240q16 0 28 -12t12 -28zM1020 880q0 -54 -15.5 -101t-35 -76.5t-55 -59.5t-57.5 -43.5t-61 -35.5q-41 -23 -68.5 -65t-27.5 -67q0 -17 -12 -32.5t-28 -15.5h-240q-15 0 -25.5 18.5 t-10.5 37.5v45q0 83 65 156.5t143 108.5q59 27 84 56t25 76q0 42 -46.5 74t-107.5 32q-65 0 -108 -29q-35 -25 -107 -115q-13 -16 -31 -16q-12 0 -25 8l-164 125q-13 10 -15.5 25t5.5 28q160 266 464 266q80 0 161 -31t146 -83t106 -127.5t41 -158.5z" />
+<glyph unicode="&#xf129;" horiz-adv-x="640" d="M640 192v-128q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64v384h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-576h64q26 0 45 -19t19 -45zM512 1344v-192q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v192 q0 26 19 45t45 19h256q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf12a;" horiz-adv-x="640" d="M512 288v-224q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v224q0 26 19 45t45 19h256q26 0 45 -19t19 -45zM542 1344l-28 -768q-1 -26 -20.5 -45t-45.5 -19h-256q-26 0 -45.5 19t-20.5 45l-28 768q-1 26 17.5 45t44.5 19h320q26 0 44.5 -19t17.5 -45z" />
+<glyph unicode="&#xf12b;" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1534 846v-206h-514l-3 27 q-4 28 -4 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q83 65 188 65q110 0 178 -59.5t68 -158.5q0 -56 -24.5 -103t-62 -76.5t-81.5 -58.5t-82 -50.5t-65.5 -51.5t-30.5 -63h232v80 h126z" />
+<glyph unicode="&#xf12c;" d="M897 167v-167h-248l-159 252l-24 42q-8 9 -11 21h-3l-9 -21q-10 -20 -25 -44l-155 -250h-258v167h128l197 291l-185 272h-137v168h276l139 -228q2 -4 23 -42q8 -9 11 -21h3q3 9 11 21l25 42l140 228h257v-168h-125l-184 -267l204 -296h109zM1536 -50v-206h-514l-4 27 q-3 45 -3 46q0 64 26 117t65 86.5t84 65t84 54.5t65 54t26 64q0 38 -29.5 62.5t-70.5 24.5q-51 0 -97 -39q-14 -11 -36 -38l-105 92q26 37 63 66q80 65 188 65q110 0 178 -59.5t68 -158.5q0 -66 -34.5 -118.5t-84 -86t-99.5 -62.5t-87 -63t-41 -73h232v80h126z" />
+<glyph unicode="&#xf12d;" horiz-adv-x="1920" d="M896 128l336 384h-768l-336 -384h768zM1909 1205q15 -34 9.5 -71.5t-30.5 -65.5l-896 -1024q-38 -44 -96 -44h-768q-38 0 -69.5 20.5t-47.5 54.5q-15 34 -9.5 71.5t30.5 65.5l896 1024q38 44 96 44h768q38 0 69.5 -20.5t47.5 -54.5z" />
+<glyph unicode="&#xf12e;" horiz-adv-x="1664" d="M1664 438q0 -81 -44.5 -135t-123.5 -54q-41 0 -77.5 17.5t-59 38t-56.5 38t-71 17.5q-110 0 -110 -124q0 -39 16 -115t15 -115v-5q-22 0 -33 -1q-34 -3 -97.5 -11.5t-115.5 -13.5t-98 -5q-61 0 -103 26.5t-42 83.5q0 37 17.5 71t38 56.5t38 59t17.5 77.5q0 79 -54 123.5 t-135 44.5q-84 0 -143 -45.5t-59 -127.5q0 -43 15 -83t33.5 -64.5t33.5 -53t15 -50.5q0 -45 -46 -89q-37 -35 -117 -35q-95 0 -245 24q-9 2 -27.5 4t-27.5 4l-13 2q-1 0 -3 1q-2 0 -2 1v1024q2 -1 17.5 -3.5t34 -5t21.5 -3.5q150 -24 245 -24q80 0 117 35q46 44 46 89 q0 22 -15 50.5t-33.5 53t-33.5 64.5t-15 83q0 82 59 127.5t144 45.5q80 0 134 -44.5t54 -123.5q0 -41 -17.5 -77.5t-38 -59t-38 -56.5t-17.5 -71q0 -57 42 -83.5t103 -26.5q64 0 180 15t163 17v-2q-1 -2 -3.5 -17.5t-5 -34t-3.5 -21.5q-24 -150 -24 -245q0 -80 35 -117 q44 -46 89 -46q22 0 50.5 15t53 33.5t64.5 33.5t83 15q82 0 127.5 -59t45.5 -143z" />
+<glyph unicode="&#xf130;" horiz-adv-x="1152" d="M1152 832v-128q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-217 24 -364.5 187.5t-147.5 384.5v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -185 131.5 -316.5t316.5 -131.5 t316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45zM896 1216v-512q0 -132 -94 -226t-226 -94t-226 94t-94 226v512q0 132 94 226t226 94t226 -94t94 -226z" />
+<glyph unicode="&#xf131;" horiz-adv-x="1408" d="M271 591l-101 -101q-42 103 -42 214v128q0 26 19 45t45 19t45 -19t19 -45v-128q0 -53 15 -113zM1385 1193l-361 -361v-128q0 -132 -94 -226t-226 -94q-55 0 -109 19l-96 -96q97 -51 205 -51q185 0 316.5 131.5t131.5 316.5v128q0 26 19 45t45 19t45 -19t19 -45v-128 q0 -221 -147.5 -384.5t-364.5 -187.5v-132h256q26 0 45 -19t19 -45t-19 -45t-45 -19h-640q-26 0 -45 19t-19 45t19 45t45 19h256v132q-125 13 -235 81l-254 -254q-10 -10 -23 -10t-23 10l-82 82q-10 10 -10 23t10 23l1234 1234q10 10 23 10t23 -10l82 -82q10 -10 10 -23 t-10 -23zM1005 1325l-621 -621v512q0 132 94 226t226 94q102 0 184.5 -59t116.5 -152z" />
+<glyph unicode="&#xf132;" horiz-adv-x="1280" d="M1088 576v640h-448v-1137q119 63 213 137q235 184 235 360zM1280 1344v-768q0 -86 -33.5 -170.5t-83 -150t-118 -127.5t-126.5 -103t-121 -77.5t-89.5 -49.5t-42.5 -20q-12 -6 -26 -6t-26 6q-16 7 -42.5 20t-89.5 49.5t-121 77.5t-126.5 103t-118 127.5t-83 150 t-33.5 170.5v768q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf133;" horiz-adv-x="1664" d="M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf134;" horiz-adv-x="1408" d="M512 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 1376v-320q0 -16 -12 -25q-8 -7 -20 -7q-4 0 -7 1l-448 96q-11 2 -18 11t-7 20h-256v-102q111 -23 183.5 -111t72.5 -203v-800q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v800 q0 106 62.5 190.5t161.5 114.5v111h-32q-59 0 -115 -23.5t-91.5 -53t-66 -66.5t-40.5 -53.5t-14 -24.5q-17 -35 -57 -35q-16 0 -29 7q-23 12 -31.5 37t3.5 49q5 10 14.5 26t37.5 53.5t60.5 70t85 67t108.5 52.5q-25 42 -25 86q0 66 47 113t113 47t113 -47t47 -113 q0 -33 -14 -64h302q0 11 7 20t18 11l448 96q3 1 7 1q12 0 20 -7q12 -9 12 -25z" />
+<glyph unicode="&#xf135;" horiz-adv-x="1664" d="M1440 1088q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1664 1376q0 -249 -75.5 -430.5t-253.5 -360.5q-81 -80 -195 -176l-20 -379q-2 -16 -16 -26l-384 -224q-7 -4 -16 -4q-12 0 -23 9l-64 64q-13 14 -8 32l85 276l-281 281l-276 -85q-3 -1 -9 -1 q-14 0 -23 9l-64 64q-17 19 -5 39l224 384q10 14 26 16l379 20q96 114 176 195q188 187 358 258t431 71q14 0 24 -9.5t10 -22.5z" />
+<glyph unicode="&#xf136;" horiz-adv-x="1792" d="M1745 763l-164 -763h-334l178 832q13 56 -15 88q-27 33 -83 33h-169l-204 -953h-334l204 953h-286l-204 -953h-334l204 953l-153 327h1276q101 0 189.5 -40.5t147.5 -113.5q60 -73 81 -168.5t0 -194.5z" />
+<glyph unicode="&#xf137;" d="M909 141l102 102q19 19 19 45t-19 45l-307 307l307 307q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf138;" d="M717 141l454 454q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l307 -307l-307 -307q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf139;" d="M1165 397l102 102q19 19 19 45t-19 45l-454 454q-19 19 -45 19t-45 -19l-454 -454q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l307 307l307 -307q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf13a;" d="M813 237l454 454q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-307 -307l-307 307q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l454 -454q19 -19 45 -19t45 19zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5 t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf13b;" horiz-adv-x="1408" d="M1130 939l16 175h-884l47 -534h612l-22 -228l-197 -53l-196 53l-13 140h-175l22 -278l362 -100h4v1l359 99l50 544h-644l-15 181h674zM0 1408h1408l-128 -1438l-578 -162l-574 162z" />
+<glyph unicode="&#xf13c;" horiz-adv-x="1792" d="M275 1408h1505l-266 -1333l-804 -267l-698 267l71 356h297l-29 -147l422 -161l486 161l68 339h-1208l58 297h1209l38 191h-1208z" />
+<glyph unicode="&#xf13d;" horiz-adv-x="1792" d="M960 1280q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1792 352v-352q0 -22 -20 -30q-8 -2 -12 -2q-13 0 -23 9l-93 93q-119 -143 -318.5 -226.5t-429.5 -83.5t-429.5 83.5t-318.5 226.5l-93 -93q-9 -9 -23 -9q-4 0 -12 2q-20 8 -20 30v352 q0 14 9 23t23 9h352q22 0 30 -20q8 -19 -7 -35l-100 -100q67 -91 189.5 -153.5t271.5 -82.5v647h-192q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h192v163q-58 34 -93 92.5t-35 128.5q0 106 75 181t181 75t181 -75t75 -181q0 -70 -35 -128.5t-93 -92.5v-163h192q26 0 45 -19 t19 -45v-128q0 -26 -19 -45t-45 -19h-192v-647q149 20 271.5 82.5t189.5 153.5l-100 100q-15 16 -7 35q8 20 30 20h352q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf13e;" horiz-adv-x="1152" d="M1056 768q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v320q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5q0 -26 -19 -45t-45 -19h-64q-26 0 -45 19t-19 45q0 106 -75 181t-181 75t-181 -75t-75 -181 v-320h736z" />
+<glyph unicode="&#xf140;" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM1152 640q0 159 -112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM1280 640q0 -212 -150 -362t-362 -150t-362 150 t-150 362t150 362t362 150t362 -150t150 -362zM1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf141;" horiz-adv-x="1408" d="M384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM896 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM1408 800v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf142;" horiz-adv-x="384" d="M384 288v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 800v-192q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68zM384 1312v-192q0 -40 -28 -68t-68 -28h-192 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h192q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf143;" d="M512 256q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM863 162q-13 232 -177 396t-396 177q-14 1 -24 -9t-10 -23v-128q0 -13 8.5 -22t21.5 -10q154 -11 264 -121t121 -264q1 -13 10 -21.5t22 -8.5h128q13 0 23 10 t9 24zM1247 161q-5 154 -56 297.5t-139.5 260t-205 205t-260 139.5t-297.5 56q-14 1 -23 -9q-10 -10 -10 -23v-128q0 -13 9 -22t22 -10q204 -7 378 -111.5t278.5 -278.5t111.5 -378q1 -13 10 -22t22 -9h128q13 0 23 10q11 9 9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf144;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1152 585q32 18 32 55t-32 55l-544 320q-31 19 -64 1q-32 -19 -32 -56v-640q0 -37 32 -56 q16 -8 32 -8q17 0 32 9z" />
+<glyph unicode="&#xf145;" horiz-adv-x="1792" d="M1024 1084l316 -316l-572 -572l-316 316zM813 105l618 618q19 19 19 45t-19 45l-362 362q-18 18 -45 18t-45 -18l-618 -618q-19 -19 -19 -45t19 -45l362 -362q18 -18 45 -18t45 18zM1702 742l-907 -908q-37 -37 -90.5 -37t-90.5 37l-126 126q56 56 56 136t-56 136 t-136 56t-136 -56l-125 126q-37 37 -37 90.5t37 90.5l907 906q37 37 90.5 37t90.5 -37l125 -125q-56 -56 -56 -136t56 -136t136 -56t136 56l126 -125q37 -37 37 -90.5t-37 -90.5z" />
+<glyph unicode="&#xf146;" d="M1280 576v128q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h896q26 0 45 19t19 45zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" />
+<glyph unicode="&#xf147;" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h832q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5 t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf148;" horiz-adv-x="1024" d="M1018 933q-18 -37 -58 -37h-192v-864q0 -14 -9 -23t-23 -9h-704q-21 0 -29 18q-8 20 4 35l160 192q9 11 25 11h320v640h-192q-40 0 -58 37q-17 37 9 68l320 384q18 22 49 22t49 -22l320 -384q27 -32 9 -68z" />
+<glyph unicode="&#xf149;" horiz-adv-x="1024" d="M32 1280h704q13 0 22.5 -9.5t9.5 -23.5v-863h192q40 0 58 -37t-9 -69l-320 -384q-18 -22 -49 -22t-49 22l-320 384q-26 31 -9 69q18 37 58 37h192v640h-320q-14 0 -25 11l-160 192q-13 14 -4 34q9 19 29 19z" />
+<glyph unicode="&#xf14a;" d="M685 237l614 614q19 19 19 45t-19 45l-102 102q-19 19 -45 19t-45 -19l-467 -467l-211 211q-19 19 -45 19t-45 -19l-102 -102q-19 -19 -19 -45t19 -45l358 -358q19 -19 45 -19t45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5 t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14b;" d="M404 428l152 -152l-52 -52h-56v96h-96v56zM818 818q14 -13 -3 -30l-291 -291q-17 -17 -30 -3q-14 13 3 30l291 291q17 17 30 3zM544 128l544 544l-288 288l-544 -544v-288h288zM1152 736l92 92q28 28 28 68t-28 68l-152 152q-28 28 -68 28t-68 -28l-92 -92zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14c;" d="M1280 608v480q0 26 -19 45t-45 19h-480q-42 0 -59 -39q-17 -41 14 -70l144 -144l-534 -534q-19 -19 -19 -45t19 -45l102 -102q19 -19 45 -19t45 19l534 534l144 -144q18 -19 45 -19q12 0 25 5q39 17 39 59zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14d;" d="M1005 435l352 352q19 19 19 45t-19 45l-352 352q-30 31 -69 14q-40 -17 -40 -59v-160q-119 0 -216 -19.5t-162.5 -51t-114 -79t-76.5 -95.5t-44.5 -109t-21.5 -111.5t-5 -110.5q0 -181 167 -404q10 -12 25 -12q7 0 13 3q22 9 19 33q-44 354 62 473q46 52 130 75.5 t224 23.5v-160q0 -42 40 -59q12 -5 24 -5q26 0 45 19zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf14e;" d="M640 448l256 128l-256 128v-256zM1024 1039v-542l-512 -256v542zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf150;" d="M1145 861q18 -35 -5 -66l-320 -448q-19 -27 -52 -27t-52 27l-320 448q-23 31 -5 66q17 35 57 35h640q40 0 57 -35zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf151;" d="M1145 419q-17 -35 -57 -35h-640q-40 0 -57 35q-18 35 5 66l320 448q19 27 52 27t52 -27l320 -448q23 -31 5 -66zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf152;" d="M1088 640q0 -33 -27 -52l-448 -320q-31 -23 -66 -5q-35 17 -35 57v640q0 40 35 57q35 18 66 -5l448 -320q27 -19 27 -52zM1280 160v960q0 14 -9 23t-23 9h-960q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h960q14 0 23 9t9 23zM1536 1120v-960q0 -119 -84.5 -203.5 t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf153;" horiz-adv-x="1024" d="M976 229l35 -159q3 -12 -3 -22.5t-17 -14.5l-5 -1q-4 -2 -10.5 -3.5t-16 -4.5t-21.5 -5.5t-25.5 -5t-30 -5t-33.5 -4.5t-36.5 -3t-38.5 -1q-234 0 -409 130.5t-238 351.5h-95q-13 0 -22.5 9.5t-9.5 22.5v113q0 13 9.5 22.5t22.5 9.5h66q-2 57 1 105h-67q-14 0 -23 9 t-9 23v114q0 14 9 23t23 9h98q67 210 243.5 338t400.5 128q102 0 194 -23q11 -3 20 -15q6 -11 3 -24l-43 -159q-3 -13 -14 -19.5t-24 -2.5l-4 1q-4 1 -11.5 2.5l-17.5 3.5t-22.5 3.5t-26 3t-29 2.5t-29.5 1q-126 0 -226 -64t-150 -176h468q16 0 25 -12q10 -12 7 -26 l-24 -114q-5 -26 -32 -26h-488q-3 -37 0 -105h459q15 0 25 -12q9 -12 6 -27l-24 -112q-2 -11 -11 -18.5t-20 -7.5h-387q48 -117 149.5 -185.5t228.5 -68.5q18 0 36 1.5t33.5 3.5t29.5 4.5t24.5 5t18.5 4.5l12 3l5 2q13 5 26 -2q12 -7 15 -21z" />
+<glyph unicode="&#xf154;" horiz-adv-x="1024" d="M1020 399v-367q0 -14 -9 -23t-23 -9h-956q-14 0 -23 9t-9 23v150q0 13 9.5 22.5t22.5 9.5h97v383h-95q-14 0 -23 9.5t-9 22.5v131q0 14 9 23t23 9h95v223q0 171 123.5 282t314.5 111q185 0 335 -125q9 -8 10 -20.5t-7 -22.5l-103 -127q-9 -11 -22 -12q-13 -2 -23 7 q-5 5 -26 19t-69 32t-93 18q-85 0 -137 -47t-52 -123v-215h305q13 0 22.5 -9t9.5 -23v-131q0 -13 -9.5 -22.5t-22.5 -9.5h-305v-379h414v181q0 13 9 22.5t23 9.5h162q14 0 23 -9.5t9 -22.5z" />
+<glyph unicode="&#xf155;" horiz-adv-x="1024" d="M978 351q0 -153 -99.5 -263.5t-258.5 -136.5v-175q0 -14 -9 -23t-23 -9h-135q-13 0 -22.5 9.5t-9.5 22.5v175q-66 9 -127.5 31t-101.5 44.5t-74 48t-46.5 37.5t-17.5 18q-17 21 -2 41l103 135q7 10 23 12q15 2 24 -9l2 -2q113 -99 243 -125q37 -8 74 -8q81 0 142.5 43 t61.5 122q0 28 -15 53t-33.5 42t-58.5 37.5t-66 32t-80 32.5q-39 16 -61.5 25t-61.5 26.5t-62.5 31t-56.5 35.5t-53.5 42.5t-43.5 49t-35.5 58t-21 66.5t-8.5 78q0 138 98 242t255 134v180q0 13 9.5 22.5t22.5 9.5h135q14 0 23 -9t9 -23v-176q57 -6 110.5 -23t87 -33.5 t63.5 -37.5t39 -29t15 -14q17 -18 5 -38l-81 -146q-8 -15 -23 -16q-14 -3 -27 7q-3 3 -14.5 12t-39 26.5t-58.5 32t-74.5 26t-85.5 11.5q-95 0 -155 -43t-60 -111q0 -26 8.5 -48t29.5 -41.5t39.5 -33t56 -31t60.5 -27t70 -27.5q53 -20 81 -31.5t76 -35t75.5 -42.5t62 -50 t53 -63.5t31.5 -76.5t13 -94z" />
+<glyph unicode="&#xf156;" horiz-adv-x="898" d="M898 1066v-102q0 -14 -9 -23t-23 -9h-168q-23 -144 -129 -234t-276 -110q167 -178 459 -536q14 -16 4 -34q-8 -18 -29 -18h-195q-16 0 -25 12q-306 367 -498 571q-9 9 -9 22v127q0 13 9.5 22.5t22.5 9.5h112q132 0 212.5 43t102.5 125h-427q-14 0 -23 9t-9 23v102 q0 14 9 23t23 9h413q-57 113 -268 113h-145q-13 0 -22.5 9.5t-9.5 22.5v133q0 14 9 23t23 9h832q14 0 23 -9t9 -23v-102q0 -14 -9 -23t-23 -9h-233q47 -61 64 -144h171q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf157;" horiz-adv-x="1027" d="M603 0h-172q-13 0 -22.5 9t-9.5 23v330h-288q-13 0 -22.5 9t-9.5 23v103q0 13 9.5 22.5t22.5 9.5h288v85h-288q-13 0 -22.5 9t-9.5 23v104q0 13 9.5 22.5t22.5 9.5h214l-321 578q-8 16 0 32q10 16 28 16h194q19 0 29 -18l215 -425q19 -38 56 -125q10 24 30.5 68t27.5 61 l191 420q8 19 29 19h191q17 0 27 -16q9 -14 1 -31l-313 -579h215q13 0 22.5 -9.5t9.5 -22.5v-104q0 -14 -9.5 -23t-22.5 -9h-290v-85h290q13 0 22.5 -9.5t9.5 -22.5v-103q0 -14 -9.5 -23t-22.5 -9h-290v-330q0 -13 -9.5 -22.5t-22.5 -9.5z" />
+<glyph unicode="&#xf158;" horiz-adv-x="1280" d="M1043 971q0 100 -65 162t-171 62h-320v-448h320q106 0 171 62t65 162zM1280 971q0 -193 -126.5 -315t-326.5 -122h-340v-118h505q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-505v-192q0 -14 -9.5 -23t-22.5 -9h-167q-14 0 -23 9t-9 23v192h-224q-14 0 -23 9t-9 23v128 q0 14 9 23t23 9h224v118h-224q-14 0 -23 9t-9 23v149q0 13 9 22.5t23 9.5h224v629q0 14 9 23t23 9h539q200 0 326.5 -122t126.5 -315z" />
+<glyph unicode="&#xf159;" horiz-adv-x="1792" d="M514 341l81 299h-159l75 -300q1 -1 1 -3t1 -3q0 1 0.5 3.5t0.5 3.5zM630 768l35 128h-292l32 -128h225zM822 768h139l-35 128h-70zM1271 340l78 300h-162l81 -299q0 -1 0.5 -3.5t1.5 -3.5q0 1 0.5 3t0.5 3zM1382 768l33 128h-297l34 -128h230zM1792 736v-64q0 -14 -9 -23 t-23 -9h-213l-164 -616q-7 -24 -31 -24h-159q-24 0 -31 24l-166 616h-209l-167 -616q-7 -24 -31 -24h-159q-11 0 -19.5 7t-10.5 17l-160 616h-208q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h175l-33 128h-142q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h109l-89 344q-5 15 5 28 q10 12 26 12h137q26 0 31 -24l90 -360h359l97 360q7 24 31 24h126q24 0 31 -24l98 -360h365l93 360q5 24 31 24h137q16 0 26 -12q10 -13 5 -28l-91 -344h111q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-145l-34 -128h179q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf15a;" horiz-adv-x="1280" d="M1167 896q18 -182 -131 -258q117 -28 175 -103t45 -214q-7 -71 -32.5 -125t-64.5 -89t-97 -58.5t-121.5 -34.5t-145.5 -15v-255h-154v251q-80 0 -122 1v-252h-154v255q-18 0 -54 0.5t-55 0.5h-200l31 183h111q50 0 58 51v402h16q-6 1 -16 1v287q-13 68 -89 68h-111v164 l212 -1q64 0 97 1v252h154v-247q82 2 122 2v245h154v-252q79 -7 140 -22.5t113 -45t82.5 -78t36.5 -114.5zM952 351q0 36 -15 64t-37 46t-57.5 30.5t-65.5 18.5t-74 9t-69 3t-64.5 -1t-47.5 -1v-338q8 0 37 -0.5t48 -0.5t53 1.5t58.5 4t57 8.5t55.5 14t47.5 21t39.5 30 t24.5 40t9.5 51zM881 827q0 33 -12.5 58.5t-30.5 42t-48 28t-55 16.5t-61.5 8t-58 2.5t-54 -1t-39.5 -0.5v-307q5 0 34.5 -0.5t46.5 0t50 2t55 5.5t51.5 11t48.5 18.5t37 27t27 38.5t9 51z" />
+<glyph unicode="&#xf15b;" d="M1024 1024v472q22 -14 36 -28l408 -408q14 -14 28 -36h-472zM896 992q0 -40 28 -68t68 -28h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h800v-544z" />
+<glyph unicode="&#xf15c;" d="M1468 1060q14 -14 28 -36h-472v472q22 -14 36 -28zM992 896h544v-1056q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h800v-544q0 -40 28 -68t68 -28zM1152 160v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704 q14 0 23 9t9 23zM1152 416v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23zM1152 672v64q0 14 -9 23t-23 9h-704q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h704q14 0 23 9t9 23z" />
+<glyph unicode="&#xf15d;" horiz-adv-x="1664" d="M1191 1128h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1572 -23 v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -11v-2l14 2q9 2 30 2h248v119h121zM1661 874v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162 l230 -662h70z" />
+<glyph unicode="&#xf15e;" horiz-adv-x="1664" d="M1191 104h177l-72 218l-12 47q-2 16 -2 20h-4l-3 -20q0 -1 -3.5 -18t-7.5 -29zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1661 -150 v-106h-288v106h75l-47 144h-243l-47 -144h75v-106h-287v106h70l230 662h162l230 -662h70zM1572 1001v-233h-584v90l369 529q12 18 21 27l11 9v3q-2 0 -6.5 -0.5t-7.5 -0.5q-12 -3 -30 -3h-232v-115h-120v229h567v-89l-369 -530q-6 -8 -21 -26l-11 -10v-3l14 3q9 1 30 1h248 v119h121z" />
+<glyph unicode="&#xf160;" horiz-adv-x="1792" d="M736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23zM1792 -32v-192q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832 q14 0 23 -9t9 -23zM1600 480v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1408 992v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1216 1504v-192q0 -14 -9 -23t-23 -9h-256 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf161;" horiz-adv-x="1792" d="M1216 -32v-192q0 -14 -9 -23t-23 -9h-256q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h256q14 0 23 -9t9 -23zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192 q14 0 23 -9t9 -23zM1408 480v-192q0 -14 -9 -23t-23 -9h-448q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h448q14 0 23 -9t9 -23zM1600 992v-192q0 -14 -9 -23t-23 -9h-640q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h640q14 0 23 -9t9 -23zM1792 1504v-192q0 -14 -9 -23t-23 -9h-832 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h832q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf162;" d="M1346 223q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9t9 -23 zM1486 165q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5 t82 -252.5zM1456 882v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165z" />
+<glyph unicode="&#xf163;" d="M1346 1247q0 63 -44 116t-103 53q-52 0 -83 -37t-31 -94t36.5 -95t104.5 -38q50 0 85 27t35 68zM736 96q0 -12 -10 -24l-319 -319q-10 -9 -23 -9q-12 0 -23 9l-320 320q-15 16 -7 35q8 20 30 20h192v1376q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1376h192q14 0 23 -9 t9 -23zM1456 -142v-114h-469v114h167v432q0 7 0.5 19t0.5 17v16h-2l-7 -12q-8 -13 -26 -31l-62 -58l-82 86l192 185h123v-654h165zM1486 1189q0 -62 -13 -121.5t-41 -114t-68 -95.5t-98.5 -65.5t-127.5 -24.5q-62 0 -108 16q-24 8 -42 15l39 113q15 -7 31 -11q37 -13 75 -13 q84 0 134.5 58.5t66.5 145.5h-2q-21 -23 -61.5 -37t-84.5 -14q-106 0 -173 71.5t-67 172.5q0 105 72 178t181 73q123 0 205 -94.5t82 -252.5z" />
+<glyph unicode="&#xf164;" horiz-adv-x="1664" d="M256 192q0 26 -19 45t-45 19q-27 0 -45.5 -19t-18.5 -45q0 -27 18.5 -45.5t45.5 -18.5q26 0 45 18.5t19 45.5zM416 704v-640q0 -26 -19 -45t-45 -19h-288q-26 0 -45 19t-19 45v640q0 26 19 45t45 19h288q26 0 45 -19t19 -45zM1600 704q0 -86 -55 -149q15 -44 15 -76 q3 -76 -43 -137q17 -56 0 -117q-15 -57 -54 -94q9 -112 -49 -181q-64 -76 -197 -78h-36h-76h-17q-66 0 -144 15.5t-121.5 29t-120.5 39.5q-123 43 -158 44q-26 1 -45 19.5t-19 44.5v641q0 25 18 43.5t43 20.5q24 2 76 59t101 121q68 87 101 120q18 18 31 48t17.5 48.5 t13.5 60.5q7 39 12.5 61t19.5 52t34 50q19 19 45 19q46 0 82.5 -10.5t60 -26t40 -40.5t24 -45t12 -50t5 -45t0.5 -39q0 -38 -9.5 -76t-19 -60t-27.5 -56q-3 -6 -10 -18t-11 -22t-8 -24h277q78 0 135 -57t57 -135z" />
+<glyph unicode="&#xf165;" horiz-adv-x="1664" d="M256 960q0 -26 -19 -45t-45 -19q-27 0 -45.5 19t-18.5 45q0 27 18.5 45.5t45.5 18.5q26 0 45 -18.5t19 -45.5zM416 448v640q0 26 -19 45t-45 19h-288q-26 0 -45 -19t-19 -45v-640q0 -26 19 -45t45 -19h288q26 0 45 19t19 45zM1545 597q55 -61 55 -149q-1 -78 -57.5 -135 t-134.5 -57h-277q4 -14 8 -24t11 -22t10 -18q18 -37 27 -57t19 -58.5t10 -76.5q0 -24 -0.5 -39t-5 -45t-12 -50t-24 -45t-40 -40.5t-60 -26t-82.5 -10.5q-26 0 -45 19q-20 20 -34 50t-19.5 52t-12.5 61q-9 42 -13.5 60.5t-17.5 48.5t-31 48q-33 33 -101 120q-49 64 -101 121 t-76 59q-25 2 -43 20.5t-18 43.5v641q0 26 19 44.5t45 19.5q35 1 158 44q77 26 120.5 39.5t121.5 29t144 15.5h17h76h36q133 -2 197 -78q58 -69 49 -181q39 -37 54 -94q17 -61 0 -117q46 -61 43 -137q0 -32 -15 -76z" />
+<glyph unicode="&#xf166;" d="M919 233v157q0 50 -29 50q-17 0 -33 -16v-224q16 -16 33 -16q29 0 29 49zM1103 355h66v34q0 51 -33 51t-33 -51v-34zM532 621v-70h-80v-423h-74v423h-78v70h232zM733 495v-367h-67v40q-39 -45 -76 -45q-33 0 -42 28q-6 16 -6 54v290h66v-270q0 -24 1 -26q1 -15 15 -15 q20 0 42 31v280h67zM985 384v-146q0 -52 -7 -73q-12 -42 -53 -42q-35 0 -68 41v-36h-67v493h67v-161q32 40 68 40q41 0 53 -42q7 -21 7 -74zM1236 255v-9q0 -29 -2 -43q-3 -22 -15 -40q-27 -40 -80 -40q-52 0 -81 38q-21 27 -21 86v129q0 59 20 86q29 38 80 38t78 -38 q21 -28 21 -86v-76h-133v-65q0 -51 34 -51q24 0 30 26q0 1 0.5 7t0.5 16.5v21.5h68zM785 1079v-156q0 -51 -32 -51t-32 51v156q0 52 32 52t32 -52zM1318 366q0 177 -19 260q-10 44 -43 73.5t-76 34.5q-136 15 -412 15q-275 0 -411 -15q-44 -5 -76.5 -34.5t-42.5 -73.5 q-20 -87 -20 -260q0 -176 20 -260q10 -43 42.5 -73t75.5 -35q137 -15 412 -15t412 15q43 5 75.5 35t42.5 73q20 84 20 260zM563 1017l90 296h-75l-51 -195l-53 195h-78l24 -69t23 -69q35 -103 46 -158v-201h74v201zM852 936v130q0 58 -21 87q-29 38 -78 38q-51 0 -78 -38 q-21 -29 -21 -87v-130q0 -58 21 -87q27 -38 78 -38q49 0 78 38q21 27 21 87zM1033 816h67v370h-67v-283q-22 -31 -42 -31q-15 0 -16 16q-1 2 -1 26v272h-67v-293q0 -37 6 -55q11 -27 43 -27q36 0 77 45v-40zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960 q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf167;" d="M971 292v-211q0 -67 -39 -67q-23 0 -45 22v301q22 22 45 22q39 0 39 -67zM1309 291v-46h-90v46q0 68 45 68t45 -68zM343 509h107v94h-312v-94h105v-569h100v569zM631 -60h89v494h-89v-378q-30 -42 -57 -42q-18 0 -21 21q-1 3 -1 35v364h-89v-391q0 -49 8 -73 q12 -37 58 -37q48 0 102 61v-54zM1060 88v197q0 73 -9 99q-17 56 -71 56q-50 0 -93 -54v217h-89v-663h89v48q45 -55 93 -55q54 0 71 55q9 27 9 100zM1398 98v13h-91q0 -51 -2 -61q-7 -36 -40 -36q-46 0 -46 69v87h179v103q0 79 -27 116q-39 51 -106 51q-68 0 -107 -51 q-28 -37 -28 -116v-173q0 -79 29 -116q39 -51 108 -51q72 0 108 53q18 27 21 54q2 9 2 58zM790 1011v210q0 69 -43 69t-43 -69v-210q0 -70 43 -70t43 70zM1509 260q0 -234 -26 -350q-14 -59 -58 -99t-102 -46q-184 -21 -555 -21t-555 21q-58 6 -102.5 46t-57.5 99 q-26 112 -26 350q0 234 26 350q14 59 58 99t103 47q183 20 554 20t555 -20q58 -7 102.5 -47t57.5 -99q26 -112 26 -350zM511 1536h102l-121 -399v-271h-100v271q-14 74 -61 212q-37 103 -65 187h106l71 -263zM881 1203v-175q0 -81 -28 -118q-37 -51 -106 -51q-67 0 -105 51 q-28 38 -28 118v175q0 80 28 117q38 51 105 51q69 0 106 -51q28 -37 28 -117zM1216 1365v-499h-91v55q-53 -62 -103 -62q-46 0 -59 37q-8 24 -8 75v394h91v-367q0 -33 1 -35q3 -22 21 -22q27 0 57 43v381h91z" />
+<glyph unicode="&#xf168;" horiz-adv-x="1408" d="M597 869q-10 -18 -257 -456q-27 -46 -65 -46h-239q-21 0 -31 17t0 36l253 448q1 0 0 1l-161 279q-12 22 -1 37q9 15 32 15h239q40 0 66 -45zM1403 1511q11 -16 0 -37l-528 -934v-1l336 -615q11 -20 1 -37q-10 -15 -32 -15h-239q-42 0 -66 45l-339 622q18 32 531 942 q25 45 64 45h241q22 0 31 -15z" />
+<glyph unicode="&#xf169;" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf16a;" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" />
+<glyph unicode="&#xf16b;" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" />
+<glyph unicode="&#xf16c;" horiz-adv-x="1408" d="M928 135v-151l-707 -1v151zM1169 481v-701l-1 -35v-1h-1132l-35 1h-1v736h121v-618h928v618h120zM241 393l704 -65l-13 -150l-705 65zM309 709l683 -183l-39 -146l-683 183zM472 1058l609 -360l-77 -130l-609 360zM832 1389l398 -585l-124 -85l-399 584zM1285 1536 l121 -697l-149 -26l-121 697z" />
+<glyph unicode="&#xf16d;" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" />
+<glyph unicode="&#xf16e;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" />
+<glyph unicode="&#xf170;" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf171;" horiz-adv-x="1408" d="M815 677q8 -63 -50.5 -101t-111.5 -6q-39 17 -53.5 58t-0.5 82t52 58q36 18 72.5 12t64 -35.5t27.5 -67.5zM926 698q-14 107 -113 164t-197 13q-63 -28 -100.5 -88.5t-34.5 -129.5q4 -91 77.5 -155t165.5 -56q91 8 152 84t50 168zM1165 1240q-20 27 -56 44.5t-58 22 t-71 12.5q-291 47 -566 -2q-43 -7 -66 -12t-55 -22t-50 -43q30 -28 76 -45.5t73.5 -22t87.5 -11.5q228 -29 448 -1q63 8 89.5 12t72.5 21.5t75 46.5zM1222 205q-8 -26 -15.5 -76.5t-14 -84t-28.5 -70t-58 -56.5q-86 -48 -189.5 -71.5t-202 -22t-201.5 18.5q-46 8 -81.5 18 t-76.5 27t-73 43.5t-52 61.5q-25 96 -57 292l6 16l18 9q223 -148 506.5 -148t507.5 148q21 -6 24 -23t-5 -45t-8 -37zM1403 1166q-26 -167 -111 -655q-5 -30 -27 -56t-43.5 -40t-54.5 -31q-252 -126 -610 -88q-248 27 -394 139q-15 12 -25.5 26.5t-17 35t-9 34t-6 39.5 t-5.5 35q-9 50 -26.5 150t-28 161.5t-23.5 147.5t-22 158q3 26 17.5 48.5t31.5 37.5t45 30t46 22.5t48 18.5q125 46 313 64q379 37 676 -50q155 -46 215 -122q16 -20 16.5 -51t-5.5 -54z" />
+<glyph unicode="&#xf172;" d="M848 666q0 43 -41 66t-77 1q-43 -20 -42.5 -72.5t43.5 -70.5q39 -23 81 4t36 72zM928 682q8 -66 -36 -121t-110 -61t-119 40t-56 113q-2 49 25.5 93t72.5 64q70 31 141.5 -10t81.5 -118zM1100 1073q-20 -21 -53.5 -34t-53 -16t-63.5 -8q-155 -20 -324 0q-44 6 -63 9.5 t-52.5 16t-54.5 32.5q13 19 36 31t40 15.5t47 8.5q198 35 408 1q33 -5 51 -8.5t43 -16t39 -31.5zM1142 327q0 7 5.5 26.5t3 32t-17.5 16.5q-161 -106 -365 -106t-366 106l-12 -6l-5 -12q26 -154 41 -210q47 -81 204 -108q249 -46 428 53q34 19 49 51.5t22.5 85.5t12.5 71z M1272 1020q9 53 -8 75q-43 55 -155 88q-216 63 -487 36q-132 -12 -226 -46q-38 -15 -59.5 -25t-47 -34t-29.5 -54q8 -68 19 -138t29 -171t24 -137q1 -5 5 -31t7 -36t12 -27t22 -28q105 -80 284 -100q259 -28 440 63q24 13 39.5 23t31 29t19.5 40q48 267 80 473zM1536 1120 v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf173;" horiz-adv-x="1024" d="M944 207l80 -237q-23 -35 -111 -66t-177 -32q-104 -2 -190.5 26t-142.5 74t-95 106t-55.5 120t-16.5 118v544h-168v215q72 26 129 69.5t91 90t58 102t34 99t15 88.5q1 5 4.5 8.5t7.5 3.5h244v-424h333v-252h-334v-518q0 -30 6.5 -56t22.5 -52.5t49.5 -41.5t81.5 -14 q78 2 134 29z" />
+<glyph unicode="&#xf174;" d="M1136 75l-62 183q-44 -22 -103 -22q-36 -1 -62 10.5t-38.5 31.5t-17.5 40.5t-5 43.5v398h257v194h-256v326h-188q-8 0 -9 -10q-5 -44 -17.5 -87t-39 -95t-77 -95t-118.5 -68v-165h130v-418q0 -57 21.5 -115t65 -111t121 -85.5t176.5 -30.5q69 1 136.5 25t85.5 50z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf175;" horiz-adv-x="768" d="M765 237q8 -19 -5 -35l-350 -384q-10 -10 -23 -10q-14 0 -24 10l-355 384q-13 16 -5 35q9 19 29 19h224v1248q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-1248h224q21 0 29 -19z" />
+<glyph unicode="&#xf176;" horiz-adv-x="768" d="M765 1043q-9 -19 -29 -19h-224v-1248q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1248h-224q-21 0 -29 19t5 35l350 384q10 10 23 10q14 0 24 -10l355 -384q13 -16 5 -35z" />
+<glyph unicode="&#xf177;" horiz-adv-x="1792" d="M1792 736v-192q0 -14 -9 -23t-23 -9h-1248v-224q0 -21 -19 -29t-35 5l-384 350q-10 10 -10 23q0 14 10 24l384 354q16 14 35 6q19 -9 19 -29v-224h1248q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf178;" horiz-adv-x="1792" d="M1728 643q0 -14 -10 -24l-384 -354q-16 -14 -35 -6q-19 9 -19 29v224h-1248q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h1248v224q0 21 19 29t35 -5l384 -350q10 -10 10 -23z" />
+<glyph unicode="&#xf179;" horiz-adv-x="1408" d="M1393 321q-39 -125 -123 -250q-129 -196 -257 -196q-49 0 -140 32q-86 32 -151 32q-61 0 -142 -33q-81 -34 -132 -34q-152 0 -301 259q-147 261 -147 503q0 228 113 374q112 144 284 144q72 0 177 -30q104 -30 138 -30q45 0 143 34q102 34 173 34q119 0 213 -65 q52 -36 104 -100q-79 -67 -114 -118q-65 -94 -65 -207q0 -124 69 -223t158 -126zM1017 1494q0 -61 -29 -136q-30 -75 -93 -138q-54 -54 -108 -72q-37 -11 -104 -17q3 149 78 257q74 107 250 148q1 -3 2.5 -11t2.5 -11q0 -4 0.5 -10t0.5 -10z" />
+<glyph unicode="&#xf17a;" horiz-adv-x="1664" d="M682 530v-651l-682 94v557h682zM682 1273v-659h-682v565zM1664 530v-786l-907 125v661h907zM1664 1408v-794h-907v669z" />
+<glyph unicode="&#xf17b;" horiz-adv-x="1408" d="M493 1053q16 0 27.5 11.5t11.5 27.5t-11.5 27.5t-27.5 11.5t-27 -11.5t-11 -27.5t11 -27.5t27 -11.5zM915 1053q16 0 27 11.5t11 27.5t-11 27.5t-27 11.5t-27.5 -11.5t-11.5 -27.5t11.5 -27.5t27.5 -11.5zM103 869q42 0 72 -30t30 -72v-430q0 -43 -29.5 -73t-72.5 -30 t-73 30t-30 73v430q0 42 30 72t73 30zM1163 850v-666q0 -46 -32 -78t-77 -32h-75v-227q0 -43 -30 -73t-73 -30t-73 30t-30 73v227h-138v-227q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73l-1 227h-74q-46 0 -78 32t-32 78v666h918zM931 1255q107 -55 171 -153.5t64 -215.5 h-925q0 117 64 215.5t172 153.5l-71 131q-7 13 5 20q13 6 20 -6l72 -132q95 42 201 42t201 -42l72 132q7 12 20 6q12 -7 5 -20zM1408 767v-430q0 -43 -30 -73t-73 -30q-42 0 -72 30t-30 73v430q0 43 30 72.5t72 29.5q43 0 73 -29.5t30 -72.5z" />
+<glyph unicode="&#xf17c;" d="M663 1125q-11 -1 -15.5 -10.5t-8.5 -9.5q-5 -1 -5 5q0 12 19 15h10zM750 1111q-4 -1 -11.5 6.5t-17.5 4.5q24 11 32 -2q3 -6 -3 -9zM399 684q-4 1 -6 -3t-4.5 -12.5t-5.5 -13.5t-10 -13q-7 -10 -1 -12q4 -1 12.5 7t12.5 18q1 3 2 7t2 6t1.5 4.5t0.5 4v3t-1 2.5t-3 2z M1254 325q0 18 -55 42q4 15 7.5 27.5t5 26t3 21.5t0.5 22.5t-1 19.5t-3.5 22t-4 20.5t-5 25t-5.5 26.5q-10 48 -47 103t-72 75q24 -20 57 -83q87 -162 54 -278q-11 -40 -50 -42q-31 -4 -38.5 18.5t-8 83.5t-11.5 107q-9 39 -19.5 69t-19.5 45.5t-15.5 24.5t-13 15t-7.5 7 q-14 62 -31 103t-29.5 56t-23.5 33t-15 40q-4 21 6 53.5t4.5 49.5t-44.5 25q-15 3 -44.5 18t-35.5 16q-8 1 -11 26t8 51t36 27q37 3 51 -30t4 -58q-11 -19 -2 -26.5t30 -0.5q13 4 13 36v37q-5 30 -13.5 50t-21 30.5t-23.5 15t-27 7.5q-107 -8 -89 -134q0 -15 -1 -15 q-9 9 -29.5 10.5t-33 -0.5t-15.5 5q1 57 -16 90t-45 34q-27 1 -41.5 -27.5t-16.5 -59.5q-1 -15 3.5 -37t13 -37.5t15.5 -13.5q10 3 16 14q4 9 -7 8q-7 0 -15.5 14.5t-9.5 33.5q-1 22 9 37t34 14q17 0 27 -21t9.5 -39t-1.5 -22q-22 -15 -31 -29q-8 -12 -27.5 -23.5 t-20.5 -12.5q-13 -14 -15.5 -27t7.5 -18q14 -8 25 -19.5t16 -19t18.5 -13t35.5 -6.5q47 -2 102 15q2 1 23 7t34.5 10.5t29.5 13t21 17.5q9 14 20 8q5 -3 6.5 -8.5t-3 -12t-16.5 -9.5q-20 -6 -56.5 -21.5t-45.5 -19.5q-44 -19 -70 -23q-25 -5 -79 2q-10 2 -9 -2t17 -19 q25 -23 67 -22q17 1 36 7t36 14t33.5 17.5t30 17t24.5 12t17.5 2.5t8.5 -11q0 -2 -1 -4.5t-4 -5t-6 -4.5t-8.5 -5t-9 -4.5t-10 -5t-9.5 -4.5q-28 -14 -67.5 -44t-66.5 -43t-49 -1q-21 11 -63 73q-22 31 -25 22q-1 -3 -1 -10q0 -25 -15 -56.5t-29.5 -55.5t-21 -58t11.5 -63 q-23 -6 -62.5 -90t-47.5 -141q-2 -18 -1.5 -69t-5.5 -59q-8 -24 -29 -3q-32 31 -36 94q-2 28 4 56q4 19 -1 18l-4 -5q-36 -65 10 -166q5 -12 25 -28t24 -20q20 -23 104 -90.5t93 -76.5q16 -15 17.5 -38t-14 -43t-45.5 -23q8 -15 29 -44.5t28 -54t7 -70.5q46 24 7 92 q-4 8 -10.5 16t-9.5 12t-2 6q3 5 13 9.5t20 -2.5q46 -52 166 -36q133 15 177 87q23 38 34 30q12 -6 10 -52q-1 -25 -23 -92q-9 -23 -6 -37.5t24 -15.5q3 19 14.5 77t13.5 90q2 21 -6.5 73.5t-7.5 97t23 70.5q15 18 51 18q1 37 34.5 53t72.5 10.5t60 -22.5zM626 1152 q3 17 -2.5 30t-11.5 15q-9 2 -9 -7q2 -5 5 -6q10 0 7 -15q-3 -20 8 -20q3 0 3 3zM1045 955q-2 8 -6.5 11.5t-13 5t-14.5 5.5q-5 3 -9.5 8t-7 8t-5.5 6.5t-4 4t-4 -1.5q-14 -16 7 -43.5t39 -31.5q9 -1 14.5 8t3.5 20zM867 1168q0 11 -5 19.5t-11 12.5t-9 3q-14 -1 -7 -7l4 -2 q14 -4 18 -31q0 -3 8 2zM921 1401q0 2 -2.5 5t-9 7t-9.5 6q-15 15 -24 15q-9 -1 -11.5 -7.5t-1 -13t-0.5 -12.5q-1 -4 -6 -10.5t-6 -9t3 -8.5q4 -3 8 0t11 9t15 9q1 1 9 1t15 2t9 7zM1486 60q20 -12 31 -24.5t12 -24t-2.5 -22.5t-15.5 -22t-23.5 -19.5t-30 -18.5 t-31.5 -16.5t-32 -15.5t-27 -13q-38 -19 -85.5 -56t-75.5 -64q-17 -16 -68 -19.5t-89 14.5q-18 9 -29.5 23.5t-16.5 25.5t-22 19.5t-47 9.5q-44 1 -130 1q-19 0 -57 -1.5t-58 -2.5q-44 -1 -79.5 -15t-53.5 -30t-43.5 -28.5t-53.5 -11.5q-29 1 -111 31t-146 43q-19 4 -51 9.5 t-50 9t-39.5 9.5t-33.5 14.5t-17 19.5q-10 23 7 66.5t18 54.5q1 16 -4 40t-10 42.5t-4.5 36.5t10.5 27q14 12 57 14t60 12q30 18 42 35t12 51q21 -73 -32 -106q-32 -20 -83 -15q-34 3 -43 -10q-13 -15 5 -57q2 -6 8 -18t8.5 -18t4.5 -17t1 -22q0 -15 -17 -49t-14 -48 q3 -17 37 -26q20 -6 84.5 -18.5t99.5 -20.5q24 -6 74 -22t82.5 -23t55.5 -4q43 6 64.5 28t23 48t-7.5 58.5t-19 52t-20 36.5q-121 190 -169 242q-68 74 -113 40q-11 -9 -15 15q-3 16 -2 38q1 29 10 52t24 47t22 42q8 21 26.5 72t29.5 78t30 61t39 54q110 143 124 195 q-12 112 -16 310q-2 90 24 151.5t106 104.5q39 21 104 21q53 1 106 -13.5t89 -41.5q57 -42 91.5 -121.5t29.5 -147.5q-5 -95 30 -214q34 -113 133 -218q55 -59 99.5 -163t59.5 -191q8 -49 5 -84.5t-12 -55.5t-20 -22q-10 -2 -23.5 -19t-27 -35.5t-40.5 -33.5t-61 -14 q-18 1 -31.5 5t-22.5 13.5t-13.5 15.5t-11.5 20.5t-9 19.5q-22 37 -41 30t-28 -49t7 -97q20 -70 1 -195q-10 -65 18 -100.5t73 -33t85 35.5q59 49 89.5 66.5t103.5 42.5q53 18 77 36.5t18.5 34.5t-25 28.5t-51.5 23.5q-33 11 -49.5 48t-15 72.5t15.5 47.5q1 -31 8 -56.5 t14.5 -40.5t20.5 -28.5t21 -19t21.5 -13t16.5 -9.5z" />
+<glyph unicode="&#xf17d;" d="M1024 36q-42 241 -140 498h-2l-2 -1q-16 -6 -43 -16.5t-101 -49t-137 -82t-131 -114.5t-103 -148l-15 11q184 -150 418 -150q132 0 256 52zM839 643q-21 49 -53 111q-311 -93 -673 -93q-1 -7 -1 -21q0 -124 44 -236.5t124 -201.5q50 89 123.5 166.5t142.5 124.5t130.5 81 t99.5 48l37 13q4 1 13 3.5t13 4.5zM732 855q-120 213 -244 378q-138 -65 -234 -186t-128 -272q302 0 606 80zM1416 536q-210 60 -409 29q87 -239 128 -469q111 75 185 189.5t96 250.5zM611 1277q-1 0 -2 -1q1 1 2 1zM1201 1132q-185 164 -433 164q-76 0 -155 -19 q131 -170 246 -382q69 26 130 60.5t96.5 61.5t65.5 57t37.5 40.5zM1424 647q-3 232 -149 410l-1 -1q-9 -12 -19 -24.5t-43.5 -44.5t-71 -60.5t-100 -65t-131.5 -64.5q25 -53 44 -95q2 -6 6.5 -17.5t7.5 -16.5q36 5 74.5 7t73.5 2t69 -1.5t64 -4t56.5 -5.5t48 -6.5t36.5 -6 t25 -4.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf17e;" d="M1173 473q0 50 -19.5 91.5t-48.5 68.5t-73 49t-82.5 34t-87.5 23l-104 24q-30 7 -44 10.5t-35 11.5t-30 16t-16.5 21t-7.5 30q0 77 144 77q43 0 77 -12t54 -28.5t38 -33.5t40 -29t48 -12q47 0 75.5 32t28.5 77q0 55 -56 99.5t-142 67.5t-182 23q-68 0 -132 -15.5 t-119.5 -47t-89 -87t-33.5 -128.5q0 -61 19 -106.5t56 -75.5t80 -48.5t103 -32.5l146 -36q90 -22 112 -36q32 -20 32 -60q0 -39 -40 -64.5t-105 -25.5q-51 0 -91.5 16t-65 38.5t-45.5 45t-46 38.5t-54 16q-50 0 -75.5 -30t-25.5 -75q0 -92 122 -157.5t291 -65.5 q73 0 140 18.5t122.5 53.5t88.5 93.5t33 131.5zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5q-130 0 -234 80q-77 -16 -150 -16q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5q0 73 16 150q-80 104 -80 234q0 159 112.5 271.5t271.5 112.5q130 0 234 -80 q77 16 150 16q143 0 273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -73 -16 -150q80 -104 80 -234z" />
+<glyph unicode="&#xf180;" horiz-adv-x="1280" d="M1000 1102l37 194q5 23 -9 40t-35 17h-712q-23 0 -38.5 -17t-15.5 -37v-1101q0 -7 6 -1l291 352q23 26 38 33.5t48 7.5h239q22 0 37 14.5t18 29.5q24 130 37 191q4 21 -11.5 40t-36.5 19h-294q-29 0 -48 19t-19 48v42q0 29 19 47.5t48 18.5h346q18 0 35 13.5t20 29.5z M1227 1324q-15 -73 -53.5 -266.5t-69.5 -350t-35 -173.5q-6 -22 -9 -32.5t-14 -32.5t-24.5 -33t-38.5 -21t-58 -10h-271q-13 0 -22 -10q-8 -9 -426 -494q-22 -25 -58.5 -28.5t-48.5 5.5q-55 22 -55 98v1410q0 55 38 102.5t120 47.5h888q95 0 127 -53t10 -159zM1227 1324 l-158 -790q4 17 35 173.5t69.5 350t53.5 266.5z" />
+<glyph unicode="&#xf181;" d="M704 192v1024q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-1024q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1376 576v640q0 14 -9 23t-23 9h-480q-14 0 -23 -9t-9 -23v-640q0 -14 9 -23t23 -9h480q14 0 23 9t9 23zM1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408 q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf182;" horiz-adv-x="1280" d="M1280 480q0 -40 -28 -68t-68 -28q-51 0 -80 43l-227 341h-45v-132l247 -411q9 -15 9 -33q0 -26 -19 -45t-45 -19h-192v-272q0 -46 -33 -79t-79 -33h-160q-46 0 -79 33t-33 79v272h-192q-26 0 -45 19t-19 45q0 18 9 33l247 411v132h-45l-227 -341q-29 -43 -80 -43 q-40 0 -68 28t-28 68q0 29 16 53l256 384q73 107 176 107h384q103 0 176 -107l256 -384q16 -24 16 -53zM864 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf183;" horiz-adv-x="1024" d="M1024 832v-416q0 -40 -28 -68t-68 -28t-68 28t-28 68v352h-64v-912q0 -46 -33 -79t-79 -33t-79 33t-33 79v464h-64v-464q0 -46 -33 -79t-79 -33t-79 33t-33 79v912h-64v-352q0 -40 -28 -68t-68 -28t-68 28t-28 68v416q0 80 56 136t136 56h640q80 0 136 -56t56 -136z M736 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf184;" d="M773 234l350 473q16 22 24.5 59t-6 85t-61.5 79q-40 26 -83 25.5t-73.5 -17.5t-54.5 -45q-36 -40 -96 -40q-59 0 -95 40q-24 28 -54.5 45t-73.5 17.5t-84 -25.5q-46 -31 -60.5 -79t-6 -85t24.5 -59zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf185;" horiz-adv-x="1792" d="M1472 640q0 117 -45.5 223.5t-123 184t-184 123t-223.5 45.5t-223.5 -45.5t-184 -123t-123 -184t-45.5 -223.5t45.5 -223.5t123 -184t184 -123t223.5 -45.5t223.5 45.5t184 123t123 184t45.5 223.5zM1748 363q-4 -15 -20 -20l-292 -96v-306q0 -16 -13 -26q-15 -10 -29 -4 l-292 94l-180 -248q-10 -13 -26 -13t-26 13l-180 248l-292 -94q-14 -6 -29 4q-13 10 -13 26v306l-292 96q-16 5 -20 20q-5 17 4 29l180 248l-180 248q-9 13 -4 29q4 15 20 20l292 96v306q0 16 13 26q15 10 29 4l292 -94l180 248q9 12 26 12t26 -12l180 -248l292 94 q14 6 29 -4q13 -10 13 -26v-306l292 -96q16 -5 20 -20q5 -16 -4 -29l-180 -248l180 -248q9 -12 4 -29z" />
+<glyph unicode="&#xf186;" d="M1262 233q-54 -9 -110 -9q-182 0 -337 90t-245 245t-90 337q0 192 104 357q-201 -60 -328.5 -229t-127.5 -384q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51q144 0 273.5 61.5t220.5 171.5zM1465 318q-94 -203 -283.5 -324.5t-413.5 -121.5q-156 0 -298 61 t-245 164t-164 245t-61 298q0 153 57.5 292.5t156 241.5t235.5 164.5t290 68.5q44 2 61 -39q18 -41 -15 -72q-86 -78 -131.5 -181.5t-45.5 -218.5q0 -148 73 -273t198 -198t273 -73q118 0 228 51q41 18 72 -13q14 -14 17.5 -34t-4.5 -38z" />
+<glyph unicode="&#xf187;" horiz-adv-x="1792" d="M1088 704q0 26 -19 45t-45 19h-256q-26 0 -45 -19t-19 -45t19 -45t45 -19h256q26 0 45 19t19 45zM1664 896v-960q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v960q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1728 1344v-256q0 -26 -19 -45t-45 -19h-1536 q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1536q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf188;" horiz-adv-x="1664" d="M1632 576q0 -26 -19 -45t-45 -19h-224q0 -171 -67 -290l208 -209q19 -19 19 -45t-19 -45q-18 -19 -45 -19t-45 19l-198 197q-5 -5 -15 -13t-42 -28.5t-65 -36.5t-82 -29t-97 -13v896h-128v-896q-51 0 -101.5 13.5t-87 33t-66 39t-43.5 32.5l-15 14l-183 -207 q-20 -21 -48 -21q-24 0 -43 16q-19 18 -20.5 44.5t15.5 46.5l202 227q-58 114 -58 274h-224q-26 0 -45 19t-19 45t19 45t45 19h224v294l-173 173q-19 19 -19 45t19 45t45 19t45 -19l173 -173h844l173 173q19 19 45 19t45 -19t19 -45t-19 -45l-173 -173v-294h224q26 0 45 -19 t19 -45zM1152 1152h-640q0 133 93.5 226.5t226.5 93.5t226.5 -93.5t93.5 -226.5z" />
+<glyph unicode="&#xf189;" horiz-adv-x="1920" d="M1917 1016q23 -64 -150 -294q-24 -32 -65 -85q-78 -100 -90 -131q-17 -41 14 -81q17 -21 81 -82h1l1 -1l1 -1l2 -2q141 -131 191 -221q3 -5 6.5 -12.5t7 -26.5t-0.5 -34t-25 -27.5t-59 -12.5l-256 -4q-24 -5 -56 5t-52 22l-20 12q-30 21 -70 64t-68.5 77.5t-61 58 t-56.5 15.5q-3 -1 -8 -3.5t-17 -14.5t-21.5 -29.5t-17 -52t-6.5 -77.5q0 -15 -3.5 -27.5t-7.5 -18.5l-4 -5q-18 -19 -53 -22h-115q-71 -4 -146 16.5t-131.5 53t-103 66t-70.5 57.5l-25 24q-10 10 -27.5 30t-71.5 91t-106 151t-122.5 211t-130.5 272q-6 16 -6 27t3 16l4 6 q15 19 57 19l274 2q12 -2 23 -6.5t16 -8.5l5 -3q16 -11 24 -32q20 -50 46 -103.5t41 -81.5l16 -29q29 -60 56 -104t48.5 -68.5t41.5 -38.5t34 -14t27 5q2 1 5 5t12 22t13.5 47t9.5 81t0 125q-2 40 -9 73t-14 46l-6 12q-25 34 -85 43q-13 2 5 24q17 19 38 30q53 26 239 24 q82 -1 135 -13q20 -5 33.5 -13.5t20.5 -24t10.5 -32t3.5 -45.5t-1 -55t-2.5 -70.5t-1.5 -82.5q0 -11 -1 -42t-0.5 -48t3.5 -40.5t11.5 -39t22.5 -24.5q8 -2 17 -4t26 11t38 34.5t52 67t68 107.5q60 104 107 225q4 10 10 17.5t11 10.5l4 3l5 2.5t13 3t20 0.5l288 2 q39 5 64 -2.5t31 -16.5z" />
+<glyph unicode="&#xf18a;" horiz-adv-x="1792" d="M675 252q21 34 11 69t-45 50q-34 14 -73 1t-60 -46q-22 -34 -13 -68.5t43 -50.5t74.5 -2.5t62.5 47.5zM769 373q8 13 3.5 26.5t-17.5 18.5q-14 5 -28.5 -0.5t-21.5 -18.5q-17 -31 13 -45q14 -5 29 0.5t22 18.5zM943 266q-45 -102 -158 -150t-224 -12 q-107 34 -147.5 126.5t6.5 187.5q47 93 151.5 139t210.5 19q111 -29 158.5 -119.5t2.5 -190.5zM1255 426q-9 96 -89 170t-208.5 109t-274.5 21q-223 -23 -369.5 -141.5t-132.5 -264.5q9 -96 89 -170t208.5 -109t274.5 -21q223 23 369.5 141.5t132.5 264.5zM1563 422 q0 -68 -37 -139.5t-109 -137t-168.5 -117.5t-226 -83t-270.5 -31t-275 33.5t-240.5 93t-171.5 151t-65 199.5q0 115 69.5 245t197.5 258q169 169 341.5 236t246.5 -7q65 -64 20 -209q-4 -14 -1 -20t10 -7t14.5 0.5t13.5 3.5l6 2q139 59 246 59t153 -61q45 -63 0 -178 q-2 -13 -4.5 -20t4.5 -12.5t12 -7.5t17 -6q57 -18 103 -47t80 -81.5t34 -116.5zM1489 1046q42 -47 54.5 -108.5t-6.5 -117.5q-8 -23 -29.5 -34t-44.5 -4q-23 8 -34 29.5t-4 44.5q20 63 -24 111t-107 35q-24 -5 -45 8t-25 37q-5 24 8 44.5t37 25.5q60 13 119 -5.5t101 -65.5z M1670 1209q87 -96 112.5 -222.5t-13.5 -241.5q-9 -27 -34 -40t-52 -4t-40 34t-5 52q28 82 10 172t-80 158q-62 69 -148 95.5t-173 8.5q-28 -6 -52 9.5t-30 43.5t9.5 51.5t43.5 29.5q123 26 244 -11.5t208 -134.5z" />
+<glyph unicode="&#xf18b;" d="M1133 -34q-171 -94 -368 -94q-196 0 -367 94q138 87 235.5 211t131.5 268q35 -144 132.5 -268t235.5 -211zM638 1394v-485q0 -252 -126.5 -459.5t-330.5 -306.5q-181 215 -181 495q0 187 83.5 349.5t229.5 269.5t325 137zM1536 638q0 -280 -181 -495 q-204 99 -330.5 306.5t-126.5 459.5v485q179 -30 325 -137t229.5 -269.5t83.5 -349.5z" />
+<glyph unicode="&#xf18c;" horiz-adv-x="1408" d="M1402 433q-32 -80 -76 -138t-91 -88.5t-99 -46.5t-101.5 -14.5t-96.5 8.5t-86.5 22t-69.5 27.5t-46 22.5l-17 10q-113 -228 -289.5 -359.5t-384.5 -132.5q-19 0 -32 13t-13 32t13 31.5t32 12.5q173 1 322.5 107.5t251.5 294.5q-36 -14 -72 -23t-83 -13t-91 2.5t-93 28.5 t-92 59t-84.5 100t-74.5 146q114 47 214 57t167.5 -7.5t124.5 -56.5t88.5 -77t56.5 -82q53 131 79 291q-7 -1 -18 -2.5t-46.5 -2.5t-69.5 0.5t-81.5 10t-88.5 23t-84 42.5t-75 65t-54.5 94.5t-28.5 127.5q70 28 133.5 36.5t112.5 -1t92 -30t73.5 -50t56 -61t42 -63t27.5 -56 t16 -39.5l4 -16q12 122 12 195q-8 6 -21.5 16t-49 44.5t-63.5 71.5t-54 93t-33 112.5t12 127t70 138.5q73 -25 127.5 -61.5t84.5 -76.5t48 -85t20.5 -89t-0.5 -85.5t-13 -76.5t-19 -62t-17 -42l-7 -15q1 -5 1 -50.5t-1 -71.5q3 7 10 18.5t30.5 43t50.5 58t71 55.5t91.5 44.5 t112 14.5t132.5 -24q-2 -78 -21.5 -141.5t-50 -104.5t-69.5 -71.5t-81.5 -45.5t-84.5 -24t-80 -9.5t-67.5 1t-46.5 4.5l-17 3q-23 -147 -73 -283q6 7 18 18.5t49.5 41t77.5 52.5t99.5 42t117.5 20t129 -23.5t137 -77.5z" />
+<glyph unicode="&#xf18d;" horiz-adv-x="1280" d="M1259 283v-66q0 -85 -57.5 -144.5t-138.5 -59.5h-57l-260 -269v269h-529q-81 0 -138.5 59.5t-57.5 144.5v66h1238zM1259 609v-255h-1238v255h1238zM1259 937v-255h-1238v255h1238zM1259 1077v-67h-1238v67q0 84 57.5 143.5t138.5 59.5h846q81 0 138.5 -59.5t57.5 -143.5z " />
+<glyph unicode="&#xf18e;" d="M1152 640q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf190;" d="M1152 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-192q0 -14 -9 -23t-23 -9q-12 0 -24 10l-319 319q-9 9 -9 23t9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h352q13 0 22.5 -9.5t9.5 -22.5zM1312 640q0 148 -73 273t-198 198t-273 73t-273 -73t-198 -198 t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf191;" d="M1024 960v-640q0 -26 -19 -45t-45 -19q-20 0 -37 12l-448 320q-27 19 -27 52t27 52l448 320q17 12 37 12q26 0 45 -19t19 -45zM1280 160v960q0 13 -9.5 22.5t-22.5 9.5h-960q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h960q13 0 22.5 9.5t9.5 22.5z M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf192;" d="M1024 640q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM768 1184q-148 0 -273 -73t-198 -198t-73 -273t73 -273t198 -198t273 -73t273 73t198 198t73 273t-73 273t-198 198t-273 73zM1536 640q0 -209 -103 -385.5t-279.5 -279.5 t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf193;" horiz-adv-x="1664" d="M1023 349l102 -204q-58 -179 -210 -290t-339 -111q-156 0 -288.5 77.5t-210 210t-77.5 288.5q0 181 104.5 330t274.5 211l17 -131q-122 -54 -195 -165.5t-73 -244.5q0 -185 131.5 -316.5t316.5 -131.5q126 0 232.5 65t165 175.5t49.5 236.5zM1571 249l58 -114l-256 -128 q-13 -7 -29 -7q-40 0 -57 35l-239 477h-472q-24 0 -42.5 16.5t-21.5 40.5l-96 779q-2 16 6 42q14 51 57 82.5t97 31.5q66 0 113 -47t47 -113q0 -69 -52 -117.5t-120 -41.5l37 -289h423v-128h-407l16 -128h455q40 0 57 -35l228 -455z" />
+<glyph unicode="&#xf194;" d="M1292 898q10 216 -161 222q-231 8 -312 -261q44 19 82 19q85 0 74 -96q-4 -57 -74 -167t-105 -110q-43 0 -82 169q-13 54 -45 255q-30 189 -160 177q-59 -7 -164 -100l-81 -72l-81 -72l52 -67q76 52 87 52q57 0 107 -179q15 -55 45 -164.5t45 -164.5q68 -179 164 -179 q157 0 383 294q220 283 226 444zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf195;" horiz-adv-x="1152" d="M1152 704q0 -191 -94.5 -353t-256.5 -256.5t-353 -94.5h-160q-14 0 -23 9t-9 23v611l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v93l-215 -66q-3 -1 -9 -1q-10 0 -19 6q-13 10 -13 26v128q0 23 23 31l233 71v250q0 14 9 23t23 9h160 q14 0 23 -9t9 -23v-181l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-93l375 116q15 5 28 -5t13 -26v-128q0 -23 -23 -31l-393 -121v-487q188 13 318 151t130 328q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf196;" horiz-adv-x="1408" d="M1152 736v-64q0 -14 -9 -23t-23 -9h-352v-352q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v352h-352q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h352v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-352h352q14 0 23 -9t9 -23zM1280 288v832q0 66 -47 113t-113 47h-832 q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf197;" horiz-adv-x="2176" d="M620 416q-110 -64 -268 -64h-128v64h-64q-13 0 -22.5 23.5t-9.5 56.5q0 24 7 49q-58 2 -96.5 10.5t-38.5 20.5t38.5 20.5t96.5 10.5q-7 25 -7 49q0 33 9.5 56.5t22.5 23.5h64v64h128q158 0 268 -64h1113q42 -7 106.5 -18t80.5 -14q89 -15 150 -40.5t83.5 -47.5t22.5 -40 t-22.5 -40t-83.5 -47.5t-150 -40.5q-16 -3 -80.5 -14t-106.5 -18h-1113zM1739 668q53 -36 53 -92t-53 -92l81 -30q68 48 68 122t-68 122zM625 400h1015q-217 -38 -456 -80q-57 0 -113 -24t-83 -48l-28 -24l-288 -288q-26 -26 -70.5 -45t-89.5 -19h-96l-93 464h29 q157 0 273 64zM352 816h-29l93 464h96q46 0 90 -19t70 -45l288 -288q4 -4 11 -10.5t30.5 -23t48.5 -29t61.5 -23t72.5 -10.5l456 -80h-1015q-116 64 -273 64z" />
+<glyph unicode="&#xf198;" horiz-adv-x="1664" d="M1519 760q62 0 103.5 -40.5t41.5 -101.5q0 -97 -93 -130l-172 -59l56 -167q7 -21 7 -47q0 -59 -42 -102t-101 -43q-47 0 -85.5 27t-53.5 72l-55 165l-310 -106l55 -164q8 -24 8 -47q0 -59 -42 -102t-102 -43q-47 0 -85 27t-53 72l-55 163l-153 -53q-29 -9 -50 -9 q-61 0 -101.5 40t-40.5 101q0 47 27.5 85t71.5 53l156 53l-105 313l-156 -54q-26 -8 -48 -8q-60 0 -101 40.5t-41 100.5q0 47 27.5 85t71.5 53l157 53l-53 159q-8 24 -8 47q0 60 42 102.5t102 42.5q47 0 85 -27t53 -72l54 -160l310 105l-54 160q-8 24 -8 47q0 59 42.5 102 t101.5 43q47 0 85.5 -27.5t53.5 -71.5l53 -161l162 55q21 6 43 6q60 0 102.5 -39.5t42.5 -98.5q0 -45 -30 -81.5t-74 -51.5l-157 -54l105 -316l164 56q24 8 46 8zM725 498l310 105l-105 315l-310 -107z" />
+<glyph unicode="&#xf199;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM1280 352v436q-31 -35 -64 -55q-34 -22 -132.5 -85t-151.5 -99q-98 -69 -164 -69v0v0q-66 0 -164 69 q-46 32 -141.5 92.5t-142.5 92.5q-12 8 -33 27t-31 27v-436q0 -40 28 -68t68 -28h832q40 0 68 28t28 68zM1280 925q0 41 -27.5 70t-68.5 29h-832q-40 0 -68 -28t-28 -68q0 -37 30.5 -76.5t67.5 -64.5q47 -32 137.5 -89t129.5 -83q3 -2 17 -11.5t21 -14t21 -13t23.5 -13 t21.5 -9.5t22.5 -7.5t20.5 -2.5t20.5 2.5t22.5 7.5t21.5 9.5t23.5 13t21 13t21 14t17 11.5l267 174q35 23 66.5 62.5t31.5 73.5z" />
+<glyph unicode="&#xf19a;" horiz-adv-x="1792" d="M127 640q0 163 67 313l367 -1005q-196 95 -315 281t-119 411zM1415 679q0 -19 -2.5 -38.5t-10 -49.5t-11.5 -44t-17.5 -59t-17.5 -58l-76 -256l-278 826q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-75 1 -202 10q-12 1 -20.5 -5t-11.5 -15t-1.5 -18.5t9 -16.5 t19.5 -8l80 -8l120 -328l-168 -504l-280 832q46 3 88 8q19 2 26 18.5t-2.5 31t-28.5 13.5l-205 -10q-7 0 -23 0.5t-26 0.5q105 160 274.5 253.5t367.5 93.5q147 0 280.5 -53t238.5 -149h-10q-55 0 -92 -40.5t-37 -95.5q0 -12 2 -24t4 -21.5t8 -23t9 -21t12 -22.5t12.5 -21 t14.5 -24t14 -23q63 -107 63 -212zM909 573l237 -647q1 -6 5 -11q-126 -44 -255 -44q-112 0 -217 32zM1570 1009q95 -174 95 -369q0 -209 -104 -385.5t-279 -278.5l235 678q59 169 59 276q0 42 -6 79zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286 t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 -215q173 0 331.5 68t273 182.5t182.5 273t68 331.5t-68 331.5t-182.5 273t-273 182.5t-331.5 68t-331.5 -68t-273 -182.5t-182.5 -273t-68 -331.5t68 -331.5t182.5 -273 t273 -182.5t331.5 -68z" />
+<glyph unicode="&#xf19b;" horiz-adv-x="1792" d="M1086 1536v-1536l-272 -128q-228 20 -414 102t-293 208.5t-107 272.5q0 140 100.5 263.5t275 205.5t391.5 108v-172q-217 -38 -356.5 -150t-139.5 -255q0 -152 154.5 -267t388.5 -145v1360zM1755 954l37 -390l-525 114l147 83q-119 70 -280 99v172q277 -33 481 -157z" />
+<glyph unicode="&#xf19c;" horiz-adv-x="2048" d="M960 1536l960 -384v-128h-128q0 -26 -20.5 -45t-48.5 -19h-1526q-28 0 -48.5 19t-20.5 45h-128v128zM256 896h256v-768h128v768h256v-768h128v768h256v-768h128v768h256v-768h59q28 0 48.5 -19t20.5 -45v-64h-1664v64q0 26 20.5 45t48.5 19h59v768zM1851 -64 q28 0 48.5 -19t20.5 -45v-128h-1920v128q0 26 20.5 45t48.5 19h1782z" />
+<glyph unicode="&#xf19d;" horiz-adv-x="2304" d="M1774 700l18 -316q4 -69 -82 -128t-235 -93.5t-323 -34.5t-323 34.5t-235 93.5t-82 128l18 316l574 -181q22 -7 48 -7t48 7zM2304 1024q0 -23 -22 -31l-1120 -352q-4 -1 -10 -1t-10 1l-652 206q-43 -34 -71 -111.5t-34 -178.5q63 -36 63 -109q0 -69 -58 -107l58 -433 q2 -14 -8 -25q-9 -11 -24 -11h-192q-15 0 -24 11q-10 11 -8 25l58 433q-58 38 -58 107q0 73 65 111q11 207 98 330l-333 104q-22 8 -22 31t22 31l1120 352q4 1 10 1t10 -1l1120 -352q22 -8 22 -31z" />
+<glyph unicode="&#xf19e;" d="M859 579l13 -707q-62 11 -105 11q-41 0 -105 -11l13 707q-40 69 -168.5 295.5t-216.5 374.5t-181 287q58 -15 108 -15q43 0 111 15q63 -111 133.5 -229.5t167 -276.5t138.5 -227q37 61 109.5 177.5t117.5 190t105 176t107 189.5q54 -14 107 -14q56 0 114 14v0 q-28 -39 -60 -88.5t-49.5 -78.5t-56.5 -96t-49 -84q-146 -248 -353 -610z" />
+<glyph unicode="&#xf1a0;" horiz-adv-x="1280" d="M981 197q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -49 2q-53 0 -104.5 -7t-107 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -56 23.5 -102t61 -75.5t87 -50t100 -29t101.5 -8.5q58 0 111.5 13t99 39t73 73t27.5 109zM864 1055 q0 59 -17 125.5t-48 129t-84 103.5t-117 41q-42 0 -82.5 -19.5t-66.5 -52.5q-46 -59 -46 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26q37 0 77.5 16.5t65.5 43.5q53 56 53 159zM752 1536h417l-137 -88h-132q75 -63 113 -133t38 -160q0 -72 -24.5 -129.5 t-59.5 -93t-69.5 -65t-59 -61.5t-24.5 -66q0 -36 32 -70.5t77 -68t90.5 -73.5t77.5 -104t32 -142q0 -91 -49 -173q-71 -122 -209.5 -179.5t-298.5 -57.5q-132 0 -246.5 41.5t-172.5 137.5q-36 59 -36 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 41 -47.5 73.5 t-15.5 73.5q0 40 21 85q-46 -4 -68 -4q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q76 66 182 98t218 32z" />
+<glyph unicode="&#xf1a1;" horiz-adv-x="1792" d="M1095 369q16 -16 0 -31q-62 -62 -199 -62t-199 62q-16 15 0 31q6 6 15 6t15 -6q48 -49 169 -49q120 0 169 49q6 6 15 6t15 -6zM788 550q0 -37 -26 -63t-63 -26t-63.5 26t-26.5 63q0 38 26.5 64t63.5 26t63 -26.5t26 -63.5zM1183 550q0 -37 -26.5 -63t-63.5 -26t-63 26 t-26 63t26 63.5t63 26.5t63.5 -26t26.5 -64zM1434 670q0 49 -35 84t-85 35t-86 -36q-130 90 -311 96l63 283l200 -45q0 -37 26 -63t63 -26t63.5 26.5t26.5 63.5t-26.5 63.5t-63.5 26.5q-54 0 -80 -50l-221 49q-19 5 -25 -16l-69 -312q-180 -7 -309 -97q-35 37 -87 37 q-50 0 -85 -35t-35 -84q0 -35 18.5 -64t49.5 -44q-6 -27 -6 -56q0 -142 140 -243t337 -101q198 0 338 101t140 243q0 32 -7 57q30 15 48 43.5t18 63.5zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191 t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf1a2;" d="M939 407q13 -13 0 -26q-53 -53 -171 -53t-171 53q-13 13 0 26q5 6 13 6t13 -6q42 -42 145 -42t145 42q5 6 13 6t13 -6zM676 563q0 -31 -23 -54t-54 -23t-54 23t-23 54q0 32 22.5 54.5t54.5 22.5t54.5 -22.5t22.5 -54.5zM1014 563q0 -31 -23 -54t-54 -23t-54 23t-23 54 q0 32 22.5 54.5t54.5 22.5t54.5 -22.5t22.5 -54.5zM1229 666q0 42 -30 72t-73 30q-42 0 -73 -31q-113 78 -267 82l54 243l171 -39q1 -32 23.5 -54t53.5 -22q32 0 54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5q-48 0 -69 -43l-189 42q-17 5 -21 -13l-60 -268q-154 -6 -265 -83 q-30 32 -74 32q-43 0 -73 -30t-30 -72q0 -30 16 -55t42 -38q-5 -25 -5 -48q0 -122 120 -208.5t289 -86.5q170 0 290 86.5t120 208.5q0 25 -6 49q25 13 40.5 37.5t15.5 54.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf1a3;" d="M866 697l90 27v62q0 79 -58 135t-138 56t-138 -55.5t-58 -134.5v-283q0 -20 -14 -33.5t-33 -13.5t-32.5 13.5t-13.5 33.5v120h-151v-122q0 -82 57.5 -139t139.5 -57q81 0 138.5 56.5t57.5 136.5v280q0 19 13.5 33t33.5 14q19 0 32.5 -14t13.5 -33v-54zM1199 502v122h-150 v-126q0 -20 -13.5 -33.5t-33.5 -13.5q-19 0 -32.5 14t-13.5 33v123l-90 -26l-60 28v-123q0 -80 58 -137t139 -57t138.5 57t57.5 139zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103 t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf1a4;" horiz-adv-x="1920" d="M1062 824v118q0 42 -30 72t-72 30t-72 -30t-30 -72v-612q0 -175 -126 -299t-303 -124q-178 0 -303.5 125.5t-125.5 303.5v266h328v-262q0 -43 30 -72.5t72 -29.5t72 29.5t30 72.5v620q0 171 126.5 292t301.5 121q176 0 302 -122t126 -294v-136l-195 -58zM1592 602h328 v-266q0 -178 -125.5 -303.5t-303.5 -125.5q-177 0 -303 124.5t-126 300.5v268l131 -61l195 58v-270q0 -42 30 -71.5t72 -29.5t72 29.5t30 71.5v275z" />
+<glyph unicode="&#xf1a5;" d="M1472 160v480h-704v704h-480q-93 0 -158.5 -65.5t-65.5 -158.5v-480h704v-704h480q93 0 158.5 65.5t65.5 158.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5 t84.5 -203.5z" />
+<glyph unicode="&#xf1a6;" horiz-adv-x="2048" d="M328 1254h204v-983h-532v697h328v286zM328 435v369h-123v-369h123zM614 968v-697h205v697h-205zM614 1254v-204h205v204h-205zM901 968h533v-942h-533v163h328v82h-328v697zM1229 435v369h-123v-369h123zM1516 968h532v-942h-532v163h327v82h-327v697zM1843 435v369h-123 v-369h123z" />
+<glyph unicode="&#xf1a7;" d="M1046 516q0 -64 -38 -109t-91 -45q-43 0 -70 15v277q28 17 70 17q53 0 91 -45.5t38 -109.5zM703 944q0 -64 -38 -109.5t-91 -45.5q-43 0 -70 15v277q28 17 70 17q53 0 91 -45t38 -109zM1265 513q0 134 -88 229t-213 95q-20 0 -39 -3q-23 -78 -78 -136q-87 -95 -211 -101 v-636l211 41v206q51 -19 117 -19q125 0 213 95t88 229zM922 940q0 134 -88.5 229t-213.5 95q-74 0 -141 -36h-186v-840l211 41v206q55 -19 116 -19q125 0 213.5 95t88.5 229zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf1a8;" horiz-adv-x="2038" d="M1222 607q75 3 143.5 -20.5t118 -58.5t101 -94.5t84 -108t75.5 -120.5q33 -56 78.5 -109t75.5 -80.5t99 -88.5q-48 -30 -108.5 -57.5t-138.5 -59t-114 -47.5q-44 37 -74 115t-43.5 164.5t-33 180.5t-42.5 168.5t-72.5 123t-122.5 48.5l-10 -2l-6 -4q4 -5 13 -14 q6 -5 28 -23.5t25.5 -22t19 -18t18 -20.5t11.5 -21t10.5 -27.5t4.5 -31t4 -40.5l1 -33q1 -26 -2.5 -57.5t-7.5 -52t-12.5 -58.5t-11.5 -53q-35 1 -101 -9.5t-98 -10.5q-39 0 -72 10q-2 16 -2 47q0 74 3 96q2 13 31.5 41.5t57 59t26.5 51.5q-24 2 -43 -24 q-36 -53 -111.5 -99.5t-136.5 -46.5q-25 0 -75.5 63t-106.5 139.5t-84 96.5q-6 4 -27 30q-482 -112 -513 -112q-16 0 -28 11t-12 27q0 15 8.5 26.5t22.5 14.5l486 106q-8 14 -8 25t5.5 17.5t16 11.5t20 7t23 4.5t18.5 4.5q4 1 15.5 7.5t17.5 6.5q15 0 28 -16t20 -33 q163 37 172 37q17 0 29.5 -11t12.5 -28q0 -15 -8.5 -26t-23.5 -14l-182 -40l-1 -16q-1 -26 81.5 -117.5t104.5 -91.5q47 0 119 80t72 129q0 36 -23.5 53t-51 18.5t-51 11.5t-23.5 34q0 16 10 34l-68 19q43 44 43 117q0 26 -5 58q82 16 144 16q44 0 71.5 -1.5t48.5 -8.5 t31 -13.5t20.5 -24.5t15.5 -33.5t17 -47.5t24 -60l50 25q-3 -40 -23 -60t-42.5 -21t-40 -6.5t-16.5 -20.5zM1282 842q-5 5 -13.5 15.5t-12 14.5t-10.5 11.5t-10 10.5l-8 8t-8.5 7.5t-8 5t-8.5 4.5q-7 3 -14.5 5t-20.5 2.5t-22 0.5h-32.5h-37.5q-126 0 -217 -43 q16 30 36 46.5t54 29.5t65.5 36t46 36.5t50 55t43.5 50.5q12 -9 28 -31.5t32 -36.5t38 -13l12 1v-76l22 -1q247 95 371 190q28 21 50 39t42.5 37.5t33 31t29.5 34t24 31t24.5 37t23 38t27 47.5t29.5 53l7 9q-2 -53 -43 -139q-79 -165 -205 -264t-306 -142q-14 -3 -42 -7.5 t-50 -9.5t-39 -14q3 -19 24.5 -46t21.5 -34q0 -11 -26 -30zM1061 -79q39 26 131.5 47.5t146.5 21.5q9 0 22.5 -15.5t28 -42.5t26 -50t24 -51t14.5 -33q-121 -45 -244 -45q-61 0 -125 11zM822 568l48 12l109 -177l-73 -48zM1323 51q3 -15 3 -16q0 -7 -17.5 -14.5t-46 -13 t-54 -9.5t-53.5 -7.5t-32 -4.5l-7 43q21 2 60.5 8.5t72 10t60.5 3.5h14zM866 679l-96 -20l-6 17q10 1 32.5 7t34.5 6q19 0 35 -10zM1061 45h31l10 -83l-41 -12v95zM1950 1535v1v-1zM1950 1535l-1 -5l-2 -2l1 3zM1950 1535l1 1z" />
+<glyph unicode="&#xf1a9;" d="M1167 -50q-5 19 -24 5q-30 -22 -87 -39t-131 -17q-129 0 -193 49q-5 4 -13 4q-11 0 -26 -12q-7 -6 -7.5 -16t7.5 -20q34 -32 87.5 -46t102.5 -12.5t99 4.5q41 4 84.5 20.5t65 30t28.5 20.5q12 12 7 29zM1128 65q-19 47 -39 61q-23 15 -76 15q-47 0 -71 -10 q-29 -12 -78 -56q-26 -24 -12 -44q9 -8 17.5 -4.5t31.5 23.5q3 2 10.5 8.5t10.5 8.5t10 7t11.5 7t12.5 5t15 4.5t16.5 2.5t20.5 1q27 0 44.5 -7.5t23 -14.5t13.5 -22q10 -17 12.5 -20t12.5 1q23 12 14 34zM1483 346q0 22 -5 44.5t-16.5 45t-34 36.5t-52.5 14 q-33 0 -97 -41.5t-129 -83.5t-101 -42q-27 -1 -63.5 19t-76 49t-83.5 58t-100 49t-111 19q-115 -1 -197 -78.5t-84 -178.5q-2 -112 74 -164q29 -20 62.5 -28.5t103.5 -8.5q57 0 132 32.5t134 71t120 70.5t93 31q26 -1 65 -31.5t71.5 -67t68 -67.5t55.5 -32q35 -3 58.5 14 t55.5 63q28 41 42.5 101t14.5 106zM1536 506q0 -164 -62 -304.5t-166 -236t-242.5 -149.5t-290.5 -54t-293 57.5t-247.5 157t-170.5 241.5t-64 302q0 89 19.5 172.5t49 145.5t70.5 118.5t78.5 94t78.5 69.5t64.5 46.5t42.5 24.5q14 8 51 26.5t54.5 28.5t48 30t60.5 44 q36 28 58 72.5t30 125.5q129 -155 186 -193q44 -29 130 -68t129 -66q21 -13 39 -25t60.5 -46.5t76 -70.5t75 -95t69 -122t47 -148.5t19.5 -177.5z" />
+<glyph unicode="&#xf1aa;" d="M1070 463l-160 -160l-151 -152l-30 -30q-65 -64 -151.5 -87t-171.5 -2q-16 -70 -72 -115t-129 -45q-85 0 -145 60.5t-60 145.5q0 72 44.5 128t113.5 72q-22 86 1 173t88 152l12 12l151 -152l-11 -11q-37 -37 -37 -89t37 -90q37 -37 89 -37t89 37l30 30l151 152l161 160z M729 1145l12 -12l-152 -152l-12 12q-37 37 -89 37t-89 -37t-37 -89.5t37 -89.5l29 -29l152 -152l160 -160l-151 -152l-161 160l-151 152l-30 30q-68 67 -90 159.5t5 179.5q-70 15 -115 71t-45 129q0 85 60 145.5t145 60.5q76 0 133.5 -49t69.5 -123q84 20 169.5 -3.5 t149.5 -87.5zM1536 78q0 -85 -60 -145.5t-145 -60.5q-74 0 -131 47t-71 118q-86 -28 -179.5 -6t-161.5 90l-11 12l151 152l12 -12q37 -37 89 -37t89 37t37 89t-37 89l-30 30l-152 152l-160 160l152 152l160 -160l152 -152l29 -30q64 -64 87.5 -150.5t2.5 -171.5 q76 -11 126.5 -68.5t50.5 -134.5zM1534 1202q0 -77 -51 -135t-127 -69q26 -85 3 -176.5t-90 -158.5l-12 -12l-151 152l12 12q37 37 37 89t-37 89t-89 37t-89 -37l-30 -30l-152 -152l-160 -160l-152 152l161 160l152 152l29 30q67 67 159 89.5t178 -3.5q11 75 68.5 126 t135.5 51q85 0 145 -60.5t60 -145.5z" />
+<glyph unicode="&#xf1ab;" d="M654 458q-1 -3 -12.5 0.5t-31.5 11.5l-20 9q-44 20 -87 49q-7 5 -41 31.5t-38 28.5q-67 -103 -134 -181q-81 -95 -105 -110q-4 -2 -19.5 -4t-18.5 0q6 4 82 92q21 24 85.5 115t78.5 118q17 30 51 98.5t36 77.5q-8 1 -110 -33q-8 -2 -27.5 -7.5t-34.5 -9.5t-17 -5 q-2 -2 -2 -10.5t-1 -9.5q-5 -10 -31 -15q-23 -7 -47 0q-18 4 -28 21q-4 6 -5 23q6 2 24.5 5t29.5 6q58 16 105 32q100 35 102 35q10 2 43 19.5t44 21.5q9 3 21.5 8t14.5 5.5t6 -0.5q2 -12 -1 -33q0 -2 -12.5 -27t-26.5 -53.5t-17 -33.5q-25 -50 -77 -131l64 -28 q12 -6 74.5 -32t67.5 -28q4 -1 10.5 -25.5t4.5 -30.5zM449 944q3 -15 -4 -28q-12 -23 -50 -38q-30 -12 -60 -12q-26 3 -49 26q-14 15 -18 41l1 3q3 -3 19.5 -5t26.5 0t58 16q36 12 55 14q17 0 21 -17zM1147 815l63 -227l-139 42zM39 15l694 232v1032l-694 -233v-1031z M1280 332l102 -31l-181 657l-100 31l-216 -536l102 -31l45 110l211 -65zM777 1294l573 -184v380zM1088 -29l158 -13l-54 -160l-40 66q-130 -83 -276 -108q-58 -12 -91 -12h-84q-79 0 -199.5 39t-183.5 85q-8 7 -8 16q0 8 5 13.5t13 5.5q4 0 18 -7.5t30.5 -16.5t20.5 -11 q73 -37 159.5 -61.5t157.5 -24.5q95 0 167 14.5t157 50.5q15 7 30.5 15.5t34 19t28.5 16.5zM1536 1050v-1079l-774 246q-14 -6 -375 -127.5t-368 -121.5q-13 0 -18 13q0 1 -1 3v1078q3 9 4 10q5 6 20 11q106 35 149 50v384l558 -198q2 0 160.5 55t316 108.5t161.5 53.5 q20 0 20 -21v-418z" />
+<glyph unicode="&#xf1ac;" horiz-adv-x="1792" d="M288 1152q66 0 113 -47t47 -113v-1088q0 -66 -47 -113t-113 -47h-128q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h128zM1664 989q58 -34 93 -93t35 -128v-768q0 -106 -75 -181t-181 -75h-864q-66 0 -113 47t-47 113v1536q0 40 28 68t68 28h672q40 0 88 -20t76 -48 l152 -152q28 -28 48 -76t20 -88v-163zM928 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM928 256v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM928 512v128q0 14 -9 23 t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1184 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1184 256v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128 q14 0 23 9t9 23zM1184 512v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 0v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 256v128q0 14 -9 23t-23 9h-128 q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1440 512v128q0 14 -9 23t-23 9h-128q-14 0 -23 -9t-9 -23v-128q0 -14 9 -23t23 -9h128q14 0 23 9t9 23zM1536 896v256h-160q-40 0 -68 28t-28 68v160h-640v-512h896z" />
+<glyph unicode="&#xf1ad;" d="M1344 1536q26 0 45 -19t19 -45v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280zM512 1248v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 992v-64q0 -14 9 -23t23 -9h64q14 0 23 9 t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 736v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM512 480v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM384 160v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64 q14 0 23 9t9 23zM384 928v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM384 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 -96v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9 t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM896 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 928v64 q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM896 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 160v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64 q14 0 23 9t9 23zM1152 416v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 672v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 928v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9 t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1152 1184v64q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h64q14 0 23 9t9 23z" />
+<glyph unicode="&#xf1ae;" horiz-adv-x="1280" d="M1188 988l-292 -292v-824q0 -46 -33 -79t-79 -33t-79 33t-33 79v384h-64v-384q0 -46 -33 -79t-79 -33t-79 33t-33 79v824l-292 292q-28 28 -28 68t28 68t68 28t68 -28l228 -228h368l228 228q28 28 68 28t68 -28t28 -68t-28 -68zM864 1152q0 -93 -65.5 -158.5 t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf1b0;" horiz-adv-x="1664" d="M780 1064q0 -60 -19 -113.5t-63 -92.5t-105 -39q-76 0 -138 57.5t-92 135.5t-30 151q0 60 19 113.5t63 92.5t105 39q77 0 138.5 -57.5t91.5 -135t30 -151.5zM438 581q0 -80 -42 -139t-119 -59q-76 0 -141.5 55.5t-100.5 133.5t-35 152q0 80 42 139.5t119 59.5 q76 0 141.5 -55.5t100.5 -134t35 -152.5zM832 608q118 0 255 -97.5t229 -237t92 -254.5q0 -46 -17 -76.5t-48.5 -45t-64.5 -20t-76 -5.5q-68 0 -187.5 45t-182.5 45q-66 0 -192.5 -44.5t-200.5 -44.5q-183 0 -183 146q0 86 56 191.5t139.5 192.5t187.5 146t193 59zM1071 819 q-61 0 -105 39t-63 92.5t-19 113.5q0 74 30 151.5t91.5 135t138.5 57.5q61 0 105 -39t63 -92.5t19 -113.5q0 -73 -30 -151t-92 -135.5t-138 -57.5zM1503 923q77 0 119 -59.5t42 -139.5q0 -74 -35 -152t-100.5 -133.5t-141.5 -55.5q-77 0 -119 59t-42 139q0 74 35 152.5 t100.5 134t141.5 55.5z" />
+<glyph unicode="&#xf1b1;" horiz-adv-x="768" d="M704 1008q0 -145 -57 -243.5t-152 -135.5l45 -821q2 -26 -16 -45t-44 -19h-192q-26 0 -44 19t-16 45l45 821q-95 37 -152 135.5t-57 243.5q0 128 42.5 249.5t117.5 200t160 78.5t160 -78.5t117.5 -200t42.5 -249.5z" />
+<glyph unicode="&#xf1b2;" horiz-adv-x="1792" d="M896 -93l640 349v636l-640 -233v-752zM832 772l698 254l-698 254l-698 -254zM1664 1024v-768q0 -35 -18 -65t-49 -47l-704 -384q-28 -16 -61 -16t-61 16l-704 384q-31 17 -49 47t-18 65v768q0 40 23 73t61 47l704 256q22 8 44 8t44 -8l704 -256q38 -14 61 -47t23 -73z " />
+<glyph unicode="&#xf1b3;" horiz-adv-x="2304" d="M640 -96l384 192v314l-384 -164v-342zM576 358l404 173l-404 173l-404 -173zM1664 -96l384 192v314l-384 -164v-342zM1600 358l404 173l-404 173l-404 -173zM1152 651l384 165v266l-384 -164v-267zM1088 1030l441 189l-441 189l-441 -189zM2176 512v-416q0 -36 -19 -67 t-52 -47l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-5 2 -7 4q-2 -2 -7 -4l-448 -224q-25 -14 -57 -14t-57 14l-448 224q-33 16 -52 47t-19 67v416q0 38 21.5 70t56.5 48l434 186v400q0 38 21.5 70t56.5 48l448 192q23 10 50 10t50 -10l448 -192q35 -16 56.5 -48t21.5 -70 v-400l434 -186q36 -16 57 -48t21 -70z" />
+<glyph unicode="&#xf1b4;" horiz-adv-x="2048" d="M1848 1197h-511v-124h511v124zM1596 771q-90 0 -146 -52.5t-62 -142.5h408q-18 195 -200 195zM1612 186q63 0 122 32t76 87h221q-100 -307 -427 -307q-214 0 -340.5 132t-126.5 347q0 208 130.5 345.5t336.5 137.5q138 0 240.5 -68t153 -179t50.5 -248q0 -17 -2 -47h-658 q0 -111 57.5 -171.5t166.5 -60.5zM277 236h296q205 0 205 167q0 180 -199 180h-302v-347zM277 773h281q78 0 123.5 36.5t45.5 113.5q0 144 -190 144h-260v-294zM0 1282h594q87 0 155 -14t126.5 -47.5t90 -96.5t31.5 -154q0 -181 -172 -263q114 -32 172 -115t58 -204 q0 -75 -24.5 -136.5t-66 -103.5t-98.5 -71t-121 -42t-134 -13h-611v1260z" />
+<glyph unicode="&#xf1b5;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM499 1041h-371v-787h382q117 0 197 57.5t80 170.5q0 158 -143 200q107 52 107 164q0 57 -19.5 96.5 t-56.5 60.5t-79 29.5t-97 8.5zM477 723h-176v184h163q119 0 119 -90q0 -94 -106 -94zM486 388h-185v217h189q124 0 124 -113q0 -104 -128 -104zM1136 356q-68 0 -104 38t-36 107h411q1 10 1 30q0 132 -74.5 220.5t-203.5 88.5q-128 0 -210 -86t-82 -216q0 -135 79 -217 t213 -82q205 0 267 191h-138q-11 -34 -47.5 -54t-75.5 -20zM1126 722q113 0 124 -122h-254q4 56 39 89t91 33zM964 988h319v-77h-319v77z" />
+<glyph unicode="&#xf1b6;" horiz-adv-x="1792" d="M1582 954q0 -101 -71.5 -172.5t-172.5 -71.5t-172.5 71.5t-71.5 172.5t71.5 172.5t172.5 71.5t172.5 -71.5t71.5 -172.5zM812 212q0 104 -73 177t-177 73q-27 0 -54 -6l104 -42q77 -31 109.5 -106.5t1.5 -151.5q-31 -77 -107 -109t-152 -1q-21 8 -62 24.5t-61 24.5 q32 -60 91 -96.5t130 -36.5q104 0 177 73t73 177zM1642 953q0 126 -89.5 215.5t-215.5 89.5q-127 0 -216.5 -89.5t-89.5 -215.5q0 -127 89.5 -216t216.5 -89q126 0 215.5 89t89.5 216zM1792 953q0 -189 -133.5 -322t-321.5 -133l-437 -319q-12 -129 -109 -218t-229 -89 q-121 0 -214 76t-118 192l-230 92v429l389 -157q79 48 173 48q13 0 35 -2l284 407q2 187 135.5 319t320.5 132q188 0 321.5 -133.5t133.5 -321.5z" />
+<glyph unicode="&#xf1b7;" d="M1242 889q0 80 -57 136.5t-137 56.5t-136.5 -57t-56.5 -136q0 -80 56.5 -136.5t136.5 -56.5t137 56.5t57 136.5zM632 301q0 -83 -58 -140.5t-140 -57.5q-56 0 -103 29t-72 77q52 -20 98 -40q60 -24 120 1.5t85 86.5q24 60 -1.5 120t-86.5 84l-82 33q22 5 42 5 q82 0 140 -57.5t58 -140.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v153l172 -69q20 -92 93.5 -152t168.5 -60q104 0 181 70t87 173l345 252q150 0 255.5 105.5t105.5 254.5q0 150 -105.5 255.5t-255.5 105.5 q-148 0 -253 -104.5t-107 -252.5l-225 -322q-9 1 -28 1q-75 0 -137 -37l-297 119v468q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5zM1289 887q0 -100 -71 -170.5t-171 -70.5t-170.5 70.5t-70.5 170.5t70.5 171t170.5 71q101 0 171.5 -70.5t70.5 -171.5z " />
+<glyph unicode="&#xf1b8;" horiz-adv-x="1792" d="M836 367l-15 -368l-2 -22l-420 29q-36 3 -67 31.5t-47 65.5q-11 27 -14.5 55t4 65t12 55t21.5 64t19 53q78 -12 509 -28zM449 953l180 -379l-147 92q-63 -72 -111.5 -144.5t-72.5 -125t-39.5 -94.5t-18.5 -63l-4 -21l-190 357q-17 26 -18 56t6 47l8 18q35 63 114 188 l-140 86zM1680 436l-188 -359q-12 -29 -36.5 -46.5t-43.5 -20.5l-18 -4q-71 -7 -219 -12l8 -164l-230 367l211 362l7 -173q170 -16 283 -5t170 33zM895 1360q-47 -63 -265 -435l-317 187l-19 12l225 356q20 31 60 45t80 10q24 -2 48.5 -12t42 -21t41.5 -33t36 -34.5 t36 -39.5t32 -35zM1550 1053l212 -363q18 -37 12.5 -76t-27.5 -74q-13 -20 -33 -37t-38 -28t-48.5 -22t-47 -16t-51.5 -14t-46 -12q-34 72 -265 436l313 195zM1407 1279l142 83l-220 -373l-419 20l151 86q-34 89 -75 166t-75.5 123.5t-64.5 80t-47 46.5l-17 13l405 -1 q31 3 58 -10.5t39 -28.5l11 -15q39 -61 112 -190z" />
+<glyph unicode="&#xf1b9;" horiz-adv-x="2048" d="M480 448q0 66 -47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47t113 47t47 113zM516 768h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5zM1888 448q0 66 -47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47t113 47t47 113zM2048 544v-384 q0 -14 -9 -23t-23 -9h-96v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-1024v-128q0 -80 -56 -136t-136 -56t-136 56t-56 136v128h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5t179 63.5h768q98 0 179 -63.5t104 -157.5 l105 -419h28q93 0 158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf1ba;" horiz-adv-x="2048" d="M1824 640q93 0 158.5 -65.5t65.5 -158.5v-384q0 -14 -9 -23t-23 -9h-96v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-1024v-64q0 -80 -56 -136t-136 -56t-136 56t-56 136v64h-96q-14 0 -23 9t-9 23v384q0 93 65.5 158.5t158.5 65.5h28l105 419q23 94 104 157.5 t179 63.5h128v224q0 14 9 23t23 9h448q14 0 23 -9t9 -23v-224h128q98 0 179 -63.5t104 -157.5l105 -419h28zM320 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM516 640h1016l-89 357q-2 8 -14 17.5t-21 9.5h-768q-9 0 -21 -9.5t-14 -17.5z M1728 160q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47z" />
+<glyph unicode="&#xf1bb;" d="M1504 64q0 -26 -19 -45t-45 -19h-462q1 -17 6 -87.5t5 -108.5q0 -25 -18 -42.5t-43 -17.5h-320q-25 0 -43 17.5t-18 42.5q0 38 5 108.5t6 87.5h-462q-26 0 -45 19t-19 45t19 45l402 403h-229q-26 0 -45 19t-19 45t19 45l402 403h-197q-26 0 -45 19t-19 45t19 45l384 384 q19 19 45 19t45 -19l384 -384q19 -19 19 -45t-19 -45t-45 -19h-197l402 -403q19 -19 19 -45t-19 -45t-45 -19h-229l402 -403q19 -19 19 -45z" />
+<glyph unicode="&#xf1bc;" d="M1127 326q0 32 -30 51q-193 115 -447 115q-133 0 -287 -34q-42 -9 -42 -52q0 -20 13.5 -34.5t35.5 -14.5q5 0 37 8q132 27 243 27q226 0 397 -103q19 -11 33 -11q19 0 33 13.5t14 34.5zM1223 541q0 40 -35 61q-237 141 -548 141q-153 0 -303 -42q-48 -13 -48 -64 q0 -25 17.5 -42.5t42.5 -17.5q7 0 37 8q122 33 251 33q279 0 488 -124q24 -13 38 -13q25 0 42.5 17.5t17.5 42.5zM1331 789q0 47 -40 70q-126 73 -293 110.5t-343 37.5q-204 0 -364 -47q-23 -7 -38.5 -25.5t-15.5 -48.5q0 -31 20.5 -52t51.5 -21q11 0 40 8q133 37 307 37 q159 0 309.5 -34t253.5 -95q21 -12 40 -12q29 0 50.5 20.5t21.5 51.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf1bd;" horiz-adv-x="1024" d="M1024 1233l-303 -582l24 -31h279v-415h-507l-44 -30l-142 -273l-30 -30h-301v303l303 583l-24 30h-279v415h507l44 30l142 273l30 30h301v-303z" />
+<glyph unicode="&#xf1be;" horiz-adv-x="2304" d="M784 164l16 241l-16 523q-1 10 -7.5 17t-16.5 7q-9 0 -16 -7t-7 -17l-14 -523l14 -241q1 -10 7.5 -16.5t15.5 -6.5q22 0 24 23zM1080 193l11 211l-12 586q0 16 -13 24q-8 5 -16 5t-16 -5q-13 -8 -13 -24l-1 -6l-10 -579q0 -1 11 -236v-1q0 -10 6 -17q9 -11 23 -11 q11 0 20 9q9 7 9 20zM35 533l20 -128l-20 -126q-2 -9 -9 -9t-9 9l-17 126l17 128q2 9 9 9t9 -9zM121 612l26 -207l-26 -203q-2 -9 -10 -9q-9 0 -9 10l-23 202l23 207q0 9 9 9q8 0 10 -9zM401 159zM213 650l25 -245l-25 -237q0 -11 -11 -11q-10 0 -12 11l-21 237l21 245 q2 12 12 12q11 0 11 -12zM307 657l23 -252l-23 -244q-2 -13 -14 -13q-13 0 -13 13l-21 244l21 252q0 13 13 13q12 0 14 -13zM401 639l21 -234l-21 -246q-2 -16 -16 -16q-6 0 -10.5 4.5t-4.5 11.5l-20 246l20 234q0 6 4.5 10.5t10.5 4.5q14 0 16 -15zM784 164zM495 785 l21 -380l-21 -246q0 -7 -5 -12.5t-12 -5.5q-16 0 -18 18l-18 246l18 380q2 18 18 18q7 0 12 -5.5t5 -12.5zM589 871l19 -468l-19 -244q0 -8 -5.5 -13.5t-13.5 -5.5q-18 0 -20 19l-16 244l16 468q2 19 20 19q8 0 13.5 -5.5t5.5 -13.5zM687 911l18 -506l-18 -242 q-2 -21 -22 -21q-19 0 -21 21l-16 242l16 506q0 9 6.5 15.5t14.5 6.5q9 0 15 -6.5t7 -15.5zM1079 169v0v0zM881 915l15 -510l-15 -239q0 -10 -7.5 -17.5t-17.5 -7.5t-17 7t-8 18l-14 239l14 510q0 11 7.5 18t17.5 7t17.5 -7t7.5 -18zM980 896l14 -492l-14 -236q0 -11 -8 -19 t-19 -8t-19 8t-9 19l-12 236l12 492q1 12 9 20t19 8t18.5 -8t8.5 -20zM1192 404l-14 -231v0q0 -13 -9 -22t-22 -9t-22 9t-10 22l-6 114l-6 117l12 636v3q2 15 12 24q9 7 20 7q8 0 15 -5q14 -8 16 -26zM2304 423q0 -117 -83 -199.5t-200 -82.5h-786q-13 2 -22 11t-9 22v899 q0 23 28 33q85 34 181 34q195 0 338 -131.5t160 -323.5q53 22 110 22q117 0 200 -83t83 -201z" />
+<glyph unicode="&#xf1c0;" d="M768 768q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 0q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127 t443 -43zM768 384q237 0 443 43t325 127v-170q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5t-103 128v170q119 -84 325 -127t443 -43zM768 1536q208 0 385 -34.5t280 -93.5t103 -128v-128q0 -69 -103 -128t-280 -93.5t-385 -34.5t-385 34.5t-280 93.5 t-103 128v128q0 69 103 128t280 93.5t385 34.5z" />
+<glyph unicode="&#xf1c1;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M894 465q33 -26 84 -56q59 7 117 7q147 0 177 -49q16 -22 2 -52q0 -1 -1 -2l-2 -2v-1q-6 -38 -71 -38q-48 0 -115 20t-130 53q-221 -24 -392 -83q-153 -262 -242 -262q-15 0 -28 7l-24 12q-1 1 -6 5q-10 10 -6 36q9 40 56 91.5t132 96.5q14 9 23 -6q2 -2 2 -4q52 85 107 197 q68 136 104 262q-24 82 -30.5 159.5t6.5 127.5q11 40 42 40h21h1q23 0 35 -15q18 -21 9 -68q-2 -6 -4 -8q1 -3 1 -8v-30q-2 -123 -14 -192q55 -164 146 -238zM318 54q52 24 137 158q-51 -40 -87.5 -84t-49.5 -74zM716 974q-15 -42 -2 -132q1 7 7 44q0 3 7 43q1 4 4 8 q-1 1 -1 2t-0.5 1.5t-0.5 1.5q-1 22 -13 36q0 -1 -1 -2v-2zM592 313q135 54 284 81q-2 1 -13 9.5t-16 13.5q-76 67 -127 176q-27 -86 -83 -197q-30 -56 -45 -83zM1238 329q-24 24 -140 24q76 -28 124 -28q14 0 18 1q0 1 -2 3z" />
+<glyph unicode="&#xf1c2;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M233 768v-107h70l164 -661h159l128 485q7 20 10 46q2 16 2 24h4l3 -24q1 -3 3.5 -20t5.5 -26l128 -485h159l164 661h70v107h-300v-107h90l-99 -438q-5 -20 -7 -46l-2 -21h-4l-3 21q-1 5 -4 21t-5 25l-144 545h-114l-144 -545q-2 -9 -4.5 -24.5t-3.5 -21.5l-4 -21h-4l-2 21 q-2 26 -7 46l-99 438h90v107h-300z" />
+<glyph unicode="&#xf1c3;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M429 106v-106h281v106h-75l103 161q5 7 10 16.5t7.5 13.5t3.5 4h2q1 -4 5 -10q2 -4 4.5 -7.5t6 -8t6.5 -8.5l107 -161h-76v-106h291v106h-68l-192 273l195 282h67v107h-279v-107h74l-103 -159q-4 -7 -10 -16.5t-9 -13.5l-2 -3h-2q-1 4 -5 10q-6 11 -17 23l-106 159h76v107 h-290v-107h68l189 -272l-194 -283h-68z" />
+<glyph unicode="&#xf1c4;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M416 106v-106h327v106h-93v167h137q76 0 118 15q67 23 106.5 87t39.5 146q0 81 -37 141t-100 87q-48 19 -130 19h-368v-107h92v-555h-92zM769 386h-119v268h120q52 0 83 -18q56 -33 56 -115q0 -89 -62 -120q-31 -15 -78 -15z" />
+<glyph unicode="&#xf1c5;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M1280 320v-320h-1024v192l192 192l128 -128l384 384zM448 512q-80 0 -136 56t-56 136t56 136t136 56t136 -56t56 -136t-56 -136t-136 -56z" />
+<glyph unicode="&#xf1c6;" d="M640 1152v128h-128v-128h128zM768 1024v128h-128v-128h128zM640 896v128h-128v-128h128zM768 768v128h-128v-128h128zM1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400 v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-128v-128h-128v128h-512v-1536h1280zM781 593l107 -349q8 -27 8 -52q0 -83 -72.5 -137.5t-183.5 -54.5t-183.5 54.5t-72.5 137.5q0 25 8 52q21 63 120 396v128h128v-128h79 q22 0 39 -13t23 -34zM640 128q53 0 90.5 19t37.5 45t-37.5 45t-90.5 19t-90.5 -19t-37.5 -45t37.5 -45t90.5 -19z" />
+<glyph unicode="&#xf1c7;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M620 686q20 -8 20 -30v-544q0 -22 -20 -30q-8 -2 -12 -2q-12 0 -23 9l-166 167h-131q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h131l166 167q16 15 35 7zM1037 -3q31 0 50 24q129 159 129 363t-129 363q-16 21 -43 24t-47 -14q-21 -17 -23.5 -43.5t14.5 -47.5 q100 -123 100 -282t-100 -282q-17 -21 -14.5 -47.5t23.5 -42.5q18 -15 40 -15zM826 145q27 0 47 20q87 93 87 219t-87 219q-18 19 -45 20t-46 -17t-20 -44.5t18 -46.5q52 -57 52 -131t-52 -131q-19 -20 -18 -46.5t20 -44.5q20 -17 44 -17z" />
+<glyph unicode="&#xf1c8;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M768 768q52 0 90 -38t38 -90v-384q0 -52 -38 -90t-90 -38h-384q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h384zM1260 766q20 -8 20 -30v-576q0 -22 -20 -30q-8 -2 -12 -2q-14 0 -23 9l-265 266v90l265 266q9 9 23 9q4 0 12 -2z" />
+<glyph unicode="&#xf1c9;" d="M1468 1156q28 -28 48 -76t20 -88v-1152q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1600q0 40 28 68t68 28h896q40 0 88 -20t76 -48zM1024 1400v-376h376q-10 29 -22 41l-313 313q-12 12 -41 22zM1408 -128v1024h-416q-40 0 -68 28t-28 68v416h-768v-1536h1280z M480 768q8 11 21 12.5t24 -6.5l51 -38q11 -8 12.5 -21t-6.5 -24l-182 -243l182 -243q8 -11 6.5 -24t-12.5 -21l-51 -38q-11 -8 -24 -6.5t-21 12.5l-226 301q-14 19 0 38zM1282 467q14 -19 0 -38l-226 -301q-8 -11 -21 -12.5t-24 6.5l-51 38q-11 8 -12.5 21t6.5 24l182 243 l-182 243q-8 11 -6.5 24t12.5 21l51 38q11 8 24 6.5t21 -12.5zM662 6q-13 2 -20.5 13t-5.5 24l138 831q2 13 13 20.5t24 5.5l63 -10q13 -2 20.5 -13t5.5 -24l-138 -831q-2 -13 -13 -20.5t-24 -5.5z" />
+<glyph unicode="&#xf1ca;" d="M1497 709v-198q-101 -23 -198 -23q-65 -136 -165.5 -271t-181.5 -215.5t-128 -106.5q-80 -45 -162 3q-28 17 -60.5 43.5t-85 83.5t-102.5 128.5t-107.5 184t-105.5 244t-91.5 314.5t-70.5 390h283q26 -218 70 -398.5t104.5 -317t121.5 -235.5t140 -195q169 169 287 406 q-142 72 -223 220t-81 333q0 192 104 314.5t284 122.5q178 0 273 -105.5t95 -297.5q0 -159 -58 -286q-7 -1 -19.5 -3t-46 -2t-63 6t-62 25.5t-50.5 51.5q31 103 31 184q0 87 -29 132t-79 45q-53 0 -85 -49.5t-32 -140.5q0 -186 105 -293.5t267 -107.5q62 0 121 14z" />
+<glyph unicode="&#xf1cb;" horiz-adv-x="1792" d="M216 367l603 -402v359l-334 223zM154 511l193 129l-193 129v-258zM973 -35l603 402l-269 180l-334 -223v-359zM896 458l272 182l-272 182l-272 -182zM485 733l334 223v359l-603 -402zM1445 640l193 -129v258zM1307 733l269 180l-603 402v-359zM1792 913v-546 q0 -41 -34 -64l-819 -546q-21 -13 -43 -13t-43 13l-819 546q-34 23 -34 64v546q0 41 34 64l819 546q21 13 43 13t43 -13l819 -546q34 -23 34 -64z" />
+<glyph unicode="&#xf1cc;" horiz-adv-x="2048" d="M1800 764q111 -46 179.5 -145.5t68.5 -221.5q0 -164 -118 -280.5t-285 -116.5q-4 0 -11.5 0.5t-10.5 0.5h-1209h-1h-2h-5q-170 10 -288 125.5t-118 280.5q0 110 55 203t147 147q-12 39 -12 82q0 115 82 196t199 81q95 0 172 -58q75 154 222.5 248t326.5 94 q166 0 306 -80.5t221.5 -218.5t81.5 -301q0 -6 -0.5 -18t-0.5 -18zM468 498q0 -122 84 -193t208 -71q137 0 240 99q-16 20 -47.5 56.5t-43.5 50.5q-67 -65 -144 -65q-55 0 -93.5 33.5t-38.5 87.5q0 53 38.5 87t91.5 34q44 0 84.5 -21t73 -55t65 -75t69 -82t77 -75t97 -55 t121.5 -21q121 0 204.5 71.5t83.5 190.5q0 121 -84 192t-207 71q-143 0 -241 -97q14 -16 29.5 -34t34.5 -40t29 -34q66 64 142 64q52 0 92 -33t40 -84q0 -57 -37 -91.5t-94 -34.5q-43 0 -82.5 21t-72 55t-65.5 75t-69.5 82t-77.5 75t-96.5 55t-118.5 21q-122 0 -207 -70.5 t-85 -189.5z" />
+<glyph unicode="&#xf1cd;" horiz-adv-x="1792" d="M896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 1408q-190 0 -361 -90l194 -194q82 28 167 28t167 -28l194 194q-171 90 -361 90zM218 279l194 194 q-28 82 -28 167t28 167l-194 194q-90 -171 -90 -361t90 -361zM896 -128q190 0 361 90l-194 194q-82 -28 -167 -28t-167 28l-194 -194q171 -90 361 -90zM896 256q159 0 271.5 112.5t112.5 271.5t-112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5 t271.5 -112.5zM1380 473l194 -194q90 171 90 361t-90 361l-194 -194q28 -82 28 -167t-28 -167z" />
+<glyph unicode="&#xf1ce;" horiz-adv-x="1792" d="M1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348q0 222 101 414.5t276.5 317t390.5 155.5v-260q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 q0 230 -145.5 406t-366.5 221v260q215 -31 390.5 -155.5t276.5 -317t101 -414.5z" />
+<glyph unicode="&#xf1d0;" horiz-adv-x="1792" d="M19 662q8 217 116 406t305 318h5q0 -1 -1 -3q-8 -8 -28 -33.5t-52 -76.5t-60 -110.5t-44.5 -135.5t-14 -150.5t39 -157.5t108.5 -154q50 -50 102 -69.5t90.5 -11.5t69.5 23.5t47 32.5l16 16q39 51 53 116.5t6.5 122.5t-21 107t-26.5 80l-14 29q-10 25 -30.5 49.5t-43 41 t-43.5 29.5t-35 19l-13 6l104 115q39 -17 78 -52t59 -61l19 -27q1 48 -18.5 103.5t-40.5 87.5l-20 31l161 183l160 -181q-33 -46 -52.5 -102.5t-22.5 -90.5l-4 -33q22 37 61.5 72.5t67.5 52.5l28 17l103 -115q-44 -14 -85 -50t-60 -65l-19 -29q-31 -56 -48 -133.5t-7 -170 t57 -156.5q33 -45 77.5 -60.5t85 -5.5t76 26.5t57.5 33.5l21 16q60 53 96.5 115t48.5 121.5t10 121.5t-18 118t-37 107.5t-45.5 93t-45 72t-34.5 47.5l-13 17q-14 13 -7 13l10 -3q40 -29 62.5 -46t62 -50t64 -58t58.5 -65t55.5 -77t45.5 -88t38 -103t23.5 -117t10.5 -136 q3 -259 -108 -465t-312 -321t-456 -115q-185 0 -351 74t-283.5 198t-184 293t-60.5 353z" />
+<glyph unicode="&#xf1d1;" horiz-adv-x="1792" d="M874 -102v-66q-208 6 -385 109.5t-283 275.5l58 34q29 -49 73 -99l65 57q148 -168 368 -212l-17 -86q65 -12 121 -13zM276 428l-83 -28q22 -60 49 -112l-57 -33q-98 180 -98 385t98 385l57 -33q-30 -56 -49 -112l82 -28q-35 -100 -35 -212q0 -109 36 -212zM1528 251 l58 -34q-106 -172 -283 -275.5t-385 -109.5v66q56 1 121 13l-17 86q220 44 368 212l65 -57q44 50 73 99zM1377 805l-233 -80q14 -42 14 -85t-14 -85l232 -80q-31 -92 -98 -169l-185 162q-57 -67 -147 -85l48 -241q-52 -10 -98 -10t-98 10l48 241q-90 18 -147 85l-185 -162 q-67 77 -98 169l232 80q-14 42 -14 85t14 85l-233 80q33 93 99 169l185 -162q59 68 147 86l-48 240q44 10 98 10t98 -10l-48 -240q88 -18 147 -86l185 162q66 -76 99 -169zM874 1448v-66q-65 -2 -121 -13l17 -86q-220 -42 -368 -211l-65 56q-38 -42 -73 -98l-57 33 q106 172 282 275.5t385 109.5zM1705 640q0 -205 -98 -385l-57 33q27 52 49 112l-83 28q36 103 36 212q0 112 -35 212l82 28q-19 56 -49 112l57 33q98 -180 98 -385zM1585 1063l-57 -33q-35 56 -73 98l-65 -56q-148 169 -368 211l17 86q-56 11 -121 13v66q209 -6 385 -109.5 t282 -275.5zM1748 640q0 173 -67.5 331t-181.5 272t-272 181.5t-331 67.5t-331 -67.5t-272 -181.5t-181.5 -272t-67.5 -331t67.5 -331t181.5 -272t272 -181.5t331 -67.5t331 67.5t272 181.5t181.5 272t67.5 331zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71 t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf1d2;" d="M582 228q0 -66 -93 -66q-107 0 -107 63q0 64 98 64q102 0 102 -61zM546 694q0 -85 -74 -85q-77 0 -77 84q0 90 77 90q36 0 55 -25.5t19 -63.5zM712 769v125q-78 -29 -135 -29q-50 29 -110 29q-86 0 -145 -57t-59 -143q0 -50 29.5 -102t73.5 -67v-3q-38 -17 -38 -85 q0 -53 41 -77v-3q-113 -37 -113 -139q0 -45 20 -78.5t54 -51t72 -25.5t81 -8q224 0 224 188q0 67 -48 99t-126 46q-27 5 -51.5 20.5t-24.5 39.5q0 44 49 52q77 15 122 70t45 134q0 24 -10 52q37 9 49 13zM771 350h137q-2 27 -2 82v387q0 46 2 69h-137q3 -23 3 -71v-392 q0 -50 -3 -75zM1280 366v121q-30 -21 -68 -21q-53 0 -53 82v225h52q9 0 26.5 -1t26.5 -1v117h-105q0 82 3 102h-140q4 -24 4 -55v-47h-60v-117q36 3 37 3q3 0 11 -0.5t12 -0.5v-2h-2v-217q0 -37 2.5 -64t11.5 -56.5t24.5 -48.5t43.5 -31t66 -12q64 0 108 24zM924 1072 q0 36 -24 63.5t-60 27.5t-60.5 -27t-24.5 -64q0 -36 25 -62.5t60 -26.5t59.5 27t24.5 62zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf1d3;" horiz-adv-x="1792" d="M595 22q0 100 -165 100q-158 0 -158 -104q0 -101 172 -101q151 0 151 105zM536 777q0 61 -30 102t-89 41q-124 0 -124 -145q0 -135 124 -135q119 0 119 137zM805 1101v-202q-36 -12 -79 -22q16 -43 16 -84q0 -127 -73 -216.5t-197 -112.5q-40 -8 -59.5 -27t-19.5 -58 q0 -31 22.5 -51.5t58 -32t78.5 -22t86 -25.5t78.5 -37.5t58 -64t22.5 -98.5q0 -304 -363 -304q-69 0 -130 12.5t-116 41t-87.5 82t-32.5 127.5q0 165 182 225v4q-67 41 -67 126q0 109 63 137v4q-72 24 -119.5 108.5t-47.5 165.5q0 139 95 231.5t235 92.5q96 0 178 -47 q98 0 218 47zM1123 220h-222q4 45 4 134v609q0 94 -4 128h222q-4 -33 -4 -124v-613q0 -89 4 -134zM1724 442v-196q-71 -39 -174 -39q-62 0 -107 20t-70 50t-39.5 78t-18.5 92t-4 103v351h2v4q-7 0 -19 1t-18 1q-21 0 -59 -6v190h96v76q0 54 -6 89h227q-6 -41 -6 -165h171 v-190q-15 0 -43.5 2t-42.5 2h-85v-365q0 -131 87 -131q61 0 109 33zM1148 1389q0 -58 -39 -101.5t-96 -43.5q-58 0 -98 43.5t-40 101.5q0 59 39.5 103t98.5 44q58 0 96.5 -44.5t38.5 -102.5z" />
+<glyph unicode="&#xf1d4;" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf1d5;" horiz-adv-x="1280" d="M842 964q0 -80 -57 -136.5t-136 -56.5q-60 0 -111 35q-62 -67 -115 -146q-247 -371 -202 -859q1 -22 -12.5 -38.5t-34.5 -18.5h-5q-20 0 -35 13.5t-17 33.5q-14 126 -3.5 247.5t29.5 217t54 186t69 155.5t74 125q61 90 132 165q-16 35 -16 77q0 80 56.5 136.5t136.5 56.5 t136.5 -56.5t56.5 -136.5zM1223 953q0 -158 -78 -292t-212.5 -212t-292.5 -78q-64 0 -131 14q-21 5 -32.5 23.5t-6.5 39.5q5 20 23 31.5t39 7.5q51 -13 108 -13q97 0 186 38t153 102t102 153t38 186t-38 186t-102 153t-153 102t-186 38t-186 -38t-153 -102t-102 -153 t-38 -186q0 -114 52 -218q10 -20 3.5 -40t-25.5 -30t-39.5 -3t-30.5 26q-64 123 -64 265q0 119 46.5 227t124.5 186t186 124t226 46q158 0 292.5 -78t212.5 -212.5t78 -292.5z" />
+<glyph unicode="&#xf1d6;" horiz-adv-x="1792" d="M270 730q-8 19 -8 52q0 20 11 49t24 45q-1 22 7.5 53t22.5 43q0 139 92.5 288.5t217.5 209.5q139 66 324 66q133 0 266 -55q49 -21 90 -48t71 -56t55 -68t42 -74t32.5 -84.5t25.5 -89.5t22 -98l1 -5q55 -83 55 -150q0 -14 -9 -40t-9 -38q0 -1 1.5 -3.5t3.5 -5t2 -3.5 q77 -114 120.5 -214.5t43.5 -208.5q0 -43 -19.5 -100t-55.5 -57q-9 0 -19.5 7.5t-19 17.5t-19 26t-16 26.5t-13.5 26t-9 17.5q-1 1 -3 1l-5 -4q-59 -154 -132 -223q20 -20 61.5 -38.5t69 -41.5t35.5 -65q-2 -4 -4 -16t-7 -18q-64 -97 -302 -97q-53 0 -110.5 9t-98 20 t-104.5 30q-15 5 -23 7q-14 4 -46 4.5t-40 1.5q-41 -45 -127.5 -65t-168.5 -20q-35 0 -69 1.5t-93 9t-101 20.5t-74.5 40t-32.5 64q0 40 10 59.5t41 48.5q11 2 40.5 13t49.5 12q4 0 14 2q2 2 2 4l-2 3q-48 11 -108 105.5t-73 156.5l-5 3q-4 0 -12 -20q-18 -41 -54.5 -74.5 t-77.5 -37.5h-1q-4 0 -6 4.5t-5 5.5q-23 54 -23 100q0 275 252 466z" />
+<glyph unicode="&#xf1d7;" horiz-adv-x="2048" d="M580 1075q0 41 -25 66t-66 25q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 66 24.5t25 65.5zM1323 568q0 28 -25.5 50t-65.5 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q40 0 65.5 22t25.5 51zM1087 1075q0 41 -24.5 66t-65.5 25 q-43 0 -76 -25.5t-33 -65.5q0 -39 33 -64.5t76 -25.5q41 0 65.5 24.5t24.5 65.5zM1722 568q0 28 -26 50t-65 22q-27 0 -49.5 -22.5t-22.5 -49.5q0 -28 22.5 -50.5t49.5 -22.5q39 0 65 22t26 51zM1456 965q-31 4 -70 4q-169 0 -311 -77t-223.5 -208.5t-81.5 -287.5 q0 -78 23 -152q-35 -3 -68 -3q-26 0 -50 1.5t-55 6.5t-44.5 7t-54.5 10.5t-50 10.5l-253 -127l72 218q-290 203 -290 490q0 169 97.5 311t264 223.5t363.5 81.5q176 0 332.5 -66t262 -182.5t136.5 -260.5zM2048 404q0 -117 -68.5 -223.5t-185.5 -193.5l55 -181l-199 109 q-150 -37 -218 -37q-169 0 -311 70.5t-223.5 191.5t-81.5 264t81.5 264t223.5 191.5t311 70.5q161 0 303 -70.5t227.5 -192t85.5 -263.5z" />
+<glyph unicode="&#xf1d8;" horiz-adv-x="1792" d="M1764 1525q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-453 185l-242 -295q-18 -23 -49 -23q-13 0 -22 4q-19 7 -30.5 23.5t-11.5 36.5v349l864 1059l-1069 -925l-395 162q-37 14 -40 55q-2 40 32 59l1664 960q15 9 32 9q20 0 36 -11z" />
+<glyph unicode="&#xf1d9;" horiz-adv-x="1792" d="M1764 1525q33 -24 27 -64l-256 -1536q-5 -29 -32 -45q-14 -8 -31 -8q-11 0 -24 5l-527 215l-298 -327q-18 -21 -47 -21q-14 0 -23 4q-19 7 -30 23.5t-11 36.5v452l-472 193q-37 14 -40 55q-3 39 32 59l1664 960q35 21 68 -2zM1422 26l221 1323l-1434 -827l336 -137 l863 639l-478 -797z" />
+<glyph unicode="&#xf1da;" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-172 0 -327 72.5t-264 204.5q-7 10 -6.5 22.5t8.5 20.5l137 138q10 9 25 9q16 -2 23 -12q73 -95 179 -147t225 -52q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5 t-163.5 109.5t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298zM896 928v-448q0 -14 -9 -23 t-23 -9h-320q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v352q0 14 9 23t23 9h64q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf1db;" d="M768 1280q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf1dc;" horiz-adv-x="1792" d="M1682 -128q-44 0 -132.5 3.5t-133.5 3.5q-44 0 -132 -3.5t-132 -3.5q-24 0 -37 20.5t-13 45.5q0 31 17 46t39 17t51 7t45 15q33 21 33 140l-1 391q0 21 -1 31q-13 4 -50 4h-675q-38 0 -51 -4q-1 -10 -1 -31l-1 -371q0 -142 37 -164q16 -10 48 -13t57 -3.5t45 -15 t20 -45.5q0 -26 -12.5 -48t-36.5 -22q-47 0 -139.5 3.5t-138.5 3.5q-43 0 -128 -3.5t-127 -3.5q-23 0 -35.5 21t-12.5 45q0 30 15.5 45t36 17.5t47.5 7.5t42 15q33 23 33 143l-1 57v813q0 3 0.5 26t0 36.5t-1.5 38.5t-3.5 42t-6.5 36.5t-11 31.5t-16 18q-15 10 -45 12t-53 2 t-41 14t-18 45q0 26 12 48t36 22q46 0 138.5 -3.5t138.5 -3.5q42 0 126.5 3.5t126.5 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17 -43.5t-38.5 -14.5t-49.5 -4t-43 -13q-35 -21 -35 -160l1 -320q0 -21 1 -32q13 -3 39 -3h699q25 0 38 3q1 11 1 32l1 320q0 139 -35 160 q-18 11 -58.5 12.5t-66 13t-25.5 49.5q0 26 12.5 48t37.5 22q44 0 132 -3.5t132 -3.5q43 0 129 3.5t129 3.5q25 0 37.5 -22t12.5 -48q0 -30 -17.5 -44t-40 -14.5t-51.5 -3t-44 -12.5q-35 -23 -35 -161l1 -943q0 -119 34 -140q16 -10 46 -13.5t53.5 -4.5t41.5 -15.5t18 -44.5 q0 -26 -12 -48t-36 -22z" />
+<glyph unicode="&#xf1dd;" horiz-adv-x="1280" d="M1278 1347v-73q0 -29 -18.5 -61t-42.5 -32q-50 0 -54 -1q-26 -6 -32 -31q-3 -11 -3 -64v-1152q0 -25 -18 -43t-43 -18h-108q-25 0 -43 18t-18 43v1218h-143v-1218q0 -25 -17.5 -43t-43.5 -18h-108q-26 0 -43.5 18t-17.5 43v496q-147 12 -245 59q-126 58 -192 179 q-64 117 -64 259q0 166 88 286q88 118 209 159q111 37 417 37h479q25 0 43 -18t18 -43z" />
+<glyph unicode="&#xf1de;" d="M352 128v-128h-352v128h352zM704 256q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM864 640v-128h-864v128h864zM224 1152v-128h-224v128h224zM1536 128v-128h-736v128h736zM576 1280q26 0 45 -19t19 -45v-256 q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM1216 768q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h256zM1536 640v-128h-224v128h224zM1536 1152v-128h-864v128h864z" />
+<glyph unicode="&#xf1e0;" d="M1216 512q133 0 226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5t-226.5 93.5t-93.5 226.5q0 12 2 34l-360 180q-92 -86 -218 -86q-133 0 -226.5 93.5t-93.5 226.5t93.5 226.5t226.5 93.5q126 0 218 -86l360 180q-2 22 -2 34q0 133 93.5 226.5t226.5 93.5 t226.5 -93.5t93.5 -226.5t-93.5 -226.5t-226.5 -93.5q-126 0 -218 86l-360 -180q2 -22 2 -34t-2 -34l360 -180q92 86 218 86z" />
+<glyph unicode="&#xf1e1;" d="M1280 341q0 88 -62.5 151t-150.5 63q-84 0 -145 -58l-241 120q2 16 2 23t-2 23l241 120q61 -58 145 -58q88 0 150.5 63t62.5 151t-62.5 150.5t-150.5 62.5t-151 -62.5t-63 -150.5q0 -7 2 -23l-241 -120q-62 57 -145 57q-88 0 -150.5 -62.5t-62.5 -150.5t62.5 -150.5 t150.5 -62.5q83 0 145 57l241 -120q-2 -16 -2 -23q0 -88 63 -150.5t151 -62.5t150.5 62.5t62.5 150.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf1e2;" horiz-adv-x="1792" d="M571 947q-10 25 -34 35t-49 0q-108 -44 -191 -127t-127 -191q-10 -25 0 -49t35 -34q13 -5 24 -5q42 0 60 40q34 84 98.5 148.5t148.5 98.5q25 11 35 35t0 49zM1513 1303l46 -46l-244 -243l68 -68q19 -19 19 -45.5t-19 -45.5l-64 -64q89 -161 89 -343q0 -143 -55.5 -273.5 t-150 -225t-225 -150t-273.5 -55.5t-273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5q182 0 343 -89l64 64q19 19 45.5 19t45.5 -19l68 -68zM1521 1359q-10 -10 -22 -10q-13 0 -23 10l-91 90q-9 10 -9 23t9 23q10 9 23 9t23 -9l90 -91 q10 -9 10 -22.5t-10 -22.5zM1751 1129q-11 -9 -23 -9t-23 9l-90 91q-10 9 -10 22.5t10 22.5q9 10 22.5 10t22.5 -10l91 -90q9 -10 9 -23t-9 -23zM1792 1312q0 -14 -9 -23t-23 -9h-96q-14 0 -23 9t-9 23t9 23t23 9h96q14 0 23 -9t9 -23zM1600 1504v-96q0 -14 -9 -23t-23 -9 t-23 9t-9 23v96q0 14 9 23t23 9t23 -9t9 -23zM1751 1449l-91 -90q-10 -10 -22 -10q-13 0 -23 10q-10 9 -10 22.5t10 22.5l90 91q10 9 23 9t23 -9q9 -10 9 -23t-9 -23z" />
+<glyph unicode="&#xf1e3;" horiz-adv-x="1792" d="M609 720l287 208l287 -208l-109 -336h-355zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM1515 186q149 203 149 454v3l-102 -89l-240 224l63 323 l134 -12q-150 206 -389 282l53 -124l-287 -159l-287 159l53 124q-239 -76 -389 -282l135 12l62 -323l-240 -224l-102 89v-3q0 -251 149 -454l30 132l326 -40l139 -298l-116 -69q117 -39 240 -39t240 39l-116 69l139 298l326 40z" />
+<glyph unicode="&#xf1e4;" horiz-adv-x="1792" d="M448 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM256 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM832 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM66 768q-28 0 -47 19t-19 46v129h514v-129q0 -27 -19 -46t-46 -19h-383zM1216 224v-192q0 -14 -9 -23t-23 -9h-192 q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1600 224v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23 zM1408 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1016v-13h-514v10q0 104 -382 102q-382 -1 -382 -102v-10h-514v13q0 17 8.5 43t34 64t65.5 75.5t110.5 76t160 67.5t224 47.5t293.5 18.5t293 -18.5t224 -47.5 t160.5 -67.5t110.5 -76t65.5 -75.5t34 -64t8.5 -43zM1792 608v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 962v-129q0 -27 -19 -46t-46 -19h-384q-27 0 -46 19t-19 46v129h514z" />
+<glyph unicode="&#xf1e5;" horiz-adv-x="1792" d="M704 1216v-768q0 -26 -19 -45t-45 -19v-576q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v512l249 873q7 23 31 23h424zM1024 1216v-704h-256v704h256zM1792 320v-512q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v576q-26 0 -45 19t-19 45v768h424q24 0 31 -23z M736 1504v-224h-352v224q0 14 9 23t23 9h288q14 0 23 -9t9 -23zM1408 1504v-224h-352v224q0 14 9 23t23 9h288q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf1e6;" horiz-adv-x="1792" d="M1755 1083q37 -37 37 -90t-37 -91l-401 -400l150 -150l-160 -160q-163 -163 -389.5 -186.5t-411.5 100.5l-362 -362h-181v181l362 362q-124 185 -100.5 411.5t186.5 389.5l160 160l150 -150l400 401q38 37 91 37t90 -37t37 -90.5t-37 -90.5l-400 -401l234 -234l401 400 q38 37 91 37t90 -37z" />
+<glyph unicode="&#xf1e7;" horiz-adv-x="1792" d="M873 796q0 -83 -63.5 -142.5t-152.5 -59.5t-152.5 59.5t-63.5 142.5q0 84 63.5 143t152.5 59t152.5 -59t63.5 -143zM1375 796q0 -83 -63 -142.5t-153 -59.5q-89 0 -152.5 59.5t-63.5 142.5q0 84 63.5 143t152.5 59q90 0 153 -59t63 -143zM1600 616v667q0 87 -32 123.5 t-111 36.5h-1112q-83 0 -112.5 -34t-29.5 -126v-673q43 -23 88.5 -40t81 -28t81 -18.5t71 -11t70 -4t58.5 -0.5t56.5 2t44.5 2q68 1 95 -27q6 -6 10 -9q26 -25 61 -51q7 91 118 87q5 0 36.5 -1.5t43 -2t45.5 -1t53 1t54.5 4.5t61 8.5t62 13.5t67 19.5t67.5 27t72 34.5z M1763 621q-121 -149 -372 -252q84 -285 -23 -465q-66 -113 -183 -148q-104 -32 -182 15q-86 51 -82 164l-1 326v1q-8 2 -24.5 6t-23.5 5l-1 -338q4 -114 -83 -164q-79 -47 -183 -15q-117 36 -182 150q-105 180 -22 463q-251 103 -372 252q-25 37 -4 63t60 -1q3 -2 11 -7 t11 -8v694q0 72 47 123t114 51h1257q67 0 114 -51t47 -123v-694l21 15q39 27 60 1t-4 -63z" />
+<glyph unicode="&#xf1e8;" horiz-adv-x="1792" d="M896 1102v-434h-145v434h145zM1294 1102v-434h-145v434h145zM1294 342l253 254v795h-1194v-1049h326v-217l217 217h398zM1692 1536v-1013l-434 -434h-326l-217 -217h-217v217h-398v1158l109 289h1483z" />
+<glyph unicode="&#xf1e9;" d="M773 217v-127q-1 -292 -6 -305q-12 -32 -51 -40q-54 -9 -181.5 38t-162.5 89q-13 15 -17 36q-1 12 4 26q4 10 34 47t181 216q1 0 60 70q15 19 39.5 24.5t49.5 -3.5q24 -10 37.5 -29t12.5 -42zM624 468q-3 -55 -52 -70l-120 -39q-275 -88 -292 -88q-35 2 -54 36 q-12 25 -17 75q-8 76 1 166.5t30 124.5t56 32q13 0 202 -77q70 -29 115 -47l84 -34q23 -9 35.5 -30.5t11.5 -48.5zM1450 171q-7 -54 -91.5 -161t-135.5 -127q-37 -14 -63 7q-14 10 -184 287l-47 77q-14 21 -11.5 46t19.5 46q35 43 83 26q1 -1 119 -40q203 -66 242 -79.5 t47 -20.5q28 -22 22 -61zM778 803q5 -102 -54 -122q-58 -17 -114 71l-378 598q-8 35 19 62q41 43 207.5 89.5t224.5 31.5q40 -10 49 -45q3 -18 22 -305.5t24 -379.5zM1440 695q3 -39 -26 -59q-15 -10 -329 -86q-67 -15 -91 -23l1 2q-23 -6 -46 4t-37 32q-30 47 0 87 q1 1 75 102q125 171 150 204t34 39q28 19 65 2q48 -23 123 -133.5t81 -167.5v-3z" />
+<glyph unicode="&#xf1ea;" horiz-adv-x="2048" d="M1024 1024h-384v-384h384v384zM1152 384v-128h-640v128h640zM1152 1152v-640h-640v640h640zM1792 384v-128h-512v128h512zM1792 640v-128h-512v128h512zM1792 896v-128h-512v128h512zM1792 1152v-128h-512v128h512zM256 192v960h-128v-960q0 -26 19 -45t45 -19t45 19 t19 45zM1920 192v1088h-1536v-1088q0 -33 -11 -64h1483q26 0 45 19t19 45zM2048 1408v-1216q0 -80 -56 -136t-136 -56h-1664q-80 0 -136 56t-56 136v1088h256v128h1792z" />
+<glyph unicode="&#xf1eb;" horiz-adv-x="2048" d="M1024 13q-20 0 -93 73.5t-73 93.5q0 32 62.5 54t103.5 22t103.5 -22t62.5 -54q0 -20 -73 -93.5t-93 -73.5zM1294 284q-2 0 -40 25t-101.5 50t-128.5 25t-128.5 -25t-101 -50t-40.5 -25q-18 0 -93.5 75t-75.5 93q0 13 10 23q78 77 196 121t233 44t233 -44t196 -121 q10 -10 10 -23q0 -18 -75.5 -93t-93.5 -75zM1567 556q-11 0 -23 8q-136 105 -252 154.5t-268 49.5q-85 0 -170.5 -22t-149 -53t-113.5 -62t-79 -53t-31 -22q-17 0 -92 75t-75 93q0 12 10 22q132 132 320 205t380 73t380 -73t320 -205q10 -10 10 -22q0 -18 -75 -93t-92 -75z M1838 827q-11 0 -22 9q-179 157 -371.5 236.5t-420.5 79.5t-420.5 -79.5t-371.5 -236.5q-11 -9 -22 -9q-17 0 -92.5 75t-75.5 93q0 13 10 23q187 186 445 288t527 102t527 -102t445 -288q10 -10 10 -23q0 -18 -75.5 -93t-92.5 -75z" />
+<glyph unicode="&#xf1ec;" horiz-adv-x="1792" d="M384 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5 t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 0q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5 t37.5 90.5zM384 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1152 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM768 768q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1536 0v384q0 52 -38 90t-90 38t-90 -38t-38 -90v-384q0 -52 38 -90t90 -38t90 38t38 90zM1152 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5z M1536 1088v256q0 26 -19 45t-45 19h-1280q-26 0 -45 -19t-19 -45v-256q0 -26 19 -45t45 -19h1280q26 0 45 19t19 45zM1536 768q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1408v-1536q0 -52 -38 -90t-90 -38 h-1408q-52 0 -90 38t-38 90v1536q0 52 38 90t90 38h1408q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf1ed;" d="M1519 890q18 -84 -4 -204q-87 -444 -565 -444h-44q-25 0 -44 -16.5t-24 -42.5l-4 -19l-55 -346l-2 -15q-5 -26 -24.5 -42.5t-44.5 -16.5h-251q-21 0 -33 15t-9 36q9 56 26.5 168t26.5 168t27 167.5t27 167.5q5 37 43 37h131q133 -2 236 21q175 39 287 144q102 95 155 246 q24 70 35 133q1 6 2.5 7.5t3.5 1t6 -3.5q79 -59 98 -162zM1347 1172q0 -107 -46 -236q-80 -233 -302 -315q-113 -40 -252 -42q0 -1 -90 -1l-90 1q-100 0 -118 -96q-2 -8 -85 -530q-1 -10 -12 -10h-295q-22 0 -36.5 16.5t-11.5 38.5l232 1471q5 29 27.5 48t51.5 19h598 q34 0 97.5 -13t111.5 -32q107 -41 163.5 -123t56.5 -196z" />
+<glyph unicode="&#xf1ee;" horiz-adv-x="1792" d="M602 949q19 -61 31 -123.5t17 -141.5t-14 -159t-62 -145q-21 81 -67 157t-95.5 127t-99 90.5t-78.5 57.5t-33 19q-62 34 -81.5 100t14.5 128t101 81.5t129 -14.5q138 -83 238 -177zM927 1236q11 -25 20.5 -46t36.5 -100.5t42.5 -150.5t25.5 -179.5t0 -205.5t-47.5 -209.5 t-105.5 -208.5q-51 -72 -138 -72q-54 0 -98 31q-57 40 -69 109t28 127q60 85 81 195t13 199.5t-32 180.5t-39 128t-22 52q-31 63 -8.5 129.5t85.5 97.5q34 17 75 17q47 0 88.5 -25t63.5 -69zM1248 567q-17 -160 -72 -311q-17 131 -63 246q25 174 -5 361q-27 178 -94 342 q114 -90 212 -211q9 -37 15 -80q26 -179 7 -347zM1520 1440q9 -17 23.5 -49.5t43.5 -117.5t50.5 -178t34 -227.5t5 -269t-47 -300t-112.5 -323.5q-22 -48 -66 -75.5t-95 -27.5q-39 0 -74 16q-67 31 -92.5 100t4.5 136q58 126 90 257.5t37.5 239.5t-3.5 213.5t-26.5 180.5 t-38.5 138.5t-32.5 90t-15.5 32.5q-34 65 -11.5 135.5t87.5 104.5q37 20 81 20q49 0 91.5 -25.5t66.5 -70.5z" />
+<glyph unicode="&#xf1f0;" horiz-adv-x="2304" d="M1975 546h-138q14 37 66 179l3 9q4 10 10 26t9 26l12 -55zM531 611l-58 295q-11 54 -75 54h-268l-2 -13q311 -79 403 -336zM710 960l-162 -438l-17 89q-26 70 -85 129.5t-131 88.5l135 -510h175l261 641h-176zM849 318h166l104 642h-166zM1617 944q-69 27 -149 27 q-123 0 -201 -59t-79 -153q-1 -102 145 -174q48 -23 67 -41t19 -39q0 -30 -30 -46t-69 -16q-86 0 -156 33l-22 11l-23 -144q74 -34 185 -34q130 -1 208.5 59t80.5 160q0 106 -140 174q-49 25 -71 42t-22 38q0 22 24.5 38.5t70.5 16.5q70 1 124 -24l15 -8zM2042 960h-128 q-65 0 -87 -54l-246 -588h174l35 96h212q5 -22 20 -96h154zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf1f1;" horiz-adv-x="2304" d="M671 603h-13q-47 0 -47 -32q0 -22 20 -22q17 0 28 15t12 39zM1066 639h62v3q1 4 0.5 6.5t-1 7t-2 8t-4.5 6.5t-7.5 5t-11.5 2q-28 0 -36 -38zM1606 603h-12q-48 0 -48 -32q0 -22 20 -22q17 0 28 15t12 39zM1925 629q0 41 -30 41q-19 0 -31 -20t-12 -51q0 -42 28 -42 q20 0 32.5 20t12.5 52zM480 770h87l-44 -262h-56l32 201l-71 -201h-39l-4 200l-34 -200h-53l44 262h81l2 -163zM733 663q0 -6 -4 -42q-16 -101 -17 -113h-47l1 22q-20 -26 -58 -26q-23 0 -37.5 16t-14.5 42q0 39 26 60.5t73 21.5q14 0 23 -1q0 3 0.5 5.5t1 4.5t0.5 3 q0 20 -36 20q-29 0 -59 -10q0 4 7 48q38 11 67 11q74 0 74 -62zM889 721l-8 -49q-22 3 -41 3q-27 0 -27 -17q0 -8 4.5 -12t21.5 -11q40 -19 40 -60q0 -72 -87 -71q-34 0 -58 6q0 2 7 49q29 -8 51 -8q32 0 32 19q0 7 -4.5 11.5t-21.5 12.5q-43 20 -43 59q0 72 84 72 q30 0 50 -4zM977 721h28l-7 -52h-29q-2 -17 -6.5 -40.5t-7 -38.5t-2.5 -18q0 -16 19 -16q8 0 16 2l-8 -47q-21 -7 -40 -7q-43 0 -45 47q0 12 8 56q3 20 25 146h55zM1180 648q0 -23 -7 -52h-111q-3 -22 10 -33t38 -11q30 0 58 14l-9 -54q-30 -8 -57 -8q-95 0 -95 95 q0 55 27.5 90.5t69.5 35.5q35 0 55.5 -21t20.5 -56zM1319 722q-13 -23 -22 -62q-22 2 -31 -24t-25 -128h-56l3 14q22 130 29 199h51l-3 -33q14 21 25.5 29.5t28.5 4.5zM1506 763l-9 -57q-28 14 -50 14q-31 0 -51 -27.5t-20 -70.5q0 -30 13.5 -47t38.5 -17q21 0 48 13 l-10 -59q-28 -8 -50 -8q-45 0 -71.5 30.5t-26.5 82.5q0 70 35.5 114.5t91.5 44.5q26 0 61 -13zM1668 663q0 -18 -4 -42q-13 -79 -17 -113h-46l1 22q-20 -26 -59 -26q-23 0 -37 16t-14 42q0 39 25.5 60.5t72.5 21.5q15 0 23 -1q2 7 2 13q0 20 -36 20q-29 0 -59 -10q0 4 8 48 q38 11 67 11q73 0 73 -62zM1809 722q-14 -24 -21 -62q-23 2 -31.5 -23t-25.5 -129h-56l3 14q19 104 29 199h52q0 -11 -4 -33q15 21 26.5 29.5t27.5 4.5zM1950 770h56l-43 -262h-53l3 19q-23 -23 -52 -23q-31 0 -49.5 24t-18.5 64q0 53 27.5 92t64.5 39q31 0 53 -29z M2061 640q0 148 -72.5 273t-198 198t-273.5 73q-181 0 -328 -110q127 -116 171 -284h-50q-44 150 -158 253q-114 -103 -158 -253h-50q44 168 171 284q-147 110 -328 110q-148 0 -273.5 -73t-198 -198t-72.5 -273t72.5 -273t198 -198t273.5 -73q181 0 328 110 q-120 111 -165 264h50q46 -138 152 -233q106 95 152 233h50q-45 -153 -165 -264q147 -110 328 -110q148 0 273.5 73t198 198t72.5 273zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf1f2;" horiz-adv-x="2304" d="M313 759q0 -51 -36 -84q-29 -26 -89 -26h-17v220h17q61 0 89 -27q36 -31 36 -83zM2089 824q0 -52 -64 -52h-19v101h20q63 0 63 -49zM380 759q0 74 -50 120.5t-129 46.5h-95v-333h95q74 0 119 38q60 51 60 128zM410 593h65v333h-65v-333zM730 694q0 40 -20.5 62t-75.5 42 q-29 10 -39.5 19t-10.5 23q0 16 13.5 26.5t34.5 10.5q29 0 53 -27l34 44q-41 37 -98 37q-44 0 -74 -27.5t-30 -67.5q0 -35 18 -55.5t64 -36.5q37 -13 45 -19q19 -12 19 -34q0 -20 -14 -33.5t-36 -13.5q-48 0 -71 44l-42 -40q44 -64 115 -64q51 0 83 30.5t32 79.5zM1008 604 v77q-37 -37 -78 -37q-49 0 -80.5 32.5t-31.5 82.5q0 48 31.5 81.5t77.5 33.5q43 0 81 -38v77q-40 20 -80 20q-74 0 -125.5 -50.5t-51.5 -123.5t51 -123.5t125 -50.5q42 0 81 19zM2240 0v527q-65 -40 -144.5 -84t-237.5 -117t-329.5 -137.5t-417.5 -134.5t-504 -118h1569 q26 0 45 19t19 45zM1389 757q0 75 -53 128t-128 53t-128 -53t-53 -128t53 -128t128 -53t128 53t53 128zM1541 584l144 342h-71l-90 -224l-89 224h-71l142 -342h35zM1714 593h184v56h-119v90h115v56h-115v74h119v57h-184v-333zM2105 593h80l-105 140q76 16 76 94q0 47 -31 73 t-87 26h-97v-333h65v133h9zM2304 1274v-1268q0 -56 -38.5 -95t-93.5 -39h-2040q-55 0 -93.5 39t-38.5 95v1268q0 56 38.5 95t93.5 39h2040q55 0 93.5 -39t38.5 -95z" />
+<glyph unicode="&#xf1f3;" horiz-adv-x="2304" d="M119 854h89l-45 108zM740 328l74 79l-70 79h-163v-49h142v-55h-142v-54h159zM898 406l99 -110v217zM1186 453q0 33 -40 33h-84v-69h83q41 0 41 36zM1475 457q0 29 -42 29h-82v-61h81q43 0 43 32zM1197 923q0 29 -42 29h-82v-60h81q43 0 43 31zM1656 854h89l-44 108z M699 1009v-271h-66v212l-94 -212h-57l-94 212v-212h-132l-25 60h-135l-25 -60h-70l116 271h96l110 -257v257h106l85 -184l77 184h108zM1255 453q0 -20 -5.5 -35t-14 -25t-22.5 -16.5t-26 -10t-31.5 -4.5t-31.5 -1t-32.5 0.5t-29.5 0.5v-91h-126l-80 90l-83 -90h-256v271h260 l80 -89l82 89h207q109 0 109 -89zM964 794v-56h-217v271h217v-57h-152v-49h148v-55h-148v-54h152zM2304 235v-229q0 -55 -38.5 -94.5t-93.5 -39.5h-2040q-55 0 -93.5 39.5t-38.5 94.5v678h111l25 61h55l25 -61h218v46l19 -46h113l20 47v-47h541v99l10 1q10 0 10 -14v-86h279 v23q23 -12 55 -18t52.5 -6.5t63 0.5t51.5 1l25 61h56l25 -61h227v58l34 -58h182v378h-180v-44l-25 44h-185v-44l-23 44h-249q-69 0 -109 -22v22h-172v-22q-24 22 -73 22h-628l-43 -97l-43 97h-198v-44l-22 44h-169l-78 -179v391q0 55 38.5 94.5t93.5 39.5h2040 q55 0 93.5 -39.5t38.5 -94.5v-678h-120q-51 0 -81 -22v22h-177q-55 0 -78 -22v22h-316v-22q-31 22 -87 22h-209v-22q-23 22 -91 22h-234l-54 -58l-50 58h-349v-378h343l55 59l52 -59h211v89h21q59 0 90 13v-102h174v99h8q8 0 10 -2t2 -10v-87h529q57 0 88 24v-24h168 q60 0 95 17zM1546 469q0 -23 -12 -43t-34 -29q25 -9 34 -26t9 -46v-54h-65v45q0 33 -12 43.5t-46 10.5h-69v-99h-65v271h154q48 0 77 -15t29 -58zM1269 936q0 -24 -12.5 -44t-33.5 -29q26 -9 34.5 -25.5t8.5 -46.5v-53h-65q0 9 0.5 26.5t0 25t-3 18.5t-8.5 16t-17.5 8.5 t-29.5 3.5h-70v-98h-64v271l153 -1q49 0 78 -14.5t29 -57.5zM1798 327v-56h-216v271h216v-56h-151v-49h148v-55h-148v-54zM1372 1009v-271h-66v271h66zM2065 357q0 -86 -102 -86h-126v58h126q34 0 34 25q0 16 -17 21t-41.5 5t-49.5 3.5t-42 22.5t-17 55q0 39 26 60t66 21 h130v-57h-119q-36 0 -36 -25q0 -16 17.5 -20.5t42 -4t49 -2.5t42 -21.5t17.5 -54.5zM2304 407v-101q-24 -35 -88 -35h-125v58h125q33 0 33 25q0 13 -12.5 19t-31 5.5t-40 2t-40 8t-31 24t-12.5 48.5q0 39 26.5 60t66.5 21h129v-57h-118q-36 0 -36 -25q0 -20 29 -22t68.5 -5 t56.5 -26zM2139 1008v-270h-92l-122 203v-203h-132l-26 60h-134l-25 -60h-75q-129 0 -129 133q0 138 133 138h63v-59q-7 0 -28 1t-28.5 0.5t-23 -2t-21.5 -6.5t-14.5 -13.5t-11.5 -23t-3 -33.5q0 -38 13.5 -58t49.5 -20h29l92 213h97l109 -256v256h99l114 -188v188h66z" />
+<glyph unicode="&#xf1f4;" horiz-adv-x="2304" d="M745 630q0 -37 -25.5 -61.5t-62.5 -24.5q-29 0 -46.5 16t-17.5 44q0 37 25 62.5t62 25.5q28 0 46.5 -16.5t18.5 -45.5zM1530 779q0 -42 -22 -57t-66 -15l-32 -1l17 107q2 11 13 11h18q22 0 35 -2t25 -12.5t12 -30.5zM1881 630q0 -36 -25.5 -61t-61.5 -25q-29 0 -47 16 t-18 44q0 37 25 62.5t62 25.5q28 0 46.5 -16.5t18.5 -45.5zM513 801q0 59 -38.5 85.5t-100.5 26.5h-160q-19 0 -21 -19l-65 -408q-1 -6 3 -11t10 -5h76q20 0 22 19l18 110q1 8 7 13t15 6.5t17 1.5t19 -1t14 -1q86 0 135 48.5t49 134.5zM822 489l41 261q1 6 -3 11t-10 5h-76 q-14 0 -17 -33q-27 40 -95 40q-72 0 -122.5 -54t-50.5 -127q0 -59 34.5 -94t92.5 -35q28 0 58 12t48 32q-4 -12 -4 -21q0 -16 13 -16h69q19 0 22 19zM1269 752q0 5 -4 9.5t-9 4.5h-77q-11 0 -18 -10l-106 -156l-44 150q-5 16 -22 16h-75q-5 0 -9 -4.5t-4 -9.5q0 -2 19.5 -59 t42 -123t23.5 -70q-82 -112 -82 -120q0 -13 13 -13h77q11 0 18 10l255 368q2 2 2 7zM1649 801q0 59 -38.5 85.5t-100.5 26.5h-159q-20 0 -22 -19l-65 -408q-1 -6 3 -11t10 -5h82q12 0 16 13l18 116q1 8 7 13t15 6.5t17 1.5t19 -1t14 -1q86 0 135 48.5t49 134.5zM1958 489 l41 261q1 6 -3 11t-10 5h-76q-14 0 -17 -33q-26 40 -95 40q-72 0 -122.5 -54t-50.5 -127q0 -59 34.5 -94t92.5 -35q29 0 59 12t47 32q0 -1 -2 -9t-2 -12q0 -16 13 -16h69q19 0 22 19zM2176 898v1q0 14 -13 14h-74q-11 0 -13 -11l-65 -416l-1 -2q0 -5 4 -9.5t10 -4.5h66 q19 0 21 19zM392 764q-5 -35 -26 -46t-60 -11l-33 -1l17 107q2 11 13 11h19q40 0 58 -11.5t12 -48.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf1f5;" horiz-adv-x="2304" d="M1597 633q0 -69 -21 -106q-19 -35 -52 -35q-23 0 -41 9v224q29 30 57 30q57 0 57 -122zM2035 669h-110q6 98 56 98q51 0 54 -98zM476 534q0 59 -33 91.5t-101 57.5q-36 13 -52 24t-16 25q0 26 38 26q58 0 124 -33l18 112q-67 32 -149 32q-77 0 -123 -38q-48 -39 -48 -109 q0 -58 32.5 -90.5t99.5 -56.5q39 -14 54.5 -25.5t15.5 -27.5q0 -31 -48 -31q-29 0 -70 12.5t-72 30.5l-18 -113q72 -41 168 -41q81 0 129 37q51 41 51 117zM771 749l19 111h-96v135l-129 -21l-18 -114l-46 -8l-17 -103h62v-219q0 -84 44 -120q38 -30 111 -30q32 0 79 11v118 q-32 -7 -44 -7q-42 0 -42 50v197h77zM1087 724v139q-15 3 -28 3q-32 0 -55.5 -16t-33.5 -46l-10 56h-131v-471h150v306q26 31 82 31q16 0 26 -2zM1124 389h150v471h-150v-471zM1746 638q0 122 -45 179q-40 52 -111 52q-64 0 -117 -56l-8 47h-132v-645l150 25v151 q36 -11 68 -11q83 0 134 56q61 65 61 202zM1278 986q0 33 -23 56t-56 23t-56 -23t-23 -56t23 -56.5t56 -23.5t56 23.5t23 56.5zM2176 629q0 113 -48 176q-50 64 -144 64q-96 0 -151.5 -66t-55.5 -180q0 -128 63 -188q55 -55 161 -55q101 0 160 40l-16 103q-57 -31 -128 -31 q-43 0 -63 19q-23 19 -28 66h248q2 14 2 52zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf1f6;" horiz-adv-x="2048" d="M1558 684q61 -356 298 -556q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5zM1024 -176q16 0 16 16t-16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5zM2026 1424q8 -10 7.5 -23.5t-10.5 -22.5 l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5 l418 363q10 8 23.5 7t21.5 -11z" />
+<glyph unicode="&#xf1f7;" horiz-adv-x="2048" d="M1040 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM503 315l877 760q-42 88 -132.5 146.5t-223.5 58.5q-93 0 -169.5 -31.5t-121.5 -80.5t-69 -103t-24 -105q0 -384 -137 -645zM1856 128 q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-180.5 74.5t-75.5 180.5l149 129h757q-166 187 -227 459l111 97q61 -356 298 -556zM1942 1520l84 -96q8 -10 7.5 -23.5t-10.5 -22.5l-1872 -1622q-10 -8 -23.5 -7t-21.5 11l-84 96q-8 10 -7.5 23.5t10.5 21.5l186 161 q-19 32 -19 66q50 42 91 88t85 119.5t74.5 158.5t50 206t19.5 260q0 152 117 282.5t307 158.5q-8 19 -8 39q0 40 28 68t68 28t68 -28t28 -68q0 -20 -8 -39q124 -18 219 -82.5t148 -157.5l418 363q10 8 23.5 7t21.5 -11z" />
+<glyph unicode="&#xf1f8;" horiz-adv-x="1408" d="M512 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM768 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1024 160v704q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-704 q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167 q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf1f9;" d="M1150 462v-109q0 -50 -36.5 -89t-94 -60.5t-118 -32.5t-117.5 -11q-205 0 -342.5 139t-137.5 346q0 203 136 339t339 136q34 0 75.5 -4.5t93 -18t92.5 -34t69 -56.5t28 -81v-109q0 -16 -16 -16h-118q-16 0 -16 16v70q0 43 -65.5 67.5t-137.5 24.5q-140 0 -228.5 -91.5 t-88.5 -237.5q0 -151 91.5 -249.5t233.5 -98.5q68 0 138 24t70 66v70q0 7 4.5 11.5t10.5 4.5h119q6 0 11 -4.5t5 -11.5zM768 1280q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 t-51 248.5t-136.5 204t-204 136.5t-248.5 51zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf1fa;" d="M972 761q0 108 -53.5 169t-147.5 61q-63 0 -124 -30.5t-110 -84.5t-79.5 -137t-30.5 -180q0 -112 53.5 -173t150.5 -61q96 0 176 66.5t122.5 166t42.5 203.5zM1536 640q0 -111 -37 -197t-98.5 -135t-131.5 -74.5t-145 -27.5q-6 0 -15.5 -0.5t-16.5 -0.5q-95 0 -142 53 q-28 33 -33 83q-52 -66 -131.5 -110t-173.5 -44q-161 0 -249.5 95.5t-88.5 269.5q0 157 66 290t179 210.5t246 77.5q87 0 155 -35.5t106 -99.5l2 19l11 56q1 6 5.5 12t9.5 6h118q5 0 13 -11q5 -5 3 -16l-120 -614q-5 -24 -5 -48q0 -39 12.5 -52t44.5 -13q28 1 57 5.5t73 24 t77 50t57 89.5t24 137q0 292 -174 466t-466 174q-130 0 -248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5t51 -248.5t136.5 -204t204 -136.5t248.5 -51q228 0 405 144q11 9 24 8t21 -12l41 -49q8 -12 7 -24q-2 -13 -12 -22q-102 -83 -227.5 -128t-258.5 -45q-156 0 -298 61 t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q344 0 556 -212t212 -556z" />
+<glyph unicode="&#xf1fb;" horiz-adv-x="1792" d="M1698 1442q94 -94 94 -226.5t-94 -225.5l-225 -223l104 -104q10 -10 10 -23t-10 -23l-210 -210q-10 -10 -23 -10t-23 10l-105 105l-603 -603q-37 -37 -90 -37h-203l-256 -128l-64 64l128 256v203q0 53 37 90l603 603l-105 105q-10 10 -10 23t10 23l210 210q10 10 23 10 t23 -10l104 -104l223 225q93 94 225.5 94t226.5 -94zM512 64l576 576l-192 192l-576 -576v-192h192z" />
+<glyph unicode="&#xf1fc;" horiz-adv-x="1792" d="M1615 1536q70 0 122.5 -46.5t52.5 -116.5q0 -63 -45 -151q-332 -629 -465 -752q-97 -91 -218 -91q-126 0 -216.5 92.5t-90.5 219.5q0 128 92 212l638 579q59 54 130 54zM706 502q39 -76 106.5 -130t150.5 -76l1 -71q4 -213 -129.5 -347t-348.5 -134q-123 0 -218 46.5 t-152.5 127.5t-86.5 183t-29 220q7 -5 41 -30t62 -44.5t59 -36.5t46 -17q41 0 55 37q25 66 57.5 112.5t69.5 76t88 47.5t103 25.5t125 10.5z" />
+<glyph unicode="&#xf1fd;" horiz-adv-x="1792" d="M1792 128v-384h-1792v384q45 0 85 14t59 27.5t47 37.5q30 27 51.5 38t56.5 11t55.5 -11t52.5 -38q29 -25 47 -38t58 -27t86 -14q45 0 85 14.5t58 27t48 37.5q21 19 32.5 27t31 15t43.5 7q35 0 56.5 -11t51.5 -38q28 -24 47 -37.5t59 -27.5t85 -14t85 14t59 27.5t47 37.5 q30 27 51.5 38t56.5 11q34 0 55.5 -11t51.5 -38q28 -24 47 -37.5t59 -27.5t85 -14zM1792 448v-192q-35 0 -55.5 11t-52.5 38q-29 25 -47 38t-58 27t-85 14q-46 0 -86 -14t-58 -27t-47 -38q-22 -19 -33 -27t-31 -15t-44 -7q-35 0 -56.5 11t-51.5 38q-29 25 -47 38t-58 27 t-86 14q-45 0 -85 -14.5t-58 -27t-48 -37.5q-21 -19 -32.5 -27t-31 -15t-43.5 -7q-35 0 -56.5 11t-51.5 38q-28 24 -47 37.5t-59 27.5t-85 14q-46 0 -86 -14t-58 -27t-47 -38q-30 -27 -51.5 -38t-56.5 -11v192q0 80 56 136t136 56h64v448h256v-448h256v448h256v-448h256v448 h256v-448h64q80 0 136 -56t56 -136zM512 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150zM1024 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51 t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150zM1536 1312q0 -77 -36 -118.5t-92 -41.5q-53 0 -90.5 37.5t-37.5 90.5q0 29 9.5 51t23.5 34t31 28t31 31.5t23.5 44.5t9.5 67q38 0 83 -74t45 -150z" />
+<glyph unicode="&#xf1fe;" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1664 1024l256 -896h-1664v576l448 576l576 -576z" />
+<glyph unicode="&#xf200;" horiz-adv-x="1792" d="M768 646l546 -546q-106 -108 -247.5 -168t-298.5 -60q-209 0 -385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103v-762zM955 640h773q0 -157 -60 -298.5t-168 -247.5zM1664 768h-768v768q209 0 385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf201;" horiz-adv-x="2048" d="M2048 0v-128h-2048v1536h128v-1408h1920zM1920 1248v-435q0 -21 -19.5 -29.5t-35.5 7.5l-121 121l-633 -633q-10 -10 -23 -10t-23 10l-233 233l-416 -416l-192 192l585 585q10 10 23 10t23 -10l233 -233l464 464l-121 121q-16 16 -7.5 35.5t29.5 19.5h435q14 0 23 -9 t9 -23z" />
+<glyph unicode="&#xf202;" horiz-adv-x="1792" d="M1292 832q0 -6 10 -41q10 -29 25 -49.5t41 -34t44 -20t55 -16.5q325 -91 325 -332q0 -146 -105.5 -242.5t-254.5 -96.5q-59 0 -111.5 18.5t-91.5 45.5t-77 74.5t-63 87.5t-53.5 103.5t-43.5 103t-39.5 106.5t-35.5 95q-32 81 -61.5 133.5t-73.5 96.5t-104 64t-142 20 q-96 0 -183 -55.5t-138 -144.5t-51 -185q0 -160 106.5 -279.5t263.5 -119.5q177 0 258 95q56 63 83 116l84 -152q-15 -34 -44 -70l1 -1q-131 -152 -388 -152q-147 0 -269.5 79t-190.5 207.5t-68 274.5q0 105 43.5 206t116 176.5t172 121.5t204.5 46q87 0 159 -19t123.5 -50 t95 -80t72.5 -99t58.5 -117t50.5 -124.5t50 -130.5t55 -127q96 -200 233 -200q81 0 138.5 48.5t57.5 128.5q0 42 -19 72t-50.5 46t-72.5 31.5t-84.5 27t-87.5 34t-81 52t-65 82t-39 122.5q-3 16 -3 33q0 110 87.5 192t198.5 78q78 -3 120.5 -14.5t90.5 -53.5h-1 q12 -11 23 -24.5t26 -36t19 -27.5l-129 -99q-26 49 -54 70v1q-23 21 -97 21q-49 0 -84 -33t-35 -83z" />
+<glyph unicode="&#xf203;" d="M1432 484q0 173 -234 239q-35 10 -53 16.5t-38 25t-29 46.5q0 2 -2 8.5t-3 12t-1 7.5q0 36 24.5 59.5t60.5 23.5q54 0 71 -15h-1q20 -15 39 -51l93 71q-39 54 -49 64q-33 29 -67.5 39t-85.5 10q-80 0 -142 -57.5t-62 -137.5q0 -7 2 -23q16 -96 64.5 -140t148.5 -73 q29 -8 49 -15.5t45 -21.5t38.5 -34.5t13.5 -46.5v-5q1 -58 -40.5 -93t-100.5 -35q-97 0 -167 144q-23 47 -51.5 121.5t-48 125.5t-54 110.5t-74 95.5t-103.5 60.5t-147 24.5q-101 0 -192 -56t-144 -148t-50 -192v-1q4 -108 50.5 -199t133.5 -147.5t196 -56.5q186 0 279 110 q20 27 31 51l-60 109q-42 -80 -99 -116t-146 -36q-115 0 -191 87t-76 204q0 105 82 189t186 84q112 0 170 -53.5t104 -172.5q8 -21 25.5 -68.5t28.5 -76.5t31.5 -74.5t38.5 -74t45.5 -62.5t55.5 -53.5t66 -33t80 -13.5q107 0 183 69.5t76 174.5zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf204;" horiz-adv-x="2048" d="M1152 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1920 640q0 104 -40.5 198.5 t-109.5 163.5t-163.5 109.5t-198.5 40.5h-386q119 -90 188.5 -224t69.5 -288t-69.5 -288t-188.5 -224h386q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM2048 640q0 -130 -51 -248.5t-136.5 -204t-204 -136.5t-248.5 -51h-768q-130 0 -248.5 51t-204 136.5 t-136.5 204t-51 248.5t51 248.5t136.5 204t204 136.5t248.5 51h768q130 0 248.5 -51t204 -136.5t136.5 -204t51 -248.5z" />
+<glyph unicode="&#xf205;" horiz-adv-x="2048" d="M0 640q0 130 51 248.5t136.5 204t204 136.5t248.5 51h768q130 0 248.5 -51t204 -136.5t136.5 -204t51 -248.5t-51 -248.5t-136.5 -204t-204 -136.5t-248.5 -51h-768q-130 0 -248.5 51t-204 136.5t-136.5 204t-51 248.5zM1408 128q104 0 198.5 40.5t163.5 109.5 t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5z" />
+<glyph unicode="&#xf206;" horiz-adv-x="2304" d="M762 384h-314q-40 0 -57.5 35t6.5 67l188 251q-65 31 -137 31q-132 0 -226 -94t-94 -226t94 -226t226 -94q115 0 203 72.5t111 183.5zM576 512h186q-18 85 -75 148zM1056 512l288 384h-480l-99 -132q105 -103 126 -252h165zM2176 448q0 132 -94 226t-226 94 q-60 0 -121 -24l174 -260q15 -23 10 -49t-27 -40q-15 -11 -36 -11q-35 0 -53 29l-174 260q-93 -95 -93 -225q0 -132 94 -226t226 -94t226 94t94 226zM2304 448q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 97 39.5 183.5t109.5 149.5l-65 98l-353 -469 q-18 -26 -51 -26h-197q-23 -164 -149 -274t-294 -110q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q114 0 215 -55l137 183h-224q-26 0 -45 19t-19 45t19 45t45 19h384v-128h435l-85 128h-222q-26 0 -45 19t-19 45t19 45t45 19h256q33 0 53 -28l267 -400 q91 44 192 44q185 0 316.5 -131.5t131.5 -316.5z" />
+<glyph unicode="&#xf207;" d="M384 320q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1408 320q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1362 716l-72 384q-5 23 -22.5 37.5t-40.5 14.5 h-918q-23 0 -40.5 -14.5t-22.5 -37.5l-72 -384q-5 -30 14 -53t49 -23h1062q30 0 49 23t14 53zM1136 1328q0 20 -14 34t-34 14h-640q-20 0 -34 -14t-14 -34t14 -34t34 -14h640q20 0 34 14t14 34zM1536 603v-603h-128v-128q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5 t-37.5 90.5v128h-768v-128q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5v128h-128v603q0 112 25 223l103 454q9 78 97.5 137t230 89t312.5 30t312.5 -30t230 -89t97.5 -137l105 -454q23 -102 23 -223z" />
+<glyph unicode="&#xf208;" horiz-adv-x="2048" d="M1463 704q0 -35 -25 -60.5t-61 -25.5h-702q-36 0 -61 25.5t-25 60.5t25 60.5t61 25.5h702q36 0 61 -25.5t25 -60.5zM1677 704q0 86 -23 170h-982q-36 0 -61 25t-25 60q0 36 25 61t61 25h908q-88 143 -235 227t-320 84q-177 0 -327.5 -87.5t-238 -237.5t-87.5 -327 q0 -86 23 -170h982q36 0 61 -25t25 -60q0 -36 -25 -61t-61 -25h-908q88 -143 235.5 -227t320.5 -84q132 0 253 51.5t208 139t139 208t52 253.5zM2048 959q0 -35 -25 -60t-61 -25h-131q17 -85 17 -170q0 -167 -65.5 -319.5t-175.5 -263t-262.5 -176t-319.5 -65.5 q-246 0 -448.5 133t-301.5 350h-189q-36 0 -61 25t-25 61q0 35 25 60t61 25h132q-17 85 -17 170q0 167 65.5 319.5t175.5 263t262.5 176t320.5 65.5q245 0 447.5 -133t301.5 -350h188q36 0 61 -25t25 -61z" />
+<glyph unicode="&#xf209;" horiz-adv-x="1280" d="M953 1158l-114 -328l117 -21q165 451 165 518q0 56 -38 56q-57 0 -130 -225zM654 471l33 -88q37 42 71 67l-33 5.5t-38.5 7t-32.5 8.5zM362 1367q0 -98 159 -521q18 10 49 10q15 0 75 -5l-121 351q-75 220 -123 220q-19 0 -29 -17.5t-10 -37.5zM283 608q0 -36 51.5 -119 t117.5 -153t100 -70q14 0 25.5 13t11.5 27q0 24 -32 102q-13 32 -32 72t-47.5 89t-61.5 81t-62 32q-20 0 -45.5 -27t-25.5 -47zM125 273q0 -41 25 -104q59 -145 183.5 -227t281.5 -82q227 0 382 170q152 169 152 427q0 43 -1 67t-11.5 62t-30.5 56q-56 49 -211.5 75.5 t-270.5 26.5q-37 0 -49 -11q-12 -5 -12 -35q0 -34 21.5 -60t55.5 -40t77.5 -23.5t87.5 -11.5t85 -4t70 0h23q24 0 40 -19q15 -19 19 -55q-28 -28 -96 -54q-61 -22 -93 -46q-64 -46 -108.5 -114t-44.5 -137q0 -31 18.5 -88.5t18.5 -87.5l-3 -12q-4 -12 -4 -14 q-137 10 -146 216q-8 -2 -41 -2q2 -7 2 -21q0 -53 -40.5 -89.5t-94.5 -36.5q-82 0 -166.5 78t-84.5 159q0 34 33 67q52 -64 60 -76q77 -104 133 -104q12 0 26.5 8.5t14.5 20.5q0 34 -87.5 145t-116.5 111q-43 0 -70 -44.5t-27 -90.5zM11 264q0 101 42.5 163t136.5 88 q-28 74 -28 104q0 62 61 123t122 61q29 0 70 -15q-163 462 -163 567q0 80 41 130.5t119 50.5q131 0 325 -581q6 -17 8 -23q6 16 29 79.5t43.5 118.5t54 127.5t64.5 123t70.5 86.5t76.5 36q71 0 112 -49t41 -122q0 -108 -159 -550q61 -15 100.5 -46t58.5 -78t26 -93.5 t7 -110.5q0 -150 -47 -280t-132 -225t-211 -150t-278 -55q-111 0 -223 42q-149 57 -258 191.5t-109 286.5z" />
+<glyph unicode="&#xf20a;" horiz-adv-x="2048" d="M785 528h207q-14 -158 -98.5 -248.5t-214.5 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-203q-5 64 -35.5 99t-81.5 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t40 -51.5t66 -18q95 0 109 139zM1497 528h206 q-14 -158 -98 -248.5t-214 -90.5q-162 0 -254.5 116t-92.5 316q0 194 93 311.5t233 117.5q148 0 232 -87t97 -247h-204q-4 64 -35 99t-81 35q-57 0 -88.5 -60.5t-31.5 -177.5q0 -48 5 -84t18 -69.5t39.5 -51.5t65.5 -18q49 0 76.5 38t33.5 101zM1856 647q0 207 -15.5 307 t-60.5 161q-6 8 -13.5 14t-21.5 15t-16 11q-86 63 -697 63q-625 0 -710 -63q-5 -4 -17.5 -11.5t-21 -14t-14.5 -14.5q-45 -60 -60 -159.5t-15 -308.5q0 -208 15 -307.5t60 -160.5q6 -8 15 -15t20.5 -14t17.5 -12q44 -33 239.5 -49t470.5 -16q610 0 697 65q5 4 17 11t20.5 14 t13.5 16q46 60 61 159t15 309zM2048 1408v-1536h-2048v1536h2048z" />
+<glyph unicode="&#xf20b;" d="M992 912v-496q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v496q0 112 -80 192t-192 80h-272v-1152q0 -14 -9 -23t-23 -9h-160q-14 0 -23 9t-9 23v1344q0 14 9 23t23 9h464q135 0 249 -66.5t180.5 -180.5t66.5 -249zM1376 1376v-880q0 -135 -66.5 -249t-180.5 -180.5 t-249 -66.5h-464q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h160q14 0 23 -9t9 -23v-768h272q112 0 192 80t80 192v880q0 14 9 23t23 9h160q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf20c;" d="M1311 694v-114q0 -24 -13.5 -38t-37.5 -14h-202q-24 0 -38 14t-14 38v114q0 24 14 38t38 14h202q24 0 37.5 -14t13.5 -38zM821 464v250q0 53 -32.5 85.5t-85.5 32.5h-133q-68 0 -96 -52q-28 52 -96 52h-130q-53 0 -85.5 -32.5t-32.5 -85.5v-250q0 -22 21 -22h55 q22 0 22 22v230q0 24 13.5 38t38.5 14h94q24 0 38 -14t14 -38v-230q0 -22 21 -22h54q22 0 22 22v230q0 24 14 38t38 14h97q24 0 37.5 -14t13.5 -38v-230q0 -22 22 -22h55q21 0 21 22zM1410 560v154q0 53 -33 85.5t-86 32.5h-264q-53 0 -86 -32.5t-33 -85.5v-410 q0 -21 22 -21h55q21 0 21 21v180q31 -42 94 -42h191q53 0 86 32.5t33 85.5zM1536 1176v-1072q0 -96 -68 -164t-164 -68h-1072q-96 0 -164 68t-68 164v1072q0 96 68 164t164 68h1072q96 0 164 -68t68 -164z" />
+<glyph unicode="&#xf20d;" d="M915 450h-294l147 551zM1001 128h311l-324 1024h-440l-324 -1024h311l383 314zM1536 1120v-960q0 -118 -85 -203t-203 -85h-960q-118 0 -203 85t-85 203v960q0 118 85 203t203 85h960q118 0 203 -85t85 -203z" />
+<glyph unicode="&#xf20e;" horiz-adv-x="2048" d="M2048 641q0 -21 -13 -36.5t-33 -19.5l-205 -356q3 -9 3 -18q0 -20 -12.5 -35.5t-32.5 -19.5l-193 -337q3 -8 3 -16q0 -23 -16.5 -40t-40.5 -17q-25 0 -41 18h-400q-17 -20 -43 -20t-43 20h-399q-17 -20 -43 -20q-23 0 -40 16.5t-17 40.5q0 8 4 20l-193 335 q-20 4 -32.5 19.5t-12.5 35.5q0 9 3 18l-206 356q-20 5 -32.5 20.5t-12.5 35.5q0 21 13.5 36.5t33.5 19.5l199 344q0 1 -0.5 3t-0.5 3q0 36 34 51l209 363q-4 10 -4 18q0 24 17 40.5t40 16.5q26 0 44 -21h396q16 21 43 21t43 -21h398q18 21 44 21q23 0 40 -16.5t17 -40.5 q0 -6 -4 -18l207 -358q23 -1 39 -17.5t16 -38.5q0 -13 -7 -27l187 -324q19 -4 31.5 -19.5t12.5 -35.5zM1063 -158h389l-342 354h-143l-342 -354h360q18 16 39 16t39 -16zM112 654q1 -4 1 -13q0 -10 -2 -15l208 -360q2 0 4.5 -1t5.5 -2.5l5 -2.5l188 199v347l-187 194 q-13 -8 -29 -10zM986 1438h-388l190 -200l554 200h-280q-16 -16 -38 -16t-38 16zM1689 226q1 6 5 11l-64 68l-17 -79h76zM1583 226l22 105l-252 266l-296 -307l63 -64h463zM1495 -142l16 28l65 310h-427l333 -343q8 4 13 5zM578 -158h5l342 354h-373v-335l4 -6q14 -5 22 -13 zM552 226h402l64 66l-309 321l-157 -166v-221zM359 226h163v189l-168 -177q4 -8 5 -12zM358 1051q0 -1 0.5 -2t0.5 -2q0 -16 -8 -29l171 -177v269zM552 1121v-311l153 -157l297 314l-223 236zM556 1425l-4 -8v-264l205 74l-191 201q-6 -2 -10 -3zM1447 1438h-16l-621 -224 l213 -225zM1023 946l-297 -315l311 -319l296 307zM688 634l-136 141v-284zM1038 270l-42 -44h85zM1374 618l238 -251l132 624l-3 5l-1 1zM1718 1018q-8 13 -8 29v2l-216 376q-5 1 -13 5l-437 -463l310 -327zM522 1142v223l-163 -282zM522 196h-163l163 -283v283zM1607 196 l-48 -227l130 227h-82zM1729 266l207 361q-2 10 -2 14q0 1 3 16l-171 296l-129 -612l77 -82q5 3 15 7z" />
+<glyph unicode="&#xf210;" d="M0 856q0 131 91.5 226.5t222.5 95.5h742l352 358v-1470q0 -132 -91.5 -227t-222.5 -95h-780q-131 0 -222.5 95t-91.5 227v790zM1232 102l-176 180v425q0 46 -32 79t-78 33h-484q-46 0 -78 -33t-32 -79v-492q0 -46 32.5 -79.5t77.5 -33.5h770z" />
+<glyph unicode="&#xf211;" d="M934 1386q-317 -121 -556 -362.5t-358 -560.5q-20 89 -20 176q0 208 102.5 384.5t278.5 279t384 102.5q82 0 169 -19zM1203 1267q93 -65 164 -155q-389 -113 -674.5 -400.5t-396.5 -676.5q-93 72 -155 162q112 386 395 671t667 399zM470 -67q115 356 379.5 622t619.5 384 q40 -92 54 -195q-292 -120 -516 -345t-343 -518q-103 14 -194 52zM1536 -125q-193 50 -367 115q-135 -84 -290 -107q109 205 274 370.5t369 275.5q-21 -152 -101 -284q65 -175 115 -370z" />
+<glyph unicode="&#xf212;" horiz-adv-x="2048" d="M1893 1144l155 -1272q-131 0 -257 57q-200 91 -393 91q-226 0 -374 -148q-148 148 -374 148q-193 0 -393 -91q-128 -57 -252 -57h-5l155 1272q224 127 482 127q233 0 387 -106q154 106 387 106q258 0 482 -127zM1398 157q129 0 232 -28.5t260 -93.5l-124 1021 q-171 78 -368 78q-224 0 -374 -141q-150 141 -374 141q-197 0 -368 -78l-124 -1021q105 43 165.5 65t148.5 39.5t178 17.5q202 0 374 -108q172 108 374 108zM1438 191l-55 907q-211 -4 -359 -155q-152 155 -374 155q-176 0 -336 -66l-114 -941q124 51 228.5 76t221.5 25 q209 0 374 -102q172 107 374 102z" />
+<glyph unicode="&#xf213;" horiz-adv-x="2048" d="M1500 165v733q0 21 -15 36t-35 15h-93q-20 0 -35 -15t-15 -36v-733q0 -20 15 -35t35 -15h93q20 0 35 15t15 35zM1216 165v531q0 20 -15 35t-35 15h-101q-20 0 -35 -15t-15 -35v-531q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM924 165v429q0 20 -15 35t-35 15h-101 q-20 0 -35 -15t-15 -35v-429q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM632 165v362q0 20 -15 35t-35 15h-101q-20 0 -35 -15t-15 -35v-362q0 -20 15 -35t35 -15h101q20 0 35 15t15 35zM2048 311q0 -166 -118 -284t-284 -118h-1244q-166 0 -284 118t-118 284 q0 116 63 214.5t168 148.5q-10 34 -10 73q0 113 80.5 193.5t193.5 80.5q102 0 180 -67q45 183 194 300t338 117q149 0 275 -73.5t199.5 -199.5t73.5 -275q0 -66 -14 -122q135 -33 221 -142.5t86 -247.5z" />
+<glyph unicode="&#xf214;" d="M0 1536h1536v-1392l-776 -338l-760 338v1392zM1436 209v926h-1336v-926l661 -294zM1436 1235v201h-1336v-201h1336zM181 937v-115h-37v115h37zM181 789v-115h-37v115h37zM181 641v-115h-37v115h37zM181 493v-115h-37v115h37zM181 345v-115h-37v115h37zM207 202l15 34 l105 -47l-15 -33zM343 142l15 34l105 -46l-15 -34zM478 82l15 34l105 -46l-15 -34zM614 23l15 33l104 -46l-15 -34zM797 10l105 46l15 -33l-105 -47zM932 70l105 46l15 -34l-105 -46zM1068 130l105 46l15 -34l-105 -46zM1203 189l105 47l15 -34l-105 -46zM259 1389v-36h-114 v36h114zM421 1389v-36h-115v36h115zM583 1389v-36h-115v36h115zM744 1389v-36h-114v36h114zM906 1389v-36h-114v36h114zM1068 1389v-36h-115v36h115zM1230 1389v-36h-115v36h115zM1391 1389v-36h-114v36h114zM181 1049v-79h-37v115h115v-36h-78zM421 1085v-36h-115v36h115z M583 1085v-36h-115v36h115zM744 1085v-36h-114v36h114zM906 1085v-36h-114v36h114zM1068 1085v-36h-115v36h115zM1230 1085v-36h-115v36h115zM1355 970v79h-78v36h115v-115h-37zM1355 822v115h37v-115h-37zM1355 674v115h37v-115h-37zM1355 526v115h37v-115h-37zM1355 378 v115h37v-115h-37zM1355 230v115h37v-115h-37zM760 265q-129 0 -221 91.5t-92 221.5q0 129 92 221t221 92q130 0 221.5 -92t91.5 -221q0 -130 -91.5 -221.5t-221.5 -91.5zM595 646q0 -36 19.5 -56.5t49.5 -25t64 -7t64 -2t49.5 -9t19.5 -30.5q0 -49 -112 -49q-97 0 -123 51 h-3l-31 -63q67 -42 162 -42q29 0 56.5 5t55.5 16t45.5 33t17.5 53q0 46 -27.5 69.5t-67.5 27t-79.5 3t-67 5t-27.5 25.5q0 21 20.5 33t40.5 15t41 3q34 0 70.5 -11t51.5 -34h3l30 58q-3 1 -21 8.5t-22.5 9t-19.5 7t-22 7t-20 4.5t-24 4t-23 1q-29 0 -56.5 -5t-54 -16.5 t-43 -34t-16.5 -53.5z" />
+<glyph unicode="&#xf215;" horiz-adv-x="2048" d="M863 504q0 112 -79.5 191.5t-191.5 79.5t-191 -79.5t-79 -191.5t79 -191t191 -79t191.5 79t79.5 191zM1726 505q0 112 -79 191t-191 79t-191.5 -79t-79.5 -191q0 -113 79.5 -192t191.5 -79t191 79.5t79 191.5zM2048 1314v-1348q0 -44 -31.5 -75.5t-76.5 -31.5h-1832 q-45 0 -76.5 31.5t-31.5 75.5v1348q0 44 31.5 75.5t76.5 31.5h431q44 0 76 -31.5t32 -75.5v-161h754v161q0 44 32 75.5t76 31.5h431q45 0 76.5 -31.5t31.5 -75.5z" />
+<glyph unicode="&#xf216;" horiz-adv-x="2048" d="M1430 953zM1690 749q148 0 253 -98.5t105 -244.5q0 -157 -109 -261.5t-267 -104.5q-85 0 -162 27.5t-138 73.5t-118 106t-109 126.5t-103.5 132.5t-108.5 126t-117 106t-136 73.5t-159 27.5q-154 0 -251.5 -91.5t-97.5 -244.5q0 -157 104 -250t263 -93q100 0 208 37.5 t193 98.5q5 4 21 18.5t30 24t22 9.5q14 0 24.5 -10.5t10.5 -24.5q0 -24 -60 -77q-101 -88 -234.5 -142t-260.5 -54q-133 0 -245.5 58t-180 165t-67.5 241q0 205 141.5 341t347.5 136q120 0 226.5 -43.5t185.5 -113t151.5 -153t139 -167.5t133.5 -153.5t149.5 -113 t172.5 -43.5q102 0 168.5 61.5t66.5 162.5q0 95 -64.5 159t-159.5 64q-30 0 -81.5 -18.5t-68.5 -18.5q-20 0 -35.5 15t-15.5 35q0 18 8.5 57t8.5 59q0 159 -107.5 263t-266.5 104q-58 0 -111.5 -18.5t-84 -40.5t-55.5 -40.5t-33 -18.5q-15 0 -25.5 10.5t-10.5 25.5 q0 19 25 46q59 67 147 103.5t182 36.5q191 0 318 -125.5t127 -315.5q0 -37 -4 -66q57 15 115 15z" />
+<glyph unicode="&#xf217;" horiz-adv-x="1664" d="M1216 832q0 26 -19 45t-45 19h-128v128q0 26 -19 45t-45 19t-45 -19t-19 -45v-128h-128q-26 0 -45 -19t-19 -45t19 -45t45 -19h128v-128q0 -26 19 -45t45 -19t45 19t19 45v128h128q26 0 45 19t19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf218;" horiz-adv-x="1664" d="M1280 832q0 26 -19 45t-45 19t-45 -19l-147 -146v293q0 26 -19 45t-45 19t-45 -19t-19 -45v-293l-147 146q-19 19 -45 19t-45 -19t-19 -45t19 -45l256 -256q19 -19 45 -19t45 19l256 256q19 19 19 45zM640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5 t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920 q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf219;" horiz-adv-x="2048" d="M212 768l623 -665l-300 665h-323zM1024 -4l349 772h-698zM538 896l204 384h-262l-288 -384h346zM1213 103l623 665h-323zM683 896h682l-204 384h-274zM1510 896h346l-288 384h-262zM1651 1382l384 -512q14 -18 13 -41.5t-17 -40.5l-960 -1024q-18 -20 -47 -20t-47 20 l-960 1024q-16 17 -17 40.5t13 41.5l384 512q18 26 51 26h1152q33 0 51 -26z" />
+<glyph unicode="&#xf21a;" horiz-adv-x="2048" d="M1811 -19q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83q19 19 45 19t45 -19l83 -83l83 83 q19 19 45 19t45 -19l83 -83zM237 19q-19 -19 -45 -19t-45 19l-128 128l90 90l83 -82l83 82q19 19 45 19t45 -19l83 -82l64 64v293l-210 314q-17 26 -7 56.5t40 40.5l177 58v299h128v128h256v128h256v-128h256v-128h128v-299l177 -58q30 -10 40 -40.5t-7 -56.5l-210 -314 v-293l19 18q19 19 45 19t45 -19l83 -82l83 82q19 19 45 19t45 -19l128 -128l-90 -90l-83 83l-83 -83q-18 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83l-83 -83 q-19 -19 -45 -19t-45 19l-83 83l-83 -83q-19 -19 -45 -19t-45 19l-83 83zM640 1152v-128l384 128l384 -128v128h-128v128h-512v-128h-128z" />
+<glyph unicode="&#xf21b;" d="M576 0l96 448l-96 128l-128 64zM832 0l128 640l-128 -64l-96 -128zM992 1010q-2 4 -4 6q-10 8 -96 8q-70 0 -167 -19q-7 -2 -21 -2t-21 2q-97 19 -167 19q-86 0 -96 -8q-2 -2 -4 -6q2 -18 4 -27q2 -3 7.5 -6.5t7.5 -10.5q2 -4 7.5 -20.5t7 -20.5t7.5 -17t8.5 -17t9 -14 t12 -13.5t14 -9.5t17.5 -8t20.5 -4t24.5 -2q36 0 59 12.5t32.5 30t14.5 34.5t11.5 29.5t17.5 12.5h12q11 0 17.5 -12.5t11.5 -29.5t14.5 -34.5t32.5 -30t59 -12.5q13 0 24.5 2t20.5 4t17.5 8t14 9.5t12 13.5t9 14t8.5 17t7.5 17t7 20.5t7.5 20.5q2 7 7.5 10.5t7.5 6.5 q2 9 4 27zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 61 4.5 118t19 125.5t37.5 123.5t63.5 103.5t93.5 74.5l-90 220h214q-22 64 -22 128q0 12 2 32q-194 40 -194 96q0 57 210 99q17 62 51.5 134t70.5 114q32 37 76 37q30 0 84 -31t84 -31t84 31 t84 31q44 0 76 -37q36 -42 70.5 -114t51.5 -134q210 -42 210 -99q0 -56 -194 -96q7 -81 -20 -160h214l-82 -225q63 -33 107.5 -96.5t65.5 -143.5t29 -151.5t8 -148.5z" />
+<glyph unicode="&#xf21c;" horiz-adv-x="2304" d="M2301 500q12 -103 -22 -198.5t-99 -163.5t-158.5 -106t-196.5 -31q-161 11 -279.5 125t-134.5 274q-12 111 27.5 210.5t118.5 170.5l-71 107q-96 -80 -151 -194t-55 -244q0 -27 -18.5 -46.5t-45.5 -19.5h-256h-69q-23 -164 -149 -274t-294 -110q-185 0 -316.5 131.5 t-131.5 316.5t131.5 316.5t316.5 131.5q76 0 152 -27l24 45q-123 110 -304 110h-64q-26 0 -45 19t-19 45t19 45t45 19h128q78 0 145 -13.5t116.5 -38.5t71.5 -39.5t51 -36.5h512h115l-85 128h-222q-30 0 -49 22.5t-14 52.5q4 23 23 38t43 15h253q33 0 53 -28l70 -105 l114 114q19 19 46 19h101q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-179l115 -172q131 63 275 36q143 -26 244 -134.5t118 -253.5zM448 128q115 0 203 72.5t111 183.5h-314q-35 0 -55 31q-18 32 -1 63l147 277q-47 13 -91 13q-132 0 -226 -94t-94 -226t94 -226 t226 -94zM1856 128q132 0 226 94t94 226t-94 226t-226 94q-60 0 -121 -24l174 -260q15 -23 10 -49t-27 -40q-15 -11 -36 -11q-35 0 -53 29l-174 260q-93 -95 -93 -225q0 -132 94 -226t226 -94z" />
+<glyph unicode="&#xf21d;" d="M1408 0q0 -63 -61.5 -113.5t-164 -81t-225 -46t-253.5 -15.5t-253.5 15.5t-225 46t-164 81t-61.5 113.5q0 49 33 88.5t91 66.5t118 44.5t131 29.5q26 5 48 -10.5t26 -41.5q5 -26 -10.5 -48t-41.5 -26q-58 -10 -106 -23.5t-76.5 -25.5t-48.5 -23.5t-27.5 -19.5t-8.5 -12 q3 -11 27 -26.5t73 -33t114 -32.5t160.5 -25t201.5 -10t201.5 10t160.5 25t114 33t73 33.5t27 27.5q-1 4 -8.5 11t-27.5 19t-48.5 23.5t-76.5 25t-106 23.5q-26 4 -41.5 26t-10.5 48q4 26 26 41.5t48 10.5q71 -12 131 -29.5t118 -44.5t91 -66.5t33 -88.5zM1024 896v-384 q0 -26 -19 -45t-45 -19h-64v-384q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v384h-64q-26 0 -45 19t-19 45v384q0 53 37.5 90.5t90.5 37.5h384q53 0 90.5 -37.5t37.5 -90.5zM928 1280q0 -93 -65.5 -158.5t-158.5 -65.5t-158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5 t158.5 -65.5t65.5 -158.5z" />
+<glyph unicode="&#xf21e;" horiz-adv-x="1792" d="M1280 512h305q-5 -6 -10 -10.5t-9 -7.5l-3 -4l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-5 2 -21 20h369q22 0 39.5 13.5t22.5 34.5l70 281l190 -667q6 -20 23 -33t39 -13q21 0 38 13t23 33l146 485l56 -112q18 -35 57 -35zM1792 940q0 -145 -103 -300h-369l-111 221 q-8 17 -25.5 27t-36.5 8q-45 -5 -56 -46l-129 -430l-196 686q-6 20 -23.5 33t-39.5 13t-39 -13.5t-22 -34.5l-116 -464h-423q-103 155 -103 300q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124 t127 -344z" />
+<glyph unicode="&#xf221;" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292 q11 134 80.5 249t182 188t245.5 88q170 19 319 -54t236 -212t87 -306zM128 960q0 -185 131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5z" />
+<glyph unicode="&#xf222;" d="M1472 1408q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-382 -383q126 -156 126 -359q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123t223.5 45.5 q203 0 359 -126l382 382h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM576 0q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf223;" horiz-adv-x="1280" d="M830 1220q145 -72 233.5 -210.5t88.5 -305.5q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5 t-147.5 384.5q0 167 88.5 305.5t233.5 210.5q-165 96 -228 273q-6 16 3.5 29.5t26.5 13.5h69q21 0 29 -20q44 -106 140 -171t214 -65t214 65t140 171q8 20 37 20h61q17 0 26.5 -13.5t3.5 -29.5q-63 -177 -228 -273zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf224;" d="M1024 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-149 16 -270.5 103t-186.5 223.5t-53 291.5q16 204 160 353.5t347 172.5q118 14 228 -19t198 -103l255 254h-134q-14 0 -23 9t-9 23v64zM576 256q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf225;" horiz-adv-x="1792" d="M1280 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q126 -158 126 -359q0 -221 -147.5 -384.5t-364.5 -187.5v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64 q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-217 24 -364.5 187.5t-147.5 384.5q0 201 126 359l-52 53l-101 -111q-9 -10 -22 -10.5t-23 7.5l-48 44q-10 8 -10.5 21.5t8.5 23.5l105 115l-111 112v-134q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9 t-9 23v288q0 26 19 45t45 19h288q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-133l106 -107l86 94q9 10 22 10.5t23 -7.5l48 -44q10 -8 10.5 -21.5t-8.5 -23.5l-90 -99l57 -56q158 126 359 126t359 -126l255 254h-134q-14 0 -23 9t-9 23v64zM832 256q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf226;" horiz-adv-x="1792" d="M1790 1007q12 -155 -52.5 -292t-186 -224t-271.5 -103v-260h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-512v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23 t23 9h224v260q-150 16 -271.5 103t-186 224t-52.5 292q17 206 164.5 356.5t352.5 169.5q206 21 377 -94q171 115 377 94q205 -19 352.5 -169.5t164.5 -356.5zM896 647q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM576 512q115 0 218 57q-154 165 -154 391 q0 224 154 391q-103 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5zM1152 128v260q-137 15 -256 94q-119 -79 -256 -94v-260h512zM1216 512q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5q-115 0 -218 -57q154 -167 154 -391 q0 -226 -154 -391q103 -57 218 -57z" />
+<glyph unicode="&#xf227;" horiz-adv-x="1920" d="M1536 1120q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-31 -182 -166 -312t-318 -156q-210 -29 -384.5 80t-241.5 300q-117 6 -221 57.5t-177.5 133t-113.5 192.5t-32 230 q9 135 78 252t182 191.5t248 89.5q118 14 227.5 -19t198.5 -103l255 254h-134q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q59 -74 93 -169q182 -9 328 -124l255 254h-134q-14 0 -23 9 t-9 23v64zM1024 704q0 20 -4 58q-162 -25 -271 -150t-109 -292q0 -20 4 -58q162 25 271 150t109 292zM128 704q0 -168 111 -294t276 -149q-3 29 -3 59q0 210 135 369.5t338 196.5q-53 120 -163.5 193t-245.5 73q-185 0 -316.5 -131.5t-131.5 -316.5zM1088 -128 q185 0 316.5 131.5t131.5 316.5q0 168 -111 294t-276 149q3 -29 3 -59q0 -210 -135 -369.5t-338 -196.5q53 -120 163.5 -193t245.5 -73z" />
+<glyph unicode="&#xf228;" horiz-adv-x="2048" d="M1664 1504q0 14 9 23t23 9h288q26 0 45 -19t19 -45v-288q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v134l-254 -255q76 -95 107.5 -214t9.5 -247q-32 -180 -164.5 -310t-313.5 -157q-223 -34 -409 90q-117 -78 -256 -93v-132h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23 t-23 -9h-96v-96q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v96h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96v132q-155 17 -279.5 109.5t-187 237.5t-39.5 307q25 187 159.5 322.5t320.5 164.5q224 34 410 -90q146 97 320 97q201 0 359 -126l255 254h-134q-14 0 -23 9 t-9 23v64zM896 391q128 131 128 313t-128 313q-128 -131 -128 -313t128 -313zM128 704q0 -185 131.5 -316.5t316.5 -131.5q117 0 218 57q-154 167 -154 391t154 391q-101 57 -218 57q-185 0 -316.5 -131.5t-131.5 -316.5zM1216 256q185 0 316.5 131.5t131.5 316.5 t-131.5 316.5t-316.5 131.5q-117 0 -218 -57q154 -167 154 -391t-154 -391q101 -57 218 -57z" />
+<glyph unicode="&#xf229;" d="M1472 1408q26 0 45 -19t19 -45v-416q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v262l-213 -214l140 -140q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-140 141l-78 -79q126 -156 126 -359q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5 t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123t223.5 45.5q203 0 359 -126l78 78l-172 172q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l172 -172l213 213h-261q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h416zM576 0q185 0 316.5 131.5t131.5 316.5t-131.5 316.5 t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf22a;" horiz-adv-x="1280" d="M640 892q217 -24 364.5 -187.5t147.5 -384.5q0 -167 -87 -306t-236 -212t-319 -54q-133 15 -245.5 88t-182 188t-80.5 249q-12 155 52.5 292t186 224t271.5 103v132h-160q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h160v165l-92 -92q-10 -9 -23 -9t-22 9l-46 46q-9 9 -9 22 t9 23l202 201q19 19 45 19t45 -19l202 -201q9 -10 9 -23t-9 -22l-46 -46q-9 -9 -22 -9t-23 9l-92 92v-165h160q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-160v-132zM576 -128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5 t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf22b;" horiz-adv-x="2048" d="M1901 621q19 -19 19 -45t-19 -45l-294 -294q-9 -10 -22.5 -10t-22.5 10l-45 45q-10 9 -10 22.5t10 22.5l185 185h-294v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-132q-24 -217 -187.5 -364.5t-384.5 -147.5q-167 0 -306 87t-212 236t-54 319q15 133 88 245.5 t188 182t249 80.5q155 12 292 -52.5t224 -186t103 -271.5h132v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224h294l-185 185q-10 9 -10 22.5t10 22.5l45 45q9 10 22.5 10t22.5 -10zM576 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5 t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf22c;" horiz-adv-x="1280" d="M1152 960q0 -221 -147.5 -384.5t-364.5 -187.5v-612q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v612q-217 24 -364.5 187.5t-147.5 384.5q0 117 45.5 223.5t123 184t184 123t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5zM576 512q185 0 316.5 131.5 t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf22d;" horiz-adv-x="1280" d="M1024 576q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1152 576q0 -117 -45.5 -223.5t-123 -184t-184 -123t-223.5 -45.5t-223.5 45.5t-184 123t-123 184t-45.5 223.5t45.5 223.5t123 184t184 123 t223.5 45.5t223.5 -45.5t184 -123t123 -184t45.5 -223.5z" />
+<glyph unicode="&#xf22e;" horiz-adv-x="1792" />
+<glyph unicode="&#xf22f;" horiz-adv-x="1792" />
+<glyph unicode="&#xf230;" d="M1451 1408q35 0 60 -25t25 -60v-1366q0 -35 -25 -60t-60 -25h-391v595h199l30 232h-229v148q0 56 23.5 84t91.5 28l122 1v207q-63 9 -178 9q-136 0 -217.5 -80t-81.5 -226v-171h-200v-232h200v-595h-735q-35 0 -60 25t-25 60v1366q0 35 25 60t60 25h1366z" />
+<glyph unicode="&#xf231;" horiz-adv-x="1280" d="M0 939q0 108 37.5 203.5t103.5 166.5t152 123t185 78t202 26q158 0 294 -66.5t221 -193.5t85 -287q0 -96 -19 -188t-60 -177t-100 -149.5t-145 -103t-189 -38.5q-68 0 -135 32t-96 88q-10 -39 -28 -112.5t-23.5 -95t-20.5 -71t-26 -71t-32 -62.5t-46 -77.5t-62 -86.5 l-14 -5l-9 10q-15 157 -15 188q0 92 21.5 206.5t66.5 287.5t52 203q-32 65 -32 169q0 83 52 156t132 73q61 0 95 -40.5t34 -102.5q0 -66 -44 -191t-44 -187q0 -63 45 -104.5t109 -41.5q55 0 102 25t78.5 68t56 95t38 110.5t20 111t6.5 99.5q0 173 -109.5 269.5t-285.5 96.5 q-200 0 -334 -129.5t-134 -328.5q0 -44 12.5 -85t27 -65t27 -45.5t12.5 -30.5q0 -28 -15 -73t-37 -45q-2 0 -17 3q-51 15 -90.5 56t-61 94.5t-32.5 108t-11 106.5z" />
+<glyph unicode="&#xf232;" d="M985 562q13 0 97.5 -44t89.5 -53q2 -5 2 -15q0 -33 -17 -76q-16 -39 -71 -65.5t-102 -26.5q-57 0 -190 62q-98 45 -170 118t-148 185q-72 107 -71 194v8q3 91 74 158q24 22 52 22q6 0 18 -1.5t19 -1.5q19 0 26.5 -6.5t15.5 -27.5q8 -20 33 -88t25 -75q0 -21 -34.5 -57.5 t-34.5 -46.5q0 -7 5 -15q34 -73 102 -137q56 -53 151 -101q12 -7 22 -7q15 0 54 48.5t52 48.5zM782 32q127 0 243.5 50t200.5 134t134 200.5t50 243.5t-50 243.5t-134 200.5t-200.5 134t-243.5 50t-243.5 -50t-200.5 -134t-134 -200.5t-50 -243.5q0 -203 120 -368l-79 -233 l242 77q158 -104 345 -104zM782 1414q153 0 292.5 -60t240.5 -161t161 -240.5t60 -292.5t-60 -292.5t-161 -240.5t-240.5 -161t-292.5 -60q-195 0 -365 94l-417 -134l136 405q-108 178 -108 389q0 153 60 292.5t161 240.5t240.5 161t292.5 60z" />
+<glyph unicode="&#xf233;" horiz-adv-x="1792" d="M128 128h1024v128h-1024v-128zM128 640h1024v128h-1024v-128zM1696 192q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM128 1152h1024v128h-1024v-128zM1696 704q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1696 1216 q0 40 -28 68t-68 28t-68 -28t-28 -68t28 -68t68 -28t68 28t28 68zM1792 384v-384h-1792v384h1792zM1792 896v-384h-1792v384h1792zM1792 1408v-384h-1792v384h1792z" />
+<glyph unicode="&#xf234;" horiz-adv-x="2048" d="M704 640q-159 0 -271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5zM1664 512h352q13 0 22.5 -9.5t9.5 -22.5v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-352v-352q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5 t-9.5 22.5v352h-352q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h352v352q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-352zM928 288q0 -52 38 -90t90 -38h256v-238q-68 -50 -171 -50h-874q-121 0 -194 69t-73 190q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q19 0 39 -17q79 -61 154.5 -91.5t164.5 -30.5t164.5 30.5t154.5 91.5q20 17 39 17q132 0 217 -96h-223q-52 0 -90 -38t-38 -90v-192z" />
+<glyph unicode="&#xf235;" horiz-adv-x="2048" d="M704 640q-159 0 -271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5zM1781 320l249 -249q9 -9 9 -23q0 -13 -9 -22l-136 -136q-9 -9 -22 -9q-14 0 -23 9l-249 249l-249 -249q-9 -9 -23 -9q-13 0 -22 9l-136 136 q-9 9 -9 22q0 14 9 23l249 249l-249 249q-9 9 -9 23q0 13 9 22l136 136q9 9 22 9q14 0 23 -9l249 -249l249 249q9 9 23 9q13 0 22 -9l136 -136q9 -9 9 -22q0 -14 -9 -23zM1283 320l-181 -181q-37 -37 -37 -91q0 -53 37 -90l83 -83q-21 -3 -44 -3h-874q-121 0 -194 69 t-73 190q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q19 0 39 -17q154 -122 319 -122t319 122q20 17 39 17q28 0 57 -6q-28 -27 -41 -50t-13 -56q0 -54 37 -91z" />
+<glyph unicode="&#xf236;" horiz-adv-x="2048" d="M256 512h1728q26 0 45 -19t19 -45v-448h-256v256h-1536v-256h-256v1216q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-704zM832 832q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM2048 576v64q0 159 -112.5 271.5t-271.5 112.5h-704 q-26 0 -45 -19t-19 -45v-384h1152z" />
+<glyph unicode="&#xf237;" d="M1536 1536l-192 -448h192v-192h-274l-55 -128h329v-192h-411l-357 -832l-357 832h-411v192h329l-55 128h-274v192h192l-192 448h256l323 -768h378l323 768h256zM768 320l108 256h-216z" />
+<glyph unicode="&#xf238;" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM768 192q80 0 136 56t56 136t-56 136t-136 56 t-136 -56t-56 -136t56 -136t136 -56zM1344 768v512h-1152v-512h1152z" />
+<glyph unicode="&#xf239;" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM288 224q66 0 113 47t47 113t-47 113t-113 47 t-113 -47t-47 -113t47 -113t113 -47zM704 768v512h-544v-512h544zM1248 224q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM1408 768v512h-576v-512h576z" />
+<glyph unicode="&#xf23a;" horiz-adv-x="1792" d="M1792 204v-209h-642v209h134v926h-6l-314 -1135h-243l-310 1135h-8v-926h135v-209h-538v209h69q21 0 43 19.5t22 37.5v881q0 18 -22 40t-43 22h-69v209h672l221 -821h6l223 821h670v-209h-71q-19 0 -41 -22t-22 -40v-881q0 -18 21.5 -37.5t41.5 -19.5h71z" />
+<glyph unicode="&#xf23b;" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 1408v-1536h-1536v1536h1536z" />
+<glyph unicode="&#xf23c;" horiz-adv-x="2296" d="M478 -139q-8 -16 -27 -34.5t-37 -25.5q-25 -9 -51.5 3.5t-28.5 31.5q-1 22 40 55t68 38q23 4 34 -21.5t2 -46.5zM1819 -139q7 -16 26 -34.5t38 -25.5q25 -9 51.5 3.5t27.5 31.5q2 22 -39.5 55t-68.5 38q-22 4 -33 -21.5t-2 -46.5zM1867 -30q13 -27 56.5 -59.5t77.5 -41.5 q45 -13 82 4.5t37 50.5q0 46 -67.5 100.5t-115.5 59.5q-40 5 -63.5 -37.5t-6.5 -76.5zM428 -30q-13 -27 -56 -59.5t-77 -41.5q-45 -13 -82 4.5t-37 50.5q0 46 67.5 100.5t115.5 59.5q40 5 63 -37.5t6 -76.5zM1158 1094h1q-41 0 -76 -15q27 -8 44 -30.5t17 -49.5 q0 -35 -27 -60t-65 -25q-52 0 -80 43q-5 -23 -5 -42q0 -74 56 -126.5t135 -52.5q80 0 136 52.5t56 126.5t-56 126.5t-136 52.5zM1462 1312q-99 109 -220.5 131.5t-245.5 -44.5q27 60 82.5 96.5t118 39.5t121.5 -17t99.5 -74.5t44.5 -131.5zM2212 73q8 -11 -11 -42 q7 -23 7 -40q1 -56 -44.5 -112.5t-109.5 -91.5t-118 -37q-48 -2 -92 21.5t-66 65.5q-687 -25 -1259 0q-23 -41 -66.5 -65t-92.5 -22q-86 3 -179.5 80.5t-92.5 160.5q2 22 7 40q-19 31 -11 42q6 10 31 1q14 22 41 51q-7 29 2 38q11 10 39 -4q29 20 59 34q0 29 13 37 q23 12 51 -16q35 5 61 -2q18 -4 38 -19v73q-11 0 -18 2q-53 10 -97 44.5t-55 87.5q-9 38 0 81q15 62 93 95q2 17 19 35.5t36 23.5t33 -7.5t19 -30.5h13q46 -5 60 -23q3 -3 5 -7q10 1 30.5 3.5t30.5 3.5q-15 11 -30 17q-23 40 -91 43q0 6 1 10q-62 2 -118.5 18.5t-84.5 47.5 q-32 36 -42.5 92t-2.5 112q16 126 90 179q23 16 52 4.5t32 -40.5q0 -1 1.5 -14t2.5 -21t3 -20t5.5 -19t8.5 -10q27 -14 76 -12q48 46 98 74q-40 4 -162 -14l47 46q61 58 163 111q145 73 282 86q-20 8 -41 15.5t-47 14t-42.5 10.5t-47.5 11t-43 10q595 126 904 -139 q98 -84 158 -222q85 -10 121 9h1q5 3 8.5 10t5.5 19t3 19.5t3 21.5l1 14q3 28 32 40t52 -5q73 -52 91 -178q7 -57 -3.5 -113t-42.5 -91q-28 -32 -83.5 -48.5t-115.5 -18.5v-10q-71 -2 -95 -43q-14 -5 -31 -17q11 -1 32 -3.5t30 -3.5q1 4 5 8q16 18 60 23h13q5 18 19 30t33 8 t36 -23t19 -36q79 -32 93 -95q9 -40 1 -81q-12 -53 -56 -88t-97 -44q-10 -2 -17 -2q0 -49 -1 -73q20 15 38 19q26 7 61 2q28 28 51 16q14 -9 14 -37q33 -16 59 -34q27 13 38 4q10 -10 2 -38q28 -30 41 -51q23 8 31 -1zM1937 1025q0 -29 -9 -54q82 -32 112 -132 q4 37 -9.5 98.5t-41.5 90.5q-20 19 -36 17t-16 -20zM1859 925q35 -42 47.5 -108.5t-0.5 -124.5q67 13 97 45q13 14 18 28q-3 64 -31 114.5t-79 66.5q-15 -15 -52 -21zM1822 921q-30 0 -44 1q42 -115 53 -239q21 0 43 3q16 68 1 135t-53 100zM258 839q30 100 112 132 q-9 25 -9 54q0 18 -16.5 20t-35.5 -17q-28 -29 -41.5 -90.5t-9.5 -98.5zM294 737q29 -31 97 -45q-13 58 -0.5 124.5t47.5 108.5v0q-37 6 -52 21q-51 -16 -78.5 -66t-31.5 -115q9 -17 18 -28zM471 683q14 124 73 235q-19 -4 -55 -18l-45 -19v1q-46 -89 -20 -196q25 -3 47 -3z M1434 644q8 -38 16.5 -108.5t11.5 -89.5q3 -18 9.5 -21.5t23.5 4.5q40 20 62 85.5t23 125.5q-24 2 -146 4zM1152 1285q-116 0 -199 -82.5t-83 -198.5q0 -117 83 -199.5t199 -82.5t199 82.5t83 199.5q0 116 -83 198.5t-199 82.5zM1380 646q-106 2 -211 0v1q-1 -27 2.5 -86 t13.5 -66q29 -14 93.5 -14.5t95.5 10.5q9 3 11 39t-0.5 69.5t-4.5 46.5zM1112 447q8 4 9.5 48t-0.5 88t-4 63v1q-212 -3 -214 -3q-4 -20 -7 -62t0 -83t14 -46q34 -15 101 -16t101 10zM718 636q-16 -59 4.5 -118.5t77.5 -84.5q15 -8 24 -5t12 21q3 16 8 90t10 103 q-69 -2 -136 -6zM591 510q3 -23 -34 -36q132 -141 271.5 -240t305.5 -154q172 49 310.5 146t293.5 250q-33 13 -30 34l3 9v1v-1q-17 2 -50 5.5t-48 4.5q-26 -90 -82 -132q-51 -38 -82 1q-5 6 -9 14q-7 13 -17 62q-2 -5 -5 -9t-7.5 -7t-8 -5.5t-9.5 -4l-10 -2.5t-12 -2 l-12 -1.5t-13.5 -1t-13.5 -0.5q-106 -9 -163 11q-4 -17 -10 -26.5t-21 -15t-23 -7t-36 -3.5q-2 0 -3 -0.5t-3 -0.5h-3q-179 -17 -203 40q-2 -63 -56 -54q-47 8 -91 54q-12 13 -20 26q-17 29 -26 65q-58 -6 -87 -10q1 -2 4 -10zM507 -118q3 14 3 30q-17 71 -51 130t-73 70 q-41 12 -101.5 -14.5t-104.5 -80t-39 -107.5q35 -53 100 -93t119 -42q51 -2 94 28t53 79zM510 53q23 -63 27 -119q195 113 392 174q-98 52 -180.5 120t-179.5 165q-6 -4 -29 -13q0 -2 -1 -5t-1 -4q31 -18 22 -37q-12 -23 -56 -34q-10 -13 -29 -24h-1q-2 -83 1 -150 q19 -34 35 -73zM579 -113q532 -21 1145 0q-254 147 -428 196q-76 -35 -156 -57q-8 -3 -16 0q-65 21 -129 49q-208 -60 -416 -188h-1v-1q1 0 1 1zM1763 -67q4 54 28 120q14 38 33 71l-1 -1q3 77 3 153q-15 8 -30 25q-42 9 -56 33q-9 20 22 38q-2 4 -2 9q-16 4 -28 12 q-204 -190 -383 -284q198 -59 414 -176zM2155 -90q5 54 -39 107.5t-104 80t-102 14.5q-38 -11 -72.5 -70.5t-51.5 -129.5q0 -16 3 -30q10 -49 53 -79t94 -28q54 2 119 42t100 93z" />
+<glyph unicode="&#xf23d;" horiz-adv-x="2304" d="M1524 -25q0 -68 -48 -116t-116 -48t-116.5 48t-48.5 116t48.5 116.5t116.5 48.5t116 -48.5t48 -116.5zM775 -25q0 -68 -48.5 -116t-116.5 -48t-116 48t-48 116t48 116.5t116 48.5t116.5 -48.5t48.5 -116.5zM0 1469q57 -60 110.5 -104.5t121 -82t136 -63t166 -45.5 t200 -31.5t250 -18.5t304 -9.5t372.5 -2.5q139 0 244.5 -5t181 -16.5t124 -27.5t71 -39.5t24 -51.5t-19.5 -64t-56.5 -76.5t-89.5 -91t-116 -104.5t-139 -119q-185 -157 -286 -247q29 51 76.5 109t94 105.5t94.5 98.5t83 91.5t54 80.5t13 70t-45.5 55.5t-116.5 41t-204 23.5 t-304 5q-168 -2 -314 6t-256 23t-204.5 41t-159.5 51.5t-122.5 62.5t-91.5 66.5t-68 71.5t-50.5 69.5t-40 68t-36.5 59.5z" />
+<glyph unicode="&#xf23e;" horiz-adv-x="1792" d="M896 1472q-169 0 -323 -66t-265.5 -177.5t-177.5 -265.5t-66 -323t66 -323t177.5 -265.5t265.5 -177.5t323 -66t323 66t265.5 177.5t177.5 265.5t66 323t-66 323t-177.5 265.5t-265.5 177.5t-323 66zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348 t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM496 704q16 0 16 -16v-480q0 -16 -16 -16h-32q-16 0 -16 16v480q0 16 16 16h32zM896 640q53 0 90.5 -37.5t37.5 -90.5q0 -35 -17.5 -64t-46.5 -46v-114q0 -14 -9 -23 t-23 -9h-64q-14 0 -23 9t-9 23v114q-29 17 -46.5 46t-17.5 64q0 53 37.5 90.5t90.5 37.5zM896 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM544 928v-96 q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v96q0 93 65.5 158.5t158.5 65.5t158.5 -65.5t65.5 -158.5v-96q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v96q0 146 -103 249t-249 103t-249 -103t-103 -249zM1408 192v512q0 26 -19 45t-45 19h-896q-26 0 -45 -19t-19 -45v-512 q0 -26 19 -45t45 -19h896q26 0 45 19t19 45z" />
+<glyph unicode="&#xf240;" horiz-adv-x="2304" d="M1920 1024v-768h-1664v768h1664zM2048 448h128v384h-128v288q0 14 -9 23t-23 9h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288zM2304 832v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113 v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf241;" horiz-adv-x="2304" d="M256 256v768h1280v-768h-1280zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
+<glyph unicode="&#xf242;" horiz-adv-x="2304" d="M256 256v768h896v-768h-896zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
+<glyph unicode="&#xf243;" horiz-adv-x="2304" d="M256 256v768h512v-768h-512zM2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9 h-1856q-14 0 -23 -9t-9 -23v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
+<glyph unicode="&#xf244;" horiz-adv-x="2304" d="M2176 960q53 0 90.5 -37.5t37.5 -90.5v-384q0 -53 -37.5 -90.5t-90.5 -37.5v-160q0 -66 -47 -113t-113 -47h-1856q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1856q66 0 113 -47t47 -113v-160zM2176 448v384h-128v288q0 14 -9 23t-23 9h-1856q-14 0 -23 -9t-9 -23 v-960q0 -14 9 -23t23 -9h1856q14 0 23 9t9 23v288h128z" />
+<glyph unicode="&#xf245;" horiz-adv-x="1280" d="M1133 493q31 -30 14 -69q-17 -40 -59 -40h-382l201 -476q10 -25 0 -49t-34 -35l-177 -75q-25 -10 -49 0t-35 34l-191 452l-312 -312q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v1504q0 42 40 59q12 5 24 5q27 0 45 -19z" />
+<glyph unicode="&#xf246;" horiz-adv-x="1024" d="M832 1408q-320 0 -320 -224v-416h128v-128h-128v-544q0 -224 320 -224h64v-128h-64q-272 0 -384 146q-112 -146 -384 -146h-64v128h64q320 0 320 224v544h-128v128h128v416q0 224 -320 224h-64v128h64q272 0 384 -146q112 146 384 146h64v-128h-64z" />
+<glyph unicode="&#xf247;" horiz-adv-x="2048" d="M2048 1152h-128v-1024h128v-384h-384v128h-1280v-128h-384v384h128v1024h-128v384h384v-128h1280v128h384v-384zM1792 1408v-128h128v128h-128zM128 1408v-128h128v128h-128zM256 -128v128h-128v-128h128zM1664 0v128h128v1024h-128v128h-1280v-128h-128v-1024h128v-128 h1280zM1920 -128v128h-128v-128h128zM1280 896h384v-768h-896v256h-384v768h896v-256zM512 512h640v512h-640v-512zM1536 256v512h-256v-384h-384v-128h640z" />
+<glyph unicode="&#xf248;" horiz-adv-x="2304" d="M2304 768h-128v-640h128v-384h-384v128h-896v-128h-384v384h128v128h-384v-128h-384v384h128v640h-128v384h384v-128h896v128h384v-384h-128v-128h384v128h384v-384zM2048 1024v-128h128v128h-128zM1408 1408v-128h128v128h-128zM128 1408v-128h128v128h-128zM256 256 v128h-128v-128h128zM1536 384h-128v-128h128v128zM384 384h896v128h128v640h-128v128h-896v-128h-128v-640h128v-128zM896 -128v128h-128v-128h128zM2176 -128v128h-128v-128h128zM2048 128v640h-128v128h-384v-384h128v-384h-384v128h-384v-128h128v-128h896v128h128z" />
+<glyph unicode="&#xf249;" d="M1024 288v-416h-928q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1344q40 0 68 -28t28 -68v-928h-416q-40 0 -68 -28t-28 -68zM1152 256h381q-15 -82 -65 -132l-184 -184q-50 -50 -132 -65v381z" />
+<glyph unicode="&#xf24a;" d="M1400 256h-248v-248q29 10 41 22l185 185q12 12 22 41zM1120 384h288v896h-1280v-1280h896v288q0 40 28 68t68 28zM1536 1312v-1024q0 -40 -20 -88t-48 -76l-184 -184q-28 -28 -76 -48t-88 -20h-1024q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1344q40 0 68 -28t28 -68 z" />
+<glyph unicode="&#xf24b;" horiz-adv-x="2304" d="M1951 538q0 -26 -15.5 -44.5t-38.5 -23.5q-8 -2 -18 -2h-153v140h153q10 0 18 -2q23 -5 38.5 -23.5t15.5 -44.5zM1933 751q0 -25 -15 -42t-38 -21q-3 -1 -15 -1h-139v129h139q3 0 8.5 -0.5t6.5 -0.5q23 -4 38 -21.5t15 -42.5zM728 587v308h-228v-308q0 -58 -38 -94.5 t-105 -36.5q-108 0 -229 59v-112q53 -15 121 -23t109 -9l42 -1q328 0 328 217zM1442 403v113q-99 -52 -200 -59q-108 -8 -169 41t-61 142t61 142t169 41q101 -7 200 -58v112q-48 12 -100 19.5t-80 9.5l-28 2q-127 6 -218.5 -14t-140.5 -60t-71 -88t-22 -106t22 -106t71 -88 t140.5 -60t218.5 -14q101 4 208 31zM2176 518q0 54 -43 88.5t-109 39.5v3q57 8 89 41.5t32 79.5q0 55 -41 88t-107 36q-3 0 -12 0.5t-14 0.5h-455v-510h491q74 0 121.5 36.5t47.5 96.5zM2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90 t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf24c;" horiz-adv-x="2304" d="M858 295v693q-106 -41 -172 -135.5t-66 -211.5t66 -211.5t172 -134.5zM1362 641q0 117 -66 211.5t-172 135.5v-694q106 41 172 135.5t66 211.5zM1577 641q0 -159 -78.5 -294t-213.5 -213.5t-294 -78.5q-119 0 -227.5 46.5t-187 125t-125 187t-46.5 227.5q0 159 78.5 294 t213.5 213.5t294 78.5t294 -78.5t213.5 -213.5t78.5 -294zM1960 634q0 139 -55.5 261.5t-147.5 205.5t-213.5 131t-252.5 48h-301q-176 0 -323.5 -81t-235 -230t-87.5 -335q0 -171 87 -317.5t236 -231.5t323 -85h301q129 0 251.5 50.5t214.5 135t147.5 202.5t55.5 246z M2304 1280v-1280q0 -52 -38 -90t-90 -38h-2048q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h2048q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf24d;" horiz-adv-x="1792" d="M1664 -96v1088q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5v-1088q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5zM1792 992v-1088q0 -66 -47 -113t-113 -47h-1088q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1088q66 0 113 -47t47 -113 zM1408 1376v-160h-128v160q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5v-1088q0 -13 9.5 -22.5t22.5 -9.5h160v-128h-160q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1088q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf24e;" horiz-adv-x="2304" d="M1728 1088l-384 -704h768zM448 1088l-384 -704h768zM1269 1280q-14 -40 -45.5 -71.5t-71.5 -45.5v-1291h608q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1344q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h608v1291q-40 14 -71.5 45.5t-45.5 71.5h-491q-14 0 -23 9t-9 23v64 q0 14 9 23t23 9h491q21 57 70 92.5t111 35.5t111 -35.5t70 -92.5h491q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-491zM1088 1264q33 0 56.5 23.5t23.5 56.5t-23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5zM2176 384q0 -73 -46.5 -131t-117.5 -91 t-144.5 -49.5t-139.5 -16.5t-139.5 16.5t-144.5 49.5t-117.5 91t-46.5 131q0 11 35 81t92 174.5t107 195.5t102 184t56 100q18 33 56 33t56 -33q4 -7 56 -100t102 -184t107 -195.5t92 -174.5t35 -81zM896 384q0 -73 -46.5 -131t-117.5 -91t-144.5 -49.5t-139.5 -16.5 t-139.5 16.5t-144.5 49.5t-117.5 91t-46.5 131q0 11 35 81t92 174.5t107 195.5t102 184t56 100q18 33 56 33t56 -33q4 -7 56 -100t102 -184t107 -195.5t92 -174.5t35 -81z" />
+<glyph unicode="&#xf250;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM874 700q77 29 149 92.5t129.5 152.5t92.5 210t35 253h-1024q0 -132 35 -253t92.5 -210t129.5 -152.5t149 -92.5q19 -7 30.5 -23.5t11.5 -36.5t-11.5 -36.5t-30.5 -23.5q-77 -29 -149 -92.5 t-129.5 -152.5t-92.5 -210t-35 -253h1024q0 132 -35 253t-92.5 210t-129.5 152.5t-149 92.5q-19 7 -30.5 23.5t-11.5 36.5t11.5 36.5t30.5 23.5z" />
+<glyph unicode="&#xf251;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM1280 1408h-1024q0 -66 9 -128h1006q9 61 9 128zM1280 -128q0 130 -34 249.5t-90.5 208t-126.5 152t-146 94.5h-230q-76 -31 -146 -94.5t-126.5 -152t-90.5 -208t-34 -249.5h1024z" />
+<glyph unicode="&#xf252;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM1280 1408h-1024q0 -206 85 -384h854q85 178 85 384zM1223 192q-54 141 -145.5 241.5t-194.5 142.5h-230q-103 -42 -194.5 -142.5t-145.5 -241.5h910z" />
+<glyph unicode="&#xf253;" d="M1408 1408q0 -261 -106.5 -461.5t-266.5 -306.5q160 -106 266.5 -306.5t106.5 -461.5h96q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h96q0 261 106.5 461.5t266.5 306.5q-160 106 -266.5 306.5t-106.5 461.5h-96q-14 0 -23 9 t-9 23v64q0 14 9 23t23 9h1472q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-96zM874 700q77 29 149 92.5t129.5 152.5t92.5 210t35 253h-1024q0 -132 35 -253t92.5 -210t129.5 -152.5t149 -92.5q19 -7 30.5 -23.5t11.5 -36.5t-11.5 -36.5t-30.5 -23.5q-137 -51 -244 -196 h700q-107 145 -244 196q-19 7 -30.5 23.5t-11.5 36.5t11.5 36.5t30.5 23.5z" />
+<glyph unicode="&#xf254;" d="M1504 -64q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9h-1472q-14 0 -23 9t-9 23v128q0 14 9 23t23 9h1472zM130 0q3 55 16 107t30 95t46 87t53.5 76t64.5 69.5t66 60t70.5 55t66.5 47.5t65 43q-43 28 -65 43t-66.5 47.5t-70.5 55t-66 60t-64.5 69.5t-53.5 76t-46 87 t-30 95t-16 107h1276q-3 -55 -16 -107t-30 -95t-46 -87t-53.5 -76t-64.5 -69.5t-66 -60t-70.5 -55t-66.5 -47.5t-65 -43q43 -28 65 -43t66.5 -47.5t70.5 -55t66 -60t64.5 -69.5t53.5 -76t46 -87t30 -95t16 -107h-1276zM1504 1536q14 0 23 -9t9 -23v-128q0 -14 -9 -23t-23 -9 h-1472q-14 0 -23 9t-9 23v128q0 14 9 23t23 9h1472z" />
+<glyph unicode="&#xf255;" d="M768 1152q-53 0 -90.5 -37.5t-37.5 -90.5v-128h-32v93q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-429l-32 30v172q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-224q0 -47 35 -82l310 -296q39 -39 39 -102q0 -26 19 -45t45 -19h640q26 0 45 19t19 45v25 q0 41 10 77l108 436q10 36 10 77v246q0 48 -32 81.5t-80 33.5q-46 0 -79 -33t-33 -79v-32h-32v125q0 40 -25 72.5t-64 40.5q-14 2 -23 2q-46 0 -79 -33t-33 -79v-128h-32v122q0 51 -32.5 89.5t-82.5 43.5q-5 1 -13 1zM768 1280q84 0 149 -50q57 34 123 34q59 0 111 -27 t86 -76q27 7 59 7q100 0 170 -71.5t70 -171.5v-246q0 -51 -13 -108l-109 -436q-6 -24 -6 -71q0 -80 -56 -136t-136 -56h-640q-84 0 -138 58.5t-54 142.5l-308 296q-76 73 -76 175v224q0 99 70.5 169.5t169.5 70.5q11 0 16 -1q6 95 75.5 160t164.5 65q52 0 98 -21 q72 69 174 69z" />
+<glyph unicode="&#xf256;" horiz-adv-x="1792" d="M880 1408q-46 0 -79 -33t-33 -79v-656h-32v528q0 46 -33 79t-79 33t-79 -33t-33 -79v-528v-256l-154 205q-38 51 -102 51q-53 0 -90.5 -37.5t-37.5 -90.5q0 -43 26 -77l384 -512q38 -51 102 -51h688q34 0 61 22t34 56l76 405q5 32 5 59v498q0 46 -33 79t-79 33t-79 -33 t-33 -79v-272h-32v528q0 46 -33 79t-79 33t-79 -33t-33 -79v-528h-32v656q0 46 -33 79t-79 33zM880 1536q68 0 125.5 -35.5t88.5 -96.5q19 4 42 4q99 0 169.5 -70.5t70.5 -169.5v-17q105 6 180.5 -64t75.5 -175v-498q0 -40 -8 -83l-76 -404q-14 -79 -76.5 -131t-143.5 -52 h-688q-60 0 -114.5 27.5t-90.5 74.5l-384 512q-51 68 -51 154q0 106 75 181t181 75q78 0 128 -34v434q0 99 70.5 169.5t169.5 70.5q23 0 42 -4q31 61 88.5 96.5t125.5 35.5z" />
+<glyph unicode="&#xf257;" horiz-adv-x="1792" d="M1073 -128h-177q-163 0 -226 141q-23 49 -23 102v5q-62 30 -98.5 88.5t-36.5 127.5q0 38 5 48h-261q-106 0 -181 75t-75 181t75 181t181 75h113l-44 17q-74 28 -119.5 93.5t-45.5 145.5q0 106 75 181t181 75q46 0 91 -17l628 -239h401q106 0 181 -75t75 -181v-668 q0 -88 -54 -157.5t-140 -90.5l-339 -85q-92 -23 -186 -23zM1024 583l-155 -71l-163 -74q-30 -14 -48 -41.5t-18 -60.5q0 -46 33 -79t79 -33q26 0 46 10l338 154q-49 10 -80.5 50t-31.5 90v55zM1344 272q0 46 -33 79t-79 33q-26 0 -46 -10l-290 -132q-28 -13 -37 -17 t-30.5 -17t-29.5 -23.5t-16 -29t-8 -40.5q0 -50 31.5 -82t81.5 -32q20 0 38 9l352 160q30 14 48 41.5t18 60.5zM1112 1024l-650 248q-24 8 -46 8q-53 0 -90.5 -37.5t-37.5 -90.5q0 -40 22.5 -73t59.5 -47l526 -200v-64h-640q-53 0 -90.5 -37.5t-37.5 -90.5t37.5 -90.5 t90.5 -37.5h535l233 106v198q0 63 46 106l111 102h-69zM1073 0q82 0 155 19l339 85q43 11 70 45.5t27 78.5v668q0 53 -37.5 90.5t-90.5 37.5h-308l-136 -126q-36 -33 -36 -82v-296q0 -46 33 -77t79 -31t79 35t33 81v208h32v-208q0 -70 -57 -114q52 -8 86.5 -48.5t34.5 -93.5 q0 -42 -23 -78t-61 -53l-310 -141h91z" />
+<glyph unicode="&#xf258;" horiz-adv-x="2048" d="M1151 1536q61 0 116 -28t91 -77l572 -781q118 -159 118 -359v-355q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v177l-286 143h-546q-80 0 -136 56t-56 136v32q0 119 84.5 203.5t203.5 84.5h420l42 128h-686q-100 0 -173.5 67.5t-81.5 166.5q-65 79 -65 182v32 q0 80 56 136t136 56h959zM1920 -64v355q0 157 -93 284l-573 781q-39 52 -103 52h-959q-26 0 -45 -19t-19 -45q0 -32 1.5 -49.5t9.5 -40.5t25 -43q10 31 35.5 50t56.5 19h832v-32h-832q-26 0 -45 -19t-19 -45q0 -44 3 -58q8 -44 44 -73t81 -29h640h91q40 0 68 -28t28 -68 q0 -15 -5 -30l-64 -192q-10 -29 -35 -47.5t-56 -18.5h-443q-66 0 -113 -47t-47 -113v-32q0 -26 19 -45t45 -19h561q16 0 29 -7l317 -158q24 -13 38.5 -36t14.5 -50v-197q0 -26 19 -45t45 -19h384q26 0 45 19t19 45z" />
+<glyph unicode="&#xf259;" horiz-adv-x="2048" d="M816 1408q-48 0 -79.5 -34t-31.5 -82q0 -14 3 -28l150 -624h-26l-116 482q-9 38 -39.5 62t-69.5 24q-47 0 -79 -34t-32 -81q0 -11 4 -29q3 -13 39 -161t68 -282t32 -138v-227l-307 230q-34 26 -77 26q-52 0 -89.5 -36.5t-37.5 -88.5q0 -67 56 -110l507 -379 q34 -26 76 -26h694q33 0 59 20.5t34 52.5l100 401q8 30 10 88t9 86l116 478q3 12 3 26q0 46 -33 79t-80 33q-38 0 -69 -25.5t-40 -62.5l-99 -408h-26l132 547q3 14 3 28q0 47 -32 80t-80 33q-38 0 -68.5 -24t-39.5 -62l-145 -602h-127l-164 682q-9 38 -39.5 62t-68.5 24z M1461 -256h-694q-85 0 -153 51l-507 380q-50 38 -78.5 94t-28.5 118q0 105 75 179t180 74q25 0 49.5 -5.5t41.5 -11t41 -20.5t35 -23t38.5 -29.5t37.5 -28.5l-123 512q-7 35 -7 59q0 93 60 162t152 79q14 87 80.5 144.5t155.5 57.5q83 0 148 -51.5t85 -132.5l103 -428 l83 348q20 81 85 132.5t148 51.5q87 0 152.5 -54t82.5 -139q93 -10 155 -78t62 -161q0 -30 -7 -57l-116 -477q-5 -22 -5 -67q0 -51 -13 -108l-101 -401q-19 -75 -79.5 -122.5t-137.5 -47.5z" />
+<glyph unicode="&#xf25a;" horiz-adv-x="1792" d="M640 1408q-53 0 -90.5 -37.5t-37.5 -90.5v-512v-384l-151 202q-41 54 -107 54q-52 0 -89 -38t-37 -90q0 -43 26 -77l384 -512q38 -51 102 -51h718q22 0 39.5 13.5t22.5 34.5l92 368q24 96 24 194v217q0 41 -28 71t-68 30t-68 -28t-28 -68h-32v61q0 48 -32 81.5t-80 33.5 q-46 0 -79 -33t-33 -79v-64h-32v90q0 55 -37 94.5t-91 39.5q-53 0 -90.5 -37.5t-37.5 -90.5v-96h-32v570q0 55 -37 94.5t-91 39.5zM640 1536q107 0 181.5 -77.5t74.5 -184.5v-220q22 2 32 2q99 0 173 -69q47 21 99 21q113 0 184 -87q27 7 56 7q94 0 159 -67.5t65 -161.5 v-217q0 -116 -28 -225l-92 -368q-16 -64 -68 -104.5t-118 -40.5h-718q-60 0 -114.5 27.5t-90.5 74.5l-384 512q-51 68 -51 154q0 105 74.5 180.5t179.5 75.5q71 0 130 -35v547q0 106 75 181t181 75zM768 128v384h-32v-384h32zM1024 128v384h-32v-384h32zM1280 128v384h-32 v-384h32z" />
+<glyph unicode="&#xf25b;" d="M1288 889q60 0 107 -23q141 -63 141 -226v-177q0 -94 -23 -186l-85 -339q-21 -86 -90.5 -140t-157.5 -54h-668q-106 0 -181 75t-75 181v401l-239 628q-17 45 -17 91q0 106 75 181t181 75q80 0 145.5 -45.5t93.5 -119.5l17 -44v113q0 106 75 181t181 75t181 -75t75 -181 v-261q27 5 48 5q69 0 127.5 -36.5t88.5 -98.5zM1072 896q-33 0 -60.5 -18t-41.5 -48l-74 -163l-71 -155h55q50 0 90 -31.5t50 -80.5l154 338q10 20 10 46q0 46 -33 79t-79 33zM1293 761q-22 0 -40.5 -8t-29 -16t-23.5 -29.5t-17 -30.5t-17 -37l-132 -290q-10 -20 -10 -46 q0 -46 33 -79t79 -33q33 0 60.5 18t41.5 48l160 352q9 18 9 38q0 50 -32 81.5t-82 31.5zM128 1120q0 -22 8 -46l248 -650v-69l102 111q43 46 106 46h198l106 233v535q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5v-640h-64l-200 526q-14 37 -47 59.5t-73 22.5 q-53 0 -90.5 -37.5t-37.5 -90.5zM1180 -128q44 0 78.5 27t45.5 70l85 339q19 73 19 155v91l-141 -310q-17 -38 -53 -61t-78 -23q-53 0 -93.5 34.5t-48.5 86.5q-44 -57 -114 -57h-208v32h208q46 0 81 33t35 79t-31 79t-77 33h-296q-49 0 -82 -36l-126 -136v-308 q0 -53 37.5 -90.5t90.5 -37.5h668z" />
+<glyph unicode="&#xf25c;" horiz-adv-x="1973" d="M857 992v-117q0 -13 -9.5 -22t-22.5 -9h-298v-812q0 -13 -9 -22.5t-22 -9.5h-135q-13 0 -22.5 9t-9.5 23v812h-297q-13 0 -22.5 9t-9.5 22v117q0 14 9 23t23 9h793q13 0 22.5 -9.5t9.5 -22.5zM1895 995l77 -961q1 -13 -8 -24q-10 -10 -23 -10h-134q-12 0 -21 8.5 t-10 20.5l-46 588l-189 -425q-8 -19 -29 -19h-120q-20 0 -29 19l-188 427l-45 -590q-1 -12 -10 -20.5t-21 -8.5h-135q-13 0 -23 10q-9 10 -9 24l78 961q1 12 10 20.5t21 8.5h142q20 0 29 -19l220 -520q10 -24 20 -51q3 7 9.5 24.5t10.5 26.5l221 520q9 19 29 19h141 q13 0 22 -8.5t10 -20.5z" />
+<glyph unicode="&#xf25d;" horiz-adv-x="1792" d="M1042 833q0 88 -60 121q-33 18 -117 18h-123v-281h162q66 0 102 37t36 105zM1094 548l205 -373q8 -17 -1 -31q-8 -16 -27 -16h-152q-20 0 -28 17l-194 365h-155v-350q0 -14 -9 -23t-23 -9h-134q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h294q128 0 190 -24q85 -31 134 -109 t49 -180q0 -92 -42.5 -165.5t-115.5 -109.5q6 -10 9 -16zM896 1376q-150 0 -286 -58.5t-234.5 -157t-157 -234.5t-58.5 -286t58.5 -286t157 -234.5t234.5 -157t286 -58.5t286 58.5t234.5 157t157 234.5t58.5 286t-58.5 286t-157 234.5t-234.5 157t-286 58.5zM1792 640 q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf25e;" horiz-adv-x="1792" d="M605 303q153 0 257 104q14 18 3 36l-45 82q-6 13 -24 17q-16 2 -27 -11l-4 -3q-4 -4 -11.5 -10t-17.5 -13t-23.5 -14.5t-28.5 -13.5t-33.5 -9.5t-37.5 -3.5q-76 0 -125 50t-49 127q0 76 48 125.5t122 49.5q37 0 71.5 -14t50.5 -28l16 -14q11 -11 26 -10q16 2 24 14l53 78 q13 20 -2 39q-3 4 -11 12t-30 23.5t-48.5 28t-67.5 22.5t-86 10q-148 0 -246 -96.5t-98 -240.5q0 -146 97 -241.5t247 -95.5zM1235 303q153 0 257 104q14 18 4 36l-45 82q-8 14 -25 17q-16 2 -27 -11l-4 -3q-4 -4 -11.5 -10t-17.5 -13t-23.5 -14.5t-28.5 -13.5t-33.5 -9.5 t-37.5 -3.5q-76 0 -125 50t-49 127q0 76 48 125.5t122 49.5q37 0 71.5 -14t50.5 -28l16 -14q11 -11 26 -10q16 2 24 14l53 78q13 20 -2 39q-3 4 -11 12t-30 23.5t-48.5 28t-67.5 22.5t-86 10q-147 0 -245.5 -96.5t-98.5 -240.5q0 -146 97 -241.5t247 -95.5zM896 1376 q-150 0 -286 -58.5t-234.5 -157t-157 -234.5t-58.5 -286t58.5 -286t157 -234.5t234.5 -157t286 -58.5t286 58.5t234.5 157t157 234.5t58.5 286t-58.5 286t-157 234.5t-234.5 157t-286 58.5zM896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191 t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71z" />
+<glyph unicode="&#xf260;" horiz-adv-x="2048" d="M736 736l384 -384l-384 -384l-672 672l672 672l168 -168l-96 -96l-72 72l-480 -480l480 -480l193 193l-289 287zM1312 1312l672 -672l-672 -672l-168 168l96 96l72 -72l480 480l-480 480l-193 -193l289 -287l-96 -96l-384 384z" />
+<glyph unicode="&#xf261;" horiz-adv-x="1792" d="M717 182l271 271l-279 279l-88 -88l192 -191l-96 -96l-279 279l279 279l40 -40l87 87l-127 128l-454 -454zM1075 190l454 454l-454 454l-271 -271l279 -279l88 88l-192 191l96 96l279 -279l-279 -279l-40 40l-87 -88zM1792 640q0 -182 -71 -348t-191 -286t-286 -191 t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf262;" horiz-adv-x="2304" d="M651 539q0 -39 -27.5 -66.5t-65.5 -27.5q-39 0 -66.5 27.5t-27.5 66.5q0 38 27.5 65.5t66.5 27.5q38 0 65.5 -27.5t27.5 -65.5zM1805 540q0 -39 -27.5 -66.5t-66.5 -27.5t-66.5 27.5t-27.5 66.5t27.5 66t66.5 27t66.5 -27t27.5 -66zM765 539q0 79 -56.5 136t-136.5 57 t-136.5 -56.5t-56.5 -136.5t56.5 -136.5t136.5 -56.5t136.5 56.5t56.5 136.5zM1918 540q0 80 -56.5 136.5t-136.5 56.5q-79 0 -136 -56.5t-57 -136.5t56.5 -136.5t136.5 -56.5t136.5 56.5t56.5 136.5zM850 539q0 -116 -81.5 -197.5t-196.5 -81.5q-116 0 -197.5 82t-81.5 197 t82 196.5t197 81.5t196.5 -81.5t81.5 -196.5zM2004 540q0 -115 -81.5 -196.5t-197.5 -81.5q-115 0 -196.5 81.5t-81.5 196.5t81.5 196.5t196.5 81.5q116 0 197.5 -81.5t81.5 -196.5zM1040 537q0 191 -135.5 326.5t-326.5 135.5q-125 0 -231 -62t-168 -168.5t-62 -231.5 t62 -231.5t168 -168.5t231 -62q191 0 326.5 135.5t135.5 326.5zM1708 1110q-254 111 -556 111q-319 0 -573 -110q117 0 223 -45.5t182.5 -122.5t122 -183t45.5 -223q0 115 43.5 219.5t118 180.5t177.5 123t217 50zM2187 537q0 191 -135 326.5t-326 135.5t-326.5 -135.5 t-135.5 -326.5t135.5 -326.5t326.5 -135.5t326 135.5t135 326.5zM1921 1103h383q-44 -51 -75 -114.5t-40 -114.5q110 -151 110 -337q0 -156 -77 -288t-209 -208.5t-287 -76.5q-133 0 -249 56t-196 155q-47 -56 -129 -179q-11 22 -53.5 82.5t-74.5 97.5 q-80 -99 -196.5 -155.5t-249.5 -56.5q-155 0 -287 76.5t-209 208.5t-77 288q0 186 110 337q-9 51 -40 114.5t-75 114.5h365q149 100 355 156.5t432 56.5q224 0 421 -56t348 -157z" />
+<glyph unicode="&#xf263;" horiz-adv-x="1280" d="M640 629q-188 0 -321 133t-133 320q0 188 133 321t321 133t321 -133t133 -321q0 -187 -133 -320t-321 -133zM640 1306q-92 0 -157.5 -65.5t-65.5 -158.5q0 -92 65.5 -157.5t157.5 -65.5t157.5 65.5t65.5 157.5q0 93 -65.5 158.5t-157.5 65.5zM1163 574q13 -27 15 -49.5 t-4.5 -40.5t-26.5 -38.5t-42.5 -37t-61.5 -41.5q-115 -73 -315 -94l73 -72l267 -267q30 -31 30 -74t-30 -73l-12 -13q-31 -30 -74 -30t-74 30q-67 68 -267 268l-267 -268q-31 -30 -74 -30t-73 30l-12 13q-31 30 -31 73t31 74l267 267l72 72q-203 21 -317 94 q-39 25 -61.5 41.5t-42.5 37t-26.5 38.5t-4.5 40.5t15 49.5q10 20 28 35t42 22t56 -2t65 -35q5 -4 15 -11t43 -24.5t69 -30.5t92 -24t113 -11q91 0 174 25.5t120 50.5l38 25q33 26 65 35t56 2t42 -22t28 -35z" />
+<glyph unicode="&#xf264;" d="M927 956q0 -66 -46.5 -112.5t-112.5 -46.5t-112.5 46.5t-46.5 112.5t46.5 112.5t112.5 46.5t112.5 -46.5t46.5 -112.5zM1141 593q-10 20 -28 32t-47.5 9.5t-60.5 -27.5q-10 -8 -29 -20t-81 -32t-127 -20t-124 18t-86 36l-27 18q-31 25 -60.5 27.5t-47.5 -9.5t-28 -32 q-22 -45 -2 -74.5t87 -73.5q83 -53 226 -67l-51 -52q-142 -142 -191 -190q-22 -22 -22 -52.5t22 -52.5l9 -9q22 -22 52.5 -22t52.5 22l191 191q114 -115 191 -191q22 -22 52.5 -22t52.5 22l9 9q22 22 22 52.5t-22 52.5l-191 190l-52 52q141 14 225 67q67 44 87 73.5t-2 74.5 zM1092 956q0 134 -95 229t-229 95t-229 -95t-95 -229t95 -229t229 -95t229 95t95 229zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf265;" horiz-adv-x="1720" d="M1565 1408q65 0 110 -45.5t45 -110.5v-519q0 -176 -68 -336t-182.5 -275t-274 -182.5t-334.5 -67.5q-176 0 -335.5 67.5t-274.5 182.5t-183 275t-68 336v519q0 64 46 110t110 46h1409zM861 344q47 0 82 33l404 388q37 35 37 85q0 49 -34.5 83.5t-83.5 34.5q-47 0 -82 -33 l-323 -310l-323 310q-35 33 -81 33q-49 0 -83.5 -34.5t-34.5 -83.5q0 -51 36 -85l405 -388q33 -33 81 -33z" />
+<glyph unicode="&#xf266;" horiz-adv-x="2304" d="M1494 -103l-295 695q-25 -49 -158.5 -305.5t-198.5 -389.5q-1 -1 -27.5 -0.5t-26.5 1.5q-82 193 -255.5 587t-259.5 596q-21 50 -66.5 107.5t-103.5 100.5t-102 43q0 5 -0.5 24t-0.5 27h583v-50q-39 -2 -79.5 -16t-66.5 -43t-10 -64q26 -59 216.5 -499t235.5 -540 q31 61 140 266.5t131 247.5q-19 39 -126 281t-136 295q-38 69 -201 71v50l513 -1v-47q-60 -2 -93.5 -25t-12.5 -69q33 -70 87 -189.5t86 -187.5q110 214 173 363q24 55 -10 79.5t-129 26.5q1 7 1 25v24q64 0 170.5 0.5t180 1t92.5 0.5v-49q-62 -2 -119 -33t-90 -81 l-213 -442q13 -33 127.5 -290t121.5 -274l441 1017q-14 38 -49.5 62.5t-65 31.5t-55.5 8v50l460 -4l1 -2l-1 -44q-139 -4 -201 -145q-526 -1216 -559 -1291h-49z" />
+<glyph unicode="&#xf267;" horiz-adv-x="1792" d="M949 643q0 -26 -16.5 -45t-41.5 -19q-26 0 -45 16.5t-19 41.5q0 26 17 45t42 19t44 -16.5t19 -41.5zM964 585l350 581q-9 -8 -67.5 -62.5t-125.5 -116.5t-136.5 -127t-117 -110.5t-50.5 -51.5l-349 -580q7 7 67 62t126 116.5t136 127t117 111t50 50.5zM1611 640 q0 -201 -104 -371q-3 2 -17 11t-26.5 16.5t-16.5 7.5q-13 0 -13 -13q0 -10 59 -44q-74 -112 -184.5 -190.5t-241.5 -110.5l-16 67q-1 10 -15 10q-5 0 -8 -5.5t-2 -9.5l16 -68q-72 -15 -146 -15q-199 0 -372 105q1 2 13 20.5t21.5 33.5t9.5 19q0 13 -13 13q-6 0 -17 -14.5 t-22.5 -34.5t-13.5 -23q-113 75 -192 187.5t-110 244.5l69 15q10 3 10 15q0 5 -5.5 8t-10.5 2l-68 -15q-14 72 -14 139q0 206 109 379q2 -1 18.5 -12t30 -19t17.5 -8q13 0 13 12q0 6 -12.5 15.5t-32.5 21.5l-20 12q77 112 189 189t244 107l15 -67q2 -10 15 -10q5 0 8 5.5 t2 10.5l-15 66q71 13 134 13q204 0 379 -109q-39 -56 -39 -65q0 -13 12 -13q11 0 48 64q111 -75 187.5 -186t107.5 -241l-56 -12q-10 -2 -10 -16q0 -5 5.5 -8t9.5 -2l57 13q14 -72 14 -140zM1696 640q0 163 -63.5 311t-170.5 255t-255 170.5t-311 63.5t-311 -63.5 t-255 -170.5t-170.5 -255t-63.5 -311t63.5 -311t170.5 -255t255 -170.5t311 -63.5t311 63.5t255 170.5t170.5 255t63.5 311zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191 t191 -286t71 -348z" />
+<glyph unicode="&#xf268;" horiz-adv-x="1792" d="M893 1536q240 2 451 -120q232 -134 352 -372l-742 39q-160 9 -294 -74.5t-185 -229.5l-276 424q128 159 311 245.5t383 87.5zM146 1131l337 -663q72 -143 211 -217t293 -45l-230 -451q-212 33 -385 157.5t-272.5 316t-99.5 411.5q0 267 146 491zM1732 962 q58 -150 59.5 -310.5t-48.5 -306t-153 -272t-246 -209.5q-230 -133 -498 -119l405 623q88 131 82.5 290.5t-106.5 277.5zM896 942q125 0 213.5 -88.5t88.5 -213.5t-88.5 -213.5t-213.5 -88.5t-213.5 88.5t-88.5 213.5t88.5 213.5t213.5 88.5z" />
+<glyph unicode="&#xf269;" horiz-adv-x="1792" d="M903 -256q-283 0 -504.5 150.5t-329.5 398.5q-58 131 -67 301t26 332.5t111 312t179 242.5l-11 -281q11 14 68 15.5t70 -15.5q42 81 160.5 138t234.5 59q-54 -45 -119.5 -148.5t-58.5 -163.5q25 -8 62.5 -13.5t63 -7.5t68 -4t50.5 -3q15 -5 9.5 -45.5t-30.5 -75.5 q-5 -7 -16.5 -18.5t-56.5 -35.5t-101 -34l15 -189l-139 67q-18 -43 -7.5 -81.5t36 -66.5t65.5 -41.5t81 -6.5q51 9 98 34.5t83.5 45t73.5 17.5q61 -4 89.5 -33t19.5 -65q-1 -2 -2.5 -5.5t-8.5 -12.5t-18 -15.5t-31.5 -10.5t-46.5 -1q-60 -95 -144.5 -135.5t-209.5 -29.5 q74 -61 162.5 -82.5t168.5 -6t154.5 52t128 87.5t80.5 104q43 91 39 192.5t-37.5 188.5t-78.5 125q87 -38 137 -79.5t77 -112.5q15 170 -57.5 343t-209.5 284q265 -77 412 -279.5t151 -517.5q2 -127 -40.5 -255t-123.5 -238t-189 -196t-247.5 -135.5t-288.5 -49.5z" />
+<glyph unicode="&#xf26a;" d="M768 -92q77 0 139.5 63t100.5 166t59 234.5t21 268.5t-21 268.5t-59 234.5t-100.5 166t-139.5 63t-139.5 -63t-100.5 -166t-59 -234.5t-21 -268.5t21 -268.5t59 -234.5t100.5 -166t139.5 -63zM768 -256q-184 0 -333 77t-240 203t-141 287t-50 329t50 329t141 287t240 203 t333 77q148 0 274 -50t214.5 -136t151.5 -201t92.5 -244t29.5 -265t-29.5 -265t-92.5 -244t-151.5 -201t-214.5 -136t-274 -50z" />
+<glyph unicode="&#xf26b;" horiz-adv-x="1792" d="M716 -69q-143 35 -261.5 114t-197.5 191q-139 -300 -17 -398q26 -21 85 -24.5t127.5 9.5t141 41.5t122.5 66.5zM693 762h452q0 108 -61.5 169t-168.5 61q-103 0 -162.5 -62.5t-59.5 -167.5zM1724 1137h-34q26 102 22.5 170t-25 110t-63.5 57t-93.5 11t-115 -26.5 t-128.5 -56.5t-134 -79q129 -37 238.5 -113.5t185 -179t110 -231.5t15.5 -262h-1005q0 -60 10 -106t34 -85t69.5 -60t112.5 -21q87 0 142.5 44t72.5 122h540q-71 -230 -281.5 -377t-477.5 -147q-83 0 -159 15q-35 -40 -151 -94t-248 -78t-219 35q-78 60 -100 159t7 214 t88 242t143.5 248t173.5 226.5t177.5 183.5t156.5 112v24q-120 -37 -258.5 -137.5t-240.5 -207t-159 -195.5q4 106 34 201t80 169t118 135.5t147.5 100.5t168 65.5t180.5 29.5t185 -8q310 186 503 189h7q57 0 103 -18q80 -30 98 -132.5t-30 -248.5z" />
+<glyph unicode="&#xf26c;" horiz-adv-x="2048" d="M1792 288v960q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1248v-960q0 -66 -47 -113t-113 -47h-736v-128h352q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23 v64q0 14 9 23t23 9h352v128h-736q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf26d;" horiz-adv-x="1792" d="M138 1408h197q-70 -64 -126 -149q-36 -56 -59 -115t-30 -125.5t-8.5 -120t10.5 -132t21 -126t28 -136.5q4 -19 6 -28q51 -238 81 -329q57 -171 152 -275h-272q-48 0 -82 34t-34 82v1304q0 48 34 82t82 34zM1346 1408h308q48 0 82 -34t34 -82v-1304q0 -48 -34 -82t-82 -34 h-178q212 210 196 565l-469 -101q-2 -45 -12 -82t-31 -72t-59.5 -59.5t-93.5 -36.5q-123 -26 -199 40q-32 27 -53 61t-51.5 129t-64.5 258q-35 163 -45.5 263t-5.5 139t23 77q20 41 62.5 73t102.5 45q45 12 83.5 6.5t67 -17t54 -35t43 -48t34.5 -56.5l468 100 q-68 175 -180 287z" />
+<glyph unicode="&#xf26e;" horiz-adv-x="2304" d="M1391 390v0l-1 1q-15 18 -34.5 37.5t-62.5 57.5t-93.5 62t-95.5 24q-48 0 -83 -21.5t-51 -54t-23 -59t-7 -47.5v0v0q0 -21 7 -48t23 -59t51 -53.5t83 -21.5q45 0 95.5 24t94 62.5t62 57t34.5 37.5zM2103 390q0 21 -7 47.5t-23 59t-51 54t-83 21.5q-45 0 -95.5 -24 t-94 -62.5t-62 -57t-34.5 -37.5l-1 -1v0v0l1 -1q15 -18 34.5 -37.5t62.5 -57.5t93.5 -62t95.5 -24q48 0 83 21.5t51 53.5t23 59t7 48zM2304 393q0 -69 -24 -137.5t-68 -126t-116 -93.5t-159 -36q-68 0 -134 24t-113.5 58.5t-84.5 69.5t-59.5 59t-25.5 24t-22.5 -24 t-54.5 -58.5t-81.5 -69.5t-115 -59t-143.5 -24q-65 0 -123.5 22.5t-96.5 54t-66.5 66.5t-41 59.5t-12.5 32.5q0 -8 -8.5 -26.5t-25 -45.5t-47 -55t-69 -52.5t-96.5 -40t-125 -15.5q-71 0 -130 15.5t-98.5 39.5t-70.5 56.5t-48 63.5t-27.5 63.5t-14 54t-3.5 36.5h217 q0 -55 49 -107.5t126 -52.5q79 0 134.5 67t55.5 148q0 80 -52 136.5t-138 56.5q-5 0 -13 -0.5t-31 -5t-43 -12t-42 -24.5t-34 -40h-195l102 583h602v-174h-445q-27 -159 -41 -248q4 0 16.5 13t31.5 28.5t65 28.5t108 13t114 -20.5t82.5 -49.5t51.5 -58.5t31 -50t11 -20.5 t13 25t36.5 60.5t60.5 71.5t97 61t133 25t140.5 -25t115.5 -60.5t83.5 -71.5t56.5 -61t21 -25q2 0 22 25t56 60.5t83.5 71.5t115.5 61t140 25q92 0 164.5 -35t115.5 -93t65 -125t22 -137z" />
+<glyph unicode="&#xf270;" horiz-adv-x="1792" d="M1551 60q15 6 26 3t11 -17.5t-15 -33.5q-13 -16 -44 -43.5t-95.5 -68t-141 -74t-188 -58t-229.5 -24.5q-119 0 -238 31t-209 76.5t-172.5 104t-132.5 105t-84 87.5q-8 9 -10 16.5t1 12t8 7t11.5 2t11.5 -4.5q192 -117 300 -166q389 -176 799 -90q190 40 391 135z M1758 175q11 -16 2.5 -69.5t-28.5 -102.5q-34 -83 -85 -124q-17 -14 -26 -9t0 24q21 45 44.5 121.5t6.5 98.5q-5 7 -15.5 11.5t-27 6t-29.5 2.5t-35 0t-31.5 -2t-31 -3t-22.5 -2q-6 -1 -13 -1.5t-11 -1t-8.5 -1t-7 -0.5h-5.5h-4.5t-3 0.5t-2 1.5l-1.5 3q-6 16 47 40t103 30 q46 7 108 1t76 -24zM1364 618q0 -31 13.5 -64t32 -58t37.5 -46t33 -32l13 -11l-227 -224q-40 37 -79 75.5t-58 58.5l-19 20q-11 11 -25 33q-38 -59 -97.5 -102.5t-127.5 -63.5t-140 -23t-137.5 21t-117.5 65.5t-83 113t-31 162.5q0 84 28 154t72 116.5t106.5 83t122.5 57 t130 34.5t119.5 18.5t99.5 6.5v127q0 65 -21 97q-34 53 -121 53q-6 0 -16.5 -1t-40.5 -12t-56 -29.5t-56 -59.5t-48 -96l-294 27q0 60 22 119t67 113t108 95t151.5 65.5t190.5 24.5q100 0 181 -25t129.5 -61.5t81 -83t45 -86t12.5 -73.5v-589zM692 597q0 -86 70 -133 q66 -44 139 -22q84 25 114 123q14 45 14 101v162q-59 -2 -111 -12t-106.5 -33.5t-87 -71t-32.5 -114.5z" />
+<glyph unicode="&#xf271;" horiz-adv-x="1792" d="M1536 1280q52 0 90 -38t38 -90v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128zM1152 1376v-288q0 -14 9 -23t23 -9 h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM384 1376v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM1536 -128v1024h-1408v-1024h1408zM896 448h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224 v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224z" />
+<glyph unicode="&#xf272;" horiz-adv-x="1792" d="M1152 416v-64q0 -14 -9 -23t-23 -9h-576q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h576q14 0 23 -9t9 -23zM128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23 t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47 t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf273;" horiz-adv-x="1792" d="M1111 151l-46 -46q-9 -9 -22 -9t-23 9l-188 189l-188 -189q-10 -9 -23 -9t-22 9l-46 46q-9 9 -9 22t9 23l189 188l-189 188q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l188 -188l188 188q10 9 23 9t22 -9l46 -46q9 -9 9 -22t-9 -23l-188 -188l188 -188q9 -10 9 -23t-9 -22z M128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280 q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf274;" horiz-adv-x="1792" d="M1303 572l-512 -512q-10 -9 -23 -9t-23 9l-288 288q-9 10 -9 23t9 22l46 46q9 9 22 9t23 -9l220 -220l444 444q10 9 23 9t22 -9l46 -46q9 -9 9 -22t-9 -23zM128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23 t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47 t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf275;" horiz-adv-x="1792" d="M448 1536q26 0 45 -19t19 -45v-891l536 429q17 14 40 14q26 0 45 -19t19 -45v-379l536 429q17 14 40 14q26 0 45 -19t19 -45v-1152q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h384z" />
+<glyph unicode="&#xf276;" horiz-adv-x="1024" d="M512 448q66 0 128 15v-655q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v655q61 -15 128 -15zM512 1536q212 0 362 -150t150 -362t-150 -362t-362 -150t-362 150t-150 362t150 362t362 150zM512 1312q14 0 23 9t9 23t-9 23t-23 9q-146 0 -249 -103t-103 -249 q0 -14 9 -23t23 -9t23 9t9 23q0 119 84.5 203.5t203.5 84.5z" />
+<glyph unicode="&#xf277;" horiz-adv-x="1792" d="M1745 1239q10 -10 10 -23t-10 -23l-141 -141q-28 -28 -68 -28h-1344q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h576v64q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-64h512q40 0 68 -28zM768 320h256v-512q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v512zM1600 768 q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19h-1344q-40 0 -68 28l-141 141q-10 10 -10 23t10 23l141 141q28 28 68 28h512v192h256v-192h576z" />
+<glyph unicode="&#xf278;" horiz-adv-x="2048" d="M2020 1525q28 -20 28 -53v-1408q0 -20 -11 -36t-29 -23l-640 -256q-24 -11 -48 0l-616 246l-616 -246q-10 -5 -24 -5q-19 0 -36 11q-28 20 -28 53v1408q0 20 11 36t29 23l640 256q24 11 48 0l616 -246l616 246q32 13 60 -6zM736 1390v-1270l576 -230v1270zM128 1173 v-1270l544 217v1270zM1920 107v1270l-544 -217v-1270z" />
+<glyph unicode="&#xf279;" horiz-adv-x="1792" d="M512 1536q13 0 22.5 -9.5t9.5 -22.5v-1472q0 -20 -17 -28l-480 -256q-7 -4 -15 -4q-13 0 -22.5 9.5t-9.5 22.5v1472q0 20 17 28l480 256q7 4 15 4zM1760 1536q13 0 22.5 -9.5t9.5 -22.5v-1472q0 -20 -17 -28l-480 -256q-7 -4 -15 -4q-13 0 -22.5 9.5t-9.5 22.5v1472 q0 20 17 28l480 256q7 4 15 4zM640 1536q8 0 14 -3l512 -256q18 -10 18 -29v-1472q0 -13 -9.5 -22.5t-22.5 -9.5q-8 0 -14 3l-512 256q-18 10 -18 29v1472q0 13 9.5 22.5t22.5 9.5z" />
+<glyph unicode="&#xf27a;" horiz-adv-x="1792" d="M640 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 640q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1408 640q0 53 -37.5 90.5t-90.5 37.5 t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-110 0 -211 18q-173 -173 -435 -229q-52 -10 -86 -13q-12 -1 -22 6t-13 18q-4 15 20 37q5 5 23.5 21.5t25.5 23.5t23.5 25.5t24 31.5t20.5 37 t20 48t14.5 57.5t12.5 72.5q-146 90 -229.5 216.5t-83.5 269.5q0 174 120 321.5t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="&#xf27b;" horiz-adv-x="1792" d="M640 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1024 640q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1408 640q0 -53 -37.5 -90.5t-90.5 -37.5 t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5 t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51 t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 130 71 248.5t191 204.5t286 136.5t348 50.5t348 -50.5t286 -136.5t191 -204.5t71 -248.5z" />
+<glyph unicode="&#xf27c;" horiz-adv-x="1024" d="M512 345l512 295v-591l-512 -296v592zM0 640v-591l512 296zM512 1527v-591l-512 -296v591zM512 936l512 295v-591z" />
+<glyph unicode="&#xf27d;" horiz-adv-x="1792" d="M1709 1018q-10 -236 -332 -651q-333 -431 -562 -431q-142 0 -240 263q-44 160 -132 482q-72 262 -157 262q-18 0 -127 -76l-77 98q24 21 108 96.5t130 115.5q156 138 241 146q95 9 153 -55.5t81 -203.5q44 -287 66 -373q55 -249 120 -249q51 0 154 161q101 161 109 246 q13 139 -109 139q-57 0 -121 -26q120 393 459 382q251 -8 236 -326z" />
+<glyph unicode="&#xf27e;" d="M0 1408h1536v-1536h-1536v1536zM1085 293l-221 631l221 297h-634l221 -297l-221 -631l317 -304z" />
+<glyph unicode="&#xf280;" d="M0 1408h1536v-1536h-1536v1536zM908 1088l-12 -33l75 -83l-31 -114l25 -25l107 57l107 -57l25 25l-31 114l75 83l-12 33h-95l-53 96h-32l-53 -96h-95zM641 925q32 0 44.5 -16t11.5 -63l174 21q0 55 -17.5 92.5t-50.5 56t-69 25.5t-85 7q-133 0 -199 -57.5t-66 -182.5v-72 h-96v-128h76q20 0 20 -8v-382q0 -14 -5 -20t-18 -7l-73 -7v-88h448v86l-149 14q-6 1 -8.5 1.5t-3.5 2.5t-0.5 4t1 7t0.5 10v387h191l38 128h-231q-6 0 -2 6t4 9v80q0 27 1.5 40.5t7.5 28t19.5 20t36.5 5.5zM1248 96v86l-54 9q-7 1 -9.5 2.5t-2.5 3t1 7.5t1 12v520h-275 l-23 -101l83 -22q23 -7 23 -27v-370q0 -14 -6 -18.5t-20 -6.5l-70 -9v-86h352z" />
+<glyph unicode="&#xf281;" horiz-adv-x="1792" />
+<glyph unicode="&#xf282;" horiz-adv-x="1792" />
+<glyph unicode="&#xf283;" horiz-adv-x="1792" />
+<glyph unicode="&#xf284;" horiz-adv-x="1792" />
+<glyph unicode="&#xf285;" horiz-adv-x="1792" />
+<glyph unicode="&#xf286;" horiz-adv-x="1792" />
+<glyph unicode="&#xf287;" horiz-adv-x="1792" />
+<glyph unicode="&#xf288;" horiz-adv-x="1792" />
+<glyph unicode="&#xf289;" horiz-adv-x="1792" />
+<glyph unicode="&#xf28a;" horiz-adv-x="1792" />
+<glyph unicode="&#xf28b;" horiz-adv-x="1792" />
+<glyph unicode="&#xf28c;" horiz-adv-x="1792" />
+<glyph unicode="&#xf28d;" horiz-adv-x="1792" />
+<glyph unicode="&#xf28e;" horiz-adv-x="1792" />
+<glyph unicode="&#xf500;" horiz-adv-x="1792" />
+</font>
+</defs></svg> 
\ No newline at end of file
Binary file src/global/web/assets/font-awesome/fonts/fontawesome-webfont.ttf has changed
Binary file src/global/web/assets/font-awesome/fonts/fontawesome-webfont.woff has changed
Binary file src/global/web/assets/font-awesome/fonts/fontawesome-webfont.woff2 has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/assets/global.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,62 @@
+body{margin:0 auto;font-family:Lato,Verdana,Arial,sans-serif}
+body>*{padding:1em;max-width:1200px;margin:0 auto}
+[full]{max-width:100%}
+[oswald]{font-family:Oswald,Arial,sans-serif}
+[lato]{font-family:Lato,Arial,sans-serif}
+[gray]{color:#444}
+[light]{color:#999}
+[center]{text-align:center}
+[dark],[dark] a{background:#444;color:#EEE}
+[paddingTop]{padding-top:2em}
+[marginTop]{margin-top:2em}
+[marginBottom]{margin-bottom:2em}
+[lineHeight]{line-height:1.6em}
+[invisible]{display:none}
+h1{margin-bottom:0}
+h2{margin-top:.4em}
+div[header]{padding:4em 0 1em}
+div[content]{max-width:1000px;margin:0 auto;clear:both}
+div[col1]{float:left;width:600px}
+div[col1]>img{max-width:100%}
+div[col2]{float:left}
+div[col2] > ul{font-size:1.5em;padding:.1em 1em;border-radius:1em}
+div[col2] > ul > li{list-style-type:none;margin:0 0 .3em;padding:.4em .5em;font-weight:bold}
+div[col2] > ul > li > a{text-decoration:none;color:#1E67D2;margin-right:1em}
+div[col2] > ul > li:hover{background:#EEF1F3;border-radius:20px}
+div[col33]{width:33%;display:inline-block;vertical-align:top}
+ul[floating] > li{display:inline-block;list-style-type:none;margin:.2em 1em .2em 0;height: 1.2em}
+div[footer] a{display:inline-block;margin:0 1em 1em 0;text-decoration:none;color:#D0D0D0;text-shadow:0px 1px 1px black;border-bottom:1px dotted #757474}
+[marginHorizontal]{margin:0 1em}
+i.fa.fa-chevron-right{font-size:.8em;margin-top:.5em}
+@media (max-width: 1000px) {
+	div[col1]{width:500px}
+	div[col2] > ul {font-size: 1.3em}
+}
+@media (max-width: 850px) {
+	[col1],[col2],[col33]{width:100% !important}
+	[col33]{padding-bottom:2em}
+	div[col2] > ul {font-size: 1.7em}
+	div[col2] > ul > li{padding:.4em .2em}
+}
+@media (max-width: 600px) {
+	div[col2] > ul {font-size: 1.3em}
+}
+input[type='text']{font-size:1.3em;border-radius:10px;border:1px solid #bbb;padding:.2em}
+input[type='submit']{
+	-webkit-box-shadow: rgba(0,0,0,0.2) 0 1px 0 0;
+	-moz-box-shadow: rgba(0,0,0,0.2) 0 1px 0 0;
+	box-shadow: rgba(0,0,0,0.2) 0 1px 0 0;
+	color: #333;
+	background-color: #DCDBD9;
+	border-radius: 5px;
+	-moz-border-radius: 5px;
+	-webkit-border-radius: 5px;
+	border: none;
+	font-family: 'Helvetica Neue',Arial,sans-serif;
+	font-size: 16px;
+	font-weight: 700;
+	height: 32px;
+	padding: 4px 16px;
+	text-shadow: #E0E0E0 0 1px 0;
+	vertical-align: 15%;
+}
\ No newline at end of file
Binary file src/global/web/assets/images/debian.png has changed
Binary file src/global/web/assets/images/exim.png has changed
Binary file src/global/web/assets/images/gray.png has changed
Binary file src/global/web/assets/images/home.png has changed
Binary file src/global/web/assets/images/java.gif has changed
Binary file src/global/web/assets/images/jetty.gif has changed
Binary file src/global/web/assets/images/logo_nabble_home.png has changed
Binary file src/global/web/assets/images/postgres.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/assets/jquery/jquery-1.9.1.min.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,5 @@
+/*! jQuery v1.9.1 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license
+//@ sourceMappingURL=jquery.min.map
+*/(function(e,t){var n,r,i=typeof t,o=e.document,a=e.location,s=e.jQuery,u=e.$,l={},c=[],p="1.9.1",f=c.concat,d=c.push,h=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,b=function(e,t){return new b.fn.init(e,t,r)},x=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^[\],:{}\s]*$/,E=/(?:^|:|,)(?:\s*\[)+/g,S=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,A=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,j=/^-ms-/,D=/-([\da-z])/gi,L=function(e,t){return t.toUpperCase()},H=function(e){(o.addEventListener||"load"===e.type||"complete"===o.readyState)&&(q(),b.ready())},q=function(){o.addEventListener?(o.removeEventListener("DOMContentLoaded",H,!1),e.removeEventListener("load",H,!1)):(o.detachEvent("onreadystatechange",H),e.detachEvent("onload",H))};b.fn=b.prototype={jquery:p,constructor:b,init:function(e,n,r){var i,a;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 b?n[0]:n,b.merge(this,b.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:o,!0)),C.test(i[1])&&b.isPlainObject(n))for(i in n)b.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(a=o.getElementById(i[2]),a&&a.parentNode){if(a.id!==i[2])return r.find(e);this.length=1,this[0]=a}return this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):b.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),b.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return h.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=b.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return b.each(this,e,t)},ready:function(e){return b.ready.promise().done(e),this},slice:function(){return this.pushStack(h.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(b.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:d,sort:[].sort,splice:[].splice},b.fn.init.prototype=b.fn,b.extend=b.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||b.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(o=arguments[u]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(b.isPlainObject(r)||(n=b.isArray(r)))?(n?(n=!1,a=e&&b.isArray(e)?e:[]):a=e&&b.isPlainObject(e)?e:{},s[i]=b.extend(c,a,r)):r!==t&&(s[i]=r));return s},b.extend({noConflict:function(t){return e.$===b&&(e.$=u),t&&e.jQuery===b&&(e.jQuery=s),b},isReady:!1,readyWait:1,holdReady:function(e){e?b.readyWait++:b.ready(!0)},ready:function(e){if(e===!0?!--b.readyWait:!b.isReady){if(!o.body)return setTimeout(b.ready);b.isReady=!0,e!==!0&&--b.readyWait>0||(n.resolveWith(o,[b]),b.fn.trigger&&b(o).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===b.type(e)},isArray:Array.isArray||function(e){return"array"===b.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?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==b.type(e)||e.nodeType||b.isWindow(e))return!1;try{if(e.constructor&&!y.call(e,"constructor")&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||y.call(e,r)},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||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=b.buildFragment([e],t,i),i&&b(i).remove(),b.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=b.trim(n),n&&k.test(n.replace(S,"@").replace(A,"]").replace(E,"")))?Function("return "+n)():(b.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||b.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&b.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(j,"ms-").replace(D,L)},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:v&&!v.call("\ufeff\u00a0")?function(e){return null==e?"":v.call(e)}:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?b.merge(n,"string"==typeof e?[e]:e):d.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(g)return g.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 f.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),b.isFunction(e)?(r=h.call(arguments,2),i=function(){return e.apply(n||this,r.concat(h.call(arguments)))},i.guid=e.guid=e.guid||b.guid++,i):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===b.type(r)){o=!0;for(u in r)b.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,b.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(b(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),b.ready.promise=function(t){if(!n)if(n=b.Deferred(),"complete"===o.readyState)setTimeout(b.ready);else if(o.addEventListener)o.addEventListener("DOMContentLoaded",H,!1),e.addEventListener("load",H,!1);else{o.attachEvent("onreadystatechange",H),e.attachEvent("onload",H);var r=!1;try{r=null==e.frameElement&&o.documentElement}catch(i){}r&&r.doScroll&&function a(){if(!b.isReady){try{r.doScroll("left")}catch(e){return setTimeout(a,50)}q(),b.ready()}}()}return n.promise(t)},b.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=b(o);var _={};function F(e){var t=_[e]={};return b.each(e.match(w)||[],function(e,n){t[n]=!0}),t}b.Callbacks=function(e){e="string"==typeof e?_[e]||F(e):b.extend({},e);var n,r,i,o,a,s,u=[],l=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=u.length,n=!0;u&&o>a;a++)if(u[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,u&&(l?l.length&&c(l.shift()):r?u=[]:p.disable())},p={add:function(){if(u){var t=u.length;(function i(t){b.each(t,function(t,n){var r=b.type(n);"function"===r?e.unique&&p.has(n)||u.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=u.length:r&&(s=t,c(r))}return this},remove:function(){return u&&b.each(arguments,function(e,t){var r;while((r=b.inArray(t,u,r))>-1)u.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?b.inArray(e,u)>-1:!(!u||!u.length)},empty:function(){return u=[],this},disable:function(){return u=l=r=t,this},disabled:function(){return!u},lock:function(){return l=t,r||p.disable(),this},locked:function(){return!l},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!u||i&&!l||(n?l.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},b.extend({Deferred:function(e){var t=[["resolve","done",b.Callbacks("once memory"),"resolved"],["reject","fail",b.Callbacks("once memory"),"rejected"],["notify","progress",b.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return b.Deferred(function(n){b.each(t,function(t,o){var a=o[0],s=b.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&b.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?b.extend(e,r):r}},i={};return r.pipe=r.then,b.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=h.call(arguments),r=n.length,i=1!==r||e&&b.isFunction(e.promise)?r:0,o=1===i?e:b.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?h.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,u,l;if(r>1)for(s=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&b.isFunction(n[t].promise)?n[t].promise().done(a(t,l,n)).fail(o.reject).progress(a(t,u,s)):--i;return i||o.resolveWith(l,n),o.promise()}}),b.support=function(){var t,n,r,a,s,u,l,c,p,f,d=o.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],!n||!r||!n.length)return{};s=o.createElement("select"),l=s.appendChild(o.createElement("option")),a=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={getSetAttribute:"t"!==d.className,leadingWhitespace:3===d.firstChild.nodeType,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:"/a"===r.getAttribute("href"),opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:!!a.value,optSelected:l.selected,enctype:!!o.createElement("form").enctype,html5Clone:"<:nav></:nav>"!==o.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===o.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},a.checked=!0,t.noCloneChecked=a.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!l.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}a=o.createElement("input"),a.setAttribute("value",""),t.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),t.radioValue="t"===a.value,a.setAttribute("checked","t"),a.setAttribute("name","t"),u=o.createDocumentFragment(),u.appendChild(a),t.appendChecked=a.checked,t.checkClone=u.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;return d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip,b(function(){var n,r,a,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",u=o.getElementsByTagName("body")[0];u&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",u.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",a=d.getElementsByTagName("td"),a[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===a[0].offsetHeight,a[0].style.display="",a[1].style.display="none",t.reliableHiddenOffsets=p&&0===a[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%;",t.boxSizing=4===d.offsetWidth,t.doesNotIncludeMarginInBodyOffset=1!==u.offsetTop,e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(o.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&&(u.style.zoom=1)),u.removeChild(n),n=d=a=r=null)}),n=s=u=l=r=a=null,t}();var O=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,B=/([A-Z])/g;function P(e,n,r,i){if(b.acceptData(e)){var o,a,s=b.expando,u="string"==typeof n,l=e.nodeType,p=l?b.cache:e,f=l?e[s]:e[s]&&s;if(f&&p[f]&&(i||p[f].data)||!u||r!==t)return f||(l?e[s]=f=c.pop()||b.guid++:f=s),p[f]||(p[f]={},l||(p[f].toJSON=b.noop)),("object"==typeof n||"function"==typeof n)&&(i?p[f]=b.extend(p[f],n):p[f].data=b.extend(p[f].data,n)),o=p[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[b.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[b.camelCase(n)])):a=o,a}}function R(e,t,n){if(b.acceptData(e)){var r,i,o,a=e.nodeType,s=a?b.cache:e,u=a?e[b.expando]:b.expando;if(s[u]){if(t&&(o=n?s[u]:s[u].data)){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in o?t=[t]:(t=b.camelCase(t),t=t in o?[t]:t.split(" "));for(r=0,i=t.length;i>r;r++)delete o[t[r]];if(!(n?$:b.isEmptyObject)(o))return}(n||(delete s[u].data,$(s[u])))&&(a?b.cleanData([e],!0):b.support.deleteExpando||s!=s.window?delete s[u]:s[u]=null)}}}b.extend({cache:{},expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?b.cache[e[b.expando]]:e[b.expando],!!e&&!$(e)},data:function(e,t,n){return P(e,t,n)},removeData:function(e,t){return R(e,t)},_data:function(e,t,n){return P(e,t,n,!0)},_removeData:function(e,t){return R(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&b.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),b.fn.extend({data:function(e,n){var r,i,o=this[0],a=0,s=null;if(e===t){if(this.length&&(s=b.data(o),1===o.nodeType&&!b._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>a;a++)i=r[a].name,i.indexOf("data-")||(i=b.camelCase(i.slice(5)),W(o,i,s[i]));b._data(o,"parsedAttrs",!0)}return s}return"object"==typeof e?this.each(function(){b.data(this,e)}):b.access(this,function(n){return n===t?o?W(o,e,b.data(o,e)):null:(this.each(function(){b.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function W(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(B,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:O.test(r)?b.parseJSON(r):r}catch(o){}b.data(e,n,r)}else r=t}return r}function $(e){var t;for(t in e)if(("data"!==t||!b.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}b.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=b._data(e,n),r&&(!i||b.isArray(r)?i=b._data(e,n,b.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=b.queue(e,t),r=n.length,i=n.shift(),o=b._queueHooks(e,t),a=function(){b.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,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 b._data(e,n)||b._data(e,n,{empty:b.Callbacks("once memory").add(function(){b._removeData(e,t+"queue"),b._removeData(e,n)})})}}),b.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?b.queue(this[0],e):n===t?this:this.each(function(){var t=b.queue(this,e,n);b._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&b.dequeue(this,e)})},dequeue:function(e){return this.each(function(){b.dequeue(this,e)})},delay:function(e,t){return e=b.fx?b.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=b.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=b._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var I,z,X=/[\t\r\n]/g,U=/\r/g,V=/^(?:input|select|textarea|button|object)$/i,Y=/^(?:a|area)$/i,J=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,G=/^(?:checked|selected)$/i,Q=b.support.getSetAttribute,K=b.support.input;b.fn.extend({attr:function(e,t){return b.access(this,b.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,t){return b.access(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return e=b.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,u="string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=b.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?b.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return b.isFunction(e)?this.each(function(n){b(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=b(this),u=t,l=e.match(w)||[];while(o=l[a++])u=r?u:!s.hasClass(o),s[u?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&b._data(this,"__className__",this.className),this.className=this.className||e===!1?"":b._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(X," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=b.isFunction(e),this.each(function(n){var o,a=b(this);1===this.nodeType&&(o=i?e.call(this,n,a.val()):e,null==o?o="":"number"==typeof o?o+="":b.isArray(o)&&(o=b.map(o,function(e){return null==e?"":e+""})),r=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=b.valHooks[o.type]||b.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(U,""):null==n?"":n)}}}),b.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value: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,u=0>i?s:o?i:0;for(;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(b.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&b.nodeName(n.parentNode,"optgroup"))){if(t=b(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=b.makeArray(t);return b(e).find("option").each(function(){this.selected=b.inArray(b(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var o,a,s,u=e.nodeType;if(e&&3!==u&&8!==u&&2!==u)return typeof e.getAttribute===i?b.prop(e,n,r):(a=1!==u||!b.isXMLDoc(e),a&&(n=n.toLowerCase(),o=b.attrHooks[n]||(J.test(n)?z:I)),r===t?o&&a&&"get"in o&&null!==(s=o.get(e,n))?s:(typeof e.getAttribute!==i&&(s=e.getAttribute(n)),null==s?t:s):null!==r?o&&a&&"set"in o&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r):(b.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=b.propFix[n]||n,J.test(n)?!Q&&G.test(n)?e[b.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:b.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!b.support.radioValue&&"radio"===t&&b.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!b.isXMLDoc(e),a&&(n=b.propFix[n]||n,o=b.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 n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):V.test(e.nodeName)||Y.test(e.nodeName)&&e.href?0:t}}}}),z={get:function(e,n){var r=b.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?K&&Q?null!=i:G.test(n)?e[b.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?b.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&b.propFix[n]||n,n):e[b.camelCase("default-"+n)]=e[n]=!0,n}},K&&Q||(b.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return b.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t},set:function(e,n,r){return b.nodeName(e,"input")?(e.defaultValue=n,t):I&&I.set(e,n,r)}}),Q||(I=b.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},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}},b.attrHooks.contenteditable={get:I.get,set:function(e,t,n){I.set(e,""===t?!1:t,n)}},b.each(["width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),b.support.hrefNormalized||(b.each(["href","src","width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),b.each(["href","src"],function(e,t){b.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),b.support.style||(b.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),b.support.optSelected||(b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),b.support.enctype||(b.propFix.enctype="encoding"),b.support.checkOn||b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,n){return b.isArray(n)?e.checked=b.inArray(b(e).val(),n)>=0:t}})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}b.event={global:{},add:function(e,n,r,o,a){var s,u,l,c,p,f,d,h,g,m,y,v=b._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=b.guid++),(u=v.events)||(u=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof b===i||e&&b.event.triggered===e.type?t:b.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(w)||[""],l=n.length;while(l--)s=rt.exec(n[l])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),p=b.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=b.event.special[g]||{},d=b.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&b.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=u[g])||(h=u[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),b.event.global[g]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,p,f,d,h,g,m=b.hasData(e)&&b._data(e);if(m&&(c=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(s=rt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=b.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),u=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));u&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||b.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(c)&&(delete m.handle,b._removeData(e,"events"))}},trigger:function(n,r,i,a){var s,u,l,c,p,f,d,h=[i||o],g=y.call(n,"type")?n.type:n,m=y.call(n,"namespace")?n.namespace.split("."):[];if(l=f=i=i||o,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+b.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),u=0>g.indexOf(":")&&"on"+g,n=n[b.expando]?n:new b.Event(g,"object"==typeof n&&n),n.isTrigger=!0,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]:b.makeArray(r,[n]),p=b.event.special[g]||{},a||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!a&&!p.noBubble&&!b.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(l=l.parentNode);l;l=l.parentNode)h.push(l),f=l;f===(i.ownerDocument||o)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((l=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(b._data(l,"events")||{})[n.type]&&b._data(l,"handle"),s&&s.apply(l,r),s=u&&l[u],s&&b.acceptData(l)&&s.apply&&s.apply(l,r)===!1&&n.preventDefault();if(n.type=g,!(a||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===g&&b.nodeName(i,"a")||!b.acceptData(i)||!u||!i[g]||b.isWindow(i))){f=i[u],f&&(i[u]=null),b.event.triggered=g;try{i[g]()}catch(v){}b.event.triggered=t,f&&(i[u]=f)}return n.result}},dispatch:function(e){e=b.event.fix(e);var n,r,i,o,a,s=[],u=h.call(arguments),l=(b._data(this,"events")||{})[e.type]||[],c=b.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=b.event.handlers.call(this,e,l),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=((b.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,u),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=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==e.type)){for(o=[],a=0;u>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?b(r,this).index(l)>=0:b.find(r,this,null,[l]).length),o[r]&&o.push(i);o.length&&s.push({elem:l,handlers:o})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[b.expando])return e;var t,n,r,i=e.type,a=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 b.Event(a),t=r.length;while(t--)n=r[t],e[n]=a[n];return e.target||(e.target=a.srcElement||o),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,a):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,a,s=n.button,u=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||o,a=i.documentElement,r=i.body,e.pageX=n.clientX+(a&&a.scrollLeft||r&&r.scrollLeft||0)-(a&&a.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(a&&a.scrollTop||r&&r.scrollTop||0)-(a&&a.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&u&&(e.relatedTarget=u===e.target?n.toElement:u),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return b.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==o.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===o.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=b.extend(new b.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?b.event.trigger(i,null,t):b.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},b.removeEvent=o.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))},b.Event=function(e,n){return this instanceof b.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&&b.extend(this,n),this.timeStamp=e&&e.timeStamp||b.now(),this[b.expando]=!0,t):new b.Event(e,n)},b.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()}},b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){b.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;
+return(!i||i!==r&&!b.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),b.support.submitBubbles||(b.event.special.submit={setup:function(){return b.nodeName(this,"form")?!1:(b.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=b.nodeName(n,"input")||b.nodeName(n,"button")?n.form:t;r&&!b._data(r,"submitBubbles")&&(b.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),b._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&b.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return b.nodeName(this,"form")?!1:(b.event.remove(this,"._submit"),t)}}),b.support.changeBubbles||(b.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(b.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),b.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),b.event.simulate("change",this,e,!0)})),!1):(b.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!b._data(t,"changeBubbles")&&(b.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||b.event.simulate("change",this.parentNode,e,!0)}),b._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 b.event.remove(this,"._change"),!Z.test(this.nodeName)}}),b.support.focusinBubbles||b.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){b.event.simulate(t,e.target,b.event.fix(e),!0)};b.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),b.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 b().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=b.guid++)),this.each(function(){b.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,b(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(){b.event.remove(this,e,r,n)})},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)},trigger:function(e,t){return this.each(function(){b.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?b.event.trigger(e,n,r,!0):t}}),function(e,t){var n,r,i,o,a,s,u,l,c,p,f,d,h,g,m,y,v,x="sizzle"+-new Date,w=e.document,T={},N=0,C=0,k=it(),E=it(),S=it(),A=typeof t,j=1<<31,D=[],L=D.pop,H=D.push,q=D.slice,M=D.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},_="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=F.replace("w","w#"),B="([*^$|!~]?=)",P="\\["+_+"*("+F+")"+_+"*(?:"+B+_+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+O+")|)|)"+_+"*\\]",R=":("+F+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+P.replace(3,8)+")*)|.*)\\)|)",W=RegExp("^"+_+"+|((?:^|[^\\\\])(?:\\\\.)*)"+_+"+$","g"),$=RegExp("^"+_+"*,"+_+"*"),I=RegExp("^"+_+"*([\\x20\\t\\r\\n\\f>+~])"+_+"*"),z=RegExp(R),X=RegExp("^"+O+"$"),U={ID:RegExp("^#("+F+")"),CLASS:RegExp("^\\.("+F+")"),NAME:RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:RegExp("^("+F.replace("w","w*")+")"),ATTR:RegExp("^"+P),PSEUDO:RegExp("^"+R),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+_+"*(even|odd|(([+-]|)(\\d*)n|)"+_+"*(?:([+-]|)"+_+"*(\\d+)|))"+_+"*\\)|)","i"),needsContext:RegExp("^"+_+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+_+"*((?:-\\d)?\\d*)"+_+"*\\)|)(?=[^-]|$)","i")},V=/[\x20\t\r\n\f]*[+~]/,Y=/^[^{]+\{\s*\[native code/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,G=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,K=/'|\\/g,Z=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,et=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{q.call(w.documentElement.childNodes,0)[0].nodeType}catch(nt){q=function(e){var t,n=[];while(t=this[e++])n.push(t);return n}}function rt(e){return Y.test(e+"")}function it(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>i.cacheLength&&delete e[t.shift()],e[n]=r}}function ot(e){return e[x]=!0,e}function at(e){var t=p.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function st(e,t,n,r){var i,o,a,s,u,l,f,g,m,v;if((t?t.ownerDocument||t:w)!==p&&c(t),t=t||p,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!d&&!r){if(i=J.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&y(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return H.apply(n,q.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&T.getByClassName&&t.getElementsByClassName)return H.apply(n,q.call(t.getElementsByClassName(a),0)),n}if(T.qsa&&!h.test(e)){if(f=!0,g=x,m=t,v=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){l=ft(e),(f=t.getAttribute("id"))?g=f.replace(K,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=l.length;while(u--)l[u]=g+dt(l[u]);m=V.test(e)&&t.parentNode||t,v=l.join(",")}if(v)try{return H.apply(n,q.call(m.querySelectorAll(v),0)),n}catch(b){}finally{f||t.removeAttribute("id")}}}return wt(e.replace(W,"$1"),t,n,r)}a=st.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},c=st.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==p&&9===n.nodeType&&n.documentElement?(p=n,f=n.documentElement,d=a(n),T.tagNameNoComments=at(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),T.attributes=at(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),T.getByClassName=at(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),T.getByName=at(function(e){e.id=x+0,e.innerHTML="<a name='"+x+"'></a><div name='"+x+"'></div>",f.insertBefore(e,f.firstChild);var t=n.getElementsByName&&n.getElementsByName(x).length===2+n.getElementsByName(x+0).length;return T.getIdNotName=!n.getElementById(x),f.removeChild(e),t}),i.attrHandle=at(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==A&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},T.getIdNotName?(i.find.ID=function(e,t){if(typeof t.getElementById!==A&&!d){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(i.find.ID=function(e,n){if(typeof n.getElementById!==A&&!d){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==A&&r.getAttributeNode("id").value===e?[r]:t:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==A&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=T.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==A?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},i.find.NAME=T.getByName&&function(e,n){return typeof n.getElementsByName!==A?n.getElementsByName(name):t},i.find.CLASS=T.getByClassName&&function(e,n){return typeof n.getElementsByClassName===A||d?t:n.getElementsByClassName(e)},g=[],h=[":focus"],(T.qsa=rt(n.querySelectorAll))&&(at(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||h.push("\\["+_+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){e.innerHTML="<input type='hidden' i=''/>",e.querySelectorAll("[i^='']").length&&h.push("[*^$]="+_+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(T.matchesSelector=rt(m=f.matchesSelector||f.mozMatchesSelector||f.webkitMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){T.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",R)}),h=RegExp(h.join("|")),g=RegExp(g.join("|")),y=rt(f.contains)||f.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},v=f.compareDocumentPosition?function(e,t){var r;return e===t?(u=!0,0):(r=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&r||e.parentNode&&11===e.parentNode.nodeType?e===n||y(w,e)?-1:t===n||y(w,t)?1:0:4&r?-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 u=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:0;if(o===a)return ut(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?ut(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},u=!1,[0,0].sort(v),T.detectDuplicates=u,p):p},st.matches=function(e,t){return st(e,null,null,t)},st.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Z,"='$1']"),!(!T.matchesSelector||d||g&&g.test(t)||h.test(t)))try{var n=m.call(e,t);if(n||T.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return st(t,p,null,[e]).length>0},st.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},st.attr=function(e,t){var n;return(e.ownerDocument||e)!==p&&c(e),d||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):d||T.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},st.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},st.uniqueSort=function(e){var t,n=[],r=1,i=0;if(u=!T.detectDuplicates,e.sort(v),u){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e};function ut(e,t){var n=t&&e,r=n&&(~t.sourceIndex||j)-(~e.sourceIndex||j);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ct(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pt(e){return ot(function(t){return t=+t,ot(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]))})})}o=st.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+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=st.selectors={cacheLength:50,createPseudo:ot,match:U,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),"~="===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]||st.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]&&st.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return U.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&z.test(n)&&(t=ft(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(et,tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[e+" "];return t||(t=RegExp("(^|"+_+")"+e+"("+_+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==A&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=st.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,u){var l,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!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[x]||(m[x]={}),l=c[e]||[],d=l[0]===N&&l[1],f=l[0]===N&&l[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]=[N,d,f];break}}else if(v&&(l=(t[x]||(t[x]={}))[e])&&l[0]===N)f=l[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[x]||(p[x]={}))[e]=[N,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||st.error("unsupported pseudo: "+e);return r[x]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ot(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=M.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ot(function(e){var t=[],n=[],r=s(e.replace(W,"$1"));return r[x]?ot(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:ot(function(e){return function(t){return st(e,t).length>0}}),contains:ot(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ot(function(e){return X.test(e||"")||st.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=d?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.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===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.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!i.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return G.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:pt(function(){return[0]}),last:pt(function(e,t){return[t-1]}),eq:pt(function(e,t,n){return[0>n?n+t:n]}),even:pt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:pt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:pt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:pt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[n]=lt(n);for(n in{submit:!0,reset:!0})i.pseudos[n]=ct(n);function ft(e,t){var n,r,o,a,s,u,l,c=E[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=i.preFilter;while(s){(!n||(r=$.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(o=[])),n=!1,(r=I.exec(s))&&(n=r.shift(),o.push({value:n,type:r[0].replace(W," ")}),s=s.slice(n.length));for(a in i.filter)!(r=U[a].exec(s))||l[a]&&!(r=l[a](r))||(n=r.shift(),o.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?st.error(e):E(e,u).slice(0)}function dt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function ht(e,t,n){var i=t.dir,o=n&&"parentNode"===i,a=C++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,s){var u,l,c,p=N+" "+a;if(s){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[x]||(t[x]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,s)||r,l[1]===!0)return!0}}function gt(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 mt(e,t,n,r,i){var o,a=[],s=0,u=e.length,l=null!=t;for(;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function yt(e,t,n,r,i,o){return r&&!r[x]&&(r=yt(r)),i&&!i[x]&&(i=yt(i,o)),ot(function(o,a,s,u){var l,c,p,f=[],d=[],h=a.length,g=o||xt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:mt(g,f,e,s,u),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,u),r){l=mt(y,d),r(l,[],s,u),c=l.length;while(c--)(p=l[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?M.call(o,p):f[c])>-1&&(o[l]=!(a[l]=p))}}else y=mt(y===a?y.splice(h,y.length):y),i?i(null,a,y,u):H.apply(a,y)})}function vt(e){var t,n,r,o=e.length,a=i.relative[e[0].type],s=a||i.relative[" "],u=a?1:0,c=ht(function(e){return e===t},s,!0),p=ht(function(e){return M.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>u;u++)if(n=i.relative[e[u].type])f=[ht(gt(f),n)];else{if(n=i.filter[e[u].type].apply(null,e[u].matches),n[x]){for(r=++u;o>r;r++)if(i.relative[e[r].type])break;return yt(u>1&&gt(f),u>1&&dt(e.slice(0,u-1)).replace(W,"$1"),n,r>u&&vt(e.slice(u,r)),o>r&&vt(e=e.slice(r)),o>r&&dt(e))}f.push(n)}return gt(f)}function bt(e,t){var n=0,o=t.length>0,a=e.length>0,s=function(s,u,c,f,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,T=l,C=s||a&&i.find.TAG("*",d&&u.parentNode||u),k=N+=null==T?1:Math.random()||.1;for(w&&(l=u!==p&&u,r=n);null!=(h=C[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,u,c)){f.push(h);break}w&&(N=k,r=++n)}o&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,o&&b!==v){g=0;while(m=t[g++])m(x,y,u,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=L.call(f));y=mt(y)}H.apply(f,y),w&&!s&&y.length>0&&v+t.length>1&&st.uniqueSort(f)}return w&&(N=k,l=T),x};return o?ot(s):s}s=st.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=ft(e)),n=t.length;while(n--)o=vt(t[n]),o[x]?r.push(o):i.push(o);o=S(e,bt(i,r))}return o};function xt(e,t,n){var r=0,i=t.length;for(;i>r;r++)st(e,t[r],n);return n}function wt(e,t,n,r){var o,a,u,l,c,p=ft(e);if(!r&&1===p.length){if(a=p[0]=p[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&!d&&i.relative[a[1].type]){if(t=i.find.ID(u.matches[0].replace(et,tt),t)[0],!t)return n;e=e.slice(a.shift().value.length)}o=U.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],i.relative[l=u.type])break;if((c=i.find[l])&&(r=c(u.matches[0].replace(et,tt),V.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=r.length&&dt(a),!e)return H.apply(n,q.call(r,0)),n;break}}}return s(e,p)(r,t,d,n,V.test(e)),n}i.pseudos.nth=i.pseudos.eq;function Tt(){}i.filters=Tt.prototype=i.pseudos,i.setFilters=new Tt,c(),st.attr=b.attr,b.find=st,b.expr=st.selectors,b.expr[":"]=b.expr.pseudos,b.unique=st.uniqueSort,b.text=st.getText,b.isXMLDoc=st.isXML,b.contains=st.contains}(e);var at=/Until$/,st=/^(?:parents|prev(?:Until|All))/,ut=/^.[^:#\[\.,]*$/,lt=b.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};b.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return r=this,this.pushStack(b(e).filter(function(){for(t=0;i>t;t++)if(b.contains(r[t],this))return!0}));for(n=[],t=0;i>t;t++)b.find(e,this[t],n);return n=this.pushStack(i>1?b.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=b(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(b.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1))},filter:function(e){return this.pushStack(ft(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?lt.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],a=lt.test(e)||"string"!=typeof e?b(e,t||this.context):0;for(;i>r;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&11!==n.nodeType){if(a?a.index(n)>-1:b.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}}return this.pushStack(o.length>1?b.unique(o):o)},index:function(e){return e?"string"==typeof e?b.inArray(this[0],b(e)):b.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?b(e,t):b.makeArray(e&&e.nodeType?[e]:e),r=b.merge(this.get(),n);return this.pushStack(b.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),b.fn.andSelf=b.fn.addBack;function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}b.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(e,t,n){return b.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(e,t,n){return b.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return b.dir(e,"previousSibling",n)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.merge([],e.childNodes)}},function(e,t){b.fn[e]=function(n,r){var i=b.map(this,t,n);return at.test(e)||(r=n),r&&"string"==typeof r&&(i=b.filter(r,i)),i=this.length>1&&!ct[e]?b.unique(i):i,this.length>1&&st.test(e)&&(i=i.reverse()),this.pushStack(i)}}),b.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?b.find.matchesSelector(t[0],e)?[t[0]]:[]:b.find.matches(e,t)},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!b(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(t=t||0,b.isFunction(t))return b.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=b.grep(e,function(e){return 1===e.nodeType});if(ut.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.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,Nt=/^(?:checkbox|radio)$/i,Ct=/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:b.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(o),Dt=jt.appendChild(o.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,b.fn.extend({text:function(e){return b.access(this,function(e){return e===t?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e))return this.each(function(t){b(this).wrapAll(e.call(this,t))});if(this[0]){var t=b(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 b.isFunction(e)?this.each(function(t){b(this).wrapInner(e.call(this,t))}):this.each(function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=b.isFunction(e);return this.each(function(n){b(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){b.nodeName(this,"body")||b(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=0;for(;null!=(n=this[r]);r++)(!e||b.filter(e,[n]).length>0)&&(t||1!==n.nodeType||b.cleanData(Ot(n)),n.parentNode&&(t&&b.contains(n.ownerDocument,n)&&Mt(Ot(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&b.cleanData(Ot(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&b.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 b.clone(this,e,t)})},html:function(e){return b.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)||!b.support.htmlSerialize&&mt.test(e)||!b.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&&(b.cleanData(Ot(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=b.isFunction(e);return t||"string"==typeof e||(e=b(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;n&&(b(this).remove(),n.insertBefore(e,t))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=f.apply([],e);var i,o,a,s,u,l,c=0,p=this.length,d=this,h=p-1,g=e[0],m=b.isFunction(g);if(m||!(1>=p||"string"!=typeof g||b.support.checkClone)&&Ct.test(g))return this.each(function(i){var o=d.eq(i);m&&(e[0]=g.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(p&&(l=b.buildFragment(e,this[0].ownerDocument,!1,this),i=l.firstChild,1===l.childNodes.length&&(l=i),i)){for(n=n&&b.nodeName(i,"tr"),s=b.map(Ot(l,"script"),Ht),a=s.length;p>c;c++)o=l,c!==h&&(o=b.clone(o,!0,!0),a&&b.merge(s,Ot(o,"script"))),r.call(n&&b.nodeName(this[c],"table")?Lt(this[c],"tbody"):this[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,b.map(s,qt),c=0;a>c;c++)o=s[c],kt.test(o.type||"")&&!b._data(o,"globalEval")&&b.contains(u,o)&&(o.src?b.ajax({url:o.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):b.globalEval((o.text||o.textContent||o.innerHTML||"").replace(St,"")));l=i=null}return this}});function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function Ht(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Mt(e,t){var n,r=0;for(;null!=(n=e[r]);r++)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function _t(e,t){if(1===t.nodeType&&b.hasData(e)){var n,r,i,o=b._data(e),a=b._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++)b.event.add(t,n,s[n][r])}a.data&&(a.data=b.extend({},a.data))}}function Ft(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Nt.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)}}b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){b.fn[e]=function(e){var n,r=0,i=[],o=b(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),b(o[r])[t](n),d.apply(i,n.get());return this.pushStack(i)}});function Ot(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||b.nodeName(o,n)?s.push(o):b.merge(s,Ot(o,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],s):s}function Bt(e){Nt.test(e.type)&&(e.defaultChecked=e.checked)}b.extend({clone:function(e,t,n){var r,i,o,a,s,u=b.contains(e.ownerDocument,e);if(b.support.html5Clone||b.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(b.support.noCloneEvent&&b.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||b.isXMLDoc(e)))for(r=Ot(o),s=Ot(e),a=0;null!=(i=s[a]);++a)r[a]&&Ft(i,r[a]);if(t)if(n)for(s=s||Ot(e),r=r||Ot(o),a=0;null!=(i=s[a]);a++)_t(i,r[a]);else _t(e,o);return r=Ot(o,"script"),r.length>0&&Mt(r,!u&&Ot(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,u,l,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===b.type(o))b.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),u=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[u]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!b.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!b.support.tbody){o="table"!==u||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)b.nodeName(l=o.childNodes[i],"tbody")&&!l.childNodes.length&&o.removeChild(l)
+}b.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),b.support.appendChecked||b.grep(Ot(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===b.inArray(o,r))&&(a=b.contains(o.ownerDocument,o),s=Ot(f.appendChild(o),"script"),a&&Mt(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,u=b.expando,l=b.cache,p=b.support.deleteExpando,f=b.event.special;for(;null!=(n=e[s]);s++)if((t||b.acceptData(n))&&(o=n[u],a=o&&l[o])){if(a.events)for(r in a.events)f[r]?b.event.remove(n,r):b.removeEvent(n,r,a.handle);l[o]&&(delete l[o],p?delete n[u]:typeof n.removeAttribute!==i?n.removeAttribute(u):n[u]=null,c.push(o))}}});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("^("+x+")(.*)$","i"),Yt=RegExp("^("+x+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+x+")","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"===b.css(e,"display")||!b.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]=b._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=b._data(r,"olddisplay",un(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&b._data(r,"olddisplay",i?n:b.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}b.fn.extend({css:function(e,n){return b.access(this,function(e,n,r){var i,o,a={},s=0;if(b.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=b.css(e,n[s],!1,o);return a}return r!==t?b.style(e,n,r):b.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?b(this).show():b(this).hide()})}}),b.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,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=b.camelCase(n),l=e.style;if(n=b.cssProps[u]||(b.cssProps[u]=tn(l,u)),s=b.cssHooks[n]||b.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(b.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||b.cssNumber[u]||(r+="px"),b.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=b.camelCase(n);return n=b.cssProps[u]||(b.cssProps[u]=tn(e.style,u)),s=b.cssHooks[n]||b.cssHooks[u],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||b.isNumeric(o)?o||0:a):a},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}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||b.contains(e.ownerDocument,e)||(u=b.style(e,n)),Yt.test(u)&&Ut.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):o.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),Yt.test(u)&&!zt.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u});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+=b.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=b.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=b.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=b.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=b.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=b.support.boxSizing&&"border-box"===b.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&&(b.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function un(e){var t=o,n=Gt[e];return n||(n=ln(e,t),"none"!==n&&n||(Pt=(Pt||b("<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=ln(e,t),Pt.detach()),Gt[e]=n),n}function ln(e,t){var n=b(t.createElement(e)).appendTo(t.body),r=b.css(n[0],"display");return n.remove(),r}b.each(["height","width"],function(e,n){b.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(b.css(e,"display"))?b.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,b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,i),i):0)}}}),b.support.opacity||(b.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=b.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===b.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),b(function(){b.support.reliableMarginRight||(b.cssHooks.marginRight={get:function(e,n){return n?b.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!b.support.pixelPosition&&b.fn.position&&b.each(["top","left"],function(e,n){b.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?b(e).position()[n]+"px":r):t}}})}),b.expr&&b.expr.filters&&(b.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!b.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||b.css(e,"display"))},b.expr.filters.visible=function(e){return!b.expr.filters.hidden(e)}),b.each({margin:"",padding:"",border:"Width"},function(e,t){b.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)||(b.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;b.fn.extend({serialize:function(){return b.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=b.prop(this,"elements");return e?b.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!b(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Nt.test(e))}).map(function(e,t){var n=b(this).val();return null==n?null:b.isArray(n)?b.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),b.param=function(e,n){var r,i=[],o=function(e,t){t=b.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=b.ajaxSettings&&b.ajaxSettings.traditional),b.isArray(e)||e.jquery&&!b.isPlainObject(e))b.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(b.isArray(t))b.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"!==b.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}b.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){b.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),b.fn.hover=function(e,t){return this.mouseenter(e).mouseleave(t||e)};var mn,yn,vn=b.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Nn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Cn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=b.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=a.href}catch(Ln){yn=o.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(w)||[];if(b.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(u){var l;return o[u]=!0,b.each(e[u]||[],function(e,u){var c=u(n,r,i);return"string"!=typeof c||a||o[c]?a?!(l=c):t:(n.dataTypes.unshift(c),s(c),!1)}),l}return s(n.dataTypes[0])||!o["*"]&&s("*")}function Mn(e,n){var r,i,o=b.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&b.extend(!0,e,r),e}b.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,u=e.indexOf(" ");return u>=0&&(i=e.slice(u,e.length),e=e.slice(0,u)),b.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&b.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?b("<div>").append(b.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},b.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){b.fn[t]=function(e){return this.on(t,e)}}),b.each(["get","post"],function(e,n){b[n]=function(e,r,i,o){return b.isFunction(r)&&(o=o||i,i=r,r=t),b.ajax({url:e,type:n,dataType:o,data:r,success:i})}}),b.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Nn.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"},converters:{"* text":e.String,"text html":!0,"text json":b.parseJSON,"text xml":b.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Mn(Mn(e,b.ajaxSettings),t):Mn(b.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,u,l,c,p=b.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?b(f):b.event,h=b.Deferred(),g=b.Callbacks("once memory"),m=p.statusCode||{},y={},v={},x=0,T="canceled",N={readyState:0,getResponseHeader:function(e){var t;if(2===x){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===x?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return x||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>x)for(t in e)m[t]=[m[t],e[t]];else N.always(e[N.status]);return this},abort:function(e){var t=e||T;return l&&l.abort(t),k(0,t),this}};if(h.promise(N).complete=g.add,N.success=N.done,N.error=N.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=b.trim(p.dataType||"*").toLowerCase().match(w)||[""],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=b.param(p.data,p.traditional)),qn(An,p,n,N),2===x)return N;u=p.global,u&&0===b.active++&&b.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Cn.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&&(b.lastModified[o]&&N.setRequestHeader("If-Modified-Since",b.lastModified[o]),b.etag[o]&&N.setRequestHeader("If-None-Match",b.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&N.setRequestHeader("Content-Type",p.contentType),N.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)N.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,N,p)===!1||2===x))return N.abort();T="abort";for(i in{success:1,error:1,complete:1})N[i](p[i]);if(l=qn(jn,p,n,N)){N.readyState=1,u&&d.trigger("ajaxSend",[N,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){N.abort("timeout")},p.timeout));try{x=1,l.send(y,k)}catch(C){if(!(2>x))throw C;k(-1,C)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,C=n;2!==x&&(x=2,s&&clearTimeout(s),l=t,a=i||"",N.readyState=e>0?4:0,r&&(w=_n(p,N,r)),e>=200&&300>e||304===e?(p.ifModified&&(T=N.getResponseHeader("Last-Modified"),T&&(b.lastModified[o]=T),T=N.getResponseHeader("etag"),T&&(b.etag[o]=T)),204===e?(c=!0,C="nocontent"):304===e?(c=!0,C="notmodified"):(c=Fn(p,w),C=c.state,y=c.data,v=c.error,c=!v)):(v=C,(e||!C)&&(C="error",0>e&&(e=0))),N.status=e,N.statusText=(n||C)+"",c?h.resolveWith(f,[y,C,N]):h.rejectWith(f,[N,C,v]),N.statusCode(m),m=t,u&&d.trigger(c?"ajaxSuccess":"ajaxError",[N,p,c?y:v]),g.fireWith(f,[N,C]),u&&(d.trigger("ajaxComplete",[N,p]),--b.active||b.event.trigger("ajaxStop")))}return N},getScript:function(e,n){return b.get(e,t,n,"script")},getJSON:function(e,t,n){return b.get(e,t,n,"json")}});function _n(e,n,r){var i,o,a,s,u=e.contents,l=e.dataTypes,c=e.responseFields;for(s in c)s in r&&(n[c[s]]=r[s]);while("*"===l[0])l.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in u)if(u[s]&&u[s].test(o)){l.unshift(s);break}if(l[0]in r)a=l[0];else{for(s in r){if(!l[0]||e.converters[s+" "+l[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==l[0]&&l.unshift(a),r[a]):t}function Fn(e,t){var n,r,i,o,a={},s=0,u=e.dataTypes.slice(),l=u[0];if(e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u[1])for(i in e.converters)a[i.toLowerCase()]=e.converters[i];for(;r=u[++s];)if("*"!==r){if("*"!==l&&l!==r){if(i=a[l+" "+r]||a["* "+r],!i)for(n in a)if(o=n.split(" "),o[1]===r&&(i=a[l+" "+o[0]]||a["* "+o[0]])){i===!0?i=a[n]:a[n]!==!0&&(r=o[0],u.splice(s--,0,r));break}if(i!==!0)if(i&&e["throws"])t=i(t);else try{t=i(t)}catch(c){return{state:"parsererror",error:i?c:"No conversion from "+l+" to "+r}}}l=r}return{state:"success",data:t}}b.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return b.globalEval(e),e}}}),b.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),b.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=o.head||b("head")[0]||o.documentElement;return{send:function(t,i){n=o.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 On=[],Bn=/(=)\?(?=&|$)|\?\?/;b.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=On.pop()||b.expando+"_"+vn++;return this[e]=!0,e}}),b.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,u=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 u||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=b.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,u?n[u]=n[u].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||b.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,On.push(o)),s&&b.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){}}b.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=b.ajaxSettings.xhr(),b.support.cors=!!Rn&&"withCredentials"in Rn,Rn=b.support.ajax=!!Rn,Rn&&b.ajaxTransport(function(n){if(!n.crossDomain||b.support.cors){var r;return{send:function(i,o){var a,s,u=n.xhr();if(n.username?u.open(n.type,n.url,n.async,n.username,n.password):u.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)u[s]=n.xhrFields[s];n.mimeType&&u.overrideMimeType&&u.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)u.setRequestHeader(s,i[s])}catch(l){}u.send(n.hasContent&&n.data||null),r=function(e,i){var s,l,c,p;try{if(r&&(i||4===u.readyState))if(r=t,a&&(u.onreadystatechange=b.noop,$n&&delete Pn[a]),i)4!==u.readyState&&u.abort();else{p={},s=u.status,l=u.getAllResponseHeaders(),"string"==typeof u.responseText&&(p.text=u.responseText);try{c=u.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,l)},n.async?4===u.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},b(e).unload($n)),Pn[a]=r),u.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+x+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n,r,i=this.createTween(e,t),o=Yn.exec(t),a=i.cur(),s=+a||0,u=1,l=20;if(o){if(n=+o[2],r=o[3]||(b.cssNumber[e]?"":"px"),"px"!==r&&s){s=b.css(i.elem,e,!0)||n||1;do u=u||".5",s/=u,b.style(i.elem,e,s+r);while(u!==(u=i.cur()/a)&&1!==u&&--l)}i.unit=r,i.start=s,i.end=o[1]?s+(o[1]+1)*n:n}return i}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=b.now()}function Zn(e,t){b.each(t,function(t,n){var r=(Qn[t]||[]).concat(Qn["*"]),i=0,o=r.length;for(;o>i;i++)if(r[i].call(e,t,n))return})}function er(e,t,n){var r,i,o=0,a=Gn.length,s=b.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;for(;u>a;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),1>o&&u?n:(s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:b.extend({},t),opts:b.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=b.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?s.resolveWith(e,[l,t]):s.rejectWith(e,[l,t]),this}}),c=l.props;for(tr(c,l.opts.specialEasing);a>o;o++)if(r=Gn[o].call(l,e,c,l.opts))return r;return Zn(l,c),b.isFunction(l.opts.start)&&l.opts.start.call(e,l),b.fx.timer(b.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function tr(e,t){var n,r,i,o,a;for(i in e)if(r=b.camelCase(i),o=t[r],n=e[i],b.isArray(n)&&(o=n[1],n=e[i]=n[0]),i!==r&&(e[r]=n,delete e[i]),a=b.cssHooks[r],a&&"expand"in a){n=a.expand(n),delete e[r];for(i in n)i in e||(e[i]=n[i],t[i]=o)}else t[r]=o}b.Animation=b.extend(er,{tweener:function(e,t){b.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,u,l,c,p,f=this,d=e.style,h={},g=[],m=e.nodeType&&nn(e);n.queue||(c=b._queueHooks(e,"fx"),null==c.unqueued&&(c.unqueued=0,p=c.empty.fire,c.empty.fire=function(){c.unqueued||p()}),c.unqueued++,f.always(function(){f.always(function(){c.unqueued--,b.queue(e,"fx").length||c.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[d.overflow,d.overflowX,d.overflowY],"inline"===b.css(e,"display")&&"none"===b.css(e,"float")&&(b.support.inlineBlockNeedsLayout&&"inline"!==un(e.nodeName)?d.zoom=1:d.display="inline-block")),n.overflow&&(d.overflow="hidden",b.support.shrinkWrapBlocks||f.always(function(){d.overflow=n.overflow[0],d.overflowX=n.overflow[1],d.overflowY=n.overflow[2]}));for(i in t)if(a=t[i],Vn.exec(a)){if(delete t[i],u=u||"toggle"===a,a===(m?"hide":"show"))continue;g.push(i)}if(o=g.length){s=b._data(e,"fxshow")||b._data(e,"fxshow",{}),"hidden"in s&&(m=s.hidden),u&&(s.hidden=!m),m?b(e).show():f.done(function(){b(e).hide()}),f.done(function(){var t;b._removeData(e,"fxshow");for(t in h)b.style(e,t,h[t])});for(i=0;o>i;i++)r=g[i],l=f.createTween(r,m?s[r]:0),h[r]=s[r]||b.style(e,r),r in s||(s[r]=l.start,m&&(l.end=l.start,l.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}b.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||(b.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?b.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=b.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){b.fx.step[e.prop]?b.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[b.cssProps[e.prop]]||b.cssHooks[e.prop])?b.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)}},b.each(["toggle","show","hide"],function(e,t){var n=b.fn[t];b.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),b.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=b.isEmptyObject(e),o=b.speed(t,n,r),a=function(){var t=er(this,b.extend({},e),o);a.finish=function(){t.stop(!0)},(i||b._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=b.timers,a=b._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)&&b.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=b._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=b.timers,a=r?r.length:0;for(n.finish=!0,b.queue(this,e,[]),i&&i.cur&&i.cur.finish&&i.cur.finish.call(this),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}b.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){b.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),b.speed=function(e,t,n){var r=e&&"object"==typeof e?b.extend({},e):{complete:n||!n&&t||b.isFunction(e)&&e,duration:e,easing:n&&t||t&&!b.isFunction(t)&&t};return r.duration=b.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in b.fx.speeds?b.fx.speeds[r.duration]:b.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){b.isFunction(r.old)&&r.old.call(this),r.queue&&b.dequeue(this,r.queue)},r},b.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},b.timers=[],b.fx=rr.prototype.init,b.fx.tick=function(){var e,n=b.timers,r=0;for(Xn=b.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||b.fx.stop(),Xn=t},b.fx.timer=function(e){e()&&b.timers.push(e)&&b.fx.start()},b.fx.interval=13,b.fx.start=function(){Un||(Un=setInterval(b.fx.tick,b.fx.interval))},b.fx.stop=function(){clearInterval(Un),Un=null},b.fx.speeds={slow:600,fast:200,_default:400},b.fx.step={},b.expr&&b.expr.filters&&(b.expr.filters.animated=function(e){return b.grep(b.timers,function(t){return e===t.elem}).length}),b.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){b.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,b.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},b.offset={setOffset:function(e,t,n){var r=b.css(e,"position");"static"===r&&(e.style.position="relative");var i=b(e),o=i.offset(),a=b.css(e,"top"),s=b.css(e,"left"),u=("absolute"===r||"fixed"===r)&&b.inArray("auto",[a,s])>-1,l={},c={},p,f;u?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),b.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(l.top=t.top-o.top+p),null!=t.left&&(l.left=t.left-o.left+f),"using"in t?t.using.call(e,l):i.css(l)}},b.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===b.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),b.nodeName(e[0],"html")||(n=e.offset()),n.top+=b.css(e[0],"borderTopWidth",!0),n.left+=b.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-b.css(r,"marginTop",!0),left:t.left-n.left-b.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||o.documentElement;while(e&&!b.nodeName(e,"html")&&"static"===b.css(e,"position"))e=e.offsetParent;return e||o.documentElement})}}),b.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);b.fn[e]=function(i){return b.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?b(a).scrollLeft():o,r?o:b(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return b.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}b.each({Height:"height",Width:"width"},function(e,n){b.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){b.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return b.access(this,function(n,r,i){var o;return b.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?b.css(n,r,s):b.style(n,r,i,s)},n,a?i:t,a,null)}})}),e.jQuery=e.$=b,"function"==typeof define&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return b})})(window);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/nabble.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,226 @@
+/* font and global ---------------------------------------------------*/
+body, input, button, textarea, select {
+	font-family: Verdana,Geneva,Helvetica,Arial,sans-serif;
+}
+.second-font, h1, h2, h3, h4, h5, h6 {
+	font-family: Arial, sans-serif;
+}
+body, table .nabble {
+	font-size: .84em;
+	padding: .8em;
+	margin:0;
+}
+
+/* color scheme ------------------------------------------------------*/
+
+/* text and link (foreground) colors -----------------*/
+.nabble,
+.nabble table {
+	color: #000000; /* black */
+}
+.nabble h1, .nabble h2, .nabble h3, .nabble h4, .nabble h5, .nabble h6 {
+	color: #333333; /* black (light) */
+}
+.nabble .important {
+	color: #cc0000; /* red (dark) */
+}
+
+.nabble .form-label,
+.nabble .weak-color {
+	color: #666666; /* gray */
+}
+
+.nabble a * { color:inherit; }
+
+.nabble a:link,
+.nabble a.not-visited-link {
+	color: #0000ee;
+}
+
+.nabble a:visited,
+.nabble a.visited-link {
+	color: #551a8b;
+}
+
+/* background colors --------------------*/
+.nabble,
+.nabble .no-bg-color {
+	background: #ffffff; /* white */
+}
+
+.nabble .light-bg-color {
+	background: #f2f2f2; /* ultra light gray */
+}
+
+.nabble .shaded-bg-color {
+	background: #eeeeee; /* gray (light) */
+}
+
+.nabble .dark-bg-color {
+	background: #dddddd; /* gray (medium) */
+}
+.nabble .highlight {
+	background: #ffff99; /* yellow */
+}
+.nabble .error-message,
+.nabble .info-message {
+	background: #ffffcc; /* yellow (light) */
+}
+
+/* border colors ------------------------*/
+.nabble .medium-border-color  {
+	border-color: #cccccc; /* gray (medium) */
+}
+.nabble .light-border-color {
+	border-color: #eeeeee; /* gray (light) */
+}
+.nabble .dark-border-color {
+	border-color: #666666; /* gray */
+}
+
+/* generic -----------------------------------------------------------*/
+.nabble a,
+.nabble table,
+.nabble input,
+.nabble textarea,
+.nabble select {
+	font-size: 1em;
+}
+.nabble h1 {
+	font-size: 1.8em;
+	font-weight: bold;
+	margin-top: .4em;
+	margin-bottom: 0.5em;
+}
+.nabble h1 a:link, .nabble h1 a:visited {
+	text-decoration: none;
+	font-weight: bold;
+}
+.nabble h2 {
+	font-size: 1.3em;
+	padding: .3em 0;
+	margin:0;
+}
+.nabble h3 {
+	font-size: 1.1em;
+	font-style:italic;
+	padding:.3em 0;
+	margin:0;
+}
+
+/*  HEADER ------------------------------------------------------------------- */
+.nabble .top-bar {
+	vertical-align: top;
+	padding-bottom: .3em;
+	height:1.6em;
+	clear:both;
+}
+
+/*  FOOTER  */
+.nabble div.footer {
+	margin: 2.4em 0 1em;
+	padding:.5em 1em;
+	border-radius: 6px;
+	clear:both;
+}
+
+/* Others */
+input[type='radio'], input[type='checkbox'] { vertical-align:baseline; }
+label { cursor: pointer; }
+.inline {display:inline}
+.invisible {display:none;}
+.nowrap {white-space:nowrap;}
+.float-left{float:left;}
+.float-right{float:right;}
+.no-decoration {text-decoration:none;}
+.bold { font-weight:bold; }
+.image16 { width:16px;height:16px;vertical-align:middle; border:none;}
+.image24 { width:24px;height:24px;vertical-align:middle; border:none;}
+.image32 { width:32px;height:32px;vertical-align:middle; border:none;}
+
+.nabble .border1 {
+	border-width: 1px;
+	border-style: solid;
+}
+.nabble .border2 {
+	border-width: 2px;
+	border-style: solid;
+}
+
+.big-title {
+	font-size: 120%;
+	font-weight:bold;
+}
+
+/*----- MISC ----*/
+.rounded {
+	-moz-border-radius: 5px;
+	-webkit-border-radius: 5px;
+	border-radius: 5px;
+}
+
+.rounded-top {
+	-moz-border-radius-topleft: 5px;
+	-moz-border-radius-topright: 5px;
+	-webkit-border-top-right-radius: 5px;
+	-webkit-border-top-left-radius: 5px;
+	border-top-left-radius:5px;
+	border-top-right-radius:5px;
+}
+
+.rounded-bottom {
+	-moz-border-radius-bottomleft: 5px;
+	-moz-border-radius-bottomright: 5px;
+	-webkit-border-bottom-left-radius: 5px;
+	-webkit-border-bottom-right-radius: 5px;
+	border-left-radius:5px;
+	border-bottom-right-radius:5px;
+}
+
+.drop-shadow {
+	-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 4px 0px;
+	-moz-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+	box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 4px 0px;
+}
+
+button.toolbar,
+input[type=submit].toolbar,
+input[type=button].toolbar
+{
+	white-space:nowrap;
+	text-shadow:1px 1px 0 white;
+	padding:.15em .25em;
+	margin:0;
+	font:100%/1.4 Arial,Sans-serif;
+	color:#333 !important;
+	cursor:pointer;
+	background:#ddd url(images/btn_bg.gif) repeat-x 0 0 !important;
+	-moz-border-radius: 3px;
+	-webkit-border-radius: 3px;
+	border-radius: 3px;
+	border-left:1px solid #bbb;
+	border-right:1px solid #aaa;
+	border-top:1px solid #bbb;
+	border-bottom:1px solid #aaa;
+	outline:0;
+}
+button.toolbar:active,
+input[type=submit].toolbar:active,
+input[type=button].toolbar:active
+{
+	background-position:0 -500px;
+	outline:0;
+}
+button.toolbar-disabled,
+input[type=submit].toolbar-disabled,
+input[type=button].toolbar-disabled
+{ color: #777; }
+button.toolbar-disabled:active,
+input[type=submit].toolbar-disabled,
+input[type=button].toolbar-disabled {
+	background-position:0 0;
+}
+button.action-button,
+input[type=submit].action-button,
+input[type=button].action-button
+{ padding:.25em .5em; font-weight: bold; }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/tools/Index.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,27 @@
+
+package global.web.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Date;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import global.Site;
+
+
+public final class Index extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<title>Nabble Global</title>\r\n		<script type=\"text/javascript\">\r\n			var months = [\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"];\r\n			function fmt(i) { return i <= 9? \"0\" + i : i; };\r\n			function formatTime(date) {\r\n				var hours = date.getHours();\r\n				if( hours < 12 ) {\r\n					var xm = \"am\";\r\n					if (hours==0)\r\n						hours = 12;\r\n				} else {\r\n					var xm = \"pm\";\r\n					if (hours > 12)\r\n						hours -= 12;\r\n				}\r\n				return fmt(hours) + \":\" + fmt(date.getMinutes()) + xm;\r\n			};\r\n			function formatDate(date) {\r\n				return months[date.getMonth()] + \" \" + fmt(date.getDate()) + \", \" + date.getFullYear();\r\n			};\r\n			function formatDateTime(date) {\r\n				return formatDate(date) + \"; \" + formatTime(date);\r\n			};\r\n		</script>\r\n	</head>\r\n	<body>\r\n		<h1>Nabble Global</h1>\r\n		<p class=\"gray\">\r\n			Built time =\r\n			<b>\r\n			<script type=\"text/javascript\">\r\n				document.write(formatDateTime(new Date(" );
+		out.print( (new Date(ClassLoader.getSystemResource("global/web/Index.class").openConnection().getLastModified()).getTime()) );
+		out.print( ")));\r\n			</script>\r\n			</b>\r\n		</p>\r\n		<form action=\"Search.jtp\">\r\n			Search for: <input name=\"query\" />\r\n		</form>\r\n		<p><a href=\"Reindex.jtp\">Reindex</a></p>\r\n		<p><a href=\"shell.luan\">Shell</a></p>\r\n		<p><a href=\"run.luan\">Run Batch</a></p>\r\n		<p><a href=\"/tools2\">Generic tools</a></p>\r\n	</body>\r\n</html>\r\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/tools/Index.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,70 @@
+<%
+package global.web.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Date;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import global.Site;
+
+
+public final class Index extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<title>Nabble Global</title>
+				<script type="text/javascript">
+					var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
+					function fmt(i) { return i <= 9? "0" + i : i; };
+					function formatTime(date) {
+						var hours = date.getHours();
+						if( hours < 12 ) {
+							var xm = "am";
+							if (hours==0)
+								hours = 12;
+						} else {
+							var xm = "pm";
+							if (hours > 12)
+								hours -= 12;
+						}
+						return fmt(hours) + ":" + fmt(date.getMinutes()) + xm;
+					};
+					function formatDate(date) {
+						return months[date.getMonth()] + " " + fmt(date.getDate()) + ", " + date.getFullYear();
+					};
+					function formatDateTime(date) {
+						return formatDate(date) + "; " + formatTime(date);
+					};
+				</script>
+			</head>
+			<body>
+				<h1>Nabble Global</h1>
+				<p class="gray">
+					Built time =
+					<b>
+					<script type="text/javascript">
+						document.write(formatDateTime(new Date(<%=new Date(ClassLoader.getSystemResource("global/web/Index.class").openConnection().getLastModified()).getTime()%>)));
+					</script>
+					</b>
+				</p>
+				<form action="Search.jtp">
+					Search for: <input name="query" />
+				</form>
+				<p><a href="Reindex.jtp">Reindex</a></p>
+				<p><a href="shell.luan">Shell</a></p>
+				<p><a href="run.luan">Run Batch</a></p>
+				<p><a href="/tools2">Generic tools</a></p>
+			</body>
+		</html>
+		<%
+	}
+
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/tools/Reindex.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,31 @@
+
+package global.web.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import global.Site;
+
+
+public final class Reindex extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		if( request.getParameter("reindex") != null ) {
+			Site.startReindexing();
+			response.sendRedirect("Reindex.jtp");
+			return;
+		}
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<title>Reindex</title>\r\n	</head>\r\n	<body>\r\n		<h1>Reindex</h1>\r\n		<p>Status: " );
+		out.print( (Site.status) );
+		out.print( " (reload page to update)</p>\r\n		<form action=\"Reindex.jtp\"><input type=\"submit\" name=\"reindex\" value=\"Reindex\"></form>\r\n	</body>\r\n</html>\r\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/tools/Reindex.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,38 @@
+<%
+package global.web.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import global.Site;
+
+
+public final class Reindex extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		if( request.getParameter("reindex") != null ) {
+			Site.startReindexing();
+			response.sendRedirect("Reindex.jtp");
+			return;
+		}
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<title>Reindex</title>
+			</head>
+			<body>
+				<h1>Reindex</h1>
+				<p>Status: <%=Site.status%> (reload page to update)</p>
+				<form action="Reindex.jtp"><input type="submit" name="reindex" value="Reindex"></form>
+			</body>
+		</html>
+		<%
+	}
+
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/tools/Search.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,71 @@
+
+package global.web.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.util.Version;
+import nabble.view.lib.Jtp;
+import global.Site;
+
+
+public final class Search extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(Search.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		String query = request.getParameter("query");
+		IndexSearcher searcher;
+		Query q;
+		TopDocs hits;
+		try {
+			searcher = new IndexSearcher(Site.dir());
+			QueryParser parser = new QueryParser(Version.LUCENE_CURRENT,Site.SUBJECT_FLD,Site.analyzer);
+			parser.setDefaultOperator(QueryParser.AND_OPERATOR);
+			q = parser.parse(query);
+			hits = searcher.search(q,100);
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		} catch(ParseException e) {
+			throw new RuntimeException(e);
+		}
+		try {
+			
+		out.print( "\r\n<html>\r\n	<head>\r\n		<title>Nabble Search Result</title>\r\n	</head>\r\n	<body>\r\n		<h1>Nabble Search for: " );
+		out.print( (q) );
+		out.print( "</h1>\r\n		<h3>showing " );
+		out.print( (hits.scoreDocs.length) );
+		out.print( " out of " );
+		out.print( (hits.totalHits) );
+		out.print( " results</h3>\r\n		" );
+
+					for( ScoreDoc sd : hits.scoreDocs ) {
+						Site site = new Site( searcher.doc(sd.doc) );
+						
+		out.print( "\r\n<p>" );
+		out.print( (site.link()) );
+		out.print( "</p>\r\n" );
+
+					}
+					
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+		} finally {
+			searcher.close();
+		}
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/tools/Search.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,70 @@
+<%
+package global.web.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.util.Version;
+import nabble.view.lib.Jtp;
+import global.Site;
+
+
+public final class Search extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(Search.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		String query = request.getParameter("query");
+		IndexSearcher searcher;
+		Query q;
+		TopDocs hits;
+		try {
+			searcher = new IndexSearcher(Site.dir());
+			QueryParser parser = new QueryParser(Version.LUCENE_CURRENT,Site.SUBJECT_FLD,Site.analyzer);
+			parser.setDefaultOperator(QueryParser.AND_OPERATOR);
+			q = parser.parse(query);
+			hits = searcher.search(q,100);
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		} catch(ParseException e) {
+			throw new RuntimeException(e);
+		}
+		try {
+			%>
+			<html>
+				<head>
+					<title>Nabble Search Result</title>
+				</head>
+				<body>
+					<h1>Nabble Search for: <%=q%></h1>
+					<h3>showing <%=hits.scoreDocs.length%> out of <%=hits.totalHits%> results</h3>
+					<%
+					for( ScoreDoc sd : hits.scoreDocs ) {
+						Site site = new Site( searcher.doc(sd.doc) );
+						%>
+						<p><%=site.link()%></p>
+						<%
+					}
+					%>
+				</body>
+			</html>
+			<%
+		} finally {
+			searcher.close();
+		}
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/tools/run.luan	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1 @@
+return require("luan:http/tools/Run.luan").respond
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/global/web/tools/shell.luan	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1 @@
+return require("luan:http/tools/Shell.luan").respond
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/CachingDBOFactory.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,70 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class CachingDBOFactory<T extends DbBackupObject> implements DBOFactory<T> {
+
+	protected final Schema.CachingSchemaFactory schemaFactory;
+
+	protected Map<String,Map<String,T>> map = null;
+
+	protected CachingDBOFactory(Schema.CachingSchemaFactory schemaFactory) {
+		this.schemaFactory = schemaFactory;
+	}
+
+	@Override
+	public Iterable<T> getDbBackupObjects(Connection con, Schema schema) throws SQLException {
+		if (map == null) {
+			loadMap(con);
+		}
+		Map<String,T> dbos = map.get(schema.getName());
+		if (dbos == null) return Collections.emptyList();
+		else return Collections.unmodifiableCollection(dbos.values());
+	}
+
+	@Override
+	public T getDbBackupObject(Connection con, String name, Schema schema) throws SQLException {
+		if (map == null) {
+			loadMap(con);
+		}
+		Map<String,T> dbos = map.get(schema.getName());
+		return dbos == null ? null : dbos.get(name);
+	}
+
+	protected void loadMap(Connection con) throws SQLException {
+		map = new HashMap<String,Map<String,T>>();
+		PreparedStatement stmt = null;
+		try {
+			stmt = getAllStatement(con);
+			ZipBackup.debug("loading map in " + CachingDBOFactory.this.getClass());
+			ResultSet rs = stmt.executeQuery();
+			while (rs.next()) {
+				Schema schema = schemaFactory.getFromCurrentBatch(rs.getInt("schema_oid"));
+				if (schema == null) continue;
+				Map<String,T> dbos = map.get(schema.getName());
+				if (dbos == null) {
+					dbos = new HashMap<String,T>();
+					map.put(schema.getName(), dbos);
+				}
+				T dbo = newDbBackupObject(con, rs, schema);
+				dbos.put(dbo.getName(), dbo);
+			}
+			rs.close();
+		} finally {
+			if (stmt != null) stmt.close();
+		}
+	}
+
+	protected abstract PreparedStatement getAllStatement(Connection con) throws SQLException;
+
+	protected abstract T newDbBackupObject(Connection con, ResultSet rs, Schema schema) throws SQLException;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/Constraint.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,135 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+final class Constraint extends DbBackupObject {
+
+	static class ConstraintFactory implements DBOFactory<Constraint> {
+
+		@Override
+		public Iterable<Constraint> getDbBackupObjects(Connection con, Schema schema) throws SQLException {
+			List<Constraint> constraints = new ArrayList<Constraint>();
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT c.oid, c.conname, p.relname, pg_get_userbyid(p.relowner) AS owner, " +
+								"pg_get_constraintdef(c.oid) AS constraintdef, c.contype " +
+								"FROM pg_constraint c, pg_class p " +
+								"WHERE c.connamespace = ? " +
+								"AND c.conrelid = p.oid " +
+						"ORDER BY c.contype DESC");
+				stmt.setInt(1, schema.getOid());
+				ResultSet rs = stmt.executeQuery();
+				while (rs.next()) {
+					constraints.add(new Constraint(rs.getString("conname"), schema, rs.getString("relname"),
+							rs.getString("owner"), rs.getString("constraintdef"), rs.getString("contype").charAt(0)));
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			return constraints;
+		}
+
+		@Override
+		public Constraint getDbBackupObject(Connection con, String constraintName, Schema schema) throws SQLException {
+			Constraint constraint = null;
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT c.oid, c.conname, p.relname, pg_get_userbyid(p.relowner) AS owner, " +
+								"pg_get_constraintdef(c.oid) AS constraintdef, c.contype " +
+								"FROM pg_constraint c, pg_class p " +
+								"WHERE c.connamespace = ? " +
+								"AND c.conrelid = p.oid " +
+						"AND c.conname = ? ");
+				stmt.setInt(1, schema.getOid());
+				stmt.setString(2, constraintName);
+				ResultSet rs = stmt.executeQuery();
+				if (rs.next()) {
+					constraint = new Constraint(constraintName, schema, rs.getString("relname"),
+							rs.getString("owner"), rs.getString("constraintdef"), rs.getString("contype").charAt(0));
+				} else {
+					throw new RuntimeException("no such constraint: " + constraintName);
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			return constraint;
+		}
+
+	}
+
+	static class CachingConstraintFactory extends CachingDBOFactory<Constraint> {
+
+		private final Table.CachingTableFactory tableFactory;
+
+		protected CachingConstraintFactory(Schema.CachingSchemaFactory schemaFactory, Table.CachingTableFactory tableFactory) {
+			super(schemaFactory);
+			this.tableFactory = tableFactory;
+		}
+
+		@Override
+		protected PreparedStatement getAllStatement(Connection con) throws SQLException {
+			return con.prepareStatement(
+					"SELECT c.oid, c.conname, c.conrelid AS table_oid, " +
+							"pg_get_constraintdef(c.oid) AS constraintdef, " +
+							"c.connamespace AS schema_oid, c.contype " +
+					"FROM pg_constraint c");
+		}
+
+		@Override
+		protected Constraint newDbBackupObject(Connection con, ResultSet rs, Schema schema) throws SQLException {
+			Table table = tableFactory.getTable(rs.getInt("table_oid"));
+			return new Constraint(rs.getString("conname"), schema, table.getName(),
+					table.getOwner(), rs.getString("constraintdef"), rs.getString("contype").charAt(0));
+		}
+
+		// get the primary key constraints before the rest
+		@Override
+		public final Iterable<Constraint> getDbBackupObjects(Connection con, Schema schema) throws SQLException {
+			Iterable<Constraint> constraints = super.getDbBackupObjects(con, schema);
+			List<Constraint> pklist = new ArrayList<Constraint>();
+			List<Constraint> fklist = new ArrayList<Constraint>();
+			for (Constraint constraint : constraints) {
+				if (constraint.type == 'p') pklist.add(constraint);
+				else fklist.add(constraint);
+			}
+			pklist.addAll(fklist);
+			return pklist;
+		}
+
+	}
+
+	private final String tableName;
+	private final String definition;
+	private final char type;
+
+	private Constraint(String name, Schema schema, String tableName, String tableOwner, String definition, char type) {
+		super(name, schema, tableOwner);
+		this.tableName = tableName;
+		this.definition = definition.replace(" REFERENCES " + schema.getName() + ".", " REFERENCES "); // remove schema name
+		this.type = type;
+	}
+
+	@Override
+	protected StringBuilder appendCreateSql(StringBuilder buf) {
+		buf.append("ALTER TABLE ");
+		buf.append(tableName);
+		buf.append(" ADD CONSTRAINT ");
+		buf.append(name);
+		buf.append(" ");
+		buf.append(definition);
+		buf.append(" ;\n");
+		return buf;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/DBOFactory.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,14 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+interface DBOFactory<T extends DbBackupObject> {
+
+	Iterable<T> getDbBackupObjects(Connection con, Schema schema) throws SQLException;
+
+	T getDbBackupObject(Connection con, String name, Schema schema) throws SQLException;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/DataFilter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,18 @@
+package jdbcpgbackup;
+
+
+public interface DataFilter {
+	public boolean dumpData(String schema,String tableName);
+
+	public static final DataFilter ALL_DATA = new DataFilter() {
+		public boolean dumpData(String schema,String tableName) {
+			return true;
+		}
+	};
+
+	public static final DataFilter NO_DATA = new DataFilter() {
+		public boolean dumpData(String schema,String tableName) {
+			return false;
+		}
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/DbBackupObject.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,49 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+abstract class DbBackupObject {
+
+	protected final String name;
+	protected final Schema schema;
+	protected final String owner;
+
+	protected DbBackupObject(String name, Schema schema, String owner) {
+		this.name = name;
+		this.schema = schema;
+		this.owner = owner;
+	}
+
+	final String getName() {
+		return name;
+	}
+
+	String getFullname() {
+		return schema.getName() + "." + name;
+	}
+
+	final String getOwner() {
+		return owner;
+	}
+
+	String getSql(DataFilter dataFilter) {
+		StringBuilder buf = new StringBuilder();
+		if (!owner.equals(schema.getOwner())) {
+			buf.append("SET ROLE ").append(owner);
+			buf.append(" ;\n");
+		}
+		appendCreateSql(buf, dataFilter);
+		if (!owner.equals(schema.getOwner())) {
+			buf.append("SET ROLE ").append(schema.getOwner());
+			buf.append(" ;\n");
+		}
+		return buf.toString();
+	}
+
+	protected abstract StringBuilder appendCreateSql(StringBuilder buf);
+	
+	protected StringBuilder appendCreateSql(StringBuilder buf, DataFilter dataFilter) {
+		return appendCreateSql(buf);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/Index.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,165 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+class Index extends DbBackupObject {
+
+	static class IndexFactory implements DBOFactory<Index> {
+
+		@Override
+		public Iterable<Index> getDbBackupObjects(Connection con, Schema schema) throws SQLException {
+			List<Index> indexes = new ArrayList<Index>();
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement(
+						//				"SELECT * FROM pg_indexes WHERE schemaname = ?");
+						// duplicate the pg_indexes view definition but skipping the primary indexes
+						"SELECT c.relname AS tablename, i.relname AS indexname, " +
+						"pg_get_indexdef(i.oid) AS indexdef " +
+						"FROM pg_index x " +
+						"JOIN pg_class c ON c.oid = x.indrelid " +
+						"JOIN pg_class i ON i.oid = x.indexrelid " +
+						//"LEFT JOIN pg_namespace n ON n.oid = c.relnamespace " +
+						//"LEFT JOIN pg_tablespace t ON t.oid = i.reltablespace " +
+						"WHERE c.relkind = 'r'::\"char\" AND i.relkind = 'i'::\"char\" " +
+						"AND NOT x.indisprimary AND c.relnamespace = ?");
+				stmt.setInt(1, schema.getOid());
+				ResultSet rs = stmt.executeQuery();
+				while (rs.next()) {
+					indexes.add(new Index(rs.getString("indexname"), schema, rs.getString("tablename"), rs.getString("indexdef")));
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+
+			/*// too slow, get primary keys from pg_constraint instead, use pg_get_constraintdef()
+		ZipBackup.timerStart("primary keys");
+		DatabaseMetaData metaData = con.getMetaData();
+		rs = metaData.getPrimaryKeys(null, schema.getName(), null);
+		Map<String,PrimaryKey> pkeys = new HashMap<String,PrimaryKey>();
+		while (rs.next()) {
+			String tableName = rs.getString("TABLE_NAME");
+			String indexName = rs.getString("PK_NAME");
+			PrimaryKey pkey = pkeys.get(indexName);
+			if (pkey == null) {
+				pkey = new PrimaryKey(indexName, schema, tableName);
+				pkeys.put(indexName, pkey);
+			}
+			pkey.columns.put(Integer.valueOf(rs.getInt("KEY_SEQ")), rs.getString("COLUMN_NAME"));		
+		}
+		rs.close();
+		indexes.addAll(pkeys.values());
+		ZipBackup.timerEnd("primary keys");
+			 */		
+			return indexes;
+		}
+
+		@Override
+		public Index getDbBackupObject(Connection con, String indexName, Schema schema) throws SQLException {
+			Index index = null;
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement("SELECT * FROM pg_indexes WHERE schemaname = ? " +
+						"AND indexname = ?");
+				stmt.setString(1, schema.getName());
+				stmt.setString(2, indexName);
+				ResultSet rs = stmt.executeQuery();
+				if (rs.next()) {
+					index = new Index(rs.getString("indexname"), schema, rs.getString("tablename"), rs.getString("indexdef"));
+				} else {
+					throw new RuntimeException("no such index: " + indexName);
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			return index;
+		}
+	}
+
+	static class CachingIndexFactory extends CachingDBOFactory<Index> {
+
+		private final Table.CachingTableFactory tableFactory;
+
+		protected CachingIndexFactory(Schema.CachingSchemaFactory schemaFactory, Table.CachingTableFactory tableFactory) {
+			super(schemaFactory);
+			this.tableFactory = tableFactory;
+		}
+
+		@Override
+		protected PreparedStatement getAllStatement(Connection con)	throws SQLException {
+			return con.prepareStatement(
+					"SELECT x.indrelid AS table_oid, i.relname AS indexname, " +
+							"pg_get_indexdef(i.oid) AS indexdef, " +
+							"i.relnamespace AS schema_oid " +
+							"FROM pg_index x " +
+							"JOIN pg_class i ON i.oid = x.indexrelid " +
+							"WHERE i.relkind = 'i'::\"char\" " +
+					"AND NOT x.indisprimary ");
+		}
+
+		@Override
+		protected Index newDbBackupObject(Connection con, ResultSet rs, Schema schema) throws SQLException {
+			Table table = tableFactory.getTable(rs.getInt("table_oid"));
+			return new Index(rs.getString("indexname"), schema, table.getName(), rs.getString("indexdef"));
+		}
+
+	}
+
+	protected final String tableName;
+	private final String definition;
+
+	private Index(String name, Schema schema, String tableName, String definition) {
+		super(name, schema, null); // no owner (always same as table)
+		this.tableName = tableName;
+		this.definition = definition;
+	}
+
+	@Override
+	String getSql(DataFilter dataFilter) {
+		return definition.replace(" ON " + schema.getName() + ".", " ON ") + " ;\n";  // remove schema name
+	}
+
+	@Override
+	protected StringBuilder appendCreateSql(StringBuilder buf) {
+		throw new UnsupportedOperationException();
+	}
+
+	/* not used because too slow getting them
+	private final static class PrimaryKey extends Index {
+
+		private SortedMap<Integer,String> columns = new TreeMap<Integer,String>();
+
+		private PrimaryKey(String name, Schema schema, String tableName) {
+			super(name, schema, tableName, null);
+		}
+
+		@Override
+		public String getSql() {
+			StringBuilder buf = new StringBuilder();
+			buf.append("ALTER TABLE ");
+			buf.append(tableName);
+			buf.append(" ADD CONSTRAINT ");
+			buf.append(name);
+			buf.append(" PRIMARY KEY (");
+			for (String column : columns.values()) {
+				buf.append(column);
+				buf.append(",");
+			}
+			buf.deleteCharAt(buf.length()-1);
+			buf.append(");\n");
+			return buf.toString();
+		}
+
+	}
+	 */
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/JdbcPgBackup.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,129 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class JdbcPgBackup {
+
+	public static final String USAGE =
+			"Usage: JdbcPgBackup -m dump|restore [-h hostname] [-p port] [-t (timing)] " +
+					"[-d database] [-U user] [-P password] [-f filename] [-o (schema only)] " +
+					"[-s schema[,schema...]] [-n schema[,schema...]] [-b batchsize]";
+
+	private static Map<String,String> parseArgs(String[] args) {
+		Map<String,String> params = new HashMap<String,String>();
+		for (int i = 0; i<args.length; i++) {
+			if (args[i].startsWith("-") && args[i].length() == 2) {
+				char option = args[i].charAt(1);
+				switch(option) {
+				case 'm':
+					params.put("mode", args[++i]);
+					break;
+				case 'h':
+					params.put("hostname", args[++i]);
+					break;
+				case 'p':
+					params.put("port", args[++i]);
+					break;
+				case 'd':
+					params.put("database", args[++i]);
+					break;
+				case 'U':
+					params.put("user", args[++i]);
+					break;
+				case 'P':
+					params.put("password", args[++i]);
+					break;
+				case 'f':
+					params.put("filename", args[++i]);
+					break;
+				case 's':
+					params.put("schemas", args[++i]);
+					break;
+				case 'n':
+					params.put("toschemas", args[++i]);
+					break;
+				case 'b':
+					params.put("batch", args[++i]);
+					break;
+				case 't':
+					params.put("debug", "true");
+					break;
+				case 'o':
+					params.put("nodata", "true");
+					break;
+				default:
+					throw new RuntimeException("invalid parameter: " + args[i]);
+				}
+			} else throw new RuntimeException("invalid parameters");
+		}
+		return params;
+	}
+
+	public static void main(String[] args) {
+		if (args.length == 0) {
+			System.err.println(USAGE);
+			System.exit(1);
+		}
+		Map<String, String> params = null;
+		try {
+			params = parseArgs(args);
+		} catch (RuntimeException e) {
+			System.err.println(USAGE);
+			System.err.println(e.getMessage());
+			System.exit(1);
+		}
+		ZipBackup backup = new ZipBackup(params);
+		try {
+			String[] schemas = null;
+			String[] toSchemas = null;
+			String schemasParam = params.get("schemas");
+			if (schemasParam != null) {
+				schemas = schemasParam.split(",");
+			}
+			String toSchemasParam = params.get("toschemas");
+			if (toSchemasParam != null) {
+				toSchemas = toSchemasParam.split(",");
+				if (schemas == null || schemas.length == 0 || schemas.length != toSchemas.length)
+					throw new RuntimeException("non-matching source schema (-s) and destination schema (-n) parameters");
+			}
+			String mode = params.get("mode");
+
+			if ("true".equals(params.get("debug")))
+				ZipBackup.setTimingOutput(System.err);
+
+			if ("dump".equals(mode)) {
+				boolean nodata = "true".equals(params.get("nodata"));
+				DataFilter dataFilter = nodata ? DataFilter.NO_DATA : DataFilter.ALL_DATA;
+				String batchS = params.get("batch");
+				int batch = batchS == null ? ZipBackup.DEFAULT_BATCH_SIZE : Integer.parseInt(batchS);
+				if (schemas == null) {
+					backup.dumpAll(dataFilter, batch);
+				} else {
+					backup.dump(Arrays.asList(schemas), dataFilter);
+				}
+			} else if ("restore".equals(mode)) {
+				if (schemas == null) {
+					backup.restoreAll();
+				} else if (toSchemas == null) {
+					for (String schema : schemas) {
+						backup.restoreSchema(schema);
+					}
+				} else {
+					for (int i=0; i<schemas.length; i++) {
+						backup.restoreSchemaTo(schemas[i], toSchemas[i]);
+					}
+				}
+			} else throw new RuntimeException("invalid mode: " + mode);
+		} catch (RuntimeException e) {
+			System.err.println("backup failed: " + e.getMessage());
+			e.printStackTrace(System.err);
+			System.exit(1);
+		}
+		System.exit(0);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/Schema.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,157 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+final class Schema extends DbBackupObject {
+
+	static class SchemaFactory implements DBOFactory<Schema> {
+
+		@Override
+		public List<Schema> getDbBackupObjects(Connection con, Schema ignored) throws SQLException {
+			List<Schema> schemas = new ArrayList<Schema>();
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT nspname, pg_get_userbyid(nspowner) AS owner, oid FROM pg_namespace " +
+								"WHERE nspname NOT LIKE 'pg_%' " +
+						"AND nspname <> 'information_schema'");
+				ResultSet rs = stmt.executeQuery();
+				while (rs.next()) {
+					schemas.add(new Schema(rs.getString("nspname"), rs.getString("owner"), rs.getInt("oid")));
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			return schemas;
+		}
+
+		@Override
+		public Schema getDbBackupObject(Connection con, String schemaName, Schema ignored) throws SQLException {
+			PreparedStatement stmt = null;
+			Schema schema = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT pg_get_userbyid(nspowner) AS owner, oid FROM pg_namespace WHERE nspname = ?");
+				stmt.setString(1, schemaName);
+				ResultSet rs = stmt.executeQuery();
+				if (rs.next())
+					schema = new Schema(schemaName, rs.getString("owner"), rs.getInt("oid"));
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			return schema;
+		}
+
+	}
+
+	static class CachingSchemaFactory implements DBOFactory<Schema> { // does not extend CachingDBOFactory
+
+		protected Map<String,Schema> map = null;
+		private final Map<Integer,Schema> batch = new HashMap<Integer,Schema>();
+		private Iterator<Schema> itr;
+
+		@Override
+		public Collection<Schema> getDbBackupObjects(Connection con, Schema ignored) throws SQLException {
+			if (map == null) {
+				loadMap(con);
+			}
+			return Collections.unmodifiableCollection(map.values());
+		}
+
+		@Override
+		public Schema getDbBackupObject(Connection con, String name, Schema ignored) throws SQLException {
+			if (map == null) {
+				loadMap(con);
+			}
+			return map.get(name);
+		}
+
+		public Collection<Schema> nextBatch(Connection con, int batchSize) throws SQLException {
+			if (itr == null) {
+				itr = getDbBackupObjects(con, null).iterator();
+			}
+			batch.clear();
+			while (itr.hasNext() && batch.size() < batchSize) {
+				Schema schema = itr.next();
+				batch.put(schema.getOid(), schema);
+			}
+			return Collections.unmodifiableCollection(batch.values());
+		}
+
+		public Schema getFromCurrentBatch(int oid) {
+			return batch.get(oid);
+		}
+
+		private void loadMap(Connection con) throws SQLException {
+			map = new HashMap<String,Schema>();
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT nspname AS schemaname, pg_get_userbyid(nspowner) AS owner, oid FROM pg_namespace " +
+								"WHERE nspname NOT LIKE 'pg_%' " +
+						"AND nspname <> 'information_schema'");
+				ResultSet rs = stmt.executeQuery();
+				while (rs.next()) {
+					String schemaName = rs.getString("schemaname");
+					map.put(schemaName, new Schema(rs.getString("schemaname"), rs.getString("owner"), rs.getInt("oid")));
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+		}
+
+	}
+
+	static Schema createSchema(Connection con, String schemaName, String owner, DBOFactory<Schema> schemaFactory) throws SQLException {
+		PreparedStatement stmt = null;
+		try {
+			stmt = con.prepareStatement(
+					"CREATE SCHEMA " + schemaName + " AUTHORIZATION " + owner);
+			stmt.executeUpdate();
+		} finally {
+			if (stmt != null) stmt.close();
+		}
+		return schemaFactory.getDbBackupObject(con, schemaName, null);
+	}
+
+	private final int oid;
+
+	private Schema(String name, String owner, int oid) {
+		super(name, null, owner);
+		this.oid = oid;
+	}
+
+	@Override
+	String getSql(DataFilter dataFilter) {
+		return "CREATE SCHEMA " + name + " AUTHORIZATION " + owner + " ;\n";
+	}
+
+	@Override
+	protected StringBuilder appendCreateSql(StringBuilder buf) {
+		throw new UnsupportedOperationException();
+	}
+
+	@Override
+	String getFullname() {
+		return name;
+	}
+
+	int getOid() {
+		return oid;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/Sequence.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,157 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+final class Sequence extends DbBackupObject {
+
+	static class SequenceFactory implements DBOFactory<Sequence> {
+
+		@Override
+		public Iterable<Sequence> getDbBackupObjects(Connection con, Schema schema) throws SQLException {
+			List<Sequence> sequences = new ArrayList<Sequence>();
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT c.relname AS sequencename, pg_get_userbyid(c.relowner) AS owner FROM pg_class c " +
+						"WHERE c.relkind='S' AND c.relnamespace = ?");
+				stmt.setInt(1, schema.getOid());
+				ResultSet rs = stmt.executeQuery();
+				while (rs.next()) {
+					Sequence sequence = getSequence(con, schema, rs.getString("sequencename"), rs.getString("owner"));
+					sequences.add(sequence);
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			return sequences;
+		}
+
+		@Override
+		public Sequence getDbBackupObject(Connection con, String sequenceName, Schema schema) throws SQLException {
+			PreparedStatement stmt = null;
+			Sequence sequence = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT c.relname AS sequencename, pg_get_userbyid(c.relowner) AS owner FROM pg_class c " +
+						"WHERE c.relkind='S' AND c.relnamespace = ? AND c.relname = ?");
+				stmt.setInt(1, schema.getOid());
+				stmt.setString(2, sequenceName);
+				ResultSet rs = stmt.executeQuery();
+				if (rs.next()) {
+					sequence = getSequence(con, schema, rs.getString("sequencename"), rs.getString("owner"));
+				} else {
+					throw new RuntimeException("no such sequence " + sequenceName);
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			return sequence;
+		}
+
+	}
+
+	static class CachingSequenceFactory extends CachingDBOFactory<Sequence> {
+
+		protected CachingSequenceFactory(Schema.CachingSchemaFactory schemaFactory) {
+			super(schemaFactory);
+		}
+
+		@Override
+		protected PreparedStatement getAllStatement(Connection con) throws SQLException {
+			return con.prepareStatement(
+					"SELECT c.relname AS sequencename, pg_get_userbyid(c.relowner) AS owner, " +
+							"c.relnamespace AS schema_oid FROM pg_class c " +
+					"WHERE c.relkind='S'");
+		}
+
+		@Override
+		protected Sequence newDbBackupObject(Connection con, ResultSet rs, Schema schema) throws SQLException {
+			return getSequence(con, schema, rs.getString("sequencename"), rs.getString("owner"));
+		}
+
+
+	}
+
+	private static Sequence getSequence(Connection con, Schema schema, String sequenceName, String owner) throws SQLException {
+		PreparedStatement stmt = null;
+		Sequence sequence = null;
+		try {
+			stmt = con.prepareStatement(
+					"SELECT * FROM " + schema.getName() + "." + sequenceName);
+			ResultSet rs = stmt.executeQuery();
+			if (rs.next())
+				sequence = new Sequence(sequenceName, rs, schema, owner);
+			else
+				throw new RuntimeException("no such sequence: " + sequenceName);
+			rs.close();
+		} finally {
+			if (stmt != null) stmt.close();
+		}
+		return sequence;
+	}
+
+	private final long last_value;
+	private final long start_value;
+	private final long increment_by;
+	private final long max_value;
+	private final long min_value;
+	private final long cache_value;
+	private final boolean is_cycled;
+
+	private Sequence(String sequenceName, ResultSet rs, Schema schema, String owner) throws SQLException {
+		//super(rs.getString("sequence_name"), schema, owner); // postgresql bug? not always consistent with pg_class.relname
+		super(sequenceName, schema, owner);
+		this.last_value = rs.getLong("last_value");
+		this.start_value = rs.getLong("start_value");
+		this.increment_by = rs.getLong("increment_by");
+		this.max_value = rs.getLong("max_value");
+		this.min_value = rs.getLong("min_value");
+		this.is_cycled = rs.getBoolean("is_cycled");
+		this.cache_value = rs.getLong("cache_value");
+	}
+
+	@Override
+	protected StringBuilder appendCreateSql(StringBuilder buf, DataFilter dataFilter) {
+		buf.append("CREATE SEQUENCE ");
+		buf.append(getName());
+		if (increment_by != 1) {
+			buf.append(" INCREMENT BY ");
+			buf.append(increment_by);
+		}
+		buf.append(" MINVALUE ");
+		buf.append(min_value);
+		buf.append(" MAXVALUE ");
+		buf.append(max_value);
+		if (is_cycled)
+			buf.append(" CYCLE");
+		if (cache_value > 1) {
+			buf.append(" CACHE ");
+			buf.append(cache_value);
+		}
+		buf.append(" START ");
+		buf.append(start_value);
+		buf.append(";\n");
+		if (dataFilter.dumpData(schema.getName(), name)) {
+			buf.append("SELECT setval('");
+			buf.append(getName());
+			buf.append("',");
+			buf.append(last_value);
+			buf.append(") ;\n");
+		}
+		return buf;
+	}
+
+	@Override
+	protected StringBuilder appendCreateSql(StringBuilder buf) {
+		throw new UnsupportedOperationException();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/Table.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,350 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.postgresql.PGConnection;
+import org.postgresql.copy.CopyManager;
+import org.postgresql.core.BaseConnection;
+
+final class Table extends DbBackupObject {
+
+	static class TableFactory implements DBOFactory<Table> {
+
+		@Override
+		public Iterable<Table> getDbBackupObjects(Connection con, Schema schema) throws SQLException {
+			ZipBackup.timerStart("tables");
+			List<Table> tables = new ArrayList<Table>();
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement("SELECT pg_get_userbyid(c.relowner) AS tableowner, " +
+						"c.relname AS tablename, c.oid AS table_oid " +
+						"FROM pg_class c " +
+						"WHERE c.relkind = 'r'::\"char\" AND c.relnamespace = ?");
+				stmt.setInt(1, schema.getOid());
+				ResultSet rs = stmt.executeQuery();
+				while (rs.next()) {
+					Table table = new Table(rs.getString("tablename"), schema, rs.getString("tableowner"));
+					loadColumns(con, table, rs.getInt("table_oid"));
+					tables.add(table);
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			ZipBackup.timerEnd("tables");
+			return tables;
+		}
+
+		// does not load columns
+		@Override
+		public Table getDbBackupObject(Connection con, String tableName, Schema schema) throws SQLException {
+			return getDbBackupObject(con, tableName, schema, false);
+		}
+
+		public Table getDbBackupObject(Connection con, String tableName, Schema schema, boolean loadColumns) throws SQLException {
+			Table table = null;
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement("SELECT pg_get_userbyid(c.relowner) AS tableowner, c.oid AS table_oid " +
+						"FROM pg_class c " +
+						"WHERE c.relkind = 'r'::\"char\" AND c.relnamespace = ? " +
+						"AND c.relname = ?");
+				stmt.setInt(1, schema.getOid());
+				stmt.setString(2, tableName);
+				ResultSet rs = stmt.executeQuery();
+				if (rs.next()) {
+					table = new Table(tableName, schema, rs.getString("tableowner"));
+					if (loadColumns) loadColumns(con, table, rs.getInt("table_oid"));
+				} else {
+					throw new RuntimeException("no such table: " + tableName);
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			return table;
+		}
+
+		private void loadColumns(Connection con, Table table, int tableOid) throws SQLException {
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT a.attname,a.atttypid," +
+								"a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) AS attnotnull,a.atttypmod," +
+								"row_number() OVER (PARTITION BY a.attrelid ORDER BY a.attnum) AS attnum, " +
+								"pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS adsrc,t.typtype " +
+								"FROM pg_catalog.pg_attribute a " +
+								"JOIN pg_catalog.pg_type t ON (a.atttypid = t.oid) " +
+								"LEFT JOIN pg_catalog.pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) " +
+								"WHERE a.attnum > 0 AND NOT a.attisdropped " +
+						"AND a.attrelid = ? ");
+				stmt.setInt(1, tableOid);
+				ResultSet rs = stmt.executeQuery();
+				while (rs.next()) {
+					table.columns.add(table.new Column((BaseConnection)con, rs));
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+		}
+
+	}
+
+	static class CachingTableFactory extends CachingDBOFactory<Table> {
+
+		private final Map<Integer,Table> oidMap;
+
+		protected CachingTableFactory(Schema.CachingSchemaFactory schemaFactory) {
+			super(schemaFactory);
+			oidMap = new HashMap<Integer,Table>();
+		}
+
+		@Override
+		protected PreparedStatement getAllStatement(Connection con) throws SQLException {
+			return con.prepareStatement("SELECT c.relnamespace AS schema_oid, c.relname AS tablename, " + 
+					"pg_get_userbyid(c.relowner) AS tableowner, c.oid " +
+					"FROM pg_class c " +
+					"WHERE c.relkind = 'r'::\"char\"");
+		}
+
+		@Override
+		protected Table newDbBackupObject(Connection con, ResultSet rs, Schema schema) throws SQLException {
+			Table table = new Table(rs.getString("tablename"), schema, rs.getString("tableowner"));
+			oidMap.put(rs.getInt("oid"), table);
+			return table;
+		}
+
+		@Override
+		protected void loadMap(Connection con) throws SQLException {
+			ZipBackup.timerStart("tables");
+			super.loadMap(con);
+			ZipBackup.timerEnd("tables");
+			loadColumns(con);
+		}
+
+		Table getTable(int table_oid) {
+			return oidMap.get(table_oid);
+		}
+
+		private void loadColumns(Connection con) throws SQLException {
+			ZipBackup.timerStart("load columns");
+			ZipBackup.debug("begin loading columns...");
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT a.attrelid AS table_oid, a.attname, a.atttypid," +
+								"a.attnotnull OR (t.typtype = 'd' AND t.typnotnull) AS attnotnull, a.atttypmod, " +
+								"row_number() OVER (PARTITION BY a.attrelid ORDER BY a.attnum) AS attnum, " +
+								"pg_catalog.pg_get_expr(def.adbin, def.adrelid) AS adsrc, t.typtype " +
+								"FROM pg_catalog.pg_attribute a " +
+								"JOIN pg_catalog.pg_type t ON (a.atttypid = t.oid) " +
+								"LEFT JOIN pg_catalog.pg_attrdef def ON (a.attrelid=def.adrelid AND a.attnum = def.adnum) " +
+						"WHERE a.attnum > 0 AND NOT a.attisdropped ");
+				int count = 0;
+				ResultSet rs = stmt.executeQuery();
+				while (rs.next()) {
+					int oid = rs.getInt("table_oid");
+					Table table = oidMap.get(oid);
+					if (table != null) {
+						table.columns.add(table.new Column((BaseConnection)con, rs));
+						if (++count%100000 == 1) ZipBackup.debug("loaded " + count + " columns");
+					}
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			ZipBackup.debug("end loading columns");
+			ZipBackup.timerEnd("load columns");		
+		}
+
+	}
+
+
+	/*
+	private static void loadSequences(Connection con, Schema schema, Map<String,Table> tables) throws SQLException {
+		ZipBackup.timerStart("load sequences");
+		PreparedStatement stmt = con.prepareStatement(
+				"SELECT c.relname AS sequencename, d.refobjsubid AS columnid, p.relname AS tablename " + 
+				"FROM pg_class c, pg_depend d, pg_class p " +
+				"WHERE d.refobjid = p.oid " +
+				"AND p.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = ?) " +
+				"AND c.oid = d.objid AND c.relkind = 'S'");
+		stmt.setString(1, schema.getName());
+		ResultSet rs = stmt.executeQuery();
+		while (rs.next()) {
+			tables.get(rs.getString("tablename")).columns.get(rs.getInt("columnid")).setSequenceName(rs.getString("sequencename"));
+		}
+		rs.close();
+		stmt.close();
+		ZipBackup.timerEnd("load sequences");
+	}
+	 */
+
+	private final Set<Column> columns = new TreeSet<Column>(
+			new Comparator<Column>() { 
+				public int compare(Column a, Column b) {
+					return a.position - b.position;
+				};
+			});
+
+	private Table(String name, Schema schema, String owner) {
+		super(name, schema, owner);
+	}
+
+	@Override
+	protected StringBuilder appendCreateSql(StringBuilder buf) {
+		buf.append("CREATE TABLE ").append(getName());
+		buf.append(" (");
+		for (Column column : columns) {
+			column.appendSql(buf);
+			buf.append(",");
+		}
+		buf.deleteCharAt(buf.length()-1);
+		buf.append(")");
+		buf.append(" ;\n");
+		for (Column column : columns) {
+			column.appendSequenceSql(buf);
+		}
+		return buf;
+	}
+
+	void dump(Connection con, OutputStream os) throws SQLException, IOException {
+		CopyManager copyManager = ((PGConnection)con).getCopyAPI();
+		copyManager.copyOut("COPY " + getFullname() + " TO STDOUT BINARY", os);
+	}
+
+	void restore(InputStream is, Connection con) throws SQLException, IOException {
+		CopyManager copyManager = ((PGConnection)con).getCopyAPI();
+		copyManager.copyIn("COPY " + getFullname() + " FROM STDIN BINARY", is);
+	}
+
+	private static final Set<String> appendSizeTo = new HashSet<String>(
+			Arrays.asList("bit", "varbit", "bit varying", "bpchar", "char", "varchar", "character", "character varying"));
+
+	private static final Set<String> appendPrecisionTo = new HashSet<String>(
+			Arrays.asList("time", "timestamp", "timetz", "timestamptz"));
+
+	/*
+	private static final Map<String,String> aliasMap = new HashMap<String,String>();
+	static {
+		aliasMap.put("serial","integer");
+		aliasMap.put("serial4","integer");
+		aliasMap.put("serial8","bigint");
+		aliasMap.put("bigserial","bigint");
+	}
+
+	private static String getAlias(String type) {
+		String alias = aliasMap.get(type);
+		return alias != null ? alias : type;
+	}
+	 */
+
+	private class Column {
+
+		private final String name;
+		private final String typeName;
+		private /*final*/ int columnSize;
+		private final int decimalDigits;
+		private final int nullable;
+		private final String defaultValue;
+		private final boolean isAutoincrement;
+		private final String sequenceName;
+		private final int position;
+
+		private Column(BaseConnection con, ResultSet rs) throws SQLException {
+
+			int typeOid = (int)rs.getLong("atttypid");
+			int typeMod = rs.getInt("atttypmod");
+
+			position = rs.getInt("attnum");
+			name = rs.getString("attname");
+			typeName = con.getTypeInfo().getPGType(typeOid);
+			decimalDigits = con.getTypeInfo().getScale(typeOid, typeMod);
+			columnSize = con.getTypeInfo().getPrecision(typeOid, typeMod);
+			if (columnSize == 0) {
+				columnSize = con.getTypeInfo().getDisplaySize(typeOid, typeMod);
+			}
+			if (columnSize == Integer.MAX_VALUE) {
+				columnSize = 0;
+			}
+			nullable = rs.getBoolean("attnotnull") ? java.sql.DatabaseMetaData.columnNoNulls : java.sql.DatabaseMetaData.columnNullable;
+			String columnDef = rs.getString("adsrc");
+			if (columnDef != null) {
+				defaultValue = columnDef.replace("nextval('" + schema.getName() + ".", "nextval('"); // remove schema name
+				isAutoincrement = columnDef.indexOf("nextval(") != -1;
+			} else {
+				defaultValue = null;
+				isAutoincrement = false;
+			}
+			if (isAutoincrement) {
+				PreparedStatement stmt = null;
+				try {
+					stmt = con.prepareStatement(
+							"SELECT pg_get_serial_sequence( ? , ? ) AS sequencename");
+					stmt.setString(1, getFullname());
+					stmt.setString(2, name);
+					ResultSet rs2 = stmt.executeQuery();
+					if (rs2.next() && rs2.getString("sequencename") != null) {
+						sequenceName = rs2.getString("sequencename").replace(schema.getName() + ".", "");
+					} else {
+						sequenceName = null;
+					}
+					rs2.close();
+				} finally {
+					if (stmt != null) stmt.close();
+				}
+			} else sequenceName = null;
+		}
+
+		private StringBuilder appendSql(StringBuilder buf) {
+			buf.append(name).append(" ");
+			buf.append(typeName);
+			if (appendSizeTo.contains(typeName) && columnSize > 0) {
+				buf.append( "(").append(columnSize).append(")");
+			} else if (appendPrecisionTo.contains(typeName) || typeName.startsWith("interval")) {
+				buf.append("(").append(decimalDigits).append(")");
+			} else if ("numeric".equals(typeName) || "decimal".equals(typeName)) {
+				buf.append("(").append(columnSize).append(",").append(decimalDigits).append(")");
+			}
+			if (defaultValue != null) {
+				buf.append(" DEFAULT ").append(defaultValue);
+			}
+			if (nullable == java.sql.DatabaseMetaData.columnNoNulls) {
+				buf.append(" NOT NULL");
+			}
+			return buf;
+		}
+
+		private StringBuilder appendSequenceSql(StringBuilder buf) {
+			if (sequenceName == null) return buf;
+			buf.append("ALTER SEQUENCE ");
+			buf.append(sequenceName);
+			buf.append(" OWNED BY ");
+			buf.append(getName());
+			buf.append(".").append(name);
+			buf.append(" ;\n");
+			return buf;
+		}
+
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/View.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,103 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+final class View extends DbBackupObject {
+
+	static class ViewFactory implements DBOFactory<View> {
+
+		@Override
+		public Iterable<View> getDbBackupObjects(Connection con, Schema schema) throws SQLException {
+			List<View> views = new ArrayList<View>();
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT * FROM pg_views WHERE schemaname = ?");
+				stmt.setString(1, schema.getName());
+				ResultSet rs = stmt.executeQuery();
+				while (rs.next()) {
+					views.add(new View(rs.getString("viewname"), schema, rs.getString("viewowner"), rs.getString("definition")));
+				}
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			return views;
+		}
+
+		@Override
+		public View getDbBackupObject(Connection con, String viewName, Schema schema) throws SQLException {
+			View view = null;
+			PreparedStatement stmt = null;
+			try {
+				stmt = con.prepareStatement(
+						"SELECT * FROM pg_views WHERE schemaname = ? AND viewname = ?");
+				stmt.setString(1, schema.getName());
+				stmt.setString(2, viewName);
+				ResultSet rs = stmt.executeQuery();
+				if (rs.next())
+					view = new View(viewName, schema, rs.getString("viewowner"), rs.getString("definition"));
+				else
+					throw new RuntimeException("no such view: " + viewName);
+				rs.close();
+			} finally {
+				if (stmt != null) stmt.close();
+			}
+			return view;
+		}
+	}
+
+	static class CachingViewFactory extends CachingDBOFactory<View> {
+
+		protected CachingViewFactory(Schema.CachingSchemaFactory schemaFactory) {
+			super(schemaFactory);
+		}
+
+		@Override
+		protected final PreparedStatement getAllStatement(Connection con) throws SQLException {
+			return con.prepareStatement(
+					"SELECT c.relnamespace AS schema_oid, c.relname AS viewname, pg_get_userbyid(c.relowner) AS viewowner, " +
+							"pg_get_viewdef(c.oid) AS definition " +
+							"FROM pg_class c " +
+					"WHERE c.relkind = 'v'::\"char\"");
+			/*
+					"SELECT * FROM pg_views " +
+							"WHERE schemaname NOT LIKE 'pg_%' " +
+							"AND schemaname <> 'information_schema'");
+			 */
+		}
+
+		@Override
+		protected final View newDbBackupObject(Connection con, ResultSet rs, Schema schema) throws SQLException {
+			return new View(rs.getString("viewname"), schema,
+					rs.getString("viewowner"), rs.getString("definition"));	
+		}
+
+	}
+
+
+	private final String definition;
+
+	private View(String name, Schema schema, String owner, String definition) {
+		super(name, schema, owner);
+		this.definition = definition;
+	}
+
+	@Override
+	protected StringBuilder appendCreateSql(StringBuilder buf) {
+		buf.append("CREATE VIEW ");
+		buf.append(getName());
+		buf.append(" AS ");
+		buf.append(definition);
+		buf.append(" ;\n");
+		return buf;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdbcpgbackup/ZipBackup.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,739 @@
+/*	Copyright (c) 2012	Tomislav Gountchev <tomi@gountchev.net>	*/
+
+package jdbcpgbackup;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public final class ZipBackup {
+
+	private static final String zipRoot = "pg_backup/";
+	public static final int DEFAULT_BATCH_SIZE = 10000;
+
+	private final String jdbcUrl;
+	private final File file;
+
+	private DBOFactory<Schema> schemaFactory = new Schema.SchemaFactory();
+	private DBOFactory<View> viewFactory = new View.ViewFactory();
+	private DBOFactory<Table> tableFactory = new Table.TableFactory();
+	private DBOFactory<Sequence> sequenceFactory = new Sequence.SequenceFactory();
+	private DBOFactory<Index> indexFactory = new Index.IndexFactory();
+	private DBOFactory<Constraint> constraintFactory = new Constraint.ConstraintFactory();
+
+	public ZipBackup(File file, String jdbcUrl) {
+		this.file = file;
+		this.jdbcUrl = jdbcUrl;
+	}
+
+	public ZipBackup(Map<String,String> params) {
+		this(params.get("filename") == null ? null : new File(params.get("filename")),
+				buildJdbcUrl(params));
+	}
+
+	public void dumpAll(DataFilter dataFilter) {
+		dumpAll(dataFilter, DEFAULT_BATCH_SIZE);
+	}
+
+	public void dumpAll(DataFilter dataFilter, int batchSize) {
+		debug("starting full dump at " + new Date());
+		Schema.CachingSchemaFactory cachingSchemaFactory = new Schema.CachingSchemaFactory();
+		schemaFactory = cachingSchemaFactory;
+		Connection con = null;
+		ZipOutputStream zos = null;
+		try {
+			zos = getZipOutputStream();
+			con = DriverManager.getConnection(jdbcUrl);
+			con.setReadOnly(true);
+			con.setAutoCommit(true);
+			timerStart("schemas");
+			Collection<Schema> schemas = cachingSchemaFactory.getDbBackupObjects(con, null);
+			setTotalCount(schemas.size());
+			dumpSchemasSql(schemas, dataFilter, con, zos);
+			debug(schemas.size() + " schemas to be dumped");
+			timerEnd("schemas");
+			debug("begin dumping schemas");
+			Collection<Schema> batch;
+			while (! (batch = cachingSchemaFactory.nextBatch(con, batchSize)).isEmpty()) {
+				viewFactory = new View.CachingViewFactory(cachingSchemaFactory);
+				tableFactory = new Table.CachingTableFactory(cachingSchemaFactory);
+				sequenceFactory = new Sequence.CachingSequenceFactory(cachingSchemaFactory);
+				indexFactory = new Index.CachingIndexFactory(cachingSchemaFactory, (Table.CachingTableFactory)tableFactory);
+				constraintFactory = new Constraint.CachingConstraintFactory(cachingSchemaFactory, (Table.CachingTableFactory)tableFactory);
+				for (Schema schema : batch) {
+					dump(schema, dataFilter, con, zos);
+				}
+				con.close();
+				con = DriverManager.getConnection(jdbcUrl);
+				con.setReadOnly(true);
+				con.setAutoCommit(true);
+			}
+			printTimings();
+		} catch (SQLException e) {
+			throw new RuntimeException(e.getMessage(), e);
+		} catch (IOException e) {
+			throw new RuntimeException(e.getMessage(), e);
+		} finally {
+			try {
+				if (con != null) con.close();
+			} catch (SQLException ignore) {}
+			try {
+				if (zos != null) zos.close();
+			} catch (IOException e) {
+				throw new RuntimeException(e.getMessage(), e);
+			}
+		}
+		debug("finished full dump at " + new Date());
+	}
+
+	public void dump(Iterable<String> schemaNames, DataFilter dataFilter) {
+		Connection con = null;
+		try {
+			con = DriverManager.getConnection(jdbcUrl);
+			con.setReadOnly(true);
+			con.setAutoCommit(false);
+			con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
+			dump(schemaNames, dataFilter, con);
+		} catch (SQLException e) {
+			throw new RuntimeException(e.getMessage(), e);
+		} finally {
+			try {
+				if (con != null) con.close();
+			} catch (SQLException ignore) {}
+		}
+	}
+
+	public void dump(Iterable<String> schemaNames, DataFilter dataFilter, Connection con) {
+		ZipOutputStream zos = null;
+		try {
+			zos = getZipOutputStream();
+			timerStart("schemas");
+			List<Schema> schemas = new ArrayList<Schema>();
+			for (String schemaName : schemaNames) {
+				Schema schema = schemaFactory.getDbBackupObject(con, schemaName, null);
+				if (schema == null) 
+					throw new RuntimeException("schema " + schemaName + " not found in database");
+				schemas.add(schema);
+			}
+			setTotalCount(schemas.size());
+			dumpSchemasSql(schemas, dataFilter, con, zos);
+			timerEnd("schemas");
+			for (Schema schema : schemas) {
+				dump(schema, dataFilter, con, zos);
+			}
+			printTimings();
+		} catch (SQLException e) {
+			throw new RuntimeException(e.getMessage(), e);
+		} catch (IOException e) {
+			throw new RuntimeException(e.getMessage(), e);
+		} finally {
+			try {
+				if (zos != null) zos.close();
+			} catch (IOException e) {
+				throw new RuntimeException(e.getMessage(), e);
+			}
+		}
+	}
+
+	private ZipOutputStream getZipOutputStream() throws IOException {
+		if (file != null) {
+			if (file.length() > 0) {
+				throw new RuntimeException("destination file is not empty");
+			}
+			return new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
+		} else {
+			return new ZipOutputStream(System.out);
+		}
+	}
+
+	private void dumpSchemasSql(Iterable<Schema> schemas, DataFilter dataFilter, Connection con, ZipOutputStream zos) {
+		try {
+			zos.putNextEntry(new ZipEntry(zipRoot));
+			putSqlZipEntry(zos, zipRoot+"schemas.sql", schemas, dataFilter);
+			zos.putNextEntry(new ZipEntry(zipRoot + "schemas/"));
+		} catch (IOException e) {
+			throw new RuntimeException(e.getMessage(), e);
+		}
+	}
+
+	private void dump(Schema schema, DataFilter dataFilter, Connection con, ZipOutputStream zos) {
+		try {
+			String schemaRoot = zipRoot + "schemas/" + schema.getName() + "/";
+			zos.putNextEntry(new ZipEntry(schemaRoot));
+
+			timerStart("sequences");
+			Iterable<Sequence> sequences = sequenceFactory.getDbBackupObjects(con, schema);
+			putSqlZipEntry(zos, schemaRoot + "sequences.sql", sequences, dataFilter);
+			timerEnd("sequences");
+
+			Iterable<Table> tables = tableFactory.getDbBackupObjects(con, schema);
+			putSqlZipEntry(zos, schemaRoot + "tables.sql", tables, dataFilter);
+
+			timerStart("table data");
+			zos.putNextEntry(new ZipEntry(schemaRoot + "tables/"));
+			for (Table table : tables) {
+				if (dataFilter.dumpData(schema.getName(), table.getName())) {
+					zos.putNextEntry(new ZipEntry(schemaRoot + "tables/" + table.getName()));
+					table.dump(con, zos);
+				}
+			}
+			timerEnd("table data");
+
+			timerStart("views");
+			Iterable<View> views = viewFactory.getDbBackupObjects(con, schema);
+			putSqlZipEntry(zos, schemaRoot + "views.sql", views, dataFilter);
+			timerEnd("views");
+
+			timerStart("indexes");
+			Iterable<Index> indexes = indexFactory.getDbBackupObjects(con, schema);
+			putSqlZipEntry(zos, schemaRoot + "indexes.sql", indexes, dataFilter);
+			timerEnd("indexes");
+
+			timerStart("constraints");
+			Iterable<Constraint> constraints = constraintFactory.getDbBackupObjects(con, schema);
+			putSqlZipEntry(zos, schemaRoot + "constraints.sql", constraints, dataFilter);
+			timerEnd("constraints");
+
+			processedSchema();
+
+		} catch (SQLException e) {
+			throw new RuntimeException("error dumping schema " + schema.getName(), e);
+		} catch (IOException e) {
+			throw new RuntimeException("error dumping schema " + schema.getName(), e);
+		}
+	}
+
+	private void putSqlZipEntry(ZipOutputStream zos, String name,
+			Iterable<? extends DbBackupObject> dbBackupObjects, DataFilter dataFilter) throws IOException {
+		zos.putNextEntry(new ZipEntry(name));
+		for (DbBackupObject o : dbBackupObjects) {
+			zos.write(o.getSql(dataFilter).getBytes());
+		}
+	}
+
+
+	public List<String> schemasInBackup() {
+		ZipFile zipFile = null;
+		try {
+			zipFile = new ZipFile(file);
+			return new ArrayList<String>(getSchemaTables(zipFile).keySet());
+		} catch (IOException e) {
+			throw new RuntimeException(e.getMessage(), e);
+		} finally {
+			try {
+				if (zipFile != null) zipFile.close();
+			} catch (IOException ignore) {}
+		}
+	}
+
+	public void restoreSchema(String schema) {
+		restoreSchemaTo(schema, schema);
+	}
+
+	public void restoreSchemaTo(String schema, String toSchema) {
+		Connection con = null;
+		try {
+			con = DriverManager.getConnection(jdbcUrl);
+			con.setAutoCommit(false);
+			restoreSchemaTo(schema, toSchema, con);
+			con.commit();
+		} catch (Exception e) {
+			try {
+				if (con != null) con.rollback();
+			} catch (SQLException ignore) {}
+			throw new RuntimeException(e.getMessage(), e);
+		} finally {
+			try {
+				if (con != null) con.close();
+			} catch (SQLException ignore) {}
+		}
+	}
+
+	public void restoreSchemaTo(String schema, String toSchema, Connection con) {
+		ZipFile zipFile = null;
+		try {
+			zipFile = new ZipFile(file);
+			restoreSchema(schema, toSchema, toSchema, zipFile, con);
+			printTimings();
+		} catch (IOException e) {
+			throw new RuntimeException(e.getMessage(), e);
+		} finally {
+			try {
+				if (zipFile != null) zipFile.close();
+			} catch (IOException ignore) {}
+		}
+	}
+
+	public void restoreAll() {
+		debug("starting full restore at " + new Date());
+		ZipFile zipFile = null;
+		Connection con = null;
+		try {
+			con = DriverManager.getConnection(jdbcUrl);
+			con.setAutoCommit(false);
+			zipFile = new ZipFile(file);
+
+			timerStart("schemas");
+			restoreSchemasSql(zipFile, con);
+			List<String> schemas = schemasInBackup();
+			setTotalCount(schemas.size());
+			timerEnd("schemas");
+
+			int count = 0;
+			for (String schemaName : schemas) {
+				restoreSchema(schemaName, schemaName, schemaName, zipFile, con);
+				if (++count%100 == 1) con.commit(); // commit every 100 schemas
+			}
+
+			con.commit();
+			printTimings();
+		} catch (Exception e) {
+			try {
+				if (con != null) con.rollback();
+			} catch (SQLException ignore) {}
+			throw new RuntimeException(e.getMessage(), e);
+		} finally {
+			try {
+				if (con != null) con.close();
+			} catch (SQLException ignore) {}
+			try {
+				if (zipFile != null) zipFile.close();
+			} catch (IOException ignore) {}
+		}
+		debug("finished full restore at " + new Date());
+	}
+
+	private void restoreSchema(String fromSchemaName, String toSchemaName, String toOwner, ZipFile zipFile, Connection con) {
+		try {
+			timerStart("schemas");
+			boolean isNewSchema = !toSchemaName.equals(fromSchemaName);
+			Schema toSchema = schemaFactory.getDbBackupObject(con, toSchemaName, null);
+			if (toSchema == null) 
+				toSchema = Schema.createSchema(con, toSchemaName, toOwner, schemaFactory);
+			else
+				toOwner = toSchema.getOwner(); // preserve existing owner
+			setRole(con, toOwner);
+			setSearchPath(con, toSchema);
+			timerEnd("schemas");
+
+			String schemaRoot = zipRoot + "schemas/" + fromSchemaName + "/";
+
+			timerStart("sequences");
+			ZipEntry sequencesSql = zipFile.getEntry(schemaRoot + "sequences.sql");
+			execSqlZipEntry(zipFile, con, sequencesSql, isNewSchema);
+			timerEnd("sequences");
+
+			timerStart("tables");
+			ZipEntry tablesSql = zipFile.getEntry(schemaRoot + "tables.sql");
+			execSqlZipEntry(zipFile, con, tablesSql, isNewSchema);
+			timerEnd("tables");
+
+			timerStart("table data");
+			Set<ZipEntry> tableEntries = getSchemaTables(zipFile).get(fromSchemaName);
+			for (ZipEntry tableEntry : tableEntries) {
+				String tableName = parseTable(tableEntry.getName());
+				Table table = tableFactory.getDbBackupObject(con, tableName, toSchema);
+				if (!table.getOwner().equals(toOwner) && !isNewSchema) {
+					setRole(con, table.getOwner());
+				}
+				table.restore(zipFile.getInputStream(tableEntry), con);
+				if (!table.getOwner().equals(toOwner) && !isNewSchema) {
+					setRole(con, toOwner);
+				}
+			}
+			timerEnd("table data");
+
+			timerStart("views");
+			ZipEntry viewsSql = zipFile.getEntry(schemaRoot + "views.sql");
+			execSqlZipEntry(zipFile, con, viewsSql, isNewSchema);
+			timerEnd("views");
+
+			timerStart("indexes");
+			ZipEntry indexesSql = zipFile.getEntry(schemaRoot + "indexes.sql");
+			execSqlZipEntry(zipFile, con, indexesSql, isNewSchema);
+			timerEnd("indexes");
+
+			timerStart("constraints");
+			ZipEntry constraintsSql = zipFile.getEntry(schemaRoot + "constraints.sql");
+			execSqlZipEntry(zipFile, con, constraintsSql, isNewSchema);
+			timerEnd("constraints");
+
+			resetSearchPath(con);
+			resetRole(con);
+			processedSchema();
+		} catch (Exception e) {
+			throw new RuntimeException(
+					"error restoring " + fromSchemaName + 
+					" to " + toSchemaName, e
+					);
+		}
+	}
+
+	private void restoreSchemasSql(ZipFile zipFile, Connection con) {
+		try {
+			ZipEntry schemasSql = zipFile.getEntry(zipRoot + "schemas.sql");
+			if (schemasSql!=null) execSqlZipEntry(zipFile, con, schemasSql, false);
+		} catch (SQLException e) {
+			throw new RuntimeException(e.getMessage(), e);
+		} catch (IOException e) {
+			throw new RuntimeException(e.getMessage(), e);
+		}
+	}
+
+	private void execSqlZipEntry(ZipFile zipFile, Connection con, ZipEntry zipEntry, boolean isNewSchema) throws IOException, SQLException {	
+		BufferedReader reader = null;
+		try {
+			reader = new BufferedReader(new InputStreamReader(zipFile.getInputStream(zipEntry)));
+			for (String sql = reader.readLine(); sql != null; sql = reader.readLine()) {
+				if (isNewSchema) { // skip any role and ownership changes if restoring to new schema
+					if (sql.startsWith("SET ROLE ") || (sql.startsWith("ALTER ") && sql.contains(" OWNER TO "))) {
+						continue;
+					}
+				}
+				PreparedStatement stmt = null;
+				try {
+					stmt = con.prepareStatement(sql);
+					if (sql.startsWith("SELECT ")) {
+						stmt.executeQuery();
+					} else {
+						stmt.executeUpdate();
+					}
+				} catch (SQLException e) {
+					throw new RuntimeException("error executing sql: " + sql, e);
+				} finally {
+					if (stmt != null) stmt.close();
+				}
+			}
+		} finally {
+			if (reader != null) reader.close();
+		}
+	}
+
+
+	private Map<String,Set<ZipEntry>> schemaTables = null;
+
+	private Map<String,Set<ZipEntry>> getSchemaTables(ZipFile zipFile) {
+		if (schemaTables == null) {
+			schemaTables = new HashMap<String,Set<ZipEntry>>();
+			Enumeration<? extends ZipEntry> entries = zipFile.entries();
+			while (entries.hasMoreElements()) {
+				ZipEntry entry = entries.nextElement();
+				String schema = parseSchema(entry.getName());
+				if (schema == null) continue;
+				Set<ZipEntry> tables = schemaTables.get(schema);
+				if (tables == null) {
+					tables = new HashSet<ZipEntry>();
+					schemaTables.put(schema, tables);
+				}
+				if (isTable(entry.getName())) {
+					tables.add(entry);
+				}
+			}
+		}
+		return schemaTables;
+	}
+
+	private void setRole(Connection con, String role) throws SQLException {
+		PreparedStatement stmt = null;
+		try {
+			stmt = con.prepareStatement("SET ROLE " + role);
+			//stmt.setString(1, role);
+			stmt.executeUpdate();
+		} finally {
+			if (stmt != null) stmt.close();
+		}
+	}
+
+	private void setSearchPath(Connection con, Schema schema) throws SQLException {
+		PreparedStatement stmt = null;
+		try {
+			stmt = con.prepareStatement("SET SEARCH_PATH = " + schema.getName());
+			//stmt.setString(1, schema.getName());
+			stmt.executeUpdate();
+		} finally {
+			if (stmt != null) stmt.close();
+		}
+	}
+
+	private void resetRole(Connection con) throws SQLException {
+		PreparedStatement stmt = null;
+		try {
+			stmt = con.prepareStatement("RESET ROLE");
+			stmt.executeUpdate();
+		} finally {
+			if (stmt != null) stmt.close();
+		}
+	}
+
+	private void resetSearchPath(Connection con) throws SQLException {
+		PreparedStatement stmt = null;
+		try {
+			stmt = con.prepareStatement("RESET SEARCH_PATH");
+			stmt.executeUpdate();
+		} finally {
+			if (stmt != null) stmt.close();
+		}
+	}
+
+	private static boolean isTable(String name) {
+		int i = name.indexOf("/tables/");
+		return i > -1 && i < name.length() - "/tables/".length();
+	}
+
+	private static String parseTable(String name) {
+		int from = name.indexOf("/tables/") + "/tables/".length();
+		return name.substring(from);
+	}
+
+	private static String parseSchema(String name) {
+		int i = name.indexOf("/schemas/");
+		if (i < 0) return null;
+		int from = i + "/schemas/".length();
+		int to = name.indexOf('/', from);
+		if (to < 0) return null;
+		return name.substring(from, to); 
+	}
+
+
+
+	private static class Timing {
+		private final Map<String,Long> timerMap = new HashMap<String,Long>();
+		private final long startTime = System.currentTimeMillis();
+		private final PrintStream ps;
+		private int totalCount = 1;
+		private int processedCount;
+		private long time = -1;
+
+		private Timing(PrintStream ps) {
+			this.ps = ps;
+		}
+
+		void start(String step) {
+			if (time != -1) throw new RuntimeException();
+			time = System.currentTimeMillis();
+		}
+
+		void end(String step) {
+			if (time == -1) throw new RuntimeException();
+			long thisTime = System.currentTimeMillis() - time;
+			Long oldTotal = timerMap.get(step);
+			if (oldTotal != null) thisTime += oldTotal.longValue();
+			timerMap.put(step, new Long(thisTime));
+			time = -1;
+		}
+
+		void processedSchema() {
+			if (++processedCount % 100 == 1) print();
+		}
+
+		void print() {
+			long totalTime = System.currentTimeMillis() - startTime;
+			long remaining = totalTime;
+			for (Map.Entry<String,Long> entry : timerMap.entrySet()) {
+				long t = entry.getValue();
+				remaining = remaining - t;
+				ps.print(entry.getKey() + ":  \t");
+				ps.println(t/1000 + " s \t" + t*100/totalTime + " %");
+			}
+			ps.println("others: \t" + remaining/1000 + " s \t" + remaining*100/totalTime + " %");
+			ps.println("total time: \t" + totalTime/1000 + " s");
+			ps.println("processed: \t" + processedCount + " out of " + totalCount +
+					" schemas \t" + processedCount*100/totalCount + "%");
+			ps.println();
+		}
+
+		void print(String msg) {
+			ps.println(msg);
+		}
+
+	}
+
+	static final ThreadLocal<Timing> timing = new ThreadLocal<Timing>() {
+		@Override
+		protected Timing initialValue() {
+			return new Timing(null) {
+				void start(String step) {}
+				void end(String step) {}
+				void processedSchema() {}
+				void print() {}
+				void print(String msg) {}
+			};
+		}
+	};
+
+	public static void setTimingOutput(PrintStream ps) {
+		timing.set(new Timing(ps));
+	}
+
+	static void timerStart(String step) {
+		timing.get().start(step);
+	}
+
+	static void timerEnd(String step) {
+		timing.get().end(step);
+	}
+
+	static void setTotalCount(int n) {
+		timing.get().totalCount = n;
+	}
+
+	static void processedSchema() {
+		timing.get().processedSchema();
+	}
+
+	static void printTimings() {
+		timing.get().print();
+	}
+
+	static void debug(String msg) {
+		timing.get().print("at " + (System.currentTimeMillis() - timing.get().startTime)/1000 + " s:");
+		timing.get().print(msg);
+	}
+
+	static String buildJdbcUrl(Map<String,String> params) {
+		StringBuilder buf = new StringBuilder();
+		buf.append("jdbc:postgresql://");
+		String hostname = params.get("hostname");
+		if (hostname == null) hostname = "localhost";
+		buf.append(hostname);
+		String port = params.get("port");
+		if (port == null) port = "5432";
+		buf.append(":").append(port);
+		buf.append("/");
+		String username = params.get("user");
+		if (username == null) username = "postgres";
+		String database = params.get("database");
+		if (database == null) database = username;
+		buf.append(database);
+		buf.append("?user=");
+		buf.append(username);
+		String password = params.get("password");
+		if (password != null) buf.append("&password=").append(password);
+		else {
+			File pgpass = new File(System.getProperty("user.home"), ".pgpass");
+			if (pgpass.exists()) {
+				BufferedReader r = null;
+				try {
+					r = new BufferedReader(new FileReader(pgpass));
+					String line;
+					while ((line = r.readLine()) != null) {
+						if (line.startsWith("#")) continue;
+						String[] entries = line.split(":");
+						if (entries.length != 5) throw new RuntimeException(
+								"unsupported pgpass file format, better specify password on command line");
+						if (! (hostname.equals(entries[0]) || "*".equals(entries[0])) ) continue;
+						if (! (port.equals(entries[1]) || "*".equals(entries[1])) ) continue;
+						if (! (database.equals(entries[2]) || "*".equals(entries[2])) ) continue;
+						if (! (username.equals(entries[3]) || "*".equals(entries[3])) ) continue;
+						buf.append("&password=").append(entries[4]);
+						break;
+					}
+				} catch (IOException e) {
+					throw new RuntimeException("failed to read the pgpass file");
+				} finally {
+					try {
+						if (r != null) r.close();
+					} catch (IOException ignore) {}
+				}
+			}
+		}
+		return buf.toString();
+	}
+
+
+
+	// new - fschmidt
+
+	private Set<String> getEntry(ZipFile zipFile, String entryName)
+		throws IOException, SQLException
+	{
+		Set<String> set = new HashSet<String>();
+		ZipEntry zipEntry = zipFile.getEntry(entryName);
+		BufferedReader reader = new BufferedReader(new InputStreamReader(zipFile.getInputStream(zipEntry)));
+		for (String sql = reader.readLine(); sql != null; sql = reader.readLine()) {
+			// skip any role and ownership changes if restoring to new schema
+			if (sql.startsWith("SET ROLE ") || (sql.startsWith("ALTER ") && sql.contains(" OWNER TO "))) {
+				continue;
+			}
+			set.add(sql);
+		}
+		reader.close();
+		return set;
+	}
+
+	private boolean matches(ZipFile zipFile1,String schemaRoot1,ZipFile zipFile2,String schemaRoot2,String what)
+		throws IOException, SQLException
+	{
+		Set<String> entry1 = getEntry( zipFile1, schemaRoot1 + what );
+		Set<String> entry2 = getEntry( zipFile2, schemaRoot2 + what );
+/*
+		if( !entry1.equals(entry2) ) {
+			System.out.println("qqqqqqqqqqqqqqqqqqqqqq "+what);
+			System.out.println(entry1);
+			System.out.println(entry2);
+		}
+*/
+		return entry1.equals(entry2);
+	}
+
+	public boolean compareTo(String schema) {
+		try {
+			List<String> schemasInBackup = schemasInBackup();
+			if( schemasInBackup.size() != 1 )
+				throw new RuntimeException("must have 1 schema");
+			String backupSchema = schemasInBackup.get(0);
+
+			File tmpFile = File.createTempFile("jdbcpgbackup",null);
+			tmpFile.deleteOnExit();
+			ZipBackup backup = new ZipBackup( tmpFile, jdbcUrl );
+			backup.dump( Collections.singleton(schema), DataFilter.NO_DATA );
+
+			ZipFile zipFile1 = new ZipFile(file);
+			ZipFile zipFile2 = new ZipFile(tmpFile);
+			String schemaRoot1 = zipRoot + "schemas/" + backupSchema + "/";
+			String schemaRoot2 = zipRoot + "schemas/" + schema + "/";
+
+			try {
+				if( !matches(zipFile1,schemaRoot1,zipFile2,schemaRoot2,"sequences.sql") )
+					return false;
+				if( !matches(zipFile1,schemaRoot1,zipFile2,schemaRoot2,"tables.sql") )
+					return false;
+				if( !matches(zipFile1,schemaRoot1,zipFile2,schemaRoot2,"views.sql") )
+					return false;
+				if( !matches(zipFile1,schemaRoot1,zipFile2,schemaRoot2,"indexes.sql") )
+					return false;
+				if( !matches(zipFile1,schemaRoot1,zipFile2,schemaRoot2,"constraints.sql") )
+					return false;
+				return true;
+			} finally {
+				tmpFile.delete();
+			}
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		} catch (SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+}
Binary file src/nabble/data/global.schema has changed
Binary file src/nabble/data/site.schema has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/AbstractType.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,44 @@
+package nabble.model;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+
+abstract class AbstractType<T extends AbstractType> {
+
+	private static final Logger logger = LoggerFactory.getLogger(AbstractType.class);
+
+	private final char code;
+	private final String name;
+
+	@SuppressWarnings("unchecked")
+	AbstractType( Map<Character,T> map, char code, String name ) {
+		this.code = code;
+		this.name = name;
+		if( map.put(code,(T)this) != null )
+			throw new RuntimeException("duplicate code: "+code);
+	}
+
+	public final char getCode() {
+		return code;
+	}
+
+	public final String getName() {
+		return name;
+	}
+
+	public String toString() {
+		return getClass().getSimpleName()+"-"+getName();
+	}
+
+	static <T> T getType( Map<Character,T> map, char code ) {
+		T t = map.get(code);
+		if( t==null ) {
+			//throw new RuntimeException("code="+code);
+			logger.warn("Invalid code="+code);
+		}
+		return t;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Anonymous.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,174 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.PreparedStatement;
+import java.util.List;
+
+
+final class Anonymous extends PersonImpl {
+
+	private static final Logger logger = LoggerFactory.getLogger(Anonymous.class);
+
+	private final String cookie;
+	private String name;
+	private final SiteImpl site;
+
+	static String newCookie(Site site) {
+		return "a" + nextId(site);
+	}
+
+	Anonymous(SiteImpl site,String cookie, String name) {
+		if( site == null )
+			throw new NullPointerException("site is null");
+		this.site = site;
+		this.cookie = cookie;
+		this.name = name;
+	}
+
+	public Site getSite() {
+		return site;
+	}
+
+	String getCookie() {
+		return cookie;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Node newChildNode(Node.Kind kind,String subject,String message,Message.Format msgFmt,Node parent) throws ModelException {
+		if( name==null )
+			throw new ModelException.RequiredName();
+		return NodeImpl.newChildNode(kind,this,subject,message,msgFmt,(NodeImpl)parent);
+	}
+
+	public String getSearchId() {
+		return cookie;
+	}
+
+	static final char SEPERATOR = '~';
+
+	public String getIdString() {
+		String s = cookie + SEPERATOR;
+		if( name != null )
+			s += name;
+		return s;
+	}
+
+	public Message getSignature() {
+		return null;
+	}
+
+	public boolean equals(Object obj) {
+		if( !(obj instanceof Anonymous) )
+			return false;
+		Anonymous anon = (Anonymous)obj;
+		return anon.cookie.equals(cookie) && anon.site.equals(site);
+	}
+
+	public int hashCode() {
+		return cookie.hashCode();
+	}
+
+	public String toString() {
+		return "anonymous-" + cookie;
+	}
+
+	private static long nextId(Site site) {
+		try {
+			Connection con = site.getDb().getConnection();
+			Statement stmt = con.createStatement();
+			try {
+				ResultSet rs = stmt.executeQuery(
+					"select nextval('cookie_seq') as id"
+				);
+				rs.next();
+				return rs.getLong("id");
+			} finally {
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+
+	public NodeIterator<? extends Node> getNodesByDateDesc(String cnd) {
+		return new CursorNodeIterator( site.siteKey,
+				"select * from node where cookie = ?" +
+				(cnd == null?"":" and "+cnd) +
+				" order by when_created desc"
+			,
+				new DbParamSetter() {
+					public void setParams(PreparedStatement stmt) throws SQLException {
+						stmt.setString( 1, cookie );
+					}
+				}
+		);
+	}
+
+	public int getNodeCount(String cnd) {
+		try {
+			Connection con = site.getDb().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select count(*) as n from node where cookie = ?" +
+				(cnd == null? "" : " and " + cnd)
+			);
+			stmt.setString( 1, cookie );
+			ResultSet rs = stmt.executeQuery();
+			rs.next();
+			int nodeCount = rs.getInt("n");
+			stmt.close();
+			con.close();
+			return nodeCount;
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public int deleteNodes() {
+		DbDatabase db = site.getDb();
+		List<NodeImpl> nodes = new CursorNodeIterator( site.siteKey,
+				"select *"
+				+" from node"
+				+" where cookie = ?"
+			,
+				new DbParamSetter() {
+					public void setParams(PreparedStatement stmt) throws SQLException {
+						stmt.setString( 1, getCookie() );
+					}
+				}
+		).asList();
+		int n = 0;
+		for( NodeImpl node : nodes ) {
+			if( node.getSite().equals(site) ) {
+				db.beginTransaction();
+				try {
+					DbUtils.getGoodCopy(node).deleteMessageOrNode();
+					db.commitTransaction();
+					n++;
+				} finally {
+					db.endTransaction();
+				}
+			}
+		}
+		return n;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Batch.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,90 @@
+package nabble.model;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Date;
+
+
+public final class Batch {
+
+	// use this logger from Runnable
+	public static final Logger logger = LoggerFactory.getLogger(Batch.class);
+
+	private static final Object lock = new Object();
+	private static Batch currentBatch = null;
+
+	public static Batch currentBatch() {
+		synchronized(lock) {
+			return currentBatch;
+		}
+	}
+
+	public static final class AlreadyRunningException extends Exception {
+		private AlreadyRunningException() {
+			super( "already running batch " + currentBatch.name );
+		}
+	}
+
+	private static final class StoppedException extends RuntimeException {}
+
+	public final String name;
+	private final Runnable task;
+	private long startTime;
+	private volatile boolean isStopped = false;
+
+	private Batch( final String name, final Runnable task ) {
+		this.name = name;
+		this.task = new Runnable(){public void run(){
+			try {
+				logger.error( "batch " + name + " started" );
+				startTime = System.currentTimeMillis();
+				task.run();
+				long after = System.currentTimeMillis();
+				logger.error( "batch " + name + " done in " + (after - startTime) + " ms" );
+			} catch(StoppedException e) {
+				logger.error( "batch " + name + " was stopped" );
+			} catch(RuntimeException e) {
+				logger.error( "batch " + name + " failed", e );
+			} finally {
+				synchronized(lock) {
+					currentBatch = null;
+				}
+			}
+		}};
+	}
+
+	public static void run( final String name, final Runnable task )
+		throws AlreadyRunningException
+	{
+		synchronized(lock) {
+			if( currentBatch != null )
+				throw new AlreadyRunningException();
+			currentBatch = new Batch(name,task);
+			Executors.executeNow(currentBatch.task);
+		}
+	}
+
+	public static void checkStopped() {
+		synchronized(lock) {
+			if( currentBatch!=null && currentBatch.isStopped )
+				throw new StoppedException();
+		}
+	}
+
+	public void stop() {
+		isStopped = true;
+	}
+	
+	static void shutdown() {
+		synchronized(lock) {
+			if (currentBatch!=null) 
+				currentBatch.stop();
+		}
+	}
+
+	public String toString() {
+		return "Batch - " + name + " started at " + new Date(startTime) + ", " +
+				(System.currentTimeMillis() - startTime) + " ms ago";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/CursorNodeIterator.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,225 @@
+package nabble.model;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+final class CursorNodeIterator extends NodeIterator<NodeImpl> {
+	private static final Logger logger = LoggerFactory.getLogger(CursorNodeIterator.class);
+
+	private static final int fetchSize = Init.get("fetchSize",100);
+
+	private final SiteKey siteKey;
+	private final Connection con;
+	private final PreparedStatement stmt;
+	private boolean isClosed = false;
+	private ResultSet rs = null;
+	private int offset = 0;
+	private boolean hasCheckedNext = false;
+	private NodeImpl next = null;
+	private final boolean oldAutoCommit;
+	private static final AtomicInteger counter = new AtomicInteger();
+	private final String cursor = "crs" + counter.incrementAndGet();
+//	private final Exception initException = new Exception("init");
+
+	CursorNodeIterator(SiteKey siteKey,String sql,DbParamSetter paramSetter) {
+		try {
+			this.siteKey = siteKey;
+			this.con = siteKey.getDb().getConnection();
+			PreparedStatement pstmt = con.prepareStatement(
+				"declare " + cursor + " cursor with hold for " +sql
+			);
+			Connection pgCon = pstmt.getConnection();
+			oldAutoCommit = pgCon.getAutoCommit();
+			if( oldAutoCommit )
+				pgCon.setAutoCommit(false);
+			paramSetter.setParams(pstmt);
+			pstmt.execute();
+			pstmt.close();
+			this.stmt = con.prepareStatement(
+				"fetch " + fetchSize + " " + cursor
+			);
+		} catch(SQLException e) {
+			close();
+			throw new RuntimeException(sql,e);
+		} catch(RuntimeException e) {
+			close();
+			throw e;
+		}
+	}
+
+	private void openRs()
+		throws SQLException
+	{
+		rs = stmt.executeQuery();
+	}
+
+	private void closeRs()
+		throws SQLException
+	{
+		rs.close();
+		rs = null;
+	}
+
+	@Override public boolean hasNext() {
+		if( isClosed )
+			return false;
+		if( !hasCheckedNext ) {
+			hasCheckedNext = true;
+			try {
+				if( rs == null )
+					openRs();
+				if( rs.next() ) {
+					next = NodeImpl.getNode(siteKey,rs);
+					if( ++offset % fetchSize == 0 )
+						closeRs();
+				} else {
+					next = null;
+					close();
+				}
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		return next != null;
+	}
+
+	@Override public NodeImpl next() {
+		if( !hasNext() )
+			throw new NoSuchElementException();
+		hasCheckedNext = false;
+		return next;
+	}
+
+	@Override public void skip(int n) {
+		try {
+			if( n==0 )
+				return;
+			if( hasCheckedNext ) {
+				hasCheckedNext = false;
+				if( --n == 0 )
+					return;
+			}
+			if( rs != null ) {
+				int left = fetchSize - (offset % fetchSize);
+				if( n < left ) {
+					rs.relative(n);
+					return;
+				}
+				closeRs();
+				offset += left;
+				n -= left;
+			}
+			if( n >= fetchSize ) {
+				int fwd = n / fetchSize * fetchSize;
+				n -= fwd;
+				offset += fwd;
+				Statement stmt = con.createStatement();
+				stmt.execute(
+					"move " + fwd + " " + cursor
+				);
+				stmt.close();
+			}
+			if( n == 0 )
+				return;
+			openRs();
+			rs.relative(n);
+			offset += n;
+		} catch(SQLException e) {
+			throw new RuntimeException("n = "+n,e);
+		}
+	}
+
+	@Override public List<Node> get(int i,int n) {
+		try {
+			i += offset;
+			Statement stmt = con.createStatement();
+			stmt.execute(
+				"move absolute " + i + " " + cursor
+			);
+			ResultSet rs = stmt.executeQuery(
+				"fetch " + n + " " + cursor
+			);
+			List<Node> list = new ArrayList<Node>();
+			while( rs.next() ) {
+				list.add( NodeImpl.getNode(siteKey,rs) );
+			}
+			rs.close();
+			stmt.close();
+			close();
+			return list;
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override public List<NodeImpl> asList() {
+		try {
+			List<NodeImpl> list = new ArrayList<NodeImpl>();
+			if( rs != null ) {
+				while( rs.next() ) {
+					list.add( NodeImpl.getNode(siteKey,rs) );
+				}
+				closeRs();
+			}
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"fetch forward all " + cursor
+			);
+			while( rs.next() ) {
+				list.add( NodeImpl.getNode(siteKey,rs) );
+			}
+			rs.close();
+			stmt.close();
+			close();
+			return list;
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override public void close() {
+		if( isClosed )
+			return;
+		isClosed = true;
+		try {
+			if( rs != null )
+				rs.close();
+			if( stmt != null ) {
+				Connection pgCon = stmt.getConnection();
+				stmt.close();
+				{
+					Statement stmt = con.createStatement();
+					stmt.execute(
+						"close " + cursor
+					);
+					stmt.close();
+				}
+				if( oldAutoCommit )
+					pgCon.setAutoCommit(true);
+			}
+			if( con != null )
+				con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override protected void finalize() throws Throwable {
+		if( !isClosed ) {
+			logger.error("didn't close NodeIterator"/*,initException*/);
+			close();
+		}
+		super.finalize();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/DailyNumber.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,219 @@
+package nabble.model;
+
+import fschmidt.db.Listener;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+public final class DailyNumber {
+	private static final long updateFreq = 1000L*60;
+	private static long lastUpdate = System.currentTimeMillis();
+	private static final Object lock = new Object();
+	private static final List<DailyNumber> dailyNumbers = new ArrayList<DailyNumber>();
+	private static boolean hasChanged = false;
+
+	private final String field;
+	private int inc = 0;
+
+	private DailyNumber(String field) {
+		this.field = field;
+		synchronized(lock) {
+			dailyNumbers.add(this);
+		}
+	}
+
+	public void inc() {
+		synchronized(lock) {
+			hasChanged = true;
+			inc++;
+			if( System.currentTimeMillis() - lastUpdate >= updateFreq )
+				doSave();
+		}
+	}
+
+	public void dec() {
+		synchronized(lock) {
+			hasChanged = true;
+			inc--;
+			if( System.currentTimeMillis() - lastUpdate >= updateFreq )
+				doSave();
+		}
+	}
+
+	static void shutdown() {
+		synchronized(lock) {
+			doSave();
+		}
+	}
+
+	private static void doSave() {
+		if( !hasChanged )
+			return;
+		hasChanged = false;
+		try {
+			Connection con = Db.dbGlobal().getConnection();
+			try {
+				boolean hasInc = false;
+				StringBuilder buf = new StringBuilder();
+				buf.append( "update daily_numbers" );
+				for( DailyNumber dn : dailyNumbers ) {
+					if( dn.inc == 0 )
+						continue;
+					if( !hasInc ) {
+						buf.append( " set " );
+						hasInc = true;
+					} else {
+						buf.append( ", " );
+					}
+					buf.append( dn.field ).append( "=" )
+						.append( dn.field ).append( "+" ).append( dn.inc );
+					dn.inc = 0;
+				}
+				if( !hasInc )
+					return;
+				java.sql.Date day = new java.sql.Date(lastUpdate);
+				buf.append( " where day=cast(" ).append( Db.dbGlobal().arcana().quote(day) ).append(" as date)");
+				Statement stmt = con.createStatement();
+				int n = stmt.executeUpdate( buf.toString() );
+				if( n == 0 ) {
+					stmt.executeUpdate(
+						"insert into daily_numbers (day) values (" + Db.dbGlobal().arcana().quote(day) + ")"
+					);
+					if( stmt.executeUpdate(buf.toString()) != 1 )
+						throw new RuntimeException();
+				}
+				stmt.close();
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		} finally {
+			lastUpdate = System.currentTimeMillis();
+		}
+	}
+
+	private static ThreadLocal<java.sql.Date> tlDay = new ThreadLocal<java.sql.Date>();
+	private static ThreadLocal<Map<DailyNumber,Integer>> tlMap = new ThreadLocal<Map<DailyNumber,Integer>>();
+
+	public int get(Date date) {
+		java.sql.Date day = new java.sql.Date(date.getTime());
+		if( !day.equals(tlDay.get()) ) {
+			Map<DailyNumber,Integer> map = new HashMap<DailyNumber,Integer>();
+			try {
+				Connection con = Db.dbGlobal().getConnection();
+				PreparedStatement stmt = con.prepareStatement(
+					"select * from daily_numbers where day=?"
+				);
+				stmt.setDate(1,day);
+				ResultSet rs = stmt.executeQuery();
+				boolean hasNumbers = rs.next();
+				for( Iterator i=dailyNumbers.iterator(); i.hasNext(); ) {
+					DailyNumber dn = (DailyNumber)i.next();
+					map.put( dn, new Integer(hasNumbers?rs.getInt(dn.field):0) );
+				}
+				stmt.close();
+				con.close();
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+			tlDay.set(day);
+			tlMap.set(map);
+		}
+		Map<DailyNumber,Integer> map = tlMap.get();
+		return map.get(this);
+	}
+
+	public static final DailyNumber totalVisits = new DailyNumber("total_visits");
+	public static final DailyNumber directVisits = new DailyNumber("direct_visits");
+	public static final DailyNumber searchEngineVisits = new DailyNumber("se_visits");
+	public static final DailyNumber externalVisits = new DailyNumber("ext_visits");
+	public static final DailyNumber adVisits = new DailyNumber("ad_visits");
+	public static final DailyNumber registrations = new DailyNumber("registrations");
+	public static final DailyNumber logins = new DailyNumber("logins");
+	public static final DailyNumber externalPosts = new DailyNumber("external_posts");
+	public static final DailyNumber internalPosts = new DailyNumber("internal_posts");
+	public static final DailyNumber replies = new DailyNumber("replies");
+	public static final DailyNumber localForumPosts = new DailyNumber("local_forum_posts");
+
+	public static final DailyNumber forumsStarted = new DailyNumber("forums_started");
+	public static final DailyNumber galleriesStarted = new DailyNumber("galleries_started");
+	public static final DailyNumber newspapersStarted = new DailyNumber("newspapers_started");
+	public static final DailyNumber blogsStarted = new DailyNumber("blogs_started");
+
+	public static final DailyNumber firstForumPosts = new DailyNumber("first_forum_posts");
+	public static final DailyNumber firstGalleryPosts = new DailyNumber("first_gallery_posts");
+	public static final DailyNumber firstBlogPosts = new DailyNumber("first_blog_posts");
+	public static final DailyNumber firstNewspaperPosts = new DailyNumber("first_newspaper_posts");
+
+	public static final DailyNumber blockedSpams = new DailyNumber("blocked_spams");
+	public static final DailyNumber tweaks = new DailyNumber("tweaks");
+
+
+	static {
+		NodeImpl.addPostInsertListener(new Listener<NodeImpl>(){
+			public void event(final NodeImpl n) {
+				if( ModelHome.insideImportProcedure.get() )
+					return;
+				Db.dbGlobal().runAfterCommit(new Runnable(){public void run(){
+					NodeImpl node = (NodeImpl)n.getGoodCopy();
+					if( node.getKind()==Node.Kind.APP ) {
+						Node parent = node.getParent();
+						if( parent==null || !parent.getOwner().equals(node.getOwner()) ) {
+							String type = node.getType();
+							if (type.equals(Node.Type.GALLERY))
+								DailyNumber.galleriesStarted.inc();
+							else if (type.equals(Node.Type.NEWS))
+								DailyNumber.newspapersStarted.inc();
+							else if (type.equals(Node.Type.BLOG))
+								DailyNumber.blogsStarted.inc();
+							else
+								DailyNumber.forumsStarted.inc();
+						}
+						return;
+					}
+					NodeImpl post = node;
+					if( post.isFromMailingList() ) {
+						DailyNumber.externalPosts.inc();
+					} else {
+						DailyNumber.internalPosts.inc();
+						if( post.getAssociatedMailingList() == null )
+							DailyNumber.localForumPosts.inc();
+					}
+					NodeImpl parent = post.getParentImpl();
+					if( parent != null && parent.getKind()==Node.Kind.POST && !parent.isFromMailingList() )
+						DailyNumber.replies.inc();
+
+					boolean isFirstPost = parent != null && parent.getKind()==Node.Kind.APP && parent.getDescendantCount() == 1;
+					if (isFirstPost) {
+						String type = parent.getType();
+						if (type.equals(Node.Type.GALLERY))
+							DailyNumber.firstGalleryPosts.inc();
+						else if (type.equals(Node.Type.NEWS))
+							DailyNumber.firstNewspaperPosts.inc();
+						else if (type.equals(Node.Type.BLOG))
+							DailyNumber.firstBlogPosts.inc();
+						else
+							DailyNumber.firstForumPosts.inc();
+					}
+				}});
+			}
+		});
+	}
+
+	static void nop() {}
+
+	static {
+		Init.dailyNumberStarted = true;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Db.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,164 @@
+/*
+
+Copyright (C) 2003  Franklin Schmidt <frank@gustos.com>
+
+*/
+
+package nabble.model;
+
+import fschmidt.db.DbArcana;
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbFinder;
+import fschmidt.db.util.WeakCacheMap;
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.SimpleCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public final class Db {
+	private static final Logger logger = LoggerFactory.getLogger(Db.class);
+	static final String url = (String)Init.get("dbUrl");
+	static final String user = (String)Init.get("dbUser");
+	static final String password = (String)Init.get("dbPassword");
+	private static final long idleTimeout = Init.get("idleTimeout",5*60*1000L);
+
+	static final String completeUrl = url + "?user=" + user + "&password=" + password;
+
+	private static final DbDatabase baseDb = DbFinder.getBaseDatabase(url,user,password);
+	private static final fschmidt.db.pool.Pool pool = new fschmidt.db.pool.Pool();
+	static {
+		pool.setIdleTimeout(idleTimeout);
+	}
+
+	private static final class NoUserException extends SQLException {
+		NoUserException(SQLException e) {
+			super(e);
+		}
+	}
+
+	static Connection getNativeConnection() {
+		return pool.getNativeConnection();
+	}
+
+	static void checkUser(String user) {
+		try {
+			Connection con = baseDb.getConnection();
+			Statement stmt = con.createStatement();
+			try {
+				stmt.executeUpdate(
+					"set role " + user
+				);
+			} catch(SQLException e) {
+				stmt.executeUpdate(
+					"create user " + user + " with password '" + password + "'"
+				);
+				stmt.executeUpdate(
+					"set role " + user
+				);
+			}
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException("check user failed for: "+user,e);
+		}
+	}
+
+	static class NoSchema extends RuntimeException {
+		private NoSchema(String msg) {
+			super(msg);
+		}
+	}
+
+	private static void checkSchema(String schema) {
+		try {
+			Connection con = dbPostgres.getConnection();
+			Statement stmt = con.createStatement();
+			try {
+				stmt.executeQuery(
+					"select * from "+schema+".version"
+				);
+			} catch(SQLException e) {
+				if( e.getMessage().contains("does not exist") )
+					throw new NoSchema(schema);
+				throw e;
+			} finally {
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException("check schema failed for: "+schema,e);
+		}
+	}
+
+	static DbDatabase pooledDb(String user) {
+		return new fschmidt.db.pool.DbDatabaseImpl(baseDb,pool,user);
+	}
+
+	private static DbDatabase getDb(String user) {
+		DbDatabase pooledDb = pooledDb(user);
+		DbDatabase db = new fschmidt.db.cache.DbDatabaseImpl(pooledDb);
+		if( !user.equals(Db.user) ) {
+			checkSchema(user);
+			try {
+				DbSiteUpdater.update(user,db);
+			} catch(UpdatingException e) {
+				throw e;
+			} catch(RuntimeException e) {
+				throw new RuntimeException("couldn't update db for: "+user,e);
+			} catch(SQLException e) {
+				throw new RuntimeException("couldn't update db for: "+user,e);
+			}
+		}
+		return db;
+	}
+
+	private static final DbDatabase dbPostgres = getDb(user);
+	public static final DbArcana arcana = dbPostgres.arcana();
+
+	private static SimpleCache<String,DbDatabase> cache = new SimpleCache<String,DbDatabase>(new WeakCacheMap<String,DbDatabase>(), new Computable<String,DbDatabase>() {
+		public DbDatabase get(String user) {
+			return getDb(user);
+		}
+	});
+
+	static void uncache(String user) {
+		cache.remove(user);
+	}
+
+	static DbDatabase db(String user) {
+		return cache.get(user);
+	}
+
+	public static DbDatabase dbPostgres() {
+		return dbPostgres;
+	}
+
+	public static DbDatabase dbGlobal() {
+		return db("global");
+	}
+
+	public static void clearCache() {
+		fschmidt.db.cache.DbDatabaseImpl.clearCache();
+	}
+
+
+	public static void main(String[] args) throws Exception {
+		Connection con = dbPostgres.getConnection();
+		Statement stmt = con.createStatement();
+		for( String arg : args ) {
+			stmt.executeUpdate(arg);
+		}
+		stmt.close();
+		con.close();
+	}
+
+	private Db() {}  // never
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/DbGlobalUpdater.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,153 @@
+
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import jdbcpgbackup.ZipBackup;
+import nabble.utils.Log4j;
+
+import java.io.File;
+import java.sql.SQLException;
+import java.util.Collections;
+
+
+public final class DbGlobalUpdater {
+
+	private static final int version = 15;
+
+	private static final boolean skipSlowSteps = Init.get("skipSlowSteps",false);
+	private static final File globalSchemaFile = new File("src/nabble/data/global.schema");
+
+	private static final String GLOBAL = "global";
+
+	private final DbUpdater up;
+
+	private DbGlobalUpdater(DbDatabase dbGlobal) {
+		this.up = new DbUpdater(dbGlobal,GLOBAL);
+	}
+
+	private void go() throws SQLException {
+		try {
+			update();
+			if( up.databaseVersion() != version )
+				throw new RuntimeException();
+		} finally {
+			up.close();
+		}
+	}
+
+	public static boolean isValid() {
+		ZipBackup backup = new ZipBackup( globalSchemaFile, Db.completeUrl );
+		return backup.compareTo(GLOBAL);
+	}
+
+	public static void backupSchema(String filename) {
+		ZipBackup backup = new ZipBackup( new File(filename), Db.completeUrl );
+		backup.dump( Collections.singleton(GLOBAL), SiteImpl.SCHEMA_DATA );
+	}
+//	in beanshell, I do:
+//	DbGlobalUpdater.backupSchema("/Users/Franklin/hg/nabble/src/nabble/data/global.schema")
+
+	public static void updateGlobal() throws SQLException {
+		DbDatabase dbGlobal;
+		try {
+			dbGlobal = Db.dbGlobal();
+		} catch(Db.NoSchema e) {
+			if( !e.getMessage().equals(GLOBAL) )
+				throw e;
+			DbDatabase db = Db.dbPostgres();
+			db.beginTransaction();
+			try {
+				DbSiteCreator.restoreSchema( globalSchemaFile, GLOBAL );
+				db.commitTransaction();
+			} finally {
+				db.endTransaction();
+			}
+			dbGlobal = Db.dbGlobal();
+		}
+		new DbGlobalUpdater(dbGlobal).go();
+	}
+
+	public static void main(String[] args) throws Exception {
+		Log4j.initForConsole();
+		updateGlobal();
+	}
+
+	void execStepAsPostgres(String sql)
+		throws SQLException
+	{
+		up.begin();
+		up.exec( 
+		"\r\n			set role "
+		+(Db.user)
+		+"\r\n		"
+ );
+		up.exec(sql);
+		up.exec( 
+		"\r\n			set role global\r\n		"
+ );
+		up.commit();
+	}
+
+	void update() throws SQLException {
+		switch( up.dbVersion() ) {
+		case 0:
+			up.execStep( 
+		"\r\n				insert into site_global (site_id)\r\n					select site_id from site\r\n						where not exists (select * from site_global where site_global.site_id=site.site_id)\r\n			"
+ );
+		case 1:  // again
+			up.execStep( 
+		"\r\n				insert into site_global (site_id)\r\n					select site_id from site\r\n						where not exists (select * from site_global where site_global.site_id=site.site_id)\r\n			"
+ );
+		case 2:
+			up.execStep( 
+		"\r\n				alter table mailing_list_lookup\r\n					drop constraint mailing_list_lookup_site_id_fkey\r\n			"
+ );
+		case 3:
+			up.execStep( 
+		"\r\n				alter table mailing_list_lookup\r\n					ADD FOREIGN KEY (site_id) REFERENCES site_global(site_id) ON DELETE CASCADE\r\n			"
+ );
+		case 4:
+			up.execStep( 
+		"\r\n				alter table task\r\n					drop constraint task_site_id_fkey\r\n			"
+ );
+		case 5:
+			up.execStep( 
+		"\r\n				alter table task\r\n					ADD FOREIGN KEY (site_id) REFERENCES site_global(site_id) ON DELETE CASCADE\r\n			"
+ );
+		case 6:
+			up.execStep(null);
+		case 7:
+			up.execStep(null);
+		case 8:
+			execStepAsPostgres( 
+		"\r\n				alter schema public rename to pulic_old\r\n			"
+ );
+		case 9:
+			up.execStep( 
+		"\r\n				alter table global.site_global\r\n					add column remote_addr character varying;\r\n			"
+ );
+		case 10:
+			up.execStep( 
+		"\r\n				create table safety (\r\n					url character varying primary key,\r\n					is_safe boolean not null,\r\n					content text not null\r\n				)\r\n			"
+ );
+		case 11:
+			up.execStep( 
+		"\r\n				alter table site_global drop column owner_email\r\n			"
+ );
+		case 12:
+			up.execStep( 
+		"\r\n				delete from safety\r\n			"
+ );
+		case 13:
+			up.execStep( 
+		"\r\n				alter table site_global\r\n					add column user_views integer NOT NULL DEFAULT 0\r\n			"
+ );
+		case 14:
+			up.execStep( 
+		"\r\n				drop table safety\r\n			"
+ );
+		}
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/DbGlobalUpdater.jmp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,165 @@
+<%
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import jdbcpgbackup.ZipBackup;
+import nabble.utils.Log4j;
+
+import java.io.File;
+import java.sql.SQLException;
+import java.util.Collections;
+
+
+public final class DbGlobalUpdater {
+
+	private static final int version = 15;
+
+	private static final boolean skipSlowSteps = Init.get("skipSlowSteps",false);
+	private static final File globalSchemaFile = new File("src/nabble/data/global.schema");
+
+	private static final String GLOBAL = "global";
+
+	private final DbUpdater up;
+
+	private DbGlobalUpdater(DbDatabase dbGlobal) {
+		this.up = new DbUpdater(dbGlobal,GLOBAL);
+	}
+
+	private void go() throws SQLException {
+		try {
+			update();
+			if( up.databaseVersion() != version )
+				throw new RuntimeException();
+		} finally {
+			up.close();
+		}
+	}
+
+	public static boolean isValid() {
+		ZipBackup backup = new ZipBackup( globalSchemaFile, Db.completeUrl );
+		return backup.compareTo(GLOBAL);
+	}
+
+	public static void backupSchema(String filename) {
+		ZipBackup backup = new ZipBackup( new File(filename), Db.completeUrl );
+		backup.dump( Collections.singleton(GLOBAL), SiteImpl.SCHEMA_DATA );
+	}
+//	in beanshell, I do:
+//	DbGlobalUpdater.backupSchema("/Users/Franklin/hg/nabble/src/nabble/data/global.schema")
+
+	public static void updateGlobal() throws SQLException {
+		DbDatabase dbGlobal;
+		try {
+			dbGlobal = Db.dbGlobal();
+		} catch(Db.NoSchema e) {
+			if( !e.getMessage().equals(GLOBAL) )
+				throw e;
+			DbDatabase db = Db.dbPostgres();
+			db.beginTransaction();
+			try {
+				DbSiteCreator.restoreSchema( globalSchemaFile, GLOBAL );
+				db.commitTransaction();
+			} finally {
+				db.endTransaction();
+			}
+			dbGlobal = Db.dbGlobal();
+		}
+		new DbGlobalUpdater(dbGlobal).go();
+	}
+
+	public static void main(String[] args) throws Exception {
+		Log4j.initForConsole();
+		updateGlobal();
+	}
+
+	void execStepAsPostgres(String sql)
+		throws SQLException
+	{
+		up.begin();
+		up.exec( %>
+			set role <%=Db.user%>
+		<% );
+		up.exec(sql);
+		up.exec( %>
+			set role global
+		<% );
+		up.commit();
+	}
+
+	void update() throws SQLException {
+		switch( up.dbVersion() ) {
+		case 0:
+			up.execStep( %>
+				insert into site_global (site_id)
+					select site_id from site
+						where not exists (select * from site_global where site_global.site_id=site.site_id)
+			<% );
+		case 1:  // again
+			up.execStep( %>
+				insert into site_global (site_id)
+					select site_id from site
+						where not exists (select * from site_global where site_global.site_id=site.site_id)
+			<% );
+		case 2:
+			up.execStep( %>
+				alter table mailing_list_lookup
+					drop constraint mailing_list_lookup_site_id_fkey
+			<% );
+		case 3:
+			up.execStep( %>
+				alter table mailing_list_lookup
+					ADD FOREIGN KEY (site_id) REFERENCES site_global(site_id) ON DELETE CASCADE
+			<% );
+		case 4:
+			up.execStep( %>
+				alter table task
+					drop constraint task_site_id_fkey
+			<% );
+		case 5:
+			up.execStep( %>
+				alter table task
+					ADD FOREIGN KEY (site_id) REFERENCES site_global(site_id) ON DELETE CASCADE
+			<% );
+		case 6:
+			up.execStep(null);
+		case 7:
+			up.execStep(null);
+		case 8:
+			execStepAsPostgres( %>
+				alter schema public rename to pulic_old
+			<% );
+		case 9:
+			up.execStep( %>
+				alter table global.site_global
+					add column remote_addr character varying;
+			<% );
+		case 10:
+			up.execStep( %>
+				create table safety (
+					url character varying primary key,
+					is_safe boolean not null,
+					content text not null
+				)
+			<% );
+		case 11:
+			up.execStep( %>
+				alter table site_global drop column owner_email
+			<% );
+		case 12:
+			up.execStep( %>
+				delete from safety
+			<% );
+		case 13:
+			up.execStep( %>
+				alter table site_global
+					add column user_views integer NOT NULL DEFAULT 0
+			<% );
+		case 14:
+			up.execStep( %>
+				drop table safety
+			<% );
+		}
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/DbParamSetter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,13 @@
+package nabble.model;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+
+public interface DbParamSetter {
+	public void setParams(PreparedStatement stmt) throws SQLException;
+
+	public final DbParamSetter NONE = new DbParamSetter() {
+		public void setParams(PreparedStatement stmt) {}
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/DbSiteCreator.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,130 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.IoUtils;
+import jdbcpgbackup.ZipBackup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.util.List;
+import java.util.ArrayList;
+
+
+public final class DbSiteCreator {
+	private static final Logger logger = LoggerFactory.getLogger(DbSiteCreator.class);
+
+	private static final File siteSchemaFile = new File("src/nabble/data/site.schema");
+
+	static Site restoreSiteToOldId(File file) {
+		SiteKey siteKey;
+		DbDatabase db = Db.dbPostgres();
+		db.beginTransaction();
+		try {
+			ZipBackup backup = new ZipBackup( file, Db.completeUrl );
+			List<String> schemasInBackup = backup.schemasInBackup();
+			if( schemasInBackup.size() != 1 )
+				throw new RuntimeException("bad backup: "+schemasInBackup);
+			String oldSchema = schemasInBackup.get(0);
+			if( !oldSchema.startsWith("s") )
+				throw new RuntimeException("invalid schema: "+oldSchema);
+			long siteId = Long.parseLong(oldSchema.substring(1));
+			SiteGlobal siteGlobal = new SiteGlobal(siteId);
+			siteKey = siteGlobal.siteKey;
+			restoreSiteSchema( file, siteKey.schema() );
+			db.commitTransaction();
+		} finally {
+			db.endTransaction();
+		}
+		return siteKey.site();
+	}
+
+	static Site restoreSiteToNewId(File file) {
+		SiteKey siteKey;
+		DbDatabase db = Db.dbPostgres();
+		db.beginTransaction();
+		try {
+			SiteGlobal siteGlobal = new SiteGlobal();
+			siteKey = siteGlobal.siteKey;
+			restoreSiteSchema( file, siteKey.schema() );
+			db.commitTransaction();
+		} finally {
+			db.endTransaction();
+		}
+		return siteKey.site();
+	}
+
+	static Site newSite(String type,String subject,String message,Message.Format msgFmt,String email,String username)
+		throws ModelException
+	{
+		DbDatabase db = Db.dbPostgres();
+		if( !db.isInTransaction() ) {
+			db.beginTransaction();
+			try {
+				Site site = newSite(type,subject,message,msgFmt,email,username);
+				db.commitTransaction();
+				return site;
+			} finally {
+				db.endTransaction();
+			}
+		}
+		SiteGlobal siteGlobal = new SiteGlobal();
+		SiteKey siteKey = siteGlobal.siteKey;
+		String schema = siteKey.schema();
+		restoreSiteSchema( siteSchemaFile, schema );
+		if( DbUpdater.databaseVersion(Db.pooledDb(schema),schema) != DbSiteUpdater.version )
+			throw new RuntimeException("site.schema has wrong version");
+		SiteImpl site = new SiteImpl(siteKey);
+		site.getDbRecord().insert();
+		UserImpl user = UserImpl.getOrCreateUnregisteredUser(site,email,username);
+		NodeImpl rootNode = NodeImpl.newRootNode(Node.Kind.APP,user,subject,message,msgFmt);
+		rootNode.setType(type);
+		rootNode.insert(false);
+		site.setRoot(rootNode);
+		return site;
+	}
+
+	private static void restoreSiteSchema(File file,String schema) {
+		try {
+			restoreSchema(file,schema);
+		} catch(SQLException e) {
+			throw new RuntimeException("couldn't create schema "+schema,e);
+		}
+	}
+
+	static void restoreSchema(File file,String schema) throws SQLException {
+		ZipBackup backup = new ZipBackup( file, Db.completeUrl );
+		List<String> schemasInBackup = backup.schemasInBackup();
+		if( schemasInBackup.size() != 1 )
+			throw new RuntimeException("bad backup: "+schemasInBackup);
+		String oldSchema = schemasInBackup.get(0);
+		Db.checkUser(schema);
+		Connection con = Db.dbPostgres().getConnection();
+		try {
+			Statement stmt = con.createStatement();
+			stmt.executeUpdate(
+				"CREATE SCHEMA AUTHORIZATION " + schema
+			);
+			stmt.close();
+		} finally {
+			con.close();
+		}
+		backup.restoreSchemaTo(oldSchema,schema,Db.getNativeConnection());
+	}
+
+	public static boolean isValid(String schema) {
+		ZipBackup backup = new ZipBackup( siteSchemaFile, Db.completeUrl );
+		return backup.compareTo(schema);
+	}
+
+	private DbSiteCreator() {}  // never
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/DbSiteUpdater.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,837 @@
+
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+final class DbSiteUpdater {
+	private static final Logger logger = LoggerFactory.getLogger(DbSiteUpdater.class);
+
+	// don't forget to update site.schema
+	static final int version = 167;
+
+	private static class Result {
+		private static final long wait = 2000L;  // 2 seconds
+		private final long waitUntil = System.currentTimeMillis() + wait;
+		private DbSiteUpdater updater;
+		private SQLException sqlException = null;
+		private RuntimeException runtimeException = null;
+		private Error error = null;
+		private final String user;
+
+		private Result(DbDatabase db,String user) {
+			updater = new DbSiteUpdater(db,this,user);
+			this.user = user;
+			updater.start();
+		}
+
+		private synchronized void check() throws SQLException {
+			if( !isDone() ) {
+				long now = System.currentTimeMillis();
+				if( now < waitUntil ) {
+					try {
+						wait( waitUntil - now );
+					} catch(InterruptedException e) {
+						throw new RuntimeException(e);
+					}
+				}
+				if( !isDone() )
+					throw new UpdatingException(user);
+			}
+			if( sqlException != null )
+				throw sqlException;
+			if( runtimeException != null )
+				throw runtimeException;
+			if( error != null )
+				throw error;
+		}
+
+		synchronized boolean isDone() {
+			return updater==null;
+		}
+
+		synchronized void done(Throwable e) {
+			updater = null;
+			if( e != null ) {
+				if( e instanceof SQLException ) {
+					sqlException = (SQLException)e;
+				} else if( e instanceof RuntimeException ) {
+					runtimeException = (RuntimeException)e;
+				} else if( e instanceof Error ) {
+					error = (Error)e;
+				} else {
+					runtimeException = new RuntimeException("invalid exception type",e);
+				}
+			}
+			notifyAll();
+		}
+	}
+
+	private static final Map<String,Result> userResults = new HashMap<String,Result>();
+	private static final Object lock = new Object();
+
+	static void update(String user,DbDatabase db) throws SQLException {
+		if( user.equals("global") )
+			return;  // done from command line
+		getResult(user,db).check();
+	}
+
+	private static Result getResult(String user,DbDatabase db) throws SQLException {
+		synchronized(lock) {
+			Result result = userResults.get(user);
+			if( result == null ) {
+				result = new Result(db,user);
+				userResults.put(user,result);
+			}
+			return result;
+		}
+	}
+
+	// for shell
+	public static void clearResult(long siteId) {
+		String user = SiteKey.getInstance(siteId).schema();
+		synchronized(lock) {
+			userResults.remove(user);
+		}
+	}
+
+
+
+
+	private final Result result;
+	private final DbUpdater up;
+
+	private DbSiteUpdater(DbDatabase db,Result result,String schema) {
+		this.up = new DbUpdater(db,schema);
+		this.result = result;
+	}
+
+	private void start() {
+		try {  // needed for site creation because version is only up to date in current transaction
+			if( up.dbVersion() == version ) {
+				result.done(null);
+				return;
+			}
+		} catch(RuntimeException e) {
+			result.done(e);
+			return;
+		} catch(Error e) {
+			result.done(e);
+			return;
+		}
+		Executors.executeNow(new Runnable(){public void run(){
+			try {
+				go();
+				result.done(null);
+			} catch(SQLException e) {
+				result.done(e);
+			} catch(RuntimeException e) {
+				result.done(e);
+			} catch(Error e) {
+				result.done(e);
+			} finally {
+				if( !result.isDone() )
+					logger.error("result isn't done");
+			}
+		}});
+	}
+
+	void go() throws SQLException {
+		try {
+			update();
+			if( up.databaseVersion() != version )
+				throw new RuntimeException();
+		} finally {
+			up.close();
+		}
+	}
+
+	@Override public String toString() {
+		return "DbSiteUpdater-" + up.schema;
+	}
+
+	private void execCatching(String sql,String exStr) throws SQLException {
+		up.begin();
+		try {
+			up.exec(sql);
+		} catch(SQLException e) {
+			if( !e.getMessage().contains(exStr) )
+				throw e;
+			logger.info(e.toString());
+			up.exec("rollback");
+		}
+		up.commit();
+	}
+
+	void update() throws SQLException {
+		switch( up.dbVersion() ) {
+		case 0:
+			up.execStep( 
+		"\r\n				create index embed_key_site_idx on embed_key(site_id)\r\n			"
+ );
+		case 1:
+			up.execStep( 
+		"\r\n				create index file_avatar_site_idx on file_avatar(site_id)\r\n			"
+ );
+		case 2:
+			up.execStep( 
+		"\r\n				create index file_node_site_idx on file_node(site_id)\r\n			"
+ );
+		case 3:
+			up.execStep( 
+		"\r\n				create index file_temp_site_idx on file_temp(site_id)\r\n			"
+ );
+		case 4:
+			up.execStep( 
+		"\r\n				create index mailing_list_site_idx on mailing_list(site_id)\r\n			"
+ );
+		case 5:
+			up.execStep( 
+		"\r\n				create index node_msg_site_idx on node_msg(site_id)\r\n			"
+ );
+		case 6:
+			up.execStep( 
+		"\r\n				create index node_property_site_idx on node_property(site_id)\r\n			"
+ );
+		case 7:
+			up.execStep( 
+		"\r\n				create index paid_site_idx on paid(site_id)\r\n			"
+ );
+		case 8:
+			up.execStep( 
+		"\r\n				create index registration_site_idx on registration(site_id)\r\n			"
+ );
+		case 9:
+			up.execStep( 
+		"\r\n				create index subscription_site_idx on subscription(site_id)\r\n			"
+ );
+		case 10:
+			up.execStep( 
+		"\r\n				create index user_property_site_idx on user_property(site_id)\r\n			"
+ );
+		case 11:
+			up.execStep( 
+		"\r\n				create index view_count_site_idx on view_count(site_id)\r\n			"
+ );
+		case 12:
+			up.execStep( 
+		"\r\n				create index visited_site_idx on visited(site_id)\r\n			"
+ );
+
+		case 13:
+			up.execStep( 
+		"\r\n				create index tag_node_idx on tag(node_id)\r\n			"
+ );
+		case 14:
+			up.execStep( 
+		"\r\n				create index tag_user_idx on tag(user_id)\r\n			"
+ );
+		case 15:
+			up.execStep( 
+		"\r\n				create index embed_key_node_idx on embed_key(node_id)\r\n			"
+ );
+		case 16:
+			up.execStep( 
+		"\r\n				create index visited_node_idx on visited(node_id)\r\n			"
+ );
+		case 17:
+			up.execStep( 
+		"\r\n				create index visited_last_node_idx on visited(last_node_id)\r\n			"
+ );
+		case 18:
+			up.execStep( 
+		"\r\n				alter table node add column poll_option_count integer default null\r\n			"
+ );
+		case 19:
+			up.execStep( 
+		"\r\n				alter table configuration drop column site_id\r\n			"
+ );
+		case 20:
+			up.execStep( 
+		"\r\n				alter table configuration ADD PRIMARY KEY (name)\r\n			"
+ );
+		case 21:
+			up.execStep( 
+		"\r\n				alter table embed_key drop column site_id\r\n			"
+ );
+		case 22:
+			up.execStep( 
+		"\r\n				alter table file_avatar drop column site_id\r\n			"
+ );
+		case 23:
+			up.execStep( 
+		"\r\n				alter table file_node drop column site_id\r\n			"
+ );
+		case 24:
+			up.execStep( 
+		"\r\n				alter table file_temp drop column site_id\r\n			"
+ );
+		case 25:
+			up.execStep( 
+		"\r\n				alter table mailing_list drop column site_id\r\n			"
+ );
+		case 26:
+			up.execStep( 
+		"\r\n				alter table node_msg drop column site_id\r\n			"
+ );
+		case 27:
+			up.execStep( 
+		"\r\n				alter table node_property drop column site_id\r\n			"
+ );
+		case 28:
+			up.execStep( 
+		"\r\n				alter table paid drop column site_id\r\n			"
+ );
+		case 29:
+			up.execStep( 
+		"\r\n				alter table registration drop column site_id\r\n			"
+ );
+		case 30:
+			up.execStep( 
+		"\r\n				alter table subscription drop column site_id\r\n			"
+ );
+		case 31:
+			up.execStep( 
+		"\r\n				alter table user_property drop column site_id\r\n			"
+ );
+		case 32:
+			up.execStep( 
+		"\r\n				alter table view_count drop column site_id\r\n			"
+ );
+		case 33:
+			up.execStep( 
+		"\r\n				alter table visited drop column site_id\r\n			"
+ );
+		case 34:
+			up.execStep( 
+		"\r\n				alter table file_site drop column site_id\r\n			"
+ );
+		case 35:
+			up.execStep( 
+		"\r\n				alter table file_site ADD PRIMARY KEY (name)\r\n			"
+ );
+		case 36:
+			up.execStep( 
+		"\r\n				alter table module drop column site_id\r\n			"
+ );
+		case 37:
+			up.execStep( 
+		"\r\n				alter table module ADD PRIMARY KEY (module_name)\r\n			"
+ );
+		case 38:
+			up.execStep( 
+		"\r\n				alter table site_property drop column site_id\r\n			"
+ );
+		case 39:
+			up.execStep( 
+		"\r\n				alter table site_property ADD PRIMARY KEY (key)\r\n			"
+ );
+		case 40:
+			up.execStep( 
+		"\r\n				alter table tweak drop column site_id\r\n			"
+ );
+		case 41:
+			up.execStep( 
+		"\r\n				alter table tweak ADD PRIMARY KEY (tweak_name)\r\n			"
+ );
+		case 42:
+			up.execStep( 
+		"\r\n				alter table tag drop column site_id\r\n			"
+ );
+		case 43:
+			up.execStep( 
+		"\r\n				CREATE UNIQUE INDEX tag_idx\r\n					ON tag (node_id, user_id, label);\r\n			"
+ );
+		case 44:
+			up.execStep( 
+		"\r\n				drop index tag_node_idx\r\n			"
+ );
+		case 45:
+			up.execStep( 
+		"\r\n				alter table user_ drop column site_id\r\n			"
+ );
+		case 46:
+			up.execStep( 
+		"\r\n				CREATE UNIQUE INDEX user_email_idx\r\n					ON user_ (lower(email));\r\n			"
+ );
+		case 47:
+			up.execStep( 
+		"\r\n				CREATE UNIQUE INDEX user_name_idx\r\n					ON user_ (lower(name));\r\n			"
+ );
+		case 48:
+			up.execStep( 
+		"\r\n				alter table node drop column site_id\r\n			"
+ );
+		case 49:
+			up.execStep( 
+		"\r\n				CREATE INDEX node_cookie_idx\r\n					ON node (cookie)\r\n					WHERE cookie IS NOT NULL;\r\n			"
+ );
+		case 50:
+			up.execStep( 
+		"\r\n				CREATE INDEX node_message_id_idx\r\n					ON node (lower(message_id))\r\n					WHERE message_id IS NOT NULL;\r\n			"
+ );
+		case 51:
+			up.execStep( 
+		"\r\n				drop table embed_key\r\n			"
+ );
+		case 52:
+			up.execStep( 
+		"\r\n				delete from site_property where key = '__embedding_warning'\r\n			"
+ );
+		case 53:
+			up.execStep( 
+		"\r\n				alter table site drop column site_id\r\n			"
+ );
+		case 54:
+			up.execStep( 
+		"\r\n				alter table site add column one integer default 1 not null\r\n			"
+ );
+		case 55:
+			up.execStep( 
+		"\r\n				alter table site ADD PRIMARY KEY (one)\r\n			"
+ );
+		case 56:
+			up.execStep( 
+		"\r\n				alter table site add column content_type character varying;\r\n			"
+ );
+		case 57:
+			up.execStep( 
+		"\r\n				update configuration set naml=E'<override_macro name=\"site_style\">\\n\\t<n.overridden/>\\n\\t#search-box { text-align:left; }\\n</override_macro>' where name='searchBoxAlignment';\r\n			"
+ );
+		case 58:
+			up.execStep( 
+		"\r\n				update site set ad_type=null where ad_type='ADBRITE' or ad_type='ADSENSE'\r\n			"
+ );
+
+		case 59:
+			up.execStep( 
+		"\r\n				alter table node\r\n					add column unsafe character varying default 'new',\r\n					add column safe_version integer\r\n			"
+ );
+		case 60:
+			up.execStep( 
+		"\r\n				alter table user_\r\n					add column unsafe character varying default 'new',\r\n					add column safe_version integer\r\n			"
+ );
+
+		case 61:
+			up.execStep( 
+		"\r\n				update site set ad_type='HOPELESS' where ad_type='COPYRIGHTED'\r\n			"
+ );
+		case 62:
+			up.execStep( 
+		"\r\n				update site set ad_type='HOPELESS' where ad_type='OURS'\r\n			"
+ );
+		case 63:
+			up.execStep( 
+		"\r\n				update site set ad_type=null, ad_free="
+		+(Integer.MAX_VALUE)
+		+" where ad_type='NONE'\r\n			"
+ );
+
+		case 64:
+			up.execStep( 
+		"\r\n				alter table node\r\n					add column is_safe boolean\r\n			"
+ );
+		case 65:
+			up.execStep( 
+		"\r\n				alter table user_\r\n					add column is_safe boolean\r\n			"
+ );
+		case 66:
+			up.execStep( null );
+		case 67:
+			up.execStep( null );
+		case 68:
+			up.execStep( 
+		"\r\n				delete from tag where label='hide_ads'\r\n			"
+ );
+		case 69:
+			up.execStep( 
+		"\r\n				update site set ad_type=null where ad_type='SOME_ADSENSE'\r\n			"
+ );
+		case 70:
+			up.execStep( 
+		"\r\n				update site set ad_type='ADULT' where ad_type='HACK'\r\n			"
+ );
+		case 71:
+			up.execStep( 
+		"\r\n				update site set content_type=null where content_type='FAMILY'\r\n			"
+ );
+		case 72:
+			up.execStep( 
+		"\r\n				drop index if exists node_is_safe_idx\r\n			"
+ );
+		case 73:
+			up.execStep( 
+		"\r\n				drop index if exists user_is_safe_idx\r\n			"
+ );
+		case 74:
+			up.execStep( 
+		"\r\n				create index node_unsafe_idx on node(unsafe) where unsafe is not null and unsafe!='new'\r\n			"
+ );
+		case 75:
+			up.execStep( 
+		"\r\n				create index user_unsafe_idx on user_(unsafe) where unsafe is not null and unsafe!='new'\r\n			"
+ );
+		case 76:
+			up.execStep( 
+		"\r\n				update site set content_type=ad_type where content_type is null and ad_type is not null and ad_type!='HOPELESS'\r\n			"
+ );
+		case 77:
+			up.execStep( 
+		"\r\n				update site set ad_type=content_type where content_type is not null and ad_type is null\r\n			"
+ );
+		case 78:
+			up.execStep( 
+		"\r\n				update site set ad_type='ADULT' where content_type='ADULT' and ad_type='PORN'\r\n			"
+ );
+
+		case 79:
+			up.execStep( 
+		"\r\n				alter table user_\r\n					drop column unsafe,\r\n					drop column safe_version,\r\n					drop column is_safe\r\n			"
+ );
+		case 80:
+			up.execStep( null );
+		case 81:
+			up.execStep( null );
+		case 82:
+			up.begin();
+			up.exec( 
+		"\r\n				alter table node\r\n					drop column unsafe,\r\n					drop column safe_version,\r\n					drop column is_safe\r\n			"
+ );
+			up.exec( 
+		"\r\n				alter table node\r\n					add column unsafe character varying,\r\n					add column safe_version integer,\r\n					add column is_safe boolean\r\n			"
+ );
+			up.commit();
+		case 83:
+			up.execStep( 
+		"\r\n				create index node_unsafe_idx on node(unsafe) where safe_version is not null\r\n			"
+ );
+		case 84:
+			up.execStep( 
+		"\r\n				delete from module where module_name = 'topic_ads';\r\n			"
+ );
+		case 85:
+			up.execStep( 
+		"\r\n				alter table node\r\n					drop column unsafe,\r\n					drop column safe_version,\r\n					drop column is_safe\r\n			"
+ );
+		case 86:
+			up.execStep( 
+		"\r\n				alter table site\r\n					add column when_created timestamp with time zone NOT NULL DEFAULT now()\r\n			"
+ );
+		case 87:
+			up.execStep( 
+		"\r\n				update site\r\n					set when_created = node.when_created\r\n					from node\r\n					where node.node_id = site.root_node_id\r\n			"
+ );
+
+		case 88:
+			up.execStep( 
+		"\r\n				alter table site\r\n					add column is_safe boolean NOT NULL DEFAULT true\r\n			"
+ );
+		case 89:
+			up.execStep( 
+		"\r\n				update site set is_safe = false where ad_type is not null\r\n			"
+ );
+		case 90:
+			up.execStep( 
+		"\r\n				update site set when_created = now() where not is_safe\r\n			"
+ );
+		case 91:
+			up.execStep( 
+		"\r\n				alter table site rename column ad_type to old_ad_type\r\n			"
+ );
+		case 92:
+			up.execStep( 
+		"\r\n				alter table site rename column content_type to old_content_type\r\n			"
+ );
+		case 93:
+			up.execStep( 
+		"\r\n				alter table site\r\n					add column ad_credits_for_users boolean NOT NULL DEFAULT false\r\n			"
+ );
+		case 94:
+			up.execStep( 
+		"\r\n				alter table site\r\n					add column monthly_views integer NOT NULL DEFAULT 0\r\n			"
+ );
+		case 95:
+			up.execStep( 
+		"\r\n				delete from tag where label='group:Administrators' and node_id is null and user_id = (select user_id from user_ where email = 'pedro@nabble.com')\r\n			"
+ );
+
+		case 96:
+			execCatching( 
+		"\r\n				alter table tweak ADD PRIMARY KEY (tweak_name)\r\n			"
+, "multiple primary keys for table" );
+		case 97:
+			execCatching( 
+		"\r\n				alter table mailing_list ADD PRIMARY KEY (node_id)\r\n			"
+, "multiple primary keys for table" );
+		case 98:
+/*
+			execCatching( 
+		"\r\n				alter table mailing_list ADD CONSTRAINT mailing_list_node_id_fkey FOREIGN KEY (node_id) REFERENCES node (node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+*/
+			up.execStep( null );
+		case 99:
+			execCatching( 
+		"\r\n				CREATE INDEX forum_ml_idx ON mailing_list (lower(list_address))\r\n			"
+, "already exists" );
+		case 100:
+			execCatching( 
+		"\r\n				alter table node ADD PRIMARY KEY (node_id)\r\n			"
+, "multiple primary keys for table" );
+		case 101:
+			execCatching( 
+		"\r\n				alter table configuration ADD PRIMARY KEY (name)\r\n			"
+, "multiple primary keys for table" );
+		case 102:
+			execCatching( 
+		"\r\n				alter table file_avatar ADD PRIMARY KEY (user_id, name)\r\n			"
+, "multiple primary keys for table" );
+		case 103:
+			execCatching( 
+		"\r\n				alter table file_node ADD PRIMARY KEY (node_id, name)\r\n			"
+, "multiple primary keys for table" );
+		case 104:
+			execCatching( 
+		"\r\n				alter table file_site ADD PRIMARY KEY (name)\r\n			"
+, "multiple primary keys for table" );
+		case 105:
+			execCatching( 
+		"\r\n				alter table file_temp ADD PRIMARY KEY (user_id, name)\r\n			"
+, "multiple primary keys for table" );
+		case 106:
+			execCatching( 
+		"\r\n				CREATE INDEX file_temp_date_idx ON file_temp (date_)\r\n			"
+, "already exists" );
+		case 107:
+			execCatching( 
+		"\r\n				alter table module ADD PRIMARY KEY (module_name)\r\n			"
+, "multiple primary keys for table" );
+		case 108:
+			execCatching( 
+		"\r\n				CREATE INDEX node_cookie_idx ON node (cookie) WHERE cookie IS NOT NULL\r\n			"
+, "already exists" );
+		case 109:
+			execCatching( 
+		"\r\n				CREATE INDEX node_export ON node (export_permalink) WHERE export_permalink IS NOT NULL\r\n			"
+, "already exists" );
+		case 110:
+			execCatching( 
+		"\r\n				CREATE INDEX node_message_id_idx ON node (lower(message_id)) WHERE message_id IS NOT NULL\r\n			"
+, "already exists" );
+		case 111:
+			execCatching( 
+		"\r\n				CREATE INDEX node_ml2_idx ON node (lower(parent_message_id)) WHERE lower(parent_message_id) IS NOT NULL\r\n			"
+, "already exists" );
+		case 112:
+			execCatching( 
+		"\r\n				CREATE INDEX node_owner_idx ON node (owner_id, when_created)\r\n			"
+, "already exists" );
+		case 113:
+			execCatching( 
+		"\r\n				CREATE UNIQUE INDEX node_parent2_idx ON node (parent_id, when_created, node_id)\r\n			"
+, "already exists" );
+		case 114:
+			execCatching( 
+		"\r\n				CREATE UNIQUE INDEX node_parent_idx ON node (parent_id, last_node_date, node_id)\r\n			"
+, "already exists" );
+		case 115:
+			execCatching( 
+		"\r\n				CREATE INDEX node_sent_idx ON node (when_sent) WHERE when_sent IS NOT NULL\r\n			"
+, "already exists" );
+		case 116:
+			execCatching( 
+		"\r\n				CREATE UNIQUE INDEX pinned_node_idx ON node (parent_id, pin) WHERE pin IS NOT NULL\r\n			"
+, "already exists" );
+		case 117:
+			execCatching( 
+		"\r\n				CREATE INDEX post_date ON node (when_created)\r\n			"
+, "already exists" );
+		case 118:
+			execCatching( 
+		"\r\n				alter table node_msg ADD PRIMARY KEY (node_id)\r\n			"
+, "multiple primary keys for table" );
+		case 119:
+			execCatching( 
+		"\r\n				alter table node_property ADD PRIMARY KEY (node_id, key)\r\n			"
+, "multiple primary keys for table" );
+		case 120:
+			execCatching( 
+		"\r\n				alter table paid ADD PRIMARY KEY (user_id)\r\n			"
+, "multiple primary keys for table" );
+		case 121:
+			execCatching( 
+		"\r\n				alter table registration ADD PRIMARY KEY (key_)\r\n			"
+, "multiple primary keys for table" );
+		case 122:
+			execCatching( 
+		"\r\n				CREATE INDEX registration_date_idx ON registration (date_)\r\n			"
+, "already exists" );
+		case 123:
+			execCatching( 
+		"\r\n				alter table site ADD PRIMARY KEY (one)\r\n			"
+, "multiple primary keys for table" );
+		case 124:
+			execCatching( 
+		"\r\n				alter table site_property ADD PRIMARY KEY (key)\r\n			"
+, "multiple primary keys for table" );
+		case 125:
+			execCatching( 
+		"\r\n				alter table subscription ADD PRIMARY KEY (user_id, node_id)\r\n			"
+, "multiple primary keys for table" );
+		case 126:
+			execCatching( 
+		"\r\n				CREATE INDEX subscription_node_idx ON subscription (node_id)\r\n			"
+, "already exists" );
+		case 127:
+			execCatching( 
+		"\r\n				CREATE UNIQUE INDEX tag_idx ON tag (node_id, user_id, label)\r\n			"
+, "already exists" );
+		case 128:
+			execCatching( 
+		"\r\n				CREATE INDEX tag_user_idx ON tag (user_id)\r\n			"
+, "already exists" );
+		case 129:
+			execCatching( 
+		"\r\n				alter table user_ ADD PRIMARY KEY (user_id)\r\n			"
+, "multiple primary keys for table" );
+		case 130:
+			execCatching( 
+		"\r\n				CREATE UNIQUE INDEX user_email_idx ON user_ (lower(email))\r\n			"
+, "already exists" );
+		case 131:
+			execCatching( 
+		"\r\n				CREATE UNIQUE INDEX user_name_idx ON user_ (lower(name))\r\n			"
+, "already exists" );
+		case 132:
+			execCatching( 
+		"\r\n				alter table user_property ADD PRIMARY KEY (user_id, key)\r\n			"
+, "multiple primary keys for table" );
+		case 133:
+			execCatching( 
+		"\r\n				alter table view_count ADD PRIMARY KEY (node_id)\r\n			"
+, "multiple primary keys for table" );
+		case 134:
+			execCatching( 
+		"\r\n				alter table visited ADD PRIMARY KEY (user_id, node_id)\r\n			"
+, "multiple primary keys for table" );
+		case 135:
+			execCatching( 
+		"\r\n				CREATE INDEX visited_last_node_idx ON visited (last_node_id)\r\n			"
+, "already exists" );
+		case 136:
+			execCatching( 
+		"\r\n				CREATE INDEX visited_node_idx ON visited (node_id)\r\n			"
+, "already exists" );
+
+		case 137:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY file_avatar \r\n					ADD CONSTRAINT file_avatar_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 138:
+			up.execStep( null );
+		case 139:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY file_temp \r\n					ADD CONSTRAINT file_temp_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 140:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY mailing_list \r\n					ADD CONSTRAINT mailing_list_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 141:
+			up.execStep( null );
+		case 142:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY node \r\n					ADD CONSTRAINT node_new_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES user_(user_id)\r\n			"
+, "already exists" );
+		case 143:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY node \r\n					ADD CONSTRAINT node_node_id_fkey FOREIGN KEY (node_id) REFERENCES node_msg(node_id) DEFERRABLE INITIALLY DEFERRED\r\n			"
+, "already exists" );
+		case 144:
+			up.execStep( null );
+		case 145:
+			up.execStep( null );
+		case 146:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY paid \r\n					ADD CONSTRAINT paid_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 147:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY site \r\n					ADD CONSTRAINT site_root_node_id_fkey FOREIGN KEY (root_node_id) REFERENCES node(node_id) DEFERRABLE INITIALLY DEFERRED\r\n			"
+, "already exists" );
+		case 148:
+			up.execStep( null );
+		case 149:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY subscription \r\n					ADD CONSTRAINT subscription_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 150:
+			up.execStep( null );
+		case 151:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY tag \r\n					ADD CONSTRAINT tag_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 152:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY user_property \r\n					ADD CONSTRAINT user_property_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 153:
+			up.execStep( null );
+		case 154:
+			up.execStep( null );
+		case 155:
+			up.execStep( null );
+		case 156:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY visited \r\n					ADD CONSTRAINT visited_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 157:
+			up.execStep( null );
+
+		// problematic constraints
+		case 158:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY file_node \r\n					ADD CONSTRAINT file_node_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 159:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY node_msg \r\n					ADD CONSTRAINT node_msg_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 160:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY node \r\n					ADD CONSTRAINT node_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 161:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY node_property \r\n					ADD CONSTRAINT node_property_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 162:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY subscription \r\n					ADD CONSTRAINT subscription_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 163:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY tag \r\n					ADD CONSTRAINT tag_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 164:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY view_count \r\n					ADD CONSTRAINT view_count_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 165:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY visited \r\n					ADD CONSTRAINT visited_last_node_id_fkey FOREIGN KEY (last_node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		case 166:
+			execCatching( 
+		"\r\n				ALTER TABLE ONLY visited \r\n					ADD CONSTRAINT visited_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE\r\n			"
+, "already exists" );
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/DbSiteUpdater.jmp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,887 @@
+<%
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+
+final class DbSiteUpdater {
+	private static final Logger logger = LoggerFactory.getLogger(DbSiteUpdater.class);
+
+	// don't forget to update site.schema
+	static final int version = 167;
+
+	private static class Result {
+		private static final long wait = 2000L;  // 2 seconds
+		private final long waitUntil = System.currentTimeMillis() + wait;
+		private DbSiteUpdater updater;
+		private SQLException sqlException = null;
+		private RuntimeException runtimeException = null;
+		private Error error = null;
+		private final String user;
+
+		private Result(DbDatabase db,String user) {
+			updater = new DbSiteUpdater(db,this,user);
+			this.user = user;
+			updater.start();
+		}
+
+		private synchronized void check() throws SQLException {
+			if( !isDone() ) {
+				long now = System.currentTimeMillis();
+				if( now < waitUntil ) {
+					try {
+						wait( waitUntil - now );
+					} catch(InterruptedException e) {
+						throw new RuntimeException(e);
+					}
+				}
+				if( !isDone() )
+					throw new UpdatingException(user);
+			}
+			if( sqlException != null )
+				throw sqlException;
+			if( runtimeException != null )
+				throw runtimeException;
+			if( error != null )
+				throw error;
+		}
+
+		synchronized boolean isDone() {
+			return updater==null;
+		}
+
+		synchronized void done(Throwable e) {
+			updater = null;
+			if( e != null ) {
+				if( e instanceof SQLException ) {
+					sqlException = (SQLException)e;
+				} else if( e instanceof RuntimeException ) {
+					runtimeException = (RuntimeException)e;
+				} else if( e instanceof Error ) {
+					error = (Error)e;
+				} else {
+					runtimeException = new RuntimeException("invalid exception type",e);
+				}
+			}
+			notifyAll();
+		}
+	}
+
+	private static final Map<String,Result> userResults = new HashMap<String,Result>();
+	private static final Object lock = new Object();
+
+	static void update(String user,DbDatabase db) throws SQLException {
+		if( user.equals("global") )
+			return;  // done from command line
+		getResult(user,db).check();
+	}
+
+	private static Result getResult(String user,DbDatabase db) throws SQLException {
+		synchronized(lock) {
+			Result result = userResults.get(user);
+			if( result == null ) {
+				result = new Result(db,user);
+				userResults.put(user,result);
+			}
+			return result;
+		}
+	}
+
+	// for shell
+	public static void clearResult(long siteId) {
+		String user = SiteKey.getInstance(siteId).schema();
+		synchronized(lock) {
+			userResults.remove(user);
+		}
+	}
+
+
+
+
+	private final Result result;
+	private final DbUpdater up;
+
+	private DbSiteUpdater(DbDatabase db,Result result,String schema) {
+		this.up = new DbUpdater(db,schema);
+		this.result = result;
+	}
+
+	private void start() {
+		try {  // needed for site creation because version is only up to date in current transaction
+			if( up.dbVersion() == version ) {
+				result.done(null);
+				return;
+			}
+		} catch(RuntimeException e) {
+			result.done(e);
+			return;
+		} catch(Error e) {
+			result.done(e);
+			return;
+		}
+		Executors.executeNow(new Runnable(){public void run(){
+			try {
+				go();
+				result.done(null);
+			} catch(SQLException e) {
+				result.done(e);
+			} catch(RuntimeException e) {
+				result.done(e);
+			} catch(Error e) {
+				result.done(e);
+			} finally {
+				if( !result.isDone() )
+					logger.error("result isn't done");
+			}
+		}});
+	}
+
+	void go() throws SQLException {
+		try {
+			update();
+			if( up.databaseVersion() != version )
+				throw new RuntimeException();
+		} finally {
+			up.close();
+		}
+	}
+
+	@Override public String toString() {
+		return "DbSiteUpdater-" + up.schema;
+	}
+
+	private void execCatching(String sql,String exStr) throws SQLException {
+		up.begin();
+		try {
+			up.exec(sql);
+		} catch(SQLException e) {
+			if( !e.getMessage().contains(exStr) )
+				throw e;
+			logger.info(e.toString());
+			up.exec("rollback");
+		}
+		up.commit();
+	}
+
+	void update() throws SQLException {
+		switch( up.dbVersion() ) {
+		case 0:
+			up.execStep( %>
+				create index embed_key_site_idx on embed_key(site_id)
+			<% );
+		case 1:
+			up.execStep( %>
+				create index file_avatar_site_idx on file_avatar(site_id)
+			<% );
+		case 2:
+			up.execStep( %>
+				create index file_node_site_idx on file_node(site_id)
+			<% );
+		case 3:
+			up.execStep( %>
+				create index file_temp_site_idx on file_temp(site_id)
+			<% );
+		case 4:
+			up.execStep( %>
+				create index mailing_list_site_idx on mailing_list(site_id)
+			<% );
+		case 5:
+			up.execStep( %>
+				create index node_msg_site_idx on node_msg(site_id)
+			<% );
+		case 6:
+			up.execStep( %>
+				create index node_property_site_idx on node_property(site_id)
+			<% );
+		case 7:
+			up.execStep( %>
+				create index paid_site_idx on paid(site_id)
+			<% );
+		case 8:
+			up.execStep( %>
+				create index registration_site_idx on registration(site_id)
+			<% );
+		case 9:
+			up.execStep( %>
+				create index subscription_site_idx on subscription(site_id)
+			<% );
+		case 10:
+			up.execStep( %>
+				create index user_property_site_idx on user_property(site_id)
+			<% );
+		case 11:
+			up.execStep( %>
+				create index view_count_site_idx on view_count(site_id)
+			<% );
+		case 12:
+			up.execStep( %>
+				create index visited_site_idx on visited(site_id)
+			<% );
+
+		case 13:
+			up.execStep( %>
+				create index tag_node_idx on tag(node_id)
+			<% );
+		case 14:
+			up.execStep( %>
+				create index tag_user_idx on tag(user_id)
+			<% );
+		case 15:
+			up.execStep( %>
+				create index embed_key_node_idx on embed_key(node_id)
+			<% );
+		case 16:
+			up.execStep( %>
+				create index visited_node_idx on visited(node_id)
+			<% );
+		case 17:
+			up.execStep( %>
+				create index visited_last_node_idx on visited(last_node_id)
+			<% );
+		case 18:
+			up.execStep( %>
+				alter table node add column poll_option_count integer default null
+			<% );
+		case 19:
+			up.execStep( %>
+				alter table configuration drop column site_id
+			<% );
+		case 20:
+			up.execStep( %>
+				alter table configuration ADD PRIMARY KEY (name)
+			<% );
+		case 21:
+			up.execStep( %>
+				alter table embed_key drop column site_id
+			<% );
+		case 22:
+			up.execStep( %>
+				alter table file_avatar drop column site_id
+			<% );
+		case 23:
+			up.execStep( %>
+				alter table file_node drop column site_id
+			<% );
+		case 24:
+			up.execStep( %>
+				alter table file_temp drop column site_id
+			<% );
+		case 25:
+			up.execStep( %>
+				alter table mailing_list drop column site_id
+			<% );
+		case 26:
+			up.execStep( %>
+				alter table node_msg drop column site_id
+			<% );
+		case 27:
+			up.execStep( %>
+				alter table node_property drop column site_id
+			<% );
+		case 28:
+			up.execStep( %>
+				alter table paid drop column site_id
+			<% );
+		case 29:
+			up.execStep( %>
+				alter table registration drop column site_id
+			<% );
+		case 30:
+			up.execStep( %>
+				alter table subscription drop column site_id
+			<% );
+		case 31:
+			up.execStep( %>
+				alter table user_property drop column site_id
+			<% );
+		case 32:
+			up.execStep( %>
+				alter table view_count drop column site_id
+			<% );
+		case 33:
+			up.execStep( %>
+				alter table visited drop column site_id
+			<% );
+		case 34:
+			up.execStep( %>
+				alter table file_site drop column site_id
+			<% );
+		case 35:
+			up.execStep( %>
+				alter table file_site ADD PRIMARY KEY (name)
+			<% );
+		case 36:
+			up.execStep( %>
+				alter table module drop column site_id
+			<% );
+		case 37:
+			up.execStep( %>
+				alter table module ADD PRIMARY KEY (module_name)
+			<% );
+		case 38:
+			up.execStep( %>
+				alter table site_property drop column site_id
+			<% );
+		case 39:
+			up.execStep( %>
+				alter table site_property ADD PRIMARY KEY (key)
+			<% );
+		case 40:
+			up.execStep( %>
+				alter table tweak drop column site_id
+			<% );
+		case 41:
+			up.execStep( %>
+				alter table tweak ADD PRIMARY KEY (tweak_name)
+			<% );
+		case 42:
+			up.execStep( %>
+				alter table tag drop column site_id
+			<% );
+		case 43:
+			up.execStep( %>
+				CREATE UNIQUE INDEX tag_idx
+					ON tag (node_id, user_id, label);
+			<% );
+		case 44:
+			up.execStep( %>
+				drop index tag_node_idx
+			<% );
+		case 45:
+			up.execStep( %>
+				alter table user_ drop column site_id
+			<% );
+		case 46:
+			up.execStep( %>
+				CREATE UNIQUE INDEX user_email_idx
+					ON user_ (lower(email));
+			<% );
+		case 47:
+			up.execStep( %>
+				CREATE UNIQUE INDEX user_name_idx
+					ON user_ (lower(name));
+			<% );
+		case 48:
+			up.execStep( %>
+				alter table node drop column site_id
+			<% );
+		case 49:
+			up.execStep( %>
+				CREATE INDEX node_cookie_idx
+					ON node (cookie)
+					WHERE cookie IS NOT NULL;
+			<% );
+		case 50:
+			up.execStep( %>
+				CREATE INDEX node_message_id_idx
+					ON node (lower(message_id))
+					WHERE message_id IS NOT NULL;
+			<% );
+		case 51:
+			up.execStep( %>
+				drop table embed_key
+			<% );
+		case 52:
+			up.execStep( %>
+				delete from site_property where key = '__embedding_warning'
+			<% );
+		case 53:
+			up.execStep( %>
+				alter table site drop column site_id
+			<% );
+		case 54:
+			up.execStep( %>
+				alter table site add column one integer default 1 not null
+			<% );
+		case 55:
+			up.execStep( %>
+				alter table site ADD PRIMARY KEY (one)
+			<% );
+		case 56:
+			up.execStep( %>
+				alter table site add column content_type character varying;
+			<% );
+		case 57:
+			up.execStep( %>
+				update configuration set naml=E'<override_macro name="site_style">\n\t<n.overridden/>\n\t#search-box { text-align:left; }\n</override_macro>' where name='searchBoxAlignment';
+			<% );
+		case 58:
+			up.execStep( %>
+				update site set ad_type=null where ad_type='ADBRITE' or ad_type='ADSENSE'
+			<% );
+
+		case 59:
+			up.execStep( %>
+				alter table node
+					add column unsafe character varying default 'new',
+					add column safe_version integer
+			<% );
+		case 60:
+			up.execStep( %>
+				alter table user_
+					add column unsafe character varying default 'new',
+					add column safe_version integer
+			<% );
+
+		case 61:
+			up.execStep( %>
+				update site set ad_type='HOPELESS' where ad_type='COPYRIGHTED'
+			<% );
+		case 62:
+			up.execStep( %>
+				update site set ad_type='HOPELESS' where ad_type='OURS'
+			<% );
+		case 63:
+			up.execStep( %>
+				update site set ad_type=null, ad_free=<%=Integer.MAX_VALUE%> where ad_type='NONE'
+			<% );
+
+		case 64:
+			up.execStep( %>
+				alter table node
+					add column is_safe boolean
+			<% );
+		case 65:
+			up.execStep( %>
+				alter table user_
+					add column is_safe boolean
+			<% );
+		case 66:
+			up.execStep( null );
+		case 67:
+			up.execStep( null );
+		case 68:
+			up.execStep( %>
+				delete from tag where label='hide_ads'
+			<% );
+		case 69:
+			up.execStep( %>
+				update site set ad_type=null where ad_type='SOME_ADSENSE'
+			<% );
+		case 70:
+			up.execStep( %>
+				update site set ad_type='ADULT' where ad_type='HACK'
+			<% );
+		case 71:
+			up.execStep( %>
+				update site set content_type=null where content_type='FAMILY'
+			<% );
+		case 72:
+			up.execStep( %>
+				drop index if exists node_is_safe_idx
+			<% );
+		case 73:
+			up.execStep( %>
+				drop index if exists user_is_safe_idx
+			<% );
+		case 74:
+			up.execStep( %>
+				create index node_unsafe_idx on node(unsafe) where unsafe is not null and unsafe!='new'
+			<% );
+		case 75:
+			up.execStep( %>
+				create index user_unsafe_idx on user_(unsafe) where unsafe is not null and unsafe!='new'
+			<% );
+		case 76:
+			up.execStep( %>
+				update site set content_type=ad_type where content_type is null and ad_type is not null and ad_type!='HOPELESS'
+			<% );
+		case 77:
+			up.execStep( %>
+				update site set ad_type=content_type where content_type is not null and ad_type is null
+			<% );
+		case 78:
+			up.execStep( %>
+				update site set ad_type='ADULT' where content_type='ADULT' and ad_type='PORN'
+			<% );
+
+		case 79:
+			up.execStep( %>
+				alter table user_
+					drop column unsafe,
+					drop column safe_version,
+					drop column is_safe
+			<% );
+		case 80:
+			up.execStep( null );
+		case 81:
+			up.execStep( null );
+		case 82:
+			up.begin();
+			up.exec( %>
+				alter table node
+					drop column unsafe,
+					drop column safe_version,
+					drop column is_safe
+			<% );
+			up.exec( %>
+				alter table node
+					add column unsafe character varying,
+					add column safe_version integer,
+					add column is_safe boolean
+			<% );
+			up.commit();
+		case 83:
+			up.execStep( %>
+				create index node_unsafe_idx on node(unsafe) where safe_version is not null
+			<% );
+		case 84:
+			up.execStep( %>
+				delete from module where module_name = 'topic_ads';
+			<% );
+		case 85:
+			up.execStep( %>
+				alter table node
+					drop column unsafe,
+					drop column safe_version,
+					drop column is_safe
+			<% );
+		case 86:
+			up.execStep( %>
+				alter table site
+					add column when_created timestamp with time zone NOT NULL DEFAULT now()
+			<% );
+		case 87:
+			up.execStep( %>
+				update site
+					set when_created = node.when_created
+					from node
+					where node.node_id = site.root_node_id
+			<% );
+
+		case 88:
+			up.execStep( %>
+				alter table site
+					add column is_safe boolean NOT NULL DEFAULT true
+			<% );
+		case 89:
+			up.execStep( %>
+				update site set is_safe = false where ad_type is not null
+			<% );
+		case 90:
+			up.execStep( %>
+				update site set when_created = now() where not is_safe
+			<% );
+		case 91:
+			up.execStep( %>
+				alter table site rename column ad_type to old_ad_type
+			<% );
+		case 92:
+			up.execStep( %>
+				alter table site rename column content_type to old_content_type
+			<% );
+		case 93:
+			up.execStep( %>
+				alter table site
+					add column ad_credits_for_users boolean NOT NULL DEFAULT false
+			<% );
+		case 94:
+			up.execStep( %>
+				alter table site
+					add column monthly_views integer NOT NULL DEFAULT 0
+			<% );
+		case 95:
+			up.execStep( %>
+				delete from tag where label='group:Administrators' and node_id is null and user_id = (select user_id from user_ where email = 'pedro@nabble.com')
+			<% );
+
+		case 96:
+			execCatching( %>
+				alter table tweak ADD PRIMARY KEY (tweak_name)
+			<%, "multiple primary keys for table" );
+		case 97:
+			execCatching( %>
+				alter table mailing_list ADD PRIMARY KEY (node_id)
+			<%, "multiple primary keys for table" );
+		case 98:
+/*
+			execCatching( %>
+				alter table mailing_list ADD CONSTRAINT mailing_list_node_id_fkey FOREIGN KEY (node_id) REFERENCES node (node_id) ON DELETE CASCADE
+			<%, "already exists" );
+*/
+			up.execStep( null );
+		case 99:
+			execCatching( %>
+				CREATE INDEX forum_ml_idx ON mailing_list (lower(list_address))
+			<%, "already exists" );
+		case 100:
+			execCatching( %>
+				alter table node ADD PRIMARY KEY (node_id)
+			<%, "multiple primary keys for table" );
+		case 101:
+			execCatching( %>
+				alter table configuration ADD PRIMARY KEY (name)
+			<%, "multiple primary keys for table" );
+		case 102:
+			execCatching( %>
+				alter table file_avatar ADD PRIMARY KEY (user_id, name)
+			<%, "multiple primary keys for table" );
+		case 103:
+			execCatching( %>
+				alter table file_node ADD PRIMARY KEY (node_id, name)
+			<%, "multiple primary keys for table" );
+		case 104:
+			execCatching( %>
+				alter table file_site ADD PRIMARY KEY (name)
+			<%, "multiple primary keys for table" );
+		case 105:
+			execCatching( %>
+				alter table file_temp ADD PRIMARY KEY (user_id, name)
+			<%, "multiple primary keys for table" );
+		case 106:
+			execCatching( %>
+				CREATE INDEX file_temp_date_idx ON file_temp (date_)
+			<%, "already exists" );
+		case 107:
+			execCatching( %>
+				alter table module ADD PRIMARY KEY (module_name)
+			<%, "multiple primary keys for table" );
+		case 108:
+			execCatching( %>
+				CREATE INDEX node_cookie_idx ON node (cookie) WHERE cookie IS NOT NULL
+			<%, "already exists" );
+		case 109:
+			execCatching( %>
+				CREATE INDEX node_export ON node (export_permalink) WHERE export_permalink IS NOT NULL
+			<%, "already exists" );
+		case 110:
+			execCatching( %>
+				CREATE INDEX node_message_id_idx ON node (lower(message_id)) WHERE message_id IS NOT NULL
+			<%, "already exists" );
+		case 111:
+			execCatching( %>
+				CREATE INDEX node_ml2_idx ON node (lower(parent_message_id)) WHERE lower(parent_message_id) IS NOT NULL
+			<%, "already exists" );
+		case 112:
+			execCatching( %>
+				CREATE INDEX node_owner_idx ON node (owner_id, when_created)
+			<%, "already exists" );
+		case 113:
+			execCatching( %>
+				CREATE UNIQUE INDEX node_parent2_idx ON node (parent_id, when_created, node_id)
+			<%, "already exists" );
+		case 114:
+			execCatching( %>
+				CREATE UNIQUE INDEX node_parent_idx ON node (parent_id, last_node_date, node_id)
+			<%, "already exists" );
+		case 115:
+			execCatching( %>
+				CREATE INDEX node_sent_idx ON node (when_sent) WHERE when_sent IS NOT NULL
+			<%, "already exists" );
+		case 116:
+			execCatching( %>
+				CREATE UNIQUE INDEX pinned_node_idx ON node (parent_id, pin) WHERE pin IS NOT NULL
+			<%, "already exists" );
+		case 117:
+			execCatching( %>
+				CREATE INDEX post_date ON node (when_created)
+			<%, "already exists" );
+		case 118:
+			execCatching( %>
+				alter table node_msg ADD PRIMARY KEY (node_id)
+			<%, "multiple primary keys for table" );
+		case 119:
+			execCatching( %>
+				alter table node_property ADD PRIMARY KEY (node_id, key)
+			<%, "multiple primary keys for table" );
+		case 120:
+			execCatching( %>
+				alter table paid ADD PRIMARY KEY (user_id)
+			<%, "multiple primary keys for table" );
+		case 121:
+			execCatching( %>
+				alter table registration ADD PRIMARY KEY (key_)
+			<%, "multiple primary keys for table" );
+		case 122:
+			execCatching( %>
+				CREATE INDEX registration_date_idx ON registration (date_)
+			<%, "already exists" );
+		case 123:
+			execCatching( %>
+				alter table site ADD PRIMARY KEY (one)
+			<%, "multiple primary keys for table" );
+		case 124:
+			execCatching( %>
+				alter table site_property ADD PRIMARY KEY (key)
+			<%, "multiple primary keys for table" );
+		case 125:
+			execCatching( %>
+				alter table subscription ADD PRIMARY KEY (user_id, node_id)
+			<%, "multiple primary keys for table" );
+		case 126:
+			execCatching( %>
+				CREATE INDEX subscription_node_idx ON subscription (node_id)
+			<%, "already exists" );
+		case 127:
+			execCatching( %>
+				CREATE UNIQUE INDEX tag_idx ON tag (node_id, user_id, label)
+			<%, "already exists" );
+		case 128:
+			execCatching( %>
+				CREATE INDEX tag_user_idx ON tag (user_id)
+			<%, "already exists" );
+		case 129:
+			execCatching( %>
+				alter table user_ ADD PRIMARY KEY (user_id)
+			<%, "multiple primary keys for table" );
+		case 130:
+			execCatching( %>
+				CREATE UNIQUE INDEX user_email_idx ON user_ (lower(email))
+			<%, "already exists" );
+		case 131:
+			execCatching( %>
+				CREATE UNIQUE INDEX user_name_idx ON user_ (lower(name))
+			<%, "already exists" );
+		case 132:
+			execCatching( %>
+				alter table user_property ADD PRIMARY KEY (user_id, key)
+			<%, "multiple primary keys for table" );
+		case 133:
+			execCatching( %>
+				alter table view_count ADD PRIMARY KEY (node_id)
+			<%, "multiple primary keys for table" );
+		case 134:
+			execCatching( %>
+				alter table visited ADD PRIMARY KEY (user_id, node_id)
+			<%, "multiple primary keys for table" );
+		case 135:
+			execCatching( %>
+				CREATE INDEX visited_last_node_idx ON visited (last_node_id)
+			<%, "already exists" );
+		case 136:
+			execCatching( %>
+				CREATE INDEX visited_node_idx ON visited (node_id)
+			<%, "already exists" );
+
+		case 137:
+			execCatching( %>
+				ALTER TABLE ONLY file_avatar 
+					ADD CONSTRAINT file_avatar_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 138:
+			up.execStep( null );
+		case 139:
+			execCatching( %>
+				ALTER TABLE ONLY file_temp 
+					ADD CONSTRAINT file_temp_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 140:
+			execCatching( %>
+				ALTER TABLE ONLY mailing_list 
+					ADD CONSTRAINT mailing_list_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 141:
+			up.execStep( null );
+		case 142:
+			execCatching( %>
+				ALTER TABLE ONLY node 
+					ADD CONSTRAINT node_new_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES user_(user_id)
+			<%, "already exists" );
+		case 143:
+			execCatching( %>
+				ALTER TABLE ONLY node 
+					ADD CONSTRAINT node_node_id_fkey FOREIGN KEY (node_id) REFERENCES node_msg(node_id) DEFERRABLE INITIALLY DEFERRED
+			<%, "already exists" );
+		case 144:
+			up.execStep( null );
+		case 145:
+			up.execStep( null );
+		case 146:
+			execCatching( %>
+				ALTER TABLE ONLY paid 
+					ADD CONSTRAINT paid_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 147:
+			execCatching( %>
+				ALTER TABLE ONLY site 
+					ADD CONSTRAINT site_root_node_id_fkey FOREIGN KEY (root_node_id) REFERENCES node(node_id) DEFERRABLE INITIALLY DEFERRED
+			<%, "already exists" );
+		case 148:
+			up.execStep( null );
+		case 149:
+			execCatching( %>
+				ALTER TABLE ONLY subscription 
+					ADD CONSTRAINT subscription_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 150:
+			up.execStep( null );
+		case 151:
+			execCatching( %>
+				ALTER TABLE ONLY tag 
+					ADD CONSTRAINT tag_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 152:
+			execCatching( %>
+				ALTER TABLE ONLY user_property 
+					ADD CONSTRAINT user_property_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 153:
+			up.execStep( null );
+		case 154:
+			up.execStep( null );
+		case 155:
+			up.execStep( null );
+		case 156:
+			execCatching( %>
+				ALTER TABLE ONLY visited 
+					ADD CONSTRAINT visited_user_id_fkey FOREIGN KEY (user_id) REFERENCES user_(user_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 157:
+			up.execStep( null );
+
+		// problematic constraints
+		case 158:
+			execCatching( %>
+				ALTER TABLE ONLY file_node 
+					ADD CONSTRAINT file_node_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 159:
+			execCatching( %>
+				ALTER TABLE ONLY node_msg 
+					ADD CONSTRAINT node_msg_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 160:
+			execCatching( %>
+				ALTER TABLE ONLY node 
+					ADD CONSTRAINT node_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES node(node_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 161:
+			execCatching( %>
+				ALTER TABLE ONLY node_property 
+					ADD CONSTRAINT node_property_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 162:
+			execCatching( %>
+				ALTER TABLE ONLY subscription 
+					ADD CONSTRAINT subscription_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 163:
+			execCatching( %>
+				ALTER TABLE ONLY tag 
+					ADD CONSTRAINT tag_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 164:
+			execCatching( %>
+				ALTER TABLE ONLY view_count 
+					ADD CONSTRAINT view_count_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 165:
+			execCatching( %>
+				ALTER TABLE ONLY visited 
+					ADD CONSTRAINT visited_last_node_id_fkey FOREIGN KEY (last_node_id) REFERENCES node(node_id) ON DELETE CASCADE
+			<%, "already exists" );
+		case 166:
+			execCatching( %>
+				ALTER TABLE ONLY visited 
+					ADD CONSTRAINT visited_node_id_fkey FOREIGN KEY (node_id) REFERENCES node(node_id) ON DELETE CASCADE
+			<%, "already exists" );
+		}
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/DbUpdater.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,108 @@
+package nabble.model;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import fschmidt.db.DbDatabase;
+
+
+final class DbUpdater {
+	private static final Logger logger = LoggerFactory.getLogger(DbUpdater.class);
+
+	final DbDatabase db;
+	final String schema;
+	private int dbVersion;
+	Connection con;
+	Statement stmt;
+
+	DbUpdater(DbDatabase db,String schema) {
+		this.db = db;
+		this.schema = schema;
+		dbVersion = databaseVersion();
+	}
+
+	int databaseVersion() {
+		return databaseVersion(db,schema);
+	}
+
+	static int databaseVersion(DbDatabase db,String schema) {
+		try {
+			Connection con = db.getConnection();
+			try {
+				Statement stmt = con.createStatement();
+				ResultSet rs = stmt.executeQuery(
+					"select version from "+schema+".version"
+				);
+				rs.next();
+				int dbVersion = rs.getInt("version");
+				stmt.close();
+				return dbVersion;
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	int dbVersion() {
+		return dbVersion;
+	}
+
+	void begin()
+		throws SQLException
+	{
+		con = db.getConnection();
+		con.setAutoCommit(false);
+		stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_UPDATABLE);
+		logger.error(schema+" step "+dbVersion);
+	}
+
+	void commit()
+		throws SQLException
+	{
+		stmt.executeUpdate(
+			"update "+schema+".version set version=" + (++dbVersion)
+		);
+		stmt.close();
+		con.commit();
+		con.close();
+		con = null;
+	}
+
+	void exec(String sql)
+		throws SQLException
+	{
+		if( sql != null ) {
+			if( sql.trim().toLowerCase().startsWith("select ") ) {
+				stmt.executeQuery(sql);
+			} else {
+				stmt.executeUpdate(sql);
+			}
+		}
+	}
+
+	void execStep(String sql)
+		throws SQLException
+	{
+		begin();
+		exec(sql);
+		commit();
+	}
+
+	void close() {
+		if( con != null ) {
+			try {
+				con.close();
+			} catch(SQLException e) {
+				logger.error(schema,e);
+			}
+			con = null;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Executors.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,160 @@
+package nabble.model;
+
+import java.util.Date;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import fschmidt.db.DbDatabase;
+import fschmidt.util.executor.RunnableWrapper;
+import fschmidt.util.executor.ThreadPool;
+import fschmidt.util.executor.ScheduledThreadPool;
+import fschmidt.util.executor.ThreadTimer;
+import fschmidt.util.java.DateUtils;
+
+
+public final class Executors {
+	private Executors() {}  // never
+
+	private static final Logger logger = LoggerFactory.getLogger(Executors.class);
+
+	public static final ThreadTimer threadTimer = new ThreadTimer();
+
+	private static final RunnableWrapper dbCleanup = new RunnableWrapper() {
+		@Override public Runnable wrap(final Runnable command) {
+			return new Runnable(){public void run(){
+				try {
+					command.run();
+				} finally {
+					fschmidt.db.pool.Pool.threadReset();
+				}
+			}};
+		}
+	};
+
+	private static final ScheduledThreadPool backgroundExecutor = new ScheduledThreadPool(1);
+	static {
+		backgroundExecutor.addRunnableWrapper(dbCleanup);
+		backgroundExecutor.addRunnableWrapper(threadTimer);
+		backgroundExecutor.addRunnableWrapper(new TimedExecuteWrapper());
+	}
+	public static final ThreadPool foregroundExecutor = new ThreadPool(Init.get("poolSize",100));
+	static {
+		foregroundExecutor.addRunnableWrapper(dbCleanup);
+		foregroundExecutor.addRunnableWrapper(threadTimer);
+	}
+	private static Random rnd = new Random();
+
+	private static final long timeLimit = Init.get("execTimeLimitSeconds", 60L);
+	private static volatile boolean isShuttingDown = false;
+	private static long start = System.currentTimeMillis();
+
+	static void shutdown() {
+		isShuttingDown = true;
+		backgroundExecutor.shutdown();
+		foregroundExecutor.shutdown();
+		try {
+			while( !backgroundExecutor.awaitTermination(1,TimeUnit.SECONDS) ) {
+				logger.error("backgroundExecutor failed to shutdown");
+				for( Thread thread : backgroundExecutor.getThreads() ) {
+					if( thread.isAlive() ) {
+						Throwable t = new Throwable(thread.toString());
+						t.setStackTrace(thread.getStackTrace());
+						logger.error("backgroundExecutor thread",t);
+					}
+				}
+			}
+/* why does this matter?
+			while( !foregroundExecutor.awaitTermination(1,TimeUnit.SECONDS) ) {
+				logger.error("foregroundExecutor failed to shutdown");
+				for( Thread thread : foregroundExecutor.getThreads() ) {
+					if( thread.isAlive() ) {
+						Throwable t = new Throwable(thread.toString());
+						t.setStackTrace(thread.getStackTrace());
+						logger.error("foregroundExecutor thread",t);
+					}
+				}
+			}
+*/
+		} catch(InterruptedException e) {
+			logger.error("",e);
+		}
+	}
+
+	public static boolean isShuttingDown() {
+		long time = (System.currentTimeMillis() - start)/1000;
+		if( time > timeLimit )
+			logger.error("exec took too long, " + time + " seconds",new Exception());
+		start = System.currentTimeMillis();
+		return isShuttingDown;
+	}
+
+	private static final class TimedExecuteWrapper implements RunnableWrapper {
+		private ThreadLocal<Exception> trace = new ThreadLocal<Exception>() {
+			protected Exception initialValue() {
+				return new Exception("exec created");
+			}
+		};
+
+		@Override public Runnable wrap(final Runnable command) {
+			return new Runnable(){public void run(){
+				Thread.currentThread().setName("background-thread");
+				trace.get();
+				start = System.currentTimeMillis();
+				try {
+					command.run();
+				} finally {
+					long time = (System.currentTimeMillis() - start)/1000;
+					if( time > timeLimit )
+						logger.error("exec took too long, " + time + " seconds",trace);
+				}
+			}};
+		}
+	}
+
+	public static void executeNow(Runnable command) {
+		foregroundExecutor.execute(command);
+	}
+
+
+
+	public static void executeSometime(Runnable command) {
+		schedule(command,0,TimeUnit.SECONDS);
+	}
+
+	public static void schedule(Runnable command,long delay,TimeUnit unit) {
+		backgroundExecutor.schedule(command,delay,unit);
+	}
+
+	public static void scheduleWithFixedDelay(final Runnable command,long initialDelay,final long delay,final TimeUnit unit) {
+		Runnable repeatedCommand = new Runnable(){public void run(){
+			command.run();
+			schedule(this,delay,unit);
+		}};
+		schedule(repeatedCommand,initialDelay,unit);
+	}
+
+	public static void runDaily(Runnable task) {
+		scheduleWithFixedDelay(task, rnd.nextInt(60*60*24), 60*60*24, TimeUnit.SECONDS);
+	}
+
+	private static final long MILLIS_PER_DAY = 1000L*60L*60L*24L;
+
+	public static void runDaily(final Runnable task, int hour, int minute) {
+		final long millisAfterMidnight = ((long) hour * 60 + minute) * 60 * 1000;
+		Date now = new Date();
+		long time = now.getTime() - DateUtils.roundToDay(now).getTime();
+		long sleep = time < millisAfterMidnight
+			? millisAfterMidnight - time
+			: MILLIS_PER_DAY - (time - millisAfterMidnight)
+		;
+		scheduleWithFixedDelay(task, sleep/(1000L*60), 60*24, TimeUnit.MINUTES);
+	}
+
+	public static void executeAfterCommit(DbDatabase db,final Runnable command) {
+		db.runAfterCommit(new Runnable(){public void run(){
+			executeNow(command);
+		}});
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/ExtensionFactory.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,15 @@
+package nabble.model;
+
+import java.io.Serializable;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+
+public interface ExtensionFactory<S,T> {
+	public String getName();
+	public Class<T> extensionClass();
+	public T construct(S source);
+	public T construct(S source,ResultSet rs) throws SQLException;
+	public Serializable getExportData(S source);
+	public void saveExportData(S source,Serializable data);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/FileUpload.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,777 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.Listener;
+import fschmidt.html.HtmlTag;
+import fschmidt.util.java.ImageUtils;
+import nabble.view.web.forum.FileDownload;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileItemHeaders;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.awt.image.ImagingOpException;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+public final class FileUpload {
+	private static final Logger logger = LoggerFactory.getLogger(FileUpload.class);
+
+	public static final int MAX_IMAGE_SIZE = 1048576;
+	public static final int MAX_FILE_SIZE = 5242880;
+
+	public static class FileDetails {
+		private String name;
+		private Date date;
+
+		public FileDetails(String name, Date date) {
+			this.name = name;
+			this.date = date;
+		}
+
+		public String getName() { return name; }
+		public Date getDate() { return date; }
+	}
+
+	private static void addToSql(Message.Source src,StringBuilder sql) {
+		Message.SourceType type = src.getMessageSourceType();
+		sql.append( " from file_" ).append( type.getName() );
+		String idField = type.getIdField();
+		if( idField == null ) {
+			sql.append( " where true" );
+		} else {
+			sql.append( " where " ).append( idField ).append( " = ?" );
+		}
+	}
+
+	private static int setParams(Message.Source src,PreparedStatement pstmt)
+		throws SQLException
+	{
+		int i = 0;
+		if( src.getMessageSourceType().getIdField() != null ) {
+			pstmt.setLong(++i,src.getSourceId());
+		}
+		return i;
+	}
+
+	public static InputStream getFileContent(Message.Source src,String name) {
+		if( src==null )
+			return null;
+		Message.SourceType type = src.getMessageSourceType();
+		try {
+			final Connection con = src.getSite().getDb().getConnection();
+			StringBuilder sql = new StringBuilder();
+			sql.append( "select content" );
+			addToSql(src,sql);
+			sql.append( " and name = ?" );
+			final PreparedStatement pstmt = con.prepareStatement(sql.toString());
+			boolean isDone = false;
+			try {
+				int i = setParams(src,pstmt);
+				pstmt.setString(++i,name);
+				ResultSet rs = pstmt.executeQuery();
+				if( !rs.next() )
+					return null;
+				InputStream rtn = new FilterInputStream(rs.getBinaryStream("content")) {
+					public void close() throws IOException {
+						super.close();
+						try {
+							pstmt.close();
+							con.close();
+						} catch(SQLException e) {
+							throw new RuntimeException(e);
+						}
+					}
+				};
+				isDone = true;
+				return rtn;
+			} finally {
+				if( !isDone ) {
+					pstmt.close();
+					con.close();
+				}
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static boolean hasFile(Message.Source src,String name) {
+		Message.SourceType type = src.getMessageSourceType();
+		try {
+			final Connection con = src.getSite().getDb().getConnection();
+			StringBuilder sql = new StringBuilder();
+			sql.append( "select 'whatever'" );
+			addToSql(src,sql);
+			sql.append( " and name = ?" );
+			final PreparedStatement pstmt = con.prepareStatement(sql.toString());
+			try {
+				int i = setParams(src,pstmt);
+				pstmt.setString(++i,name);
+				return pstmt.executeQuery().next();
+			} finally {
+				pstmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static FileDetails[] getFileDetails(Message.Source src,String prefix) {
+		Message.SourceType type = src.getMessageSourceType();
+		try {
+			final Connection con = src.getSite().getDb().getConnection();
+			StringBuilder sql = new StringBuilder();
+			sql.append( "select name, date_" );
+			addToSql(src,sql);
+			if( prefix != null )
+				sql.append(  " and name like '" ).append( prefix ).append( "%'" );
+			sql.append( " order by name" );
+			final PreparedStatement pstmt = con.prepareStatement(sql.toString());
+			try {
+				setParams(src,pstmt);
+				List<FileDetails> names = new ArrayList<FileDetails>();
+				ResultSet set = pstmt.executeQuery();
+				while (set.next()) {
+					names.add(new FileDetails(set.getString("name"), set.getDate("date_")));
+				}
+				return names.toArray(new FileDetails[0]);
+			} finally {
+				pstmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static String getName(FileItem fi) {
+		String[] a = fi.getName().split("[\\\\/:]");
+		String[] b = a[a.length-1].split("\\.");
+		String ext2 = b[b.length-1];
+		int n = (int)(Math.random()*1000);
+		if(b[0].equals("image"))
+		b[0] = b[0]+n;
+		return b[0]+"."+ext2;
+	}
+
+	public static String uploadImage(final FileItem fi,Message.Source src, int resize)
+		throws ModelException
+	{
+		try {
+			return uploadImage1(fi,src,resize);
+		} catch(IOException e) {
+			throw ModelException.newInstance("upload_image_failed",e);
+		}
+	}
+
+	private static String uploadImage1(final FileItem fi,Message.Source src, int resize)
+		throws ModelException, IOException
+	{
+		InputStream in = fi.getInputStream();
+		BufferedImage bi;
+		try {
+			bi = ImageIO.read(in);
+		} catch(IllegalArgumentException e) {
+			throw ModelException.newInstance("upload_image_failed",e);
+		} catch (RuntimeException e) {
+			throw ModelException.newInstance("unknown_image_error",e);
+		}
+		in.close();
+		if (bi == null)
+			throw ModelException.newInstance("unsupported_image_type","Unsupported image type - please use PNG, JPG or GIF.");
+
+		String filename = getName(fi);
+		filename = filename.replaceAll("[ ']+","_"); // Replace spaces and single quotes with underscores
+		int iDot = filename.lastIndexOf('.');
+		if( iDot == -1 )
+			throw ModelException.newInstance("file_has_no_ending","File must have ending");
+		String ending = filename.substring(iDot+1).toLowerCase();
+		InputStream bais;
+		long size;
+		if (resize > 0) {
+			try {
+				bi = ImageUtils.getThumbnail(bi, resize, resize);
+				final byte[] contents = ImageUtils.asOutputStream(bi, ending).toByteArray();
+				bi = null;
+				size = contents.length;
+				bais = new ByteArrayInputStream(contents);
+			} catch (ImagingOpException e) {
+				throw ModelException.newInstance("unable_to_resize_image","Unable to resize image", e);
+			}
+		} else {
+			bi = null;
+			bais = fi.getInputStream();
+			size = fi.getSize();
+		}
+		final InputStream inputStream = bais;
+		if (size <= MAX_IMAGE_SIZE) {
+			filename = filename.substring(0,iDot+1) + ending;
+			return uploadFile2(size,filename,src, true,
+				new InputStreamFactory() {
+					public InputStream in() throws IOException {
+						return inputStream;
+					}
+				}
+			);
+		} else {
+			throw ModelException.newInstance("image_too_large","Image is too large: use the resize option to make it smaller. Maximum size 1MB.");
+		}
+	}
+
+	/*
+	static boolean isDifferent(final FileItem fi,Message.Source src)
+		throws ModelException
+	{
+		try {
+			InputStream inDb = getFileContent(src,fi.getName());
+			if( inDb==null )
+				return true;
+			InputStream inFi = null;
+			try {
+				inFi = fi.getInputStream();
+				return IoUtils.compare(inDb,inFi) != 0;
+			} finally {
+				if (inFi != null)
+					inFi.close();
+				inDb.close();
+			}
+		} catch(IOException e) {
+			throw ModelException.newInstance("file_io_exception",e);
+		}
+	}
+	*/
+
+	public static String uploadFile(final FileItem fi,Message.Source src)
+		throws ModelException
+	{
+		try {
+			return uploadFile1(fi,src);
+		} catch(IOException e) {
+			throw ModelException.newInstance("file_io_exception",e);
+		}
+	}
+
+	private static String uploadFile1(final FileItem fi,Message.Source src)
+		throws ModelException, IOException
+	{
+		String filename = getName(fi);
+		filename = filename.replaceAll("[ ']+", "_"); // Replace spaces and single quotes with underscores
+		long size = fi.getSize();
+		return uploadFile2(size,filename,src, true,
+			new InputStreamFactory() {
+				public InputStream in() throws IOException {
+					return fi.getInputStream();
+				}
+			}
+		);
+	}
+
+	private static interface InputStreamFactory {
+		public InputStream in() throws IOException;
+	}
+
+	private static String uploadFile2(long size, String filename, final Message.Source src, boolean checkSize, InputStreamFactory inf)
+		throws ModelException, IOException
+	{
+		if( "".equals(filename.trim()) )
+			throw ModelException.newInstance("empty_filename","empty filename");
+		if( size==0 )
+			throw ModelException.newInstance("empty_file","empty file");
+		if( size > MAX_FILE_SIZE && checkSize )
+			throw ModelException.newInstance("file_is_too_large","file is too large - maximum size 5mb");
+		synchronized(src) {
+			try {
+				Connection con = src.getSite().getDb().getConnection();
+				try {
+					{
+						StringBuilder sql = new StringBuilder();
+						sql.append( "delete" );
+						addToSql(src,sql);
+						sql.append( " and name = ?" );
+						PreparedStatement pstmt = con.prepareStatement(sql.toString());
+						int i = setParams(src,pstmt);
+						pstmt.setString(++i,filename);
+						pstmt.executeUpdate();
+						pstmt.close();
+					}
+					{
+						StringBuilder sql = new StringBuilder();
+						Message.SourceType type = src.getMessageSourceType();
+						sql.append( "insert into file_" ).append( type.getName() );
+						sql.append( " ( name, content" );
+						String idField = type.getIdField();
+						if( idField != null )
+							sql.append( ", " ).append( idField );
+						sql.append( " ) values (?,?" );
+						if( idField != null )
+							sql.append( ",?" );
+						sql.append( ")" );
+						PreparedStatement pstmt = con.prepareStatement(sql.toString());
+						pstmt.setString(1,filename);
+						InputStream in = inf.in();
+						pstmt.setBinaryStream(2,in,(int)size);
+						if( idField != null )
+							pstmt.setLong(3,src.getSourceId());
+						pstmt.executeUpdate();
+						in.close();
+						pstmt.close();
+					}
+					return filename;
+				} finally {
+					con.close();
+					src.getSite().getDb().runAfterCommit(new Runnable(){public void run(){
+						fireFileUpdateListeners(src);
+					}});
+				}
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+		}
+	}
+
+
+	private static List<Listener<Message.Source>> fileUpdateListeners = new ArrayList<Listener<Message.Source>>();
+
+	public static void addFileUpdateListener(Listener<Message.Source> listener) {
+		fileUpdateListeners.add(listener);
+	}
+
+	static void fireFileUpdateListeners(Message.Source src) {
+		for( Listener<Message.Source> listener : fileUpdateListeners ) {
+			listener.event(src);
+		}
+	}
+
+
+	static {
+		NodeImpl.addPostInsertListener(new Listener<NodeImpl>(){
+			public void event(NodeImpl node) {
+				Message.Format fmt = node.getMessage().getFormat();
+				if( !(fmt instanceof MailMessageFormat) && node.getOwner() instanceof User)
+					fixFileTags(node.getMessage(),(User)node.getOwner());
+			}
+		});
+		NodeImpl.addPostUpdateListener(new Listener<NodeImpl>(){
+			public void event(NodeImpl node) {
+				if( ModelHome.insideImportProcedure.get() )
+					return;
+				Message.Format fmt = node.getMessage().getFormat();
+				if( !(fmt instanceof MailMessageFormat)  )
+					deleteUnusedFiles(node.getMessage());
+			}
+		});
+	}
+
+	private static void deleteUnusedFiles(Message message) {
+		Message.Source src = message.getSource();
+		if (src == null)
+			return;
+		DbDatabase db = src.getSite().getDb();
+		Message.SourceType type = src.getMessageSourceType();
+		Set<String> names = getFileInfo(message.parse(),src).keySet();
+		StringBuilder sql = new StringBuilder();
+		sql.append( "delete" );
+		addToSql(src,sql);
+		if( !names.isEmpty() ) {
+			sql.append( " and name not in (" );
+			Iterator<String> iter = names.iterator();
+			sql.append( db.arcana().quote(iter.next()) );
+			while( iter.hasNext() ) {
+				sql.append( ',' );
+				sql.append( db.arcana().quote(iter.next()) );
+			}
+			sql.append( ")" );
+		}
+		try {
+			Connection con = db.getConnection();
+			PreparedStatement pstmt = con.prepareStatement(sql.toString());
+			setParams(src,pstmt);
+			pstmt.executeUpdate();
+			pstmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static void deleteFile(String fileName, Message.Source src) {
+		Message.SourceType type = src.getMessageSourceType();
+		try {
+			Connection con = src.getSite().getDb().getConnection();
+			try {
+				StringBuilder sql = new StringBuilder();
+				sql.append( "delete" );
+				addToSql(src,sql);
+				sql.append( " and name = ?" );
+				PreparedStatement pstmt = con.prepareStatement(sql.toString());
+				int i = setParams(src,pstmt);
+				pstmt.setString(++i,fileName);
+				pstmt.executeUpdate();
+				pstmt.close();
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static void fixFileTags(Message message,User user) {
+		for( Object obj : message.parse() ) {
+			if( !(obj instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)obj;
+			String tagName = tag.getName().toLowerCase();
+			if( tagName.equals("nabble_a") ) {
+				fix(tag,"href",message.getSource(),user);
+			} else if( tagName.equals("nabble_img") ) {
+				fix(tag,"src",message.getSource(),user);
+			}
+		}
+	}
+
+	public static void checkFileTags(Message message,Person visitor) throws ModelException {
+		Set<String> fileNames = new HashSet<String>();
+		if( visitor instanceof User ) {
+			User user = (User)visitor;
+			Message.Source srcTemp = Message.SourceType.getType('t').getSource(user.getSite(),user.getId());
+			FileDetails[] fileDetails = getFileDetails(srcTemp, null);
+			for (FileDetails d : fileDetails) {
+				fileNames.add(d.name);
+			}
+		}
+		for( Object obj : message.parse() ) {
+			if( !(obj instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)obj;
+			String tagName = tag.getName().toLowerCase();
+			if( tagName.equals("nabble_a") ) {
+				String href = HtmlTag.unquote(tag.getAttributeValue("href"));
+				if (!fileNames.contains(href))
+					throw new ModelException.InvalidFile(href);
+			} else if( tagName.equals("nabble_img") ) {
+				String src = HtmlTag.unquote(tag.getAttributeValue("src"));
+				if (!fileNames.contains(src))
+					throw new ModelException.InvalidFile(src);
+			}
+		}
+	}
+
+	private static void fix(HtmlTag tag,String fileAttr,Message.Source src,User user)
+	{
+		String filename = HtmlTag.unquote(tag.getAttributeValue(fileAttr));
+		if( filename==null )
+			return;
+		Message.SourceType type = src.getMessageSourceType();
+		try {
+			Connection con = src.getSite().getDb().getConnection();
+			try {
+				{
+					PreparedStatement pstmt = con.prepareStatement(
+						"select 'x' from file_temp"
+						+" where user_id = ?"
+						+" and name = ?"
+					);
+					pstmt.setLong(1,user.getId());
+					pstmt.setString(2,filename);
+					ResultSet rs = pstmt.executeQuery();
+					try {
+						if( !rs.next() )
+							return;
+					} finally {
+						rs.close();
+						pstmt.close();
+					}
+				}
+				{
+					StringBuilder sql = new StringBuilder();
+					sql.append( "delete" );
+					addToSql(src,sql);
+					sql.append( " and name = ?" );
+					PreparedStatement pstmt = con.prepareStatement(sql.toString());
+					int i = setParams(src,pstmt);
+					pstmt.setString(++i,filename);
+					pstmt.executeUpdate();
+					pstmt.close();
+				}
+				PreparedStatement pstmt = con.prepareStatement(
+					"insert into file_" + type.getName()
+					+" (" + type.getIdField() + ", name, content)"
+					+" select ?, name, content"
+					+" from file_temp"
+					+" where user_id = ?"
+					+" and name=?"
+				);
+				pstmt.setLong(1,src.getSourceId());
+				pstmt.setLong(2,user.getId());
+				pstmt.setString(3,filename);
+				pstmt.executeUpdate();
+				pstmt.close();
+				{
+					Statement stmt = con.createStatement();
+					stmt.executeUpdate(
+						"delete from file_temp"
+						+" where date_ < " + Db.arcana.dateSub("now()",1,"day")
+					);
+					stmt.close();
+				}
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static void processFileTags(List<Object> list,Message.Source src) {
+		if( src == null )
+			return;
+		for( Object obj : list ) {
+			if( !(obj instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)obj;
+			String tagName = tag.getName().toLowerCase();
+			if( tagName.equals("nabble_a") ) {
+				String filename = HtmlTag.unquote(tag.getAttributeValue("href"));
+				if( filename==null )
+					continue;
+				String url = getUrl(filename,src);
+				tag.setAttribute("href",HtmlTag.quote(url));
+				tag.setName("a");
+				if( tag.getAttributeValue("target") == null ) {
+					tag.setAttribute("target","\"_top\"");
+				}
+			} else if( tagName.equals("/nabble_a") ) {
+				tag.setName("/a");
+			} else if( tagName.equals("nabble_img") ) {
+				String filename = HtmlTag.unquote(tag.getAttributeValue("src"));
+				if( filename==null )
+					continue;
+				String url = getUrl(filename,src);
+				tag.setAttribute("src",HtmlTag.quote(url));
+				tag.setName("img");
+			}
+		}
+	}
+
+	static Map<String,String> getFileInfo(List<Object> list,Message.Source src) {
+		Map<String,String> info = new HashMap<String,String>();
+		for( Object obj : list ) {
+			if( !(obj instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)obj;
+			String tagName = tag.getName().toLowerCase();
+			if( tagName.equals("nabble_a") ) {
+				String filename = HtmlTag.unquote(tag.getAttributeValue("href"));
+				if( filename==null )
+					continue;
+				String url = getUrl(filename,src);
+				info.put(filename,url);
+			} else if( tagName.equals("nabble_img") ) {
+				String filename = HtmlTag.unquote(tag.getAttributeValue("src"));
+				if( filename==null )
+					continue;
+				String url = getUrl(filename,src);
+				info.put(filename,url);
+			}
+		}
+		return info;
+	}
+
+	static String getUrl(String filename,Message.Source src) {
+		return FileDownload.url(filename,src);
+	}
+
+	public static void saveImage(BufferedImage image, String fileName, Message.Source src)
+			throws ModelException
+	{
+		try {
+			// First convert to byte array
+			ByteArrayOutputStream baos = new ByteArrayOutputStream();
+			ImageIO.write(image, "png", baos);
+			final byte[] bytes = baos.toByteArray();
+
+			InputStreamFactory factory = new InputStreamFactory() {
+				public InputStream in() throws IOException {
+					return new ByteArrayInputStream(bytes);
+				}
+			};
+			uploadFile2(bytes.length, fileName, src, true, factory);
+		} catch(IOException e) {
+			throw ModelException.newInstance("file_io_exception",e);
+		}
+	}
+
+	public static void saveFile(final byte[] contents, String fileName, Message.Source src)
+			throws ModelException
+	{
+		saveFile(contents, fileName, src, true);
+	}
+
+	public static void saveFile(final byte[] contents, String fileName, Message.Source src, boolean checkSize)
+			throws ModelException
+	{
+		try {
+			InputStreamFactory factory = new InputStreamFactory() {
+				public InputStream in() throws IOException {
+					return new ByteArrayInputStream(contents);
+				}
+			};
+			uploadFile2(contents.length, fileName, src, checkSize, factory);
+		} catch(IOException e) {
+			throw ModelException.newInstance("file_io_exception",e);
+		}
+	}
+
+
+	public static FileDetails[] getFiles(Message.Source src) {
+		return getFileDetails(src, null);
+	}
+
+	public static final class UrlFileItem implements FileItem {
+		private final URL url;
+
+		public UrlFileItem(URL url) {
+			this.url = url;
+		}
+
+		public InputStream getInputStream()
+			throws IOException
+		{
+			try {
+				return url.openConnection().getInputStream();
+			} catch(IllegalArgumentException e) {
+				logger.warn("",e);
+				throw new IOException(e.getMessage());
+			} catch(RuntimeException e) {
+				logger.error("url = "+url,e);
+				throw e;
+			}
+		}
+
+		public String getContentType() {
+			throw new UnsupportedOperationException();
+		}
+
+		public String getName() {
+			String s = url.getPath();
+			int i = s.lastIndexOf('/');
+			if( i != -1 )
+				s = s.substring(i+1);
+			return s;
+		}
+
+		public boolean isInMemory() {
+			return false;
+		}
+
+		public long getSize() {
+			try {
+				long len = url.openConnection().getContentLength();
+				if( len == -1 ) {
+					len = 0;
+					InputStream in = getInputStream();
+					while(true) {
+						long n = in.skip(1000000L);
+						len += n;
+						if( n==0 ) {
+							if( in.read() == -1 )
+								break;
+							len++;
+						}
+					}
+					in.close();
+				}
+				return len;
+			} catch(IOException e) {
+				throw new RuntimeException(e);
+			}
+		}
+
+		public byte[] get() {
+			throw new UnsupportedOperationException();
+		}
+
+		public java.lang.String getString(java.lang.String encoding)
+			throws java.io.UnsupportedEncodingException
+		{
+			throw new UnsupportedOperationException();
+		}
+
+		public java.lang.String getString() {
+			throw new UnsupportedOperationException();
+		}
+
+		public void write(java.io.File file)
+			throws java.lang.Exception
+		{
+			throw new UnsupportedOperationException();
+		}
+
+		public void delete() {
+			throw new UnsupportedOperationException();
+		}
+
+		public java.lang.String getFieldName() {
+			throw new UnsupportedOperationException();
+		}
+
+		public void setFieldName(java.lang.String name) {
+			throw new UnsupportedOperationException();
+		}
+
+		public boolean isFormField() {
+			return false;
+		}
+
+		public void setFormField(boolean state) {
+			throw new UnsupportedOperationException();
+		}
+
+		public java.io.OutputStream getOutputStream()
+			throws java.io.IOException
+		{
+			throw new UnsupportedOperationException();
+		}
+
+		public FileItemHeaders getHeaders() {
+			throw new UnsupportedOperationException();
+		}
+
+		public void setHeaders(FileItemHeaders fileItemHeaders) {
+			throw new UnsupportedOperationException();
+		}
+
+		public String toString() {
+			return "UrlFileItem-"+url;
+		}
+	}
+
+
+
+	static void nop() {}
+
+	private FileUpload() {}  // never
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/FilteredNodeIterator.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,38 @@
+package nabble.model;
+
+import java.util.NoSuchElementException;
+import fschmidt.util.java.Filter;
+
+
+public final class FilteredNodeIterator extends NodeIterator<Node> {
+	private final NodeIterator<? extends Node> iter;
+	private final Filter<Node> filter;
+	private Node next = null;
+
+	public FilteredNodeIterator(NodeIterator<? extends Node> iter,Filter<Node> filter) {
+		this.iter = iter;
+		this.filter = filter;
+	}
+
+	public boolean hasNext() {
+		while( next == null && iter.hasNext() ) {
+			Node node = iter.next();
+			if( filter.ok(node) )
+				next = node;
+		}
+		return next != null;
+	}
+
+	public Node next() throws NoSuchElementException {
+		if( !hasNext() )
+			throw new NoSuchElementException();
+		Node node = next;
+		next = null;
+		return node;
+	}
+
+	public void close() {
+		iter.close();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Init.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,213 @@
+package nabble.model;
+
+import fschmidt.util.java.BasicRMIClientSocketFactory;
+import fschmidt.util.java.BasicRMIServerSocketFactory;
+import luan.LuanException;
+import luan.Luan;
+import luan.LuanTable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+public final class Init {
+
+	private static final Logger logger = LoggerFactory.getLogger(Init.class);
+
+	private static final Map initMap;
+
+	private static final Thread shutdownThread = new Thread(new Runnable(){public void run() {
+		logger.info("begin shutdown");
+		Batch.shutdown();
+		logger.info("batch shutdown done");
+		for( Runnable r : shutdownHooks ) {
+			r.run();
+		}
+		logger.info("shutdownHooks done");
+		if( modelHomeStarted ) {
+			Executors.shutdown();
+			logger.info("Executors shutdown done");
+		}
+		if( luceneStarted ) {
+			Lucene.shutdown();
+			logger.info("Lucene shutdown done");
+		}
+		if( dailyNumberStarted ) {
+			DailyNumber.shutdown();
+		}
+		logger.info("end shutdown");
+		cachingfilter.CachingFilter.shutdown();
+		logger.info("CachingFilter shutdown done");
+	}});
+
+
+	static volatile boolean luceneStarted = false;
+	static volatile boolean modelHomeStarted = false;
+	static volatile boolean dailyNumberStarted = false;
+	static {
+		setCustomProperties();
+
+//		ClassLoader cl = Init.class.getClassLoader();
+//		interp.setClassLoader(cl);
+//		Thread.currentThread().setContextClassLoader(cl);
+		try {
+			Luan luan = new Luan();
+			LuanTable mod = (LuanTable)luan.eval("return require 'file:conf/Init.luan'");
+			initMap = mod.asMap();
+			Runtime.getRuntime().addShutdownHook(shutdownThread);
+		} catch(LuanException e) {
+			logger.error(e.getLuanStackTraceString());
+			System.exit(-1);
+			throw new RuntimeException();  // for compiler
+		} catch(Throwable e) {
+			logger.error("",e);
+			System.exit(-1);
+			throw new RuntimeException();  // for compiler
+		}
+	}
+
+	private static List<Runnable> shutdownHooks = new ArrayList<Runnable>();
+
+	public static void addShutdownHook(Runnable r) {
+		shutdownHooks.add(r);
+	}
+
+	private static void setCustomProperties() {
+		// InetAddress cache should last 30 seconds only.
+		System.setProperty("networkaddress.cache.ttl", "30");
+	}
+
+	public static Object get(String var) {
+		return initMap.get(var);
+	}
+
+	public static Integer getInteger(String var) {
+		Number n = (Number)get(var);
+		if( n==null )
+			return null;
+		int i = n.intValue();
+		if( i != n.doubleValue() )
+			throw new RuntimeException("init var '"+var+"' isn't an integer");
+		return i;
+	}
+
+	public static Float getFloat(String var) {
+		Number n = (Number)get(var);
+		return n==null ? null : n.floatValue();
+	}
+
+	public static Set getSet(String var) {
+		return (Set)get(var);
+	}
+
+	public static <T> T get(String var,T defaultVal) {
+		if( defaultVal instanceof Integer ) {
+			@SuppressWarnings("unchecked")
+			T obj = (T)getInteger(var);
+			return obj!=null ? obj : defaultVal;
+		}
+		if( defaultVal instanceof Float ) {
+			@SuppressWarnings("unchecked")
+			T obj = (T)getFloat(var);
+			return obj!=null ? obj : defaultVal;
+		}
+		if( defaultVal instanceof Set ) {
+			@SuppressWarnings("unchecked")
+			T obj = (T)getSet(var);
+			return obj!=null ? obj : defaultVal;
+		}
+		if( defaultVal != null ) {
+			Class defaultCls = defaultVal.getClass();
+			if( defaultCls.isArray() && !defaultCls.getComponentType().isPrimitive() ) {
+				Object obj = get(var);
+				if( obj instanceof LuanTable ) {
+					LuanTable t = (LuanTable)obj;
+					if( t.isList() ) {
+						@SuppressWarnings("unchecked")
+						T rtn = (T)t.asList().toArray((Object[])defaultVal);
+						return rtn;
+					}
+				}
+			}
+		}
+		@SuppressWarnings("unchecked")
+		T obj = (T)get(var);
+		return obj!=null ? obj : defaultVal;
+	}
+
+	public static final boolean hasDaemons = nabble.utils.Jetty.isJetty;
+
+	public static final String tempDir = (String)Init.get("local_dir")+"temp/"; // General purpose folder
+
+	public static final int quotedLinesToHide = Init.get("quotedLinesToHide",10);
+
+
+	private static final RMIClientSocketFactory rmiClientSocketFactory = new BasicRMIClientSocketFactory();
+	private static RMIServerSocketFactory rmiServerSocketFactory;
+	public static final String localRmiServer = (String)get("localRmiServer");
+	private static Registry registry;
+
+	static {
+		if( localRmiServer!=null && Init.hasDaemons ) {
+			try {
+				String[] a = localRmiServer.split(":");
+				String rmiHost = a[0];
+				String rmiPort = a[1];
+				System.setProperty("java.rmi.server.hostname", rmiHost);
+				rmiServerSocketFactory = new InternalRMIServerSocketFactory(InetAddress.getByName(rmiHost));
+				logger.info("Starting rmi server on port "+rmiPort);
+				registry = LocateRegistry.createRegistry(Integer.parseInt(rmiPort), rmiClientSocketFactory, rmiServerSocketFactory);
+				nabble.model.export.ImportServerImpl.bind();
+			} catch (Exception e) {
+				logger.error("",e);
+				System.exit(-1);
+			}
+		}
+	}
+
+	public static void rmiBind(String name,Remote obj) {
+		try {
+			registry.bind(name,rmiExport(obj));
+		} catch (Exception e) {
+			logger.error("rmiBind failed, exiting",e);
+			System.exit(-1);
+		}
+	}
+
+	public static <T extends Remote> T rmiExport(T obj) throws RemoteException {
+		@SuppressWarnings("unchecked")
+		T t = (T)UnicastRemoteObject.exportObject( obj,0, rmiClientSocketFactory, rmiServerSocketFactory);
+		return t;
+	}
+
+	private static class InternalRMIServerSocketFactory extends BasicRMIServerSocketFactory {
+		private final InetAddress host;
+
+		InternalRMIServerSocketFactory(InetAddress host) {
+			this.host = host;
+		}
+
+		public ServerSocket createServerSocket(int port) throws IOException {
+			return new ServerSocket(port, 0, host);
+		}
+	}
+
+	public static void nop() {}
+
+	private Init() {}  // never
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/ListServer.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,698 @@
+package nabble.model;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public abstract class ListServer {
+
+	private final static Map<String, ListServer> servers = new HashMap<String, ListServer>();
+
+	public static ListServer getServer(String type) {
+		ListServer listServer = servers.get(type);
+		return listServer==null?unknown:listServer;
+	}
+
+	public static String[] getAllServerTypes() {
+		String[] s = servers.keySet().toArray(new String[servers.keySet().size()]);
+		Arrays.sort(s);
+		return s;
+	}
+
+	private String type;
+
+	private ListServer(String type) {
+		servers.put(type, this);
+		this.type = type;
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public String getViewName() {
+		return getType();
+	}
+
+	public abstract boolean supportsDeliveryOff();
+	public abstract boolean needsDefaults();
+	
+	abstract Mail defaultsMail(MailAddress emailAddress, String listAddress, String password);
+	abstract Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList);
+	abstract Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password);
+
+	public boolean canSubscribe() {
+		return true;
+	}
+
+	public boolean showInInitialSetup() {
+		return true;
+	}
+
+	String getEnvelopeFrom(Mail mail, MailingList mailingList) {
+		return mailingList.getSubscriberAddress().getAddrSpec();
+	}
+
+	protected Mail adminMail(MailAddress emailAddress, String adminAddress, String command) {
+		Mail mail = MailHome.newMail();
+		mail.setFrom(emailAddress);
+		mail.setTo(new MailAddress(adminAddress));
+		mail.setSentDate(new Date());
+		mail.setContent(new PlainTextContent(command));
+		return mail;
+	}
+
+	protected Mail subjectAdminMail(MailAddress emailAddress, String adminAddress, String subject) {
+		Mail mail = MailHome.newMail();
+		mail.setFrom(emailAddress);
+		mail.setTo(new MailAddress(adminAddress));
+		mail.setSentDate(new Date());
+		mail.setSubject(subject);
+		mail.setContent(new PlainTextContent(""));
+		return mail;
+	}
+
+	protected String listName(String listAddress) {
+		return listAddress.substring(0, listAddress.indexOf('@'));
+	}
+
+	protected String listDomain(String listAddress) {
+		return listAddress.substring(listAddress.indexOf('@')+1);
+	}
+
+	static abstract class Mailman extends ListServer {
+
+		protected Mailman(String type) {
+			super(type);
+		}
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return true;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			return adminMail(emailAddress, getAdminAddress(listAddress), "subscribe "+(password==null?"":password));
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			return adminMail(emailAddress, getAdminAddress(listAddress), "unsubscribe "+(password==null?"":password));
+		}
+
+		protected String getAdminAddress(String listAddress) {
+			return listAddress.replaceFirst("@", "-request@");
+		}
+	}
+
+	public static final ListServer mailman20 = new Mailman("mailman20") {
+
+		public String getViewName() {
+			return "Mailman 2.0.*";
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("set ack off ");
+			buf.append(password);
+			buf.append("\n");
+			buf.append("set nomail on ");
+			buf.append(password);
+			buf.append("\n");
+			buf.append("set notmetoo on ");
+			buf.append(password);
+			buf.append("\n");
+			buf.append("end\n");
+			return adminMail(emailAddress, getAdminAddress(listAddress), buf.toString());
+		}
+
+	};
+
+	public static final ListServer mailman21 = new Mailman("mailman21") {
+
+		public String getViewName() {
+			return "Mailman 2.1.*";
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("set authenticate ");
+			buf.append(password);
+			buf.append("\n");
+			buf.append("set delivery off\n");
+			buf.append("set ack off\n");
+			buf.append("set myposts off\n");
+			buf.append("set duplicates off\n");
+			buf.append("set reminders off\n");
+			buf.append("end\n");
+			return adminMail(emailAddress, getAdminAddress(listAddress), buf.toString());
+		}
+
+	};
+
+	public static final ListServer ezmlm = new ListServer("ezmlm") {
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return false;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			String suffix = useMainList?"-subscribe-":"-allow-subscribe-";
+			suffix += emailAddress.getAddrSpec().replace('@','=') + '@';
+			return adminMail(emailAddress, listAddress.replaceFirst("@", suffix), "");
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			String suffix = "-unsubscribe-" + emailAddress.getAddrSpec().replace('@','=') + '@';
+			return adminMail(emailAddress, listAddress.replaceFirst("@", suffix), "");
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			throw new UnsupportedOperationException();
+		}
+
+	};
+
+	public static final ListServer mlmmj = new ListServer("mlmmj") {
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return false;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			String suffix = useMainList?"+subscribe@":"+subscribe-nomail@";
+			return adminMail(emailAddress, listAddress.replaceFirst("@", suffix), "");
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			return adminMail(emailAddress, listAddress.replaceFirst("@", "+unsubscribe@"), "");
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			throw new UnsupportedOperationException();
+		}
+
+	};
+
+	public static final ListServer yahoo = new ListServer("yahoo") {
+
+		public String getViewName() {
+			return "Yahoo! Groups";
+		}
+
+		String getEnvelopeFrom(Mail mail, MailingList mailingList) {
+			//  for yahoogroups it should be the users address
+			return mail.getFrom().getAddrSpec();
+		}
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return true;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			return adminMail(emailAddress, listAddress.replaceFirst("@", "-subscribe@"), "");
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			return adminMail(emailAddress, listAddress.replaceFirst("@", "-unsubscribe@"), "");
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			return adminMail(emailAddress, listAddress.replaceFirst("@", "-nomail@"), "");
+		}
+
+	};
+
+	private abstract static class ListProc extends ListServer {
+
+		protected ListProc(String type) {
+			super(type);
+		}
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return true;
+		}
+
+		public boolean showInInitialSetup() {
+			return false;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("subscribe ");
+			buf.append(listName(listAddress));
+			buf.append(" ");
+			buf.append(emailAddress.getDisplayName());
+			return adminMail(emailAddress, "listproc@"+listDomain(listAddress), buf.toString());
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("unsubscribe ");
+			buf.append(listName(listAddress));
+			return adminMail(emailAddress, "listproc@"+listDomain(listAddress), buf.toString());
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("set ");
+			buf.append(listName(listAddress));
+			buf.append(" mail postpone");
+			return adminMail(emailAddress, "listproc@"+listDomain(listAddress), buf.toString());
+		}
+
+	}
+
+	// cren listproc 
+	public final static ListServer listproc = new ListProc("listproc") {
+	};
+
+	// old listproc broken - uses smtp from only
+	public final static ListServer oldlistproc = new ListProc("oldlistproc") {
+		public boolean canSubscribe() {
+			return false;
+		}
+	};
+
+	// l-soft listserv
+	public static final ListServer listserv = new ListServer("listserv") {
+
+		public String getViewName() {
+			return "LISTSERV";
+		}
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return false;
+		}
+
+		public boolean showInInitialSetup() {
+			return false;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("subscribe ");
+			buf.append(listName(listAddress));
+			buf.append(" ");
+			buf.append("anonymous");
+			if (!useMainList)
+				buf.append(" with noack nomail norepro");
+			return adminMail(emailAddress, "listserv@"+listDomain(listAddress), buf.toString());
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("unsubscribe ");
+			buf.append(listName(listAddress));
+			return adminMail(emailAddress, "listserv@"+listDomain(listAddress), buf.toString());
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			throw new UnsupportedOperationException();
+		}
+
+	};
+
+	public static final ListServer lyris = new ListServer("lyris") {
+
+		public String getViewName() {
+			return "Lyris";
+		}
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return true;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("join ");
+			buf.append(listName(listAddress));
+			buf.append(" ");
+			buf.append(emailAddress.getDisplayName());
+			return adminMail(emailAddress, "lyris@"+listDomain(listAddress), buf.toString());
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("leave ");
+			buf.append(listName(listAddress));
+			return adminMail(emailAddress, "lyris@"+listDomain(listAddress), buf.toString());
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("set ");
+			buf.append(listName(listAddress));
+			buf.append(" noack nomail norepro");
+			return adminMail(emailAddress, "lyris@"+listDomain(listAddress), buf.toString());
+		}
+
+	};
+
+	public static final ListServer listserver = new ListServer("listserver") {
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return true;
+		}
+
+		public boolean showInInitialSetup() {
+			return false;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			return adminMail(emailAddress, "listserver@"+listDomain(listAddress),
+					"subscribe "+listName(listAddress)
+					);
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			return adminMail(emailAddress, "listserver@"+listDomain(listAddress),
+					"unsubscribe "+listName(listAddress)
+					);
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			return adminMail(emailAddress, "listserver@"+listDomain(listAddress), "nomail "+listName(listAddress));
+		}
+
+	};
+
+	public static final ListServer communigate = new ListServer("communigate") {
+
+		public String getViewName() {
+			return "CommuniGate";
+		}
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return false;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			String suffix = useMainList?"-subscribe@":"-null@";
+			return adminMail(emailAddress, listAddress.replaceFirst("@", suffix), "");
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			return adminMail(emailAddress, listAddress.replaceFirst("@", "-unsubscribe@"), "");
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			throw new UnsupportedOperationException();
+		}
+
+	};
+
+	public static final ListServer smartlist = new ListServer("smartlist") {
+
+		public String getViewName() {
+			return "SmartList";
+		}
+
+		public boolean supportsDeliveryOff() {
+			return false;
+		}
+
+		public boolean needsDefaults() {
+			return false;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			return subjectAdminMail(emailAddress, listAddress.replaceFirst("@", "-request@"), "subscribe");
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			return subjectAdminMail(emailAddress, listAddress.replaceFirst("@", "-request@"), "unsubscribe");
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			throw new UnsupportedOperationException();
+		}
+
+	};
+
+	public static final ListServer ecartis = new ListServer("ecartis") {
+
+		public String getViewName() {
+			return "Ecartis";
+		}
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return true;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			return subjectAdminMail(emailAddress, "ecartis@"+listDomain(listAddress), "subscribe "+listName(listAddress));
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			return subjectAdminMail(emailAddress, "ecartis@"+listDomain(listAddress), "unsubscribe "+listName(listAddress));
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			return subjectAdminMail(emailAddress, "ecartis@"+listDomain(listAddress), "set "+listName(listAddress)+" vacation");
+		}
+
+	};
+
+	public static final ListServer majordomo = new ListServer("majordomo") {
+
+		public String getViewName() {
+			return "Majordomo";
+		}
+
+		public boolean supportsDeliveryOff() {
+			return false;
+		}
+
+		public boolean needsDefaults() {
+			return false;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			return adminMail(emailAddress, "majordomo@"+listDomain(listAddress), "subscribe "+listName(listAddress));
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			return adminMail(emailAddress, "majordomo@"+listDomain(listAddress), "unsubscribe "+listName(listAddress));
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			throw new UnsupportedOperationException();
+		}
+
+	};
+
+	public static final ListServer majordomo2 = new ListServer("majordomo2") {
+
+		public String getViewName() {
+			return "Majordomo 2";
+		}
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return false;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			return adminMail(emailAddress, "majordomo@"+listDomain(listAddress),
+					"subscribe-set-welcome "+listName(listAddress)+(useMainList?" ":" nomail,")+"noselfcopy,noackpost");
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			return adminMail(emailAddress, "majordomo@"+listDomain(listAddress), "unsubscribe "+listName(listAddress));
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			throw new UnsupportedOperationException();
+		}
+
+	};
+
+	public static final ListServer sympa = new ListServer("sympa") {
+
+		public String getViewName() {
+			return "Sympa";
+		}
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return true;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("subscribe ");
+			buf.append(listName(listAddress));
+			buf.append(" ");
+			buf.append(emailAddress.getDisplayName());
+			return adminMail(emailAddress, "sympa@"+listDomain(listAddress), buf.toString());
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			StringBuilder buf = new StringBuilder();
+			buf.append("unsubscribe ");
+			buf.append(listName(listAddress));
+			return adminMail(emailAddress, "sympa@"+listDomain(listAddress), buf.toString());
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			String listname = listName(listAddress);
+			StringBuilder buf = new StringBuilder();
+			buf.append("set ");
+			buf.append(listname);
+			buf.append(" nomail\n");
+			buf.append("set ");
+			buf.append(listname);
+			buf.append(" not_me\n");
+			return adminMail(emailAddress, "sympa@"+listDomain(listAddress), buf.toString());
+		}
+
+	};
+
+	public static final ListServer google = new ListServer("google") {
+
+		public String getViewName() {
+			return "Google Groups";
+		}
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean needsDefaults() {
+			return true;
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			return adminMail(emailAddress, listAddress.replaceFirst("@", "+subscribe@"), "");
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			return adminMail(emailAddress, listAddress.replaceFirst("@", "+unsubscribe@"), "");
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			throw new UnsupportedOperationException(); // set nomail only via google website
+		}
+
+		public boolean canSubscribe() {
+			return true;
+		}
+
+	};
+
+	private static abstract class Unsupported extends ListServer {
+
+		protected Unsupported(String type) {
+			super(type);
+		}
+
+		public boolean needsDefaults() {
+			throw new UnsupportedOperationException();
+		}
+
+		Mail subscribeMail(MailAddress emailAddress, String listAddress, String password, boolean useMainList) {
+			throw new UnsupportedOperationException();
+		}
+
+		Mail unsubscribeMail(MailAddress emailAddress, String listAddress, String password) {
+			throw new UnsupportedOperationException();
+		}
+
+		Mail defaultsMail(MailAddress emailAddress, String listAddress, String password) {
+			throw new UnsupportedOperationException();
+		}
+
+		public boolean canSubscribe() {
+			return false;
+		}
+
+	}
+
+	public static final ListServer websitenomail = new Unsupported("websitenomail") {
+
+		public boolean supportsDeliveryOff() {
+			return true;
+		}
+
+		public boolean showInInitialSetup() {
+			return false;
+		}
+	};
+
+	public static final ListServer websitemail = new Unsupported("websitemail") {
+
+		public boolean supportsDeliveryOff() {
+			return false;
+		}
+
+		public boolean showInInitialSetup() {
+			return false;
+		}
+
+	};
+
+	public static final ListServer unknown = new Unsupported("unknown") {
+
+		public String getViewName() {
+			return "Unknown or Other";
+		}
+
+		public boolean supportsDeliveryOff() {
+			return false;
+		}
+
+	};
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Lucene.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,583 @@
+/*
+
+Copyright (C) 2004  Franklin Schmidt <frank@gustos.com>
+
+*/
+
+package nabble.model;
+
+import fschmidt.db.Listener;
+import fschmidt.util.java.CollectionUtils;
+import fschmidt.util.mail.MailEncodingException;
+import nabble.model.lucene.HitCollector;
+import nabble.model.lucene.IndexCache;
+import nabble.model.lucene.LuceneSearcher;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.help.Help;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.snowball.SnowballAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.NumericField;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanFilter;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.CachingWrapperFilter;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.FilterClause;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.NumericRangeFilter;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.util.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+public final class Lucene {
+	private static final Logger logger = LoggerFactory.getLogger(Lucene.class);
+
+	public static interface DocumentListener {
+		public void event(Node node,Document doc);
+	}
+
+	private static final int nodeIndexVersion = 3;
+
+	private static final String NODE_ID_FLD = "nodeId";
+	static final String KIND_FLD = "kind";
+	static final String SUBJECT_FLD = "subject";
+	static final String MESSAGE_FLD = "message";
+	static final String ANCESTORS_FLD = "ancestors";
+	static final String PARENT_ID_FLD = "parentId";
+	static final String DATE_FLD = "date";
+	private static final String RANGE_SEARCH_DATE_FLD = "rangeSearchDate";
+	private static final String DAY_FLD = "day";
+	static final String USER_ID_FLD = "userId";
+	static final String AUTHOR_FLD = "author";
+	static final String PRIVATE_NODE_FLD = "privateNode";
+	static final String MAILING_LIST_FLD = "mailingList";
+
+	static final Analyzer analyzer = new SnowballAnalyzer(Version.LUCENE_CURRENT,"English");
+
+	private static final List<DocumentListener> documentListeners = new ArrayList<DocumentListener>();
+
+	private Lucene() {}  // never
+
+	static LuceneSearcher newSearcher(Site site) throws IOException {
+		return nodeIndex.openSearcher(site.getId());
+	}
+
+	static long getNodeId(Document doc) {
+		return Long.parseLong(doc.get(NODE_ID_FLD));
+	}
+
+	static NodeImpl getNode(SiteImpl site, LuceneSearcher searcher, int docId) throws IOException {
+		return getNode( site, searcher.doc(docId) );
+	}
+
+	static NodeImpl getNode(SiteImpl site,Document doc) {
+		long nodeId = getNodeId(doc);
+		NodeImpl node = NodeImpl.getNode(site.siteKey,nodeId);
+		if( node==null ) {
+			logger.error("missing node "+nodeId+", removing from lucene");
+			removeNode(site,nodeId);
+		}
+		return node;
+	}
+
+	private static void add(final Node node) {
+		Document doc = document(node);
+		try {
+			IndexWriter indexWriter = nodeIndex.openIndexWriter(node.getSite().getId());
+			try {
+				indexWriter.addDocument(doc);
+			} finally {
+				indexWriter.close();
+			}
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+/*
+	private static void removeSite(long siteId) {
+		try {
+			nodeIndex.delete(siteId);
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+*/
+	private static void removeNode(Site site,long nodeId) {
+		Term term = new Term(NODE_ID_FLD,Long.toString(nodeId));
+		try {
+			IndexWriter indexWriter = nodeIndex.openIndexWriter(site.getId());
+			try {
+				indexWriter.deleteDocuments(term);
+			} finally {
+				indexWriter.close();
+			}
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static void update(final Node node) {
+		try {
+			Document doc = document(node);
+			if( doc==null ) {
+				removeNode(node.getSite(),node.getId());
+			} else {
+				IndexWriter indexWriter = nodeIndex.openIndexWriter(node.getSite().getId());
+				try {
+					indexWriter.updateDocument( new Term(NODE_ID_FLD,doc.get(NODE_ID_FLD)), doc );
+				} finally {
+					indexWriter.close();
+				}
+			}
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static void updateNode(SiteImpl site,long nodeId) {
+		Node node = NodeImpl.getNode(site.siteKey,nodeId);
+		if( node == null ) {
+			removeNode(site,nodeId);
+		} else {
+			update(node);
+		}
+	}
+
+	static {
+/*
+		SiteImpl.table.getPostDeleteListeners().add(new Listener<SiteImpl>(){
+			public void event(SiteImpl site) {
+				removeSite(site.getId());
+			}
+		});
+*/
+		NodeImpl.postDeleteListeners.add(new Listener<NodeImpl>(){
+			public void event(NodeImpl node) {
+				// remove descendants
+				Term term = new Term(ANCESTORS_FLD,Long.toString(node.getId()));
+				try {
+					IndexWriter indexWriter = nodeIndex.openIndexWriter(node.siteKey.getId());
+					try {
+						indexWriter.deleteDocuments(term);
+					} finally {
+						indexWriter.close();
+					}
+				} catch(IOException e) {
+					throw new RuntimeException(e);
+				}
+			}
+		});
+		NodeImpl.postInsertListeners.add(new Listener<NodeImpl>(){
+			public void event(final NodeImpl node) {
+				node.siteKey.getDb().runAfterCommit(new Runnable(){public void run(){
+					try {
+						add(node);
+					} catch(MailEncodingException e) {
+						logger.warn(node.toString(),e);
+					}
+				}});
+			}
+		});
+		NodeImpl.preUpdateListeners.add(new Listener<NodeImpl>(){
+			public void event(NodeImpl node) {
+				Set fields = node.getDbRecord().fields().keySet();
+				if( CollectionUtils.intersects(fields,nodeDbFields) ) {
+					final long nodeId = node.getId();
+					final SiteKey siteKey = node.siteKey;
+					siteKey.getDb().runAfterCommit(new Runnable() {
+						public void run() {
+							NodeImpl node = NodeImpl.getNode(siteKey,nodeId);
+							if (node != null) update(node);
+						}
+					});
+				}
+			}
+		});
+		MailingListImpl.postDeleteListeners.add(new Listener<MailingListImpl>(){
+			public void event(MailingListImpl mailingList) {
+				update(mailingList.getForum());
+			}
+		});
+		MailingListImpl.postInsertListeners.add(new Listener<MailingListImpl>(){
+			public void event(final MailingListImpl mailingList) {
+				mailingList.siteKey.getDb().runAfterCommit(new Runnable(){public void run(){
+					update(mailingList.getForum());
+				}});
+			}
+		});
+		MailingListImpl.preUpdateListeners.add(new Listener<MailingListImpl>(){
+			public void event(MailingListImpl mailingList) {
+				Set fields = mailingList.getDbRecord().fields().keySet();
+				if( CollectionUtils.intersects(fields,mailingListDbFields) ) {
+					final long nodeId = mailingList.getForum().getId();
+					final SiteKey siteKey = mailingList.siteKey;
+					siteKey.getDb().runAfterCommit(new Runnable() {
+						public void run() {
+							NodeImpl node = NodeImpl.getNode(siteKey,nodeId);
+							update(node);
+						}
+					});
+				}
+			}
+		});
+	}
+
+	static void staleNode(NodeImpl node) throws IOException {
+		if( node==null )
+			return;
+		logger.debug("staleNode update");
+		updateNodes( node.getSiteImpl(), descendants(node) );
+		logger.debug("staleNode done");
+	}
+
+	static void nop() {}
+
+	public static void addDocumentListener(DocumentListener documentListener) {
+		documentListeners.add(documentListener);
+	}
+
+	static Document document(Node node) {
+		Document doc = new Document();
+		doc.add( new Field(NODE_ID_FLD, Long.toString(node.getId()), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS) );
+		doc.add( new Field(KIND_FLD, node.getKind().toString(), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS) );
+		String subject = node.getSubject();
+		Field subjectFld = new Field(SUBJECT_FLD, subject, Field.Store.NO, Field.Index.ANALYZED);
+		subjectFld.setBoost(2.0f);
+		doc.add(subjectFld);
+		try {
+			String message = MessageUtils.htmlToSearchText(node.getMessage().parse());
+			doc.add( new Field(MESSAGE_FLD, message, Field.Store.NO, Field.Index.ANALYZED) );
+		} catch(RuntimeException e) {
+			logger.error("nodeId="+node.getId(),e);
+		}
+
+		for( Node f : node.getAncestors() ) {
+			doc.add( new Field(ANCESTORS_FLD, Long.toString(f.getId()), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS) );
+		}
+		Node parent = node.getParent();
+		if (parent != null)
+			doc.add(new Field(PARENT_ID_FLD, Long.toString(parent.getId()), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
+
+		int date = (int)(-node.getWhenCreated().getTime()/1000);
+		doc.add( new NumericField(DATE_FLD).setIntValue(date) );
+		int rangeSearchDate = formatRangeSearchDate(node.getWhenCreated());
+		doc.add( new NumericField(RANGE_SEARCH_DATE_FLD).setIntValue(rangeSearchDate) );
+		String day = formatDay(node.getWhenCreated());
+		doc.add( new Field(DAY_FLD, day, Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS) );
+
+		Person owner = node.getOwner();
+		String userId = owner.getSearchId();
+		doc.add( new Field(USER_ID_FLD, userId, Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS) );
+		String author = owner.getName();
+		doc.add( new Field(AUTHOR_FLD, author, Field.Store.NO, Field.Index.ANALYZED) );
+		doc.add( new Field(PRIVATE_NODE_FLD, formatPrivateNode(node), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));
+		MailingList mailingList = node.getMailingList();
+		if (mailingList != null) { // only for forums
+			Field listAddrFld = new Field(MAILING_LIST_FLD, mailingList.getListAddress().toLowerCase(), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS);
+			doc.add( listAddrFld );
+		}
+		for( DocumentListener documentListener : documentListeners ) {
+			documentListener.event(node,doc);
+		}
+		return doc;
+	}
+
+	private static final String[] nodeDbFields =
+		{"subject", "when_created", "msg_fmt", "parent_id", "is_app", "owner_id", "cookie", "anonymous_name"};
+
+	private static final String[] mailingListDbFields = {"mailing_list"};
+
+
+	public static void updateRecursively(Node node) {
+		update(node);
+		for (Node n : node.getChildren()) {
+			updateRecursively(n);
+		}
+	}
+
+
+
+
+
+
+
+	// from SearchServer
+
+	static NodeImpl node(SiteImpl site,Document doc) {
+		long nodeId = getNodeId(doc);
+		NodeImpl node = NodeImpl.getNode(site.siteKey,nodeId);
+		if (node==null)
+			logger.error("invalid node_id in lucene index: "+nodeId);
+		return node;
+	}
+
+	private static final IndexCache.Builder<Long> builder = new IndexCache.Builder<Long>() {
+
+		public void build(Long siteId) throws SQLException, IOException {
+			SiteKey siteKey = SiteKey.getInstance(siteId);
+			Connection con = siteKey.getDb().getConnection();
+			long[] nodeIds;
+			{
+				Statement stmt = con.createStatement();
+				ResultSet rs = stmt.executeQuery(
+					"select count(*) as n from node"
+				);
+				rs.next();
+				nodeIds = new long[rs.getInt("n")];
+				rs.close();
+				stmt.close();
+			}
+			{
+				PreparedStatement stmt = con.prepareStatement(
+					"select node_id from node order by node_id limit ?"
+				);
+				stmt.setInt(1,nodeIds.length);
+				ResultSet rs = stmt.executeQuery();
+				for( int i=0; rs.next(); i++ ) {
+					nodeIds[i] = rs.getLong("node_id");
+				}
+				rs.close();
+				stmt.close();
+			}
+			logger.error("Lucene started - site_id = " + siteId + " / " + nodeIds.length + " nodes");
+			IndexWriter indexWriter = nodeIndex.openIndexWriter(siteId);
+			int count = 0;
+			int lastPercent = 0;
+			try {
+				for( long nodeId : nodeIds ) {
+					Node node = NodeImpl.getNode(siteKey,nodeId);
+					if( node != null ) {
+						Document doc = document(node);
+						indexWriter.updateDocument( new Term(NODE_ID_FLD,doc.get(NODE_ID_FLD)), doc );
+					}
+					count++;
+					int percent = Math.round(100f * count / (float) nodeIds.length);
+					if (percent > lastPercent) {
+						logger.error("Lucene build " + percent + "% completed");
+						lastPercent = percent;
+					}
+				}
+			} finally {
+				indexWriter.close();
+			}
+			con.close();
+		}
+
+		public boolean exists(String keyString) {
+			long id;
+			try {
+				id = Long.parseLong(keyString);
+			} catch(NumberFormatException e) {
+				return false;
+			}
+			return SiteKey.getInstance(id).siteGlobal() != null;
+		}
+	};
+
+	private static final IndexCache<Long> nodeIndex;
+	static {
+		logger.info("Starting search server");
+		Init.luceneStarted = true;
+		String localDir = (String)Init.get("local_dir");
+		String luceneDir = localDir + "lucene/";
+		File dirFile = new File(luceneDir);
+		nodeIndex = new IndexCache<Long>(dirFile,analyzer,nodeIndexVersion,builder);
+	}
+
+	private static void updateNodes(final SiteImpl site,Query query) {
+		try {
+			final LuceneSearcher searcher = newSearcher(site);
+			try {
+				searcher.search(query,new HitCollector() {
+					protected void process(Document doc) {
+						Node node = getNode(site,doc);
+						if( node != null )
+							update(node);
+					}
+				});
+			} finally {
+				searcher.close();
+			}
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+
+	public static boolean isReady(Site site) {
+		return nodeIndex.isReady(site.getId());
+	}
+
+	public static void rebuild(Site site) throws IOException {
+		nodeIndex.rebuild(site.getId());
+	}
+
+	static synchronized void shutdown() {
+		nodeIndex.shutdown();
+	}
+
+
+
+
+
+	private static final long tenMinutes = 1000L*60*10;
+
+	static int formatRangeSearchDate(Date date) {
+		return (int)(date.getTime()/tenMinutes);
+	}
+
+
+	private static final DateFormat dayFormat = new SimpleDateFormat("yyyyMMdd");
+
+	static String formatDay(Date date) {
+		synchronized(dayFormat) {
+			return dayFormat.format(date);
+		}
+	}
+
+	static String formatPrivateNode(Node node) {
+		Node privateNode = Permissions.getPrivateNodeForSearch(node);
+		return privateNode==null ? "none" : Long.toString(privateNode.getId());
+	}
+
+
+	public static Filter and(Filter f1,Filter f2) {
+		BooleanFilter f = new BooleanFilter();
+		f.add(new FilterClause(f1,BooleanClause.Occur.MUST));
+		f.add(new FilterClause(f2,BooleanClause.Occur.MUST));
+		return f;
+	}
+
+	public static Filter getRangeFilter(Date from, Date to) {
+		Integer lowerDateTerm = (from==null)?null:formatRangeSearchDate(from);
+		Integer upperDateTerm = (to==null)?null:formatRangeSearchDate(to);
+		return NumericRangeFilter.newIntRange(RANGE_SEARCH_DATE_FLD, lowerDateTerm, upperDateTerm, true,true);
+	}
+
+
+	private static final int maxCachedFilters = Init.get("maxCachedFilters", 20);
+
+	private static Map<Filter,CachingWrapperFilter> filterCache = new LinkedHashMap<Filter,CachingWrapperFilter>() {
+	     protected boolean removeEldestEntry(Map.Entry eldest) {
+	        return size() > maxCachedFilters;
+	     }
+	};
+
+	public static synchronized CachingWrapperFilter getCachedFilter(Filter filter) {
+		CachingWrapperFilter f = filterCache.get(filter);
+		if( f == null ) {
+			f = new CachingWrapperFilter(filter);
+			filterCache.put(filter,f);
+		}
+		return f;
+	}
+
+
+	static Query descendants(Node node) {
+		return descendants(node.getId());
+	}
+
+	private static Query descendants(long nodeId) {
+		return new TermQuery(new Term(ANCESTORS_FLD,Long.toString(nodeId)));
+	}
+
+	static Query children(Node node) {
+		return new TermQuery(new Term(PARENT_ID_FLD,Long.toString(node.getId())));
+	}
+
+	static Query node(Node node) {
+		return node(node.getId());
+	}
+
+	static Query node(long nodeId) {
+		return new TermQuery(new Term(NODE_ID_FLD,Long.toString(nodeId)));
+	}
+
+	static Query day(Date date) {
+		return new TermQuery(new Term(DAY_FLD,formatDay(date)));
+	}
+
+
+	private static final Directory helpDir = new RAMDirectory();
+	private static IndexReader helpIndexReader;
+
+	private static final String[] helpSearchFields = new String[] {
+		"answer", "question"
+	};
+
+	public static Help[] searchHelp(String line) throws ParseException {
+		try {
+			Query query = NodeSearcher.parse(line,helpSearchFields);
+			Searcher searcher = new IndexSearcher(helpIndexReader);
+			try {
+				TopDocs hits = searcher.search(query,helpIndexReader.numDocs());
+				Help[] helps = new Help[hits.scoreDocs.length];
+				for( int i=0; i<helps.length; i++ ) {
+					helps[i] = Help.getHelp(Integer.parseInt(searcher.doc(hits.scoreDocs[i].doc).get("id")));
+				}
+				return helps;
+			} catch (BooleanQuery.TooManyClauses e) {
+				throw new RuntimeException("Your search will give too many matches.");
+			} finally {
+				searcher.close();
+			}
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static void addHelp(final Collection<Help> helps) {
+		try {
+			IndexWriter writer = new IndexWriter(helpDir,analyzer,true,IndexWriter.MaxFieldLength.LIMITED);
+			for( Help help : helps ) {
+				writer.addDocument(document(help));
+			}
+			writer.close();
+			helpIndexReader = IndexReader.open(helpDir,true);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static Document document(Help help) {
+		Document doc = new Document();
+		String id = Integer.toString(help.id);
+		doc.add( new Field("id", id, Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS));
+		Field answer = new Field("answer", help.answer(), Field.Store.NO, Field.Index.ANALYZED);
+		doc.add(answer);
+		Field question = new Field("question", help.question, Field.Store.NO, Field.Index.ANALYZED);
+		doc.add(question);
+		return doc;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/MailMessageFormat.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,595 @@
+package nabble.model;
+
+import fschmidt.html.Html;
+import fschmidt.html.HtmlTag;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.mail.AlternativeMultipartContent;
+import fschmidt.util.mail.Content;
+import fschmidt.util.mail.FileAttachmentContent;
+import fschmidt.util.mail.HtmlTextContent;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.MailParseException;
+import fschmidt.util.mail.MultipartContent;
+import fschmidt.util.mail.TextContent;
+import nabble.view.lib.Permissions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.regex.Matcher;
+
+
+/**
+ * User: dv
+ * Date: Dec 29, 2007
+ * Time: 4:27:45 PM
+ */
+public final class MailMessageFormat extends Message.Format {
+	private static final Logger logger = LoggerFactory.getLogger(MailMessageFormat.class);
+
+	MailMessageFormat(char code, String name) {
+		super(code, name);
+	}
+
+	protected final Html parse(String msg,Message.Source source) {
+		try {
+			return doParse(msg,source);
+		} catch(MailParseException e) {
+			logger.error(e.toString());
+			return new Html();
+		}
+	}
+
+	protected final Html parseForMailText(String msg,Message.Source source) {
+		throw new UnsupportedOperationException();
+	}
+
+	private static Html getTextContentAsHtml(Content content) {
+		String s = getTextContent(content);
+		return s==null ? null : textToHtml(s);
+	}
+
+	private static Html getHtmlContentAsHtml(Content content,Message.Source source) {
+		String s = getHtmlContent(content);
+		if( s == null )
+			return null;
+		Html html = new Html();
+		html.removeBadTags(true);
+		html.parse(s);
+		if( source != null ) {
+			markManualQuotes(html);
+		}
+		return html;
+	}
+
+	private static Html getMailAsHtml(Mail mail,Message.Source source) {
+		Content content = mail.getContent();
+		Html list;
+		if (preferTextContent(mail)) {
+			list = getTextContentAsHtml(content);
+			if( list == null )
+				list = getHtmlContentAsHtml(content, source);
+		} else {
+			list = getHtmlContentAsHtml(content, source);
+			if( list == null )
+				list = getTextContentAsHtml(content);
+		}
+		return list;
+	}
+
+	private static Html doParse(String msg, Message.Source source) {
+		Mail mail = MailHome.newMail(msg);
+		Html list = getMailAsHtml(mail, source);
+		if( list == null )
+			list = new Html();
+		List<FileAttachmentContent> attachments = getAttachments(mail);
+		setImgAttachmentSrc(list, attachments, source);
+		tagEmails(list,(Node)source);
+		list.addAll( new Html(listAttachmentsHtml(attachments, source)) );
+		return list;
+	}
+
+	boolean isOk(String message) {
+		try {
+			doParse(message,null);  // make sure we can
+			return true;
+		} catch(MailParseException e) {
+			logger.warn("",e);
+			return false;
+		}
+	}
+
+	protected String getText(String msg, Message.Source source) {
+		try {
+			// basic text message
+			String text = getMailText(msg,source);
+
+			// append attachments
+			Mail mail = MailHome.newMail(msg);
+			List<FileAttachmentContent> attachments = getAttachments(mail);
+			String attachmentText = listAttachmentsText(attachments, source);
+			if (attachmentText.length() > 0) {
+				text = text + "\n\n" + attachmentText;
+			}
+			return text;
+		} catch(MailParseException e) {
+			logger.error(e.toString());
+			return "";
+		}
+	}
+
+	String getMailText(String msg, Message.Source source) {
+		Mail mail = MailHome.newMail(msg);
+		Content content = mail.getContent();
+		String s = getTextContent(content);
+		if (s == null) {
+			s = getHtmlContent(content);
+			if (s == null)
+				s = "";
+			s = htmlToText(s);
+		}
+		return s;
+	}
+
+	protected final String getTextWithoutQuotes(String msg, Message.Source source) {
+		return getText(msg,source);
+	}
+
+	protected final String getEmail(String msg, int i) {
+		Mail mail = MailHome.newMail(msg);
+		Html list = getMailAsHtml(mail, null);
+		tagEmails(list);
+		return MessageFormatImpls.getEmail(list, i);
+	}
+
+	/**
+	 * Process html and replace source url in img tags with a new Nabble-specific attachment url
+	 *
+	 * @param list        html to process
+	 * @param attachments list of attachments
+	 * @param source      source
+	 */
+	private static void setImgAttachmentSrc(Html list, List<FileAttachmentContent> attachments, Message.Source source) {
+		if (source instanceof NodeImpl) {
+			FileAttachmentContent[] fa = attachments.toArray(new FileAttachmentContent[attachments.size()]);
+			for (ListIterator i = list.listIterator(); i.hasNext();) {
+				Object o = i.next();
+				if (o instanceof HtmlTag) {
+					HtmlTag tag = (HtmlTag) o;
+					if (tag.getName().toLowerCase().equals("img")) {
+						String src = HtmlTag.unquote(tag.getAttributeValue("src"));
+						if (src == null || !src.startsWith("cid:")) continue;
+						src = src.substring(4);
+						int n = 0;
+						FileAttachmentContent attachment = null;
+						for (; n < fa.length; n++) {
+							String contentId = fa[n].getContentID();
+							if (contentId!=null && src.equals(MailSubsystem.stripBrackets(contentId))) {
+								attachment = fa[n];
+								attachments.set(n, null);
+								break;
+							}
+						}
+						if (attachment != null) {
+							String fileName = getFileName(attachment, n);
+							StringBuilder buf = new StringBuilder();
+							buf.append("\"");
+							buf.append(source.getSite().getBaseUrl());
+							buf.append("/attachment/");
+							buf.append(source.getSourceId());
+							buf.append("/");
+							buf.append(n);
+							buf.append("/");
+							buf.append(HtmlUtils.urlEncode(fileName).replaceAll("\\+", "%20"));
+							buf.append("\"");
+							tag.setAttribute("src", buf.toString());
+						} else {
+							i.remove();
+						}
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Create string representation to an attachment list
+	 *
+	 * @param attachments list of attachments
+	 * @param source      source
+	 * @return s with attachment list
+	 */
+	private static String listAttachmentsHtml(List<FileAttachmentContent> attachments, Message.Source source) {
+		if (attachments.size() == 0 || !(source instanceof NodeImpl)) return "";
+		StringBuilder buf = new StringBuilder();
+		for (int i = 0; i < attachments.size(); i++) {
+			if (attachments.get(i) == null) continue;
+			String fileName = getFileName(attachments.get(i), i);
+			String fileSize = getFileSize(attachments.get(i));
+			buf.append("<br/><img src=\"");
+			buf.append(source.getSite().getBaseUrl());
+			buf.append("/images/icon_attachment.gif\" > <strong>");
+			buf.append(fileName);
+			buf.append("</strong>");
+			if (fileSize != null) {
+				buf.append(" (");
+				buf.append(fileSize);
+				buf.append(")");
+			}
+			buf.append(" <a href=\"");
+			buf.append(source.getSite().getBaseUrl());
+			buf.append("/attachment/");
+			buf.append(source.getSourceId());
+			buf.append("/");
+			buf.append(i);
+			buf.append("/");
+			buf.append(HtmlUtils.urlEncode(fileName).replaceAll("\\+", "%20"));
+			buf.append("\" target=\"_top\">");
+			buf.append("Download Attachment");
+			buf.append("</a>");
+		}
+		if (buf.length() == 0) return "";
+		buf.insert(0, "<!--start-attachments--><div class=\"small\">");
+		buf.append("</div><!--end-attachments-->");
+		return buf.toString();
+	}
+
+	/**
+	 * Create string representation to an attachment list
+	 *
+	 * @param attachments list of attachments
+	 * @param source      source
+	 * @return s with attachment list
+	 */
+	private static String listAttachmentsText(List<FileAttachmentContent> attachments, Message.Source source) {
+		if (attachments.size() == 0 || !(source instanceof NodeImpl)) return "";
+		StringBuilder buf = new StringBuilder();
+		for (int i = 0; i < attachments.size(); i++) {
+			if (attachments.get(i) == null) continue;
+			String fileName = getFileName(attachments.get(i), i);
+			String fileSize = getFileSize(attachments.get(i));
+			buf.append(fileName);
+			if (fileSize != null) {
+				buf.append(" (");
+				buf.append(fileSize);
+				buf.append(")");
+			}
+			buf.append(" <");
+			buf.append(source.getSite().getBaseUrl());
+			buf.append("/attachment/");
+			buf.append(source.getSourceId());
+			buf.append("/");
+			buf.append(i);
+			buf.append("/");
+			buf.append(HtmlUtils.urlEncode(fileName).replaceAll("\\+", "%20"));
+			buf.append(">\n");
+		}
+		if (buf.length() == 0) return "";
+		return buf.toString();
+	}
+
+	/**
+	 * Get a name of a file in the attachment
+	 *
+	 * @param attachment attachment with file
+	 * @param n          suffix for name
+	 * @return the name of the file in the attachment
+	 */
+	private static String getFileName(FileAttachmentContent attachment, int n) {
+		try {
+			if (attachment.getFileName() != null)
+				return attachment.getFileName();
+		} catch (MailException e) {
+		}
+		try {
+			if (attachment.getContentID() != null)
+				return MailSubsystem.stripBrackets(attachment.getContentID());
+		} catch (MailException e) {
+		}
+		return "attachment" + n;
+	}
+
+	/**
+	 * Get a size of a file in the attachment
+	 *
+	 * @param attachment attachment with file
+	 * @return symbolic file size presentation or null
+	 */
+	private static String getFileSize(FileAttachmentContent attachment) {
+		int size = attachment.getSize();
+		if( size == -1 )
+			return null;
+		if (size > 1024 * 1024)
+			return size / (1024 * 1024) + "M";
+		else if (size > 1024)
+			return size / 1024 + "K";
+		else
+			return size + " bytes";
+	}
+
+	/**
+	 * Get attachment as a stream
+	 *
+	 * @param post attachment source
+	 * @param i    index of the attachment
+	 * @return a stream with attachment's content
+	 */
+	public InputStream getAttachment(Node post, int i) {
+		String msg = post.getMessage().getRaw();
+		Mail mail = MailHome.newMail(msg);
+		List<FileAttachmentContent> attachments = getAttachments(mail);
+		FileAttachmentContent attachment = attachments.get(i);
+		return attachment.getInputStream();
+	}
+
+	/**
+	 * Extract html content from content object
+	 *
+	 * @param content     content to analyze
+	 * @return string representation of content
+	 */
+	private static String getHtmlContent(Content content) {
+		if (content instanceof HtmlTextContent) {
+			HtmlTextContent html = (HtmlTextContent) content;
+			return html.getText();
+		} else if (content instanceof AlternativeMultipartContent) {
+			AlternativeMultipartContent amp = (AlternativeMultipartContent) content;
+			Content[] amparts = amp.getParts();
+			for (Content ampart : amparts) {
+				String ams = getHtmlContent(ampart);
+				if (ams != null) return ams;
+			}
+			return null;
+		} else if (content instanceof MultipartContent) {
+			MultipartContent mmp = (MultipartContent) content;
+			Content[] mmparts = mmp.getParts();
+			StringBuilder buf = new StringBuilder();
+			boolean hasContent = false;
+			for (Content mmpart : mmparts) {
+				String mms = getHtmlContent(mmpart);
+				if (mms == null) {
+					mms = getTextContent(mmpart);
+					if (mms != null) mms = textToHtml(mms).toString();
+				}
+				if (mms != null) {
+					hasContent = true;
+					if (buf.length() > 0)
+						buf.append("<br />");
+					buf.append(mms);
+				}
+			}
+			return hasContent ? buf.toString() : null;
+		} else if (content instanceof Mail) {
+			return getHtmlContent(((Mail) content).getContent());
+		} else {
+			return null;
+		}
+
+	}
+
+	/**
+	 * Does a source prefer text content
+	 * @return true if text content is prefered
+	 */
+	private static boolean preferTextContent(Mail mail) {
+		String returnPath = MailSubsystem.getReturnPath(mail);
+		return returnPath!=null && returnPath.endsWith(".groups.yahoo.com");
+	}
+
+	/**
+	 * Extract text content from content object
+	 *
+	 * @param content     content to analyze
+	 * @return string representation of content
+	 */
+	private static String getTextContent(Content content) {
+		if (content instanceof AlternativeMultipartContent) {
+			AlternativeMultipartContent amp = (AlternativeMultipartContent) content;
+			Content[] amparts = amp.getParts();
+			for (Content ampart : amparts) {
+				String ams = getTextContent(ampart);
+				if (ams != null) return ams;
+			}
+			return null;
+		} else if (content instanceof MultipartContent) {
+			MultipartContent mmp = (MultipartContent) content;
+			Content[] mmparts = mmp.getParts();
+			StringBuilder buf = new StringBuilder();
+			boolean hasContent = false;
+			for (Content mmpart : mmparts) {
+				String mms = getTextContent(mmpart);
+				if (mms == null) {
+					mms = getHtmlContent(mmpart);
+					if (mms != null) mms = htmlToText(mms);
+				}
+				if (mms != null) {
+					hasContent = true;
+					if (buf.length() > 0)
+						buf.append('\n');
+					buf.append(mms);
+				}
+			}
+			return hasContent ? buf.toString() : null;
+		} else if (content instanceof TextContent && !(content instanceof HtmlTextContent)) {
+			TextContent textContent = (TextContent) content;
+			return textContent.getText();
+		} else if (content instanceof Mail) {
+			return getTextContent(((Mail) content).getContent());
+		} else {
+			return null;
+		}
+	}
+
+	private static List<FileAttachmentContent> getAttachments(Content content) {
+		if (content instanceof FileAttachmentContent) {
+			return Collections.singletonList( (FileAttachmentContent)content );
+		}
+		if (content instanceof MultipartContent) {
+			List<FileAttachmentContent> attachments = new ArrayList<FileAttachmentContent>();
+			MultipartContent mmp = (MultipartContent) content;
+			for (Content mmpart : mmp.getParts()) {
+				attachments.addAll( getAttachments(mmpart) );
+			}
+			return attachments;
+		}
+		if (content instanceof Mail) {
+			return getAttachments( ((Mail)content).getContent() );
+		}
+		return Collections.emptyList();
+	}
+
+
+	private static Html textToHtml(String text) {
+		Html list = MessageFormatImpls.convertLinks(text);
+		for( ListIterator<Object> iter = list.listIterator(); iter.hasNext(); ) {
+			Object obj = iter.next();
+			if( obj instanceof String ) {
+				String s = (String)obj;
+				s = HtmlUtils.htmlEncode(s);
+				iter.set(s);
+			}
+		}
+		MessageFormatImpls.textToHtml(list);
+		markManualQuotes(list);
+		return list;
+	}
+
+
+	private static final HtmlTag emailTag = new HtmlTag("email");
+	private static final HtmlTag _emailTag = new HtmlTag("/email");
+
+	/**
+	 * Use <email>me@host.com</email> to replace
+	 * email. MessageFormatImpls.processEmail should be called to touch up the final result
+	 */
+	public static void tagEmails(Html list,Node node) {
+		if( node!=null && !Permissions.isPrivate(node) )
+			tagEmails(list);
+	}
+
+	private static void tagEmails(Html list) {
+		boolean insideA = false;
+		boolean insideEmail = false;
+		for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) {
+			Object o = i.next();
+			if( o instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)o;
+				String tagName = tag.getName().toLowerCase();
+				if( insideEmail ) {
+					if( tagName.equals("/email") )
+						insideEmail = false;
+					continue;
+				}
+				if( tagName.equals("email") ) {
+					insideEmail = true;
+				} else if( tagName.equals("a") ) {
+					String href = tag.getAttributeValue("href");
+					if (href != null) {
+						if (href.startsWith("\"mailto:")) {
+							i.remove();
+							boolean foundCloseTag = false;
+							while (i.hasNext() && !foundCloseTag) {
+								Object o1 = i.next();
+								if ( o1 instanceof HtmlTag ) {
+									HtmlTag tag1 = (HtmlTag)o1;
+									if (tag1.getName().toLowerCase().equals("/a")) {
+										foundCloseTag = true;
+									}
+								}
+								i.remove();
+							}
+							i.add(emailTag);
+							i.add(href.substring(8, href.length() - 1));
+							i.add(_emailTag);
+						} else {
+							insideA = true;
+						}
+					}
+				} else if (tagName.equals("/a")) {
+					insideA = false;
+				}
+			} else if (o instanceof String && !insideEmail) {
+				String s = (String)o;
+				Matcher m = MailAddress.EMAIL_PATTERN.matcher(s);
+				if( m.find() ) {
+					StringBuffer buf = new StringBuffer();
+					do {
+						m.appendReplacement(buf, insideA?"$1@...":"<email>$1@$2</email>");
+					} while (m.find());
+					m.appendTail(buf);
+					Html hlist = new Html(buf.toString());
+					i.remove();
+					for(ListIterator j=hlist.listIterator();j.hasNext();) {
+						i.add(j.next());
+					}
+				}
+			}
+		}
+	}
+
+
+
+	private static final HtmlTag divHiddenQuote = new HtmlTag("div class='shrinkable-quote'");
+	private static final HtmlTag _div = new HtmlTag("/div");
+
+	private static void markManualQuotes(Html list) {
+		int n = list.size() - 1;
+		if( n==-1 ) return;
+		int nLines = 0;
+		int iStart = -1;
+		Object o = list.get(0);
+		if( o instanceof String ) {
+			String s = (String)o;
+			if( s.startsWith("&gt;") ) {
+				nLines++;
+			}
+		}
+		for( int i=0; i<n; i++ ) {
+			o = list.get(i);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)o;
+			if( !tag.getName().toLowerCase().equals("br") )
+				continue;
+			o = list.get(i+1);
+			if( o instanceof String ) {
+				String s = (String)o;
+				if( s.startsWith("&gt;") ) {
+					if( nLines == 0 )
+						iStart = i;
+					nLines++;
+					continue;
+				}
+			}
+			if( nLines >= Init.quotedLinesToHide ) {
+				if( iStart == -1 ) {
+					list.add(0,divHiddenQuote);
+				} else {
+					list.add(iStart,divHiddenQuote);
+				}
+				i++;
+				n++;
+				list.set(i,_div);
+			}
+			nLines = 0;
+		}
+		if( nLines >= Init.quotedLinesToHide ) {
+			if( iStart == -1 )
+				iStart = 0;
+			list.add(iStart,divHiddenQuote);
+			list.add(_div);
+		}
+	}
+
+	private static String htmlToText(String msg) {
+		return MessageUtils.htmlToText(new Html(msg));
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/MailSubsystem.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,133 @@
+package nabble.model;
+
+import fschmidt.util.mail.Content;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.MailParseException;
+import fschmidt.util.mail.MixedMultipartContent;
+import fschmidt.util.mail.PlainTextContent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+
+final class MailSubsystem {
+	private static final Logger logger = LoggerFactory.getLogger(MailSubsystem.class);
+
+	private MailSubsystem() {}  // never
+
+	/**
+	 * Send an error message.
+	 *
+	 * @param mail      a mail caused a message, may be null
+	 * @param mailTo    sent error message to
+	 * @param subject   message subject
+	 * @param errorText error text
+	 */
+	static void sendErrorMail(Mail mail, MailAddress mailTo, String subject, String errorText) {
+		Mail errMail = MailHome.newMail();
+		errMail.setFrom(new MailAddress(ModelHome.noReply));
+		errMail.setTo(mailTo);
+		errMail.setSubject(subject);
+		errMail.setSentDate(new Date());
+		Content content = (mail != null) ?
+				new MixedMultipartContent(new Content[]{
+						new PlainTextContent(errorText),
+						mail,
+				}) :
+				new MixedMultipartContent(new Content[]{
+						new PlainTextContent(errorText)
+				});
+		errMail.setContent(content);
+		ModelHome.send(errMail);
+	}
+
+/*
+	static String stripBrackets(String s) {
+		if (s == null) return null;
+		int start = s.lastIndexOf('<');
+		int end = s.lastIndexOf('>');
+		if (start >= 0 && end > start)
+			s = s.substring(start + 1, end);
+		return s.trim();
+	}
+*/
+	static String stripBrackets(String s) {
+		int start = s.lastIndexOf('<');
+		int end = s.indexOf('>');
+		if( start == -1 && end == -1 )
+			return s;
+		if( start == -1 || end < start )
+			throw new MailParseException("no brackets found in '"+s+"'");
+		return s.substring(start + 1, end);
+	}
+
+	static List<String> stripMultiBrackets(final String s) {
+		List<String> list = new ArrayList<String>();
+		for( int i=0; i<s.length(); ) {
+			int start = s.indexOf('<',i);
+			int end = s.indexOf('>',i);
+			if( start == -1 && end == -1 )
+				break;
+			if( start == -1 || end < start )
+				throw new MailParseException("malformed brackets found in '"+s+"'");
+			list.add( s.substring(start + 1, end) );
+			i = end + 1;
+		}
+		return list;
+	}
+
+
+
+
+	/**
+	 * Prepare message headers for sending
+	 *
+	 * @param mail	   to prepare
+	 * @param node	   node to be sent
+	 */
+	static void setHeaders(Mail mail, NodeImpl node) {
+		mail.setSentDate(new Date());
+		mail.setMessageID("<" + node.getOrGenerateMessageID() + ">");
+		NodeImpl parent = node.getParentImpl();
+		if (parent != null && parent.getKind() != Node.Kind.APP) {
+			mail.setHeader("In-Reply-To", "<" + parent.getOrGenerateMessageID() + ">");
+			mail.setHeader("References", getReferences(parent, 0).trim());
+		}
+	}
+
+	private static final int MAX_REFERENCES = 10;
+
+	private static String getReferences(NodeImpl node, int count) {
+		return (node == null || node.getKind() == Node.Kind.APP || count==MAX_REFERENCES) ?
+				"" :
+				getReferences(node.getParentImpl(), count+1) + " <" + node.getOrGenerateMessageID() + ">";
+	}
+
+
+	static String getReturnPath(Mail mail) {
+		String[] a = mail.getHeader("Return-path");
+		return a==null ? null : MailSubsystem.stripBrackets(a[0]);
+	}
+
+	static void bounce(Mail mail,String msg) {
+		Mail bounce = MailHome.newMail();
+		bounce.setFrom( new MailAddress(ModelHome.noReply,"Nabble") );
+		bounce.setTo( new MailAddress(getReturnPath(mail)) );
+		bounce.setSubject( "Delivery Status Notification (Failure)" );
+		bounce.setHeader( "X-Failed-Recipients", mail.getHeader("Envelope-To") );
+		StringBuilder content = new StringBuilder();
+		content
+			.append( msg )
+			.append( "\n----- Original message -----\n\n" )
+			.append( mail.getRawInput() )
+		;
+		bounce.setContent(new PlainTextContent(content.toString()));
+		ModelHome.send(bounce);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/MailingList.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,48 @@
+package nabble.model;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+
+import java.io.File;
+
+public interface MailingList {
+	public Node getForum();
+	public String getListAddress();
+	public void setListAddress(String listAddress) throws ModelException.EmailFormat;
+	public String getUrl();
+	public long getId();
+	public void delete();
+	public void setUrl(String url) throws ModelException.UrlFormat;
+	public boolean ignoreNoArchive();
+	public void setIgnoreNoArchive(boolean ignoreNoArchive);
+	public boolean plainTextOnly();
+	public void setPlainTextOnly(boolean plainTextOnly);
+	public String getListName();
+	public void setListName(String listName);
+	public ListServer getListServer();
+	public void setListServer(ListServer listServer);
+	public void update();
+	public ImportResult importMbox(File file,String mailErrorsTo,int maxErrors) throws ModelException;
+	public MailAddress getSubscriberAddress();
+	public Node getNodeFromMessageID(String messageID);
+	public void subscribe();
+	public void unsubscribe();
+
+	public Mail subscribeMail();
+	public Mail subscribeMail(User user);
+	public Mail unsubscribeMail();
+	public Mail unsubscribeMail(User user);
+	public Mail defaultsMail(User user, String password);
+
+	// Email of the person who exported this mailing list to another server.
+	public String getExportOwner();
+	public void setExportOwner(String email) throws ModelException.EmailFormat;
+
+	public interface ImportResult {
+		public int getImported();
+		public int getErrors();
+	}
+
+	public String getPassword(User user);
+	public void rethread();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/MailingListImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,504 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbNull;
+import fschmidt.db.DbObject;
+import fschmidt.db.DbObjectFactory;
+import fschmidt.db.DbRecord;
+import fschmidt.db.DbTable;
+import fschmidt.db.DbUtils;
+import fschmidt.db.ListenerList;
+import fschmidt.db.LongKey;
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.SimpleCache;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.sql.Connection;
+import java.sql.Statement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Random;
+import java.util.WeakHashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class MailingListImpl implements MailingList, DbObject<LongKey, MailingListImpl> {
+	private static final Logger logger = LoggerFactory.getLogger(MailingListImpl.class);
+
+	final SiteKey siteKey;
+	private final DbRecord<LongKey, MailingListImpl> record;
+	private String listAddress;
+	private String listName;
+	private String url;
+	private String emailId;
+	private boolean ignoreNoArchive;
+	private boolean plainTextOnly;
+	private ListServer listServer;
+	private String exportOwner;
+
+	private NodeImpl forum;
+
+	private MailingListImpl(SiteKey siteKey,LongKey key, ResultSet rs)
+			throws SQLException
+	{
+		this.siteKey = siteKey;
+		record = table(siteKey).newRecord(this, key);
+		listAddress = rs.getString("list_address");
+		emailId = rs.getString("email_id");
+		listName = rs.getString("list_name");
+		url = rs.getString("list_home_url");
+		ignoreNoArchive = rs.getBoolean("ignore_no_archive");
+		plainTextOnly = rs.getBoolean("plain_text_only");
+		listServer = ListServer.getServer(rs.getString("list_server"));
+		exportOwner = rs.getString("export_owner");
+	}
+
+	MailingListImpl(NodeImpl forum, ListServer listServer, String listAddress, String url)
+		throws ModelException
+	{
+		this.siteKey = forum.siteKey;
+		record = table(siteKey).newRecord(this);
+		long id = forum.getId();
+		record.fields().put("node_id", id);
+		this.forum = forum;
+		setListServer(listServer);
+		setListAddress(listAddress);
+		setUrl(url);
+		record.insert();
+	}
+
+	public DbRecord<LongKey, MailingListImpl> getDbRecord() {
+		return record;
+	}
+
+	private DbTable<LongKey,MailingListImpl> table() {
+		return record.getDbTable();
+	}
+
+	private DbDatabase db() {
+		return table().getDbDatabase();
+	}
+
+	public long getId() {
+		return record.getPrimaryKey().value();
+	}
+
+	NodeImpl getForumImpl() {
+		if (DbUtils.isStale(forum)) {
+			forum = NodeImpl.getNode(siteKey,getId());
+		}
+		return forum;
+	}
+
+	public Node getForum() {
+		return getForumImpl();
+	}
+
+	public String getListAddress() {
+		return listAddress;
+	}
+
+	private static MailingListImpl getMailingList(SiteKey siteKey,long id) {
+		DbTable<LongKey,MailingListImpl> tbl = table(siteKey);
+		return tbl==null ? null : tbl.findByPrimaryKey(new LongKey(id));
+	}
+
+	public void delete() {
+		if (!db().isInTransaction()) {
+			db().beginTransaction();
+			try {
+				MailingListImpl mailingList = DbUtils.getGoodCopy(this);
+				mailingList.delete();
+				db().commitTransaction();
+			} finally {
+				db().endTransaction();
+			}
+			return;
+		}
+		record.delete();
+	}
+
+	static MailingListImpl getMailingListForForum(NodeImpl forum) {
+		MailingListImpl mailingList = table(forum.siteKey).findByPrimaryKey(new LongKey(forum.getId()));
+		if (mailingList != null) {
+			mailingList.forum = forum;
+		}
+		return mailingList;
+	}
+
+	private static class Lazy {
+		static final String emailPrefix;
+		static final String emailSuffix;
+		static final Pattern EMAIL_SUFFIXPATTERN;
+		static final Pattern pattern;
+		static {
+			String addrSpec = MailingLists.pop3Server.getUsername();
+			int ind = addrSpec.indexOf('@');
+			emailPrefix = addrSpec.substring(0, ind) + "+";
+			emailSuffix = addrSpec.substring(ind);
+			EMAIL_SUFFIXPATTERN = Pattern.compile(
+					Pattern.quote(emailPrefix) + "([^@]+)" + Pattern.quote(emailSuffix),
+					Pattern.CASE_INSENSITIVE);
+			pattern = Pattern.compile(
+					Pattern.quote(emailPrefix) + "s(\\d+)n(\\d+)h(\\d+)" + Pattern.quote(emailSuffix),
+					Pattern.CASE_INSENSITIVE);
+		}
+	}
+
+	static MailingListImpl getMailingListByEnvelopeAddress(String address) {
+		Matcher matcher = Lazy.pattern.matcher(address);
+		if( matcher.matches() ) {
+			long siteId = Long.valueOf(matcher.group(1));
+			SiteImpl site = SiteKey.getInstance(siteId).site();
+			if( site == null )
+				return null;
+			long nodeId = Long.valueOf(matcher.group(2));
+			NodeImpl node = site.getNodeImpl(nodeId);
+			if( node == null )
+				return null;
+			MailingListImpl ml = node.getMailingListImpl();
+			if( ml==null )
+				return null;
+			String hash = matcher.group(3);
+			if( !ml.generateHash().equals(hash) )
+				return null;
+			return ml;
+		}
+		matcher = Lazy.EMAIL_SUFFIXPATTERN.matcher(address);
+		if( matcher.matches() ) {
+			String emailId = matcher.group(1);
+			return getMailingListByOldEmailId(emailId);
+		}
+		return null;
+	}
+
+	private static MailingListImpl getMailingListByOldEmailId(String emailId) {
+		try {
+			Connection con = Db.dbGlobal().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select * from mailing_list_lookup where email_id = ?"
+				, ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_UPDATABLE
+			);
+			stmt.setString(1, emailId.trim().toLowerCase());
+			ResultSet rs = stmt.executeQuery();
+			try {
+				if( !rs.next() )
+					return null;
+				long siteId = rs.getLong("site_id");
+				long nodeId = rs.getLong("node_id");
+				SiteKey siteKey = SiteKey.getInstance(siteId);
+				MailingListImpl ml = getMailingList(siteKey,nodeId);
+				if( ml == null ) {
+					logger.error("couldn't find mailing list site="+siteId+" node="+nodeId);
+					rs.deleteRow();
+					return null;
+				}
+				return ml;
+			} finally {
+				rs.close();
+				stmt.close();
+				con.close();
+			}
+		} catch (SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static int cleanMailingListLookup() {
+		List<String> emailIds = new ArrayList<String>();
+		try {
+			Connection con = Db.dbGlobal().getConnection();
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"select email_id from mailing_list_lookup"
+			);
+			while( rs.next() ) {
+				String emailId = rs.getString("email_id");
+				emailIds.add(emailId);
+			}
+			rs.close();
+			stmt.close();
+			con.close();
+		} catch (SQLException e) {
+			throw new RuntimeException(e);
+		}
+		int count = 0;
+		for( String emailId : emailIds ) {
+			MailingList ml = getMailingListByOldEmailId(emailId);
+			if (ml == null)
+				count++;
+		}
+		return count;
+	}
+
+	public static List<MailingList> getOldMailingLists() {
+		List<String> emailIds = new ArrayList<String>();
+		try {
+			Connection con = Db.dbGlobal().getConnection();
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"select email_id from mailing_list_lookup"
+			);
+			while( rs.next() ) {
+				String emailId = rs.getString("email_id");
+				emailIds.add(emailId);
+			}
+			rs.close();
+			stmt.close();
+			con.close();
+		} catch (SQLException e) {
+			throw new RuntimeException(e);
+		}
+		List<MailingList> list = new ArrayList<MailingList>();
+		for( String emailId : emailIds ) {
+			MailingList ml = getMailingListByOldEmailId(emailId);
+			if (ml != null)
+				list.add(ml);
+		}
+		return list;
+	}
+
+	private String generateHash() {
+		int h = 31*(int)getId();
+		return Integer.toString(Math.abs(h)%100);
+	}
+
+	public void setListAddress(String listAddress) throws ModelException.EmailFormat {
+		listAddress = listAddress.trim();
+		UserImpl.validateEmail(listAddress);
+		this.listAddress = listAddress;
+		record.fields().put("list_address", listAddress);
+	}
+
+	public String getListName() {
+		return listName;
+	}
+
+	public void setListName(String listName) {
+		if( listName != null ) {
+			listName = listName.trim();
+			if( listName.equals("") )
+				listName = null;
+		}
+		this.listName = listName;
+		record.fields().put("list_name", DbNull.fix(listName));
+	}
+
+	String fixSubject(String subject) {
+		if( listName != null && subject != null ) {
+			if( subject.startsWith(listName) )
+				return subject.substring(listName.length()).trim();
+			String lowerListName = listName.toLowerCase();
+			String lowerSubject = subject.toLowerCase();
+			if( lowerSubject.startsWith( "re: " + lowerListName + " re: " ) )
+				return subject.substring(listName.length()+5);
+			if( lowerSubject.startsWith( "re: " + lowerListName ) )
+				return subject.substring(0,4) + subject.substring(4+listName.length()).trim();
+		}
+		return subject;
+	}
+
+	public String getUrl() {
+		return url;
+	}
+
+	public void setUrl(String url) throws ModelException.UrlFormat {
+		url = url.trim();
+		validateUrl(url);
+		this.url = url;
+		record.fields().put("list_home_url", url);
+	}
+
+	static void validateUrl(String url) throws ModelException.UrlFormat {
+		try {
+			new URL(url);
+		} catch(MalformedURLException e) {
+			throw new ModelException.UrlFormat(url,e);
+		}
+	}
+
+	public boolean ignoreNoArchive() {
+		return ignoreNoArchive;
+	}
+
+	public void setIgnoreNoArchive(boolean ignoreNoArchive) {
+		this.ignoreNoArchive = ignoreNoArchive;
+		record.fields().put("ignore_no_archive", ignoreNoArchive);
+	}
+
+	public boolean plainTextOnly() {
+		return plainTextOnly;
+	}
+
+	public void setPlainTextOnly(boolean plainTextOnly) {
+		this.plainTextOnly = plainTextOnly;
+		record.fields().put("plain_text_only", plainTextOnly);
+	}
+
+	public ListServer getListServer() {
+		return listServer;
+	}
+
+	public void setListServer(ListServer listServer) {
+		this.listServer = listServer;
+		record.fields().put("list_server", listServer.getType());
+	}
+
+	public String getEmailId() {
+		return emailId;
+	}
+
+	public void setEmailId(String emailId) {
+		this.emailId = emailId;
+		record.fields().put("email_id", DbNull.fix(emailId));
+	}
+
+	public void update() {
+		record.update();
+	}
+
+	public boolean equals(Object obj) {
+		return obj instanceof MailingList && ((MailingList) obj).getId() == getId();
+	}
+
+	public int hashCode() {
+		return (int) getId();
+	}
+
+	public String toString() {
+		return "mailing_list-" + getId();
+	}
+
+	public ImportResult importMbox(File file, String mailErrorsTo, int maxErrors)
+		throws ModelException
+	{
+		ModelHome.beginImport();
+		try {
+			return MailingLists.importMbox(file, this, mailErrorsTo, maxErrors);
+		} finally {
+			ModelHome.endImport();
+		}
+	}
+
+	public MailAddress getSubscriberAddress() {
+		return new MailAddress( Lazy.emailPrefix
+			+ (emailId != null ? emailId
+				: "s" + siteKey.getId()
+				+ 'n' + getId()
+				+ 'h' + generateHash()
+			)
+		+ Lazy.emailSuffix );
+	}
+
+	public String getPassword(User user) {
+		Random r = new Random(getId() + user.getId());
+		long p = Math.round(r.nextDouble() * Math.pow(Character.MAX_RADIX, 8));
+		return Long.toString(p, Character.MAX_RADIX);
+	}
+
+	public String getExportOwner() {
+		return exportOwner;
+	}
+
+	public void setExportOwner(String email) throws ModelException.EmailFormat {
+		if( email != null ) {
+			email = email.trim();
+			UserImpl.validateEmail(email);
+		}
+		this.exportOwner = email;
+		record.fields().put("export_owner", DbNull.fix(this.exportOwner));
+	}
+
+	public Node getNodeFromMessageID(String messageID) {
+		return forum.getNodeImplFromMessageID(messageID);
+	}
+
+	public void subscribe() {
+		if (listServer.canSubscribe())
+			ModelHome.send(subscribeMail());
+	}
+
+	public void unsubscribe() {
+		if (listServer.canSubscribe())
+			ModelHome.send(unsubscribeMail());
+	}
+
+	public Mail subscribeMail() {
+		return listServer.subscribeMail(getSubscriberAddress(), getListAddress(), null, true);
+	}
+
+	public Mail subscribeMail(User user) {
+		MailAddress userAddress = new MailAddress(user.getEmail(), user.getName());
+		return listServer.subscribeMail(userAddress, getListAddress(), getPassword(user), false);
+	}
+
+	public Mail unsubscribeMail() {
+		return listServer.unsubscribeMail(getSubscriberAddress(), getListAddress(), null);
+	}
+
+	public Mail unsubscribeMail(User user) {
+		MailAddress userAddress = new MailAddress(user.getEmail(), user.getName());
+		return listServer.unsubscribeMail(userAddress, getListAddress(), null);
+	}
+
+	public Mail defaultsMail(User user, String password) {
+		MailAddress userAddress = new MailAddress(user.getEmail(), user.getName());
+		return listServer.defaultsMail(userAddress, getListAddress(), password);
+	}
+
+
+	static final ListenerList<MailingListImpl> preUpdateListeners = new ListenerList<MailingListImpl>();
+	static final ListenerList<MailingListImpl> postInsertListeners = new ListenerList<MailingListImpl>();
+	static final ListenerList<MailingListImpl> postUpdateListeners = new ListenerList<MailingListImpl>();
+	static final ListenerList<MailingListImpl> postDeleteListeners = new ListenerList<MailingListImpl>();
+
+	private static Computable<SiteKey,DbTable<LongKey,MailingListImpl>> tables = new SimpleCache<SiteKey,DbTable<LongKey,MailingListImpl>>(new WeakHashMap<SiteKey,DbTable<LongKey,MailingListImpl>>(), new Computable<SiteKey,DbTable<LongKey,MailingListImpl>>() {
+		public DbTable<LongKey,MailingListImpl> get(SiteKey siteKey) {
+			DbDatabase db;
+			try {
+				db = siteKey.getDb();
+			} catch(Db.NoSchema e) {
+				return null;
+			}
+			final long siteId = siteKey.getId();
+			DbTable<LongKey,MailingListImpl> table = db.newTable("mailing_list",db.newAssignedLongKeySetter("node_id")
+				, new DbObjectFactory<LongKey,MailingListImpl>() {
+					public MailingListImpl makeDbObject(LongKey key,ResultSet rs,String tableName)
+						throws SQLException
+					{
+						SiteKey siteKey = SiteKey.getInstance(siteId);
+						return new MailingListImpl(siteKey,key,rs);
+					}
+				}
+			);
+			table.getPreUpdateListeners().add(preUpdateListeners);
+			table.getPostInsertListeners().add(postInsertListeners);
+			table.getPostUpdateListeners().add(postUpdateListeners);
+			table.getPostDeleteListeners().add(postDeleteListeners);
+			return table;
+		}
+	});
+
+	private static DbTable<LongKey,MailingListImpl> table(SiteKey siteKey) {
+		return tables.get(siteKey);
+	}
+
+	public void rethread() {
+		try {
+			MailingLists.rethreadForum( getForumImpl(), false );
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/MailingLists.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1170 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbNull;
+import fschmidt.util.java.DateUtils;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailAddressException;
+import fschmidt.util.mail.MailEncodingException;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.MailIterator;
+import fschmidt.util.mail.MailParseException;
+import fschmidt.util.mail.Pop3Server;
+import fschmidt.util.mail.javamail.MstorInServer;
+import nabble.model.lucene.HitCollector;
+import nabble.model.lucene.LuceneSearcher;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.util.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.AddressException;
+
+
+final class MailingLists {
+	private static final Logger logger = LoggerFactory.getLogger(MailingLists.class);
+
+	private static final float nameChangeFreq = Init.get("mlNameChangeFreq",0.1f);
+	static final Pop3Server pop3Server = (Pop3Server)Init.get("mailingListArchivePop3Server");
+
+	private MailingLists() {}  // never
+
+	static {
+		if( Init.hasDaemons ) {
+			runMailingLists();
+		}
+	}
+
+	private static void runMailingLists() {
+		if( pop3Server == null ) {
+			logger.warn("no pop3 server defined, mailing lists not running");
+			return;
+		}
+		Executors.scheduleWithFixedDelay(new Runnable() {
+			public void run(){
+				try {
+					processMail();
+					processFwds();
+				} catch(MailException e) {
+					logger.error("mailing list processing",e);
+				}
+			}
+		}, 10, 10, TimeUnit.SECONDS );
+		logger.info("mailing lists enabled");
+	}
+
+	private static void processMail() {
+		MailIterator mails = pop3Server.getMail();
+		int count = 0;
+		try {
+			while( mails.hasNext() ) {
+				Mail mail = mails.next();
+				try {
+					makePost(mail);
+					count++;
+				} catch (MailAddressException e) {
+					logger.warn("mail:\n"+mail.getRawInput(),e);  // screwed-up mail
+				} catch (Exception e) {
+					logger.error("mail:\n"+mail.getRawInput(),e);
+				}
+			}
+		} finally {
+			mails.close();
+			if( count > 0 )
+				logger.error("Processed " + count + " emails.");
+		}
+	}
+
+	static MailingList.ImportResult importMbox(File file,MailingListImpl ml,String mailErrorsToS,int maxErrors)
+		throws ModelException
+	{
+		final DateFormat mailmanDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy");
+		final DateFormat mailDateFormat = new javax.mail.internet.MailDateFormat();
+		mailmanDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+		mailDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+		MailAddress mailErrorsTo = new MailAddress(mailErrorsToS);
+		MstorInServer server = new MstorInServer(file);
+		server.setMetaEnabled(false);
+		MailIterator mails = server.getMail();
+		try {
+			int imported = 0;
+			int errors = 0;
+			while( mails.hasNext() ) {
+				Mail mail = mails.next();
+				try {
+					try {
+						mail.getFrom();
+					} catch (MailAddressException e) {
+						String[] from = mail.getHeader("From");
+						if (from == null || from.length == 0)
+							throw new MailAddressException("'From' not found in the header", e);
+						mail.setHeader("From", from[0].replace(" at ", "@"));
+					}
+					Date sentDate = mail.getSentDate();
+					if ((sentDate==null || sentDate.getTime() < 0) && mail.getHeader("Date")!=null) {
+						String dateH = mail.getHeader("Date")[0];
+						if (dateH!=null) {
+							try {
+								sentDate = mailmanDateFormat.parse(dateH);
+							} catch (java.text.ParseException e) {}
+							if (sentDate!=null)
+								mail.setSentDate(sentDate);
+						}
+					}
+					if ((sentDate==null || sentDate.getTime() < 0) && mail.getHeader("Resent-date")!=null) {
+						String dateH = mail.getHeader("Resent-date")[0];
+						if (dateH!=null) {
+							try {
+								sentDate = mailDateFormat.parse(dateH);
+							} catch (java.text.ParseException e) {}
+							if (sentDate!=null)
+								mail.setSentDate(sentDate);
+						}
+					}
+					if ((sentDate==null || sentDate.getTime() < 0)) {
+						String rawInput = mail.getRawInput();
+						try {
+							String dateH = rawInput.substring(rawInput.indexOf(' ',5), rawInput.indexOf('\n')).trim();
+							sentDate = mailmanDateFormat.parse(dateH);
+						} catch (Exception e) {
+							logger.error("",e);  // what kind of exception is ok?
+						}
+						if (sentDate!=null)
+							mail.setSentDate(sentDate);
+					}
+					makeForumPost(mail,ml,true);
+					imported++;
+				} catch (Exception e) {
+					sendErrorMail(mail, e, mailErrorsTo);
+					errors++;
+					if( errors >= maxErrors )
+						throw ModelException.newInstance("import_mbox_errors",""+errors+" errors reached after importing "+imported+" messages");
+				}
+			}
+			final int imported2 = imported;
+			final int errors2 = errors;
+			return new MailingList.ImportResult() {
+				public int getImported() { return imported2; }
+				public int getErrors() { return errors2; }
+			};
+		} finally {
+			mails.close();
+		}
+	}
+
+	private static void makePost(Mail mail)
+		throws ModelException
+	{
+		MailingListImpl ml = getMailingList(mail);
+		if (ml == null) {
+			logger.info("Mailing list not found for: " + Arrays.asList(mail.getTo()));
+			return;
+		}
+		if (checkForward(mail, ml)) {
+			return;
+		}
+		if (checkPending(mail, ml)) {
+			return;
+		}
+		makeForumPost(mail, ml, false);
+	}
+
+	private static void makeForumPost(Mail mail, MailingListImpl ml, boolean isImport)
+		throws ModelException
+	{
+		String messageID = getMessageID(mail, msgFmt);
+		mail.setMessageID(messageID);
+
+		String message = mail.getRawInput();
+		message = message.replace("\000","");  // postgres can't handle 0
+		if( !msgFmt.isOk(message) )
+			return;
+		String text = msgFmt.getMailText(message,null);
+		NodeImpl forum = ml.getForumImpl();
+
+		if( doNotArchive(text) || (doNotArchive(mail) && !ml.ignoreNoArchive()) ) {
+			logger.info("XNoArchive in "+forum.getSubject());
+			return;
+		}
+
+		DbDatabase db;
+		try {
+			db = forum.siteKey.getDb();
+		} catch(UpdatingException e) {
+			return;  // hack for schema migration
+		}
+		db.beginTransaction();
+		try {
+			forum = (NodeImpl)forum.getGoodCopy();
+			MailingListImpl mailingList = forum.getMailingListImpl();
+
+			{
+				NodeImpl post = forum.getNodeImplFromMessageID(messageID);
+				if( post != null) {
+					if(isImport)
+						return;
+					throw new RuntimeException("MessageID "+messageID+" already in db for forum "+forum.getId());
+				}
+			}
+
+			UserImpl user = getUser(mail, mailingList);
+			if (user.isNoArchive())
+				return;
+
+			String subject = mailingList.fixSubject(mail.getSubject());
+			if( subject==null || subject.trim().equals("") )
+				subject = "(no subject)";
+
+			if (!isImport) {
+				ListServer oldListServer = mailingList.getListServer();
+				if (oldListServer==ListServer.unknown || oldListServer instanceof ListServer.Mailman) {
+					ListServer listServer = detectListServer(mail);
+					if (listServer!=null && listServer!=oldListServer && (oldListServer==ListServer.unknown || listServer==ListServer.mailman21)) {
+						mailingList.setListServer(listServer);
+						mailingList.update();
+					}
+				}
+			}
+
+			Date now = new Date();
+			Date date = mail.getSentDate();
+			if( date==null || date.compareTo(now) > 0 || date.getTime() < 0)
+				date = now;
+
+			boolean isGuessedParent = false;
+			String parentID = getParentID(mail, messageID);
+			NodeImpl parent = forum.getNodeImplFromMessageID(parentID);
+			if ( parent!=null && threadBySubject(forum, subject, parent.getSubject()) ) {
+				parent = null;
+			}
+
+			NodeImpl[] orphans = NodeImpl.getFromParentID(messageID,mailingList);
+			if ( parent==null ) {
+				try {
+					parent = guessParent(mail, date, mailingList, subject, orphans);
+					if ( parent != null )
+						isGuessedParent = true;
+				} catch(IOException e) {
+					logger.error("guessParent failed",e);
+				}
+			}
+
+			NodeImpl post = NodeImpl.newChildNode(Node.Kind.POST,user,subject,message,msgFmt,parent==null?forum:parent);
+			if( parent==null && parentID != null ) {
+				logger.debug("Orphan "+messageID+" starting new thread ");
+				isGuessedParent = true;
+			}
+
+			post.setWhenCreated(date);
+			post.setMessageID(messageID);
+			if (isGuessedParent) {
+				post.setGuessedParent(parentID);
+			} else if (parent==null) {
+				//  for root posts which do not have parentID set guess flag to uncertain
+				post.setGuessedParent((Boolean) null);
+			}
+			post.insert(false);
+			if( isGuessedParent && parentID==null )
+				logger.debug("no parentID for "+post);
+
+			for (NodeImpl orphan : orphans) {
+				try {
+					if (!threadBySubject(forum, subject, orphan.getSubject())) {
+						orphan.changeParentImpl(post);
+					}
+				} catch (ModelException.NodeLoop e) {
+					logger.error("", e); // should not happen now...
+					orphan.getDbRecord().fields().put("parent_message_id", DbNull.STRING);
+					orphan.getDbRecord().update();
+				}
+			}
+
+			db.commitTransaction();
+		} finally {
+			db.endTransaction();
+		}
+	}
+
+	private static boolean threadBySubject(Node forum, String subject, String parentSubject) {
+		if (!forumsThreadedBySubject.contains(forum.getId())) return false;
+		return ! normalizeSubject(subject).equals(normalizeSubject(parentSubject));
+	}
+
+	static final Set<Long> forumsThreadedBySubject = new HashSet<Long>(Arrays.asList((Long[])Init.get("forumsThreadedBySubject", new Long[0])));
+
+
+	private static void sendErrorMail(Mail mail, Exception e, MailAddress mailTo) {
+		if( e instanceof UnsupportedEncodingException
+			|| e instanceof MailAddressException
+			|| e instanceof MailEncodingException
+			|| e instanceof MailParseException
+		) {
+			logger.info(e.toString());
+		} else {
+			logger.error("",e);
+		}
+		StringWriter sb = new StringWriter();
+		PrintWriter out = new PrintWriter(sb);
+		e.printStackTrace( out );
+		out.close();
+		String msg = e.getMessage();
+		if (msg!=null && msg.indexOf('\n')>=0) msg = msg.substring(0, msg.indexOf('\n')).trim();
+		String subject = "error: "+msg;
+		MailSubsystem.sendErrorMail(mail, mailTo, subject, sb.toString());
+	}
+
+	private static String getParentID(Mail mail, String messageID) {
+		String[] inReplyTos = mail.getHeader("In-Reply-To");
+		if( inReplyTos == null ) {
+			inReplyTos = mail.getHeader("In-Reply-to");
+			if( inReplyTos != null )
+				logger.error("does this happen - case sensitive");
+		}
+		if( inReplyTos != null ) {
+			for (String inReplyTo : inReplyTos) {
+				for( String s : MailSubsystem.stripMultiBrackets(inReplyTo) ) {
+					if (!s.equals(messageID) && !s.equals("")) return s;
+				}
+			}
+		}
+		try {
+			String[] refs = mail.getHeader("References");
+			if( refs != null ) {
+				for (String ref : refs) {
+					List<String> list = MailSubsystem.stripMultiBrackets(ref);
+					if( list.isEmpty() )
+						continue;
+					String s = list.get(list.size()-1);
+					if (!s.equals(messageID) && !s.equals("")) return s;
+				}
+			}
+		} catch(MailParseException e) {
+			logger.warn("screwed up References for messageID="+messageID,e);
+		}
+		return null;
+	}
+
+	private static NodeImpl guessParent(Mail mail, Date date, MailingListImpl mailingList, String subject, NodeImpl[] orphans) throws IOException {
+		Set<NodeImpl> offspring = new HashSet<NodeImpl>();
+		for (NodeImpl orphan : orphans) {
+			for (NodeImpl n : orphan.getDescendantImpls()) {
+				offspring.add(n);
+			}
+		}
+		return guessParent(mail, date, mailingList, subject, offspring);
+	}
+
+	static NodeImpl guessParent(NodeImpl post, Collection<NodeImpl> ignore) {
+		Mail mail = MailHome.newMail(post.getMessage().getRaw());
+		NodeImpl forum = post.getAppImpl();
+		if (forum == null) {
+			return null;  //  detached post
+		}
+		MailingListImpl mailingList = forum.getAssociatedMailingListImpl();
+		if (mailingList == null) {
+			return null; // forum no longer a mailing list
+		}
+		try {
+			Set<NodeImpl> ignoreSet = new HashSet<NodeImpl>();
+			for( NodeImpl n : post.getDescendantImpls() ) {
+				ignoreSet.add(n);
+			}
+			if (ignore != null) {
+				ignoreSet.addAll(ignore);
+			}
+			return guessParent(mail, post.getWhenCreated(), mailingList, post.getSubject(), ignoreSet);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static NodeImpl guessParent(Mail mail, Date date, MailingListImpl mailingList, String subject, Set<NodeImpl> offspring)
+		throws IOException {
+		// attach to ancestor if any
+		NodeImpl forum = mailingList.getForumImpl();
+		try {
+			String[] refs = mail.getHeader("References");
+			if( refs != null ) {
+				for( String ref : refs ) {
+					final List<String> list = MailSubsystem.stripMultiBrackets(ref);
+					for( int i=list.size()-1; i>=0; i-- ) {
+						String ancestorID = list.get(i);
+						NodeImpl parent = forum.getNodeImplFromMessageID(ancestorID);
+						if (parent!=null && !offspring.contains(parent) && !threadBySubject(forum,subject,parent.getSubject())) {
+							logger.debug("Attaching orphan "+mail.getMessageID()+" to grandparent "+parent);
+							return parent;
+						}
+					}
+				}
+			}
+		} catch(MailParseException e) {
+			logger.warn("screwed up References",e);
+		}
+		// handle lost In-Reply-To headers
+		// heuristics - use Thread-Topic header to find matching subjects in last 3 days
+		String[] threadTopics = mail.getHeader("Thread-Topic");
+		String threadTopic = threadTopics==null?null:mailingList.fixSubject(threadTopics[0]);
+		long forumId = forum.getId();
+		Filter filter = Lucene.getRangeFilter(DateUtils.addDays(date, -7), date);
+		SiteImpl site = forum.getSiteImpl();
+		LuceneSearcher searcher = Lucene.newSearcher(site);
+		try {
+			if( threadTopic != null ) {
+				threadTopic = threadTopic.toLowerCase();
+				if (threadTopic.startsWith("re: "))
+					threadTopic = threadTopic.substring(4);
+				threadTopic = threadTopic.trim();
+				if (!threadTopic.equals("")) {
+					NodeImpl parent = (NodeImpl)getPriorPost(site,searcher, forumId, threadTopic, filter, date, offspring);
+					if( parent!=null && !offspring.contains(parent) && !threadBySubject(forum,subject,parent.getSubject())) return parent;
+				}
+			}
+			// if no thread-topic, but subject starts with Re:, try with subject
+			subject = subject.toLowerCase();
+			if( subject.startsWith("re: ") ) {
+				subject = subject.substring(4).trim();
+				if ( !subject.equals(threadTopic) && !"".equals(subject) ) {
+					NodeImpl parent = (NodeImpl)getPriorPost(site,searcher, forumId, subject, filter, date, offspring);
+					if( parent!=null && !offspring.contains(parent)) return parent;
+				}
+			}
+		} finally {
+			searcher.close();
+		}
+		return null;
+	}
+
+	private static boolean checkPending(Mail mail, MailingListImpl ml) {
+		if (checkPending(getMessageID(mail, msgFmt), ml)) {
+			return true;
+		}
+		String[] xMessageId = mail.getHeader("X-Message-Id");
+		if (xMessageId != null && xMessageId.length > 0 && checkPending(MailSubsystem.stripBrackets(xMessageId[0]), ml)) {
+			return true;
+		}
+		xMessageId = mail.getHeader("X-Original-Message-Id");
+		if (xMessageId != null && xMessageId.length > 0 && checkPending(MailSubsystem.stripBrackets(xMessageId[0]), ml)) {
+			return true;
+		}
+		return false;
+	}
+
+	private static boolean checkPending(String messageID, MailingListImpl ml) {
+		NodeImpl pendingPost = ml.getForumImpl().getNodeImplFromMessageID(messageID);
+		if( pendingPost==null )
+			return false;
+		Node.MailToList mail = pendingPost.getMailToList();
+		if( mail == null ) {
+			logger.warn("MessageID "+messageID+" already in db as "+pendingPost+" for forum "+ml.getId());
+		} else if( !mail.isPending() ) {
+			logger.error("post not pending "+pendingPost);
+		} else {
+			mail.clearPending();
+		}
+		return true;
+	}
+
+	private static boolean doNotArchive(Mail mail) {
+		String[] xNoArchive = mail.getHeader("X-No-Archive");
+		if (xNoArchive != null && xNoArchive.length > 0 && "yes".equalsIgnoreCase(xNoArchive[0])) {
+			return true;
+		}
+		String[] xArchive = mail.getHeader("X-Archive");
+		if (xArchive != null && xArchive.length > 0 && xArchive[0] != null && (xArchive[0].startsWith("expiry") || "no".equalsIgnoreCase(xArchive[0]))) {
+			return true;
+		}
+		String[] archive = mail.getHeader("Archive");
+		if (archive != null && archive.length > 0 && "no".equalsIgnoreCase(archive[0])) {
+			return true;
+		}
+		return false;
+	}
+
+	private static final Pattern xNoArchivePtn = Pattern.compile("(?im)\\AX-No-Archive: yes *$");
+	private static boolean doNotArchive(String text) {
+		return xNoArchivePtn.matcher(text).find();
+	}
+
+	private static MailingListImpl getMailingList(Mail mail) {
+		MailingListImpl ml = null;
+		String[] a = mail.getHeader("Envelope-To");
+		if (a == null)
+			a = mail.getHeader("X-Delivered-to"); // fastmail
+		if (a == null)
+			a = mail.getHeader("X-Original-To"); // postfix
+		if( a.length > 1 )
+			a = new String[] { a[0] };
+		for( String address : a[0].split(",") ) {
+			address = address.trim();
+			MailingListImpl candidate = MailingListImpl.getMailingListByEnvelopeAddress(address);
+			if (candidate == null) {
+				//  escaped list mail, bounce mail
+				String returnPath = MailSubsystem.getReturnPath(mail);
+				if( returnPath.equals(address) )
+					continue;  // ignore spam
+				MailSubsystem.bounce(mail,
+					"Delivery to the following recipient failed permanently:\n\n    "
+					+ address
+					+ "\n\nNo archive exists for this address.\n"
+				);
+				logger.warn( "no mailing list found for "+address+" - bouncing mail to "+returnPath + ":\n" + mail);
+			} else {
+				if( ml != null )
+					logger.error("mailing list already set");
+				ml = candidate;
+			}
+		}
+		return ml;
+	}
+
+	private static String extractDomain(String email) {
+		String domain = email.substring(email.indexOf('@')+1).toLowerCase();
+		// hack to unify google messages
+		return domain.replace("google.com","googlegroups.com");
+	}
+
+	private static boolean checkForward(Mail mail, MailingListImpl ml) {
+		//  check if the archive guessed from subscription address is not presented in the
+		//    common headers that contain list address, forward to the archive owner in this case
+		String envTo[] = mail.getHeader("Envelope-To");
+		if (envTo == null)
+			envTo = mail.getHeader("X-Delivered-to"); // fastmail
+		if (envTo == null)
+			envTo = mail.getHeader("X-Original-To"); // postfix
+		String originalTo = envTo[0];
+		{
+			MailAddress[] to = mail.getTo();
+			if( to==null || to.length!=1 || !to[0].getAddrSpec().equalsIgnoreCase(originalTo) )
+				return false;
+		}
+		//  check for domain of the message's From: or Reply-To:
+		String listAddress = ml.getListAddress();
+		String domain = extractDomain(listAddress);
+		String maintenanceMessageReplyTo = null;
+		{
+			MailAddress[] replyTos = mail.getReplyTo();
+			if (replyTos != null) {
+				for (MailAddress replyTo : replyTos) {
+					String replyDomain = extractDomain(replyTo.getAddrSpec());
+					if (replyDomain.endsWith(domain) || domain.endsWith(replyDomain)) {
+						maintenanceMessageReplyTo = replyTo.getAddrSpec();
+						break;
+					}
+				}
+			}
+		}
+		MailAddress from = mail.getFrom();
+		// first we compare the domains
+		if( maintenanceMessageReplyTo == null && from != null && (extractDomain(from.getAddrSpec()).endsWith(domain) || domain.endsWith(extractDomain(from.getAddrSpec()))))
+			maintenanceMessageReplyTo = from.getAddrSpec();
+		// check if this is a majordomo email
+		if (maintenanceMessageReplyTo == null && from != null && from.getAddrSpec().toLowerCase().startsWith("majordomo@"))
+			maintenanceMessageReplyTo = from.getAddrSpec();
+
+		if( maintenanceMessageReplyTo != null ) {
+			mail.setReplyTo( new MailAddress(fwdEmail(originalTo, maintenanceMessageReplyTo) ));
+			MailAddress ownerAddress = getArchiveOwnerAddress(ml);
+			MailHome.getDefaultSmtpServer().send(mail,ownerAddress);
+			logger.info("Forwarding maintenance message to owner: " + ownerAddress);
+			logger.info(mail.getRawInput());
+			return true;
+		}
+		if( MailSubsystem.getReturnPath(mail).equals("") ) {
+			MailAddress ownerAddress = getArchiveOwnerAddress(ml);
+			MailHome.getDefaultSmtpServer().send(mail,ownerAddress);
+			logger.info("Forwarding maintenance message to owner: " + ownerAddress);
+			logger.info(mail.getRawInput());
+			return true;
+		}
+		logger.info("Bouncing email to: " + MailSubsystem.getReturnPath(mail) + " / envelopeTo = " + originalTo + "\n" + mail.getRawInput());
+		MailSubsystem.bounce(mail,
+			"Delivery to the following recipient failed permanently:\n\n    "
+			+ originalTo
+			+ "\n\nThis email address is only for archiving mailing lists and should not be used directly.\n"
+		);
+		return true;
+	}
+
+	private static MailAddress getArchiveOwnerAddress(MailingListImpl mailingList) {
+		// If this list was exported to another server, we have to send this email
+		// to the person that did the export. Otherwise we send to the current owner.
+		String exportOwner = mailingList.getExportOwner();
+		if (exportOwner == null) {
+			// Send to the current owner...
+			User owner = mailingList.getForumImpl().getOwnerImpl();
+			return new MailAddress(owner.getEmail(), owner.getName());
+		} else {
+			// Send to the person who exported the archive...
+			return new MailAddress(exportOwner);
+		}
+	}
+
+	private static MailAddress toMailAddress(String s) {
+		try {
+			InternetAddress ia = new InternetAddress(s);
+			return new MailAddress(ia.getAddress(),ia.getPersonal());
+		} catch(AddressException e) {
+			return null;
+		}
+	}
+
+	private static UserImpl getUser(Mail mail, MailingListImpl mailingList) {
+		MailAddress addr = null;
+		String a[] = mail.getHeader("X-Original-From");
+		if( a != null )
+			addr = toMailAddress(a[0]);
+		if( addr == null )
+			addr = mail.getFrom();
+		String email = addr.getAddrSpec();
+		if (email == null || "".equals(email.trim()))
+		{
+			throw new MailAddressException("Invalid sender address: "+addr);
+		}
+		SiteImpl site = mailingList.getForumImpl().getSiteImpl();
+		UserImpl user = site.getUserImplFromEmail(email);
+		if( user==null || !user.isRegistered() ) {
+			String username;
+			if( email.equalsIgnoreCase(mailingList.getListAddress()) ) {
+				username = mailingList.getForum().getSubject() + " mailing list";
+			} else {
+				username = addr.getDisplayName();
+				if( username == null || "".equals(username.trim()) ) {
+					username = email.indexOf('@')>0 ? email.substring(0, email.indexOf('@')) : email;
+				}
+			}
+			if( username.endsWith(" (JIRA)") ) {
+				username = "JIRA "+email;
+			}
+			if( user==null ) {
+				user = UserImpl.createGhost(site,email);
+				user.setNameLike(username,false);
+				user.insert();
+			} else {
+				String oldName = user.getName();
+				if( !oldName.toLowerCase().startsWith(username.toLowerCase())
+					&& (Math.random() < nameChangeFreq)
+				) {
+					user.setNameLike(username,false);
+					user.getDbRecord().update();
+					logger.warn("changed name of "+user+" from '"+oldName+"' to '"+user.getName()+"'");
+				}
+			}
+		}
+		return user;
+	}
+
+	static final MailMessageFormat msgFmt = new MailMessageFormat('m', "mail");
+
+	private static Node getPriorPost(final SiteImpl site,final LuceneSearcher searcher, long forumId, final String subject, Filter filter, final Date to, final Set offspring) throws IOException {
+		//String phrase = "\""+QueryParser.escape(subject.replace('\"',' '))+"\"";
+		try {
+			NodeSearcher.Builder query = new NodeSearcher.Builder(site,forumId);
+			query.addNodeKind(Node.Kind.POST);
+			QueryParser parser = new QueryParser(Version.LUCENE_CURRENT,Lucene.SUBJECT_FLD, Lucene.analyzer);
+			parser.setDefaultOperator(QueryParser.AND_OPERATOR);
+			Query subjectQuery = parser.parse(QueryParser.escape(subject.replace('\"',' ').replace("&&"," ")));
+			if (! (subjectQuery instanceof BooleanQuery && ((BooleanQuery)subjectQuery).getClauses().length==0) )
+				query.addQuery(subjectQuery);
+			final Node[] resultHolder = new Node[1];
+			searcher.search( query.build().getQuery(), filter, new HitCollector() {
+				protected void process(Document doc) {
+					NodeImpl post = Lucene.node(site,doc);
+					if (post==null)
+						return;
+					String parentSubject = post.getSubject().toLowerCase();
+					if ( (parentSubject.equals(subject) || (parentSubject.startsWith("re: ") && parentSubject.substring(4).trim().equals(subject)))
+							&& to.after(post.getWhenCreated())
+							&& (resultHolder[0]==null || resultHolder[0].getWhenCreated().before(post.getWhenCreated()))
+							&& !offspring.contains(post)
+					)
+						resultHolder[0] = post;
+				}
+			});
+			Node result = resultHolder[0];
+
+			if (result != null) {
+				//  find the uppermost post with almost-the-same subject
+				String subjectEtalon = normalizeSubject(subject.toLowerCase());
+				Node resultCandidate = result.getTopic();
+				while (result != null) {
+					String resultSubject = normalizeSubject(result.getSubject().toLowerCase());
+					if (!resultSubject.equals(subjectEtalon)) break; //  break when subject really changes
+
+					//  this post has almost-the-same subject
+					if (!offspring.contains(result)) {
+						//  set only if this node is not presented in escape-set
+						resultCandidate = result;
+					}
+					result = result.getParent();
+				}
+				result = resultCandidate;
+			}
+			return result;
+		} catch (ParseException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static final Pattern bracketRegex = Pattern.compile("\\[[^\\[]+\\]");
+
+	static Pattern prefixRegex(String prefixes) {
+		return Pattern.compile( "^((" + prefixes + "): *)+" );
+	}
+
+	private static final Pattern defaultPrefixRegex = prefixRegex("re|aw|res|fwd|答复");
+
+	/**
+	 * Remove from subject all possible prefixes which are added while forwarding, replying, etc...
+	 *
+	 * @param subject original subject
+	 * @return normalized subject
+	 */
+	private static String normalizeSubject(String subject) {
+		return normalizeSubject(subject,defaultPrefixRegex);
+	}
+
+	static String normalizeSubject(String subject,Pattern prefixRegex) {
+		if (subject != null) {
+			subject = subject.toLowerCase().trim();
+			subject = bracketRegex.matcher(subject).replaceAll("");
+			subject = prefixRegex.matcher(subject).replaceAll("");
+		}
+		return subject;
+	}
+
+	static void nop() {}
+
+
+	private static ListServer detectListServer(Mail mail) {
+		String[] mailman = mail.getHeader("X-Mailman-Version");
+		if (mailman!=null && mailman.length==1 && mailman[0]!=null) {
+			if (mailman[0].startsWith("2.0")) {
+				return ListServer.mailman20;
+			} else if (mailman[0].startsWith("2.1")) {
+				return ListServer.mailman21;
+			} else if (mailman[0].startsWith("2.")) {
+				logger.error("unknown mailman version: "+mailman[0]+" in message "+mail.getMessageID());
+			}
+			return null;
+		}
+
+		String[] mList = mail.getHeader("Mailing-List");
+		if (mList!=null && mList.length==1 && mList[0]!=null) {
+			if (mList[0].indexOf("run by ezmlm")>=0) {
+				return ListServer.ezmlm;
+			} else if (mList[0].indexOf("@yahoogr")>0 || mList[0].indexOf("@gruposyahoo")>0) {
+				return ListServer.yahoo;
+			} else if (mList[0].indexOf("@googlegroups")>0) {
+				return ListServer.google;
+			}
+		}
+
+		String[] listproc = mail.getHeader("X-Listprocessor-Version");
+		if (listproc!=null && listproc.length==1 && listproc[0]!=null) {
+			if (listproc[0].indexOf("ListProc")>=0) {
+				if (listproc[0].indexOf("CREN")>=0) {
+					return ListServer.listproc;
+				} else {
+					return ListServer.oldlistproc;
+				}
+			} else {
+				logger.error("unknown listproc version: "+listproc[0]+" in message "+mail.getMessageID());
+				return null;
+			}
+		}
+
+		String[] ecartis = mail.getHeader("X-ecartis-version");
+		if (ecartis!=null && ecartis.length==1 && ecartis[0]!=null) {
+			if (ecartis[0].indexOf("Ecartis")>=0) {
+				return ListServer.ecartis;
+			} else {
+				logger.error("unknown ecartis version: "+ecartis[0]+" in message "+mail.getMessageID());
+				return null;
+			}
+		}
+
+		String[] lyris = mail.getHeader("X-LISTMANAGER-Message-Id");
+		if (lyris!=null && lyris.length==1 && lyris[0]!=null) {
+			if (lyris[0].indexOf("LISTMANAGER")>=0) {
+				return ListServer.lyris;
+			} else {
+				logger.error("unexpected x-listmanager-message-id header: "+lyris[0]+" in message "+mail.getMessageID());
+				return null;
+			}
+		}
+
+		String[] xListServer = mail.getHeader("X-ListServer");
+		if (xListServer!=null && xListServer.length==1 && xListServer[0]!=null) {
+			if (xListServer[0].indexOf("CommuniGate")>=0) {
+				return ListServer.communigate;
+			} else {
+				logger.error("unknown x-listserver header: "+xListServer[0]+" in message "+mail.getMessageID());
+				return null;
+			}
+		}
+
+		// may not be reliable
+		String[] listSubscribe = mail.getHeader("List-Subscribe");
+		if (listSubscribe!=null && listSubscribe.length==1 && listSubscribe[0]!=null) {
+			if (listSubscribe[0].indexOf("+subscribe@")>0) {
+				return ListServer.mlmmj;
+			} else if (listSubscribe[0].indexOf("listserver@")>=0) {
+				return ListServer.listserver;
+			} else if (listSubscribe[0].indexOf("sympa@")>=0) {
+				return ListServer.sympa;
+			}
+		}
+
+		// not possible to detect:
+		// listserv
+		// majordomo / majordomo2
+		// smartlist
+
+		return null;
+	}
+/*
+	static void redoGuessedParents() {}
+
+	public static void rethreadPosts(boolean inBatch) throws SQLException{
+		rethreadPosts(1, 0, inBatch);
+	}
+
+	public static void rethreadPosts(long startingPostId, boolean inBatch) throws SQLException{
+		rethreadPosts(startingPostId, 0, inBatch);
+	}
+
+	static void rethreadPosts(long startingPostId, long forumId, boolean inBatch) throws SQLException{
+		Logger batchLog = inBatch ? Batch.logger : logger;
+		if(startingPostId > 1)
+			batchLog.info("Starting rethread from post " + startingPostId);
+		if(forumId > 0)
+			batchLog.info("Rethreading for forum " + forumId);
+
+		//the WHERE condition is post_id > postId !
+		long postId = startingPostId - 1;
+		int processed_post_count = 0;
+		int modified_post_count = 0;
+		int null_parent_count = 0;
+		NodeImpl post = null;
+		boolean more = true;
+		try {
+			outer: while (more) {
+				Connection con = Db.db.getConnection();
+				try {
+					con.setAutoCommit(false);
+					PreparedStatement stmt = con.prepareStatement(
+							(forumId > 0) ?
+									"SELECT * FROM descendants(" + forumId + ") WHERE node_id > ? AND " +
+											"(guessed_parent='t' OR guessed_parent is null) AND " +
+											"msg_fmt='m'" +
+											"ORDER BY node_id LIMIT 1"
+									:
+									"SELECT * FROM node WHERE node_id > ? AND " +
+											"(guessed_parent='t' OR guessed_parent is null) AND " +
+											"msg_fmt='m'" +
+											"ORDER BY node_id LIMIT 1"
+					);
+					stmt.setLong(1, postId);
+					ResultSet rs = stmt.executeQuery();
+					more = false;
+					while (rs.next()) {
+						more = true;
+						post = NodeImpl.getNode(rs);
+						try {
+							postId = post.getId();
+							Mail mail = MailHome.newMail(post.getMessage().getRaw());
+							MailingListImpl mailingList = post.getAppImpl().getAssociatedMailingListImpl();
+							if (mailingList==null) continue; // forum no longer a mailing list
+							List<NodeImpl> descendants = new ArrayList<NodeImpl>();
+							for( NodeImpl n : post.getDescendantImpls() ) {
+								descendants.add(n);
+							}
+							NodeImpl parent = guessParent(mail, post.getWhenCreated(), mailingList, post.getSubject(), descendants.toArray(new NodeImpl[0]));
+
+							if (parent != null && parent.getId() != post.getParentId()) {
+								try {
+									batchLog.debug("setting parent of " + post + " to " + parent);
+									post.setGuessedParent(parent);
+									if(++ modified_post_count % 100 == 0)
+										batchLog.info("Modified " + modified_post_count + " posts");
+								} catch (ModelException.NodeLoop e) {
+									batchLog.error("",e);
+								}
+							} else if(parent == null && post.getParentId() != 0 && post.getParent().getKind()!=Node.Kind.APP){
+								batchLog.info("Null parent at " + post);
+								null_parent_count ++;
+							}
+						} catch(Exception x){
+							batchLog.error("Exception at post " + post, x);
+							break outer;
+						}
+
+						if(++ processed_post_count % 3000 == 0)
+							batchLog.info("Processed " + processed_post_count + " posts, current postId: "+postId);
+
+						if (inBatch) {
+							Batch.checkStopped();
+						}
+					}
+					stmt.close();
+					con.commit();
+				} finally {
+					con.close();
+				}
+			}
+		} finally {
+			batchLog.info("Exited at post " + post);
+			batchLog.info("Processed " + processed_post_count + " posts");
+			batchLog.info("Modified " + modified_post_count + " posts");
+			batchLog.info("Guessed null parent at " + null_parent_count + " posts");
+		}
+	}
+*/
+	private static void getRethreadIds(Connection con,long parentId,Collection<Long> ids)
+		throws SQLException
+	{
+		PreparedStatement stmt = con.prepareStatement(
+			"select node_id, guessed_parent, msg_fmt from node where parent_id = ?"
+		);
+		stmt.setLong(1,parentId);
+		ResultSet rs = stmt.executeQuery();
+		while( rs.next() ) {
+			long id = rs.getLong("node_id");
+			if( "m".equals(rs.getString("msg_fmt"))
+				&& ( rs.getBoolean("guessed_parent") || rs.wasNull() )
+			)
+				ids.add(id);
+			getRethreadIds(con,id,ids);
+		}
+		rs.close();
+		stmt.close();
+	}
+
+	static void rethreadForum(NodeImpl forum, boolean inBatch) throws SQLException{
+		long forumId = forum.getId();
+		long rethreadStart = System.currentTimeMillis();
+		Logger batchLog = inBatch ? Batch.logger : logger;
+		batchLog.info("Rethreading for forum " + forumId);
+		SiteKey siteKey = forum.getSiteImpl().siteKey;
+		DbDatabase db = siteKey.getDb();
+
+		Collection<Long> ids = new ArrayList<Long>();
+		{
+			Connection con = db.getConnection();
+			long queryStart = System.currentTimeMillis();
+			getRethreadIds(con,forumId,ids);
+			batchLog.info("Query took " + (System.currentTimeMillis() - queryStart) + " ms");
+			con.close();
+		}
+
+		batchLog.info(ids.size() + " posts to process...");
+
+		//the WHERE condition is post_id > postId !
+		int processed_post_count = 0;
+		int modified_post_count = 0;
+		int null_parent_count = 0;
+
+		try {
+			while (ids.size() > 0) {
+				Connection con = db.getConnection();
+				try {
+					con.setAutoCommit(false);
+
+					long id = ids.iterator().next();
+					ids.remove(id);
+					NodeImpl post = NodeImpl.getNode(siteKey,id);
+					try {
+						Mail mail = MailHome.newMail(post.getMessage().getRaw());
+						MailingListImpl mailingList = post.getAppImpl().getAssociatedMailingListImpl();
+						if (mailingList==null) continue; // forum no longer a mailing list
+						List<NodeImpl> descendants = new ArrayList<NodeImpl>();
+						for( NodeImpl n : post.getDescendantImpls() ) {
+							descendants.add(n);
+						}
+						NodeImpl parent = guessParent(mail, post.getWhenCreated(), mailingList, post.getSubject(), descendants.toArray(new NodeImpl[0]));
+
+						if (parent != null && parent.getId() != post.getParentId()) {
+							try {
+								batchLog.info("setting parent of " + post + " to " + parent);
+								post.setGuessedParent(parent);
+
+								if(++ modified_post_count % 1000 == 0)
+									batchLog.info("Modified " + modified_post_count + " posts");
+							} catch (ModelException.NodeLoop e) {
+								batchLog.error("",e);
+							}
+						} else if(parent == null && post.getParentId() != 0 && post.getParent().getKind()!=Node.Kind.APP){
+							batchLog.info("Null parent at " + post);
+							null_parent_count ++;
+						}
+					} catch(Exception x){
+						batchLog.error("Exception at " + post + " - message:\n"+post.getMessage().getRaw(), x);
+						break;
+					}
+
+					if(++ processed_post_count % 1000 == 0)
+						batchLog.info("Processed " + processed_post_count + " posts, current postId: "+id);
+
+					if (inBatch) {
+						Batch.checkStopped();
+					}
+					con.commit();
+				} finally {
+					con.close();
+				}
+			}
+		} finally {
+			batchLog.info("Rethread took " + (System.currentTimeMillis() - rethreadStart) + " ms");
+			batchLog.info("Processed " + processed_post_count + " posts");
+			batchLog.info("Modified " + modified_post_count + " posts");
+			batchLog.info("Guessed null parent at " + null_parent_count + " posts");
+		}
+	}
+
+
+	/**
+	 * Get or create message id from an email
+	 *
+	 * @param mail   email message
+	 * @param msgFmt message format to use
+	 * @return message id, never null
+	 */
+	private static String getMessageID(Mail mail, Message.Format msgFmt) {
+		String[] messageIds = mail.getHeader("Message-Id"); // returns both Id and ID
+		if (messageIds == null || messageIds.length == 0 || messageIds[messageIds.length - 1] == null) {
+			return calcMessageID(mail, msgFmt);
+		} else {
+			return MailSubsystem.stripBrackets(messageIds[messageIds.length - 1]);
+		}
+	}
+
+	/**
+	 * Create a new message if for an email message
+	 *
+	 * @param mail   mail message to process
+	 * @param msgFmt message format to use
+	 * @return a new message id, never null
+	 */
+	private static String calcMessageID(Mail mail, Message.Format msgFmt) {
+		StringBuilder msgId = new StringBuilder();
+		msgId.append("MissingID.");
+		String text = msgFmt.getText(mail.getRawInput(),null);
+		msgId.append(Integer.toHexString(text.hashCode()));
+		MailAddress from = mail.getFrom();
+		if (from != null) msgId.append(Integer.toHexString(from.toString().hashCode()));
+		MailAddress[] to = mail.getTo();
+		if (to != null && to.length > 0) msgId.append(Integer.toHexString(to[0].toString().hashCode()));
+		Date date = mail.getSentDate();
+		if (date != null) msgId.append(Integer.toHexString(date.hashCode()));
+		String subject = mail.getSubject();
+		if (subject != null) msgId.append(Integer.toHexString(subject.hashCode()));
+		msgId.append("@nabble.com");
+		return msgId.toString();
+	}
+
+
+
+
+
+	private static final Pop3Server fwdPop3Server = (Pop3Server)Init.get("fwdPop3Server");
+
+	private static class Lazy {
+		static final String emailPrefix;
+		static final String emailSuffix;
+		static final Pattern pattern;
+		static {
+			String addrSpec = fwdPop3Server.getUsername();
+			int ind = addrSpec.indexOf('@');
+			emailPrefix = addrSpec.substring(0, ind) + "+";
+			emailSuffix = addrSpec.substring(ind);
+			pattern = Pattern.compile(
+				Pattern.quote(emailPrefix) + "([^@]+)\\+([^@]+)\\+([^@]+)" + Pattern.quote(emailSuffix)
+				, Pattern.CASE_INSENSITIVE
+			);
+		}
+	}
+
+	private static void processFwds() {
+		if( fwdPop3Server == null ) {
+			logger.error("fwdPop3Server not defined");
+			System.exit(-1);
+		}
+		MailIterator mails = fwdPop3Server.getMail();
+		try {
+			while( mails.hasNext() ) {
+				Mail mail = mails.next();
+				try {
+					fwdMail(mail);
+				} catch (Exception e) {
+					logger.error("mail:\n"+mail.getRawInput(),e);
+				}
+			}
+		} finally {
+			mails.close();
+		}
+	}
+
+	private static void fwdMail(Mail mail) {
+		String[] envTo = mail.getHeader("Envelope-To");
+		if (envTo == null)
+			envTo = mail.getHeader("X-Delivered-to"); // fastmail
+		if (envTo == null)
+			envTo = mail.getHeader("X-Original-To"); // postfix
+		String originalTo = envTo[0];
+		Matcher matcher = Lazy.pattern.matcher(originalTo);
+		if( !matcher.matches() )
+			throw new RuntimeException("invalid email: "+originalTo);
+		String fwdFrom = emailDecode(matcher.group(1));
+		String fwdTo = emailDecode(matcher.group(2));
+		if( (fwdFrom+fwdTo).hashCode() != Integer.parseInt(matcher.group(3)) )
+			throw new RuntimeException("invalid hash: "+originalTo);
+		mail.setFrom(new MailAddress(fwdFrom));
+		mail.setTo(new MailAddress(fwdTo));
+		logger.info("Forwarding email to mailing list: " + fwdTo);
+		MailHome.getDefaultSmtpServer().send(mail);
+	}
+
+	private static String fwdEmail(String from,String to) {
+		return Lazy.emailPrefix + emailEncode(from) + '+' + emailEncode(to) + '+' + (from+to).hashCode() + Lazy.emailSuffix;
+	}
+
+	private static String emailEncode(String s) {
+		return HtmlUtils.urlEncode(s).replace('%','~');
+	}
+
+	private static String emailDecode(String s) {
+		return HtmlUtils.urlDecode(s.replace('~','%'));
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Message.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,312 @@
+package nabble.model;
+
+import fschmidt.html.Html;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.compiler.Program;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.web.template.MessageNamespace;
+import nabble.modules.ModuleManager;
+
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class Message {
+
+	public static abstract class SourceType extends AbstractType<SourceType> {
+		private static final Map<Character,SourceType> map = new HashMap<Character,SourceType>();
+
+		public static final SourceType TEMP = new SourceType('t',"temp") {
+			public Source getSource(Site site,long id) {
+				User user = site.getUser(id);
+				return new TempSource(user);
+			}
+			public String getIdField() {
+				return "user_id";
+			}
+		};
+		public static final SourceType NODE = new SourceType('n',"node") {
+			public Source getSource(Site site,long id) {
+				return site.getNode(id);
+			}
+			public String getIdField() {
+				return "node_id";
+			}
+		};
+		public static final SourceType AVATAR = new SourceType('a',"avatar") {
+			public Source getSource(Site site,long id) {
+				User user = site.getUser(id);
+				return user == null? null : new AvatarSource(user);
+			}
+			public String getIdField() {
+				return "user_id";
+			}
+		};
+		public static final SourceType SITE = new SourceType('s',"site") {
+			public Source getSource(Site site,long id) {
+				return site;
+			}
+			public String getIdField() {
+				return null;
+			}
+		};
+
+		private SourceType(char code,String name) {
+			super(map,code,name);
+		}
+
+		public abstract Source getSource(Site site,long id);
+		public abstract String getIdField();
+
+		public static SourceType getType(char code) {
+			return AbstractType.getType(map,code);
+		}
+	}
+
+	public interface Source {
+		public Site getSite();
+		public long getSourceId();
+		public SourceType getMessageSourceType();
+	}
+
+	public static class TempSource implements Source {
+		private final User user;
+
+		public TempSource(User user) {
+			this.user = user;
+		}
+
+		public Site getSite() {
+			return user.getSite();
+		}
+
+		public long getSourceId() {
+			return user.getId();
+		}
+
+		public SourceType getMessageSourceType() {
+			return SourceType.TEMP;
+		}
+	}
+
+	public static class AvatarSource implements Source {
+		private final User user;
+
+		public AvatarSource(User user) {
+			this.user = user;
+		}
+
+		public Site getSite() {
+			return user.getSite();
+		}
+
+		public long getSourceId() {
+			return user.getId();
+		}
+
+		public SourceType getMessageSourceType() {
+			return SourceType.AVATAR;
+		}
+
+		public User getUser() {
+			return user;
+		}
+	}
+
+
+	public static abstract class Format extends AbstractType<Format> {
+	
+		private static final Map<Character,Format> map = new HashMap<Character,Format>();
+	
+		public static Format getMessageFormat(char code) {
+			return AbstractType.getType(map,code);
+		}
+
+		Format(char code,String name) {
+			super(map,code,name);
+		}
+	
+		protected abstract String getText(String msg, Source source);
+	
+		protected abstract String getTextWithoutQuotes(String msg, Source source);
+		
+		protected abstract String getEmail(String msg,int i);
+
+		protected abstract Html parse(String msg,Message.Source source);
+
+		protected abstract Html parseForMailText(String msg,Message.Source source);
+
+		public String toString() {
+			return getName();
+		}
+	
+		public static final Format TEXT = MessageFormatImpls.TEXT;
+		public static final Format HTML = MessageFormatImpls.HTML;
+		public static final Format MAILING_LIST = MailingLists.msgFmt;
+		private static final Format SUBSCRIPTION = PostByEmail.msgFmt;
+
+		public boolean isMail() {
+			return this==MAILING_LIST || this==SUBSCRIPTION;
+		}
+	}
+
+
+	// Message
+
+	protected String raw;
+	protected final Format format;
+
+	public Message(String raw,Format format) {
+		this.raw = raw;
+		this.format = format;
+	}
+
+	public String getRaw() {
+		return raw;
+	}
+
+	public final Format getFormat() {
+		return format;
+	}
+
+	public Source getSource() {
+		return null;
+	}
+
+	public boolean equals(Object obj) {
+		if( !(obj instanceof Message) )
+			return false;
+		Message msg = (Message)obj;
+		return msg.format.equals(format) && msg.getRaw().equals(getRaw());
+	}
+	
+	public final String getText() {
+		return getFormat().getText(getRaw(),getSource());
+	}
+
+	public final String getTextWithoutQuotes() {
+		return getFormat().getTextWithoutQuotes(getRaw(),getSource());
+	}
+
+	public final String getEmail(int i) {
+		return getFormat().getEmail(getRaw(),i);
+	}
+
+	public final Html parse() {
+		return getFormat().parse(getRaw(),getSource());
+	}
+
+	public final Html parseForMailText() {
+		return getFormat().parseForMailText(getRaw(),getSource());
+	}
+
+
+	public static String wrapQuoteText(String text) {
+		StringBuilder buf = new StringBuilder(text);
+		wrapQuoteText(buf);
+		return buf.toString();
+	}
+
+	public static void wrapQuoteText(StringBuilder buf) {
+		String quot = "";
+		for (int i=0,n=0,space=-1; i<buf.length(); i++,n++) {
+			char c = buf.charAt(i);
+			if (c=='\n' || c=='\r') {
+				n = 0;
+				space = -1;
+				quot = getQuot(buf,i+1);
+			} else if (Character.isWhitespace(c) && n>quot.length()) {
+				space = i;
+			} else if (n>76 && space>=0) {
+				buf.setCharAt(space,'\n');
+				buf.insert(space+1,quot);
+				i+=quot.length();
+				n = i-space;
+				space = -1;
+			}
+		}
+	}
+
+	private static String getQuot(StringBuilder buf, int start) {
+		int end = buf.indexOf(" ",start);
+		if (end<=start) return "";
+		for (int i=start;i<end;i++) {
+			if (buf.charAt(i)!='>') return "";
+		}
+		return buf.substring(start, end+1);
+	}
+
+	public boolean isDeleted() {
+		return false;
+	}
+
+	public boolean isDeactivated() {
+		return false;
+	}
+
+	// for testing
+	public static String toHtml(String raw,Format format)
+		throws CompileException
+	{
+		Message msg = new Message(raw,format);
+		Program program = Program.getInstance(ModuleManager.getGenericModules());
+		Template template = program.getTemplate( "message_as_html",
+			BasicNamespace.class, MessageNamespace.class
+		);
+		StringWriter sw = new StringWriter();
+		template.run( new TemplatePrintWriter(sw), Collections.<String,Object>emptyMap(),
+			new BasicNamespace(template), new MessageNamespace(msg)
+		);
+		return sw.toString();
+	}
+
+
+	public static final Map<String,String> htmlSeparators = new HashMap<String,String>();
+	static {
+		htmlSeparators.put("a", " ");
+		htmlSeparators.put("/a", " ");
+		htmlSeparators.put("blockquote", "\n");
+		htmlSeparators.put("/blockquote", "\n");
+		htmlSeparators.put("br", "\n");
+		htmlSeparators.put("center", "\n");
+		htmlSeparators.put("/center", "\n");
+		htmlSeparators.put("div", "\n");
+		htmlSeparators.put("/div", "\n");
+		htmlSeparators.put("hr", "\n");
+		htmlSeparators.put("img", " ");
+		htmlSeparators.put("ul", "\n");
+		htmlSeparators.put("/ul", "\n");
+		htmlSeparators.put("li", "\n");
+		htmlSeparators.put("nabble_a", " ");
+		htmlSeparators.put("/nabble_a", " ");
+		htmlSeparators.put("nabble_img", " ");
+		htmlSeparators.put("ol", "\n");
+		htmlSeparators.put("/ol", "\n");
+		htmlSeparators.put("p", "\n");
+		htmlSeparators.put("/p", "\n");
+		htmlSeparators.put("pre", "\n");
+		htmlSeparators.put("/pre", "\n");
+		htmlSeparators.put("table", "\n");
+		htmlSeparators.put("/table", "\n");
+		htmlSeparators.put("tr", "\n");
+		htmlSeparators.put("th", "\t");
+		htmlSeparators.put("td", "\t");
+		htmlSeparators.put("h1", "\n");
+		htmlSeparators.put("/h1", "\n");
+		htmlSeparators.put("h2", "\n");
+		htmlSeparators.put("/h2", "\n");
+		htmlSeparators.put("h3", "\n");
+		htmlSeparators.put("/h3", "\n");
+		htmlSeparators.put("h4", "\n");
+		htmlSeparators.put("/h4", "\n");
+		htmlSeparators.put("h5", "\n");
+		htmlSeparators.put("/h5", "\n");
+		htmlSeparators.put("h6", "\n");
+		htmlSeparators.put("/h6", "\n");
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/MessageFormatImpls.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,398 @@
+package nabble.model;
+
+import fschmidt.html.Html;
+import fschmidt.html.HtmlTag;
+import fschmidt.html.HtmlTextContainer;
+import fschmidt.util.java.HtmlUtils;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+final class MessageFormatImpls {
+	private MessageFormatImpls() {}  // never
+
+	private static final Pattern urlPtn = Pattern.compile(
+		"\\bhttps?://[^\\s\\p{Z}<>\"{}|\\\\^\\[\\]`]*[^\\s\\p{Z}<>\"{}|\\\\^\\[\\]`~,.?\\)]"
+	);
+
+
+	private static abstract class InputFormat extends Message.Format {
+
+		InputFormat(char code,String name) {
+			super(code,name);
+		}
+
+		protected final String getText(String msg, Message.Source source) {
+		    return MessageUtils.htmlToText(MessageUtils.newHtml(msg));
+		}
+
+		protected final String getTextWithoutQuotes(String msg, Message.Source source) {
+			return MessageUtils.getTextWithoutQuotes(msg);
+		}
+
+		protected final String getEmail(String msg,int i) {
+			Html list = MessageUtils.newHtml(msg);
+			return MessageFormatImpls.getEmail(list,i);
+		}
+
+	}
+
+	static final Message.Format TEXT = new InputFormat('t',"text") {
+
+		protected Html parse(String msg,Message.Source source) {
+			Html html = MessageUtils.newHtml(msg);
+			nestIgnoredBlocks(html);
+			encodeUnallowedTags(html);
+			convertLinks(html);
+			textToHtml(html);
+			unnestIgnoredBlocks(html);
+			return html;
+		}
+
+		protected Html parseForMailText(String msg,Message.Source source) {
+			Html html = MessageUtils.newHtml(msg);
+			changeUnallowedTagsToStrings(html);
+			return html;
+		}
+
+	};
+
+	static void convertLinks(Html list) {
+		boolean convertLinks = true;
+		for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) {
+			Object o = i.next();
+			if( o instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)o;
+				String name = tag.getName().toLowerCase();
+				if( name.equals("a") || name.equals("nabble_a") ) {
+					convertLinks = false;
+				} else if( name.equals("/a") || name.equals("/nabble_a") ) {
+					convertLinks = true;
+				}
+			} else if( convertLinks && o instanceof String ) {
+				String s = (String)o;
+				i.remove();
+				for( Object o2 : convertLinks(s) ) {
+					i.add(o2);
+				}
+			}
+		}
+	}
+
+	static Html convertLinks(String s) {
+		Html list = new Html();
+		Matcher m = urlPtn.matcher(s);
+		int i = 0;
+		while( m.find() ) {
+			String s2 = s.substring(i,m.start());
+			if( s2.length() > 0 )
+				list.add(s2);
+			String url = m.group();
+			HtmlTag tag = new HtmlTag("a");
+			tag.setAttribute("href",HtmlTag.quote(url));
+			list.add(tag);
+			list.add(url);
+			list.add(_a);
+			i = m.end();
+		}
+		String s2 = s.substring(i);
+		if( s2.length() > 0 )
+			list.add(s2);
+		return list;
+	}
+
+	private static final Set<String> asBlock = new HashSet<String>(Arrays.asList(
+		"table",
+		"/table",
+		"thead",
+		"/thead",
+		"tbody",
+		"/tbody",
+		"tr",
+		"/tr",
+		"th",
+		"/th",
+		"td",
+		"/td"
+	));
+
+	static void textToHtml(Html list) {
+		boolean isBlock = false;
+		int n = list.size();
+		for( int i=0; i<n; i++ ) {
+			Object o = list.get(i);
+			if( o instanceof String ) {
+				String s = (String)o;
+				Html l = textToHtml(s,isBlock);
+				list.remove(i);
+				list.addAll(i,l);
+				int d = l.size() - 1;
+				i += d;
+				n += d;
+			}
+			isBlock = false;
+			if( o instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)o;
+				String name = tag.getName().toLowerCase();
+				if( name.charAt(0) == '/' && Message.htmlSeparators.get(name) == "\n"
+					|| asBlock.contains(name)
+				) {
+					isBlock = true;
+				}
+			} else if( o instanceof HtmlTextContainer ) {
+				isBlock = true;
+//			} else if( o instanceof Html ) {
+//				isBlock = true;
+			}
+		}
+	}
+
+	private static final HtmlTag br = new HtmlTag("br/");
+
+	private static Html textToHtml(String s,boolean isAfterBlock) {
+		s = encodeHtml(s);
+		Html list = new Html();
+		StringBuilder buf = new StringBuilder();
+		final char[] a = s.toCharArray();
+		boolean isLineStart = true;
+		for (char c : a) {
+			switch (c) {
+			case '\r':
+				buf.append( c );
+				break;
+			case '\n':
+				buf.append( c );
+				String t = buf.toString();
+				if( t.trim().length() > 0 )
+					list.add(t);
+				if( isAfterBlock )
+					isAfterBlock = false;
+				else
+					list.add( br );
+				isLineStart = true;
+				buf.setLength(0);
+				break;
+			case '\t':
+				if( isLineStart ) {
+					buf.append( "&nbsp; &nbsp; &nbsp; &nbsp; " );
+				} else {
+					buf.append( '\t' );
+				}
+				break;
+			case ' ':
+				if( isLineStart ) {
+					buf.append( "&nbsp;" );
+					isLineStart = false;
+				} else if( buf.charAt(buf.length()-1)==' ' ) {
+					buf.append( "&nbsp;" );
+				} else {
+					buf.append( ' ' );
+				}
+				break;
+			default:
+				buf.append( c );
+				isLineStart = false;
+				isAfterBlock = false;
+			}
+		}
+		if( buf.length() > 0 )
+			list.add( buf.toString() );
+		return list;
+	}
+
+
+	static final Message.Format HTML = new InputFormat('h',"html") {
+
+		protected Html parse(String msg,Message.Source source) {
+			return MessageUtils.newHtml(msg);
+		}
+
+		protected Html parseForMailText(String msg,Message.Source source) {
+			return MessageUtils.newHtml(msg);
+		}
+
+	};
+
+	private static final HtmlTag _a = new HtmlTag("/a");
+
+	static String getEmail(Html list,int iEmail) {
+		int count = 0;
+		int n = list.size() - 3 + 1;
+		for( int i=0; i<n; i++ ) {
+			Object o = list.get(i);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)o;
+			if( !tag.getName().toLowerCase().equals("email") )
+				continue;
+			o = list.get(i+1);
+			if( !(o instanceof String) )
+				continue;
+			String email = (String)o;
+			o = list.get(i+2);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag endTag = (HtmlTag)o;
+			if( !endTag.getName().toLowerCase().equals("/email") )
+				continue;
+			if( count++ == iEmail )
+				return email;
+			i += 2;
+		}
+		return null;
+	}
+
+
+	static String encodeHtml(String s) {
+		char[] a = s.toCharArray();
+		StringBuilder buf = new StringBuilder();
+		for( int i=0; i<a.length; i++ ) {
+			char c = a[i];
+			switch(c) {
+/*
+			case '&':
+				buf.append("&amp;");
+				break;
+*/
+			case '<':
+				buf.append("&lt;");
+				break;
+			case '>':
+				buf.append("&gt;");
+				break;
+			case '"':
+				buf.append("&quot;");
+				break;
+			default:
+				buf.append(c);
+			}
+		}
+		return buf.toString();
+	}
+
+
+	static {
+		MailingLists.nop();
+	}
+
+
+	private static final List<String> ignoredTags = Arrays.asList(
+		"nabble_embed"
+	);
+
+	private static void nestIgnoredBlocks(Html html) {
+		outer:
+		for( String tagName : ignoredTags ) {
+			String endTagName = "/" + tagName;
+		    for( ListIterator<Object> i=html.listIterator(); i.hasNext(); ) {
+		        Object obj = i.next();
+				if( obj instanceof HtmlTag ) {
+					HtmlTag tag = (HtmlTag)obj;
+					if( tag.getName().equalsIgnoreCase(tagName) ) {
+						Html block = new Html();
+						i.remove();
+						block.add(obj);
+						while(true) {
+							if( !i.hasNext() ) {  // unclosed
+								i.add(HtmlUtils.htmlEncode(block.toString()));
+								continue outer;
+							}
+							obj = i.next();
+							i.remove();
+							block.add(obj);
+							if( obj instanceof HtmlTag ) {
+								tag = (HtmlTag)obj;
+								if( tag.getName().equalsIgnoreCase(endTagName) ) {
+									i.add(block);
+									break;
+								}
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	private static void unnestIgnoredBlocks(Html html) {
+	    for( ListIterator<Object> i=html.listIterator(); i.hasNext(); ) {
+	        Object obj = i.next();
+			if( obj instanceof Html ) {
+				Html block = (Html)obj;
+				i.remove();
+				for( Object obj2 : block ) {
+					i.add(obj2);
+				}
+			}
+		}
+	}
+
+	private static final Set<String> allowedTextTags = new HashSet<String>();
+	static {
+		for( String tag : new String[]{
+			"b",
+			"i",
+			"quote",
+			"email",
+			"img",
+			"a",
+			"font",
+			"span",
+			"smiley",
+			"nabble_img",
+			"nabble_a",
+			"object",
+			"param",
+			"embed",
+			"raw",
+			"nabble_embed",
+			"h1",
+			"h2",
+			"h3",
+			"h4",
+			"h5",
+			"h6",
+			"table",
+			"thead",
+			"tbody",
+			"tr",
+			"th",
+			"td",
+		} ) {
+			allowedTextTags.add(tag);
+			allowedTextTags.add("/"+tag);
+		}
+	}
+
+	private static void encodeUnallowedTags(Html html) {
+		for( ListIterator<Object> i=html.listIterator(); i.hasNext(); ) {
+			Object obj = i.next();
+			if( obj instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)obj;
+				if( !allowedTextTags.contains( tag.getName().toLowerCase() ) ) {
+					i.remove();
+					i.add( HtmlUtils.htmlEncode(tag.toString()) );
+				}
+			}
+		}
+	}
+
+	private static void changeUnallowedTagsToStrings(Html html) {
+		for( ListIterator<Object> i=html.listIterator(); i.hasNext(); ) {
+			Object obj = i.next();
+			if( obj instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)obj;
+				if( !allowedTextTags.contains( tag.getName().toLowerCase() ) ) {
+					i.remove();
+					i.add( tag.toString() );
+				}
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/MessageUtils.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,105 @@
+package nabble.model;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+import java.util.Iterator;
+import java.util.regex.Matcher;
+import fschmidt.html.Html;
+import fschmidt.html.HtmlTag;
+import fschmidt.html.HtmlTextContainer;
+import fschmidt.util.mail.MailAddress;
+
+
+public final class MessageUtils {
+	private MessageUtils() {}  // never
+
+	public static String getTextWithoutQuotes(String msg) {
+		Html list = newHtml(msg);
+		removeQuotes(list);
+		return htmlToText(list);
+	}
+
+	static Html newHtml(String msg) {
+		Html html = new Html();
+		html.containerTags().add("raw");
+		html.parse(msg);
+		return html;
+	}
+
+	private static void removeQuotes(Html list) {
+		int quoteDepth = 0;
+		for( Iterator i=list.iterator(); i.hasNext(); ) {
+			Object next = i.next();
+			boolean remove = quoteDepth > 0;
+			if (next instanceof HtmlTag) {
+				String tagName = ((HtmlTag)next).getName().toLowerCase();
+				if( tagName.equals("quote") ) {
+					quoteDepth++;
+					remove = true;
+				} else if( tagName.equals("/quote") && quoteDepth > 0 ) {
+					quoteDepth--;
+				}
+			}
+			if( remove )
+				i.remove();
+		}
+	}
+
+	static String htmlToText(Html html) {
+		StringBuilder buf = new StringBuilder();
+		for( Iterator i=html.iterator(); i.hasNext(); ) {
+			Object next = i.next();
+			if( next instanceof String ) {
+				buf.append(next);
+			} else if (next instanceof HtmlTag) {
+				boolean isSeparator = Message.htmlSeparators.containsKey(((HtmlTag)next).getName().toLowerCase());
+				if (isSeparator && buf.length()>0 && !Character.isWhitespace(buf.charAt(buf.length()-1)))
+					buf.append(' ');
+			}
+		}
+		return buf.toString();
+	}
+
+	public static String htmlToSearchText(Html html) {
+		StringBuilder buf = new StringBuilder();
+		for( Iterator i=html.iterator(); i.hasNext(); ) {
+			Object next = i.next();
+			if( next instanceof String ) {
+				buf.append(next);
+			} else if (next instanceof HtmlTag) {
+				HtmlTag tag = (HtmlTag)next;
+				String name = tag.getName().toLowerCase();
+				if( name.equals("iframe") ) {
+					buf.append(" html_iframe");
+				} else if( name.equals("img") ) {
+					String src = HtmlTag.unquote(tag.getAttributeValue("src"));
+					if( src != null )
+						buf.append(" html_img_").append( src.substring(src.lastIndexOf('.')+1) );
+				} else if( name.equals("a") ) {
+					String href = HtmlTag.unquote(tag.getAttributeValue("href"));
+					if( href != null ) {
+						buf.append(" html_a");
+						try {
+							String domain = new URL(href).getHost();
+							buf.append(" link_").append(domain);
+						} catch(MalformedURLException e) {}  // ignore
+					}
+				}
+				buf.append(' ');
+			}
+		}
+		return buf.toString();
+	}
+
+	public static String hideAllEmails(String txt) {
+		if (txt==null) return null;
+		Matcher m = MailAddress.EMAIL_PATTERN.matcher(txt);
+		StringBuffer buf = new StringBuffer();
+		while (m.find()) {
+			m.appendReplacement(buf, "$1@...");
+		}
+		m.appendTail(buf);
+		return buf.toString();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/ModelException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,188 @@
+/*
+
+Copyright (C) 2003  Franklin Schmidt <frank@gustos.com>
+
+*/
+
+package nabble.model;
+
+import nabble.naml.namespaces.TemplateException;
+import java.net.MalformedURLException;
+
+
+public class ModelException extends TemplateException {
+
+	protected ModelException(Name name) {
+		super(name);
+	}
+
+	protected ModelException(Name name,String msg) {
+		super(name,msg);
+	}
+
+	protected ModelException(Name name,String msg,Exception e) {
+		super(name,msg,e);
+	}
+
+	protected ModelException(Name name,Exception e) {
+		super(name,e);
+	}
+
+	public static ModelException newInstance(String name,String msg) {
+		return new ModelException(name(name),msg);
+	}
+
+	public static ModelException newInstance(String name,Exception cause) {
+		return new ModelException(name(name),cause);
+	}
+
+	public static ModelException newInstance(String name,String msg,Exception cause) {
+		return new ModelException(name(name),msg,cause);
+	}
+
+
+	public static final class TooManyPosts extends ModelException {
+		TooManyPosts() {
+			super(name("too_many_posts"),"You have made too many posts in a short time. Please try again later.");
+		}
+	}
+
+	public static final class NodeLoop extends ModelException {
+		NodeLoop(Node node) {
+			super(name("node_loop"),"setParent creates loop " + node);
+		}
+//		protected NodeLoop() {}
+	}
+
+	public static final class RequiredSubject extends ModelException {
+		RequiredSubject() {
+			super(name("required_subject"),"The subject is required.");
+		}
+	}
+
+
+	public static final class RequiredName extends ModelException {
+		RequiredName() {
+			super(name("required_name"),"You must provide a user name.");
+		}
+	}
+
+	public static final class InvalidPermalink extends ModelException {
+		public InvalidPermalink() {
+			super(name("invalid_permalink"));
+		}
+	}
+
+	public static final class ReadOnly extends ModelException {
+		ReadOnly() {
+			super(name("read_only"),"Unauthorized to post here.");
+		}
+	}
+
+	public static final class NoAnonymous extends ModelException {
+		public final Node authorizingNode;
+
+		NoAnonymous(Node authorizingNode) {
+			super(name("no_anonymous"),"Anonymous users may not post here.");
+			this.authorizingNode = authorizingNode;
+		}
+	}
+
+	public static final class AlreadyMember extends ModelException {
+		AlreadyMember() {
+			super(name("already_member"),"User is already a member.");
+		}
+	}
+
+	public static final class InvalidRecaptcha extends ModelException {
+		public InvalidRecaptcha() {
+			super(name("invalid_recaptcha"),"Please verify that you are not a robot.");
+		}
+	}
+
+	public static final class InvalidDate extends ModelException {
+		public InvalidDate(String date) {
+			super(name("invalid_date"),"invalid date format: " + date);
+		}
+	}
+
+	// Spam Handling --------------------------------------------------------------
+
+	public static abstract class SpamException extends ModelException {
+		public final String badText;
+
+		public SpamException(Name name, String badText, String msg) {
+			super(name, msg);
+			this.badText = badText;
+		}
+	}
+
+	public static final class SubjectContainsInvalidWord extends SpamException {
+		public SubjectContainsInvalidWord(String badText) {
+			super(name("subject_contains_invalid_spam_word"),badText,"Subject cannot contain \"" + badText + '"');
+		}
+	}
+
+	public static final class MessageContainsInvalidWord extends SpamException {
+		public MessageContainsInvalidWord(String badText) {
+			super(name("message_contains_invalid_spam_word"),badText,"Message cannot contain \"" + badText + '"');
+		}
+	}
+
+	public static final class MessageContainsManyInvalidWords extends SpamException {
+		public MessageContainsManyInvalidWords(String badText) {
+			super(name("message_contains_many_invalid_spam_words"),badText,"Too many words: \"" + badText + '"');
+		}
+	}
+
+	public static final class SubjectContainsCommonSpamWords extends SpamException {
+		public SubjectContainsCommonSpamWords(String subject) {
+			super(name("subject_contains_common_spam_words"),subject,"Subject contains common spam words: \"" + subject + '"');
+		}
+	}
+
+	public static final class MessageContainsCommonSpamWords extends SpamException {
+		public MessageContainsCommonSpamWords(String message) {
+			super(name("message_contains_common_spam_words"),message,"Message contains common spam words: \"" + message + '"');
+		}
+	}
+
+
+
+	public static final class EmailFormat extends ModelException {
+		public final String badEmail;
+
+		public EmailFormat(String badEmail) {
+			super(name("invalid_email"),"Invalid email address: '" + badEmail + "'");
+			this.badEmail = badEmail;
+		}
+	}
+
+	public static final class UrlFormat extends ModelException {
+		public final String badUrl;
+
+		UrlFormat(String badUrl,MalformedURLException e) {
+			super(name("url_format"),"Invalid URL: '" + badUrl + "'",e);
+			this.badUrl = badUrl;
+		}
+	}
+
+	public static class InvalidFile extends ModelException {
+		public final String fileName;
+
+		public InvalidFile(String fileName) {
+			super(name("invalid_file_exception"));
+			this.fileName = fileName;
+		}
+	}
+
+	public static class InvalidImage extends ModelException {
+		public final String fileName;
+
+		public InvalidImage(String fileName) {
+			super(name("invalid_image_exception"));
+			this.fileName = fileName;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/ModelHome.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,350 @@
+/*
+
+Copyright (C) 2003  Franklin Schmidt <frank@gustos.com>
+
+*/
+
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.Listener;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import nabble.modules.ModuleManager;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.NodePageNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+public final class ModelHome {
+	private ModelHome() {}  // never
+
+	private static final Logger logger = LoggerFactory.getLogger(ModelHome.class);
+
+	public static final String AVATAR_SMALL = "avatar24.png";
+	public static final String AVATAR_BIG = "avatar100.png";
+
+	public static volatile long lastDigestRun = System.currentTimeMillis();
+
+	public static Site newSite(String type,String subject,String message,Message.Format msgFmt,String email,String username)
+		throws ModelException
+	{
+		return DbSiteCreator.newSite(type,subject,message,msgFmt,email,username);
+	}
+
+	public static Site restoreSiteToOldId(String filename) {
+		return DbSiteCreator.restoreSiteToOldId(new File(filename));
+	}
+
+	public static Site restoreSiteToNewId(String filename) {
+		return DbSiteCreator.restoreSiteToNewId(new File(filename));
+	}
+
+	public static void validateEmail(String email) throws ModelException {
+		UserImpl.validateEmail(email);
+	}
+
+	public static String hideEmail(String emailTo) {
+		return "[hidden email]";
+	}
+
+	public static String hideAllEmails(String txt) {
+		return MessageUtils.hideAllEmails(txt);
+	}
+
+	public static Site getSite(long id) {
+		SiteKey siteKey = SiteKey.getInstance(id);
+		if( siteKey.siteGlobal() == null )
+			return null;
+		return siteKey.siteGlobal().site();
+	}
+/*
+	// unfiltered, Site.getUser() is preferred
+	public static User getUserFromId(long id) {
+		return UserImpl.getUser(id);
+	}
+*/
+	public static String getNodeReplyAddress(Node node, User user) {
+		return PostByEmail.getMailAddress(user, node);
+	}
+
+	public static String getUserBouncesAddress(User user) {
+		return PostByEmail.getBouncesAddress(user);
+	}
+
+	public static void setNodeHeaders(Mail mail, Node node) {
+		MailSubsystem.setHeaders(mail, (NodeImpl) node);
+	}
+
+	public static void setRemoteAddr(Site site, String removeAddr) {
+		((SiteImpl) site).siteGlobal().setRemoteAddr(removeAddr);
+	}
+
+	// force these classes to add their listeners first
+	static {
+		Init.modelHomeStarted = true;
+		NodeImpl.nop();
+		Lucene.nop();
+		DailyNumber.nop();
+		FileUpload.nop();
+		nabble.view.lib.MyJtpServlet.nop();
+		SubscriptionImpl.nop();
+		ModuleManager.nop();
+	}
+
+	private static Set<Node> doneSet(Map<String,Object> transMap) {
+		if( transMap==null )
+			return new HashSet<Node>();
+		@SuppressWarnings("unchecked")
+		Set<Node> done = (Set<Node>)transMap.get("doneNodes");
+		if( done == null ) {
+			done = new HashSet<Node>();
+			transMap.put("doneNodes",done);
+		}
+		return done;
+	}
+
+	public static void addDescendantChangeListener(final Listener<Node> listener) {
+		Listener<Node> myListener = new Listener<Node>() {
+			public void event(final Node node) {
+				if( ModelHome.insideImportProcedure.get() )
+					return;
+				DbDatabase db = node.getSite().getDb();
+				final Map<String,Object> transMap = db.transactionMap();
+				db.runAfterCommit(new Runnable(){public void run(){
+					Set<Node> done = doneSet(transMap);
+					for( Node n : node.getAncestors() ) {
+						if( done.add(n) )
+							listener.event(n);
+					}
+				}});
+			}
+		};
+		NodeImpl.childChangeListeners.add(myListener);
+		NodeImpl.addPostUpdateListener(myListener);
+		NodeImpl.addPostDeleteListener(myListener);
+		NodeImpl.addPostInsertListener(new Listener<Node>() {
+			public void event(final Node node) {
+				DbDatabase db = node.getSite().getDb();
+				final Map<String,Object> transMap = db.transactionMap();
+				db.runAfterCommit(new Runnable(){public void run(){
+					Set<Node> done = doneSet(transMap);
+					NodeIterator<? extends Node> ancestors = node.getAncestors();
+					done.add(ancestors.next());  // skip self
+					for( Node n : ancestors ) {
+						if( done.add(n) )
+							listener.event(n);
+					}
+				}});
+			}
+		});
+	}
+
+	public static void addSiteChangeListener(Listener<Site> listener) {
+		SiteImpl.addChangeListener(listener);
+	}
+
+	public static void addSitePreChangeListener(Listener<Site> listener) {
+		SiteImpl.addPreChangeListener(listener);
+	}
+
+	public static void addNodeChangeListener(final Listener<Node> listener) {
+		NodeImpl.addChangeListener(listener);
+	}
+
+	public static void addNodeInsertListener(final Listener<Node> listener) {
+		NodeImpl.addPostInsertListener(listener);
+	}
+
+	public static void addNodeDeleteListener(final Listener<Node> nodeDeleteListener) {
+		NodeImpl.addPostDeleteListener(nodeDeleteListener);
+	}
+
+	public static void addNodePreUpdatedListener(final Listener<Node> listener) {
+		NodeImpl.preUpdateListeners.add(listener);
+	}
+
+	public static void addGotParentListener(Listener<Node> listener) {
+		NodeImpl.gotParentListeners.add(listener);
+	}
+
+	public static void addUserInsertListener(final Listener<User> listener) {
+		UserImpl.addPostInsertListener(listener);
+	}
+
+	public static void addUserPreUpdatedListener(final Listener<User> listener) {
+		UserImpl.preUpdateListeners.add(listener);
+	}
+
+	static ThreadLocal<Boolean> insideImportProcedure = new ThreadLocal<Boolean>() {
+		protected Boolean initialValue() {
+			return Boolean.FALSE;
+		}
+	};
+
+	public static void beginImport() {
+		insideImportProcedure.set(true);
+	}
+
+	public static void endImport() {
+		insideImportProcedure.set(false);
+	}
+/*
+	static {
+		NodeImpl.addPostInsertListener(new Listener<NodeImpl>() {
+			public void event(final NodeImpl post) {
+				if (insideImportProcedure.get())
+					return;
+				Node.MailToList mailToList = post.getMailToList();
+				if( mailToList==null )
+					return;
+				MailingList mailingList = post.getAssociatedMailingListImpl();
+				if( mailingList == null )
+					return;
+				Template template = post.getSite().getTemplate( "mail to list",
+					BasicNamespace.class, NabbleNamespace.class, NodePageNamespace.class
+				);
+				template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(),
+					new BasicNamespace(template),
+					new NabbleNamespace(post.getSite()),
+					new NodePageNamespace(post)
+				);
+			}
+		});
+	}
+*/
+
+	public static void preloadMessages(List<Node> nodes) {
+		NodeImpl.preloadMessages(nodes);
+	}
+
+	public static String assignmentCondition(User user,Integer priority) {
+		if( user==null && priority==null )
+			return null;
+		StringBuilder buf = new StringBuilder();
+		if( user != null ) {
+			buf.append( "assigned_user=" ).append( user.getId() );
+		}
+		if( priority != null ) {
+			if( buf.length() > 0 )
+				buf.append( " and " );
+			buf.append( "assigned_priority=" ).append( priority );
+		}
+		return buf.toString();
+	}
+
+
+
+	public static Long getSiteIdFromDomain(String customDomain) {
+		return SiteGlobal.getSiteIdFromDomain(customDomain);
+	}
+
+
+	public static final String noReply = "no-reply@nabble.com";
+	static final String bounces = "bounces@n2.nabble.com";
+
+	public static void send(Mail mail) throws MailException {
+		if( mail.getFrom().getAddrSpec().equals(noReply) ) {
+			MailHome.getDefaultSmtpServer().sendFrom(mail, bounces);
+		} else {
+			MailHome.getDefaultSmtpServer().send(mail);
+		}
+	}
+
+	public static void send(Mail mail, String smtpFrom) throws MailException {
+		if( mail.getFrom().getAddrSpec().equals(noReply) ) {
+			MailHome.getDefaultSmtpServer().sendFrom(mail, bounces);
+		} else {
+			MailHome.getDefaultSmtpServer().sendFrom(mail, smtpFrom);
+		}
+	}
+
+
+	public static <S,T> ExtensionFactory<S,T> standardExtensionFactory(final String name,final Class<S> sourceClass,final Class<T> extensionClass) {
+		return new ExtensionFactory<S,T>() {
+
+			public String getName() {
+				return name;
+			}
+
+			public Class<T> extensionClass() {
+				return extensionClass;
+			}
+
+			public T construct(S source) {
+				try {
+					Constructor<T> constructor = extensionClass.getConstructor(sourceClass);
+					constructor.setAccessible(true);
+					return constructor.newInstance(source);
+				} catch(NoSuchMethodException e) {
+					throw new RuntimeException(e);
+				} catch(InstantiationException e) {
+					throw new RuntimeException(e);
+				} catch(IllegalAccessException e) {
+					throw new RuntimeException(e);
+				} catch(InvocationTargetException e) {
+					Throwable cause = e.getCause();
+					if( cause instanceof Error )
+						throw (Error)cause;
+					if( cause instanceof RuntimeException )
+						throw (RuntimeException)cause;
+					throw new RuntimeException(e);
+				}
+			}
+
+			public T construct(S source,ResultSet rs) {
+				return construct(source);
+			}
+
+			public Serializable getExportData(S source) {
+				return null;
+			}
+
+			public void saveExportData(S source,Serializable data) {
+				throw new RuntimeException();
+			}
+		};
+	}
+
+	public static <T> void addSiteExtensionFactory(ExtensionFactory<Site,T> factory) {
+		SiteImpl.addExtensionFactory(factory);
+	}
+
+	public static <T> void addNodeExtensionFactory(ExtensionFactory<Node,T> factory) {
+		NodeImpl.addExtensionFactory(factory);
+	}
+
+	public static <T> void addUserExtensionFactory(ExtensionFactory<User,T> factory) {
+		UserImpl.addExtensionFactory(factory);
+	}
+
+
+	public static List<Site> getSitesForTask(String task) throws SQLException {
+		List<Site> sites = new ArrayList<Site>();
+		for( SiteKey siteKey : SiteKey.getSiteKeys(task) ) {
+			try {
+				sites.add( siteKey.site() );
+			} catch(UpdatingException e) {}  // skip it
+		}
+		return sites;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Node.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,196 @@
+package nabble.model;
+
+import fschmidt.db.DbObject;
+import fschmidt.db.LongKey;
+import fschmidt.util.java.Filter;
+import nabble.model.export.NodeData;
+
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Map;
+
+
+// for both posts and apps
+public interface Node extends Message.Source, DbObject<LongKey,NodeImpl> {
+	public long getId();
+	public Person getOwner();
+	public void setOwner(User user);
+	public Node getParent();
+	public void changeParent(Node parent) throws ModelException;
+	public Site getSite();
+	public boolean isRoot();
+	public NodeIterator<? extends Node> getChildren();
+	public NodeIterator<? extends Node> getChildren(String cnd);
+	public int getChildCount();
+	public MailingList getMailingList();
+	public MailingList getAssociatedMailingList();  // only returns MailingList if not isUIHidden
+	public Kind getKind();
+	public NodeIterator<? extends Node> getAncestors();  // starting with self
+	public NodeIterator<? extends Node> getDescendants();
+	public NodeIterator<? extends Node> getDescendantApps();
+	public int getDescendantCount();
+	public int getDescendantAppCount();
+	public int getDescendantPostCount();
+	public NodeIterator<? extends Node> getPostsByDate(Filter<Node> filter);
+	public NodeIterator<? extends Node> getPostsByDateAscending(Filter<Node> filter);
+	public NodeIterator<? extends Node> getTopicsByLastNodeDate(String cnd,Filter<Node> filter);
+	public NodeIterator<? extends Node> getTopicsBySubject(String cnd,Filter<Node> filter);
+	public NodeIterator<? extends Node> getTopicsByPinnedAndLastNodeDate(String cnd,Filter<Node> filter);
+	public NodeIterator<? extends Node> getTopicsByPinnedAndRootNodeDate(String cnd,Filter<Node> filter);
+	public NodeIterator<? extends Node> getTopicsByPopularity(String cnd,Filter<Node> filter);
+	public int getTopicCount(String cnd,Filter<Node> filter);
+	public Node getLastNode();
+	public Date getLastNodeDate();
+	public MailToList getMailToList();
+	public MailFromList getMailFromList();
+	public String getSubject();
+	public void setSubject(String subject) throws ModelException.RequiredSubject;
+	public String getSubjectHtml();
+	public Message getMessage();
+	public void setMessage(String message,Message.Format msgFmt);
+	public void deleteMessageOrNode();
+	public void deleteRecursively();
+	public Date getWhenCreated();
+	public void setWhenCreated(Date whenCreated);
+	public Date getWhenUpdated();
+	public Node getGoodCopy();
+	public Node getApp();
+	public Node getTopic();
+	public void update();
+	public void insert(boolean isDoneByOwner) throws ModelException.TooManyPosts;
+	public boolean isInDb();
+	public Collection<Subscription> getSubscriptions(int i, int n);
+	public int getSubscriptionCount();
+	public Map<User,Subscription> getSubscribersToNotify();
+	public String getType();
+	public void setType(String type);
+	public boolean isPinned();
+	public void pin(Node[] children);
+	public long getExportedNodeId();
+	public void setExportedNodeId(long id);
+	public NodeData getData();
+	public void export(String permalink,String email);
+	public String getEmbeddingUrl();
+	public void setEmbeddingUrl(String url);
+	public Map<ExtensionFactory<Node, ?>, Object> getExtensionMap();
+
+	public boolean hasPreviousTopic();
+	public Node getPreviousTopic();
+	public boolean hasNextTopic();
+	public Node getNextTopic();
+
+	public boolean hasChildApps();
+	public boolean hasChildTopics();
+	public boolean hasPinnedApps();
+	public boolean hasPinnedTopics();
+	public NodeIterator<? extends Node> getChildApps();
+	public NodeIterator<? extends Node> getChildApps(String cnd);
+	public MailingList newMailingList(ListServer listServer,String listAddress,String url) throws ModelException;
+	public void deleteMailingList();
+
+	public interface MailToList {
+		public Node getNode();
+		public boolean isPending();
+		public void clearPending();
+		public Date getWhenSent();
+		public String getOrGenerateMessageID();
+	}
+
+	public interface MailFromList {
+		public boolean hasGuessedParent();
+		public String getMessageID();
+		public MailFromList getParentMailFromList();
+	}
+
+	public enum Kind { APP, POST }
+
+	public static final class Type {
+		// apps
+		public static final String FORUM = "forum";
+		public static final String MIXED = "mixed";
+		public static final String BOARD = "board";
+		public static final String CATEGORY = "category";
+		public static final String NEWS = "news";
+		public static final String GALLERY = "gallery";
+		public static final String BLOG = "blog";
+
+		// posts
+		public static final String BLOG_ENTRY = "blog_entry";
+		public static final String GALLERY_ENTRY = "gallery_entry";
+		public static final String NEWS_ENTRY = "news_entry";
+		public static final String COMMENT = "comment";
+	}
+
+
+	public Comparator<Node> dateComparator = new Comparator<Node>(){
+		public int compare(Node n1,Node n2) {
+			return n1.getWhenCreated().compareTo(n2.getWhenCreated());
+		}
+	};
+
+	public Comparator<Node> subjectComparator = new Comparator<Node>(){
+		public int compare(Node o1,Node o2) {
+			NodeImpl n1 = (NodeImpl)o1;
+			NodeImpl n2 = (NodeImpl)o2;
+			return n1.getCollationKey().compareTo(n2.getCollationKey());
+		}
+	};
+
+	public <T> T getExtension(ExtensionFactory<Node,T> factory);
+
+	public String getProperty(String key);
+	public void setProperty(String key,String value);
+
+	public interface Order {
+		public String sqlOrder();
+		public Comparable getComparable(Node node);
+		public Comparable getPostComparable(Node node);
+
+		public abstract class BasicOrder implements Order {
+			public Comparable getPostComparable(Node node) {
+				return getComparable(node);
+			}
+		}
+
+		public static final Order BY_LAST_NODE_DATE_DESC = new BasicOrder() {
+			public String sqlOrder() {
+				return "last_node_date desc, node_id desc";
+			}
+			public Comparable getComparable(Node node) {
+				return -node.getLastNodeDate().getTime();
+			}
+		};
+
+		public static final Order BY_WHEN_CREATED = new BasicOrder() {
+			public String sqlOrder() {
+				return "when_created, node_id";
+			}
+			public Comparable getComparable(Node node) {
+				return node.getWhenCreated();
+			}
+		};
+
+		public static final Order BY_SUBJECT = new BasicOrder() {
+			public String sqlOrder() {
+				return "is_app, lower(subject), node_id";
+			}
+			public Comparable getComparable(Node node) {
+				return node.getKind()==Kind.APP ? "" : node.getSubject().toLowerCase();
+			}
+		};
+
+		public static final Order BY_DATE_DESC = new Order() {
+			public String sqlOrder() {
+				return "last_node_date desc, node_id desc";
+			}
+			public Comparable getComparable(Node node) {
+				return -node.getLastNodeDate().getTime();
+			}
+			public Comparable getPostComparable(Node node) {
+				return -node.getWhenCreated().getTime();
+			}
+		};
+
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/NodeImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,2347 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbNull;
+import fschmidt.db.DbObjectFactory;
+import fschmidt.db.DbRecord;
+import fschmidt.db.DbTable;
+import fschmidt.db.DbUtils;
+import fschmidt.db.Listener;
+import fschmidt.db.ListenerList;
+import fschmidt.db.LongKey;
+import fschmidt.html.Html;
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.Filter;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.java.Memoizer;
+import fschmidt.util.java.ObjectUtils;
+import fschmidt.util.java.SimpleCache;
+import nabble.model.export.Export;
+import nabble.model.export.NodeData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.text.CollationKey;
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+
+
+final class NodeImpl implements Node, Node.MailFromList {
+	private static final Logger logger = LoggerFactory.getLogger(NodeImpl.class);
+
+	private static final Message DELETED_MESSAGE = new Message("- deleted -", Message.Format.TEXT) {
+		public boolean isDeleted() { return true; }
+		public boolean isDeactivated() { return true; }
+	};
+
+	final SiteKey siteKey;
+	private final DbRecord<LongKey,NodeImpl> record;
+	private String subject;
+	private MyMessage message;
+	private Date whenCreated;
+	private long ownerId;
+	private UserImpl owner;
+	private long parentId = 0L;
+	private NodeImpl parent;
+	private String messageID;
+	private Date whenUpdated;
+	private Boolean isGuessedParent = false;
+	private Date whenSent;
+	private long lastNodeId;
+	private Date lastNodeDate;
+	private int nodeCount;
+	private Kind kind;
+	private String type;
+	private boolean isPinned;
+	private int childCount;
+	private String cookie;
+	private String anonymousName;
+	private long exportedNodeId;
+	private String exportPermalink = null;
+	private String exportEmail = null;
+	private String embeddingUrl;
+
+	private NodeImpl(SiteKey siteKey,LongKey key,ResultSet rs)
+			throws SQLException
+	{
+		this.siteKey = siteKey;
+		record = table(siteKey).newRecord(this,key);
+		subject = rs.getString("subject");
+		Message.Format msgFmt = Message.Format.getMessageFormat( rs.getString("msg_fmt").charAt(0) );
+		message = new MyMessage(msgFmt);
+		whenCreated = rs.getTimestamp("when_created");
+		ownerId = rs.getLong("owner_id");
+		parentId = rs.getLong("parent_id");
+		messageID = rs.getString("message_id");
+		whenUpdated = rs.getTimestamp("when_updated");
+		isGuessedParent = rs.getBoolean("guessed_parent");
+		if (rs.wasNull()) isGuessedParent = null;
+		whenSent = rs.getTimestamp("when_sent");
+		lastNodeId = rs.getLong("last_node_id");
+		lastNodeDate = rs.getTimestamp("last_node_date");
+		nodeCount = rs.getInt("node_count");
+		kind = rs.getBoolean("is_app") ? Kind.APP : Kind.POST;
+		type = rs.getString("type");
+		if( type==null )
+			type = Type.COMMENT;
+		rs.getInt("pin");
+		isPinned = !rs.wasNull();
+		childCount = rs.getInt("child_count");
+		cookie = rs.getString("cookie");
+		anonymousName = rs.getString("anonymous_name");
+		embeddingUrl = rs.getString("embedding_url");
+		exportedNodeId = rs.getLong("exported_node_id");
+		exportPermalink = rs.getString("export_permalink");
+		exportEmail = rs.getString("export_email");
+		for( ExtensionFactory<Node,?> factory : extensionFactories ) {
+			Object obj = factory.construct(this,rs);
+			if( obj != null )
+				getExtensionMap().put(factory,obj);
+		}
+	}
+
+	static NodeImpl newRootNode(Kind kind,UserImpl owner,String subject,String messageRaw,Message.Format msgFmt)
+		throws ModelException
+	{
+		SiteImpl site = owner.getSiteImpl();
+		return new NodeImpl(site.siteKey,kind,owner,subject,messageRaw,msgFmt);
+	}
+
+	static NodeImpl newRootNode(Kind kind,Person owner,String subject,String messageRaw,Message.Format msgFmt,SiteImpl site,String type)
+		throws ModelException
+	{
+		NodeImpl newRoot = new NodeImpl(site.siteKey,kind,owner,subject,messageRaw,msgFmt);
+		if( type != null )
+			newRoot.setType(type);
+		newRoot.insert(true);
+		NodeImpl oldRoot = site.getRootNodeImpl();
+		oldRoot.parentId = newRoot.getId();
+		oldRoot.record.fields().put("parent_id", oldRoot.parentId);
+		oldRoot.update();
+		site.setRoot(newRoot);
+		site.getDbRecord().update();
+		oldRoot.addNode();
+		return newRoot;
+	}
+
+	static NodeImpl newChildNode(Kind kind,Person owner,String subject,String messageRaw,Message.Format msgFmt,NodeImpl parent)
+		throws ModelException
+	{
+		SiteImpl site = parent.getSiteImpl();
+		if( !site.equals(owner.getSite()) )
+			throw new RuntimeException();
+		NodeImpl node = new NodeImpl(site.siteKey,kind,owner,subject,messageRaw,msgFmt);
+		node.setParent(parent);
+		return node;
+	}
+
+	private NodeImpl(SiteKey siteKey,Kind kind,Person owner,String subject,String messageRaw,Message.Format msgFmt)
+		throws ModelException
+	{
+		this.siteKey = siteKey;
+		record = table(siteKey).newRecord(this);
+		if( owner instanceof UserImpl ) {
+			this.owner = (UserImpl)owner;
+			ownerId = this.owner.getId();
+			record.fields().put("owner_id", ownerId);
+		} else {
+			Anonymous anon = (Anonymous)owner;
+			this.cookie = anon.getCookie();
+			this.anonymousName = anon.getName();
+			record.fields().put("cookie",cookie);
+			record.fields().put("anonymous_name", anonymousName);
+		}
+		setKind(kind);
+		setSubject0(subject);
+		message = new MyMessage(msgFmt);
+		setMessage0(messageRaw,msgFmt);
+		setWhenCreated(new Date());
+		nodeCount = 1;
+		childCount = 0;
+	}
+
+	private void setParent(NodeImpl parent)
+		throws ModelException
+	{
+		if( isInDb() )
+			throw new RuntimeException();
+		this.parentId = parent.getId();
+		record.fields().put("parent_id", parentId);
+		if( siteKey != parent.siteKey )
+			throw new RuntimeException();
+		if( !parent.isInDb() ) {
+			// hack for dummy nodes
+			this.parent = parent;
+			List<NodeImpl> children = dummyChildMap.get(parent);
+			if( children == null ) {
+				children = new ArrayList<NodeImpl>();
+				dummyChildMap.put(parent,children);
+			}
+			children.add(this);
+			return;
+		}
+		if( db().isInTransaction() )
+			uncacheAncestors();
+	}
+
+	public DbRecord<LongKey,NodeImpl> getDbRecord() {
+		return record;
+	}
+
+	private DbTable<LongKey,NodeImpl> table() {
+		return record.getDbTable();
+	}
+
+	private DbDatabase db() {
+		return table().getDbDatabase();
+	}
+
+	public long getId() {
+		LongKey key = record.getPrimaryKey();
+		if( key==null )
+			return 0L;
+		return key.value();
+	}
+
+	public Kind getKind() {
+		return kind;
+	}
+
+	public void setKind(Kind kind) {
+		this.kind = kind;
+		record.fields().put( "is_app", DbNull.fix(kind==Kind.APP) );
+	}
+
+	long getParentId() {
+		return parentId;
+	}
+
+	long getOwnerId() {
+		return ownerId;
+	}
+
+	public String getSubject() {
+		return subject;
+	}
+
+	public String getSubjectHtml() {
+		return HtmlUtils.htmlEncode(subject);
+	}
+
+	private synchronized boolean setSubject0(String subject) throws ModelException.RequiredSubject {
+		subject = subject.trim();
+		if( subject.equals("") )
+			throw new ModelException.RequiredSubject();
+		if( subject.equals(this.subject) )
+			return false;
+		this.subject = subject;
+		record.fields().put("subject",subject);
+		return true;
+	}
+
+	public void setSubject(String subject) throws ModelException.RequiredSubject {
+		boolean changed = setSubject0(subject);
+		if (changed)
+			setWhenUpdated();
+	}
+
+	private static final long TEN_MINUTES = 10 * 60 * 1000;
+
+	private void setWhenUpdated() {
+		long timeDiff = new Date().getTime() - getWhenCreated().getTime();
+		boolean acceptChanges = timeDiff > TEN_MINUTES || !getChildren().isEmpty() || isMailToList();
+		if (acceptChanges) {
+			setWhenUpdated( new Date() );
+		}
+	}
+
+
+	private final class MyMessage extends Message {
+
+		MyMessage(Message.Format format) {
+			super(null,format);
+		}
+
+		MyMessage(String raw,Message.Format format) {
+			super(raw,format);
+		}
+
+		private synchronized boolean hasLoaded() {
+			return raw!=null;
+		}
+
+		public synchronized String getRaw() {
+			if( raw==null && record.isInDb() ) {
+				try {
+					Connection con = db().getConnection();
+					PreparedStatement stmt = con.prepareStatement(
+							"select message from node_msg where node_id=?"
+					);
+					stmt.setLong(1,getId());
+					ResultSet rs = stmt.executeQuery();
+					if( rs.next() ) {
+						raw = rs.getString("message");
+					} else {
+						logger.error("no message for "+NodeImpl.this+" - inserting empty message");
+						raw = "";
+						insertMessage(raw);
+					}
+					rs.close();
+					stmt.close();
+					con.close();
+				} catch(SQLException e) {
+					throw new RuntimeException(e);
+				}
+				if( raw==null )
+					throw new NullPointerException(toString());
+			}
+			return raw;
+		}
+
+		public Source getSource() {
+			if( record.isInDb() )
+				return NodeImpl.this;
+			Person owner = getOwner();
+			return owner instanceof User ? new Message.TempSource((User)owner) : null;
+		}
+
+		public boolean isDeleted() {
+			return isDeletedMessage();
+		}
+
+		public boolean isDeactivated() {
+			UserImpl user = getOwnerImpl();
+			return user != null && user.isDeactivated();
+		}
+	}
+
+	public Message getMessage() {
+		return message;
+	}
+
+	private boolean setMessage0(String messageRaw,Message.Format msgFmt) {
+		MyMessage newMessage = new MyMessage(messageRaw,msgFmt);
+		if( message.equals(newMessage) )
+			return false;
+		message = newMessage;
+		record.fields().put("msg_fmt",Character.toString(msgFmt.getCode()));
+		record.fields().put("message",messageRaw);
+		return true;
+	}
+
+	public void setMessage(String message,Message.Format msgFmt) {
+		boolean changed = setMessage0(message,msgFmt);
+		if (changed)
+			setWhenUpdated();
+	}
+
+	public void deleteMessageOrNode() {
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				NodeImpl node = DbUtils.getGoodCopy(NodeImpl.this);
+				node.deleteMessageOrNode();
+				db().commitTransaction();
+			} finally {
+				db().endTransaction();
+			}
+			return;
+		}
+		MailingListImpl mailingList = getMailingListImpl();
+		if (mailingList != null) {
+			mailingList.unsubscribe();
+		}
+		if( getChildCount() == 0 ) {
+			deleteRecursively();
+		} else {
+			setMessage(DELETED_MESSAGE.getRaw(),DELETED_MESSAGE.getFormat());
+			clearPending();
+			update();
+		}
+	}
+
+	private boolean isDeletedMessage() {
+		return getMessage().getRaw().equals(DELETED_MESSAGE.getRaw());
+	}
+
+	public void deleteRecursively() {
+		if( isRoot() ) {
+			getSiteImpl().delete();
+			return;
+		}
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				NodeImpl node = DbUtils.getGoodCopy(NodeImpl.this);
+				node.deleteRecursively();
+				db().commitTransaction();
+			} finally {
+				db().endTransaction();
+			}
+			return;
+		}
+		NodeImpl parent = getParentImpl();
+		record.delete();
+		removedNodeFrom(parent);
+		fireChangeListeners();
+	}
+
+	private synchronized void cacheMessage(String message) {
+		this.message.raw = message;
+	}
+
+	public Date getWhenCreated() {
+		return whenCreated;
+	}
+
+	public void setWhenCreated(Date whenCreated) {
+		this.whenCreated = whenCreated;
+		record.fields().put("when_created",whenCreated);
+		if( record.isInDb() && childCount==0 ) {
+			setLastNodeDate( whenCreated );
+			removedNodeFrom( getParentImpl() );
+			addNode();
+		}
+	}
+
+	private static final Object lastNodeLock = new Object();
+
+	public Node getLastNode() {
+		Node lastNode = getNode(lastNodeId);
+		if( lastNode == null ) {
+			logger.error("lastNode not found for "+this+" with lastNodeId="+lastNodeId,new NullPointerException());
+			// hack fix  -fschmidt
+			synchronized(lastNodeLock) {
+				DbUtils.uncache(this);
+				NodeImpl node = DbUtils.getGoodCopy(this);
+				if( lastNodeId != node.lastNodeId )
+					return getNode(node.lastNodeId);
+				populateLastNodeFields();
+			}
+			DbUtils.uncache(this);
+			NodeImpl node = DbUtils.getGoodCopy(this);
+			if( lastNodeId != node.lastNodeId )
+				return getNode(node.lastNodeId);
+			logger.error("after populateLastNodeFields, lastNode not found for "+node+" with lastNodeId="+node.lastNodeId);
+		}
+		return lastNode;
+	}
+
+	long getLastNodeId() {
+		return lastNodeId;
+	}
+
+	public Date getLastNodeDate() {
+		return lastNodeDate;
+	}
+
+	public Node getGoodCopy() {
+		return DbUtils.getGoodCopy(this);
+	}
+
+	public void insert(boolean isDoneByPoster) throws ModelException.TooManyPosts {
+		if( !db().isInTransaction() )
+			throw new RuntimeException();
+		if( kind==Kind.APP && type==null )
+			throw new RuntimeException("type not set for app");
+		if( isDoneByPoster ) {
+			UserImpl owner = getOwnerImpl();
+			if (owner != null)
+				owner.updateNewPostLimit();
+		}
+		if( isMailToList() && isDoneByPoster )
+			throw new RuntimeException("no more pending");
+		String message = (String)record.fields().remove("message");
+		record.insert();
+		insertMessage(message);
+		setLastNodeId( getId() );
+		setLastNodeDate( whenCreated );
+		record.update();
+		addNode();
+	}
+
+	private void insertMessage(String message) {
+		try {
+			Connection con = db().getConnection();
+			try {
+				PreparedStatement pstmt = con.prepareStatement(
+						"insert into node_msg (node_id,message) values (?,?)"
+				);
+				pstmt.setLong(1,getId());
+				pstmt.setString(2,message);
+				pstmt.executeUpdate();
+				pstmt.close();
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public void update() {
+		String message = (String)record.fields().remove("message");
+		if( message!=null && !db().isInTransaction() )
+			throw new RuntimeException();
+		record.update();
+		if( message != null ) {
+			try {
+				Connection con = db().getConnection();
+				PreparedStatement stmt = con.prepareStatement(
+						"update node_msg set message=? where node_id=?"
+				);
+				stmt.setLong(2,getId());
+				stmt.setString(1,message);
+				stmt.executeUpdate();
+				stmt.close();
+				con.close();
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+		}
+	}
+
+	void checkNewPostLimit() throws ModelException.TooManyPosts {
+		UserImpl owner = getOwnerImpl();
+		if (owner!=null && owner.hasTooManyPosts()) {
+			logger.warn( "Too many posts by "+owner );
+			throw new ModelException.TooManyPosts();
+		}
+	}
+
+	private void uncacheAncestors() {
+		for( NodeImpl node=this; node!=null; node=node.getParentImpl() ) {
+			DbUtils.uncache(node);
+		}
+	}
+
+	public void changeParent(Node n) throws ModelException {
+		NodeImpl node = (NodeImpl)n;
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				DbUtils.getGoodCopy(this).changeParent( node.getGoodCopy() );
+				db().commitTransaction();
+				return;
+			} finally {
+				db().endTransaction();
+			}
+		}
+		changeParentImpl(node);
+	}
+
+	void changeParentImpl(NodeImpl newParent) throws ModelException {
+		if( !isInDb() )
+			throw new RuntimeException();
+		if( !db().isInTransaction() )
+			throw new RuntimeException();
+
+		final NodeImpl oldParent = getParentImpl();
+		if( oldParent.siteKey != newParent.siteKey )
+			throw new RuntimeException("can't change site");
+		db().runAfterCommit(new Runnable(){public void run(){
+			oldParent.fireChildChangeListeners();
+		}});
+		record.fields().put("pin", DbNull.INTEGER);
+
+		if (newParent.getAncestors().contains(this))
+			throw new ModelException.NodeLoop(this);
+		parentId = newParent.getId();
+		record.fields().put("parent_id", parentId);
+		parent = null;
+
+		getDbRecord().update();
+		removedNodeFrom(oldParent);
+		addNode();
+		stale();
+
+		uncacheAncestors();
+	}
+
+	void makeRoot() {
+		record.fields().put("pin", DbNull.INTEGER);
+		parentId = 0L;
+		record.fields().put("parent_id", DbNull.INTEGER);
+		parent = null;
+		getDbRecord().update();
+		stale();
+	}
+
+	private void stale() {
+		Executors.executeAfterCommit(db(),new Runnable(){public void run(){
+			try {
+				Lucene.staleNode(DbUtils.getGoodCopy(NodeImpl.this));
+			} catch(IOException e) {
+				logger.error("StaleNode failed",e);
+			}
+		}});
+	}
+
+
+	private void addNode() {
+		NodeImpl parent = getParentImpl();
+		if( parent == null )
+			return;
+//		synchronized(siteKey.lastNodeLock) {
+			parent.setChildCount();
+			for( NodeImpl node = parent; node != null; node = node.getParentImpl() ) {
+				if( node.nodeCount==1 || node.lastNodeDate.before(lastNodeDate) ) {
+					node.setLastNodeId( lastNodeId );
+					node.setLastNodeDate( lastNodeDate );
+				}
+				node.setNodeCount();
+				node.record.update();
+			}
+//		}
+	}
+
+
+	private void removedNodeFrom(NodeImpl parent) {
+		if( parent == null )
+			return;
+//		synchronized(siteKey.lastNodeLock) {
+			parent.setChildCount();
+			try {
+				Connection con = db().getConnection();
+				try {
+					PreparedStatement pstmtGetLastNode = con.prepareStatement(
+						"select * from node"
+						+"	where parent_id = ?"
+						+"	order by last_node_date desc, node_id desc"
+						+"	limit 1"
+					);
+					for( NodeImpl node = parent; node != null; node = node.getParentImpl() ) {
+						if( node.lastNodeId == lastNodeId ) {
+							pstmtGetLastNode.setLong(1,node.getId());
+							ResultSet rs = pstmtGetLastNode.executeQuery();
+							if( rs.next() ) {
+								NodeImpl lastNode = getNode(rs);
+								node.setLastNodeId( lastNode.lastNodeId );
+								node.setLastNodeDate( lastNode.lastNodeDate );
+							} else {
+								node.setLastNodeId( node.getId() );
+								node.setLastNodeDate( node.whenCreated );
+							}
+							rs.close();
+						}
+						node.setNodeCount();
+						node.record.update();
+					}
+					pstmtGetLastNode.close();
+				} finally {
+					con.close();
+				}
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+//		}
+		parent.uncacheAncestors();
+	}
+
+	private void setLastNodeId(long lastNodeId) {
+		if( this.lastNodeId == lastNodeId )
+			return;
+		this.lastNodeId = lastNodeId;
+		record.fields().put("last_node_id", lastNodeId);
+	}
+
+	private void setLastNodeDate(Date lastNodeDate) {
+		if( ObjectUtils.equals(this.lastNodeDate,lastNodeDate) )
+			return;
+		this.lastNodeDate = lastNodeDate;
+		record.fields().put("last_node_date", lastNodeDate);
+	}
+
+	private void setNodeCount() {
+		int nodeCount = getCount( "select 1 + coalesce(sum(node_count),0) as n from node where parent_id = ?" );
+		if( this.nodeCount == nodeCount )
+			return;
+		this.nodeCount = nodeCount;
+		record.fields().put("node_count", nodeCount);
+	}
+
+	private void setChildCount() {
+		int childCount = getCount( "select count(*) as n from node where parent_id = ?" );
+		if( this.childCount == childCount )
+			return;
+		this.childCount = childCount;
+		record.fields().put("child_count", childCount);
+	}
+
+
+	public NodeIterator<? extends Node> getChildren() {
+		return getChildrenImpl(null);
+	}
+
+	public NodeIterator<? extends Node> getChildren(String cnd) {
+		return getChildrenImpl(cnd);
+	}
+
+	private static final Map<NodeImpl,List<NodeImpl>> dummyChildMap = Collections.synchronizedMap(new WeakHashMap<NodeImpl,List<NodeImpl>>());
+
+	NodeIterator<NodeImpl> getChildrenImpl(String cnd) {
+		if( !isInDb() ) {
+			List<NodeImpl> children = dummyChildMap.get(this);
+			if( children == null )
+				return NodeIterator.empty();
+			return NodeIterator.nodeIterator( children.iterator() );
+		}
+		return new CursorNodeIterator( siteKey,
+				"(select *"
+				+" from node"
+				+" where parent_id = ?"
+				+" and pin is not null "
+				+(cnd==null?"":"and " + cnd)
+				+" order by pin)"
+				+" union all"
+				+" (select *"
+				+" from node"
+				+" where parent_id = ?"
+				+" and pin is null "
+				+(cnd==null?"":"and " + cnd)
+				+" order by last_node_date desc, node_id desc)"
+			,
+				new DbParamSetter() {
+					public void setParams(PreparedStatement stmt) throws SQLException {
+						stmt.setLong( 1, getId() );
+						stmt.setLong( 2, getId() );
+					}
+				}
+		);
+	}
+
+	private int getCount(String sql) {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(sql);
+			stmt.setLong(1,getId());
+			ResultSet rs = stmt.executeQuery();
+			rs.next();
+			int n = rs.getInt("n");
+			rs.close();
+			stmt.close();
+			con.close();
+			return n;
+		} catch(SQLException e) {
+			logger.error("sql = " + sql);
+			throw new RuntimeException(e);
+		}
+	}
+
+	public int getChildCount() {
+		return childCount;
+	}
+
+
+
+	public boolean isPinned() {
+		return isPinned;
+	}
+
+	public void pin(Node[] children) {
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				pin(children);
+				db().commitTransaction();
+			} finally {
+				db().endTransaction();
+			}
+			return;
+		}
+		try {
+			Connection con = db().getConnection();
+			try {
+				{
+					PreparedStatement stmt = con.prepareStatement(
+						"select node_id"
+						+" from node"
+						+" where parent_id = ?"
+						+" and pin is not null"
+					);
+					stmt.setLong(1,getId());
+					ResultSet rs = stmt.executeQuery();
+					while( rs.next() ) {
+						table().uncache( new LongKey(rs.getLong("node_id")) );
+					}
+					rs.close();
+					stmt.close();
+				}
+				PreparedStatement stmt = con.prepareStatement(
+					"update node set pin=null where parent_id=? and pin is not null"
+				);
+				stmt.setLong(1,getId());
+				stmt.executeUpdate();
+				stmt.close();
+				stmt = con.prepareStatement(
+					"update node set pin=? where node_id=?"
+				);
+				for( int i=0; i<children.length; i++ ) {
+					Node child = children[i];
+					if( !this.equals(child.getParent()) )
+						throw new RuntimeException(""+child+" not a child of "+this);
+					stmt.setInt(1,i+1);
+					stmt.setLong(2,child.getId());
+					stmt.executeUpdate();
+					DbUtils.uncache((NodeImpl)child);
+				}
+				stmt.close();
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+		fireChildChangeListeners();
+	}
+
+
+	public long getExportedNodeId() {
+		return exportedNodeId;
+	}
+
+	public void setExportedNodeId(long id) {
+		if( id==0L )
+			throw new RuntimeException();
+		this.exportedNodeId = id;
+		record.fields().put( "exported_node_id", Long.valueOf(id) );
+		record.update();
+	}
+
+	public String getEmbeddingUrl() {
+		return embeddingUrl;
+	}
+
+	public void setEmbeddingUrl(String url) {
+		this.embeddingUrl = url;
+		record.fields().put( "embedding_url", DbNull.fix(embeddingUrl));
+		record.update();
+	}
+
+
+	@Override public boolean equals(Object obj) {
+		return this==obj || obj instanceof NodeImpl && record.isInDb() && ((NodeImpl)obj).getId()==getId();
+	}
+
+	@Override public int hashCode() {
+		return (int)getId();
+	}
+
+	@Override public String toString() {
+		return "node-"+getId();
+	}
+
+//	private final ListenerList<V> preInsertListeners = new ListenerList<V>();
+	static final ListenerList<NodeImpl> preUpdateListeners = new ListenerList<NodeImpl>();
+//	private final ListenerList<V> preDeleteListeners = new ListenerList<V>();
+	static final ListenerList<NodeImpl> postInsertListeners = new ListenerList<NodeImpl>();
+	static final ListenerList<NodeImpl> postUpdateListeners = new ListenerList<NodeImpl>();
+	static final ListenerList<NodeImpl> postDeleteListeners = new ListenerList<NodeImpl>();
+
+	private static Computable<SiteKey,DbTable<LongKey,NodeImpl>> tables = new SimpleCache<SiteKey,DbTable<LongKey,NodeImpl>>(new WeakHashMap<SiteKey,DbTable<LongKey,NodeImpl>>(), new Computable<SiteKey,DbTable<LongKey,NodeImpl>>() {
+		public DbTable<LongKey,NodeImpl> get(SiteKey siteKey) {
+			DbDatabase db = siteKey.getDb();
+			final long siteId = siteKey.getId();
+			DbTable<LongKey,NodeImpl> table = db.newTable("node",db.newIdentityLongKeySetter("node_id")
+				, new DbObjectFactory<LongKey,NodeImpl>() {
+					public NodeImpl makeDbObject(LongKey key,ResultSet rs,String tableName)
+						throws SQLException
+					{
+						SiteKey siteKey = SiteKey.getInstance(siteId);
+						return new NodeImpl(siteKey,key,rs);
+					}
+				}
+			);
+			table.getPreUpdateListeners().add(preUpdateListeners);
+			table.getPostInsertListeners().add(postInsertListeners);
+			table.getPostUpdateListeners().add(postUpdateListeners);
+			table.getPostDeleteListeners().add(postDeleteListeners);
+			return table;
+		}
+	});
+
+	static DbTable<LongKey,NodeImpl> table(SiteKey siteKey) {
+		return tables.get(siteKey);
+	}
+
+	static NodeImpl getNode(SiteKey siteKey,long id) {
+		return table(siteKey).findByPrimaryKey(new LongKey(id));
+	}
+
+	private NodeImpl getNode(long id) {
+		return getNode(siteKey,id);
+	}
+
+	static Collection<NodeImpl> getNodes(SiteKey siteKey,Collection<Long> ids) {
+		List<LongKey> list = new ArrayList<LongKey>();
+		for( long id : ids ) {
+			list.add( new LongKey(id) );
+		}
+		return table(siteKey).findByPrimaryKey(list).values();
+	}
+
+	static NodeImpl getNode(SiteKey siteKey,ResultSet rs)
+		throws SQLException
+	{
+		return table(siteKey).getDbObject(rs);
+	}
+
+	private NodeImpl getNode(ResultSet rs)
+		throws SQLException
+	{
+		return getNode(siteKey,rs);
+	}
+
+
+
+
+
+	private static final Collator collator = Collator.getInstance();
+	private CollationKey collationKey = null;
+
+	CollationKey getCollationKey() {
+		if( collationKey==null )
+			collationKey = collator.getCollationKey(getSubject());
+		return collationKey;
+	}
+
+
+
+	public Collection<Subscription> getSubscriptions(int i, int n) {
+		return getSubscriptions(
+			"select * from subscription where node_id = ? limit " + n + " offset " + i
+		);
+	}
+
+	Collection<Subscription> getSubscriptions(String sql) {
+		List<Subscription> list = new ArrayList<Subscription>();
+		try {
+			Connection con = db().getConnection();
+			try {
+				PreparedStatement stmt = con.prepareStatement(sql);
+				stmt.setLong( 1, getId() );
+				ResultSet rs = stmt.executeQuery();
+				while( rs.next() ) {
+					SubscriptionImpl subscription = SubscriptionImpl.getSubscription(siteKey,rs);
+					if( subscription != null )
+						list.add( subscription );
+				}
+				rs.close();
+				stmt.close();
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+		return list;
+	}
+
+	public int getSubscriptionCount() {
+		db().beginTransaction();
+		try {
+			return getCount(
+				"select count(*) as n from subscription where node_id=?"
+			);
+		} finally {
+			db().endTransaction();
+		}
+	}
+
+	public Map<User,Subscription> getSubscribersToNotify() {
+		return SubscriptionImpl.getSubscribersToNotify(this);
+	}
+
+	public NodeIterator<? extends Node> getAncestors() {
+		return getAncestorImpls();
+	}
+
+    NodeIterator<NodeImpl> getAncestorImpls() {
+		return new NodeIterator<NodeImpl>() {
+			private NodeImpl next = NodeImpl.this;
+
+			public boolean hasNext() {
+				return next != null;
+			}
+
+			public NodeImpl next() {
+				if( !hasNext() )
+					throw new NoSuchElementException();
+				try {
+					return next;
+				} finally {
+					next = next.getParentImpl();
+				}
+			}
+
+			public void close() {
+				next = null;
+			}
+		};
+    }
+
+
+	synchronized UserImpl getOwnerImpl() {
+		if( ownerId!=0L && DbUtils.isStale(owner) ) {
+			owner = UserImpl.getUser(siteKey,ownerId);
+		}
+		return owner;
+	}
+
+	public final Person getOwner() {
+		UserImpl user = getOwnerImpl();
+		if( user != null )
+			return user;
+		if( cookie != null )
+			return new Anonymous(getSiteImpl(), cookie, anonymousName);
+		throw new RuntimeException();
+	}
+
+	public void setOwner(User owner) {
+		this.owner = null;
+		this.ownerId = owner.getId();
+		record.fields().put("owner_id", ownerId);
+		// Remove any anonymous information
+		record.fields().put("cookie", DbNull.STRING);
+		record.fields().put("anonymous_name", DbNull.STRING);
+	}
+
+
+	NodeImpl getAppImpl() {
+		for( NodeImpl node=this; node!=null; node=node.getParentImpl() ) {
+			if( node.getKind() == Kind.APP )
+				return node;
+		}
+		return null;
+	}
+
+	public final Node getApp() {
+		return getAppImpl();
+	}
+
+
+	static ListenerList<NodeImpl> gotParentListeners = new ListenerList<NodeImpl>();
+
+	synchronized NodeImpl getParentImpl() {
+		if( parentId == 0L && parent==null )
+			return null;
+		if( DbUtils.isStale(parent) ) {
+			parent = getNode(parentId);
+			if( parent == null )
+				logger.error(""+this+" parent="+parentId+" doesn't exist");
+			else if( parent.siteKey != siteKey )
+				logger.error(""+this+" parent="+parentId+" siteId="+siteKey+" parent.siteId="+parent.siteKey);
+			gotParentListeners.event(this);
+		}
+		return parent;
+	}
+
+	public final Node getParent(){
+		return getParentImpl();
+	}
+
+	SiteImpl getSiteImpl() {
+		return siteKey.site();
+	}
+
+	public Site getSite() {
+		return getSiteImpl();
+	}
+
+	public boolean isRoot() {
+		return parentId == 0L;
+	}
+
+
+	NodeImpl getTopicImpl() {
+		if( getKind() != Kind.POST )
+			return null;
+		NodeImpl node = this;
+		while(true) {
+			NodeImpl parent = node.getParentImpl();
+			if( parent==null || parent.getKind() != Kind.POST )
+				break;
+			node = parent;
+		}
+		return node;
+	}
+
+	public final Node getTopic() {
+		return getTopicImpl();
+	}
+
+	static void preloadMessages(List<Node> nodes) {
+		if( nodes.size()==0 )
+			return;
+		try {
+			Map<Long,NodeImpl> map = new HashMap<Long,NodeImpl>();
+			StringBuilder query = new StringBuilder();
+			query.append( "select node_id, message from node_msg where node_id in (" );
+			for (Node n : nodes) {
+				NodeImpl node = (NodeImpl) n;
+				if (node.message.hasLoaded())
+					continue;
+				if (!map.isEmpty())
+					query.append(',');
+				query.append(node.getId());
+				map.put(node.getId(), node);
+			}
+			if( map.isEmpty() )
+				return;
+			query.append( ')' );
+			Connection con = nodes.get(0).getSite().getDb().getConnection();
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(query.toString());
+			while( rs.next() ) {
+				long nodeId = rs.getLong("node_id");
+				String message = rs.getString("message");
+				map.get(nodeId).cacheMessage(message);
+			}
+			rs.close();
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+
+	int getIntId() {
+		return getIntId(getId());
+	}
+
+	static int getIntId(long id) {
+		if( id > Integer.MAX_VALUE )
+			throw new RuntimeException();
+		return (int)id;
+	}
+
+	public Date getWhenUpdated() {
+		return whenUpdated;
+	}
+
+	void setWhenUpdated(Date whenUpdated) {
+		this.whenUpdated = whenUpdated;
+		record.fields().put("when_updated", whenUpdated);
+	}
+
+
+
+
+	static ListenerList<NodeImpl> childChangeListeners = new ListenerList<NodeImpl>();
+
+	private void fireChildChangeListeners() {
+		childChangeListeners.event(this);
+	}
+
+	static void addPostInsertListener(final Listener<? super NodeImpl> listener) {
+		postInsertListeners.add(listener);
+	}
+
+	static void addPostUpdateListener(final Listener<? super NodeImpl> listener) {
+		postUpdateListeners.add(listener);
+		Listener<MailingListImpl> mlListener = new Listener<MailingListImpl>() {
+			public void event(MailingListImpl ml) {
+				listener.event(ml.getForumImpl());
+			}
+		};
+		MailingListImpl.postInsertListeners.add(mlListener);
+		MailingListImpl.postUpdateListeners.add(mlListener);
+		MailingListImpl.postDeleteListeners.add(mlListener);
+	}
+
+	static void addPostDeleteListener(final Listener<? super NodeImpl> listener) {
+		postDeleteListeners.add(listener);
+	}
+
+
+	private static ListenerList<NodeImpl> changeListeners = new ListenerList<NodeImpl>();
+
+	private void fireChangeListeners() {
+		changeListeners.event(this);
+	}
+
+	static void addChangeListener(final Listener<? super NodeImpl> listener) {
+		addPostUpdateListener(listener);
+		addPostDeleteListener(listener);
+		changeListeners.add(listener);
+	}
+
+	private boolean hasNeighborTopic(boolean next) {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"SELECT 1 " +
+				"FROM node " +
+				"WHERE parent_id = ? " +
+					"AND (is_app = 'f' or is_app is null) " +
+					"AND last_node_date " + (next?'<':'>') + " ? " +
+				"LIMIT 1"
+			);
+			stmt.setLong( 1, getParentId() );
+			stmt.setTimestamp( 2, new java.sql.Timestamp(getLastNodeDate().getTime()));
+			ResultSet rs = stmt.executeQuery();
+			try {
+				return rs.next();
+			} finally {
+				rs.close();
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private Node getNeighborTopic(boolean next) {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"SELECT * "+
+				"FROM node "+
+				"WHERE parent_id = ? "+
+					"AND (is_app = 'f' or is_app is null) " +
+					"AND last_node_date " + (next?'<':'>') + " ? " +
+				"ORDER BY last_node_date " + (next?"DESC ":"ASC ") +
+				"LIMIT 1"
+			);
+
+			stmt.setLong( 1, getParentId() );
+			stmt.setTimestamp( 2, new java.sql.Timestamp(getLastNodeDate().getTime()));
+			ResultSet rs = stmt.executeQuery();
+			try {
+				return rs.next()? getNode(rs) : null;
+			} finally {
+				rs.close();
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public boolean hasPreviousTopic() {
+		return hasNeighborTopic(false);
+	}
+
+	public Node getPreviousTopic() {
+		return getNeighborTopic(false);
+	}
+
+	public boolean hasNextTopic() {
+		return hasNeighborTopic(true);
+	}
+
+	public Node getNextTopic() {
+		return getNeighborTopic(true);
+	}
+
+
+	public int getDescendantPostCount() {
+		return getDescendantCount() - getDescendantAppCount();
+	}
+
+	public int getDescendantAppCount() {
+		int count = 1;
+		for( Node f : getChildApps() ) {
+			count += f.getDescendantAppCount();
+		}
+		return count;
+	}
+
+
+	private Collection<NodeImpl> getDescendantApps(Filter<Node> filter) {
+		List<NodeImpl> list = new ArrayList<NodeImpl>();
+		getDescendantApps(filter,list);
+		return list;
+	}
+
+	private void getDescendantApps(Filter<Node> filter,Collection<NodeImpl> list) {
+		list.add(this);
+		for( Node f : getChildApps() ) {
+			NodeImpl node = (NodeImpl)f;
+			if( filter.ok(node) )
+				node.getDescendantApps(filter,list);
+		}
+	}
+
+	private boolean hasChildKind(Node.Kind kind) {
+		String cnd = kind == Node.Kind.APP? "is_app" : "(is_app = 'f' or is_app is null)";
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select exists"
+				+" (select * from node"
+				+" where parent_id = ?"
+				+" and "
+				+ cnd
+				+") as b"
+			);
+			stmt.setLong( 1, getId() );
+			ResultSet rs = stmt.executeQuery();
+			rs.next();
+			try {
+				return rs.getBoolean("b");
+			} finally {
+				rs.close();
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private boolean hasPinnedKind(Node.Kind kind) {
+		String cnd = kind == Node.Kind.APP? "is_app" : "(is_app = 'f' or is_app is null)";
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select exists"
+				+" (select 1 from node"
+				+" where parent_id = ?"
+				+" and pin is not null"
+				+" and "
+				+ cnd
+				+") as b"
+			);
+			stmt.setLong( 1, getId() );
+			ResultSet rs = stmt.executeQuery();
+			rs.next();
+			try {
+				return rs.getBoolean("b");
+			} finally {
+				rs.close();
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public boolean hasChildApps() {
+		return hasChildKind(Node.Kind.APP);
+	}
+
+	public boolean hasChildTopics() {
+		return hasChildKind(Node.Kind.POST);
+	}
+
+	public boolean hasPinnedApps() {
+		return hasPinnedKind(Node.Kind.APP);
+	}
+
+	public boolean hasPinnedTopics() {
+		return hasPinnedKind(Node.Kind.POST);
+	}
+
+	public NodeIterator<? extends Node> getChildApps() {
+		return getChildApps(null);
+	}
+
+	public NodeIterator<? extends Node> getChildApps(String cnd) {
+		cnd = cnd==null ? "is_app" : "is_app and (" + cnd + ")";
+		return getChildrenImpl(cnd);
+	}
+
+	public String getType() {
+		return type;
+	}
+
+	public void setType(String type) {
+		this.type = type;
+		record.fields().put( "type",
+			Type.COMMENT.equals(type) ? DbNull.STRING : type
+		);
+	}
+
+
+	public boolean isInDb() {
+		return record.isInDb();
+	}
+
+
+	public int getDescendantCount() {
+		return nodeCount;
+	}
+
+	public Message.SourceType getMessageSourceType() {
+		return Message.SourceType.NODE;
+	}
+
+
+	static void nop() {}
+
+
+	private DbParamSetter simpleParamSetter() {
+		return new DbParamSetter() {
+			public void setParams(PreparedStatement stmt) throws SQLException {
+				stmt.setLong( 1, getId() );
+			}
+		};
+	}
+
+	private List<NodeImpl> childAppList() {
+		return new CursorNodeIterator( siteKey,
+				"select * from node where parent_id = ? and is_app"
+			, simpleParamSetter()
+		).asList();
+	}
+
+	private int getTopicCount(Filter<Node> filter) {
+		int n = childCount;
+		for( NodeImpl childApp : childAppList() ) {
+			n--;
+			if( filter.ok(childApp) )
+				n += childApp.getTopicCount(filter);
+		}
+		return n;
+	}
+
+	private int getTopicCount2(String cnd,Filter<Node> filter) {
+		int n = getCount(
+			"select count(*) as n from node where parent_id=? and is_app is null and (" + cnd + ")"
+		);
+		for( NodeImpl childApp : childAppList() ) {
+			if( filter.ok(childApp) )
+				n += childApp.getTopicCount2(cnd,filter);
+		}
+		return n;
+	}
+
+	public int getTopicCount(String cnd,Filter<Node> filter) {
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				return getTopicCount(cnd,filter);
+			} finally {
+				db().endTransaction();
+			}
+		}
+		return getTopicCount0(cnd, filter);
+	}
+
+	private Map<String, Integer> topicCountCache;
+
+	private synchronized int getTopicCount0(String cnd, Filter<Node> filter) {
+		if (topicCountCache == null)
+			topicCountCache = new HashMap<String, Integer>();
+		String cacheKey = (cnd == null? "" : cnd + '|') + filter.getClass().getName();
+		Integer countValue = topicCountCache.get(cacheKey);
+		if (countValue == null) {
+			countValue = cnd==null ? getTopicCount(filter) : getTopicCount2(cnd,filter);
+			topicCountCache.put(cacheKey, countValue);
+		}
+		return countValue;
+	}
+
+
+	private static class MyIter {
+		NodeImpl node;
+		Comparable cmp;
+		final NodeIterator<NodeImpl> iter;
+
+		MyIter(NodeImpl node,Comparable cmp,NodeIterator<NodeImpl> iter) {
+			this.node = node;
+			this.cmp = cmp;
+			this.iter = iter;
+		}
+	}
+
+	private class OrderedNodeIterator extends NodeIterator<NodeImpl> {
+		private final List<MyIter> mis = new ArrayList<MyIter>();
+		NodeImpl next = null;
+		private final boolean isTopics;
+		private final boolean skipPinned;
+		private final Order order;
+		private final String sql;
+		private final Filter<Node> filter;
+
+		OrderedNodeIterator(boolean isTopics,boolean skipPinned,String cnd,Filter<Node> filter,Order order) {
+			if( !isTopics && skipPinned )
+				throw new UnsupportedOperationException();
+			this.isTopics = isTopics;
+			this.skipPinned = skipPinned;
+			this.order = order;
+			this.filter = filter;
+			StringBuilder buf = new StringBuilder();
+			buf.append( "select * from node where parent_id = ?" );
+			if( cnd != null )
+				buf.append( " and (is_app or (" + cnd + ")) " );
+			buf.append( " order by " ).append( order.sqlOrder() );
+			this.sql = buf.toString();
+			NodeIterator<NodeImpl> empty = NodeIterator.empty();
+			mis.add( new MyIter(NodeImpl.this,order.getComparable(NodeImpl.this),empty) );
+		}
+
+		void add(MyIter mi) {
+			int len = mis.size();
+			for( int i=0; i<len; i++ ) {
+				@SuppressWarnings("unchecked")
+				boolean precedes = mi.cmp.compareTo(mis.get(i).cmp) < 0;
+				if( precedes ) {
+					mis.add(i,mi);
+					return;
+				}
+			}
+			mis.add(mi);
+		}
+
+		public boolean hasNext() {
+			if( next != null )
+				return true;
+			while( !mis.isEmpty() ) {
+				MyIter mi = mis.remove(0);
+				if( mi.iter == null ) {
+					next = mi.node;
+					return true;
+				}
+				final NodeImpl node = mi.node;
+				if( mi.iter.hasNext() ) {
+					mi.node = mi.iter.next();
+					mi.cmp = order.getComparable(mi.node);
+					add(mi);
+				}
+				if( node != NodeImpl.this && filter != null && !filter.ok(node) )
+					continue;
+				boolean isPost = node.getKind() == Kind.POST;
+				if( isPost && isTopics ) {
+					if( skipPinned && node.isPinned() && node.parentId==getId() )
+						continue;
+					next = node;
+					return true;
+				}
+				NodeIterator<NodeImpl> children = new CursorNodeIterator( siteKey, sql,
+					new DbParamSetter() {
+						public void setParams(PreparedStatement stmt) throws SQLException {
+							stmt.setLong( 1, node.getId() );
+						}
+					}
+				);
+				if( children.hasNext() ) {
+					NodeImpl firstChild = children.next();
+					add( new MyIter(firstChild,order.getComparable(firstChild),children) );
+				}
+				if( isPost ) {
+					Comparable postCmp = order.getPostComparable(node);
+					if( postCmp != order.getComparable(node) ) {
+						add( new MyIter(node,postCmp,null) );
+					} else {
+						next = node;
+						return true;
+					}
+				}
+			}
+			return false;
+		}
+
+		public NodeImpl next() throws NoSuchElementException {
+			if( !hasNext() )
+				throw new NoSuchElementException();
+			try {
+				return next;
+			} finally {
+				next = null;
+			}
+		}
+
+		public void close() {
+			while( !mis.isEmpty() ) {
+				NodeIterator<NodeImpl> iter = mis.remove(0).iter;
+				if( iter != null )
+					iter.close();
+			}
+		}
+	}
+
+	private static class ConcatNodeIterator extends NodeIterator<NodeImpl> {
+		private final NodeIterator<NodeImpl>[] a;
+		private int i = 0;
+
+		ConcatNodeIterator(NodeIterator<NodeImpl>... a) {
+			this.a = a;
+		}
+
+		public boolean hasNext() {
+			while(true) {
+				if( i==a.length )
+					return false;
+				if( a[i].hasNext() )
+					return true;
+				a[i++].close();
+			}
+		}
+
+		public NodeImpl next() throws NoSuchElementException {
+			return a[i].next();
+		}
+
+		public void close() {
+			while( i < a.length ) {
+				a[i++].close();
+			}
+		}
+	}
+
+	private String fixCnd(String cnd) {
+		if( cnd==null )
+			return "";
+		cnd = cnd.trim();
+		return cnd.length()==0 ? "" : " and (" + cnd + ") ";
+	}
+
+	private NodeIterator<NodeImpl> getPinned(String cnd) {
+		cnd = fixCnd(cnd);
+		return new CursorNodeIterator( siteKey,
+				"select * from node where pin is not null and parent_id = ? and is_app is null"
+				+ cnd
+				+" order by pin"
+			, simpleParamSetter()
+		);
+	}
+
+
+	public NodeIterator<? extends Node> getTopicsByPinnedAndLastNodeDate(String cnd,Filter<Node> filter) {
+		@SuppressWarnings("unchecked")
+		NodeIterator<NodeImpl> i = new ConcatNodeIterator(
+			getPinned(cnd),
+			new OrderedNodeIterator(true,true,cnd,filter,Order.BY_LAST_NODE_DATE_DESC)
+		);
+		return i;
+	}
+
+	public NodeIterator<? extends Node> getPostsByDate(Filter<Node> filter) {
+		return new OrderedNodeIterator(false,false,null,filter,Order.BY_DATE_DESC);
+	}
+
+	public NodeIterator<? extends Node> getPostsByDateAscending(Filter<Node> filter) {
+		return new OrderedNodeIterator(false,false,null,filter,Order.BY_WHEN_CREATED);
+	}
+
+	public NodeIterator<? extends Node> getTopicsByLastNodeDate(String cnd,Filter<Node> filter) {
+		return new OrderedNodeIterator(true,false,cnd,filter,Order.BY_LAST_NODE_DATE_DESC);
+	}
+
+	public NodeIterator<? extends Node> getTopicsBySubject(String cnd,Filter<Node> filter) {
+		return new OrderedNodeIterator(true,false,cnd,filter,Order.BY_SUBJECT);
+	}
+
+	public NodeIterator<? extends Node> getTopicsByPinnedAndRootNodeDate(String cnd,Filter<Node> filter) {
+		String fixedCnd = fixCnd(cnd);
+		StringBuilder sql = new StringBuilder();
+		Collection<NodeImpl> apps = getDescendantApps(filter);
+		apps.remove(this);
+		sql.append( "select * from node where parent_id=" ).append( getId() )
+			.append( " and is_app is null and pin is null" ).append( fixedCnd );
+		if( !apps.isEmpty() ) {
+			sql.append( " or parent_id in (" );
+			Iterator<NodeImpl> iter = apps.iterator();
+			sql.append( iter.next().getId() );
+			while( iter.hasNext() ) {
+				sql.append( "," ).append( iter.next().getId() );
+			}
+			sql.append( ") and is_app is null" ).append( fixedCnd );
+		}
+		sql.append(" order by when_created desc");
+
+		@SuppressWarnings("unchecked")
+		NodeIterator<NodeImpl> i = new ConcatNodeIterator(
+			getPinned(cnd),
+			new CursorNodeIterator( siteKey, sql.toString(), DbParamSetter.NONE )
+		);
+		return i;
+	}
+
+	public NodeIterator<? extends Node> getTopicsByPopularity(String cnd,Filter<Node> filter) {
+		String fixedCnd = fixCnd(cnd);
+		StringBuilder sql = new StringBuilder();
+		Collection<NodeImpl> apps = getDescendantApps(filter);
+		sql.append( "SELECT n.* FROM node n, view_count vc ")
+			.append("WHERE n.node_id = vc.node_id ")
+			.append("AND is_app is null ")
+			.append("AND pin is null " )
+			.append(fixedCnd)
+			.append("AND parent_id in (");
+		Iterator<NodeImpl> iter = apps.iterator();
+		sql.append( iter.next().getId() );
+		while( iter.hasNext() ) {
+			sql.append( "," ).append( iter.next().getId() );
+		}
+		sql.append(") ")
+			.append("ORDER BY views desc");
+
+		@SuppressWarnings("unchecked")
+		NodeIterator<NodeImpl> i = new ConcatNodeIterator(
+			getPinned(cnd),
+			new CursorNodeIterator( siteKey, sql.toString(), DbParamSetter.NONE )
+		);
+		return i;
+	}
+
+	public NodeIterator<? extends Node> getDescendants() {
+		return getDescendantImpls();
+	}
+
+	NodeIterator<NodeImpl> getDescendantImpls() {
+		return getDescendantImpls(null);
+	}
+
+	public NodeIterator<? extends Node> getDescendantApps() {
+		return getDescendantImpls("is_app");
+	}
+
+	private NodeIterator<NodeImpl> getDescendantImpls(String cnd) {
+		List<NodeImpl> list = new ArrayList<NodeImpl>();
+		list.add(this);
+		int i = 0;
+		while( i < list.size() ) {
+			NodeImpl node = list.get(i++);
+			for( NodeImpl child : node.getChildrenImpl(cnd) ) {
+				list.add(child);
+			}
+		}
+		return NodeIterator.nodeIterator(list);
+	}
+
+	public long getSourceId() {
+		return getId();
+	}
+
+
+
+	public void populateLastNodeFields() {
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				DbUtils.getGoodCopy(this).populateLastNodeFields();
+				db().commitTransaction();
+			} finally {
+				db().endTransaction();
+			}
+			return;
+		}
+//		synchronized(siteKey.lastNodeLock) {
+			populateLastNodeFields2();
+//		}
+	}
+
+	private void populateLastNodeFields2() {
+		NodeImpl lastChild = null;
+		NodeIterator<NodeImpl> nodes = new CursorNodeIterator( siteKey,
+				"select * from node where parent_id = ?"
+			, simpleParamSetter()
+		);
+		for( NodeImpl child : nodes ) {
+			child.populateLastNodeFields2();
+			if( lastChild==null || lastChild.lastNodeDate.before(child.lastNodeDate) )
+				lastChild = child;
+		}
+		if( lastChild==null ) {
+			setLastNodeId( getId() );
+			setLastNodeDate( whenCreated );
+		} else {
+			setLastNodeId( lastChild.lastNodeId );
+			setLastNodeDate( lastChild.lastNodeDate );
+		}
+		setNodeCount();
+		setChildCount();
+		if( !record.fields().isEmpty() )
+			record.update();
+	}
+
+
+
+	// mailing list related
+
+	static final String messageIDEnding;
+	static {
+		try {
+			messageIDEnding = ".post@" + Init.get("mailDomain",InetAddress.getLocalHost().getHostName());
+		} catch(UnknownHostException e) {
+			logger.error("",e);
+			System.exit(-1);
+			throw new RuntimeException();  // for compiler
+		}
+	}
+
+	public String getMessageID() {
+		return messageID;
+	}
+
+	public MailFromList getParentMailFromList() {
+		Node node = getParent();
+		return (node == null) ? null : node.getMailFromList();
+	}
+
+	public String getOrGenerateMessageID() {
+		if (messageID == null) {
+			String id = "" + System.currentTimeMillis() + "-" + getId() + messageIDEnding;
+			setMessageID(id);
+			if( isInDb() )
+				this.record.update();
+		}
+		return messageID;
+	}
+
+	void setMessageID(String messageID) {
+		this.messageID = messageID;
+		record.fields().put("message_id",messageID);
+	}
+
+	void setGuessedParent(NodeImpl newParent)
+		throws ModelException
+	{
+		setGuessedParent(true);
+		changeParentImpl(newParent);
+	}
+
+	boolean isGuessedParent() {
+		return (isGuessedParent != null) && isGuessedParent;
+	}
+
+	boolean isNotGuessedParent() {
+		return (isGuessedParent != null) && !isGuessedParent;
+	}
+
+	public boolean hasGuessedParent() {
+		return isGuessedParent();
+	}
+
+	Boolean rawGuessedParent() {
+		return isGuessedParent;
+	}
+
+	/**
+	 * Set 'guessed_parent' flag of the post.
+	 *
+	 * @param guessed flag value
+	 */
+	void setGuessedParent(Boolean guessed) {
+		this.isGuessedParent = guessed;
+		record.fields().put("guessed_parent", DbNull.fix(this.isGuessedParent) );
+	}
+
+	void setGuessedParent(String parentID) {
+		setGuessedParent(Boolean.TRUE);
+		setRawParentMessageId(parentID);
+	}
+
+	// only returns MailingList if not isUIHidden
+	public MailingList getAssociatedMailingList() {
+		return getAssociatedMailingListImpl();
+	}
+
+	MailingListImpl getAssociatedMailingListImpl() {
+		for( NodeImpl node=this; node!=null; node=node.getParentImpl() ) {
+			if( node.getKind()==Kind.APP ) {
+				MailingListImpl ml = node.getMailingListImpl();
+				if( ml != null )
+					return ml;
+			}
+		}
+		return null;
+	}
+
+	boolean isFromMailingList() {
+		return message.getFormat() == Message.Format.MAILING_LIST;
+	}
+
+	void clearPending() {
+		whenSent = null;
+		record.fields().put("when_sent", DbNull.TIMESTAMP);
+		if( !db().isInTransaction() ) {
+			record.update();
+			DbUtils.uncache(getOwnerImpl());
+		}
+	}
+
+	private boolean isMailToList() {
+		return getKind() == Kind.POST && getAssociatedMailingListImpl() != null && !isFromMailingList();
+	}
+
+	public MailToList getMailToList() {
+		if( !isMailToList() )
+			return null;
+		return new MailToList() {
+
+			public Node getNode() {
+				return NodeImpl.this;
+			}
+
+			public boolean isPending() {
+				return whenSent != null;
+			}
+
+			public void clearPending() {
+				NodeImpl.this.clearPending();
+			}
+
+			public Date getWhenSent() {
+				return whenSent;
+			}
+
+			public String getOrGenerateMessageID() {
+				return NodeImpl.this.getOrGenerateMessageID();
+			}
+
+		};
+	}
+
+
+	public MailFromList getMailFromList() {
+		//  MailFromList is used to work with all messages from mailing lists, both local and external
+		return (getKind() == Kind.POST && getAssociatedMailingListImpl() != null) ? this : null;
+	}
+
+	public MailingList newMailingList(ListServer listServer,String listAddress,String url) throws ModelException {
+		if( getKind() != Kind.APP )
+			throw new UnsupportedOperationException();
+		if( !ModelHome.insideImportProcedure.get() )
+			DailyNumber.forumsStarted.dec();
+        setMailingList(new MailingListImpl(this, listServer, listAddress, url));
+        DbUtils.uncache(this);
+        return mailingList;
+	}
+
+	private void setRawParentMessageId(String parentMessageId) {
+		record.fields().put("parent_message_id", DbNull.fix(parentMessageId));
+	}
+
+	static NodeImpl[] getFromParentID(String parentID, MailingListImpl mailingList) {
+		try {
+			SiteKey siteKey = mailingList.siteKey;
+			Connection con = siteKey.getDb().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+					"select *"
+					+" from node"
+					+" where lower(parent_message_id)=?"
+			);
+			stmt.setString(1,parentID.toLowerCase());
+			ResultSet rs = stmt.executeQuery();
+			try {
+				List<NodeImpl> list = new ArrayList<NodeImpl>();
+				while( rs.next() ) {
+					NodeImpl post = getNode(siteKey,rs);
+					if (mailingList.equals(post.getAssociatedMailingListImpl()))
+						list.add( post );
+				}
+				return list.toArray(new NodeImpl[0]);
+			} finally {
+				rs.close();
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	NodeImpl getNodeImplFromMessageID(String messageID) {
+		if( messageID == null )
+			return null;
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+					"select * from node"
+					+" where lower(message_id)=? and message_id is not null"
+			);
+			stmt.setString(1,messageID.toLowerCase());
+			ResultSet rs = stmt.executeQuery();
+			try {
+				while (rs.next()) {
+					NodeImpl post = getNode(rs);
+					if( post.getAncestors().contains(this) )
+						return post;
+				}
+				return null;
+			} finally {
+				rs.close();
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+
+
+
+
+	private MailingListImpl mailingList;
+    private boolean mailingListSet = false;
+
+    private void setMailingList(MailingListImpl mailingList) {
+        this.mailingList = mailingList;
+        mailingListSet = true;
+    }
+
+	MailingListImpl getMailingListImpl() {
+		if( getKind() != Kind.APP )
+			return null;
+        if (!mailingListSet || (mailingList != null && DbUtils.isStale(mailingList))) {
+            setMailingList(MailingListImpl.getMailingListForForum(this));
+        }
+        return mailingList;
+    }
+
+	public MailingList getMailingList() {
+		return getMailingListImpl();
+	}
+
+	public void deleteMailingList() {
+		MailingList mailingList = getMailingList();
+		if (mailingList != null) {
+			mailingList.delete();
+			this.mailingList = null;
+			this.mailingListSet = false;
+			DbUtils.uncache(this);
+		}
+	}
+
+
+
+
+	// export/import
+
+	NodeImpl(SiteImpl site,NodeData data)
+		throws ModelException
+	{
+		this(
+			site.siteKey,
+			Kind.valueOf(data.kind),
+			data.ownerAnonymousId == null ?
+				site.getOrCreateUser(data.ownerEmail,data.ownerName) :
+				new Anonymous(site, data.ownerAnonymousId, data.ownerName),
+			data.subject,
+			data.message,
+			Message.Format.getMessageFormat(data.msgFmt)
+		);
+		if( data.parentId != null ) {
+			setParent( getNode(data.parentId) );
+		}
+		setWhenCreated( data.whenCreated );
+		if( data.whenUpdated != null )
+			setWhenUpdated( data.whenUpdated );
+		setType( data.type );
+		if( data.pin != null )
+			record.fields().put( "pin", data.pin );
+		if( data.messageID != null )
+			setMessageID( data.messageID );
+		setGuessedParent( data.isGuessedParent );
+		insert(false);
+
+		if( data.mlAddress != null ) {
+			ListServer listServer = ListServer.getServer( data.mlServer );
+			MailingList mailingList = newMailingList( listServer, data.mlAddress, data.mlUrl );
+			mailingList.setPlainTextOnly( data.mlPlainTextOnly );
+			mailingList.setIgnoreNoArchive( data.mlIgnoreNoArchive );
+			mailingList.setListName( data.mlListName );
+			mailingList.update();
+			update();
+		}
+
+		for( URL url : data.fileUrls ) {
+			try {
+				FileUpload.uploadFile( new FileUpload.UrlFileItem(url), this );
+			} catch(ModelException e) {}
+		}
+
+		// Only for backup recovery
+		if (data.files != null && data.files.size() > 0) {
+			Set<Map.Entry<String, byte[]>> entries = data.files.entrySet();
+			for (Map.Entry<String, byte[]> entry : entries) {
+				FileUpload.saveFile(entry.getValue(), entry.getKey(), this);
+			}
+		}
+
+		for( ExtensionFactory<Node,?> factory : extensionFactories ) {
+			Serializable obj = data.extensionData.get(factory.getName());
+			if( obj != null )
+				factory.saveExportData(this,obj);
+		}
+	}
+
+	public NodeData getData() {
+		NodeData data = new NodeData();
+
+		data.exportId = getId();
+		data.kind = kind.toString();
+		Person owner = getOwner();
+		if (owner instanceof Anonymous)
+			data.ownerAnonymousId = ((Anonymous)owner).getCookie();
+		else
+			data.ownerEmail = ((User) owner).getEmail();
+		data.ownerName = owner.getName();
+		data.subject = subject;
+		data.message = getMessage().getRaw();
+		data.msgFmt = getMessage().getFormat().getCode();
+		data.whenCreated = whenCreated;
+		data.whenUpdated = whenUpdated;
+		data.type = type;
+		data.messageID = messageID;
+		data.isGuessedParent = isGuessedParent;
+
+		MailingListImpl mailingList = getMailingListImpl();
+		if( mailingList != null ) {
+			data.mlAddress = mailingList.getListAddress();
+			data.mlUrl = mailingList.getUrl();
+			if( data.mlUrl==null )
+				data.mlUrl = "http://localhost";
+			data.mlPlainTextOnly = mailingList.plainTextOnly();
+			data.mlIgnoreNoArchive = mailingList.ignoreNoArchive();
+			data.mlServer = mailingList.getListServer().getType();
+			data.mlListName = mailingList.getListName();
+		}
+
+		List<URL> urls = new ArrayList<URL>();
+		Message.Format fmt = message.getFormat();
+		if( !(fmt instanceof MailMessageFormat) ) {
+			Html list = message.parse();
+			Map<String,String> files = FileUpload.getFileInfo(list,this);
+			for( String url : files.values() ) {
+				try {
+					urls.add( new URL(url) );
+				} catch(MalformedURLException e) {
+					throw new RuntimeException(e);
+				}
+			}
+		}
+		data.fileUrls = urls.toArray(new URL[0]);
+
+		for( ExtensionFactory<Node,?> factory : extensionFactories ) {
+			Serializable obj = factory.getExportData(this);
+			if( obj != null )
+				data.extensionData.put(factory.getName(),obj);
+		}
+
+		return data;
+	}
+
+
+
+
+
+
+
+
+	private static final String EXPORT_TASK = "export";
+
+	private static class LazyExports {
+		static {
+			try {
+				for( SiteKey siteKey : SiteKey.getSiteKeys(EXPORT_TASK) ) {
+					Connection con = siteKey.getDb().getConnection();
+					try {
+						Statement stmt = con.createStatement();
+						ResultSet rs = stmt.executeQuery(
+							"select * from node"
+							+" where export_permalink is not null"
+						);
+						while( rs.next() ) {
+							// Put the task back because the export hasn't finished yet and
+							// it should keep trying until everything has moved. Since the node
+							// is deleted at the end of the export, the SQL above will not find any
+							// node to be migrated when the export finishes. So this task loop will
+							// finally come to an end.
+							siteKey.site().addTask(EXPORT_TASK);
+
+							NodeImpl node = getNode(siteKey,rs);
+							// Logs node/site ids because we may need this in the shell.
+							logger.error("Export restarted for node=" + node.getId() + " [site=" + node.getSite().getId() + ']');
+							// Continue exporting...
+							node.doExport();
+						}
+						rs.close();
+						stmt.close();
+					} finally {
+						con.close();
+					}
+				}
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		static void start() {}
+	}
+
+	static {
+		Executors.schedule(
+			new Runnable(){public void run(){
+				LazyExports.start();
+			}}, 200, TimeUnit.SECONDS
+		);
+	}
+
+	public void export(String permalink,String email) {
+		LazyExports.start();
+		setExport(permalink,email);
+		doExport();
+	}
+
+	private void setExport(String permalink,String email) {
+		if( exportPermalink != null )
+			throw new RuntimeException("already set");
+		this.exportPermalink = permalink;
+		getDbRecord().fields().put("export_permalink",exportPermalink);
+		this.exportEmail = email;
+		getDbRecord().fields().put("export_email",exportEmail);
+		getDbRecord().update();
+		getSiteImpl().addTask(EXPORT_TASK);
+	}
+
+	/* to be called from luan shell */
+	public void clearExport() {
+		if( exportPermalink == null )
+			throw new RuntimeException("already cleared");
+		this.exportPermalink = null;
+		getDbRecord().fields().put("export_permalink",DbNull.STRING);
+		this.exportEmail = null;
+		getDbRecord().fields().put("export_email",DbNull.STRING);
+		getDbRecord().update();
+	}
+
+	/* to be called from luan shell */
+	public static void clearExportedNodeIds(Node node) {
+		if (node.getExportedNodeId() > 0) {
+			node.getDbRecord().fields().put( "exported_node_id", DbNull.INTEGER );
+			node.getDbRecord().update();
+			for (Node child : node.getChildren()) {
+				clearExportedNodeIds(child);
+			}
+		}
+	}
+
+	private void doExport() {
+		final Export export;
+		try {
+			export = new Export(this, exportPermalink, exportEmail);
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+		Executors.executeNow(new Runnable(){public void run(){
+			export.run();
+			//clearExport();
+		}});
+	}
+
+
+
+
+	private Map<ExtensionFactory<Node,?>,Object> extensionMap;
+
+	public synchronized Map<ExtensionFactory<Node, ?>, Object> getExtensionMap() {
+		if (extensionMap == null)
+			extensionMap = new HashMap<ExtensionFactory<Node, ?>, Object>();
+		return extensionMap;
+	}
+
+	public <T> T getExtension(ExtensionFactory<Node,T> factory) {
+		synchronized(getExtensionMap()) {
+			Object obj = extensionMap.get(factory);
+			if( obj == null ) {
+				obj = factory.construct(this);
+				if( obj != null )
+					extensionMap.put(factory,obj);
+			}
+			return factory.extensionClass().cast(obj);
+		}
+	}
+
+	private static Collection<ExtensionFactory<Node,?>> extensionFactories = new CopyOnWriteArrayList<ExtensionFactory<Node,?>>();
+
+	static <T> void addExtensionFactory(ExtensionFactory<Node,T> factory) {
+		extensionFactories.add(factory);
+		Db.clearCache();
+	}
+
+
+
+	private final Memoizer<String,String> propertyCache = new Memoizer<String,String>(new Computable<String,String>() {
+		public String get(String key) {
+			try {
+				Connection con = db().getConnection();
+				PreparedStatement stmt = con.prepareStatement(
+					"select value from node_property where node_id = ? and key = ?"
+				);
+				stmt.setLong( 1, getId() );
+				stmt.setString( 2, key );
+				ResultSet rs = stmt.executeQuery();
+				try {
+					return rs.next() ? rs.getString("value") : null;
+				} finally {
+					rs.close();
+					stmt.close();
+					con.close();
+				}
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+		}
+	});
+
+	public String getProperty(String key) {
+		return propertyCache.get(key);
+	}
+
+	public void setProperty(String key,String value) {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"delete from node_property where node_id = ? and key = ?"
+			);
+			stmt.setLong( 1, getId() );
+			stmt.setString( 2, key );
+			stmt.executeUpdate();
+			stmt.close();
+			if( value != null ) {
+				stmt = con.prepareStatement(
+					"insert into node_property (node_id,key,value) values (?,?,?)"
+				);
+				stmt.setLong( 1, getId() );
+				stmt.setString( 2, key );
+				stmt.setString( 3, value );
+				stmt.executeUpdate();
+				stmt.close();
+			}
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		} finally {
+			propertyCache.remove(key);
+		}
+	}
+
+
+	final Memoizer<String,Boolean> tagCache = new Memoizer<String,Boolean>(new Computable<String,Boolean>() {
+		public Boolean get(String sqlCondition) {
+			return TagImpl.countTags(siteKey,sqlCondition) > 0;
+		}
+	});
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/NodeIterator.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,134 @@
+package nabble.model;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collection;
+
+
+public abstract class NodeIterator<C extends Node> implements Iterator<C>, Iterable<C> {
+	public abstract boolean hasNext();
+	public abstract C next() throws NoSuchElementException;
+	public abstract void close();
+
+	public final void remove() {
+		throw new UnsupportedOperationException();
+	}
+
+	public final Iterator<C> iterator() {
+		return this;
+	}
+
+	public void skip(int n) {
+		while( hasNext() && n-- > 0 ) {
+			next();
+		}
+	}
+
+	// calls close()
+	public final boolean isEmpty() {
+		try {
+			return !hasNext();
+		} finally {
+			close();
+		}
+	}
+
+	public final boolean contains(Node node) {
+		try {
+			while( hasNext() ) {
+				if( next().equals(node) )
+					return true;
+			}
+			return false;
+		} finally {
+			close();
+		}
+	}
+
+	public final void addTo(Collection<Node> col) {
+		while( hasNext() ) {
+			col.add( next() );
+		}
+		close();
+	}
+
+	public List<Node> get(int i,int n) {
+		List<Node> list = new ArrayList<Node>();
+		skip(i);
+		while( hasNext() && n-- > 0 ) {
+			list.add( next() );
+		}
+		close();
+		return list;
+	}
+
+	public List<C> asList() {
+		List<C> list = new ArrayList<C>();
+		while( hasNext()) {
+			list.add( next() );
+		}
+		return list;
+	}
+
+	public static final <T extends Node> NodeIterator<T> empty() {
+		return new NodeIterator<T>() {
+	
+			@Override public boolean hasNext() {
+				return false;
+			}
+	
+			@Override public T next() throws NoSuchElementException {
+				throw new NoSuchElementException();
+			}
+	
+			@Override public void close() {}
+	
+		};
+	}
+
+	public static <T extends Node> NodeIterator<T> nodeIterator(final Iterator<T> iter) {
+		return new NodeIterator<T>() {
+
+			@Override public boolean hasNext() {
+				return iter.hasNext();
+			}
+
+			@Override public T next() throws NoSuchElementException {
+				return iter.next();
+			}
+
+			@Override public void close() {}
+
+		};
+	}
+
+	public static <T extends Node> NodeIterator<T> nodeIterator(final List<T> list) {
+		return new NodeIterator<T>() {
+			private Iterator<T> iter = null;
+
+			private Iterator<T> iter() {
+				if( iter == null )
+					iter = list.iterator();
+				return iter;
+			}
+
+			@Override public boolean hasNext() {
+				return iter().hasNext();
+			}
+
+			@Override public T next() throws NoSuchElementException {
+				return iter().next();
+			}
+
+			@Override public void close() {}
+
+			@Override public List<T> asList() {
+				return iter==null ? list : super.asList();
+			}
+
+		};
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/NodeSearcher.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,423 @@
+package nabble.model;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.Token;
+import org.apache.lucene.analysis.TokenFilter;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.snowball.SnowballAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.NumberTools;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.queryParser.MultiFieldQueryParser;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.CachingWrapperFilter;
+import org.apache.lucene.search.ConstantScoreQuery;
+import org.apache.lucene.search.Filter;
+import nabble.model.lucene.HitCollector;
+import nabble.model.lucene.LuceneSearcher;
+import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.QueryWrapperFilter;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.highlight.Formatter;
+import org.apache.lucene.search.highlight.Highlighter;
+import org.apache.lucene.search.highlight.NullFragmenter;
+import org.apache.lucene.search.highlight.QueryScorer;
+import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
+import org.apache.lucene.search.highlight.SimpleSpanFragmenter;
+import org.apache.lucene.search.highlight.TokenGroup;
+import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;
+import org.apache.lucene.util.Version;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+
+public final class NodeSearcher {
+	private static final Logger logger = LoggerFactory.getLogger(NodeSearcher.class);
+
+	public static final Sort SORT_BY_DATE = new Sort(new SortField(Lucene.DATE_FLD, SortField.INT));
+
+	public static class Builder {
+		private static final String[] nodeSearchFields = new String[]{
+			Lucene.SUBJECT_FLD, Lucene.MESSAGE_FLD, Lucene.AUTHOR_FLD, Lucene.MAILING_LIST_FLD
+		};
+
+		private final SiteImpl site;
+		private final BooleanQuery query = new BooleanQuery();
+		private Query textQuery = null;
+		private boolean isAuthenticated = false;
+		private final long nodeId;
+		private User currentUser;
+		private String userSearchId = null;
+		private Sort sort = null;
+		private Filter filter = null;
+		private Date from = null;
+		private Date to = null;
+
+		public Builder(Node node) {
+			this(node.getSite(),node.getId());
+		}
+	
+		public Builder(Site site,long nodeId) {
+			if( nodeId == 0L )
+				throw new RuntimeException();
+			this.site = (SiteImpl)site;
+			this.nodeId = nodeId;
+			Query query2 = new TermQuery(new Term(Lucene.ANCESTORS_FLD,Long.toString(nodeId)));
+			query.add(query2,BooleanClause.Occur.MUST);
+		}
+
+		public void setCurrentUser(User user) {
+			this.isAuthenticated = true;
+			this.currentUser = user;
+		}
+
+		private BooleanQuery getQuery() {
+			if( !isAuthenticated )
+				return query;
+			if( currentUser!=null && currentUser.getSearchId().equals(userSearchId) )
+				return query;
+			BooleanQuery q = new BooleanQuery();
+			q.add(query, BooleanClause.Occur.MUST);
+			if( currentUser != null ) {
+				NodeImpl node = NodeImpl.getNode(site.siteKey,nodeId);
+				q.add(new TermQuery(new Term(Lucene.PRIVATE_NODE_FLD, Lucene.formatPrivateNode(node))), BooleanClause.Occur.MUST);
+				return q;
+			}
+			q.add(publicQuery, BooleanClause.Occur.MUST);
+			return q;
+		}
+
+		public void addQuery(Query query2) {
+			query.add(query2,BooleanClause.Occur.MUST);
+		}
+		
+		public void addLine(String line) throws ParseException {
+			if( textQuery != null )
+				throw new RuntimeException();
+			textQuery = parse(line,nodeSearchFields);
+			if( textQuery != null )
+				query.add(textQuery,BooleanClause.Occur.MUST);
+		}
+
+		public void addUser(Person user) {
+			if( user==null )
+				return;
+			addUser(user.getSearchId());
+		}
+	
+		public void addUser(String userSearchId) {
+			this.userSearchId = userSearchId;
+			Query query2 = new TermQuery(new Term(Lucene.USER_ID_FLD,userSearchId));
+			query.add(query2,BooleanClause.Occur.MUST);
+		}
+
+		public void addUsers(List<? extends Person> visitors) {
+			if (visitors != null && visitors.size() > 0) {
+				BooleanQuery usersClause = new BooleanQuery();
+				for (Person v : visitors) {
+					Query q = new TermQuery(new Term(Lucene.USER_ID_FLD,v.getSearchId()));
+					usersClause.add(q, BooleanClause.Occur.SHOULD);
+				}
+				query.add(usersClause, BooleanClause.Occur.MUST);
+			}
+		}
+	
+		void addExcludeUser(String userSearchId) {
+			BooleanClause excludeUserClause = new BooleanClause(
+					new TermQuery(new Term(Lucene.USER_ID_FLD, userSearchId)),
+					BooleanClause.Occur.MUST_NOT);
+			query.add(excludeUserClause);
+		}
+
+		public void setUserSearchId(String userSearchId) {
+			this.userSearchId = userSearchId;
+		}
+
+		private final static Query appQuery =
+			new ConstantScoreQuery(
+				new CachingWrapperFilter(
+					new QueryWrapperFilter(
+						new TermQuery(new Term(Lucene.KIND_FLD,Node.Kind.APP.toString()))
+					)
+				)
+			)
+		;
+		
+		public void addNodeKind(Node.Kind kind) {
+			query.add(appQuery,
+					kind==Node.Kind.APP?BooleanClause.Occur.MUST:BooleanClause.Occur.MUST_NOT);
+		}
+	
+		private final static Query publicQuery =
+			new ConstantScoreQuery(
+				new CachingWrapperFilter(
+					new QueryWrapperFilter(
+						new TermQuery(new Term(Lucene.PRIVATE_NODE_FLD,"none"))
+					)
+				)
+			)
+		;
+	
+		public void excludePrivate() {
+			query.add(publicQuery,BooleanClause.Occur.MUST);
+		}
+
+		public void setSort(Sort sort) {
+			this.sort = sort;
+		}
+	
+		public void setFilter(Filter filter) {
+			this.filter = filter;
+		}
+	
+		public void setDateRange(Date from, Date to) {
+			if( sort != SORT_BY_DATE )
+				throw new UnsupportedOperationException();
+			this.from = from;
+			this.to = to;
+		}
+
+		public NodeSearcher build() {
+			return new NodeSearcher(this);
+		}
+	}
+
+	private final SiteImpl site;
+	private final BooleanQuery query;
+	private final Query textQuery;
+	private final Sort sort;
+	private final Filter filter;
+	private final Date from;
+	private final Date to;
+	private Set<String> searchTerms = null;
+	private int totalHits = -1;
+	private final QueryScorer scorer;
+
+	private NodeSearcher(Builder builder) {
+		this.site = builder.site;
+		this.query = builder.getQuery();
+		this.textQuery = builder.textQuery;
+		this.sort = builder.sort;
+		this.filter = builder.filter;
+		this.from = builder.from;
+		this.to = builder.to;
+		this.scorer = new QueryScorer(query);
+	}
+
+	public BooleanQuery getQuery() {
+		return query;
+	}
+	
+	static Query parse(String line, String[] fields) throws ParseException {
+		if( line == null || line.length() == 0 )
+			return null;
+		line = line.replace('[','|').replace(']','|'); // hack - treat [] as punctuation
+		MultiFieldQueryParser parser = new MultiFieldQueryParser(Version.LUCENE_CURRENT,fields, Lucene.analyzer);
+		parser.setDefaultOperator(QueryParser.AND_OPERATOR);
+		return parser.parse(line);
+	}
+	
+	public String toString() {
+		return query.toString();
+	}
+
+	public Set<String> getSearchTerms() {
+		if( searchTerms==null ) {
+			searchTerms = new HashSet<String>();
+			if( textQuery != null )
+				searchTerms(searchTerms,textQuery);
+		}
+		return searchTerms;
+	}
+
+	private static void searchTerms(Set<String> searchTerms,Query query) {
+		if( query instanceof BooleanQuery ) {
+			BooleanQuery q = (BooleanQuery)query;
+			BooleanClause[] clauses = q.getClauses();
+			for (BooleanClause clause : clauses) {
+				if (!clause.isProhibited())
+					searchTerms(searchTerms, clause.getQuery());
+			}
+		} else if( query instanceof TermQuery ) {
+			TermQuery q = (TermQuery)query;
+			searchTerms.add( q.getTerm().text() );
+		} else if( query instanceof PhraseQuery ) {
+			PhraseQuery q = (PhraseQuery)query;
+			Term[] terms = q.getTerms();
+			for (Term term : terms) {
+				searchTerms.add(term.text());
+			}
+		} 
+	}
+
+	public String highlight(String text,String pre,String post) {
+		try {
+			Highlighter hl = new Highlighter( new SimpleHTMLFormatter(pre,post), scorer );
+			hl.setTextFragmenter( new NullFragmenter() );
+			String s = hl.getBestFragment(Lucene.analyzer,null,text);
+			return s != null ? s : text;
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		} catch(InvalidTokenOffsetsException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static String getStartingFragment(String text,int size,String dotdotdot) {
+		if (text.length() <= size) return text;
+		int end = text.lastIndexOf(' ', size);
+		if (end < 0) end = size;
+		String fragment = text.substring(0, end);
+		if (dotdotdot != null && fragment.length() < text.length()) 
+			fragment = fragment + dotdotdot;
+		return fragment;
+	}
+
+	private static final Formatter nullFormatter = new Formatter() {
+		public String highlightTerm(String originalText,TokenGroup tokenGroup) {
+			return originalText;
+		}
+	};
+
+	public String getFragment(String text,int size,String dotdotdot) {
+		try {
+			Highlighter hl = new Highlighter(nullFormatter,scorer);
+			hl.setTextFragmenter( new SimpleSpanFragmenter(scorer,size) );
+			String s = hl.getBestFragment(Lucene.analyzer,null,text);
+			if( s == null )
+				s = getStartingFragment(text,size,dotdotdot);
+			if( dotdotdot != null && s.length() < text.length() ) {
+				boolean atStart = text.startsWith(s);
+				boolean atEnd = text.endsWith(s);
+				if( !atStart )
+					s = dotdotdot + s;
+				if( !atEnd )
+					s = s + dotdotdot;
+			}
+			return s;
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		} catch(InvalidTokenOffsetsException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static class DoneException extends RuntimeException {}
+
+	public boolean hasNodes() {
+		try {
+			LuceneSearcher searcher = Lucene.newSearcher(site);
+			try {
+				try {
+					searcher.search( query, new HitCollector() {
+						protected void process(Document doc) {
+							throw new DoneException();
+						}
+					} );
+					return false;
+				} catch(DoneException e) {
+					return true;
+				}
+			} finally {
+				searcher.close();
+			}
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public interface Handler {
+		public void handle(long nodeId);
+	}
+
+	public void forEach(final Handler h) {
+		try {
+			final LuceneSearcher searcher = Lucene.newSearcher(site);
+			try {
+				searcher.search( query, new HitCollector() {
+					protected void process(Document doc) {
+						h.handle( Lucene.getNodeId(doc) );
+					}
+				} );
+			} finally {
+				searcher.close();
+			}
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public int getTotalHits() {
+		if( totalHits == -1 ) {
+			try {
+				LuceneSearcher searcher = Lucene.newSearcher(site);
+				try {
+					TopDocs hits = searcher.search(query, filter, 0);
+					totalHits = hits.totalHits;
+				} finally {
+					searcher.close();
+				}
+			} catch (BooleanQuery.TooManyClauses e) {
+				throw new RuntimeException("Your search will give too many matches.");
+			} catch(IOException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		return totalHits;
+	}
+
+	public List<Node> getNodes(int i, int n) throws TooManyClauses {
+		try {
+			LuceneSearcher searcher = Lucene.newSearcher(site);
+			try {
+				TopDocs hits = sort==null ? searcher.search(query,filter,i+n) : searcher.search(query,filter,i+n,sort);
+				totalHits = hits.totalHits;
+				int lim = hits.scoreDocs.length;
+				if( lim <= i )
+					return Collections.emptyList();
+				List<Node> a = new ArrayList<Node>();
+				for (int j=i; j<lim; j++) {
+					try {
+						int docId = hits.scoreDocs[j].doc;
+						Node node = Lucene.getNode(site, searcher, docId);
+						if (node != null) {
+							a.add(node);
+						}
+					} catch(IOException e) {
+						logger.error(e.toString());
+					}
+				}
+				return a;
+			} finally {
+				searcher.close();
+			}
+		} catch (BooleanQuery.TooManyClauses e) {
+			throw new TooManyClauses(e);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static final class TooManyClauses extends RuntimeException {
+		TooManyClauses(BooleanQuery.TooManyClauses e) {
+			super(e);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Person.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,16 @@
+package nabble.model;
+
+
+public interface Person {
+	public Site getSite();
+	public String getName();
+	public void setName(String name) throws ModelException;
+	public String getNameHtml();
+	public Node newChildNode(Node.Kind kind,String subject,String message,Message.Format msgFmt,Node parent) throws ModelException;
+	public String getSearchId();
+	public String getIdString();
+	public NodeIterator<? extends Node> getNodesByDateDesc(String cnd);
+	public int getNodeCount(String cnd);
+	public int deleteNodes();  // returns count of node removed
+	public Message getSignature();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/PersonImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,12 @@
+package nabble.model;
+
+import fschmidt.util.java.HtmlUtils;
+
+
+abstract class PersonImpl implements Person {
+
+	public final String getNameHtml() {
+		return HtmlUtils.htmlEncode(getName());
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/PostByEmail.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,431 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.MailIterator;
+import fschmidt.util.mail.PlainTextContent;
+import fschmidt.util.mail.Pop3Server;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Permissions;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.NodeNamespace;
+import nabble.view.web.template.UserNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+@Namespace (
+	name = "post_by_email",
+	global = true
+)
+public final class PostByEmail {
+	private static final Logger logger = LoggerFactory.getLogger(PostByEmail.class);
+
+	// I would like to get rid of this.
+	static final MailMessageFormat msgFmt = new MailMessageFormat('s', "subscription");
+
+	private static final Pop3Server pop3Server = (Pop3Server)Init.get("subscriptionsPop3Server");
+
+	static {
+		if (Init.hasDaemons) {
+			runSubscriptions();
+		}
+	}
+
+	private static class Lazy {
+		static final String emailPrefix;
+		static final String emailSuffix;
+		static final Pattern pattern;
+		static {
+			String addrSpec = pop3Server.getUsername();
+			int ind = addrSpec.indexOf('@');
+			emailPrefix = addrSpec.substring(0, ind) + "+";
+			emailSuffix = addrSpec.substring(ind);
+			pattern = Pattern.compile(
+					"\\+s(\\d+)n(\\d+)h(\\d+)" + Pattern.quote(emailSuffix),
+					Pattern.CASE_INSENSITIVE);
+		}
+	}
+
+	private static void runSubscriptions() {
+		if (pop3Server == null) {
+			logger.error("Subscriptions: no pop3 specified for subscriptions");
+			return;
+		}
+		Executors.scheduleWithFixedDelay(new Runnable() {
+
+			public void run() {
+				try {
+					processSubscriptions();
+					processBounces();
+				} catch(MailException e) {
+					logger.error("mail processing",e);
+				}
+			}
+
+		}, 10, 10, TimeUnit.SECONDS );
+		logger.info("Subscriptions: pop3 reading thread started");
+	}
+
+	private static void processSubscriptions() {
+		MailIterator mails = pop3Server.getMail();
+		try {
+			while (mails.hasNext()) {
+				Mail mail = mails.next();
+				try {
+					new PostByEmail(mail).processMessage();
+				} catch (Exception e) {
+					logger.error("mail:\n"+mail.getRawInput(),e);
+				}
+			}
+		} finally {
+			mails.close();
+		}
+	}
+
+
+	// begin non-static part
+
+	private final Mail mail;
+	private String email;
+	private String messageId;
+	private String address;
+	private NodeImpl repliedToNode;
+	private NodeImpl postedNode;
+	private UserImpl mailAuthor;
+
+	private PostByEmail(Mail mail) {
+		this.mail = mail;
+	}
+
+	private void processMessage() {
+		if( MailSubsystem.getReturnPath(mail).equals("") ) {
+			logger.info("ignoring bounce");
+			return;
+		}
+
+		email = mail.getFrom().getAddrSpec();
+
+		String[] messageIds = mail.getHeader("Message-Id"); // returns both Id and ID
+		messageId = messageIds!=null && messageIds.length==1 ? MailSubsystem.stripBrackets(messageIds[0]) : null;
+
+		String[] a = mail.getHeader("Envelope-To");
+		if (a == null)
+			a = mail.getHeader("X-Original-To"); // postfix
+		if (a == null)
+			a = mail.getHeader("X-Delivered-to"); // fastmail
+		if (a.length > 1)
+			a = new String[] { a[0] };
+		for( String s : a[0].split(",") ) {
+			address = s.trim();
+			Matcher matcher = Lazy.pattern.matcher(address);
+			if( matcher.find() ) {
+				long siteId = Long.valueOf(matcher.group(1));
+				SiteImpl site = SiteKey.getInstance(siteId).site();
+				if( site != null ) {
+					long nodeId = Long.valueOf(matcher.group(2));
+					repliedToNode = site.getNodeImpl(nodeId);
+					if( repliedToNode != null ) {
+						if( repliedToNode.getAssociatedMailingList() != null) {
+							sendFailureMail( "Nabble", failureMessagePrefix() + "You can't post by email to a mailing list archive." );
+							continue;
+						}
+						mailAuthor = site.getUserImplFromEmail(email);
+						if( mailAuthor != null && !generateHash(mailAuthor,repliedToNode).equals(matcher.group(3)) )
+							mailAuthor = null;
+						callNaml();
+						continue;
+					}
+				}
+			}
+//System.out.println("qqqqqqqqqqqqqqqqqqqqqqqqqqqqqq "+address);
+			sendFailureMail( "Nabble", failureMessagePrefix() + "No forum exists for this address." );
+		}
+	}
+
+	private void callNaml() {
+		Site site = repliedToNode.getSite();
+		Template template = site.getTemplate( "post by email",
+			BasicNamespace.class, NabbleNamespace.class, PostByEmail.class
+		);
+		template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(),
+			new BasicNamespace(template), new NabbleNamespace(site), this
+		);
+	}
+
+	private String failureMessagePrefix() {
+		return
+			"Delivery to the following recipient failed permanently:\n\n"
+			+ "    " + address + "\n\n"
+		;
+	}
+
+	private void sendFailureMail(String fromName,String failureMessage) {
+/* why?
+		MailSubsystem.bounce(mail,
+			"Delivery to the following recipient failed permanently:\n\n    "
+			+ address + "\n\n"
+			+ failureMessage + "\n"
+		);
+*/
+		Mail bounce = MailHome.newMail();
+		bounce.setFrom( new MailAddress(ModelHome.noReply,fromName) );
+		bounce.setTo( new MailAddress(email) );
+		bounce.setSubject( "Delivery Status Notification (Failure)" );
+		bounce.setHeader( "X-Failed-Recipients", mail.getHeader("Envelope-To") );
+		StringBuilder content = new StringBuilder();
+		content
+			.append( failureMessage ).append( "\n\n" )
+			.append( "----- Original message -----\n\n" )
+			.append( mail.getRawInput() )
+		;
+		bounce.setContent(new PlainTextContent(content.toString()));
+		ModelHome.send(bounce);
+		logger.warn("bouncing subscription mail for "+email);
+	}
+
+	private UserImpl mailAuthor() throws TemplateException {
+		if( mailAuthor==null )
+			throw TemplateException.newInstance("subscription_processing_bad_user");
+		return mailAuthor;
+	}
+
+	private void saveToPost() throws TemplateException {
+		logger.info("Processing email from: " + address);
+
+		Date date = mail.getSentDate();
+		Date now = new Date();
+		if (date == null || date.compareTo(now) > 0 || date.getTime() < 0) {
+			date = now;
+		}
+
+		String subject = mail.getSubject();
+		if (subject == null || subject.trim().length() == 0)
+			subject = "(no subject)";
+
+		String message = mail.getRawInput();
+		message = message.replace("\000","");  // postgres can't handle 0
+		if( !msgFmt.isOk(message) )
+			throw TemplateException.newInstance("bad_mail");
+		UserImpl mailAuthor = mailAuthor();
+
+		logger.info("Making a post from a message from " + address);
+		DbDatabase db = repliedToNode.siteKey.getDb();
+		db.beginTransaction();
+		try {
+			postedNode = NodeImpl.newChildNode(Node.Kind.POST, mailAuthor, subject, message, msgFmt, repliedToNode);
+			postedNode.setWhenCreated(date);
+			if( messageId != null )
+				postedNode.setMessageID(messageId);
+
+			postedNode.checkNewPostLimit();
+
+			postedNode.insert(true);
+
+			db.commitTransaction();
+		} catch(ModelException e) {
+			logger.error("Subscription processing failed: " + mail.getRawInput(), e);
+		} finally {
+			db.endTransaction();
+		}
+	}
+
+	// naml
+
+	public static final CommandSpec thread_by_subject = new CommandSpec.Builder()
+		.parameters("prefixes")
+		.build()
+	;
+
+	@Command public void thread_by_subject(IPrintWriter out,Interpreter interp) {
+		if( repliedToNode.getKind() == Node.Kind.APP )
+			return;
+		String subject = mail.getSubject();
+		if (subject == null)
+			return;
+		String prefixes = interp.getArgString("prefixes");
+		Pattern prefixRegex = MailingLists.prefixRegex(prefixes);
+		if( MailingLists.normalizeSubject(subject,prefixRegex).equals(MailingLists.normalizeSubject(repliedToNode.getSubject(),prefixRegex)) )
+			return;
+		NodeImpl app = repliedToNode.getAppImpl();
+		if( app != null )
+			repliedToNode = app;
+	}
+
+	@Command public void new_post_subject(IPrintWriter out,Interpreter interp) {
+		out.print(mail.getSubject());
+	}
+
+	public static final CommandSpec set_new_post_subject = new CommandSpec.Builder()
+		.dotParameter("subject")
+		.build()
+	;
+
+	@Command public void set_new_post_subject(IPrintWriter out,Interpreter interp) {
+		String newSubject = interp.getArgString("subject");
+		mail.setSubject(newSubject);
+	}
+
+
+	public static final CommandSpec save_to_post = CommandSpec.NO_OUTPUT;
+
+	@Command public void save_to_post(IPrintWriter out,Interpreter interp) throws TemplateException {
+		saveToPost();
+	}
+
+	public static final CommandSpec send_failure_mail = CommandSpec.NO_OUTPUT()
+		.dotParameter("text")
+		.optionalParameters("from")
+		.build()
+	;
+
+	@Command public void send_failure_mail(IPrintWriter out,Interpreter interp) {
+		String from = interp.getArgString("from");
+		if( from == null )
+			from = "Nabble";
+		String msg = interp.getArgString("text");
+		sendFailureMail(from,msg);
+	}
+
+	@Command public void email_from(IPrintWriter out,Interpreter interp) throws TemplateException {
+		out.print( email );
+	}
+
+	@Command public void email_to(IPrintWriter out,Interpreter interp) throws TemplateException {
+		out.print( address );
+	}
+
+	public static final CommandSpec replied_to_node = CommandSpec.DO;
+
+	@Command public void replied_to_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		out.print( interp.getArg( new NodeNamespace(repliedToNode), "do" ) );
+	}
+
+	public static final CommandSpec posted_node = CommandSpec.DO;
+
+	@Command public void posted_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		out.print( interp.getArg( new NodeNamespace(postedNode), "do" ) );
+	}
+
+	public static final CommandSpec mail_author = CommandSpec.DO;
+
+	@Command public void mail_author(IPrintWriter out,ScopedInterpreter<UserNamespace> interp)
+		throws TemplateException
+	{
+		out.print( interp.getArg( new UserNamespace(mailAuthor()), "do" ) );
+	}
+
+	// end non-static part
+
+
+	static String getMailAddress(User user, Node node) {
+		Site site = user.getSite();
+		if( !node.getSite().equals(site) )
+			throw new RuntimeException();
+		return
+			Lazy.emailPrefix
+			+ 's' + site.getId()
+			+ 'n' + node.getId()
+			+ 'h' + generateHash(user,node)
+			+ Lazy.emailSuffix
+		;
+	}
+
+
+	private static String generateHash(User user,Node node) {
+		int h = user.hashCode();
+		h = 31*h + node.hashCode();
+		h = 31*h + 23;
+		return Integer.toString(Math.abs(h)%100);
+	}
+
+
+
+
+
+
+
+	private static final Pop3Server bouncesPop3Server = (Pop3Server)Init.get("subscriptionBouncesPop3Server");
+
+	private static class LazyBounces {
+		static final String emailPrefix;
+		static final String emailSuffix;
+		static final Pattern pattern;
+		static {
+			String addrSpec = bouncesPop3Server.getUsername();
+			int ind = addrSpec.indexOf('@');
+			emailPrefix = addrSpec.substring(0, ind) + "+";
+			emailSuffix = addrSpec.substring(ind);
+			pattern = Pattern.compile(
+				"\\+s(\\d+)u(\\d+)" + Pattern.quote(emailSuffix)
+				, Pattern.CASE_INSENSITIVE
+			);
+		}
+	}
+
+	private static synchronized void processBounces() {
+		if( bouncesPop3Server == null ) {
+			logger.error("subscriptionBouncesPop3Server not defined");
+			System.exit(-1);
+		}
+		MailIterator mails = bouncesPop3Server.getMail();
+		try {
+			while( mails.hasNext() ) {
+				Mail mail = mails.next();
+				try {
+					processBounce(mail);
+				} catch (Exception e) {
+					logger.error("mail:\n"+mail.getRawInput(),e);
+				}
+			}
+		} finally {
+			mails.close();
+		}
+	}
+
+	private static void processBounce(Mail mail) {
+		String[] envTo = mail.getHeader("Envelope-To");
+		if (envTo == null)
+			envTo = mail.getHeader("X-Original-To"); // postfix
+		if (envTo == null)
+			envTo = mail.getHeader("X-Delivered-to"); // fastmail
+		String originalTo = envTo[0];
+		Matcher matcher = LazyBounces.pattern.matcher(originalTo);
+		if( !matcher.find() )
+			throw new RuntimeException("invalid email: "+originalTo);
+		long siteId = Long.parseLong( matcher.group(1) );
+		long userId = Long.parseLong( matcher.group(2) );
+		SiteImpl site = SiteKey.getInstance(siteId).site();
+		UserImpl user = site.getUserImpl(userId);
+		user.bounced();
+		logger.info(""+user+" has "+user.getBounces()+" bounces");
+	}
+
+	static String getBouncesAddress(User user) {
+		return
+			LazyBounces.emailPrefix
+			+ 's' + user.getSite().getId()
+			+ 'u' + user.getId()
+			+ LazyBounces.emailSuffix
+		;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Site.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,100 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbObject;
+import fschmidt.db.NoKey;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.CompileException;
+import nabble.model.export.NodeData;
+
+import java.io.File;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+
+public interface Site extends Message.Source, DbObject<NoKey,SiteImpl> {
+	public DbDatabase getDb();
+	public long getId();
+	public Node getRootNode();
+	public Date getWhenCreated();
+	public Program getProgram();
+	public Template getTemplate(String templateName,Class... base);
+	public void setCustomDomain(String customDomain);
+	public String getCustomDomain();
+	public String getBaseUrl();
+	public int getActivity();
+	public boolean isEmbarrassing();
+	public void setEmbarrassing(boolean isEmbarrassing);
+	public Date getDeleteDate();
+	public void clearDeleteDate();
+	public void deleteRootNode() throws ModelException;  // root must have one child
+	public void delete();
+
+	public User getUser(long id);
+	public User getUserFromEmail(String email);
+	public User getUserFromName(String name);
+	public User getOrCreateUnregisteredUser(String email,String name) throws ModelException;
+	public User getOrCreateUser(String email);
+	public User getOrCreateUser(String email,String name);  // for export/import
+	public String newRegistration(String email,String password,String name,String nextUrl) throws ModelException;
+	public User getRegistration(String registrationKey) throws ModelException;
+
+	public List<User> getUsers(String cnd);
+	public List<User> getUsersByNodeCount(int i, int n, String cnd);
+	public int getUserCount(String cnd);
+
+	public Person getAnonymous(String cookie, String name);
+	public String newAnonymousCookie();
+	public Person getPerson(String id);
+
+	public void addTag(Node node,User user,String label);
+	public void deleteTags(Node node,User user,String sqlCondition);
+	public void deleteTags(String sqlCondition);  // doesn't clear caches
+	public boolean hasTags(Node node,User user,String sqlCondition);
+	public int countTags(String sqlCondition);
+	public List<String> findTagLabels(String sqlCondition);
+	public List<User> findTagUsers(String sqlCondition);
+	public List<Long> findTagUserIds(String sqlCondition);
+	public List<Node> findTagNodes(String sqlCondition);
+	public List<Long> findTagNodeIds(String sqlCondition);
+
+	public boolean isModuleEnabled(String moduleName);
+	public void setModuleEnabled(String moduleName,boolean isEnabled);
+	public Map<String,String> getCustomTweaks();
+	public void setCustomTweaks(Map<String,String> tweaks);
+	public boolean setTweakException(CompileException tweakException);
+	public CompileException getTweakException();
+	public void update();
+	public Site getGoodCopy();
+
+	// moved from ModelHome
+	public Node getNode(long id);
+	public Node getNode(ResultSet rs) throws SQLException;
+	public Collection<? extends Node> getNodes(Collection<Long> ids);
+	public NodeIterator<? extends Node> getNodeIterator(String sql,DbParamSetter paramSetter);
+
+	public String getProperty(String key);
+	public void setProperty(String key,String value);
+
+	public boolean isValidConfiguration(String name);
+	public void deleteConfiguration(String name);
+	public void saveConfiguration(String name,String value,String naml);
+	public String getConfigurationValue(String name);
+	public String getConfigurationTweak();
+
+	public String getNextUrl(String registrationKey);
+	public Node newNode(NodeData data) throws ModelException;
+	public Collection<Node> cacheLastNodes(Collection<Node> nodes);
+
+	public <T> T getExtension(ExtensionFactory<Site,T> factory);
+	public void addTask(String task);
+
+	public File backup();
+	public void backup(String filename);
+	public void backupSchema(String filename);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/SiteGlobal.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,339 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbNull;
+import fschmidt.db.DbObject;
+import fschmidt.db.DbObjectFactory;
+import fschmidt.db.DbRecord;
+import fschmidt.db.DbTable;
+import fschmidt.db.Listener;
+import fschmidt.db.LongKey;
+import fschmidt.util.java.DateUtils;
+import fschmidt.util.java.ObjectUtils;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.SiteDeleteMail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+
+final class SiteGlobal implements DbObject<LongKey,SiteGlobal> {
+	private static final Logger logger = LoggerFactory.getLogger(SiteGlobal.class);
+
+	final SiteKey siteKey;
+	private final DbRecord<LongKey,SiteGlobal> record;
+	private String customDomain;
+	private Date deleteDate;
+	private int activity;
+	private boolean isEmbarrassing = false;
+	private String remoteAddr = null; // IP of the user who created the site
+	private String baseUrl;
+
+	SiteGlobal() {
+		record = table.newRecord(this);
+		record.insert();
+		this.siteKey = SiteKey.getInstance(getId());
+	}
+
+	SiteGlobal(long siteId) {
+		record = table.newRecord(this);
+		record.fields().put("site_id", siteId );
+		record.insert();
+		this.siteKey = SiteKey.getInstance(getId());
+	}
+
+	private SiteGlobal(LongKey key,ResultSet rs)
+		throws SQLException
+	{
+		record = table.newRecord(this,key);
+		this.siteKey = SiteKey.getInstance(getId());
+		customDomain = rs.getString("custom_domain");
+		if( customDomain != null )
+			customDomain = customDomain.intern();
+		deleteDate = rs.getDate("delete_date");
+		activity = rs.getInt("activity");
+		isEmbarrassing = rs.getBoolean("is_embarrassing");
+		remoteAddr = rs.getString("remote_addr");
+	}
+
+	public DbRecord<LongKey,SiteGlobal> getDbRecord() {
+		return record;
+	}
+
+	public long getId() {
+		return record.getPrimaryKey().value();
+	}
+
+	private DbTable<LongKey,SiteGlobal> table() {
+		return record.getDbTable();
+	}
+
+	private DbDatabase db() {
+		return table().getDbDatabase();
+	}
+
+	SiteImpl site() {
+		return siteKey.site();
+	}
+
+	static DbTable<LongKey,SiteGlobal> table = Db.dbGlobal().newTable(
+		"site_global",
+		Db.dbGlobal().newIdentityLongKeySetter("site_id"),
+		new DbObjectFactory<LongKey, SiteGlobal>() {
+			public SiteGlobal makeDbObject(LongKey key,ResultSet rs,String tableName)
+				throws SQLException
+			{
+				return new SiteGlobal(key,rs);
+			}
+		}
+	);
+	static {
+		table.getPostUpdateListeners().add(new Listener<SiteGlobal>() {
+			public void event(SiteGlobal siteGlobal) {
+				SiteImpl.postUpdateListeners.event(siteGlobal.site());
+			}
+		} );
+/*
+		// for debugging
+		table.getPreDeleteListeners().add(new Listener<SiteGlobal>() {
+			public void event(SiteGlobal siteGlobal) {
+				logger.error("deleting "+siteGlobal,new Exception());
+			}
+		} );
+*/
+	}
+
+	static SiteGlobal getSiteGlobal(long siteId) {
+		return table.findByPrimaryKey(new LongKey(siteId));
+	}
+
+	static SiteGlobal getSiteGlobal(ResultSet rs)
+		throws SQLException
+	{
+		return table.getDbObject(rs);
+	}
+
+	public void setCustomDomain(String customDomain) {
+		if( customDomain != null )
+			customDomain = customDomain.toLowerCase();
+		this.customDomain = customDomain;
+		record.fields().put("custom_domain", DbNull.fix(customDomain) );
+		if( !db().isInTransaction() )
+			record.update();
+//		calcBaseUrl();
+	}
+
+	public String getCustomDomain() {
+		return customDomain;
+	}
+
+	public String getBaseUrl() {
+		if( baseUrl == null ) {
+			baseUrl = "http://" +
+				(customDomain != null ? customDomain : Jtp.getDefaultBaseUrl(siteKey.site()));
+		}
+		return baseUrl;
+	}
+/*
+	void calcBaseUrl() {
+		baseUrl = "http://" +
+			(customDomain != null ? customDomain : Jtp.getDefaultBaseUrl(siteKey.site()));
+	}
+*/
+
+
+	private static final Map<String,Long> domainMap = new WeakHashMap<String,Long>();
+
+	static Long getSiteIdFromDomain(String customDomain) {
+		Long siteId;
+		synchronized(domainMap) {
+			siteId = domainMap.get(customDomain);
+			if( siteId == null ) {
+				try {
+					Connection con = Db.dbGlobal().getConnection();
+					try {
+						PreparedStatement stmt = con.prepareStatement(
+							"select site_id from site_global where custom_domain = ?"
+						);
+						stmt.setString(1,customDomain);
+						ResultSet rs = stmt.executeQuery();
+						try {
+							if( !rs.next() )
+								return null;
+							siteId = rs.getLong("site_id");
+						} finally {
+							rs.close();
+							stmt.close();
+						}
+					} finally {
+						con.close();
+					}
+				} catch(SQLException e) {
+					throw new RuntimeException(e);
+				}
+				domainMap.put( customDomain.intern(), siteId );
+			}
+		}
+		return siteId;
+	}
+
+
+
+
+	public int getActivity() {
+		return activity;
+	}
+
+	void setActivity(int activity) {
+		this.activity = activity;
+		record.fields().put( "activity", activity );
+		if( !db().isInTransaction() )
+			record.update();
+	}
+
+
+	public Date getDeleteDate() {
+		return deleteDate;
+	}
+
+	private void setDeleteDate(Date deleteDate) {
+		if( deleteDate==null )
+			throw new NullPointerException();
+		this.deleteDate = deleteDate;
+		record.fields().put("delete_date",deleteDate);
+		record.update();
+	}
+
+	public void clearDeleteDate() {
+		record.fields().put("delete_date",DbNull.TIMESTAMP);
+		record.fields().put("activity",ViewCount.initialActivity);
+		record.update();
+	}
+
+	private static final boolean deleteInactiveSites = Init.hasDaemons && Init.get("deleteInactiveSites",false);
+	private static final int daysToDeletion = Init.get("daysToDeletion",30);
+
+	private static void deleteInactiveSites() {
+		logger.error("Starting deleteInactiveSites");
+		List<SiteGlobal> sitesToDelete = new ArrayList<SiteGlobal>();
+		try {
+			Date deletionDate = DateUtils.addDays(new Date(),daysToDeletion);
+			Connection con = Db.dbGlobal().getConnection();
+			try {
+				{
+					Statement stmt = con.createStatement();
+					ResultSet rs = stmt.executeQuery(
+						"select * from site_global"
+						+" where delete_date is null and activity=0"
+					);
+					int count = 0;
+					while( rs.next() ) {
+						SiteGlobal siteGlobal = SiteGlobal.getSiteGlobal(rs);
+						try {
+							SiteImpl site = siteGlobal.site();
+							siteGlobal.setDeleteDate(deletionDate);
+							count++;
+							for( UserImpl user : site.getPosters() ) {
+								if( !user.isRegistered() )
+									continue;
+								// Send an email if site has more than one node
+								if (site.getRootNode().getDescendantCount() > 1) {
+									SiteDeleteMail.send(user,site,daysToDeletion);
+									logger.info("Site deletion email sent to " + user.getEmail() + " for " + site);
+								} else
+									logger.info("Site scheduled for deletion: " + site);
+							}
+						} catch(UpdatingException e) {
+							logger.error("Couldn't schedule for deletion: " + e);
+						}
+						if( Executors.isShuttingDown() )
+							return;
+					}
+					rs.close();
+					stmt.close();
+					logger.error("Sites scheduled for deletion = " + count);
+				}
+				{
+					Statement stmt = con.createStatement();
+					ResultSet rs = stmt.executeQuery(
+						"select * from site_global"
+						+" where delete_date < now()"
+					);
+					while( rs.next() ) {
+						SiteGlobal siteGlobal = SiteGlobal.getSiteGlobal(rs);
+						sitesToDelete.add(siteGlobal);
+					}
+					rs.close();
+					stmt.close();
+				}
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+		for( SiteGlobal siteGlobal : sitesToDelete ) {
+			try {
+				Site site = siteGlobal.site();
+				logger.error("Deleting inactive site ID=" + site.getId() + " (" + site.getRootNode().getSubject() + " / " + site.getRootNode().getDescendantCount() + " nodes)");
+				site.delete();
+			} catch(UpdatingException e) {
+				logger.error("Couldn't delete inactive site: " + e);
+			} catch(RuntimeException e) {
+				logger.error("couldn't delete inactive site "+siteGlobal,e);
+			}
+		}
+		logger.error("Finished deleteInactiveSites");
+	}
+
+	static {
+		if( deleteInactiveSites ) {
+			Executors.runDaily(new Runnable(){public void run(){
+				deleteInactiveSites();
+			}});
+		}
+	}
+
+
+	public boolean isEmbarrassing() {
+		return isEmbarrassing;
+	}
+
+	public void setEmbarrassing(boolean isEmbarrassing) {
+		this.isEmbarrassing = isEmbarrassing;
+		record.fields().put( "is_embarrassing", DbNull.fix(isEmbarrassing) );
+		if( !db().isInTransaction() )
+			record.update();
+	}
+
+	public String toString() {
+		return "siteGlobal-"+getId();
+	}
+
+	@Override public boolean equals(Object obj) {
+		return this==obj || obj instanceof SiteGlobal && record.isInDb() && ((SiteGlobal)obj).getId()==getId();
+	}
+
+	@Override public int hashCode() {
+		return (int)getId();
+	}
+
+
+	boolean setRemoteAddr(String remoteAddr) {
+		this.remoteAddr = remoteAddr;
+		record.fields().put( "remote_addr", DbNull.fix(remoteAddr) );
+		record.update();
+		return true;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/SiteImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1103 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbObjectFactory;
+import fschmidt.db.DbRecord;
+import fschmidt.db.DbTable;
+import fschmidt.db.DbUtils;
+import fschmidt.db.Listener;
+import fschmidt.db.ListenerList;
+import fschmidt.db.LongKey;
+import fschmidt.db.NoKey;
+import fschmidt.db.NoKeySetter;
+import fschmidt.util.java.CollectionUtils;
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.FutureValue;
+import fschmidt.util.java.Memoizer;
+import fschmidt.util.java.SimpleCache;
+import jdbcpgbackup.DataFilter;
+import jdbcpgbackup.ZipBackup;
+import nabble.model.export.NodeData;
+import nabble.modules.ModuleManager;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Module;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.StackTraceElement;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.web.template.NabbleNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+
+final class SiteImpl implements Site {
+	private static final Logger logger = LoggerFactory.getLogger(SiteImpl.class);
+
+	final SiteKey siteKey;
+	private final DbRecord<NoKey,SiteImpl> record;
+	private long rootNodeId;
+	private NodeImpl rootNode = null;
+
+	private final Object tweakLock = new Object();
+	private CompileException tweakException = null;
+	private Program program = null;
+	private final Date whenCreated;
+
+	SiteImpl(SiteKey siteKey) {
+		this.siteKey = siteKey;
+		record = table(siteKey).newRecord(this);
+		record.fields().put( "root_node_id", 0L );
+		whenCreated = new Date();
+	}
+
+	void setRoot(NodeImpl node) {
+		if( !node.isInDb() )
+			throw new RuntimeException("node must be in db");
+		this.rootNodeId = node.getId();
+		record.fields().put( "root_node_id", rootNodeId );
+		this.rootNode = node;
+		record.update();
+	}
+
+	private SiteImpl(SiteKey siteKey,NoKey key,ResultSet rs)
+		throws SQLException
+	{
+		this.siteKey = siteKey;
+		record = table(siteKey).newRecord(this,key);
+		rootNodeId = rs.getLong("root_node_id");
+		whenCreated = rs.getTimestamp("when_created");
+		for( ExtensionFactory<Site,?> factory : extensionFactories ) {
+			Object obj = factory.construct(this,rs);
+			if( obj != null )
+				getExtensionMap().put(factory,obj);
+		}
+	}
+
+	public DbRecord<NoKey,SiteImpl> getDbRecord() {
+		return record;
+	}
+
+	private DbTable<NoKey,SiteImpl> table() {
+		return record.getDbTable();
+	}
+
+	private DbDatabase db() {
+		return table().getDbDatabase();
+	}
+
+	public DbDatabase getDb() {
+		return siteKey.getDb();
+	}
+
+	public long getId() {
+		return siteKey.getId();
+	}
+/*
+	private void calcBaseUrl() {
+		siteGlobal().calcBaseUrl();
+	}
+*/
+	long getRootNodeId() {
+		return rootNodeId;
+	}
+
+	NodeImpl getRootNodeImpl() {
+		if( DbUtils.isStale(rootNode) ) {
+			rootNode = NodeImpl.getNode(siteKey,rootNodeId);
+		}
+		return rootNode;
+	}
+
+	public Node getRootNode() {
+		return getRootNodeImpl();
+	}
+
+	public Date getWhenCreated() {
+		return whenCreated;
+	}
+
+	/** To be called from the shell */
+	public void setWhenCreated(int day, int month, int year) {
+		Calendar cal = Calendar.getInstance();
+		cal.set(Calendar.DAY_OF_MONTH, day);
+		cal.set(Calendar.MONTH, month);
+		cal.set(Calendar.YEAR, year);
+		DbRecord<NoKey,?> record = getDbRecord();
+		record.fields().put("when_created", cal.getTime());
+		record.update();
+	}
+
+	SiteGlobal siteGlobal() {
+		return siteKey.siteGlobal();
+	}
+
+	@Override public boolean equals(Object obj) {
+		return this==obj || obj instanceof SiteImpl && record.isInDb() && ((SiteImpl)obj).getId()==getId();
+	}
+
+	@Override public int hashCode() {
+		return (int)getId();
+	}
+
+	public Program getProgram() {
+		synchronized(tweakLock) {
+			if( program == null ) {
+				if(trace()) logger.error("getting Program for "+this+" "+System.identityHashCode(this)+" tweakException="+tweakException);
+				List<Module> modules = ModuleManager.getModules(SiteImpl.this);
+				program = Program.getInstance(modules);
+			}
+			return program;
+		}
+	}
+
+	public Template getTemplate(String templateName,Class... base) {
+		synchronized(tweakLock) {
+			try {
+				return getProgram().getTemplate(templateName,base);
+			} catch(CompileException e) {
+				if( setTweakException(e) ) {
+					return getTemplate(templateName,base);
+				}
+				throw new RuntimeException(""+this+" "+System.identityHashCode(this),e);
+			}
+		}
+	}
+
+	public void setCustomDomain(String customDomain) {
+		siteGlobal().setCustomDomain(customDomain);
+	}
+
+	public String getCustomDomain() {
+		return siteGlobal().getCustomDomain();
+	}
+
+
+	public String getBaseUrl() {
+		return siteGlobal().getBaseUrl();
+	}
+
+	List<UserImpl> getPosters() {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select * from user_ where user_id in ("
+					+"select distinct owner_id from node where redirect is null"
+				+")"
+			);
+			try {
+				return UserImpl.getUsers(siteKey,stmt);
+			} finally {
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public List<User> getUsers(String cnd) {
+		List<User> list = new ArrayList<User>();
+		getUserImpls(list, cnd);
+		return list;
+	}
+
+	List<UserImpl> getUserImpls(String cnd) {
+		List<UserImpl> list = new ArrayList<UserImpl>();
+		getUserImpls(list, cnd);
+		return list;
+	}
+
+	void getUserImpls(List<? super UserImpl> list, String cnd) {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select * from user_" +
+				(cnd == null? "" :  " where " + cnd)
+			);
+			try {
+				UserImpl.getUsers(siteKey,stmt,list);
+			} finally {
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public int getActivity() {
+		return siteGlobal().getActivity();
+	}
+
+	public void setActivity(int activity) {
+		siteGlobal().setActivity(activity);
+	}
+
+	public boolean isEmbarrassing() {
+		return siteGlobal().isEmbarrassing();
+	}
+
+	public void setEmbarrassing(boolean isEmbarrassing) {
+		siteGlobal().setEmbarrassing(isEmbarrassing);
+	}
+
+
+	public String toString() {
+		return "site-"+getId();
+	}
+
+	static final ListenerList<SiteImpl> postUpdateListeners = new ListenerList<SiteImpl>();
+	private static final ListenerList<SiteImpl> postDeleteListeners = new ListenerList<SiteImpl>();
+	private static final ListenerList<SiteImpl> preInsertListeners = new ListenerList<SiteImpl>();
+	private static final ListenerList<SiteImpl> preUpdateListeners = new ListenerList<SiteImpl>();
+
+	private static Computable<SiteKey,DbTable<NoKey,SiteImpl>> tables = new SimpleCache<SiteKey,DbTable<NoKey,SiteImpl>>(new WeakHashMap<SiteKey,DbTable<NoKey,SiteImpl>>(), new Computable<SiteKey,DbTable<NoKey,SiteImpl>>() {
+		public DbTable<NoKey,SiteImpl> get(SiteKey siteKey) {
+			DbDatabase db = siteKey.getDb();
+			final long siteId = siteKey.getId();
+			DbTable<NoKey,SiteImpl> table = db.newTable("site",NoKeySetter.INSTANCE
+				, new DbObjectFactory<NoKey,SiteImpl>() {
+					public SiteImpl makeDbObject(NoKey key,ResultSet rs,String tableName)
+						throws SQLException
+					{
+						SiteKey siteKey = SiteKey.getInstance(siteId);
+						return new SiteImpl(siteKey,key,rs);
+					}
+				}
+			);
+			table.getPreInsertListeners().add(preInsertListeners);
+			table.getPreUpdateListeners().add(preUpdateListeners);
+			table.getPostUpdateListeners().add(postUpdateListeners);
+			table.getPostDeleteListeners().add(postDeleteListeners);
+			return table;
+		}
+	});
+
+	private static DbTable<NoKey,SiteImpl> table(SiteKey siteKey) {
+		return tables.get(siteKey);
+	}
+
+	static SiteImpl getSite(SiteKey siteKey,long siteId) {
+		return table(siteKey).findByPrimaryKey(NoKey.INSTANCE);
+	}
+
+	static SiteImpl getSite(SiteKey siteKey,ResultSet rs)
+		throws SQLException
+	{
+		return table(siteKey).getDbObject(rs);
+	}
+
+	public Date getDeleteDate() {
+		return siteGlobal().getDeleteDate();
+	}
+
+	public void clearDeleteDate() {
+		siteGlobal().clearDeleteDate();
+	}
+
+
+
+
+
+
+
+	static void addChangeListener(final Listener<? super SiteImpl> listener) {
+		postUpdateListeners.add(listener);
+		postDeleteListeners.add(listener);
+	}
+
+	static void addPreChangeListener(final Listener<? super SiteImpl> listener) {
+		preInsertListeners.add(listener);
+		preUpdateListeners.add(listener);
+	}
+
+
+	public User getUser(long id) {
+		return getUserImpl(id);
+	}
+
+	UserImpl getUserImpl(long id) {
+		return UserImpl.getUser(siteKey,id);
+	}
+
+	public User getUserFromEmail(String email) {
+		return getUserImplFromEmail(email);
+	}
+
+	UserImpl getUserImplFromEmail(String email) {
+		return UserImpl.getUserFromEmail(this,email);
+	}
+
+	public User getUserFromName(String name) {
+		return getUserImplFromName(name);
+	}
+
+	UserImpl getUserImplFromName(String name) {
+		return UserImpl.getUserFromName(this,name);
+	}
+
+	public User getOrCreateUnregisteredUser(String email,String name)
+		throws ModelException
+	{
+		return UserImpl.getOrCreateUnregisteredUser(this,email,name);
+	}
+
+	public User getOrCreateUser(String email) {
+		return UserImpl.getOrCreateUser(this,email);
+	}
+
+	public User getOrCreateUser(String email,String name) {
+		UserImpl user = getUserImplFromEmail(email);
+		if( user==null ) {
+			user = UserImpl.createGhost(this,email);
+			user.setNameLike(name,false);
+			user.insert();
+		}
+		return user;
+	}
+
+	public String newRegistration(String email,String password,String name,String nextUrl)
+		throws ModelException
+	{
+		return UserImpl.createUser(this,email,password,name).newRegistration(nextUrl);
+	}
+
+	public User getRegistration(String registrationKey)
+		throws ModelException
+	{
+		return UserImpl.getRegistration(this,registrationKey);
+	}
+
+	public List<User> getUsersByNodeCount(int i, int n, String cnd) {
+		try {
+			List<User> list = new ArrayList<User>();
+			Connection con = db().getConnection();
+			PreparedStatement stmt1 = con.prepareStatement(
+				"select *, (select count(*) from node where user_.user_id=node.owner_id) as n"
+				+" from user_"
+				+ (cnd == null? "" : " where " + cnd)
+				+" order by n desc"
+				+" limit ? offset ?"
+			);
+			stmt1.setInt(1,n);
+			stmt1.setInt(2,i);
+			ResultSet rs1 = stmt1.executeQuery();
+			while( rs1.next() ) {
+				UserImpl user = UserImpl.getUser(siteKey,rs1);
+				user.setNodeCount( rs1.getInt("n") );
+				list.add(user);
+			}
+			rs1.close();
+			stmt1.close();
+			con.close();
+			return list;
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public int getUserCount(String cnd) {
+		try {
+			Connection con = db().getConnection();
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"select count(*) as n from user_" +
+				(cnd == null? "" : " where " + cnd)
+			);
+			rs.next();
+			int n = rs.getInt("n");
+			rs.close();
+			stmt.close();
+			con.close();
+			return n;
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+
+	public void deleteRootNode() throws ModelException {
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				SiteImpl site = DbUtils.getGoodCopy(this);
+				site.deleteRootNode();
+				db().commitTransaction();
+			} finally {
+				db().endTransaction();
+			}
+			return;
+		}
+		NodeImpl oldRoot = getRootNodeImpl();
+		List<NodeImpl> children = oldRoot.getChildrenImpl(null).asList();
+		if( children.size() != 1 )
+			throw ModelException.newInstance("cant_delete_root","Root node must have exactly one child");
+		NodeImpl child = children.get(0);
+		child.makeRoot();
+		setRoot(child);
+		oldRoot.getDbRecord().delete();
+	}
+
+	public Person getAnonymous(String cookie, String name) {
+		return new Anonymous(this,cookie,name);
+	}
+
+	public String newAnonymousCookie() {
+		return Anonymous.newCookie(this);
+	}
+
+	public Person getPerson(String id) {
+		int i = id.indexOf(Anonymous.SEPERATOR);
+		if( i == -1 )
+			return getUser(Long.parseLong(id));
+		String cookie = id.substring(0,i);
+		String name = id.substring(i+1);
+		if( name.length() == 0 )
+			name = null;
+		return getAnonymous(cookie,name);
+	}
+
+
+	public void addTag(Node node,User user,String label) {
+		TagImpl.addTag(this,node,user,label);
+		uncacheTags(node,user);
+	}
+
+	public void deleteTags(String sqlCondition) {
+		TagImpl.deleteTags( siteKey, sqlCondition );
+	}
+
+	private static String tagSql(Node node,User user,String sqlCondition) {
+		StringBuilder sb = new StringBuilder();
+		if( node == null )
+			sb.append( "node_id is null" );
+		else
+			sb.append( "node_id=" ).append( node.getId() );
+		sb.append( " and " );
+		if( user == null )
+			sb.append( "user_id is null" );
+		else
+			sb.append( "user_id=" ).append( user.getId() );
+		sb.append( " and " ).append( sqlCondition );
+		return sb.toString();
+	}
+
+	public void deleteTags(Node node,User user,String sqlCondition) {
+		deleteTags( tagSql(node,user,sqlCondition) );
+		uncacheTags(node,user);
+	}
+
+	private final Memoizer<String,Boolean> tagCache = new Memoizer<String,Boolean>(new Computable<String,Boolean>() {
+		public Boolean get(String sqlCondition) {
+			return TagImpl.countTags(siteKey,sqlCondition) > 0;
+		}
+	});
+
+	private Memoizer<String,Boolean> tagCache(Node node,User user) {
+		if( user != null )
+			return ((UserImpl)user).tagCache;
+		else if( node != null )
+			return ((NodeImpl)node).tagCache;
+		else
+			return tagCache;
+	}
+
+	private void uncacheTags(Node node,User user) {
+		if( user != null )
+			DbUtils.uncache((UserImpl)user);
+		else if( node != null )
+			DbUtils.uncache((NodeImpl)node);
+		else
+			DbUtils.uncache(this);
+	}
+
+	public boolean hasTags(Node node,User user,String sqlCondition) {
+		return tagCache(node,user).get( tagSql(node,user,sqlCondition) );
+	}
+
+	public int countTags(String sqlCondition) {
+		return TagImpl.countTags(siteKey,sqlCondition);
+	}
+
+	public List<String> findTagLabels(String sqlCondition) {
+		return TagImpl.findTagLabels(this,sqlCondition);
+	}
+
+	public List<User> findTagUsers(String sqlCondition) {
+		return new ArrayList<User>( UserImpl.getUsers( siteKey, TagImpl.findTagUserIds(this,sqlCondition) ) );
+	}
+
+	public List<Long> findTagUserIds(String sqlCondition) {
+		return TagImpl.findTagUserIds(this,sqlCondition);
+	}
+
+	public List<Node> findTagNodes(String sqlCondition) {
+		return new ArrayList<Node>( NodeImpl.getNodes( siteKey, TagImpl.findTagNodeIds(this,sqlCondition) ) );
+	}
+
+	public List<Long> findTagNodeIds(String sqlCondition) {
+		return TagImpl.findTagNodeIds(this,sqlCondition);
+	}
+
+
+	private FutureValue<Map<String,Boolean>> modulesEnabled = new FutureValue<Map<String,Boolean>>() {
+		protected Map<String,Boolean> compute() {
+			Map<String,Boolean> map = new HashMap<String,Boolean>();
+			try {
+				Connection con = db().getConnection();
+				Statement stmt = con.createStatement();
+				ResultSet rs = stmt.executeQuery(
+					"select module_name, is_enabled from module"
+				);
+				while( rs.next() ) {
+					String moduleName = rs.getString("module_name");
+					boolean isEnabled = rs.getBoolean("is_enabled");
+					map.put(moduleName,isEnabled);
+				}
+				rs.close();
+				stmt.close();
+				con.close();
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+			if( map.isEmpty() )
+				map = Collections.emptyMap();
+			return map;
+		}
+	};
+
+	public boolean isModuleEnabled(String moduleName) {
+		Boolean b = modulesEnabled.get().get(moduleName);
+		return b != null ? b : ModuleManager.isEnabledByDefault(moduleName);
+	}
+
+	public void setModuleEnabled(String moduleName,boolean isEnabled) {
+		try {
+			Connection con = db().getConnection();
+			if( isEnabled == ModuleManager.isEnabledByDefault(moduleName) ) {
+				PreparedStatement stmt = con.prepareStatement(
+					"delete from module where module_name = ?"
+				);
+				stmt.setString( 1, moduleName );
+				stmt.executeUpdate();
+				stmt.close();
+			} else if( modulesEnabled.get().get(moduleName) == null ) {
+				PreparedStatement stmt = con.prepareStatement(
+					"insert into module (module_name,is_enabled) values (?,?)"
+				);
+				stmt.setString( 1, moduleName );
+				stmt.setBoolean( 2, isEnabled );
+				stmt.executeUpdate();
+				stmt.close();
+			} else {
+				PreparedStatement stmt = con.prepareStatement(
+					"update module set is_enabled = ? where module_name = ?"
+				);
+				stmt.setBoolean( 1, isEnabled );
+				stmt.setString( 2, moduleName );
+				stmt.executeUpdate();
+				stmt.close();
+			}
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+		record.update();  // uncache and fire update listeners
+	}
+
+	private FutureValue<String> config = new FutureValue<String>() {
+		protected String compute() {
+			StringBuilder tweak = new StringBuilder();
+			final Set<String> names = new HashSet<String>();
+			try {
+				Connection con = db().getConnection();
+				Statement stmt = con.createStatement();
+				ResultSet rs = stmt.executeQuery(
+					"select name, naml from configuration"
+				);
+				while( rs.next() ) {
+					names.add( rs.getString("name") );
+					tweak.append(rs.getString("naml"));
+					tweak.append("\n\n");
+				}
+				rs.close();
+				stmt.close();
+				con.close();
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+			if( !names.isEmpty() ) {
+				Executors.executeSometime(new Runnable(){
+					public void run() {
+						boolean didDelete = false;
+						for( String name : names ) {
+							if( !isValidConfiguration(name) ) {
+								deleteConfiguration(name);
+								didDelete = true;
+								logger.error("deleted invalid config: "+name);
+							}
+						}
+						if( didDelete )
+							update();
+					}
+				});
+			}
+			return tweak.toString();
+		}
+	};
+
+	public String getConfigurationTweak() {
+		return config.get();
+	}
+
+	private volatile FutureValue<Map<String,String>> tweaks = newTweaks();
+
+	private FutureValue<Map<String,String>> newTweaks() {
+		return new FutureValue<Map<String,String>>() {
+			protected Map<String,String> compute() {
+				Map<String,String> map = new HashMap<String,String>();
+				try {
+					Connection con = db().getConnection();
+					Statement stmt = con.createStatement();
+					ResultSet rs = stmt.executeQuery(
+						"select tweak_name, content from tweak"
+					);
+					while( rs.next() ) {
+						String tweakName = rs.getString("tweak_name");
+						String content = rs.getString("content");
+						map.put(tweakName,content);
+					}
+					rs.close();
+					stmt.close();
+					con.close();
+				} catch(SQLException e) {
+					throw new RuntimeException(e);
+				}
+				return CollectionUtils.optimizeMap(map);
+			}
+		};
+	}
+
+	public Map<String,String> getCustomTweaks() {
+		return tweaks.get();
+	}
+
+	public void setCustomTweaks(Map<String,String> tweaks) {
+		try {
+			Connection con = db().getConnection();
+			try {
+				{
+					Statement stmt = con.createStatement();
+					stmt.executeUpdate(
+						"delete from tweak"
+					);
+					stmt.close();
+				}
+				{
+					PreparedStatement stmt = con.prepareStatement(
+						"insert into tweak (tweak_name,content) values (?,?)"
+					);
+					for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
+						String tweakName = entry.getKey();
+						String content = entry.getValue();
+						stmt.setString( 1, tweakName );
+						stmt.setString( 2, content );
+						stmt.executeUpdate();
+					}
+					stmt.close();
+				}
+			} finally {
+				con.close();
+			}
+			DailyNumber.tweaks.inc();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+		this.tweaks = newTweaks();
+		synchronized(tweakLock) {
+			this.tweakException = null;
+			this.program = null;
+		}
+		record.update();  // uncache and fire update listeners
+	}
+
+	public void resetCustomTweaks() {
+		setCustomTweaks(Collections.<String,String>emptyMap());
+	}
+
+	private static long traceSiteId = Init.get("traceSiteId",0L);
+
+	private boolean trace() {
+		return getId() == traceSiteId;
+	}
+
+	public boolean setTweakException(CompileException tweakException) {
+		synchronized(tweakLock) {
+			if( this.tweakException != null ) {
+				if(trace()) logger.error("this.tweakException already set in "+this+" "+System.identityHashCode(this),new Exception(this.tweakException));
+				return false;
+			}
+			for( StackTraceElement ste : tweakException.stackTrace ) {
+				if( ModuleManager.isConfigurationTweak(ste.source) || ModuleManager.isCustomTweak(ste.source) ) {
+					logger.debug("tweak exception in "+this,tweakException);
+					if(trace()) logger.error("tweak exception in "+this+" "+System.identityHashCode(this),tweakException);
+					this.tweakException = tweakException;
+					this.program = null;
+					return true;
+				}
+			}
+			if(trace()) logger.error("no tweak in stack trace");
+			return false;
+		}
+	}
+
+	public CompileException getTweakException() {
+		synchronized(tweakLock) {
+			return tweakException;
+		}
+	}
+
+	public void update() {
+		record.update();
+	}
+
+	public Site getGoodCopy() {
+		return DbUtils.getGoodCopy(this);
+	}
+
+	NodeImpl getNodeImpl(long id) {
+		return NodeImpl.getNode(siteKey,id);
+	}
+
+	public Node getNode(long id) {
+		return getNodeImpl(id);
+	}
+
+	public Node getNode(ResultSet rs) throws SQLException {
+		return NodeImpl.getNode(siteKey,rs);
+	}
+
+	private void check(Collection<? extends Node> nodes) {
+		for( Iterator<? extends Node> i = nodes.iterator(); i.hasNext(); ) {
+			if( !i.next().getSite().equals(this) )
+				throw new RuntimeException("node from wrong site");
+		}
+	}
+
+	public Collection<? extends Node> getNodes(Collection<Long> ids) {
+		Collection<NodeImpl> nodes = NodeImpl.getNodes(siteKey,ids);
+		check(nodes);
+		return nodes;
+	}
+
+	public NodeIterator<? extends Node> getNodeIterator(String sql,DbParamSetter paramSetter) {
+		return new CursorNodeIterator( siteKey, sql, paramSetter );
+	}
+
+
+	private final Memoizer<String,String> propertyCache = new Memoizer<String,String>(new Computable<String,String>() {
+		public String get(String key) {
+			try {
+				Connection con = db().getConnection();
+				PreparedStatement stmt = con.prepareStatement(
+					"select value from site_property where key = ?"
+				);
+				stmt.setString( 1, key );
+				ResultSet rs = stmt.executeQuery();
+				try {
+					return rs.next() ? rs.getString("value") : null;
+				} finally {
+					rs.close();
+					stmt.close();
+					con.close();
+				}
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+		}
+	});
+
+	public String getProperty(String key) {
+		return propertyCache.get(key);
+	}
+
+	public void setProperty(String key,String value) {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"delete from site_property where key = ?"
+			);
+			stmt.setString( 1, key );
+			stmt.executeUpdate();
+			stmt.close();
+			if( value != null ) {
+				stmt = con.prepareStatement(
+					"insert into site_property (key,value) values (?,?)"
+				);
+				stmt.setString( 1, key );
+				stmt.setString( 2, value );
+				stmt.executeUpdate();
+				stmt.close();
+			}
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		} finally {
+			propertyCache.remove(key);
+		}
+	}
+
+
+	public boolean isValidConfiguration(String name) {
+		Template template = getTemplate( "is_valid_configuration",
+			BasicNamespace.class, NabbleNamespace.class
+		);
+		StringWriter sw = new StringWriter();
+		template.run( new TemplatePrintWriter(sw), Collections.<String,Object>singletonMap("config",name),
+			new BasicNamespace(template), new NabbleNamespace(this)
+		);
+		return Template.booleanValue(sw.toString().trim());
+	}
+
+	private void deleteConfiguration(Connection con,String name) throws SQLException {
+		PreparedStatement stmt = con.prepareStatement(
+			"delete from configuration where name = ?"
+		);
+		stmt.setString( 1, name );
+		stmt.executeUpdate();
+		stmt.close();
+	}
+
+	public void deleteConfiguration(String name) {
+		try {
+			Connection con = db().getConnection();
+			deleteConfiguration(con,name);
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public void saveConfiguration(String name,String value,String naml) {
+		if( !isValidConfiguration(name) )
+			throw new RuntimeException("invalid configuration: "+name);
+		try {
+			Connection con = db().getConnection();
+			deleteConfiguration(con,name);
+			PreparedStatement stmt = con.prepareStatement(
+				"insert into configuration (name,value,naml) values (?,?,?)"
+			);
+			stmt.setString( 1, name );
+			stmt.setString( 2, value );
+			stmt.setString( 3, naml );
+			stmt.executeUpdate();
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public String getConfigurationValue(String name) {
+		if( !isValidConfiguration(name) )
+			throw new RuntimeException("invalid configuration: "+name);
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select value from configuration where name = ?"
+			);
+			stmt.setString( 1, name );
+			ResultSet rs = stmt.executeQuery();
+			try {
+				return rs.next() ? rs.getString("value") : null;
+			} finally {
+				rs.close();
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+
+	public String getNextUrl(String registrationKey) {
+		return UserImpl.getNextUrl(siteKey,registrationKey);
+	}
+
+	public Node newNode(NodeData data)
+		throws ModelException
+	{
+		return new NodeImpl(this,data);
+	}
+
+	public Collection<Node> cacheLastNodes(Collection<Node> nodes) {
+		Collection<LongKey> keys = new ArrayList<LongKey>();
+		for( Node n : nodes ) {
+			NodeImpl node = (NodeImpl)n;
+			keys.add( new LongKey(node.getLastNodeId()) );
+		}
+		Map<LongKey,NodeImpl> objs = NodeImpl.table(siteKey).findByPrimaryKey(keys);
+		return new ArrayList<Node>(objs.values());
+	}
+
+
+	public void addTask(String task) {
+		siteKey.addTask(task);
+	}
+
+
+
+	private Map<ExtensionFactory<Site,?>,Object> extensionMap;
+
+	private synchronized Map<ExtensionFactory<Site, ?>, Object> getExtensionMap() {
+		if (extensionMap == null)
+			extensionMap = new HashMap<ExtensionFactory<Site, ?>, Object>();
+		return extensionMap;
+	}
+
+	public <T> T getExtension(ExtensionFactory<Site,T> factory) {
+		synchronized(getExtensionMap()) {
+			Object obj = extensionMap.get(factory);
+			if( obj == null ) {
+				obj = factory.construct(this);
+				if( obj != null )
+					extensionMap.put(factory,obj);
+			}
+			return factory.extensionClass().cast(obj);
+		}
+	}
+
+	private static Collection<ExtensionFactory<Site,?>> extensionFactories = new CopyOnWriteArrayList<ExtensionFactory<Site,?>>();
+
+	static <T> void addExtensionFactory(ExtensionFactory<Site,T> factory) {
+		extensionFactories.add(factory);
+		Db.clearCache();
+	}
+
+
+
+	public Site getSite() {
+		return this;
+	}
+
+	public long getSourceId() {
+		throw new UnsupportedOperationException();
+	}
+
+	public Message.SourceType getMessageSourceType() {
+		return Message.SourceType.SITE;
+	}
+
+
+	public void delete() {
+		if( getRootNode().getOwner() instanceof User ) {
+			File file = backup();
+			// Don't sent the deletion email if the site has only one node
+			if (getRootNode().getDescendantCount() > 1) {
+				Template template = getTemplate( "site deletion email",
+					BasicNamespace.class, NabbleNamespace.class
+				);
+				Map<String,Object> params = new HashMap<String,Object>();
+				params.put("file",file.getName());
+				template.run( TemplatePrintWriter.NULL, params,
+					new BasicNamespace(template),
+					new NabbleNamespace(this)
+				);
+			}
+		}
+		kill();
+	}
+
+	public void kill() {
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				SiteImpl site = DbUtils.getGoodCopy(SiteImpl.this);
+				site.kill();
+				db().commitTransaction();
+			} finally {
+				db().endTransaction();
+			}
+			return;
+		}
+		SiteGlobal siteGlobal = siteGlobal();
+		if( siteGlobal == null )
+			throw new NullPointerException("siteGlobal not found for "+siteKey);
+		try {
+			Connection con = Db.dbPostgres().getConnection();
+			Statement stmt = con.createStatement();
+			stmt.executeUpdate(
+				"drop schema " + siteKey.schema() + " cascade"
+			);
+			DbUtils.uncache(this);
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+		siteGlobal.getDbRecord().delete();
+	}
+
+	private static final String SALT = "zDf3s";
+	private static final File schemaDir = new File((String)Init.get("local_dir")+"schemas/");
+
+	private static File getBackupFile(long siteId) {
+		int hash = Math.abs(( SALT + siteId ).hashCode());
+		String filename = "site_"+siteId+"_"+hash+".zip";
+		return new File(schemaDir,filename);
+	}
+
+	public File backup() {
+		File file = getBackupFile(getId());
+		backup(file);
+		return file;
+	}
+
+	public void backup(String filename) {
+		backup( new File(filename) );
+	}
+
+	private void backup(File file) {
+		file.delete();
+		ZipBackup backup = new ZipBackup( file, Db.completeUrl );
+		backup.dump( Collections.singleton(siteKey.schema()), DataFilter.ALL_DATA );
+	}
+
+	static final DataFilter SCHEMA_DATA = new DataFilter() {
+		public boolean dumpData(String schema,String tableName) {
+			return tableName.equals("version");
+		}
+	};
+
+	public void backupSchema(String filename) {
+		ZipBackup backup = new ZipBackup( new File(filename), Db.completeUrl );
+		backup.dump( Collections.singleton(siteKey.schema()), SCHEMA_DATA );
+	}
+//	in beanshell, I do:
+//	s = ModelHome.getSite(2)
+//	s.backupSchema("/Users/Franklin/hg/nabble/src/nabble/data/site.schema")
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/SiteKey.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,135 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbUtils;
+import fschmidt.db.postgres.DbDatabaseImpl;
+import fschmidt.db.util.WeakCacheMap;
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.SimpleCache;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.Statement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+
+final class SiteKey {
+	private static final Logger logger = LoggerFactory.getLogger(SiteKey.class);
+
+	private final long siteId;
+	private SiteImpl site = null;
+	private SiteGlobal siteGlobal = null;
+//	final Object lastNodeLock = new Object();
+
+	private SiteKey(long siteId) {
+		this.siteId = siteId;
+	}
+
+	public long getId() {
+		return siteId;
+	}
+
+	SiteImpl site() {
+		if( DbUtils.isStale(site) ) {
+			try {
+				site = SiteImpl.getSite(this,siteId);
+			} catch(Db.NoSchema e) {
+				site = null;
+			}
+		}
+		return site;
+	}
+
+	SiteGlobal siteGlobal() {
+		if( DbUtils.isStale(siteGlobal) ) {
+			siteGlobal = SiteGlobal.getSiteGlobal(siteId);
+		}
+		return siteGlobal;
+	}
+
+	String schema() {
+		return "s" + siteId;
+	}
+
+	public DbDatabase getDb() {
+		return Db.db(schema());
+	}
+
+	@Override public String toString() {
+		return "siteKey-"+siteId;
+	}
+
+	private static Computable<Long,SiteKey> cache = new SimpleCache<Long,SiteKey>(new WeakCacheMap<Long,SiteKey>(), new Computable<Long,SiteKey>() {
+		public SiteKey get(Long siteId) {
+			return new SiteKey(siteId);
+		}
+	});
+
+	static SiteKey getInstance(long siteId) {
+		return cache.get(siteId);
+	}
+
+	static List<SiteKey> getSiteKeys(String task) throws SQLException {
+		List<SiteKey> list = new ArrayList<SiteKey>();
+		Connection con = Db.dbGlobal().getConnection();
+		PreparedStatement stmt = con.prepareStatement(
+			"select * from task where task = ?"
+			, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE
+		);
+		stmt.setString(1,task);
+		ResultSet rs = stmt.executeQuery();
+		while( rs.next() ) {
+			list.add( getInstance( rs.getLong("site_id") ) );
+			rs.deleteRow();
+		}
+		rs.close();
+		stmt.close();
+		con.close();
+		return list;
+	}
+
+	void addTask(String task) {
+		try {
+			Connection con = Db.dbGlobal().getConnection();
+			try {
+				PreparedStatement stmt = con.prepareStatement(
+					"insert into task (site_id,task) values (?,?)"
+				);
+				stmt.setLong( 1, getId() );
+				stmt.setString( 2, task );
+				DbDatabaseImpl.executeUpdateIgnoringDuplicateKeys(stmt);
+				stmt.close();
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static List<SiteKey> getAllSiteKeys() {
+		try {
+			List<SiteKey> list = new ArrayList<SiteKey>();
+			Connection con = Db.dbGlobal().getConnection();
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"select site_id from site_global"
+			);
+			while( rs.next() ) {
+				list.add( getInstance( rs.getLong("site_id") ) );
+			}
+			rs.close();
+			stmt.close();
+			con.close();
+			return list;
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/Subscription.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,17 @@
+package nabble.model;
+
+
+public interface Subscription {
+
+	public enum To { DESCENDANTS, CHILDREN };
+
+	public enum Type { INSTANT, DAILY_DIGEST };
+
+	public User getSubscriber();
+	public Node getNode();
+	public To getTo();
+	public void setTo(To to);
+	public Type getType();
+	public void setType(Type type);
+	public void delete();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/SubscriptionImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,367 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbUtils;
+import fschmidt.db.Listener;
+import fschmidt.util.java.DateUtils;
+import fschmidt.util.mail.MailHome;
+import nabble.model.lucene.LuceneSearcher;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.web.template.NodeNamespace;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.NodeList;
+import nabble.view.web.template.SubscriptionNamespace;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+
+final class SubscriptionImpl implements Subscription {
+	private static final Logger logger = LoggerFactory.getLogger(SubscriptionImpl.class);
+
+	private final UserImpl subscriber;
+	private final NodeImpl node;
+	private To to;
+	private Type type;
+
+	private SubscriptionImpl(UserImpl subscriber,NodeImpl node,To to,Type type) {
+		this.subscriber = subscriber;
+		this.node = node;
+		this.to = to;
+		this.type = type;
+	}
+
+	private DbDatabase db() {
+		return node.siteKey.getDb();
+	}
+
+	public User getSubscriber() {
+		return subscriber;
+	}
+
+	public Node getNode() {
+		return node;
+	}
+
+	public To getTo() {
+		return to;
+	}
+
+	public void setTo(To to) {
+		if( this.to == to )
+			return;
+		this.to = to;
+		set( "direct_only", to==To.CHILDREN );
+	}
+
+	public Type getType() {
+		return type;
+	}
+
+	public void setType(Type type) {
+		if( this.type == type )
+			return;
+		this.type = type;
+		set( "daily_digest", type==Type.DAILY_DIGEST );
+	}
+
+	private void set(String field,boolean b) {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"update subscription set "+field+"= ? where user_id = ? and node_id = ?"
+			);
+			stmt.setBoolean( 1, b );
+			stmt.setLong( 2, subscriber.getId() );
+			stmt.setLong( 3, node.getId() );
+			stmt.executeUpdate();
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public void delete() {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"delete from subscription where user_id = ? and node_id = ?"
+			);
+			stmt.setLong( 1, subscriber.getId() );
+			stmt.setLong( 2, node.getId() );
+			stmt.executeUpdate();
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static SubscriptionImpl insert(UserImpl subscriber,NodeImpl node,To to,Type type) {
+		SubscriptionImpl subscription = new SubscriptionImpl(subscriber,node,to,type);
+		subscription.insert();
+		return subscription;
+	}
+
+	private void insert() {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"insert into subscription (user_id,node_id,direct_only,daily_digest) values (?,?,?,?)"
+			);
+			stmt.setLong( 1, subscriber.getId() );
+			stmt.setLong( 2, node.getId() );
+			stmt.setBoolean( 3, to==To.CHILDREN );
+			stmt.setBoolean( 4, type==Type.DAILY_DIGEST );
+			stmt.executeUpdate();
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private boolean autoUnsubscribe() {
+		if( subscriber.isAutoUnsubscribe() ) {
+			logger.warn("auto-unsubscribing "+subscriber);
+			delete();
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	static SubscriptionImpl getSubscription(SiteKey siteKey,ResultSet rs) throws SQLException {
+		UserImpl subscriber = UserImpl.getUser( siteKey, rs.getLong("user_id") );
+		NodeImpl node = NodeImpl.getNode( siteKey, rs.getLong("node_id") );
+		To to = rs.getBoolean("direct_only") ? To.CHILDREN : To.DESCENDANTS;
+		Type type = rs.getBoolean("daily_digest") ? Type.DAILY_DIGEST : Type.INSTANT;
+		SubscriptionImpl subscription = new SubscriptionImpl(subscriber,node,to,type);
+		return subscription.autoUnsubscribe() ? null : subscription;
+	}
+
+	static SubscriptionImpl getSubscription(UserImpl subscriber,NodeImpl node) {
+		if( !node.getDbRecord().isInDb() )
+			return null;
+		try {
+			Connection con = node.siteKey.getDb().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select * from subscription where user_id = ? and node_id = ?"
+			);
+			stmt.setLong( 1, subscriber.getId() );
+			stmt.setLong( 2, node.getId() );
+			ResultSet rs = stmt.executeQuery();
+			try {
+				return rs.next() ? SubscriptionImpl.getSubscription(node.siteKey,rs) : null;
+			} finally {
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static boolean isSubscribed(UserImpl subscriber,NodeImpl node) {
+		try {
+			Connection con = node.siteKey.getDb().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select 1 from subscription where user_id = ? and node_id = ?"
+			);
+			stmt.setLong( 1, subscriber.getId() );
+			stmt.setLong( 2, node.getId() );
+			try {
+				return stmt.executeQuery().next();
+			} finally {
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+
+	static {
+		if( Init.hasDaemons && MailHome.isSendingMail() ) {
+
+			NodeImpl.addPostInsertListener( new Listener<NodeImpl>() {
+				public void event(final NodeImpl node) {
+					if( ModelHome.insideImportProcedure.get() )
+						return;
+					DbDatabase db = node.siteKey.getDb();
+					Executors.executeAfterCommit( db, new Runnable(){public void run(){
+						sendInstantMailsFor( DbUtils.getGoodCopy(node) );
+					}});
+				}
+			} );
+
+			long secondsPerDay = 24*60*60;
+			long runTime = 60*60;  // 1 am
+			Date now = new Date();
+			long time = (now.getTime() - DateUtils.roundToDay(now).getTime())/1000;
+			long initialDelay = time < runTime
+				? runTime - time
+				: secondsPerDay - (time - runTime)
+			;
+			Executors.scheduleWithFixedDelay(new Runnable(){public void run(){
+				Date yesterday = DateUtils.addDays( new Date(), -1 );
+				sendDailyDigestsFor(yesterday);
+				ModelHome.lastDigestRun = System.currentTimeMillis();
+			}}, initialDelay, secondsPerDay, TimeUnit.SECONDS );
+
+		}
+	}
+
+	private static final String DAILY_DIGEST_TASK = "daily_digest";
+
+	static Map<User,Subscription> getSubscribersToNotify(NodeImpl node) {
+		Map<User,Subscription> map = new HashMap<User,Subscription>();
+		NodeImpl subscribed = node.getParentImpl();
+		if( subscribed==null )
+			return map;
+		Set<User> subscribers = new HashSet<User>();
+		boolean addedTask = false;
+		for( Subscription subscription : subscribed.getSubscriptions(
+			"select * from subscription"
+			+" where node_id = ?"
+		) ) {
+			if( !addedTask && subscription.getType()==Type.DAILY_DIGEST ) {
+				node.getSiteImpl().addTask(DAILY_DIGEST_TASK);
+				addedTask = true;
+			}
+			User subscriber = subscription.getSubscriber();
+			if( subscribers.add(subscriber) && subscription.getType()==Type.INSTANT )
+				map.put( subscriber, subscription );
+		}
+		while( (subscribed = subscribed.getParentImpl()) != null ) {
+			for( Subscription subscription : subscribed.getSubscriptions(
+				"select * from subscription"
+				+" where node_id = ?"
+				+" and not direct_only"
+			) ) {
+				if( !addedTask && subscription.getType()==Type.DAILY_DIGEST ) {
+					node.getSiteImpl().addTask(DAILY_DIGEST_TASK);
+					addedTask = true;
+				}
+				User subscriber = subscription.getSubscriber();
+				if( subscribers.add(subscriber ) && subscription.getType()==Type.INSTANT )
+					map.put( subscriber, subscription );
+			}
+		}
+		return map;
+	}
+
+	private static void sendInstantMailsFor(NodeImpl node) {
+		if( node==null )
+			return;
+		Template template = node.getSite().getTemplate( "notify_subscribers",
+			BasicNamespace.class, NabbleNamespace.class, NodeNamespace.class
+		);
+		template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(),
+			new BasicNamespace(template),
+			new NabbleNamespace(node.getSite()),
+			new NodeNamespace(node)
+		);
+	}
+
+	private static void sendDailyDigestsFor(Date date) {
+		logger.info("starting daily digests");
+		try {
+			Query day = Lucene.day(date);
+			for( SiteKey siteKey : SiteKey.getSiteKeys(DAILY_DIGEST_TASK) ) {
+				logger.info("daily digests for site = " + siteKey.getId());
+				Connection con = siteKey.getDb().getConnection();
+				Statement stmt = con.createStatement();
+				ResultSet rs = stmt.executeQuery(
+					"select *"
+					+" from subscription"
+					+" where daily_digest"
+				);
+				while( rs.next() ) {
+					SubscriptionImpl subscription = getSubscription(siteKey,rs);
+					if( subscription==null )
+						continue;
+					NodeImpl subscribed = subscription.node;
+					SiteImpl site = subscribed.getSiteImpl();
+					User subscriber = subscription.getSubscriber();
+					List<Node> nodes = new ArrayList<Node>();
+					BooleanQuery query = new BooleanQuery();
+					query.add( day, BooleanClause.Occur.MUST );
+					if( subscription.getTo() == To.CHILDREN ) {
+						query.add( Lucene.children(subscribed), BooleanClause.Occur.MUST );
+					} else {
+						query.add( Lucene.descendants(subscribed), BooleanClause.Occur.MUST );
+						query.add( Lucene.node(subscribed), BooleanClause.Occur.MUST_NOT );
+					}
+					LuceneSearcher searcher = Lucene.newSearcher(subscribed.getSite());
+					try {
+						for( ScoreDoc sdoc : searcher.search(query,1000).scoreDocs ) {
+							Node node = Lucene.getNode( site, searcher, sdoc.doc );
+							if( node != null )
+								nodes.add(node);
+						}
+					} finally {
+						searcher.close();
+					}
+					if( !nodes.isEmpty() ) {
+						Collections.sort(nodes, Node.dateComparator);
+						sendDigestEmail(subscribed, subscriber, nodes);
+					}
+				}
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			logger.error("SQLException", e);
+			throw new RuntimeException(e);
+		} catch(IOException e) {
+			logger.error("IOException", e);
+			throw new RuntimeException(e);
+		}
+		logger.info("finished daily digests");
+	}
+
+	static void nop() {}
+
+
+	private static void sendDigestEmail(Node subscriptionNode, User subscriber, List<Node> nodes) {
+		Template template = subscriptionNode.getSite().getTemplate( "digest email",
+			BasicNamespace.class, NabbleNamespace.class, NodeList.class, SubscriptionNamespace.class
+		);
+		template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(),
+			new BasicNamespace(template),
+			new NabbleNamespace(subscriptionNode.getSite()),
+			new NodeList(nodes,null,false),
+			new SubscriptionNamespace(subscriptionNode, subscriber)
+		);
+	}
+
+	// luan shell
+	public static void testDigest() {
+		sendDailyDigestsFor(new Date());
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/SystemProperties.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,120 @@
+package nabble.model;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Holds a Map with strings.
+ * Basically, this is used to persist keys and values used by the application.
+ * The cache Map is just a copy of the system table in the database.
+ *
+ * @author Hugo Teixeira
+ */
+public class SystemProperties {
+
+	private static final Logger logger = LoggerFactory.getLogger(SystemProperties.class);
+
+	private static final Map<String, String> cache = new TreeMap<String, String>();
+
+	static {
+		load();
+	}
+
+	private static void load() {
+		long start = System.currentTimeMillis();
+		try {
+			Connection con = Db.dbGlobal().getConnection();
+			try {
+				PreparedStatement stmt = con.prepareStatement("select * from system");
+				ResultSet rs = stmt.executeQuery();
+				while (rs.next()) {
+					String key = rs.getString("key_");
+					String value = rs.getString("value");
+					cache.put(key, value);
+				}
+			} finally {
+				con.close();
+			}
+			logger.info("System properties loaded in " + (System.currentTimeMillis() - start) + " ms");
+		} catch(SQLException e) {
+			logger.error(SystemProperties.class.toString(), e);
+		}
+	}
+
+	public static synchronized void set(String key, String value) {
+		try {
+			Connection con = Db.dbGlobal().getConnection();
+			try {
+				if (cache.containsKey(key)) {
+					PreparedStatement stmt = con.prepareStatement("update system set value=? where key_=?");
+					stmt.setString(1, value);
+					stmt.setString(2, key);
+					stmt.execute();
+					stmt.close();
+				} else {
+					PreparedStatement stmt = con.prepareStatement("insert into system values (?,?)");
+					stmt.setString(1, key);
+					stmt.setString(2, value);
+					stmt.execute();
+					stmt.close();
+				}
+			} finally {
+				con.close();
+			}
+			cache.put(key, value);
+			logger.info("System Property added: " + key + " -- " + value);
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public synchronized static String get(String key) {
+		return cache.get(key);
+	}
+
+	public synchronized static int size() {
+		return cache.size();
+	}
+
+	public synchronized static void remove(String key) {
+		try {
+			String sql = "delete from system where key_='" + key + "';";
+			Connection con = Db.dbGlobal().getConnection();
+			try {
+				PreparedStatement stmt = con.prepareStatement(sql);
+				stmt.execute();
+			} finally {
+				con.close();
+			}
+			cache.remove(key);
+			logger.info("System Properties -- Key Removed = " + key);
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public synchronized static void clear() {
+		try {
+			String sql = "delete from system";
+			Connection con = Db.dbGlobal().getConnection();
+			try {
+				PreparedStatement stmt = con.prepareStatement(sql);
+				stmt.execute();
+			} finally {
+				con.close();
+			}
+			cache.clear();
+			logger.info("System Properties cleared");
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/TagImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,138 @@
+package nabble.model;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+import fschmidt.db.postgres.DbDatabaseImpl;
+
+
+final class TagImpl {
+
+	private TagImpl() {}  // never
+
+	static void addTag(Site site,Node node,User user,String label) {
+		try {
+			Connection con = site.getDb().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"insert into tag (node_id,user_id,label) values (?,?,?)"
+			);
+			if( node == null ) {
+				stmt.setNull( 1, Types.INTEGER );
+			} else {
+				stmt.setLong( 1, node.getId() );
+			}
+			if( user == null ) {
+				stmt.setNull( 2, Types.INTEGER );
+			} else {
+				stmt.setLong( 2, user.getId() );
+			}
+			stmt.setString( 3, label );
+			DbDatabaseImpl.executeUpdateIgnoringDuplicateKeys(stmt);
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static int countTags(SiteKey siteKey,String sqlCondition) {
+		try {
+			Connection con = siteKey.getDb().getConnection();
+			try {
+				Statement stmt = con.createStatement();
+				ResultSet rs = stmt.executeQuery(
+					"select count(*) as n from tag where " + sqlCondition
+				);
+				rs.next();
+				int n = rs.getInt("n");
+				rs.close();
+				stmt.close();
+				return n;
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException("sqlCondition = "+sqlCondition,e);
+		}
+	}
+
+	static List<String> findTagLabels(Site site,String sqlCondition) {
+		try {
+			List<String> list = new ArrayList<String>();
+			Connection con = site.getDb().getConnection();
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"select distinct label from tag where " + sqlCondition
+			);
+			while( rs.next() ) {
+				list.add( rs.getString("label") );
+			}
+			rs.close();
+			stmt.close();
+			con.close();
+			return list;
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static List<Long> findTagUserIds(Site site,String sqlCondition) {
+		try {
+			List<Long> list = new ArrayList<Long>();
+			Connection con = site.getDb().getConnection();
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"select distinct user_id from tag where " + sqlCondition
+			);
+			while( rs.next() ) {
+				list.add( rs.getLong("user_id") );
+			}
+			rs.close();
+			stmt.close();
+			con.close();
+			return list;
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static List<Long> findTagNodeIds(Site site,String sqlCondition) {
+		try {
+			List<Long> list = new ArrayList<Long>();
+			Connection con = site.getDb().getConnection();
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"select distinct node_id from tag where " + sqlCondition
+			);
+			while( rs.next() ) {
+				list.add( rs.getLong("node_id") );
+			}
+			rs.close();
+			stmt.close();
+			con.close();
+			return list;
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static void deleteTags(SiteKey siteKey,String sqlCondition) {
+		try {
+			Connection con = siteKey.getDb().getConnection();
+			Statement stmt = con.createStatement();
+			stmt.executeUpdate(
+				"delete from tag where " + sqlCondition
+			);
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/UpdatingException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,20 @@
+package nabble.model;
+
+
+public final class UpdatingException extends RuntimeException {
+
+	UpdatingException() {}
+
+	UpdatingException(String msg) {
+		super(msg);
+	}
+
+	public static boolean isIn(Throwable th) {
+		while( th != null ) {
+			if( th instanceof UpdatingException )
+				return true;
+			th = th.getCause();
+		}
+		return false;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/User.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,86 @@
+/*
+
+Copyright (C) 2003  Franklin Schmidt <frank@gustos.com>
+
+*/
+
+package nabble.model;
+
+import fschmidt.db.DbObject;
+import fschmidt.db.LongKey;
+
+import java.awt.image.BufferedImage;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Map;
+
+
+public interface User extends Person, DbObject<LongKey,UserImpl> {
+	public long getId();
+	public String getEmail();
+	public void setEmail(String email) throws ModelException;
+	public String getPasswordDigest();
+	public void setPasswordDigest(String passwordDigest);
+	public void setPassword(String password) throws ModelException;
+	public void register() throws ModelException;  // insert into db
+	public void register(Date registerDate) throws ModelException;  // insert into db
+	public boolean isRegistered();
+	public void update();
+	public java.util.Date getRegistered();
+	public User getGoodCopy();
+
+	public int getExternalHash(String url); // not used anywhere
+
+	public NodeIterator<? extends Node> getPendingPosts();
+
+	public User setSignature( String signature, Message.Format signatureFormat ) throws ModelException;
+
+	public boolean isSubscribed(Node node);
+	public Subscription getSubscription(Node node);
+	public Subscription subscribe(Node node,Subscription.To to,Subscription.Type type);
+
+	public String getDecoratedAddress(Node node);
+
+	public void deactivate();
+	public boolean isDeactivated();
+
+	public void saveAvatar(BufferedImage smallImage,BufferedImage bigImage) throws ModelException;
+	public void deleteAvatar();
+	public boolean hasAvatar();
+
+	public boolean hasTooManyPosts();
+
+	public static final Comparator<User> BY_NAME_COMPARATOR = new Comparator<User>() {
+		public int compare(User o1, User o2) {
+			return o1.getName().compareToIgnoreCase(o2.getName());
+		}
+	};
+
+	public Node newRootNode(Node.Kind kind,String subject,String message,Message.Format msgFmt,Site site,String type) throws ModelException;  // will be already inserted into db
+
+	public Comparator<User> nodeCountComparatorDesc = new Comparator<User>(){
+		public int compare(User u1,User u2) {
+			return u2.getNodeCount(null) - u1.getNodeCount(null);
+		}
+	};
+
+	public void moveToRegisteredAccount(String cookie);
+	public <T> T getExtension(ExtensionFactory<User,T> factory);
+
+	public Long lastVisitedNodeId(long nodeId);
+	public Map<Long,Long> lastVisitedNodeIds(Collection<Long> nodeIds);
+	public void markVisited(Node node, long lastNodeId);
+	public void unmarkVisited(Node node);
+
+	public String getProperty(String key);
+	public void setProperty(String key,String value);
+
+	public String getPasscookie();
+	public boolean checkPassword(String password);
+	public boolean checkPasscookie(String passcookie);
+	public String getResetcode();
+	public boolean checkResetcode(String resetcode);
+
+	public void setNoArchive(boolean noArchive);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/UserImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1355 @@
+/*
+
+Copyright (C) 2003  Franklin Schmidt <frank@gustos.com>
+
+*/
+
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbNull;
+import fschmidt.db.DbObjectFactory;
+import fschmidt.db.DbRecord;
+import fschmidt.db.DbTable;
+import fschmidt.db.DbUtils;
+import fschmidt.db.Listener;
+import fschmidt.db.ListenerList;
+import fschmidt.db.LongKey;
+import fschmidt.db.postgres.DbDatabaseImpl;
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.Memoizer;
+import fschmidt.util.java.ObjectUtils;
+import fschmidt.util.java.SimpleCache;
+import fschmidt.util.java.TimedCacheMap;
+import fschmidt.util.mail.MailAddress;
+import org.jasypt.digest.PooledStringDigester;
+import org.jasypt.salt.FixedByteArraySaltGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.awt.image.BufferedImage;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+
+final class UserImpl extends PersonImpl implements User {
+	private static final Logger logger = LoggerFactory.getLogger(UserImpl.class);
+
+	final SiteKey siteKey;
+	private final DbRecord<LongKey,UserImpl> record;
+	private String email;
+	private String passwordDigest;
+	private String name;
+	private Date registered;
+	private boolean noArchive;
+	private Message signature = null;
+	private int bounces;
+
+	private UserImpl(SiteKey siteKey,LongKey key,ResultSet rs)
+		throws SQLException
+	{
+		this.siteKey = siteKey;
+		record = table(siteKey).newRecord(this,key);
+		email = rs.getString("email");
+		passwordDigest = rs.getString("password_digest");
+		name = rs.getString("name");
+		registered = DbUtils.getDate(rs,"registered");
+		noArchive = rs.getBoolean("no_archive");
+		String signatureRaw = rs.getString("signature");
+		String signatureFormatS = rs.getString("signature_format");
+		if( signatureRaw!=null && signatureFormatS!=null ) {
+			Message.Format signatureFormat = Message.Format.getMessageFormat( signatureFormatS.charAt(0) );
+			signature = new Message(signatureRaw,signatureFormat);
+		}
+		bounces = rs.getInt("bounces");
+		for( ExtensionFactory<User,?> factory : extensionFactories ) {
+			Object obj = factory.construct(this,rs);
+			if( obj != null )
+				getExtensionMap().put(factory,obj);
+		}
+	}
+
+	private UserImpl(SiteImpl site) {
+		this.siteKey = site.siteKey;
+		record = table(siteKey).newRecord(this);
+	}
+
+
+	public DbRecord<LongKey,UserImpl> getDbRecord() {
+		return record;
+	}
+
+	private DbTable<LongKey,UserImpl> table() {
+		return record.getDbTable();
+	}
+
+	private DbDatabase db() {
+		return table().getDbDatabase();
+	}
+
+	public long getId() {
+		return record.getPrimaryKey().value();
+	}
+
+	SiteImpl getSiteImpl() {
+		return siteKey.site();
+	}
+
+	public Site getSite() {
+		return getSiteImpl();
+	}
+
+	public boolean isDeactivated() {
+		return !isRegistered() && isNoArchive();
+	}
+
+	boolean isNoArchive() {
+		return noArchive;
+	}
+
+	public void setNoArchive(boolean noArchive) {
+		if( this.noArchive == noArchive )
+			return;
+
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				UserImpl user = DbUtils.getGoodCopy(this);
+				user.setNoArchive(noArchive);
+				user.getDbRecord().update();
+				db().commitTransaction();
+				return;
+			} finally {
+				db().endTransaction();
+			}
+		}
+		this.noArchive = noArchive;
+		record.fields().put("no_archive",DbNull.fix(noArchive));
+	}
+
+	public String getEmail() {
+		return email;
+	}
+
+	static void validateEmail(String email) throws ModelException.EmailFormat {
+		if (!new MailAddress(email).isValid()) {
+			throw new ModelException.EmailFormat(email);
+		}
+	}
+
+	public void setEmail(String email) throws ModelException {
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				UserImpl user = DbUtils.getGoodCopy(this);
+				user.setEmail(email);
+				user.getDbRecord().update();
+				db().commitTransaction();
+				return;
+			} finally {
+				db().endTransaction();
+			}
+		}
+		validateEmail(email);
+		setEmail2(email);
+	}
+
+	private void setEmail2(String email) throws ModelException {
+		if( email.equals(this.email) )
+			return;
+		SiteImpl site = getSiteImpl();
+		if( site.getUserImplFromEmail(email) != null )
+			throw ModelException.newInstance("email_already_in_user","Email already in use");
+		this.email = email;
+		record.fields().put("email",email);
+	}
+
+	public String getPasswordDigest() {
+		return passwordDigest;
+	}
+
+	public void setPassword(String password) throws ModelException {
+		if( "".equals(password) )
+			throw ModelException.newInstance("empty_password","Password cannot be empty");
+		setPasswordDigest(digestPassword(password));
+	}
+
+	public void setPasswordDigest(String passwordDigest) {
+		if( ObjectUtils.equals(passwordDigest,this.passwordDigest) )
+			return;
+		this.passwordDigest = passwordDigest;
+		record.fields().put("password_digest",DbNull.fix(passwordDigest));
+		synchronized (passcookieLock) {
+			this.passcookie = null;
+		}
+	}
+
+	private volatile String passcookie = null;
+	private Object passcookieLock = new Object();
+
+	public String getPasscookie() {
+		String p = passcookie;
+		if (p==null) {
+			synchronized (passcookieLock) {
+				p = passcookie;
+				if (p==null) {
+					p = calcPasscookie();
+					passcookie = p;
+				}
+			}
+		}
+		return p;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) throws ModelException {
+		setName(name,true);
+	}
+
+	private void setName(String name,boolean replaceUnregistered) throws ModelException {
+		name = name.trim();
+		if( name.equals("") )
+			throw ModelException.newInstance("empty_user_name","User name cannot be empty.");
+		if( name.equals(this.name) )
+			return;
+		if( !name.equalsIgnoreCase(this.name) ) {
+			UserImpl user = getSiteImpl().getUserImplFromName(name);
+			if( user != null ) {
+				if( !replaceUnregistered || user.isRegistered() )
+					throw ModelException.newInstance("user_name_already_in_use","User name '"+name+"' already in use");
+				user.setNameLike2(name);
+				user.update();
+			}
+			try {
+				Connection con = db().getConnection();
+				PreparedStatement stmt = con.prepareStatement(
+					"select 'x' from registration where email!=? and name=?"
+				);
+				stmt.setString(1,this.email);
+				stmt.setString(2,name);
+				try {
+					if( stmt.executeQuery().next() )
+						throw ModelException.newInstance("user_name_already_in_use","User name '"+name+"' already in use");
+				} finally {
+					stmt.close();
+					con.close();
+				}
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		this.name = name;
+		record.fields().put("name",name);
+	}
+
+	void setNameLike(String name,boolean replaceUnregistered) {
+		try {
+			setName(name,replaceUnregistered);
+		} catch(ModelException e) {
+			setNameLike2(name);
+		}
+	}
+
+	private void setNameLike2(String name) {
+		for( int i=2; true; i++ ) {
+			try {
+				setName(name+"-"+i,false);
+				break;
+			} catch(ModelException e2) {}
+		}
+	}
+
+	/* To be called from the shell */
+	public void changeNameTo(String newName) {
+		db().beginTransaction();
+		try {
+			UserImpl u = (UserImpl) getGoodCopy();
+			u.setName(newName);
+			u.update();
+			db().commitTransaction();
+			DbUtils.uncache(u);
+		} catch (ModelException e) {
+			throw new RuntimeException(e);
+		} finally {
+			db().endTransaction();
+		}
+	}
+
+	public Date getRegistered() {
+		return registered;
+	}
+
+	void setRegistered(Date registered) {
+		if( ObjectUtils.equals(registered,this.registered) )
+			return;
+		this.registered = registered;
+		record.fields().put("registered",DbNull.fix(registered));
+	}
+
+	public boolean equals(Object obj) {
+		return obj instanceof User && ((User)obj).getId()==getId();
+	}
+
+	public int hashCode() {
+		return (int)getId();
+	}
+
+	public String toString() {
+		return record.isInDb() ? "user-"+getId() : "user-new";
+	}
+
+	public void register() throws ModelException {
+		register(new Date());
+	}
+
+	public void register(Date registerDate) throws ModelException {
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				UserImpl user;
+				if( record.isInDb() ) {
+					user = DbUtils.getGoodCopy(this);
+					user.setEmail(email);
+					user.setName(name);
+					user.setPasswordDigest(passwordDigest);
+				} else {
+					user = this;
+				}
+				user.register();
+				db().commitTransaction();
+			} finally {
+				db().endTransaction();
+			}
+			return;
+		}
+		if( passwordDigest==null )
+			throw new RuntimeException();
+		setRegistered( registerDate );
+		if( record.isInDb() ) {
+			if( isNoArchive() )
+				setNoArchive(false);
+			record.update();
+		} else {
+			insert();
+		}
+	}
+
+	public boolean isRegistered() {
+		return record.isInDb() && registered!=null;
+	}
+
+	void insert() {
+		if( email==null || name==null )
+			throw new RuntimeException();
+		record.insert();
+	}
+
+	public void update() {
+		if( !db().isInTransaction() )
+			throw new RuntimeException("this should be done in a transaction");
+		Set<String> keys = record.fields().keySet();
+		if( keys.contains("name") || keys.contains("signature") ) {
+			getSiteImpl().update();  // fire change listeners
+		}
+		getDbRecord().update();
+	}
+
+	public User getGoodCopy() {
+		return DbUtils.getGoodCopy(this);
+	}
+
+
+
+
+	public int getExternalHash(String url) {
+		return (url.toLowerCase() + getId()).hashCode();
+	}
+
+
+	static final ListenerList<UserImpl> preUpdateListeners = new ListenerList<UserImpl>();
+	static final ListenerList<UserImpl> postInsertListeners = new ListenerList<UserImpl>();
+
+	private static Computable<SiteKey,DbTable<LongKey,UserImpl>> tables = new SimpleCache<SiteKey,DbTable<LongKey,UserImpl>>(new WeakHashMap<SiteKey,DbTable<LongKey,UserImpl>>(), new Computable<SiteKey,DbTable<LongKey,UserImpl>>() {
+		public DbTable<LongKey,UserImpl> get(SiteKey siteKey) {
+			DbDatabase db = siteKey.getDb();
+			final long siteId = siteKey.getId();
+			DbTable<LongKey,UserImpl> table = db.newTable("user_",db.newIdentityLongKeySetter("user_id")
+				, new DbObjectFactory<LongKey,UserImpl>() {
+					public UserImpl makeDbObject(LongKey key,ResultSet rs,String tableName)
+						throws SQLException
+					{
+						SiteKey siteKey = SiteKey.getInstance(siteId);
+						return new UserImpl(siteKey,key,rs);
+					}
+				}
+			);
+			table.getPreUpdateListeners().add(preUpdateListeners);
+			table.getPostInsertListeners().add(postInsertListeners);
+			return table;
+		}
+	});
+
+	private static DbTable<LongKey,UserImpl> table(SiteKey siteKey) {
+		return tables.get(siteKey);
+	}
+
+	static UserImpl getUser(SiteKey siteKey,long id) {
+		UserImpl user = table(siteKey).findByPrimaryKey(new LongKey(id));
+		if( user==null )
+			logger.warn("user "+id+" not found");
+		return user;
+	}
+
+	static Collection<UserImpl> getUsers(SiteKey siteKey,Collection<Long> ids) {
+		List<LongKey> list = new ArrayList<LongKey>();
+		for( long id : ids ) {
+			list.add( new LongKey(id) );
+		}
+		return table(siteKey).findByPrimaryKey(list).values();
+	}
+
+	static UserImpl getUser(SiteKey siteKey,ResultSet rs)
+		throws SQLException
+	{
+		return table(siteKey).getDbObject(rs);
+	}
+
+	static void getUsers(SiteKey siteKey,PreparedStatement stmt,List<? super UserImpl> list)
+		throws SQLException
+	{
+		ResultSet rs = stmt.executeQuery();
+		while( rs.next() ) {
+			UserImpl user = getUser(siteKey,rs);
+			list.add(user);
+		}
+		rs.close();
+		stmt.close();
+	}
+
+	static List<UserImpl> getUsers(SiteKey siteKey,PreparedStatement stmt)
+		throws SQLException
+	{
+		List<UserImpl> list = new ArrayList<UserImpl>();
+		getUsers(siteKey,stmt,list);
+		return list;
+	}
+
+	private static UserImpl getUser(SiteImpl site,String val,String sql) {
+		try {
+			SiteKey siteKey = site.siteKey;
+			Connection con = siteKey.getDb().getConnection();
+			PreparedStatement stmt = con.prepareStatement(sql);
+			stmt.setString(1,val);
+			ResultSet rs = stmt.executeQuery();
+			UserImpl user = rs.next() ? getUser(siteKey,rs) : null;
+			rs.close();
+			stmt.close();
+			con.close();
+			return user;
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static UserImpl getUserFromEmail(SiteImpl site,String email) {
+		return getUser( site, email.toLowerCase(),
+			"select * from user_"
+			+" where lower(email)=?"
+		);
+	}
+
+	static UserImpl getUserFromName(SiteImpl site,String name) {
+		return getUser( site, name.toLowerCase(),
+			"select * from user_"
+			+" where lower(name)=?"
+		);
+	}
+
+	static UserImpl createGhost(SiteImpl site,String email) {
+		UserImpl user = new UserImpl(site);
+		try {
+			user.setEmail2(email);
+		} catch(ModelException e) {
+			throw new RuntimeException(e);
+		}
+		return user;
+	}
+
+	// Subscriptions -----------------------------------------------------------
+
+	public boolean isSubscribed(Node node) {
+		return SubscriptionImpl.isSubscribed(this, (NodeImpl) node);
+	}
+
+	public Subscription getSubscription(Node node) {
+		return SubscriptionImpl.getSubscription( this, (NodeImpl)node );
+	}
+
+	public Subscription subscribe(Node node,Subscription.To to,Subscription.Type type) {
+		clearBounces();
+		Subscription subscription = getSubscription(node);
+		if( subscription != null ) {
+			subscription.setTo(to);
+			subscription.setType(type);
+			return subscription;
+		} else {
+			return SubscriptionImpl.insert( this, (NodeImpl)node, to, type );
+		}
+	}
+
+	/*10 posts in 5 minutes */
+	private static final RecentPostLimit postLimit1 = new RecentPostLimit(5 * 60 * 1000L, 10);
+
+	/* 30 posts in 15 minutes */
+	private static final RecentPostLimit postLimit2 = new RecentPostLimit(15 * 60 * 1000L, 30);
+
+	void updateNewPostLimit() {
+		String key = siteKey.getId() + "-" + record.getPrimaryKey().value();
+		postLimit1.insert(key);
+		postLimit2.insert(key);
+	}
+
+	public boolean hasTooManyPosts() {
+		String key = siteKey.getId() + "-" + record.getPrimaryKey().value();
+		return postLimit1.hasTooManyPosts(key) || postLimit2.hasTooManyPosts(key);
+	}
+
+	private static class RecentPostLimit {
+		private final long timeLimit;
+		private final int postLimit;
+		private final Map<String,long[]> floodMap;
+
+		private RecentPostLimit(long timeLimit, int postLimit) {
+			this.timeLimit = timeLimit;
+			this.postLimit = postLimit;
+			this.floodMap = new TimedCacheMap<String,long[]>(timeLimit);
+		}
+
+		public void insert(String key) {
+			long[] recentPostTimes;
+			synchronized(floodMap) {
+				recentPostTimes = floodMap.get(key);
+				if( recentPostTimes==null ) {
+					recentPostTimes = new long[postLimit];
+					floodMap.put(key,recentPostTimes);
+				}
+			}
+			long now = System.currentTimeMillis();
+			long recently = now - timeLimit;
+			synchronized(recentPostTimes) {
+				for( int i=0; i<recentPostTimes.length; i++ ) {
+					if( recentPostTimes[i] < recently ) {
+						recentPostTimes[i] = now;
+						return;
+					}
+				}
+			}
+		}
+
+		public boolean hasTooManyPosts(String key) {
+			long[] recentPostTimes;
+			synchronized(floodMap) {
+				recentPostTimes = floodMap.get(key);
+				if (recentPostTimes==null)
+					return false;
+			}
+			long now = System.currentTimeMillis();
+			long recently = now - timeLimit;
+			synchronized(recentPostTimes) {
+				for (long time : recentPostTimes) {
+					if (time < recently) {
+						return false;
+					}
+				}
+			}
+			return true;
+		}
+	}
+
+
+	static UserImpl getOrCreateUnregisteredUser(SiteImpl site,String email,String name)
+		throws ModelException
+	{
+		DbDatabase db = site.getDb();
+		if( !db.isInTransaction() ) {
+			db.beginTransaction();
+			try {
+				UserImpl user = getOrCreateUnregisteredUser(site,email,name);
+				db.commitTransaction();
+				return user;
+			} finally {
+				db.endTransaction();
+			}
+		}
+		UserImpl user = site.getUserImplFromEmail(email);
+		if( user==null ) {
+			user = new UserImpl(site);
+			user.setEmail(email);
+		} else {
+			if( user.isRegistered() )
+				throw ModelException.newInstance("email_already_registered","This email is already registered");
+			validateEmail(user.getEmail());
+		}
+		user.setName(name);
+		if( !user.record.isInDb() ) {
+			user.insert();
+		} else if( !user.record.fields().isEmpty() ) {
+			user.update();
+		}
+		return user;
+	}
+
+	// registration
+
+	static UserImpl createUser(SiteImpl site,String email,String password,String name) throws ModelException {
+		return createUser2(site, email, digestPassword(password), name);
+	}
+
+	private static UserImpl createUser2(SiteImpl site,String email,String passwordDigest,String name) throws ModelException {
+		// transaction used because setName() may update user
+		DbDatabase db = site.getDb();
+		if( !db.isInTransaction() ) {
+			db.beginTransaction();
+			try {
+				UserImpl user = createUser2(site,email,passwordDigest,name);
+				db.commitTransaction();
+				return user;
+			} finally {
+				db.endTransaction();
+			}
+		}
+		if (!new MailAddress(email).isValid()) {
+			throw new ModelException.EmailFormat("invalid_email");
+		}
+		UserImpl user = site.getUserImplFromEmail(email);
+		if( user==null ) {
+			user = new UserImpl(site);
+			user.setEmail(email);
+		} else {
+			if( user.isRegistered() )
+				throw ModelException.newInstance("user_already_registered","User is already registered");
+			validateEmail(user.getEmail());
+		}
+		user.setPasswordDigest(passwordDigest);
+		user.setName(name);
+		return user;
+	}
+
+	static UserImpl getOrCreateUser(SiteImpl site,String email) {
+		UserImpl user = site.getUserImplFromEmail(email);
+		if (user == null) {
+			String username = email.substring(0, email.indexOf('@'));
+			user = createGhost(site,email);
+			user.setNameLike(username, false);
+			user.insert();
+		}
+		return user;
+	}
+
+	private static final Object regLock = new Object();
+
+	String newRegistration(String nextUrl) {
+		if( nextUrl.equals("null") )
+			throw new RuntimeException("nextUrl is \"null\"");
+		synchronized(regLock) {
+			String key;
+			try {
+				Connection con = db().getConnection();
+				{
+					PreparedStatement stmt = con.prepareStatement(
+						"select 'x' from registration where key_=?"
+					);
+					do {
+						key = Double.toString(Math.random());
+						stmt.setString(1,key);
+					} while( stmt.executeQuery().next() );
+					stmt.close();
+				}
+				{
+					PreparedStatement stmt = con.prepareStatement(
+						"insert into registration"
+						+" ( key_, email, password_digest, name, next_url ) values (?,?,?,?,?)"
+					);
+					int i = 0;
+					stmt.setString(++i,key);
+					stmt.setString(++i,getEmail());
+					stmt.setString(++i,getPasswordDigest());
+					stmt.setString(++i,getName());
+					stmt.setString(++i,nextUrl);
+					stmt.executeUpdate();
+					stmt.close();
+				}
+				{
+					Statement stmt = con.createStatement();
+					stmt.executeUpdate(
+						"delete from registration where date_<" + Db.arcana.dateSub("now()",7,"day")
+					);
+					stmt.close();
+				}
+				con.close();
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+			return key;
+		}
+	}
+
+	static User getRegistration(SiteImpl site,String registrationKey)
+		throws ModelException
+	{
+		try {
+			DbDatabase db = site.getDb();
+			Connection con = db.getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select * from registration where key_=?"
+			);
+			stmt.setString(1,registrationKey);
+			ResultSet rs = stmt.executeQuery();
+			try {
+				if( !rs.next() )
+					return null;
+				String email = rs.getString("email");
+				String passwordDigest = rs.getString("password_digest");
+				String name = rs.getString("name");
+				return createUser2(site,email,passwordDigest,name);
+			} finally {
+				rs.close();
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	static String getNextUrl(SiteKey siteKey,String registrationKey)
+	{
+		try {
+			Connection con = siteKey.getDb().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"select next_url from registration where key_=?"
+			);
+			stmt.setString(1,registrationKey);
+			ResultSet rs = stmt.executeQuery();
+			try {
+				if( !rs.next() )
+					return null;
+				return rs.getString("next_url");
+			} finally {
+				rs.close();
+				stmt.close();
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	// Called from beanshell
+	private static void deletePendingRegistration(Site site,String email, String username) {
+		try {
+			Connection con = site.getDb().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"delete from registration where email=? or name = ?"
+			);
+			stmt.setString(1,email);
+			stmt.setString(2,username);
+			stmt.executeUpdate();
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	public void deactivate() {
+		db().beginTransaction();
+		try {
+			UserImpl user = DbUtils.getGoodCopy(this);
+			user.setNoArchive(true);
+			user.setRegistered(null);
+			user.setPasswordDigest(null);
+			user.record.update();
+			db().commitTransaction();
+			logger.info("User removed his/her account: " + getEmail());
+		} finally {
+			db().endTransaction();
+		}
+	}
+
+	private DbParamSetter simpleParamSetter() {
+		return new DbParamSetter() {
+			public void setParams(PreparedStatement stmt) throws SQLException {
+				stmt.setLong( 1, getId() );
+			}
+		};
+	}
+
+	public NodeIterator<? extends Node> getPendingPosts() {
+		return new CursorNodeIterator( siteKey,
+				"select *"
+				+" from node"
+				+" where owner_id = ?"
+				+" and when_sent is not null"
+				+" order by when_sent"
+			, simpleParamSetter()
+		);
+	}
+
+	public Message getSignature() {
+		return signature;
+	}
+
+	public User setSignature( String signatureRaw, Message.Format signatureFormat )
+		throws ModelException
+	{
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				UserImpl user = DbUtils.getGoodCopy(this);
+				user.setSignature(signatureRaw,signatureFormat);
+				user.getDbRecord().update();
+				db().commitTransaction();
+				return DbUtils.getGoodCopy(user);
+			} finally {
+				db().endTransaction();
+			}
+		}
+		if( signatureRaw==null || signatureRaw.trim().length()==0 ) {
+			if( signature != null ) {
+				signature = null;
+				record.fields().put("signature",DbNull.STRING);
+				record.fields().put("signature_format",DbNull.STRING);
+			}
+		} else {
+			Message newSignature = new Message(signatureRaw,signatureFormat);
+			if( !newSignature.equals(signature) ) {
+				signature = newSignature;
+				record.fields().put("signature",signatureRaw);
+				record.fields().put("signature_format",Character.toString(signatureFormat.getCode()));
+			}
+		}
+		return this;
+	}
+
+
+	public String getDecoratedAddress(Node node) {
+		return PostByEmail.getMailAddress(this, node);
+	}
+
+	public void saveAvatar(BufferedImage smallImage,BufferedImage bigImage) throws ModelException {
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				DbUtils.getGoodCopy(this).saveAvatar(smallImage,bigImage);
+				db().commitTransaction();
+			} finally {
+				db().endTransaction();
+			}
+			return;
+		}
+		Message.AvatarSource as = new Message.AvatarSource(this);
+		FileUpload.saveImage(smallImage,ModelHome.AVATAR_SMALL,as);
+		FileUpload.saveImage(bigImage,ModelHome.AVATAR_BIG,as);
+		getSiteImpl().update();  // fire change listeners
+		DbUtils.uncache(this);
+	}
+
+	public void deleteAvatar() {
+		if( !db().isInTransaction() ) {
+			db().beginTransaction();
+			try {
+				DbUtils.getGoodCopy(this).deleteAvatar();
+				db().commitTransaction();
+			} finally {
+				db().endTransaction();
+			}
+			return;
+		}
+		final Message.AvatarSource as = new Message.AvatarSource(this);
+		FileUpload.deleteFile(ModelHome.AVATAR_SMALL,as);
+		FileUpload.deleteFile(ModelHome.AVATAR_BIG,as);
+		getSiteImpl().update();  // fire change listeners
+		db().runAfterCommit(new Runnable(){public void run(){
+			FileUpload.fireFileUpdateListeners(as);
+		}});
+		DbUtils.uncache(this);
+	}
+
+	private boolean hasAvatar;
+	private boolean checkedAvatar = false;
+
+	public synchronized boolean hasAvatar() {
+		if( !checkedAvatar ) {
+			Message.AvatarSource as = new Message.AvatarSource(this);
+			hasAvatar = FileUpload.hasFile(as,ModelHome.AVATAR_SMALL) && FileUpload.hasFile(as,ModelHome.AVATAR_BIG);
+			checkedAvatar = true;
+		}
+		return hasAvatar;
+	}
+
+
+
+	public Node newRootNode(Node.Kind kind,String subject,String message,Message.Format msgFmt,Site site,String type) throws ModelException {
+		return NodeImpl.newRootNode(kind,this,subject,message,msgFmt,(SiteImpl)site,type);
+	}
+
+	public Node newChildNode(Node.Kind kind,String subject,String message,Message.Format msgFmt,Node parent) throws ModelException {
+		return NodeImpl.newChildNode(kind,this,subject,message,msgFmt,(NodeImpl)parent);
+	}
+
+	public String getSearchId() {
+		return Long.toString(getId());
+	}
+
+	public String getIdString() {
+		return Long.toString(getId());
+	}
+
+	private void clearBounces() {
+		if( bounces==0 )
+			return;
+		bounces = 0;
+		record.fields().put("bounces",DbNull.INTEGER);
+		record.update();
+	}
+
+	void bounced() {
+		record.fields().put("bounces",++bounces);
+		record.update();
+	}
+
+	int getBounces() {
+		return bounces;
+	}
+
+	private static final int bounceLimit = Init.get("bounceLimit",100);
+
+	boolean isAutoUnsubscribe() {
+		return isDeactivated() || bounces > bounceLimit;
+	}
+
+
+
+
+	private volatile Map<String, Integer> nodeCount = new HashMap<String, Integer>();
+
+	public final int getNodeCount(String cnd) {
+		String key = cnd == null? "none" : cnd;
+		if (!nodeCount.containsKey(key)) {
+			try {
+				Connection con = db().getConnection();
+				PreparedStatement stmt = con.prepareStatement(
+					"select count(*) as n from node where owner_id = ?" +
+					(cnd == null? "" : " and " + cnd)
+				);
+				stmt.setLong(1,getId());
+				ResultSet rs = stmt.executeQuery();
+				rs.next();
+				nodeCount.put(key, rs.getInt("n"));
+				rs.close();
+				stmt.close();
+				con.close();
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		return nodeCount.get(key);
+	}
+
+	void setNodeCount(int nodeCount) {
+		this.nodeCount.put("none", nodeCount);
+	}
+
+	static {
+		Listener<NodeImpl> listener = new Listener<NodeImpl>() {
+			public void event(NodeImpl node) {
+				table(node.siteKey).uncache(new LongKey(node.getOwnerId()));
+			}
+		};
+		NodeImpl.postInsertListeners.add(listener);
+		NodeImpl.postDeleteListeners.add(listener);
+	}
+
+
+	public void moveToRegisteredAccount(final String cookie) {
+		List<NodeImpl> nodes = new CursorNodeIterator( siteKey,
+				"select * from node where cookie=?"
+			,
+				new DbParamSetter() {
+					public void setParams(PreparedStatement stmt) throws SQLException {
+						stmt.setString(1,cookie);
+					}
+				}
+		).asList();
+		for( NodeImpl n : nodes ) {
+			n.setOwner(this);
+			n.update();
+		}
+	}
+
+
+	public NodeIterator<? extends Node> getNodesByDateDesc(String cnd) {
+		return new CursorNodeIterator( siteKey,
+				"select * from node where owner_id = ?" +
+				(cnd == null? "" : " and " + cnd) +
+				" order by when_created desc"
+			,
+				new DbParamSetter() {
+					public void setParams(PreparedStatement stmt) throws SQLException {
+						stmt.setLong( 1, getId() );
+					}
+				}
+		);
+	}
+
+
+	public int deleteNodes() {
+		List<NodeImpl> nodes = new CursorNodeIterator( siteKey,
+				"select *"
+				+" from node"
+				+" where owner_id = ?"
+			, simpleParamSetter()
+		).asList();
+		int n = 0;
+		for( NodeImpl node : nodes ) {
+			db().beginTransaction();
+			try {
+				DbUtils.getGoodCopy(node).deleteMessageOrNode();
+				db().commitTransaction();
+				n++;
+			} finally {
+				db().endTransaction();
+			}
+		}
+		return n;
+	}
+
+	public int deleteNodesRecursively() {
+		List<NodeImpl> nodes = new CursorNodeIterator( siteKey,
+				"select *"
+				+" from node"
+				+" where owner_id = ?"
+			, simpleParamSetter()
+		).asList();
+		int n = 0;
+		for( NodeImpl node : nodes ) {
+			db().beginTransaction();
+			try {
+				DbUtils.getGoodCopy(node).deleteRecursively();
+				db().commitTransaction();
+				n++;
+			} finally {
+				db().endTransaction();
+			}
+		}
+		return n;
+	}
+
+
+	private Map<ExtensionFactory<User,?>,Object> extensionMap;
+
+	private synchronized Map<ExtensionFactory<User, ?>, Object> getExtensionMap() {
+		if (extensionMap == null)
+			extensionMap = new HashMap<ExtensionFactory<User, ?>, Object>();
+		return extensionMap;
+	}
+
+	public <T> T getExtension(ExtensionFactory<User,T> factory) {
+		synchronized(getExtensionMap()) {
+			Object obj = extensionMap.get(factory);
+			if( obj == null ) {
+				obj = factory.construct(this);
+				if( obj != null )
+					extensionMap.put(factory,obj);
+			}
+			return factory.extensionClass().cast(obj);
+		}
+	}
+
+	private static Collection<ExtensionFactory<User,?>> extensionFactories = new CopyOnWriteArrayList<ExtensionFactory<User,?>>();
+
+	static <T> void addExtensionFactory(ExtensionFactory<User,T> factory) {
+		extensionFactories.add(factory);
+		Db.clearCache();
+	}
+
+
+
+
+	// visited node
+
+	private final Map<Long,Long> visitedNodeCache = new HashMap<Long,Long>();
+
+	public Long lastVisitedNodeId(long nodeId) {
+		synchronized(visitedNodeCache) {
+			return visitedNodeCache.containsKey(nodeId) ? visitedNodeCache.get(nodeId) : lastVisitedNodeIds(Collections.singletonList(nodeId)).get(nodeId);
+		}
+	}
+
+	public Map<Long,Long> lastVisitedNodeIds(Collection<Long> nodeIds) {
+		synchronized(visitedNodeCache) {
+			Set<Long> notCached = new HashSet<Long>();
+			for( Long nodeId : nodeIds ) {
+				if( !visitedNodeCache.containsKey(nodeId) )
+					notCached.add(nodeId);
+			}
+			if( !notCached.isEmpty() ) {
+				StringBuilder sql = new StringBuilder();
+				sql
+					.append( "select node_id, last_node_id from visited where user_id = " )
+					.append( getId() )
+					.append( " and node_id in (" )
+				;
+				Iterator<Long> iter = notCached.iterator();
+				sql.append( iter.next() );
+				while( iter.hasNext() ) {
+					sql.append( ',' ).append( iter.next() );
+				}
+				sql.append( ")" );
+				try {
+					Connection con = db().getConnection();
+					Statement stmt = con.createStatement();
+					ResultSet rs = stmt.executeQuery(sql.toString());
+					while( rs.next() ) {
+						Long nodeId = rs.getLong("node_id");
+						Long lastNodeId = rs.getLong("last_node_id");
+						visitedNodeCache.put(nodeId,lastNodeId);
+						notCached.remove(nodeId);
+					}
+					rs.close();
+					stmt.close();
+					con.close();
+				} catch(SQLException e) {
+					throw new RuntimeException(e);
+				}
+				for( Long nodeId : notCached ) {
+					visitedNodeCache.put(nodeId,null);
+				}
+			}
+			Map<Long,Long> map = new HashMap<Long,Long>();
+			for( Long nodeId : nodeIds ) {
+				map.put( nodeId, visitedNodeCache.get(nodeId) );
+			}
+			return map;
+		}
+	}
+
+	public void markVisited(Node topic, long lastNodeId) {
+		NodeImpl topicNode = (NodeImpl)topic;
+		long nodeId = topicNode.getId();
+		boolean updated = false;
+		try {
+			Connection con = db().getConnection();
+			try {
+				Long persistedLastVisitedNodeId = lastVisitedNodeId(nodeId);
+				if( persistedLastVisitedNodeId == null ) {
+					PreparedStatement stmt = con.prepareStatement(
+						"insert into visited (user_id, node_id, last_node_id)"
+						+" values (?, ?, ?)"
+					);
+					stmt.setLong( 1, getId() );
+					stmt.setLong( 2, nodeId );
+					stmt.setLong( 3, lastNodeId );
+					DbDatabaseImpl.executeUpdateIgnoringDuplicateKeys(stmt);
+					stmt.close();
+					updated = true;
+				} else if (lastNodeId > persistedLastVisitedNodeId) {
+					PreparedStatement stmt = con.prepareStatement(
+						"update visited set last_node_id = ?"
+						+" where user_id = ? and node_id = ?"
+					);
+					stmt.setLong( 1, lastNodeId );
+					stmt.setLong( 2, getId() );
+					stmt.setLong( 3, nodeId );
+					stmt.executeUpdate();
+					stmt.close();
+					updated = true;
+				}
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			if( !e.getMessage().contains("violates foreign key constraint \"visited_last_node_id_fkey\"") )
+				throw new RuntimeException(e);
+		}
+		if (updated) {
+			synchronized(visitedNodeCache) {
+				visitedNodeCache.remove(nodeId);
+			}
+		}
+	}
+
+	public void unmarkVisited(Node node) {
+		long nodeId = node.getId();
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"delete from visited"
+				+" where user_id = ? and node_id = ?"
+			);
+			stmt.setLong( 1, getId() );
+			stmt.setLong( 2, nodeId );
+			stmt.executeUpdate();
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+		synchronized(visitedNodeCache) {
+			visitedNodeCache.remove(nodeId);
+		}
+	}
+
+
+	static void addPostInsertListener(final Listener<? super UserImpl> listener) {
+		postInsertListeners.add(listener);
+	}
+
+
+
+	private final Memoizer<String,String> propertyCache = new Memoizer<String,String>(new Computable<String,String>() {
+		public String get(String key) {
+			try {
+				Connection con = db().getConnection();
+				PreparedStatement stmt = con.prepareStatement(
+					"select value from user_property where user_id = ? and key = ?"
+				);
+				stmt.setLong( 1, getId() );
+				stmt.setString( 2, key );
+				ResultSet rs = stmt.executeQuery();
+				try {
+					return rs.next() ? rs.getString("value") : null;
+				} finally {
+					rs.close();
+					stmt.close();
+					con.close();
+				}
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+		}
+	});
+
+	public String getProperty(String key) {
+		return propertyCache.get(key);
+	}
+
+	public void setProperty(String key,String value) {
+		try {
+			Connection con = db().getConnection();
+			PreparedStatement stmt = con.prepareStatement(
+				"delete from user_property where user_id = ? and key = ?"
+			);
+			stmt.setLong( 1, getId() );
+			stmt.setString( 2, key );
+			stmt.executeUpdate();
+			stmt.close();
+			if( value != null ) {
+				stmt = con.prepareStatement(
+					"insert into user_property (user_id,key,value) values (?,?,?)"
+				);
+				stmt.setLong( 1, getId() );
+				stmt.setString( 2, key );
+				stmt.setString( 3, value );
+				stmt.executeUpdate();
+				stmt.close();
+			}
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		} finally {
+			propertyCache.remove(key);
+		}
+	}
+
+
+	final Memoizer<String,Boolean> tagCache = new Memoizer<String,Boolean>(new Computable<String,Boolean>() {
+		public Boolean get(String sqlCondition) {
+			return TagImpl.countTags(siteKey,sqlCondition) > 0;
+		}
+	});
+
+
+
+
+
+	private final static PooledStringDigester passwordDigester = new PooledStringDigester();
+
+	static {
+        passwordDigester.setAlgorithm(Init.get("passwordDigestAlgorithm","SHA-256"));
+        passwordDigester.setIterations(Init.get("passwordDigestIterations",100000));
+        passwordDigester.setSaltSizeBytes(Init.get("passwordDigestSaltSize",16));
+        passwordDigester.setPoolSize(Init.get("passwordDigestPoolSize",4));
+        passwordDigester.initialize();
+	}
+
+	private final static PooledStringDigester passcookieDigester = new PooledStringDigester();
+
+	static {
+        passcookieDigester.setAlgorithm(Init.get("passcookieDigestAlgorithm","SHA-256"));
+        passcookieDigester.setIterations(Init.get("passcookieDigestIterations",100000));
+        FixedByteArraySaltGenerator sg = new FixedByteArraySaltGenerator();
+        // this fixed salt needs to be kept secret
+        sg.setSalt(Init.get("passcookieSalt", new byte[]{105, 4, 40, 78, 24, 46, 30, 100, 18, -27, 114, -21, -44, -59, 103, 43}));
+        passcookieDigester.setSaltGenerator(sg);
+        passcookieDigester.setPoolSize(Init.get("passcookieDigestPoolSize",4));
+        passcookieDigester.initialize();
+	}
+
+	private final static PooledStringDigester resetcodeDigester = new PooledStringDigester();
+
+	static {
+        resetcodeDigester.setAlgorithm(Init.get("resetcodeDigestAlgorithm","SHA-256"));
+        resetcodeDigester.setIterations(Init.get("resetcodeDigestIterations",100000));
+        FixedByteArraySaltGenerator sg = new FixedByteArraySaltGenerator();
+        // this fixed salt needs to be kept secret
+        sg.setSalt(Init.get("resetcodeSalt", new byte[]{-47, 9, -128, 109, 112, -88, -91, 39, 77, 111, 57, -102, 120, 12, 54, 16}));
+        resetcodeDigester.setSaltGenerator(sg);
+        resetcodeDigester.setPoolSize(Init.get("resetcodeDigestPoolSize",4));
+        resetcodeDigester.initialize();
+	}
+
+	public boolean checkPassword(String password) {
+		return passwordDigest!=null && passwordDigester.matches(password, passwordDigest);
+	}
+
+	private String calcPasscookie() {
+		return passcookieDigester.digest(passwordDigest);
+	}
+
+	public boolean checkPasscookie(String passcookie) {
+		return passwordDigest!=null && getPasscookie().equals(passcookie);
+	}
+
+	public String getResetcode() {
+		return resetcodeDigester.digest(passwordDigest);
+	}
+
+	public boolean checkResetcode(String resetcode) {
+		return passwordDigest!=null && getResetcode().equals(resetcode);
+	}
+
+	private static String digestPassword(String password) {
+		return passwordDigester.digest(password);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/ViewCount.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,299 @@
+package nabble.model;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.postgres.PostgresExceptionHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+
+public final class ViewCount {
+	private static final Logger logger = LoggerFactory.getLogger(ViewCount.class);
+
+
+	// the ViewCount class is views per site
+	private Map<Long,int[]> nodeMap = new HashMap<Long,int[]>();
+	private int userViews = 0;
+
+	private ViewCount() {}
+
+	private void inc(long nodeId,boolean isUser) {
+		int[] i = nodeMap.get(nodeId);
+		if( i == null ) {
+			i = new int[1];
+			nodeMap.put(nodeId,i);
+		}
+		i[0]++;
+		if( isUser )
+			userViews++;
+	}
+
+
+	public static long lastSaved = System.currentTimeMillis();
+	private static final Object lock = new Object();
+	private static Map<Long,ViewCount> counts = new HashMap<Long,ViewCount>();
+
+	public static void inc(Site site,long nodeId,boolean isUser) {
+		Long siteId = site.getId();
+		synchronized(lock) {
+			ViewCount viewCount = counts.get(siteId);
+			if( viewCount == null ) {
+				viewCount = new ViewCount();
+				counts.put(siteId,viewCount);
+			}
+			viewCount.inc(nodeId,isUser);
+		}
+	}
+
+	public static void save() {
+		Map<Long,ViewCount> map;
+		synchronized(lock) {
+			map = counts;
+			counts = new HashMap<Long,ViewCount>();
+		}
+		Db.dbGlobal().beginTransaction();
+		try {
+			for( Map.Entry<Long,ViewCount> entry : map.entrySet() ) {
+				long siteId = entry.getKey();
+				ViewCount viewCount = entry.getValue();
+				Map<Long,int[]> nodeMap =viewCount.nodeMap;
+				SiteKey siteKey = SiteKey.getInstance(siteId);
+				DbDatabase db;
+				try {
+					db = siteKey.getDb();
+				} catch(UpdatingException e) {
+					continue;
+				}
+				int siteViews = 0;
+				{
+					Connection con = db.getConnection();
+					try {
+						PreparedStatement pstmtUpdate = con.prepareStatement(
+							"update view_count set views = views + ? where node_id = ?"
+						);
+						PreparedStatement pstmtInsert = null;
+						for( Map.Entry<Long,int[]> nodeEntry : nodeMap.entrySet() ) {
+							long nodeId = nodeEntry.getKey();
+							int views = nodeEntry.getValue()[0];
+							siteViews += views;
+							{
+								pstmtUpdate.setInt(1,views);
+								pstmtUpdate.setLong(2,nodeId);
+								PostgresExceptionHandler peh = new PostgresExceptionHandler(pstmtUpdate);
+								try {
+									int n = pstmtUpdate.executeUpdate();
+									if( n==0 ) {
+										if( pstmtInsert == null ) {
+											pstmtInsert = con.prepareStatement(
+												"insert into view_count (node_id,views) values (?,?)"
+											);
+										}
+										pstmtInsert.setLong(1,nodeId);
+										pstmtInsert.setInt(2,views);
+										pstmtInsert.executeUpdate();
+									}
+								} catch(SQLException e) {
+									if( e.getMessage().contains("violates foreign key constraint") ) {
+										logger.info("in site "+siteId+" node "+nodeId+" was viewed and then deleted");
+										peh.handleException();
+									} else if( e.getMessage().contains("relation \"view_count\" does not exist") ) {
+										logger.info("site "+siteId+" was deleted");
+										peh.handleException();
+									} else {
+										throw e;
+									}
+								} finally {
+									peh.close();
+								}
+							}
+						}
+						if( pstmtInsert != null )
+							pstmtInsert.close();
+						pstmtUpdate.close();
+					} finally {
+						con.close();
+					}
+				}
+				{
+					Connection con = Db.dbGlobal().getConnection();
+					PreparedStatement pstmt = con.prepareStatement(
+						"update site_global"
+						+" set views = views + ?"
+						+" , user_views = user_views + ?"
+						+" where site_id = ?"
+					);
+					pstmt.setInt(1,siteViews);
+					pstmt.setInt(2,viewCount.userViews);
+					pstmt.setLong(3,siteId);
+					pstmt.executeUpdate();
+					con.close();
+				}
+			}
+			Db.dbGlobal().commitTransaction();
+			lastSaved = System.currentTimeMillis();
+		} catch(SQLException e) {
+			logger.error("Views not saved", e);
+		} finally {
+			Db.dbGlobal().endTransaction();
+		}
+	}
+
+	private static final long secondsBetweenViewCountSaves = Init.get("secondsBetweenViewCountSaves",60);
+
+	static {
+		Executors.scheduleWithFixedDelay(new Runnable(){public void run(){
+			save();
+		}},secondsBetweenViewCountSaves,secondsBetweenViewCountSaves,TimeUnit.SECONDS);
+		logger.info("Started ViewCounter");
+
+		Executors.runDaily(
+			new Runnable(){public void run(){
+				try {
+					calculateActivity();
+				} catch(SQLException e) {
+					logger.error("",e);
+				} catch(IOException e) {
+					logger.error("",e);
+				}
+			}}
+		);
+	}
+
+	private static final float DECAY = Init.get("activityDecay",0.95f);
+	static final int initialActivity = Init.get("initialActivity",1000);
+	static final int updateChunks = Init.get("updateChunks",100);
+
+	public static final Map<Long,Integer> activityBoosts = Init.get("activityBoosts",new HashMap<Long,Integer>());
+
+	private  static final List<Runnable> calculateActivityListeners = new ArrayList<Runnable>();
+
+	public static void addCalculateActivityListener(Runnable listener) {
+		synchronized(calculateActivityListeners) {
+			calculateActivityListeners.add(listener);
+		}
+	}
+
+	private static void fireCalculateActivityListeners() {
+		synchronized(calculateActivityListeners) {
+			for( Runnable listener : calculateActivityListeners ) {
+				listener.run();
+			}
+		}
+	}
+
+	public static void calculateActivity() throws SQLException, IOException {
+		logger.error("calculateActivity start");
+		Connection con = Db.dbGlobal().getConnection();
+		try {
+			{
+				Statement stmt = con.createStatement();
+
+				stmt.executeUpdate(
+					"update site_global"
+					+"	set activity = " + initialActivity
+					+"	where activity is null"
+				);
+
+				PreparedStatement pstmt = con.prepareStatement(
+					"update site_global"
+					+"	set activity = floor(activity * ?)"
+					+"	where activity > 0"
+					+"		and site_id % ? = ?"
+				);
+				for( int chunk = 0; chunk < updateChunks && !Executors.isShuttingDown(); chunk++ ) {
+					long start = System.currentTimeMillis();
+					pstmt.setFloat(1,DECAY);
+					pstmt.setInt(2,updateChunks);
+					pstmt.setInt(3,chunk);
+					pstmt.executeUpdate();
+					logger.info("calculateActivity - chunk #"+chunk+" took " + (System.currentTimeMillis()-start)/1000 + " seconds");
+				}
+				pstmt.close();
+
+				fireCalculateActivityListeners();
+
+				stmt.executeUpdate(
+					"update site_global"
+					+"	set activity = activity + views"
+					+"		, views = 0"
+					+"		, user_views = 0"
+					+"	where views > 0"
+				);
+
+				stmt.close();
+			}
+			{
+				PreparedStatement stmt = con.prepareStatement(
+					"update site set activity = activity + ? where site_id = ?"
+				);
+				for( Map.Entry<Long,Integer> boost : activityBoosts.entrySet() ) {
+					stmt.setInt(1,boost.getValue());
+					stmt.setLong(2,boost.getKey());
+					stmt.executeQuery();
+				}
+				stmt.close();
+			}
+		} finally {
+			con.close();
+		}
+		logger.error("calculateActivity end");
+	}
+
+	public static Map<Long,Integer> getCounts(Site site,Collection<Long> nodeIds) {
+		if( nodeIds.isEmpty() )
+			return Collections.emptyMap();
+		Map<Long,Integer> map = new HashMap<Long,Integer>();
+		for( Long nodeId : nodeIds ) {
+			map.put(nodeId,0);
+		}
+		try {
+			Connection con = site.getDb().getConnection();
+			try {
+				StringBuilder buf = new StringBuilder();
+				buf.append( "select * from view_count where node_id in (" );
+				Iterator<Long> iter = nodeIds.iterator();
+				buf.append( iter.next() );
+				while( iter.hasNext() ) {
+					buf.append( ',' ).append( iter.next() );
+				}
+				buf.append(')');
+				Statement stmt = con.createStatement();
+				ResultSet rs = stmt.executeQuery( buf.toString() );
+				while( rs.next() ) {
+					map.put( rs.getLong("node_id"), rs.getInt("views") );
+				}
+				stmt.close();
+			} finally {
+				con.close();
+			}
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+		synchronized(lock) {
+			ViewCount viewCount = counts.get(site.getId());
+			if( viewCount != null ) {
+				for( Map.Entry<Long,Integer> entry : map.entrySet() ) {
+					Long nodeId = entry.getKey();
+					int[] views = viewCount.nodeMap.get(nodeId);
+					if( views != null )
+						map.put( nodeId, entry.getValue() + views[0] );
+				}
+			}
+		}
+		return map;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/export/AbstractImportImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,144 @@
+package nabble.model.export;
+
+import fschmidt.db.SQLRuntimeException;
+import fschmidt.util.java.ProxyIntoThread;
+import nabble.model.MailingList;
+import nabble.model.ModelException;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import nabble.view.web.template.UrlMapperNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public abstract class AbstractImportImpl implements Import {
+
+	private static final Logger logger = LoggerFactory.getLogger(AbstractImportImpl.class);
+
+	final Site site;
+	private ProxyIntoThread pit = null;
+	private MailingList currentMailingList = null;
+	private boolean isStarted = false;
+	private boolean isClosed = false;
+
+	AbstractImportImpl(Site site) {
+		this.site = site;
+	}
+
+	protected abstract String createNode(NodeData data) throws ModelException;
+	protected abstract void start();
+	protected abstract void end();
+
+	void setProxy(ProxyIntoThread<Import> pit) {
+		this.pit = pit;
+	}
+/*
+	public Pattern[] getRedirectUrlPatterns() {
+		Pattern rootPtn = Pattern.compile("http://.+\\.(\\d+)\\..+/");
+		Pattern appPtn = Pattern.compile("-f(\\d+)\\.html$");
+		Pattern postPtn = Pattern.compile("-td(\\d+)\\.html$");
+		Pattern postPermalinkPtn = Pattern.compile("-tp\\d+p(\\d+)\\.html$");
+		return new Pattern[] { appPtn, postPtn, postPermalinkPtn, rootPtn };
+	}
+*/
+	private static final Pattern[] redirectUrlPatterns =  new Pattern[]{
+		Pattern.compile("-td(\\d+)\\.html$"),  // post
+		Pattern.compile("-tp(\\d+)\\.html$"),  // topic permalink
+		Pattern.compile("-tp\\d+p(\\d+)\\.html$"),  // post permalink
+	};
+
+	public long getNodeId(String permalink) throws BadLink {
+		Node node = UrlMapperNamespace.getNodeFromUrl(permalink);
+		if( node != null )
+			return node.getId();
+		for (Pattern p : redirectUrlPatterns) {
+			Matcher m = p.matcher(permalink);
+			if (m.find()) {
+				String s = m.group(m.groupCount());
+				return Long.parseLong(s);
+			}
+		}
+		throw new BadLink();
+	}
+
+	public String importNode(NodeData data) {
+		if( isClosed )
+			throw new RuntimeException("Closed");
+		if( !isStarted ) {
+			start();
+			isStarted = true;
+		}
+		try {
+			Node node = null;
+			if( currentMailingList != null )
+				node = currentMailingList.getNodeFromMessageID(data.messageID);
+
+			if (node != null)
+				return Jtp.url(node);
+			else
+				return createNode(data);
+		} catch(ModelException e) {
+			logger.error("node "+data.exportId + " / data.exportId=" + data.exportId, e);
+			throw new RuntimeException(e.toString());
+		} catch(SQLRuntimeException e) {
+			logger.error("node "+data.exportId + " / data.exportId=" + data.exportId, e);
+			throw new RuntimeException(e.toString());
+		}
+	}
+
+	Node getNode(long nodeId) {
+		return site.getNode(nodeId);
+	}
+
+	public void setMailingList(Long nodeId) {
+		if( nodeId==null ) {
+			currentMailingList = null;
+		} else {
+			if( currentMailingList != null )
+				throw new RuntimeException("Already have mailing list "+currentMailingList);
+			currentMailingList = getNode(nodeId).getMailingList();
+			if( currentMailingList == null )
+				throw new RuntimeException("No mailing list for node "+nodeId);
+		}
+	}
+
+	public void subscribe(long nodeId) {
+		getNode(nodeId).getMailingList().subscribe();
+	}
+
+	public void setExportOwner(long nodeId,String exportOwner) {
+		try {
+			MailingList ml = getNode(nodeId).getMailingList();
+			ml.setExportOwner(exportOwner);
+			ml.update();
+		} catch(ModelException e) {
+			logger.error(exportOwner,e);
+			throw new RuntimeException(e.toString());
+		}
+	}
+
+	public void close() {
+		isClosed = true;
+		if (pit != null) {
+			pit.stop();
+			pit = null;
+		}
+		end();
+	}
+
+	@Override protected void finalize() throws Throwable {
+		try {
+			if( !isClosed ) {
+				logger.error("import not closed");
+				close();
+			}
+		} finally {
+			super.finalize();
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/export/Export.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,279 @@
+package nabble.model.export;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.MailingList;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Executors;
+import nabble.model.Node;
+import nabble.model.NodeIterator;
+import nabble.model.Person;
+import nabble.model.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.rmi.Naming;
+import java.rmi.NotBoundException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class Export implements Runnable {
+
+	private static final Logger logger = LoggerFactory.getLogger(Export.class);
+
+	protected static class ExportMessages {
+		public String getSuccessMessage(Node node) { return "Export finished successfully."; }
+		public String getErrorMessage(Node node, Exception e) { return "Export error: \n" + getStackTrace(e); }
+		public String getPermalinkNotFoundMessage(String permalink) { return "Export couldn't start because the link you provided is not a valid Nabble application: \n" + permalink; }
+		public String getEmailSubject(Node node) { return "Export of node " + node.getId() + " | " + node.getSubject(); }
+	}
+
+	protected static final class ShutdownException extends RuntimeException {}
+
+	private static final Pattern URL_BASE = Pattern.compile( "^\\w+://[^/]+/");
+	private static final Pattern SERVER_FORMAT = Pattern.compile( "(localhost|\\d+\\.\\d+\\.\\d+\\.\\d+):\\d+");
+	private final String email;
+	private final Node rootNode;
+	private final Import imp;
+	private final ExportMessages messages;
+	private final String permalink;
+	private boolean handledMailingList = false;
+	private Set<Long> movedUserIds = new TreeSet<Long>();
+
+	protected Export(Node rootNode,String permalink,String email, ImportServer is, ExportMessages messages)
+		throws IOException
+	{
+		logger.info("Exporting node ID = " + rootNode.getId());
+		setExporting(rootNode.getSite().getId(), true);
+		this.rootNode = rootNode;
+		imp = is.newImport(permalink,rootNode.getId());
+		this.email = email;
+		this.messages = messages;
+		this.permalink = permalink;
+	}
+
+	public Export(Node rootNode,String permalink,String email)
+		throws IOException
+	{
+		this(rootNode,permalink,email,getServer(permalink),new ExportMessages());
+	}
+
+	// Global Information
+	public static Set<Long> exportSiteIds = new HashSet<Long>();
+
+	private static synchronized void setExporting(long siteId, boolean start) {
+		if (start) {
+			exportSiteIds.add(siteId);
+		} else
+			exportSiteIds.remove(siteId);
+	}
+
+	private static String getServerAddress(String permalink) throws IOException {
+		Matcher m = URL_BASE.matcher(permalink);
+		if( !m.find() )
+			throw new IllegalArgumentException();
+		String base = m.group();
+		return IoUtils.readPage(base+"util/Rmi.jtp");
+	}
+
+	private static Remote lookup(String rmiServer,String name)
+		throws RemoteException
+	{
+		try {
+			return Naming.lookup("//"+rmiServer+"/"+name);
+		} catch(MalformedURLException e) {
+			throw new RuntimeException(e);
+		} catch(NotBoundException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static ImportServer getServer(String permalink) throws IOException {
+		return (ImportServer) lookup(getServerAddress(permalink),"import");
+	}
+
+	public static boolean isValidExportServer(String permalink) {
+		try {
+			String server = getServerAddress(permalink);
+			return SERVER_FORMAT.matcher(server).matches();
+		} catch (IOException e) {
+			return false;
+		} catch (IllegalArgumentException e) {
+			return false;
+		}
+	}
+
+	public void run() {
+		logger.info("Started export thread for ID = " + rootNode.getId() + " / " + email);
+		PlainTextContent content = null;
+		ModelHome.beginImport();
+		long siteId = this.rootNode.getSite().getId();
+		boolean closed = false;
+		try {
+			Node rootNode = this.rootNode.getGoodCopy();
+			long parentId = imp.getNodeId(permalink);
+			export(rootNode.getGoodCopy(),parentId,null);
+			if( handledMailingList ) {
+				handledMailingList = false;
+				export(rootNode.getGoodCopy(),parentId,null);
+				if( handledMailingList )
+					throw new RuntimeException();
+			}
+			// The last step is to delete the old nodes, so there is no reason
+			// to keep this connection open. Let's close it.
+			imp.close();
+			closed = true;
+
+			// Delete the old nodes now
+			rootNode.deleteRecursively();
+			content = new PlainTextContent(messages.getSuccessMessage(rootNode));
+		} catch(ShutdownException e) {
+			throw e;
+		} catch(RuntimeException e) {
+			logger.error(rootNode.toString(),e);
+			content = new PlainTextContent(messages.getErrorMessage(rootNode, e));
+		} catch(RemoteException e) {
+			logger.error(rootNode.toString(),e);
+			content = new PlainTextContent(messages.getErrorMessage(rootNode, e));
+		} catch(Import.BadLink e) {
+			content = new PlainTextContent(messages.getPermalinkNotFoundMessage(permalink));
+		} finally {
+			ModelHome.endImport();
+			setExporting(siteId, false);
+			if (!closed) {
+				try {
+					imp.close();
+				} catch(Exception e) {
+					logger.error("imp.close",e);
+					if (content == null)
+						content = new PlainTextContent(messages.getErrorMessage(rootNode, e));
+				}
+			}
+		}
+		Mail mail = MailHome.newMail();
+		MailAddress to = new MailAddress(email);
+		mail.setFrom(new MailAddress(ModelHome.noReply, "Nabble"));
+		mail.setTo(to);
+		mail.setContent(content);
+		mail.setSubject(messages.getEmailSubject(rootNode));
+		ModelHome.send(mail);
+	}
+
+	private void export(Node node,long parentId,int[] pin)
+		throws RemoteException
+	{
+		if( Executors.isShuttingDown() )
+			throw new ShutdownException();
+		long id = node.getExportedNodeId();
+		try {
+			final MailingList ml = node.getMailingList();
+			Integer pinOrder = pin == null || !node.isPinned()? null : ++pin[0];
+			if( id==0L ) {
+				NodeData data = node.getData();
+				data.parentId = parentId;
+				data.pin = pinOrder;
+				String redirectUrl = imp.importNode(data);
+				try {
+					id = imp.getNodeId(redirectUrl);
+				} catch(Import.BadLink e) {
+					logger.error(""+node+" url = "+redirectUrl,e);
+					throw new RuntimeException("redirect url not found: "+redirectUrl);
+				}
+				setExportedNodeId(node, id);
+				if (ml != null) {
+					imp.setExportOwner(id,email);
+					imp.subscribe(id);
+					ml.setExportOwner(email);
+					ml.update();
+					ml.unsubscribe();
+					handledMailingList = true;
+				}
+			} else {
+				if (ml!=null) {
+					imp.setExportOwner(id,null);
+					ml.setExportOwner(null);
+					ml.update();
+				}
+			}
+
+			if (ml != null)
+				imp.setMailingList(id);
+
+			// Move author
+			Person author = node.getOwner();
+			if (author instanceof User) {
+				User user = (User) author;
+				if (user.isRegistered() && !movedUserIds.contains(user.getId())) {
+					String smallAvatarUrl = null;
+					String bigAvatarUrl = null;
+					if (user.hasAvatar()) {
+						String base = node.getSite().getBaseUrl() + "/file/a" + user.getId() + '/';
+						smallAvatarUrl = base + ModelHome.AVATAR_SMALL;
+						bigAvatarUrl = base + ModelHome.AVATAR_BIG;
+					}
+					imp.addUser(user.getName(), user.getEmail(), user.getPasswordDigest(), user.getRegistered(), smallAvatarUrl, bigAvatarUrl);
+					movedUserIds.add(user.getId());
+				}
+			}
+
+			pin = new int[1];
+
+			// Here we use an iterator because something may go wrong with the
+			// migration (e.g., the other server may go down or the network may fail)
+			// So we should make sure all DB connections are properly closed.
+			NodeIterator<? extends Node> iterator = node.getChildren();
+			try {
+				while (iterator.hasNext()) {
+					export(iterator.next(), id, pin );
+				}
+			} finally {
+				iterator.close();
+			}
+			if (ml != null)
+				imp.setMailingList(null);
+		} catch(ModelException e) {
+			logger.error(""+node,e);
+			throw new RuntimeException(e.toString());
+		}
+	}
+
+	protected void setExportedNodeId(Node node, long id) {
+		final DbDatabase db = rootNode.getSite().getDb();
+		db.beginTransaction();
+		try {
+			node.getGoodCopy().setExportedNodeId(id);
+			db.commitTransaction();
+		} finally {
+			db.endTransaction();
+		}
+	}
+
+	// Utilities --------------------------------------------------------------
+
+	protected static String getStackTrace(Throwable e) {
+		StackTraceElement[] arr = e.getStackTrace();
+		StringBuilder builder = new StringBuilder(e.toString());
+		builder.append('\n');
+		for (StackTraceElement elem : arr) {
+			builder.append('\t').append(elem).append('\n');
+		}
+		if (e.getCause() != null) {
+			builder.append("Caused by:\n");
+			builder.append(getStackTrace(e.getCause()));
+		}
+		return builder.toString();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/export/Import.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,20 @@
+package nabble.model.export;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.util.Date;
+
+
+public interface Import extends Remote {
+	public static final class BadLink extends Exception {}
+	public long getNodeId(String permalink) throws RemoteException, BadLink;
+	public String importNode(NodeData nodeData) throws RemoteException;
+	public void setMailingList(Long nodeId) throws RemoteException;
+	public void subscribe(long nodeId) throws RemoteException;
+	public void setExportOwner(long nodeId,String exportOwner) throws RemoteException;
+	public void addUser(String name, String email, String password, Date registrationDate, String smallAvatarUrl, String bigAvatarUrl) throws RemoteException;
+	public void close() throws RemoteException;
+
+	/** FOR OLD NABBLE ONLY -- TO BE REMOVED SOON */
+	public void addUser0(String name, String email, String password, long registrationDate) throws RemoteException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/export/ImportImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,89 @@
+package nabble.model.export;
+
+import fschmidt.db.DbDatabase;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Date;
+
+
+final class ImportImpl extends AbstractImportImpl {
+
+	ImportImpl(Site site) {
+		super(site);
+	}
+
+	protected void start() {
+		ModelHome.beginImport();
+	}
+
+	protected void end() {
+		ModelHome.endImport();
+	}
+
+	protected String createNode(NodeData data) throws ModelException {
+		Node node;
+		DbDatabase db = site.getDb();
+		db.beginTransaction();
+		try {
+			node = site.getGoodCopy().newNode(data);
+			db.commitTransaction();
+		} finally {
+			db.endTransaction();
+		}
+		return Jtp.url(node.getGoodCopy());
+	}
+
+	public void addUser(String name, String email, String passwordDigest, Date registrationDate, String smallAvatarUrl, String bigAvatarUrl) {
+		final DbDatabase db = site.getDb();
+		db.beginTransaction();
+		try {
+			User user = site.getOrCreateUser(email, name);
+			if (!user.isRegistered()) {
+				user.setPasswordDigest(passwordDigest);
+				user.register(registrationDate);
+				user.update();
+				user = user.getGoodCopy();
+				if (!user.hasAvatar() && smallAvatarUrl != null && bigAvatarUrl != null) {
+					BufferedImage small = ImageIO.read(new URL(smallAvatarUrl));
+					BufferedImage big = ImageIO.read(new URL(bigAvatarUrl));
+					user.saveAvatar(small, big);
+				}
+			}
+			db.commitTransaction();
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		} catch (ModelException e) {
+			throw new RuntimeException(e);
+		} finally {
+			db.endTransaction();
+		}
+	}
+
+	/** FOR OLD NABBLE ONLY -- TO BE REMOVED SOON */
+	public void addUser0(String name, String email, String password, long registrationDate) {
+		final DbDatabase db = site.getDb();
+		db.beginTransaction();
+		try {
+			User user = site.getOrCreateUser(email, name);
+			if (!user.isRegistered()) {
+				user.setPassword(password);
+				user.register(new Date(registrationDate));
+				user.update();
+			}
+			db.commitTransaction();
+		} catch (ModelException e) {
+			// do nothing -- skip this user
+		} finally {
+			db.endTransaction();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/export/ImportServer.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,10 @@
+package nabble.model.export;
+
+import java.net.MalformedURLException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+
+public interface ImportServer extends Remote {
+	public Import newImport(String permalink,long nodeId) throws RemoteException, MalformedURLException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/export/ImportServerImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,34 @@
+package nabble.model.export;
+
+import fschmidt.util.java.ProxyIntoThread;
+import nabble.model.Init;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+import java.rmi.RemoteException;
+
+
+public final class ImportServerImpl implements ImportServer {
+	private static final long timeout = Init.get("importTimeout",1000L*60*60);  // 1 hour
+
+	public Import newImport(String permalink,long nodeId) throws RemoteException, MalformedURLException {
+		URL url = new URL(permalink);
+		String domain = url.getHost();
+		Long siteId = Jtp.getSiteIdFromDomain(domain);
+		Site site = ModelHome.getSite(siteId);
+
+		AbstractImportImpl imp = new ImportImpl(site);
+		ProxyIntoThread<Import> pit = new ProxyIntoThread<Import>( "importing "+nodeId, timeout, imp, Import.class );
+		imp.setProxy(pit);
+		return Init.rmiExport(pit.newInstance());
+	}
+
+	private static final ImportServerImpl importServer = new ImportServerImpl();
+
+	public static void bind() {
+		Init.rmiBind("import",importServer);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/export/NodeData.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,40 @@
+package nabble.model.export;
+
+import java.io.Serializable;
+import java.net.URL;
+import java.util.Date;
+import java.util.Map;
+import java.util.HashMap;
+
+
+public final class NodeData implements Serializable {
+	public long exportId;
+	public String kind;
+	public String ownerEmail;
+	public String ownerName;
+	public String ownerAnonymousId;
+	public String subject;
+	public String message;
+	public char msgFmt;
+	public Long parentId;
+	public Date whenCreated;
+	public Date whenUpdated;
+	public String type;
+	public String customStyle;
+	public Integer pin;
+	public String messageID;
+	public Boolean isGuessedParent;
+	// didn't bother with parent_message_id and when_sent
+
+	public String mlAddress;
+	public String mlUrl;
+	public boolean mlPlainTextOnly;
+	public boolean mlIgnoreNoArchive;
+	public String mlServer;
+	public String mlListName;
+
+	public URL[] fileUrls;
+	public transient Map<String, byte[]> files;
+
+	public final Map<String,Serializable> extensionData = new HashMap<String,Serializable>();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/lucene/HitCollector.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,32 @@
+package nabble.model.lucene;
+
+import java.io.IOException;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.Scorer;
+import org.apache.lucene.document.Document;
+
+
+public abstract class HitCollector extends Collector {
+	private IndexReader reader;
+
+	public final boolean acceptsDocsOutOfOrder() {
+		return true;
+	}
+
+	public final void setNextReader(IndexReader reader,int docBase) {
+		this.reader = reader;
+	}
+
+	public final void setScorer(Scorer scorer) {}
+
+	public final void collect(int doc) {
+		try {
+			process( reader.document(doc) );
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	protected abstract void process(Document doc);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/lucene/IndexCache.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,367 @@
+package nabble.model.lucene;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.NoSuchDirectoryException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import fschmidt.db.util.Unique;
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.Memoizer;
+import fschmidt.util.java.IoUtils;
+import nabble.model.Init;
+import nabble.model.Executors;
+
+
+public final class IndexCache<K> {
+	private static final Logger logger = LoggerFactory.getLogger(IndexCache.class);
+
+	public interface Builder<K> {
+		public void build(K key) throws Exception;
+		public boolean exists(String keyString);
+	}
+
+	private final Unique<K> unique = new Unique<K>();
+	private final File dir;
+	private final Analyzer analyzer;
+	private final int version;
+	private final Builder<K> builder;
+	private volatile boolean isShutdown = false;
+
+	public IndexCache(File dir, Analyzer analyzer, int version, Builder<K> builder) {
+		this.dir = dir;
+		this.analyzer = analyzer;
+		this.version = version;
+		this.builder = builder;
+		Executors.executeSometime(new Runnable(){
+			public void run() {
+				deleteUnusedIndexes();
+			}
+		});
+	}
+
+	private final Memoizer<K,FSDirectory> dirs = new Memoizer<K,FSDirectory>(new Computable<K,FSDirectory>() {
+		public FSDirectory get(K key) {
+			File dirFile = new File(dir,key.toString());
+			dirFile.mkdirs();
+			try {
+				FSDirectory dir = FSDirectory.open(dirFile);
+				if( IndexWriter.isLocked(dir) ) {
+					logger.error("Lucene index "+dir+" was locked");
+					IndexWriter.unlock(dir);
+				}
+				check(key,dir);
+				return dir;
+			} catch(IOException e) {
+				logger.error(e.toString());
+				System.exit(-1);
+				throw new RuntimeException();  // never
+			} catch(RuntimeException e) {
+				logger.error(e.toString());
+				System.exit(-1);
+				throw new RuntimeException();  // never
+			}
+		}
+	});
+
+
+	private final Map<K,MyIndexSearcher> searcherCache = new HashMap<K,MyIndexSearcher>();
+
+	private class MyIndexSearcher extends IndexSearcher implements LuceneSearcher {
+		private final K key;
+		private int opens = 0;
+		private int closes = 0;
+		private boolean isRemoved = false;
+
+		MyIndexSearcher(K key,IndexReader reader) throws IOException {
+			super(reader);
+			this.key = key;
+		}
+
+		@Override public void close() throws IOException {
+			K k = unique.get(key);
+			try {
+				synchronized(k) {
+					if( ++closes == opens ) {
+						final int oldOpens = opens;
+						Executors.schedule(new Runnable() {
+							public void run() {
+								K k = unique.get(key);
+								try {
+									synchronized(k) {
+										if( oldOpens == opens ) {
+											if( !isRemoved )
+												searcherCache.remove(key);
+											try {
+												MyIndexSearcher.super.close();
+												getIndexReader().close();
+											} catch(IOException e) {
+												logger.error("",e);
+											}
+										}
+									}
+								} finally {
+									unique.free(k);
+								}
+							}
+						}, 5, TimeUnit.SECONDS );
+					}
+				}
+			} finally {
+				unique.free(k);
+			}
+		}
+	}
+
+	public LuceneSearcher openSearcher(K key) throws IOException {
+		K k = unique.get(key);
+		try {
+			synchronized(k) {
+				MyIndexSearcher searcher = searcherCache.get(key);
+				if( searcher == null ) {
+					searcher = new MyIndexSearcher( key, IndexReader.open(dirs.get(key)) );
+					searcherCache.put(key,searcher);
+				} else {
+					IndexReader indexReader = searcher.getIndexReader();
+					IndexReader newReader = indexReader.reopen();
+					if( newReader != indexReader ) {
+						searcherCache.remove(key);
+						searcher.isRemoved = true;
+						searcher = new MyIndexSearcher( key, newReader );
+						searcherCache.put(key,searcher);
+					}
+				}
+				searcher.opens++;
+				return new LuceneSearcherImpl(searcher);
+			}
+		} finally {
+			unique.free(k);
+		}
+	}
+
+
+	private final Map<K,MyIndexWriter> writerCache = new HashMap<K,MyIndexWriter>();
+
+	private class MyIndexWriter extends IndexWriter {
+		private final K key;
+		private int opens = 0;
+		private int closes = 0;
+		private boolean willCommit = false;
+
+		MyIndexWriter(K key) throws IOException {
+			super(dirs.get(key),analyzer,IndexWriter.MaxFieldLength.LIMITED);
+			this.key = key;
+		}
+
+		@Override public void close() throws IOException {
+			K k = unique.get(key);
+			try {
+				synchronized(k) {
+					if( ++closes == opens ) {
+						final int oldOpens = opens;
+						Executors.schedule(new Runnable() {
+							public void run() {
+								K k = unique.get(key);
+								try {
+									synchronized(k) {
+										if( oldOpens == opens ) {
+											writerCache.remove(key);
+											try {
+												MyIndexWriter.super.close();
+											} catch(NoSuchDirectoryException e) {
+												logger.info("",e);  // site could be deleted
+											} catch(IOException e) {
+												logger.error("",e);
+											}
+											willCommit = false;
+										}
+									}
+								} finally {
+									unique.free(k);
+								}
+							}
+						}, 5, TimeUnit.SECONDS );
+					}
+					if( !willCommit ) {
+						willCommit = true;
+						Executors.schedule(new Runnable() {
+							public void run() {
+								K k = unique.get(key);
+								try {
+									synchronized(k) {
+										if( willCommit ) {
+											try {
+												commit();
+											} catch(IOException e) {
+												logger.error("",e);
+											}
+											willCommit = false;
+										}
+									}
+								} finally {
+									unique.free(k);
+								}
+							}
+						}, 5, TimeUnit.SECONDS );
+					}
+				}
+			} finally {
+				unique.free(k);
+			}
+		}
+	}
+
+	public IndexWriter openIndexWriter(K key) throws IOException {
+		if( isShutdown )
+			throw new RuntimeException("shutdown");
+		key = unique.get(key);
+		try {
+			synchronized(key) {
+				MyIndexWriter indexWriter = writerCache.get(key);
+				if( indexWriter == null ) {
+					indexWriter = new MyIndexWriter(key);
+					writerCache.put(key,indexWriter);
+				}
+				indexWriter.opens++;
+				return indexWriter;
+			}
+		} finally {
+			unique.free(key);
+		}
+	}
+
+
+	private final Set<K> buildSet = Collections.newSetFromMap(new ConcurrentHashMap<K,Boolean>());
+
+	private void check(K key,FSDirectory dir) throws IOException {
+		if( !Init.hasDaemons )
+			return;
+		File checkFile = checkFile(dir);
+		int currentVersion = 0;
+		try {
+			currentVersion = Integer.parseInt(IoUtils.read(checkFile).trim());
+		} catch (Exception e) {}
+		if (currentVersion > version) {
+			throw new RuntimeException("Lucene index version for "+key+" is "+version
+					+", found version "+currentVersion+" in "+dir.getFile());
+		}
+		if( currentVersion == version )
+			return;  // ok
+
+		if( !buildSet.add(key) )
+			throw new RuntimeException("already building "+key);
+		build(key,dir);
+	}
+
+	private File checkFile(FSDirectory dir) {
+		return new File(dir.getFile(), "version");
+	}
+
+	private void build(K k,FSDirectory dir) throws IOException {
+		final File checkFile = checkFile(dir);
+		if( checkFile.exists() && !checkFile.delete() )
+			logger.error("couldn't delete "+checkFile+" for build");
+		final K key = unique.get(k);
+		try {
+			synchronized(key) {
+				IndexWriter indexWriter = writerCache.remove(key);
+				if( indexWriter != null )
+					indexWriter.close();
+				new IndexWriter(dir,analyzer,true,IndexWriter.MaxFieldLength.LIMITED).close(); // clear dir
+			}
+		} finally {
+			unique.free(key);
+		}
+		Thread thread = new Thread(new Runnable(){public void run(){
+			try {
+				logger.info("starting lucene index "+key);
+				builder.build(key);
+				IoUtils.write(checkFile,Integer.toString(version));
+				buildSet.remove(key);
+				logger.info("finished lucene index "+key);
+			} catch(Exception e) {
+				logger.error("lucene build failed for "+key,e);
+				buildSet.remove(key);
+				dirs.remove(key);
+			}
+		}},"lucene build "+key);
+		thread.setDaemon(true);
+		thread.start();
+	}
+
+	public boolean isReady(K key) {
+		dirs.get(key);
+		return !buildSet.contains(key);
+	}
+
+	public void rebuild(K key) throws IOException {
+		build( key, dirs.get(key) );
+	}
+
+	public void shutdown() {
+		isShutdown = true;
+		try {
+			while( !writerCache.isEmpty() ) {
+				List<K> keys = new ArrayList<K>(writerCache.keySet());
+				for( K key : keys ) {
+					IndexWriter indexWriter = writerCache.remove(key);
+					if( indexWriter != null )
+						indexWriter.close();
+				}
+			}
+		} catch(IOException e) {
+			logger.error("",e);
+		}
+	}
+/*
+	public void delete(K key) throws IOException {
+		key = unique.get(key);
+		try {
+			synchronized(key) {
+				MyIndexSearcher searcher = searcherCache.remove(key);
+				if( searcher != null )
+					searcher.getIndexReader().close();
+				IndexWriter indexWriter = writerCache.remove(key);
+				if( indexWriter == null )
+					indexWriter = new IndexWriter(dirs.get(key),analyzer,IndexWriter.MaxFieldLength.LIMITED);
+				indexWriter.deleteAll();
+				dirs.remove(key);
+				File dirFile = new File(dir,key.toString());
+				File versionFile = new File(dirFile, "version");
+				if( !versionFile.delete() )
+					logger.error("couldn't delete "+versionFile+" for site delete");
+				if( !dirFile.delete() )
+					logger.error("couldn't delete "+dirFile);
+			}
+		} finally {
+			unique.free(key);
+		}
+	}
+*/
+	void deleteUnusedIndexes() {
+		File[] files = dir.listFiles();
+		if( files == null )
+			return;  // not made yet
+		for( File indexDir : files ) {
+			if( Executors.isShuttingDown() )
+				return;
+			if( !builder.exists( indexDir.getName() ) ) {
+				if( !IoUtils.delete(indexDir) )
+					logger.error("couldn't delete "+indexDir);
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/lucene/LuceneSearcher.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,22 @@
+package nabble.model.lucene;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.CorruptIndexException;
+import java.io.IOException;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.TopFieldDocs;
+import org.apache.lucene.search.Sort;
+
+
+public interface LuceneSearcher {
+	public void close() throws IOException;
+	public void search(Query query,Collector results) throws IOException;
+	public void search(Query query,Filter filter,Collector results) throws IOException;
+	public TopDocs search(Query query,int n) throws IOException;
+	public TopDocs search(Query query,Filter filter,int n) throws IOException;
+	public TopFieldDocs search(Query query,Filter filter,int n,Sort sort) throws IOException;
+	public Document doc(int i) throws CorruptIndexException, IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/model/lucene/LuceneSearcherImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,64 @@
+package nabble.model.lucene;
+
+import java.io.IOException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.CorruptIndexException;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Filter;
+import org.apache.lucene.search.Collector;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.search.TopFieldDocs;
+import org.apache.lucene.search.Sort;
+
+
+final class LuceneSearcherImpl implements LuceneSearcher {
+	private static final Logger logger = LoggerFactory.getLogger(LuceneSearcherImpl.class);
+
+	private final LuceneSearcher searcher;
+	private boolean isClosed = false;
+
+	LuceneSearcherImpl(LuceneSearcher searcher) {
+		this.searcher = searcher;
+	}
+
+	public void close() throws IOException {
+		if( !isClosed ) {
+			searcher.close();
+			isClosed = true;
+		}
+	}
+
+	@Override protected void finalize() throws Throwable {
+		if( !isClosed ) {
+			logger.error("didn't close IndexSearcher"/*,initException*/);
+			close();
+		}
+		super.finalize();
+	}
+
+	public void search(Query query,Collector results) throws IOException {
+		searcher.search(query,results);
+	}
+
+	public void search(Query query,Filter filter,Collector results) throws IOException {
+		searcher.search(query,filter,results);
+	}
+
+	public TopDocs search(Query query,int n) throws IOException {
+		return searcher.search(query,n);
+	}
+
+	public TopDocs search(Query query,Filter filter,int n) throws IOException {
+		return searcher.search(query,filter,n);
+	}
+
+	public TopFieldDocs search(Query query,Filter filter,int n,Sort sort) throws IOException {
+		return searcher.search(query,filter,n,sort);
+	}
+
+	public Document doc(int i) throws CorruptIndexException, IOException {
+		return searcher.doc(i);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/ModuleManager.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,283 @@
+package nabble.modules;
+
+import fschmidt.util.java.CollectionUtils;
+import fschmidt.util.java.IoUtils;
+import nabble.model.Site;
+import nabble.modules.hacks.HacksModule;
+import nabble.modules.poll.PollModule;
+import nabble.modules.workgroup.WorkgroupModule;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Macro;
+import nabble.naml.compiler.Module;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.Source;
+import nabble.naml.compiler.StackTrace;
+import nabble.naml.compiler.StackTraceElement;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.compiler.TemplateRuntimeException;
+import nabble.naml.dom.Attribute;
+import nabble.naml.dom.Element;
+import nabble.naml.dom.ElementName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.io.Writer;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+public final class ModuleManager {
+	private static final Logger logger = LoggerFactory.getLogger(ModuleManager.class);
+
+	private static final Map<String,ModuleInfo> MODULES = new LinkedHashMap<String,ModuleInfo>();
+
+	private static class ModuleInfo {
+		final Module module;
+		final boolean isEnabledByDefault;
+
+		ModuleInfo(Module module,boolean isEnabledByDefault) {
+			this.module = module;
+			this.isEnabledByDefault = isEnabledByDefault;
+			MODULES.put( module.getName(), this );
+		}
+	}
+
+	static {
+		new ModuleInfo(WorkgroupModule.INSTANCE,false);
+		new ModuleInfo(PollModule.INSTANCE,true);
+		new ModuleInfo(HacksModule.INSTANCE,false);
+	}
+
+	private static final String CUSTOM_TWEAK_PREFIX = "custom_tweak:";
+	public static final String CONFIGURATION_TWEAK = "configuration";
+
+	private static List<Module> getBaseModules() {
+		List<Module> modules = new ArrayList<Module>();
+		modules.add(SOURCE_MODULE);
+		return modules;
+	}
+
+	public static List<Module> getGenericModules() {
+		List<Module> modules = getBaseModules();
+		for( ModuleInfo mi : MODULES.values() ) {
+			if( mi.isEnabledByDefault )
+				modules.add( mi.module );
+		}
+		modules = sort(modules);
+		return modules;
+	}
+
+	public static List<Module> getModules(Site site) {
+		List<Module> modules = getBaseModules();
+		for( ModuleInfo mi : MODULES.values() ) {
+			if( site.isModuleEnabled(mi.module.getName()) ) {
+				modules.add( mi.module );
+			}
+		}
+		modules = sort(modules);
+		if( site.getTweakException() == null ) {
+			String config = site.getConfigurationTweak();
+			if( config.length() > 0 ) {
+				Source source = Source.getInstance(CONFIGURATION_TWEAK,config);
+				Module module = new NamlModule( "config", Collections.singleton(source), Collections.<String>emptySet() );
+				modules.add(module);
+			}
+			Map<String,String> tweaks = site.getCustomTweaks();
+			if( !tweaks.isEmpty() ) {
+				List<Source> sources = new ArrayList<Source>();
+				for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
+					String name = entry.getKey();
+					String content = entry.getValue();
+					Source source = Source.getInstance(CUSTOM_TWEAK_PREFIX+name,content);
+					sources.add(source);
+				}
+				Module module = new NamlModule( "tweak", sources, Collections.<String>emptySet() );
+				modules.add(module);
+			}
+		}
+		return modules;
+	}
+
+	private static List<Module> sort(List<Module> modules) {
+		List<Module> rtn = new ArrayList<Module>();
+		Set<String> names = new HashSet<String>();
+		while( !modules.isEmpty() ) {
+			boolean changed = false;
+			for( Iterator<Module> iter = modules.iterator(); iter.hasNext(); ) {
+				Module m = iter.next();
+				if( names.containsAll(m.getDependencies()) ) {
+					rtn.add(m);
+					names.add(m.getName());
+					iter.remove();
+					changed = true;
+				}
+			}
+			if( !changed )
+				throw new RuntimeException("circular dependencies: "+modules);
+		}
+		return rtn;
+	}
+
+	public static Collection<Source> loadSource(Module module) {
+		String moduleName = module.getName();
+		try {
+			return Collections.singleton(
+				Source.getInstance(
+					moduleName + ":" + moduleName + ".naml",
+					IoUtils.read( ClassLoader.getSystemResource(
+						module.getClass().getPackage().getName().replace('.','/') + '/' + moduleName+".naml"
+					) )
+				)
+			);
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+
+	private static List<URL> listNamlFiles(String path) {
+		List<URL> list = new ArrayList<URL>();
+		for( URL url : IoUtils.getResources(ModuleManager.class) ) {
+			String s = url.toString();
+			if( s.endsWith(".naml") && s.indexOf(path) != -1 )
+				list.add(url);
+		}
+		return list;
+	}
+
+	private static final ElementName DEPENDENCY = new ElementName("dependency");
+	private static final Set<ElementName> IGNORE_TAGS = Collections.singleton(DEPENDENCY);
+	private static final Set<String> DEFAULT_ON = new HashSet<String>();
+	static {
+		DEFAULT_ON.add("responsive");
+		DEFAULT_ON.add("ads");
+	}
+
+	static {
+		try {
+			for( URL url : listNamlFiles("/nabble/modules/naml/") ) {
+				String s = url.toString();
+				String name = s.substring(s.lastIndexOf('/')+1);
+				name = name.substring(0,name.length()-5);
+				Source source = Source.getInstance( name + ":" + name + ".naml", IoUtils.read(url), IGNORE_TAGS );
+				Set<String> dependencies = new HashSet<String>();
+				StackTrace stackTrace = new StackTrace();
+				for( Object obj : source.parse() ) {
+					if( obj instanceof Element ) {
+						Element element = (Element)obj;
+						if( element.name().equals(DEPENDENCY) ) {
+							stackTrace.push( new StackTraceElement(source,element) );
+							try {
+								Attribute attr = element.getAttribute("module");
+								if( attr == null )
+									throw new CompileException(stackTrace,"module attribute required");
+								String dependency = attr.value().toString();
+								dependencies.add(dependency);
+							} finally {
+								stackTrace.pop();
+							}
+						}
+					}
+				}
+				Module module = new NamlModule( name, Collections.singleton(source), CollectionUtils.optimizeSet(dependencies) );
+				new ModuleInfo(module,DEFAULT_ON.contains(name));
+			}
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		} catch(CompileException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+
+	public static Module getModule(String moduleName) {
+		return MODULES.get(moduleName).module;
+	}
+
+	public static boolean isEnabledByDefault(String moduleName) {
+		ModuleInfo info = MODULES.get(moduleName);
+		return info != null && info.isEnabledByDefault;
+	}
+
+
+	// from TemplateManager
+
+	private static final Module SOURCE_MODULE;
+
+	static {
+		List<Source> sources = new ArrayList<Source>();
+		try {
+			for( URL url : listNamlFiles("/nabble/view/naml/") ) {
+				String s = url.toString();
+				String name = s.substring(s.lastIndexOf('/')+1);
+				String content = IoUtils.read(url);
+				sources.add( Source.getInstance("nabble:"+name,content) );
+			}
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+		SOURCE_MODULE = new NamlModule( "nabble", sources, Collections.<String>emptySet() );
+		logger.info("SOURCES has " + sources.size() + " macros");
+	}
+
+
+
+	public static boolean isConfigurationTweak(Source source) {
+		return source.id.equals(CONFIGURATION_TWEAK);
+	}
+
+	public static boolean isCustomTweak(Source source) {
+		return source.id.startsWith(CUSTOM_TWEAK_PREFIX);
+	}
+
+	public static List<Macro> getConfigurationMacros(Program program) throws CompileException {
+		List<Macro> macros = new ArrayList<Macro>();
+		for( Source source : program.getSources() ) {
+			if( isConfigurationTweak(source) ) {
+				macros.addAll(source.getMacros());
+			}
+		}
+		return macros;
+	}
+
+	public static List<Macro> getCustomMacros(Program program) throws CompileException {
+		List<Macro> macros = new ArrayList<Macro>();
+		for( Source source : program.getSources() ) {
+			if( isCustomTweak(source) ) {
+				macros.addAll(source.getMacros());
+			}
+		}
+		return macros;
+	}
+
+	public static void run(Template template,Writer out,Map<String,Object> args,Object... base)
+		throws IOException, ServletException
+	{
+		try {
+			template.run( new TemplatePrintWriter(out), args, base );
+		} catch(TemplateRuntimeException e) {
+			Throwable cause = e.getCause();
+			if( cause instanceof IOException )
+				throw (IOException)cause;
+			if( cause instanceof ServletException )
+				throw new ServletException(cause.getMessage(),e);
+			throw e;
+		}
+	}
+
+	public static void nop() {}
+
+	private ModuleManager() {}  // never
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/NamlModule.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,36 @@
+package nabble.modules;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import nabble.naml.compiler.Module;
+import nabble.naml.compiler.Source;
+
+
+public final class NamlModule implements Module {
+	private final String name;
+	private final Collection<Source> sources;
+	private final Set<String> dependencies;
+
+	public NamlModule(String name,Collection<Source> sources,Set<String> dependencies) {
+		this.name = name;
+		this.sources = sources;
+		this.dependencies = dependencies;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public Iterable<Class> getExtensions() {
+		return Collections.emptySet();
+	}
+
+	public Collection<Source> getSources() {
+		return sources;
+	}
+
+	public Set<String> getDependencies() {
+		return dependencies;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/ad/Ad.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,211 @@
+package nabble.modules.ad;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbExpression;
+import fschmidt.db.DbNull;
+import fschmidt.db.DbRecord;
+import fschmidt.db.NoKey;
+import nabble.model.Db;
+import nabble.model.ExtensionFactory;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.model.ViewCount;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.web.template.NabbleNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+
+public final class Ad {
+	private static final Logger logger = LoggerFactory.getLogger(Ad.class);
+
+	private static final String AD_MODULE_TASK = "ad_free";
+
+	private static final ExtensionFactory<Site,Ad> FACTORY = new ExtensionFactory<Site,Ad>() {
+
+		private final Set<Long> adFreeSites = Collections.newSetFromMap(new ConcurrentHashMap<Long,Boolean>());
+
+		public String getName() {
+			return AdModule.INSTANCE.getName();
+		}
+
+		public Class<Ad> extensionClass() {
+			return Ad.class;
+		}
+
+		public Ad construct(Site site) {
+			return null;
+		}
+
+		public Ad construct(Site site,ResultSet rs)
+			throws SQLException
+		{
+			boolean isSafe = rs.getBoolean("is_safe");
+			int credits = rs.getInt("ad_free");
+			if( credits > 0 && adFreeSites.add(site.getId()) )
+				site.addTask(AD_MODULE_TASK);
+			boolean isCreditsForUsersOnly = rs.getBoolean("ad_credits_for_users");
+			return new Ad(site,isSafe,credits,isCreditsForUsersOnly);
+		}
+
+		public Serializable getExportData(Site site) {
+			throw new UnsupportedOperationException();
+		}
+
+		public void saveExportData(Site site,Serializable s) {
+			throw new UnsupportedOperationException();
+		}
+	};
+
+	static {
+		ModelHome.addSiteExtensionFactory(FACTORY);
+		ViewCount.addCalculateActivityListener(new Runnable(){public void run(){
+			try {
+				for( Site site : ModelHome.getSitesForTask(AD_MODULE_TASK) ) {
+					Ad ad = Ad.of(site);
+					int views;
+					{
+						Connection con = Db.dbGlobal().getConnection();
+						String field = ad.isCreditsForUsersOnly ? "user_views" : "views";
+						PreparedStatement stmt = con.prepareStatement(
+							"select " + field + " from site_global where site_id = ?"
+						);
+						stmt.setLong(1,site.getId());
+						ResultSet rs = stmt.executeQuery();
+						rs.next();
+						views = rs.getInt(field);
+						rs.close();
+						stmt.close();
+						con.close();
+						if( views > 0 && !ad.wasCreatedRecently() ) {
+							ad.decCredits(views);
+						} else {
+							site.addTask(AD_MODULE_TASK);
+						}
+					}
+					if( ad.getCredits() > 0 ) {
+						Connection con = site.getDb().getConnection();
+						PreparedStatement stmt = con.prepareStatement(
+							"update site set monthly_views = monthly_views*29/30 + ?"
+						);
+						stmt.setInt(1,views);
+						stmt.executeUpdate();
+						stmt.close();
+						con.close();
+					}
+				}
+			} catch(SQLException e) {
+				logger.error("",e);
+				throw new RuntimeException(e);
+			}
+		}});
+	}
+
+	static void init() {}
+
+	public static Ad of(Site site) {
+		return site.getExtension(FACTORY);
+	}
+
+
+	private final Site site;
+	private boolean isSafe;
+	private int credits = 0;
+	private boolean isCreditsForUsersOnly;
+
+	Ad(Site site,boolean isSafe,int credits,boolean isCreditsForUsersOnly) {
+		this.site = site;
+		this.isSafe = isSafe;
+		this.credits = credits;
+		this.isCreditsForUsersOnly = isCreditsForUsersOnly;
+	}
+
+	public boolean isSafe() {
+		return isSafe;
+	}
+
+	public void setSafe(boolean isSafe) {
+		DbRecord<NoKey,?> record = site.getDbRecord();
+		this.isSafe = isSafe;
+		record.fields().put( "is_safe", isSafe );
+//		if( !isSafe ) {
+//			record.fields().put( "when_created", DbExpression.NOW );
+//			record.fields().put( "ad_credits_for_users", false );
+//		}
+		if (!site.getDb().isInTransaction()) {
+			record.update();
+		}
+	}
+
+	public int getCredits() {
+		return credits;
+	}
+
+	public void setCredits(int credits) {
+		if( credits < 0 )
+			credits = 0;
+		DbRecord<NoKey,?> record = site.getDbRecord();
+		this.credits = credits;
+		record.fields().put( "ad_free", DbNull.fix(credits) );
+		DbDatabase db = site.getDb();
+		if( !db.isInTransaction() )
+			record.update();
+		if( credits > 0 )
+			site.addTask(AD_MODULE_TASK);
+	}
+
+	public void incCredits(int n) {
+		setCredits(this.credits + n);
+	}
+
+	private void decCredits(int n) {
+		setCredits(this.credits - n);
+
+		// notify user?
+		// I'm supposing that everyday we will deduct n credits, so he has one more day to buy new ones
+		// I test if he has more than 0 credits to avoid send emails to everyone that doesn't have or buy credits
+		if( this.credits < n && this.credits > 0) {  // condition here
+			Template template = site.getTemplate( "ad_notice",
+				BasicNamespace.class, NabbleNamespace.class
+			);
+			template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(),
+				new BasicNamespace(template),
+				new NabbleNamespace(site)
+			);
+		}
+	}
+
+	public boolean isPaid() {
+		return false;
+	}
+
+	public boolean isCreditsForUsersOnly() {
+		return isCreditsForUsersOnly;
+	}
+
+	private static long ONE_DAY = 24 * 60 * 60 * 1000L; // 1 day
+	private static long LIMIT_DAYS = 6 * 30 * ONE_DAY; // 6 months
+
+	boolean wasCreatedRecently() {
+		long whenCreated = site.getWhenCreated().getTime();
+		long now = new Date().getTime();
+		return now < whenCreated + LIMIT_DAYS;
+	}
+
+ 	long lastDayWithoutAds() {
+		return site.getWhenCreated().getTime() + LIMIT_DAYS;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/ad/AdModule.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,47 @@
+package nabble.modules.ad;
+
+import fschmidt.util.java.FutureValue;
+import nabble.modules.ModuleManager;
+import nabble.naml.compiler.Module;
+import nabble.naml.compiler.Source;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+
+public enum AdModule implements Module {
+	INSTANCE;
+
+	private static final Iterable<Class> extensions = Arrays.asList(new Class[] {
+		BaseNamespaceExt.class,
+//		StripePayment.class,
+	});
+
+	private final FutureValue<Collection<Source>> sources = new FutureValue<Collection<Source>>() {
+		protected Collection<Source> compute() {
+			return ModuleManager.loadSource(AdModule.this);
+		}
+	};
+
+	public String getName() {
+		return "ad";
+	}
+
+	public Iterable<Class> getExtensions() {
+		return extensions;
+	}
+
+	public Collection<Source> getSources() {
+		return sources.get();
+	}
+
+	public Set<String> getDependencies() {
+		return Collections.emptySet();
+	}
+
+	static {
+		Ad.init();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/ad/BaseNamespaceExt.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,61 @@
+package nabble.modules.ad;
+
+import nabble.model.Init;
+import nabble.model.Site;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.NamespaceExtension;
+import nabble.view.web.template.NabbleNamespace;
+
+
+@NamespaceExtension (
+	name = "ad",
+	target = NabbleNamespace.class
+)
+public final class BaseNamespaceExt {
+
+	private final Site site;
+
+	public BaseNamespaceExt(NabbleNamespace ns) {
+		this.site = ns.site();
+	}
+
+	@Command public void is_ad_safe(IPrintWriter out,Interpreter interp) {
+		Ad ad = Ad.of(site);
+		out.print( ad.isSafe() );
+	}
+
+	@Command public void current_credits(IPrintWriter out,Interpreter interp) {
+		Ad ad = Ad.of(site);
+		out.print( ad==null ? 0 : ad.getCredits() );
+	}
+
+	@Command public void ad_javascript(IPrintWriter out,Interpreter interp) {
+		out.print( Javascript.text(Ad.of(site)) );
+	}
+
+	@Command public void has_ads(IPrintWriter out,Interpreter interp) {
+		out.print(false);
+	}
+
+	@Command public void has_credits(IPrintWriter out,Interpreter interp) {
+		Ad ad = Ad.of(site);
+		out.print( ad.getCredits() > 0 );
+	}
+
+	@Command public void is_paid_site(IPrintWriter out,Interpreter interp) {
+		out.print(false);
+	}
+
+	@Command public void was_created_recently(IPrintWriter out,Interpreter interp) {
+		Ad ad = Ad.of(site);
+		out.print(ad.wasCreatedRecently());
+	}
+
+	public static String adbayesLink = Init.get("adbayesLink", null);
+
+	@Command public void adbayes_link(IPrintWriter out,Interpreter interp) {
+		out.print(adbayesLink);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/ad/Javascript.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,41 @@
+
+package nabble.modules.ad;
+
+import nabble.model.Init;
+
+
+final class Javascript {
+
+	public static String BANNER_IMG = Init.get("bannerImg", "ad_nabble.png");
+	public static String BANNER_URL = Init.get("bannerUrl", "https://www.nabble.com/");
+
+	public static volatile String hackAds = "";
+
+	static String text(Ad ad) {
+		boolean isSafe = ad.isSafe();
+		return 
+		"\r\n\r\n		var _gaq = _gaq || [];\r\n\r\n		var isA;\r\n\r\n		(function() {\r\n			var ab = Nabble.getCookie(\"ab\");\r\n			if( ab == null ) {\r\n				ab = Math.random() < 0.5 ? \"a\" : \"b\";\r\n				Nabble.setCookie(\"ab\",ab);\r\n			}\r\n			isA = ab == \"a\";\r\n		})();\r\n\r\n		var isB = !isA;\r\n\r\n		Nabble.isSafe = "
+		+(isSafe)
+		+";\r\n		Nabble.adGroup = '"
+		+( isSafe ? "SAFE" : "BAD" )
+		+"';\r\n\r\n		var lastDayWithoutAds = "
+		+(ad.lastDayWithoutAds())
+		+";\r\n		var daysLeft = Math.round((lastDayWithoutAds - new Date().getTime()) / (24 * 60 * 60 * 1000));\r\n		Nabble.currentCredits = "
+		+(ad.getCredits())
+		+";\r\n		Nabble.isPaid = "
+		+(ad.isPaid())
+		+";\r\n		Nabble.creditsForUsersOnly = "
+		+(ad.isCreditsForUsersOnly())
+		+";\r\n		Nabble.wasCreatedRecently = daysLeft > 0;\r\n		Nabble.removeAdsPath = '/template/NamlServlet.jtp?macro=site_payment';\r\n\r\n		Nabble.noAds = function(location) {\r\n			Nabble.noAds = function(location) {};\r\n			_gaq.push(['nabble._trackEvent', 'ad-type', 'NONE']);\r\n		};\r\n\r\n		Nabble.ads = function(location) {\r\n			if (Nabble.currentCredits > 0 && Nabble.creditsForUsersOnly && Nabble.userId)\r\n				return;\r\n			Nabble.fixAdGroup(location);\r\n			Ads[Nabble.adGroup](location);\r\n		};\r\n\r\n		Nabble.adbayes = function() {\r\n			window.Adbayes = window.Adbayes || { properties:{} };\r\n			Adbayes.properties['domain'] = window.location.hostname;\r\n			Adbayes.properties['adtype'] = Nabble.adGroup;\r\n\r\n			if (Nabble.wasCreatedRecently && Nabble.adGroup == 'SAFE')\r\n				Nabble.adGroup = \"NEW\";\r\n\r\n			if( Nabble.adGroup=='SAFE' && Adbayes.result != 'safe' )\r\n				Nabble.adGroup = 'UNSAFE';\r\n\r\n			if( Nabble.adGroup=='SAFE' )\r\n				_gaq.push(['nabble._setCustomVar', 1, 'Safe', 'x']);\r\n		};\r\n\r\n		Nabble.fixAdGroup = function(location) {\r\n			Nabble.fixAdGroup = function(location) {};\r\n\r\n			if (!window.Adbayes) {\r\n				Nabble.adGroup = \"NEW\";\r\n				_gaq.push(['nabble._trackEvent', 'ad-type', Nabble.adGroup]);\r\n				return;\r\n			}\r\n\r\n			switch(location) {\r\n				case 'app_bottom':\r\n					Adbayes.properties['page-type'] = 'forum';\r\n					break;\r\n				case 'first_classic_message':\r\n				case 'last_classic_message':\r\n					Adbayes.properties['page-type'] = 'classic-view';\r\n					break;\r\n				case 'list_bottom':\r\n					Adbayes.properties['page-type'] = 'list-view';\r\n					break;\r\n				case 'threaded_bottom':\r\n					Adbayes.properties['page-type'] = 'threaded-view';\r\n					break;\r\n				case 'blog_topic':\r\n					Adbayes.properties['page-type'] = 'blog-topic';\r\n					break;\r\n				case 'search_top':\r\n				case 'search_bottom':\r\n					Adbayes.properties['page-type'] = 'search';\r\n					break;\r\n				case 'widget':\r\n					Adbayes.properties['page-type'] = 'widget';\r\n					break;\r\n				default:\r\n					Adbayes.properties['page-type'] = 'unknown:' + location;\r\n					break;\r\n			}\r\n			Nabble.realTimeAnalytics();\r\n		};\r\n\r\n		Nabble.realTimeAnalytics = function() {\r\n			_gaq.push(['nabble._trackEvent', 'ad-type', Nabble.adGroup]);\r\n			if (Adbayes.properties['adtype'] == 'SAFE' && window.Adbayes.listeners) {\r\n				window.Adbayes.listeners.push(function() {\r\n					var page = Adbayes.properties['page-type'];\r\n					var status;\r\n					if (Adbayes.isNew) {\r\n						status = \"new\";\r\n					} else if (Adbayes.isCanonical) {\r\n						status = \"canonical\";\r\n					} else if(Adbayes.isOutdated) {\r\n						status = \"outdated\";\r\n					} else {\r\n						status = \"set\";\r\n					}\r\n					_gaq.push(['nabble._trackEvent', 'adbayes-status', page + ':' + status, Adbayes.result]);\r\n				});\r\n			}\r\n		};\r\n\r\n\r\n		var Ads = {};\r\n\r\n		Ads.styles = {\r\n			widget: { cls: '', style: '' },\r\n			blog_topic: { cls: 'shaded-bg-color rounded', style: 'padding:.5em;' },\r\n			first_classic_message: { cls: '', style: 'text-align:left;' },\r\n			last_classic_message: { cls: '', style: 'text-align:left;padding-top:1em;' },\r\n			list_bottom: { cls: '', style: 'margin-top:1.5em;' },\r\n			threaded_bottom: { cls: '', style: 'margin-top:1.5em;' },\r\n			search_top: { cls: '', style: 'text-align:left;margin:1em 0;padding:.5em 0;' },\r\n			search_bottom: { cls: '', style: 'text-align:left;padding:.5em 0;' },\r\n			app_bottom: { cls: '', style: 'margin:2.5em auto;text-align:center;' },\r\n			topic_bottom: { cls: '', style: '' }\r\n		};\r\n\r\n		Ads.NEW = function(location) {\r\n			switch(location) {\r\n			case 'blog_topic':\r\n			case 'first_classic_message':\r\n			case 'list_bottom':\r\n			case 'threaded_bottom':\r\n			case 'search_bottom':\r\n			case 'app_bottom':\r\n			}\r\n		};\r\n\r\n		Ads.NABBLE = function(location) {\r\n			switch(location) {\r\n			case 'blog_topic':\r\n			case 'first_classic_message':\r\n			case 'list_bottom':\r\n			case 'threaded_bottom':\r\n			case 'search_bottom':\r\n				Nabble.showAd( location, function() {\r\n					var imgName = '"
+		+(BANNER_IMG)
+		+"';\r\n					var url = '"
+		+(BANNER_URL)
+		+"';\r\n					_gaq.push(['nabble._trackEvent', 'banner', imgName]);\r\n					document.write('<a href=\"'+url+'\" rel=\"nofollow\" style=\"display:block !important;visibility:visible !important;height:auto !important; width: auto !important;position:static !important;top:auto !important;left:auto !important\"><img src=\"http://static.nabble.com/images/'+imgName+'\" style=\"border:1px solid #bbb;display:inline !important;visibility:visible !important;height:auto !important;width:auto !important\"/></a>');\r\n				} );\r\n			}\r\n		};\r\n\r\n		Ads.AdSense = function(location) {\r\n			switch(location) {\r\n			case 'blog_topic':\r\n				Nabble.showAd( location, function() {\r\n					google_ad_slot = \"8807592501\";\r\n					google_ad_width = 728;\r\n					google_ad_height = 90;\r\n					Nabble._adsense();\r\n				} );\r\n				break;\r\n			case 'first_classic_message':\r\n				Nabble.showAd( location, function() {\r\n					var fn = function() {\r\n						google_ad_slot = \"8703015991\";\r\n						google_ad_width = 336;\r\n						google_ad_height = 280;\r\n						Nabble._adsense();\r\n					};\r\n					document.writeln('<div style=\"width:336px; float:left; clear:none; display:block !important;\">');\r\n					Nabble.writeFn(fn);\r\n					document.writeln('</div>');\r\n					document.writeln('<div style=\"width:336px; float:left; clear:none; display:block !important;\">');\r\n					Nabble.writeFn(fn);\r\n					document.writeln('</div>');\r\n				}, \"width:675px !important;\" );\r\n				break;\r\n			case 'last_classic_message':\r\n				Nabble.showAd( location, function() {\r\n					google_ad_slot = \"8323336297\";\r\n					google_ad_width = 728;\r\n					google_ad_height = 90;\r\n					Nabble._adsense();\r\n				} );\r\n				break;\r\n			case 'list_bottom':\r\n				Nabble.showAd( location, function() {\r\n					google_ad_slot = \"6508328921\";\r\n					google_ad_width = 728;\r\n					google_ad_height = 90;\r\n					Nabble._adsense();\r\n				} );\r\n				break;\r\n			case 'threaded_bottom':\r\n				Nabble.showAd( location, function() {\r\n					google_ad_slot = \"8434893994\";\r\n					google_ad_width = 728;\r\n					google_ad_height = 90;\r\n					Nabble._adsense();\r\n				} );\r\n				break;\r\n			case 'search_top':\r\n				Nabble.AdsenseSearchAds();\r\n				break;\r\n			case 'search_bottom':\r\n				Nabble.showAd( location, function() {\r\n					google_ad_slot = \"0099567777\";\r\n					google_ad_width = 728;\r\n					google_ad_height = 90;\r\n					google_ad_type = \"image\";\r\n					Nabble._adsense();\r\n				} );\r\n				break;\r\n			case 'app_bottom':\r\n				Nabble.showAd( location, function() {\r\n					var fn = function() {\r\n						google_ad_slot = \"1057993213\";\r\n						google_ad_width = 728;\r\n						google_ad_height = 90;\r\n						Nabble._adsense();\r\n					};\r\n					Nabble.writeFn(fn);\r\n				} );\r\n				break;\r\n			}\r\n		};\r\n\r\n		Ads.SAFE = function(location) {\r\n			Ads.AdSense(location);\r\n		};\r\n\r\n		Ads.ST = function(location) {\r\n			if (Nabble.currentCredits > 0 || Nabble.isPaid)\r\n				return;\r\n			window.has_st = true;\r\n		};\r\n\r\n		Ads.BAD = Ads.ST;\r\n		Ads.UNSAFE = Ads.ST;\r\n\r\n		Nabble.showAd = function(location,fn,style) {\r\n			var customStyle = Ads.styles[location].style + (style == null?'':style);\r\n			document.writeln('<div class=\"ad '+Ads.styles[location].cls+'\" style=\"display:block !important;visibility:visible !important;height:auto !important; width: auto !important;position:static !important;top:auto !important;left:auto !important;'+customStyle+'\">');\r\n			fn();\r\n			if (!Nabble.creditsForUsersOnly || Nabble.userId) {\r\n				document.writeln('<div style=\"padding-top:.3em;font-size:90%;clear:both\"><a href=\"'+Nabble.removeAdsPath+'\">'+Ads.text.removeAds+'</a></div>');\r\n				document.writeln('</div>');\r\n			}\r\n		};\r\n\r\n		Nabble.AdsenseSearchAds = function() {\r\n			if (nabble_search_query && nabble_search_query != '') {\r\n				(function(G,o,O,g,L,e){G[g]=G[g]||function(){(G[g]['q']=G[g]['q']||[]).push(\r\n				arguments)},G[g]['t']=1*new Date;L=o.createElement(O),e=o.getElementsByTagName(\r\n				O)[0];L.async=1;L.src='//www.google.com/adsense/search/async-ads.js';\r\n				e.parentNode.insertBefore(L,e)})(window,document,'script','_googCsa');\r\n\r\n				document.writeln('<div id=\"ad1\" style=\"margin:0 0 1em -.3em\"></div>');\r\n				var pageOptions = {\r\n					'pubId': 'pub-6703598369329977',\r\n					'query': nabble_search_query,\r\n					'hl': 'en'\r\n				};\r\n				var ad = Nabble._ad();\r\n				var bg = '#'+Nabble._bgcolor(ad);\r\n				var tc = '#'+Nabble._color('div.search-results-header','808080');\r\n				var lc = '#'+Nabble._linkColor(ad);\r\n				var adblock1 = {\r\n					'container': 'ad1',\r\n					'width': '100%',\r\n					'fontFamily': 'verdana',\r\n					'fontSizeTitle': 15,\r\n					'fontSizeDescription': 13,\r\n					'fontSizeDomainLink': 13,\r\n					'colorTitleLink': lc,\r\n					'colorText': tc,\r\n					'colorDomainLink': tc,\r\n					'colorBackground': bg,\r\n					'colorAdBorder': bg,\r\n					'colorBorder': bg\r\n				};\r\n				_googCsa('ads', pageOptions, adblock1);\r\n			}\r\n		};\r\n\r\n		Nabble.writeFn = function(fn) {\r\n			Nabble._fn = fn;\r\n			document.writeln('<script type=\"text/javascript\">Nabble._fn()</script>');\r\n		};\r\n\r\n\r\n		Nabble._adwords = function() {\r\n			google_conversion_id = 975361083;\r\n			google_conversion_language = \"en\";\r\n			google_conversion_format = \"3\";\r\n			google_conversion_color = \"ffffff\";\r\n			google_conversion_label = \"sfP9CM34nwgQu6iL0QM\";\r\n			google_conversion_value = 10;\r\n			google_remarketing_only = false;\r\n			document.write('<script type=\"text/javascript\" src=\"//www.googleadservices.com/pagead/conversion.js\"></script>');\r\n		};\r\n\r\n		Nabble._adsense = function() {\r\n			var ad = Nabble._ad();\r\n			if( google_ad_width==728 && Nabble._narrowUnit(ad,728) ) {\r\n				google_ad_slot = \"1143776366\";\r\n				google_ad_width = 468;\r\n				google_ad_height = 60;\r\n			}\r\n			var bg = Nabble._bgcolor(ad);\r\n			google_color_border = bg;\r\n			google_color_bg = bg;\r\n			google_color_text = \"808080\";\r\n			google_color_url = \"808080\";\r\n			google_color_link = Nabble._linkColor(ad);\r\n			google_ad_client = \"ca-pub-6703598369329977\";\r\n			document.write('<script type=\"text/javascript\" src=\"http://pagead2.googlesyndication.com/pagead/show_ads.js\"></script>');\r\n			var __ad = 'div'+ad.getAttribute('id').substring(2);\r\n			$(ad).parent().addClass(__ad);\r\n 			document.write('<style type=\"text/css\">');\r\n 			document.write('.nabble .' + __ad + ' ins { display:inline-table !important; visibility: visible !important; height:' + google_ad_height + 'px !important; width: ' + google_ad_width + 'px !important; position:static !important;top:auto !important;left:auto !important; }');\r\n 			document.write('.nabble .' + __ad + ' ins ins iframe { display:block !important; visibility: visible !important; height:' + google_ad_height + 'px !important; width: ' + google_ad_width + 'px !important; position:static !important;top:auto !important;left:auto !important; }');\r\n 			document.write('</style>');\r\n		};\r\n\r\n		Nabble._ad = function() {\r\n			var ad = \"ad\" + Math.round(Math.random() * 999999);\r\n			document.writeln('<a id=\"'+ad+'\" href=\"#\" style=\"display:none\">.</a>');\r\n			return Nabble.get(ad);\r\n		};\r\n\r\n		Nabble._bgcolor = function(ad) {\r\n			var $elem = $(ad);\r\n			var $parent = $elem;\r\n			var bg;\r\n			while ($parent.size() > 0) {\r\n				try {\r\n					bg = $parent.css('background-color');\r\n				} catch(err) {\r\n					$elem.addClass('shaded-bg-color');\r\n					bg = $elem.css('background-color');\r\n					break;\r\n				}\r\n				if (bg != 'transparent' && bg.indexOf('rgba') != 0)\r\n					break;\r\n				$parent = $parent.parent();\r\n			}\r\n			if (bg.charAt(0) == '#')\r\n				return bg.substring(1);\r\n			else if (bg.indexOf('rgb') == 0) {\r\n				bg = bg.substring(4, bg.length-1);\r\n				var rgb = bg.split(',');\r\n				return Nabble._hexa(rgb[0]) + Nabble._hexa(rgb[1]) + Nabble._hexa(rgb[2]);\r\n			}\r\n			return bg;\r\n		};\r\n\r\n		Nabble._linkColor = function(ad) {\r\n			var c = $(ad).css('color');\r\n			if (c.charAt(0) == '#')\r\n				return c.substring(1);\r\n			else if (c.indexOf('rgb') == 0) {\r\n				c = c.substring(4, c.length-1);\r\n				var rgb = c.split(',');\r\n				return Nabble._hexa(rgb[0]) + Nabble._hexa(rgb[1]) + Nabble._hexa(rgb[2]);\r\n			}\r\n			return 'EEEEEE';\r\n		};\r\n\r\n		Nabble._color = function(sel,def) {\r\n			var c = $(sel).css('color');\r\n			if (c.charAt(0) == '#')\r\n				return c.substring(1);\r\n			else if (c.indexOf('rgb') == 0) {\r\n				c = c.substring(4, c.length-1);\r\n				var rgb = c.split(',');\r\n				return Nabble._hexa(rgb[0]) + Nabble._hexa(rgb[1]) + Nabble._hexa(rgb[2]);\r\n			}\r\n			return def;\r\n		};\r\n\r\n		Nabble._hexa = function(c) {\r\n			var n = parseInt(c).toString(16);\r\n			return n.length == 1? '0' + n : n;\r\n		};\r\n\r\n		Nabble._narrowUnit = function(ad,limit) {\r\n			return $(ad).parent().width() < limit;\r\n		};\r\n\r\n\r\n		var b = function() {};\r\n\r\n		"
+		+(hackAds)
+		+"\r\n\r\n		if(isB) b();\r\n		"
+;
+	}
+
+	private Javascript() {}  // never
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/ad/Javascript.jmp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,424 @@
+<%
+package nabble.modules.ad;
+
+import nabble.model.Init;
+
+
+final class Javascript {
+
+	public static String BANNER_IMG = Init.get("bannerImg", "ad_nabble.png");
+	public static String BANNER_URL = Init.get("bannerUrl", "https://www.nabble.com/");
+
+	public static volatile String hackAds = "";
+
+	static String text(Ad ad) {
+		boolean isSafe = ad.isSafe();
+		return %>
+
+		var _gaq = _gaq || [];
+
+		var isA;
+
+		(function() {
+			var ab = Nabble.getCookie("ab");
+			if( ab == null ) {
+				ab = Math.random() < 0.5 ? "a" : "b";
+				Nabble.setCookie("ab",ab);
+			}
+			isA = ab == "a";
+		})();
+
+		var isB = !isA;
+
+		Nabble.isSafe = <%=isSafe%>;
+		Nabble.adGroup = '<%= isSafe ? "SAFE" : "BAD" %>';
+
+		var lastDayWithoutAds = <%=ad.lastDayWithoutAds()%>;
+		var daysLeft = Math.round((lastDayWithoutAds - new Date().getTime()) / (24 * 60 * 60 * 1000));
+		Nabble.currentCredits = <%=ad.getCredits()%>;
+		Nabble.isPaid = <%=ad.isPaid()%>;
+		Nabble.creditsForUsersOnly = <%=ad.isCreditsForUsersOnly()%>;
+		Nabble.wasCreatedRecently = daysLeft > 0;
+		Nabble.removeAdsPath = '/template/NamlServlet.jtp?macro=site_payment';
+
+		Nabble.noAds = function(location) {
+			Nabble.noAds = function(location) {};
+			_gaq.push(['nabble._trackEvent', 'ad-type', 'NONE']);
+		};
+
+		Nabble.ads = function(location) {
+			if (Nabble.currentCredits > 0 && Nabble.creditsForUsersOnly && Nabble.userId)
+				return;
+			Nabble.fixAdGroup(location);
+			Ads[Nabble.adGroup](location);
+		};
+
+		Nabble.adbayes = function() {
+			window.Adbayes = window.Adbayes || { properties:{} };
+			Adbayes.properties['domain'] = window.location.hostname;
+			Adbayes.properties['adtype'] = Nabble.adGroup;
+
+			if (Nabble.wasCreatedRecently && Nabble.adGroup == 'SAFE')
+				Nabble.adGroup = "NEW";
+
+			if( Nabble.adGroup=='SAFE' && Adbayes.result != 'safe' )
+				Nabble.adGroup = 'UNSAFE';
+
+			if( Nabble.adGroup=='SAFE' )
+				_gaq.push(['nabble._setCustomVar', 1, 'Safe', 'x']);
+		};
+
+		Nabble.fixAdGroup = function(location) {
+			Nabble.fixAdGroup = function(location) {};
+
+			if (!window.Adbayes) {
+				Nabble.adGroup = "NEW";
+				_gaq.push(['nabble._trackEvent', 'ad-type', Nabble.adGroup]);
+				return;
+			}
+
+			switch(location) {
+				case 'app_bottom':
+					Adbayes.properties['page-type'] = 'forum';
+					break;
+				case 'first_classic_message':
+				case 'last_classic_message':
+					Adbayes.properties['page-type'] = 'classic-view';
+					break;
+				case 'list_bottom':
+					Adbayes.properties['page-type'] = 'list-view';
+					break;
+				case 'threaded_bottom':
+					Adbayes.properties['page-type'] = 'threaded-view';
+					break;
+				case 'blog_topic':
+					Adbayes.properties['page-type'] = 'blog-topic';
+					break;
+				case 'search_top':
+				case 'search_bottom':
+					Adbayes.properties['page-type'] = 'search';
+					break;
+				case 'widget':
+					Adbayes.properties['page-type'] = 'widget';
+					break;
+				default:
+					Adbayes.properties['page-type'] = 'unknown:' + location;
+					break;
+			}
+			Nabble.realTimeAnalytics();
+		};
+
+		Nabble.realTimeAnalytics = function() {
+			_gaq.push(['nabble._trackEvent', 'ad-type', Nabble.adGroup]);
+			if (Adbayes.properties['adtype'] == 'SAFE' && window.Adbayes.listeners) {
+				window.Adbayes.listeners.push(function() {
+					var page = Adbayes.properties['page-type'];
+					var status;
+					if (Adbayes.isNew) {
+						status = "new";
+					} else if (Adbayes.isCanonical) {
+						status = "canonical";
+					} else if(Adbayes.isOutdated) {
+						status = "outdated";
+					} else {
+						status = "set";
+					}
+					_gaq.push(['nabble._trackEvent', 'adbayes-status', page + ':' + status, Adbayes.result]);
+				});
+			}
+		};
+
+
+		var Ads = {};
+
+		Ads.styles = {
+			widget: { cls: '', style: '' },
+			blog_topic: { cls: 'shaded-bg-color rounded', style: 'padding:.5em;' },
+			first_classic_message: { cls: '', style: 'text-align:left;' },
+			last_classic_message: { cls: '', style: 'text-align:left;padding-top:1em;' },
+			list_bottom: { cls: '', style: 'margin-top:1.5em;' },
+			threaded_bottom: { cls: '', style: 'margin-top:1.5em;' },
+			search_top: { cls: '', style: 'text-align:left;margin:1em 0;padding:.5em 0;' },
+			search_bottom: { cls: '', style: 'text-align:left;padding:.5em 0;' },
+			app_bottom: { cls: '', style: 'margin:2.5em auto;text-align:center;' },
+			topic_bottom: { cls: '', style: '' }
+		};
+
+		Ads.NEW = function(location) {
+			switch(location) {
+			case 'blog_topic':
+			case 'first_classic_message':
+			case 'list_bottom':
+			case 'threaded_bottom':
+			case 'search_bottom':
+			case 'app_bottom':
+			}
+		};
+
+		Ads.NABBLE = function(location) {
+			switch(location) {
+			case 'blog_topic':
+			case 'first_classic_message':
+			case 'list_bottom':
+			case 'threaded_bottom':
+			case 'search_bottom':
+				Nabble.showAd( location, function() {
+					var imgName = '<%=BANNER_IMG%>';
+					var url = '<%=BANNER_URL%>';
+					_gaq.push(['nabble._trackEvent', 'banner', imgName]);
+					document.write('<a href="'+url+'" rel="nofollow" style="display:block !important;visibility:visible !important;height:auto !important; width: auto !important;position:static !important;top:auto !important;left:auto !important"><img src="http://static.nabble.com/images/'+imgName+'" style="border:1px solid #bbb;display:inline !important;visibility:visible !important;height:auto !important;width:auto !important"/></a>');
+				} );
+			}
+		};
+
+		Ads.AdSense = function(location) {
+			switch(location) {
+			case 'blog_topic':
+				Nabble.showAd( location, function() {
+					google_ad_slot = "8807592501";
+					google_ad_width = 728;
+					google_ad_height = 90;
+					Nabble._adsense();
+				} );
+				break;
+			case 'first_classic_message':
+				Nabble.showAd( location, function() {
+					var fn = function() {
+						google_ad_slot = "8703015991";
+						google_ad_width = 336;
+						google_ad_height = 280;
+						Nabble._adsense();
+					};
+					document.writeln('<div style="width:336px; float:left; clear:none; display:block !important;">');
+					Nabble.writeFn(fn);
+					document.writeln('</div>');
+					document.writeln('<div style="width:336px; float:left; clear:none; display:block !important;">');
+					Nabble.writeFn(fn);
+					document.writeln('</div>');
+				}, "width:675px !important;" );
+				break;
+			case 'last_classic_message':
+				Nabble.showAd( location, function() {
+					google_ad_slot = "8323336297";
+					google_ad_width = 728;
+					google_ad_height = 90;
+					Nabble._adsense();
+				} );
+				break;
+			case 'list_bottom':
+				Nabble.showAd( location, function() {
+					google_ad_slot = "6508328921";
+					google_ad_width = 728;
+					google_ad_height = 90;
+					Nabble._adsense();
+				} );
+				break;
+			case 'threaded_bottom':
+				Nabble.showAd( location, function() {
+					google_ad_slot = "8434893994";
+					google_ad_width = 728;
+					google_ad_height = 90;
+					Nabble._adsense();
+				} );
+				break;
+			case 'search_top':
+				Nabble.AdsenseSearchAds();
+				break;
+			case 'search_bottom':
+				Nabble.showAd( location, function() {
+					google_ad_slot = "0099567777";
+					google_ad_width = 728;
+					google_ad_height = 90;
+					google_ad_type = "image";
+					Nabble._adsense();
+				} );
+				break;
+			case 'app_bottom':
+				Nabble.showAd( location, function() {
+					var fn = function() {
+						google_ad_slot = "1057993213";
+						google_ad_width = 728;
+						google_ad_height = 90;
+						Nabble._adsense();
+					};
+					Nabble.writeFn(fn);
+				} );
+				break;
+			}
+		};
+
+		Ads.SAFE = function(location) {
+			Ads.AdSense(location);
+		};
+
+		Ads.ST = function(location) {
+			if (Nabble.currentCredits > 0 || Nabble.isPaid)
+				return;
+			window.has_st = true;
+		};
+
+		Ads.BAD = Ads.ST;
+		Ads.UNSAFE = Ads.ST;
+
+		Nabble.showAd = function(location,fn,style) {
+			var customStyle = Ads.styles[location].style + (style == null?'':style);
+			document.writeln('<div class="ad '+Ads.styles[location].cls+'" style="display:block !important;visibility:visible !important;height:auto !important; width: auto !important;position:static !important;top:auto !important;left:auto !important;'+customStyle+'">');
+			fn();
+			if (!Nabble.creditsForUsersOnly || Nabble.userId) {
+				document.writeln('<div style="padding-top:.3em;font-size:90%;clear:both"><a href="'+Nabble.removeAdsPath+'">'+Ads.text.removeAds+'</a></div>');
+				document.writeln('</div>');
+			}
+		};
+
+		Nabble.AdsenseSearchAds = function() {
+			if (nabble_search_query && nabble_search_query != '') {
+				(function(G,o,O,g,L,e){G[g]=G[g]||function(){(G[g]['q']=G[g]['q']||[]).push(
+				arguments)},G[g]['t']=1*new Date;L=o.createElement(O),e=o.getElementsByTagName(
+				O)[0];L.async=1;L.src='//www.google.com/adsense/search/async-ads.js';
+				e.parentNode.insertBefore(L,e)})(window,document,'script','_googCsa');
+
+				document.writeln('<div id="ad1" style="margin:0 0 1em -.3em"></div>');
+				var pageOptions = {
+					'pubId': 'pub-6703598369329977',
+					'query': nabble_search_query,
+					'hl': 'en'
+				};
+				var ad = Nabble._ad();
+				var bg = '#'+Nabble._bgcolor(ad);
+				var tc = '#'+Nabble._color('div.search-results-header','808080');
+				var lc = '#'+Nabble._linkColor(ad);
+				var adblock1 = {
+					'container': 'ad1',
+					'width': '100%',
+					'fontFamily': 'verdana',
+					'fontSizeTitle': 15,
+					'fontSizeDescription': 13,
+					'fontSizeDomainLink': 13,
+					'colorTitleLink': lc,
+					'colorText': tc,
+					'colorDomainLink': tc,
+					'colorBackground': bg,
+					'colorAdBorder': bg,
+					'colorBorder': bg
+				};
+				_googCsa('ads', pageOptions, adblock1);
+			}
+		};
+
+		Nabble.writeFn = function(fn) {
+			Nabble._fn = fn;
+			document.writeln('<script type="text/javascript">Nabble._fn()</script>');
+		};
+
+
+		Nabble._adwords = function() {
+			google_conversion_id = 975361083;
+			google_conversion_language = "en";
+			google_conversion_format = "3";
+			google_conversion_color = "ffffff";
+			google_conversion_label = "sfP9CM34nwgQu6iL0QM";
+			google_conversion_value = 10;
+			google_remarketing_only = false;
+			document.write('<script type="text/javascript" src="//www.googleadservices.com/pagead/conversion.js"></script>');
+		};
+
+		Nabble._adsense = function() {
+			var ad = Nabble._ad();
+			if( google_ad_width==728 && Nabble._narrowUnit(ad,728) ) {
+				google_ad_slot = "1143776366";
+				google_ad_width = 468;
+				google_ad_height = 60;
+			}
+			var bg = Nabble._bgcolor(ad);
+			google_color_border = bg;
+			google_color_bg = bg;
+			google_color_text = "808080";
+			google_color_url = "808080";
+			google_color_link = Nabble._linkColor(ad);
+			google_ad_client = "ca-pub-6703598369329977";
+			document.write('<script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>');
+			var __ad = 'div'+ad.getAttribute('id').substring(2);
+			$(ad).parent().addClass(__ad);
+ 			document.write('<style type="text/css">');
+ 			document.write('.nabble .' + __ad + ' ins { display:inline-table !important; visibility: visible !important; height:' + google_ad_height + 'px !important; width: ' + google_ad_width + 'px !important; position:static !important;top:auto !important;left:auto !important; }');
+ 			document.write('.nabble .' + __ad + ' ins ins iframe { display:block !important; visibility: visible !important; height:' + google_ad_height + 'px !important; width: ' + google_ad_width + 'px !important; position:static !important;top:auto !important;left:auto !important; }');
+ 			document.write('</style>');
+		};
+
+		Nabble._ad = function() {
+			var ad = "ad" + Math.round(Math.random() * 999999);
+			document.writeln('<a id="'+ad+'" href="#" style="display:none">.</a>');
+			return Nabble.get(ad);
+		};
+
+		Nabble._bgcolor = function(ad) {
+			var $elem = $(ad);
+			var $parent = $elem;
+			var bg;
+			while ($parent.size() > 0) {
+				try {
+					bg = $parent.css('background-color');
+				} catch(err) {
+					$elem.addClass('shaded-bg-color');
+					bg = $elem.css('background-color');
+					break;
+				}
+				if (bg != 'transparent' && bg.indexOf('rgba') != 0)
+					break;
+				$parent = $parent.parent();
+			}
+			if (bg.charAt(0) == '#')
+				return bg.substring(1);
+			else if (bg.indexOf('rgb') == 0) {
+				bg = bg.substring(4, bg.length-1);
+				var rgb = bg.split(',');
+				return Nabble._hexa(rgb[0]) + Nabble._hexa(rgb[1]) + Nabble._hexa(rgb[2]);
+			}
+			return bg;
+		};
+
+		Nabble._linkColor = function(ad) {
+			var c = $(ad).css('color');
+			if (c.charAt(0) == '#')
+				return c.substring(1);
+			else if (c.indexOf('rgb') == 0) {
+				c = c.substring(4, c.length-1);
+				var rgb = c.split(',');
+				return Nabble._hexa(rgb[0]) + Nabble._hexa(rgb[1]) + Nabble._hexa(rgb[2]);
+			}
+			return 'EEEEEE';
+		};
+
+		Nabble._color = function(sel,def) {
+			var c = $(sel).css('color');
+			if (c.charAt(0) == '#')
+				return c.substring(1);
+			else if (c.indexOf('rgb') == 0) {
+				c = c.substring(4, c.length-1);
+				var rgb = c.split(',');
+				return Nabble._hexa(rgb[0]) + Nabble._hexa(rgb[1]) + Nabble._hexa(rgb[2]);
+			}
+			return def;
+		};
+
+		Nabble._hexa = function(c) {
+			var n = parseInt(c).toString(16);
+			return n.length == 1? '0' + n : n;
+		};
+
+		Nabble._narrowUnit = function(ad,limit) {
+			return $(ad).parent().width() < limit;
+		};
+
+
+		var b = function() {};
+
+		<%=hackAds%>
+
+		if(isB) b();
+		<%;
+	}
+
+	private Javascript() {}  // never
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/ad/SetAdType.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,61 @@
+
+package nabble.modules.ad;
+
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class SetAdType extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String siteStr = request.getParameter("site");
+		if( siteStr == null ) {
+			
+		out.print( "\r\n<html>\r\n	<head>\r\n		<title>Set Ad Type</title>\r\n	</head>\r\n	<body>\r\n		<h1>Set Ad Type</h1>\r\n		<form action=\"SetAdType.jtp\">\r\n			<p>Site ID: <input name=\"site\" /></p>\r\n		</form>\r\n	</body>\r\n</html>\r\n" );
+
+			return;
+		}
+		Site site = ModelHome.getSite(Long.parseLong(siteStr));
+		Ad ad = Ad.of(site);
+		String typeStr = request.getParameter("type");
+		if( typeStr != null ) {
+			ad.setSafe(typeStr.equals("SAFE"));
+			response.sendRedirect("SetAdType.jtp?site="+siteStr);
+			return;
+		}
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<title>Set Ad Type</title>\r\n	</head>\r\n	<body>\r\n		<h1>Set Ad Type</h1>\r\n		<p>" );
+		out.print( ( Jtp.link(site.getRootNode()) ) );
+		out.print( "</p>\r\n		<p>\r\n			safe = " );
+		out.print( (ad.isSafe()) );
+		out.print( " <br/>\r\n		</p>\r\n		<p>\r\n			<form>\r\n				<input type=\"hidden\" name=\"site\" value=\"" );
+		out.print( (site.getId()) );
+		out.print( "\" />\r\n				" );
+
+						for( String type : new String[]{"SAFE","BAD"} ) {
+							
+		out.print( "\r\n<input type=\"radio\" name=\"type\" value=\"" );
+		out.print( (type) );
+		out.print( "\" /> " );
+		out.print( (type) );
+		out.print( "<br/>\r\n" );
+
+						}
+						
+		out.print( "\r\n<input type=\"submit\" value=\"Set Type\" />\r\n</form>\r\n</p>\r\n</body>\r\n</html>\r\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/ad/SetAdType.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,77 @@
+<%
+package nabble.modules.ad;
+
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class SetAdType extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String siteStr = request.getParameter("site");
+		if( siteStr == null ) {
+			%>
+			<html>
+				<head>
+					<title>Set Ad Type</title>
+				</head>
+				<body>
+					<h1>Set Ad Type</h1>
+					<form action="SetAdType.jtp">
+						<p>Site ID: <input name="site" /></p>
+					</form>
+				</body>
+			</html>
+			<%
+			return;
+		}
+		Site site = ModelHome.getSite(Long.parseLong(siteStr));
+		Ad ad = Ad.of(site);
+		String typeStr = request.getParameter("type");
+		if( typeStr != null ) {
+			ad.setSafe(typeStr.equals("SAFE"));
+			response.sendRedirect("SetAdType.jtp?site="+siteStr);
+			return;
+		}
+		%>
+		<html>
+			<head>
+				<title>Set Ad Type</title>
+			</head>
+			<body>
+				<h1>Set Ad Type</h1>
+				<p><%= Jtp.link(site.getRootNode()) %></p>
+				<p>
+					safe = <%=ad.isSafe()%> <br/>
+				</p>
+				<p>
+					<form>
+						<input type="hidden" name="site" value="<%=site.getId()%>" />
+						<%
+						for( String type : new String[]{"SAFE","BAD"} ) {
+							%>
+							<input type="radio" name="type" value="<%=type%>" /> <%=type%><br/>
+							<%
+						}
+						%>
+						<input type="submit" value="Set Type" />
+					</form>
+				</p>
+			</body>
+		</html>
+		<%
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/ad/ad.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,297 @@
+
+<override_macro name="nabble_shared_scripts">
+	<n.overridden/>
+	<n.array_of_user_ids var_name="site_admins" group="[n.administrators_group/]"/>
+	<script type="text/javascript">
+		var isSafe = <n.is_ad_safe/>;
+		var Ads = Ads || {};
+		Ads.text = {
+			daysLeft: '<t>Trial period ends in <t.number>$1</t.number> day(s)</t>',
+			removeAds: '<t>Remove Ads</t>',
+			buyCredits: '<t>Upgrade</t>'
+		};
+		$(document).ready(function() {
+			if (Nabble.wasCreatedRecently && Nabble.currentCredits == 0 && isSafe) {
+				var s = '<div class="weak-color" style="text-align:center;margin:1em;font-size:80%">';
+				s += Ads.text.daysLeft.replace(/\$1/,daysLeft)+' &ndash; ';
+				s += '<a href="[n.site_payment_path/]">'+(Nabble.isSafe?Ads.text.removeAds:Ads.text.buyCredits)+'</a>';
+				s += '</div>';
+				var isAdmin = Nabble.userId && site_admins.indexOf(Number(Nabble.userId)) >= 0;
+				isAdmin && $('div.nabble').append(s);
+			}
+		});
+	</script>
+	<n.visible_for_admins selector="div.no-ads-message"/>
+</override_macro>
+
+<macro name="show_ad" parameters="location" requires="servlet">
+	<n.comment.>
+		WARNING: users are NOT allowed to change this macro in order to remove ads from their apps.
+		If you want to remove ads, please buy the premium subscription. Also, if you want to place your
+		own ads code, please put them in the 'show_custom_ads' macro (your ads will be displayed while
+		your premium subscription is active). Please DO NOT try dirty tricks to hide the ads. We may delete
+		your site at any time if you do that.
+	</n.comment.>
+	<n.if.has_ads>
+		<then>
+			<n.put_in_head.adbayes_script params="[n.adbayes_params_for.location/]"/>
+			<script type="text/javascript">
+				Nabble.ads('<n.location/>');
+			</script>
+		</then>
+		<else>
+			<script type="text/javascript">
+				Nabble.noAds('<n.location/>');
+			</script>
+			<n.show_custom_ads location="[n.location/]" />
+		</else>
+	</n.if.has_ads>
+</macro>
+
+<macro name="show_custom_ads" parameters="location" requires="servlet" >
+	<n.comment.>
+		Here you can place your own ads and make money with your Nabble application.
+		But please remember that you must pay for ad-free credits if you want to remove Nabble's ads.
+	</n.comment.>
+</macro>
+
+<macro name="menu_premium_upgrade" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('premiumUpgrade', '<n.javascript_string_encode.site_payment_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if>
+				<condition>
+					<n.both>
+						<condition1.local_node.is_root/>
+						<condition2.both condition1="[n.visitor.can_edit.local_node/]" condition2="[n.not.is_paid_site/]"/>
+					</n.both>
+				</condition>
+				<then>
+					NabbleDropdown.show('premiumUpgrade');
+				</then>
+			</n.if>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="ads_widget">
+	<n.widget.>
+		<n.show_ad location="widget" />
+	</n.widget.>
+</macro>
+
+<override_macro name="sidebar_widget" requires="node_page,servlet">
+	<n.overridden />
+	<n.ads_widget />
+</override_macro>
+
+<override_macro name="topic_controls" requires="blog_topic_namespace">
+	<n.overridden />
+	<n.show_ad location="blog_topic"/>
+</override_macro>
+
+<override_macro name="classic_message_cell" requires="node">
+	<n.overridden />
+	<n.if.not.is_blog_topic>
+		<then>
+			<n.if.is_first_element>
+				<then>
+					<n.show_ad location="first_classic_message"/>
+				</then>
+			</n.if.is_first_element>
+			<n.if.is_last_element>
+			 <then>
+				 <n.if.not.regex_matches pattern='1' text="[n.page_node.descendant_count/]">
+					<then.show_ad location="last_classic_message"/>
+				 </n.if.not.regex_matches>
+			 </then>
+		 </n.if.is_last_element>
+		</then>
+	</n.if.not.is_blog_topic>
+</override_macro>
+
+<override_macro name="topic_rows" requires="node,forum_topic_namespace,list_view_namespace">
+	<n.overridden />
+	<n.show_ad location="list_bottom"/>
+</override_macro>
+
+<override_macro name="topic_rows" requires="node,forum_topic_namespace,threaded_view_namespace">
+	<n.overridden />
+	<n.show_ad location="threaded_bottom"/>
+</override_macro>
+
+<override_macro name="search_pagination" requires="search">
+	<n.overridden />
+	<n.show_ad location="search_bottom"/>
+</override_macro>
+
+<override_macro name="search_results_header" requires="search">
+	<n.overridden />
+	<script type="text/javascript">
+		var nabble_search_query = '<n.hide_null.get_parameter name="query"/>';
+	</script>
+	<n.show_ad location="search_top"/>
+</override_macro>
+
+<override_macro name="app_body_footer">
+	<n.overridden/>
+	<n.show_ad location="app_bottom"/>
+</override_macro>
+
+<override_macro name="topic_bottom">
+	<n.overridden/>
+	<n.show_ad location="topic_bottom"/>
+</override_macro>
+
+<macro name="ad_notice">
+	<n.new_email.>
+		<n.send>
+			<to><n.root_node.owner.user_email/></to>
+			<subject><t><t.location.root_node.subject/> - Your credits are running out</t></subject>
+			<text_part>
+				<t>Dear user,</t>
+
+				<t><t.location.root_node.subject/> is running out of credits.</t>
+				<t>Current Credits</t>: <t><t.number.current_credits/> Credits</t>
+
+				<t>For a premium upgrade, please visit:</t>
+				<n.base_url/><n.site_payment_path/>
+
+				<t>Regards,</t>
+				<t>The Nabble team</t>
+			</text_part>
+			<html_part>
+				<t>Dear user,</t><br/>
+				<br/>
+				<t><t.location.root_node.subject/> is running out of credits.</t><br/>
+				<t>Current Credits</t>: <t><t.number.current_credits/> Credits</t><br/>
+				<br/>
+				<t>For a premium upgrade, please visit:</t><br/>
+				<a href="[n.base_url/][n.site_payment_path/]"><n.base_url/><n.site_payment_path/></a><br/>
+				<br/>
+				<t>Regards,</t><br/>
+				<t>Nabble Team</t>
+			</html_part>
+			</n.send>
+	</n.new_email.>
+</macro>
+
+<override_macro name="bottom_scripts">
+	<n.as_html_comments.>Ad.<n.is_ad_safe/> | <n.current_credits/> credits</n.as_html_comments.>
+	<script type="text/javascript">
+		Nabble.trk = Nabble.trk || {};
+		Nabble.trk.safe = <n.is_ad_safe/>;
+	</script>
+	<n.overridden/>
+</override_macro>
+
+<macro name="adbayes_script" parameters="params" requires="ad, servlet">
+	<n.if.not.is_null.adbayes_link>
+		<then>
+			<script type="text/javascript" src="[n.adbayes_link/][n.hide_null.params/]"></script>
+			<script type="text/javascript">
+				Nabble.adbayes();
+			</script>
+		</then>
+	</n.if.not.is_null.adbayes_link>
+</macro>
+
+<macro name="adbayes_params_for" dot_parameter="location" requires="ad, servlet">
+	<n.switch. value="[n.location/]">
+		<n.case. value="list_bottom">
+			&canonical=<n.encode.use_url_encoder.regex_replace_all text="[n.current_url/]" pattern="-tc(\d+)" replacement="-td$1"/>
+		</n.case.>
+		<n.case. value="threaded_bottom">
+			&canonical=<n.encode.use_url_encoder.regex_replace_all text="[n.current_url/]" pattern="-tt(\d+)" replacement="-td$1"/>
+		</n.case.>
+		<n.case. value="search_bottom">
+			&text=<n.encode.use_url_encoder.get_parameter name="query"/>
+		</n.case.>
+		<n.case. value="search_top">
+			&text=<n.encode.use_url_encoder.get_parameter name="query"/>
+		</n.case.>
+	</n.switch.>
+</macro>
+
+<override_macro name="javascript_library" requires="servlet">
+	<n.overridden/>
+	<n.compress.ad_javascript/>
+</override_macro>
+
+<override_macro name="report_inappropriate_content_submit" requires="servlet">
+	<n.if.is_ad_safe>
+		<then.overridden/>
+	</n.if.is_ad_safe>
+</override_macro>
+
+<macro name="site_payment_link">
+	<a href="[n.site_payment_path/]"><t>Premium upgrade</t></a>
+</macro>
+
+<macro name="site_payment_path">
+	/template/NamlServlet.jtp?macro=site_payment
+</macro>
+
+<macro name="site_payment">
+	<n.if.visitor.is_authenticated>
+		<then>
+			<n.redirect_to.site_payment_url/>
+		</then>
+		<else>
+			<n.login.>You must login to upgrade this site.</n.login.>
+		</else>
+	</n.if.visitor.is_authenticated>
+</macro>
+
+<macro name="payment_confirmed">
+	<n.html>
+		<head>
+			<meta name="robots" content="noindex,nofollow"/>
+			<n.title.><t>Payment Confirmation</t></n.title.>
+		</head>
+		<body>
+			<n.if.confirm_payment customer_id="[n.get_parameter name='c'/]" user="[n.get_parameter name='user'/]" key1="[n.get_parameter name='key1'/]" key2="[n.get_parameter name='key2'/]">
+				<then>
+					<h1><t>Payment Confirmed</t></h1>
+					<p>Your payment has been confirmed. You can find payment options on your account settings page.</p>
+				</then>
+				<else>
+					<h1><t>Payment Failed</t></h1>
+					<p>
+						Sorry, but something went wrong with your payment.
+						You may send an email to <a href="mailto:payments@nabble.com">payments@nabble.com</a> if you need help.
+					</p>
+				</else>
+			</n.if.confirm_payment>
+		</body>
+	</n.html>
+</macro>
+
+<override_macro name="authenticated_self_profile_header" requires="user">
+	<n.overridden/>
+	<n.if.both condition1="[n.not.is_null.site_payment_cancellation_url/]" condition2="[n.is_paid_site/]">
+		<then>
+			<div style="margin-top:.5em">
+				<img src="/images/icon_info.png" class="image16" style="margin:0 1px"/>
+				<a href="[n.site_payment_cancellation_url/]" target="_top"><t>Cancel Payment Subscription</t></a>
+			</div>
+		</then>
+	</n.if.both>
+</override_macro>
+
+<macro name="cancelled">
+	<n.html>
+		<head>
+			<meta name="robots" content="noindex,nofollow"/>
+			<n.title.><t>Payment Subscription Cancelled</t></n.title.>
+		</head>
+		<body>
+			<n.update_status customer_id="[n.get_parameter name='c'/]"/>
+			<h1><t>Payment Subscription Cancelled</t></h1>
+			<p>Your payment subscription has been cancelled and your card has been removed from the billing cycle.</p>
+		</body>
+	</n.html>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/hacks/HacksModule.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,34 @@
+package nabble.modules.hacks;
+
+import nabble.naml.compiler.Module;
+import nabble.naml.compiler.Source;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+
+public enum HacksModule implements Module {
+	INSTANCE;
+
+	private static final Iterable<Class> extensions = Arrays.<Class>asList(
+		UserNamespaceExt.class
+	);
+
+	public String getName() {
+		return "hacks";
+	}
+
+	public Iterable<Class> getExtensions() {
+		return extensions;
+	}
+
+	public Collection<Source> getSources() {
+		return Collections.emptyList();
+	}
+
+	public Set<String> getDependencies() {
+		return Collections.emptySet();
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/hacks/UserHack.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,48 @@
+package nabble.modules.hacks;
+
+import nabble.model.Db;
+import nabble.model.ExtensionFactory;
+import nabble.model.ModelHome;
+import nabble.model.User;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class UserHack {
+
+	private static final ExtensionFactory<User,UserHack> FACTORY = ModelHome.standardExtensionFactory(HacksModule.INSTANCE.getName(),User.class,UserHack.class);
+
+	public static UserHack of(User user) {
+		return user.getExtension(FACTORY);
+	}
+
+	private final User user;
+
+	public UserHack(User user) {
+		this.user = user;
+	}
+
+	private int registrationSequence = -1;
+
+	public int getRegistrationSequence() {
+		if (user.isRegistered() && registrationSequence == -1) {
+			try {
+				Connection con = user.getSite().getDb().getConnection();
+				PreparedStatement stmt = con.prepareStatement(
+					"select count(1) as n from user_ where user_id <= ?"
+				);
+				stmt.setLong(1,user.getId());
+				ResultSet rs = stmt.executeQuery();
+				rs.next();
+				registrationSequence = rs.getInt("n");
+				stmt.close();
+				con.close();
+			} catch(SQLException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		return registrationSequence;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/hacks/UserNamespaceExt.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,26 @@
+package nabble.modules.hacks;
+
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.NamespaceExtension;
+import nabble.view.web.template.UserNamespace;
+
+
+@NamespaceExtension(
+	name = "user_hacks",
+	target = UserNamespace.class
+)
+public final class UserNamespaceExt {
+	private final User user;
+
+	public UserNamespaceExt(UserNamespace ns) {
+		user = ns.user();
+	}
+
+	@Command
+	public void registration_sequence(IPrintWriter out, Interpreter interp) {
+		out.print(user == null? -1 : UserHack.of(user).getRegistrationSequence());
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/ads.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,4 @@
+<override_macro name="nabble_footer_left">
+	<n.overridden/>
+	<n.link_test/>
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/ads_auto.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,87 @@
+<macro name="auto_ads">
+<script async="true" src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
+<script>
+	var nbl_showAds = !(window.nbl_canHideAds && window.localStorage && localStorage.nbl_noAds);
+	if(nbl_showAds) {
+		(adsbygoogle = window.adsbygoogle || []).push({
+			google_ad_client: "ca-pub-1691702211255830",
+			enable_page_level_ads: true
+		});
+	}
+</script>
+</macro>
+
+<override_macro name="topic_html" requires="servlet">
+    <n.topic_min_html>
+        <head>
+            <n.topic_common_head/>
+            <n.topic_head/>
+            <n.topic_meta/>
+            <n.increment_view_count/>
+						<n.auto_ads/>
+        </head>
+        <body>
+            <table width="100%" cellpadding="0" style="border-collapse:collapse">
+                <tr>
+                    <td></td>
+                    <td>
+                        <n.page_node.topic_hardcoded_notices/>
+                        <n.newsflash/>
+                        <n.topic_header/>
+                        <n.topic_controls/>
+                    </td>
+                </tr>
+                <tr valign="top">
+                    <td>
+                    </td>
+                    <td>
+                        <n.topic_contents.view_contents.page_node.topic_rows/>
+                        <n.topic_footer/>
+                    </td>
+                </tr>
+            </table>
+       </body>
+    </n.topic_min_html>
+</override_macro>
+
+<override_macro name="view_standard_page">
+    <n.set_app_rows_per_page rows_per_page="[n.forum_topics_per_page/]"/>
+    <n.app_html>
+        <head>
+            <n.app_title />
+            <n.standard_table_stylesheet/>
+						<n.auto_ads/>
+        </head>
+        <body>
+            <n.new_topic_action_link/>
+            <n.topics_action_link only_if_has_subapps="true"/>
+            <n.people_action_link/>
+            <n.options_action_menu/>
+            <n.app_child_pagination margin=".8em .3em .5em 0"/>
+            <n.standard_table.standard_table_columns />
+            <n.app_child_pagination margin=".8em .3em .5em 0"/>
+            <n.forum_footer/>
+        </body>
+    </n.app_html>
+</override_macro>
+
+<override_macro name="view_topics_page">
+    <n.set_app_rows_per_page rows_per_page="[n.forum_topics_per_page/]"/>
+    <n.app_html>
+        <head>
+            <n.app_title/>
+            <n.topics_table_stylesheet/>
+						<n.auto_ads/>
+        </head>
+        <body>
+            <n.new_topic_action_link/>
+            <n.subapps_action_link/>
+            <n.people_action_link/>
+            <n.options_action_menu/>
+            <n.app_topic_pagination margin=".8em .3em .5em 0"/>
+            <n.topics_table.topics_table_columns />
+            <n.app_topic_pagination margin=".8em .3em .5em 0"/>
+            <n.forum_footer/>
+        </body>
+    </n.app_html>
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/ads_manual.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,150 @@
+<static>
+    ins
+</static>
+
+<macro name="auto_ads">
+<script async="true" src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
+<script>
+	var nbl_showAds = !(window.nbl_canHideAds && window.localStorage && localStorage.nbl_noAds);
+	if(nbl_showAds) {
+		(adsbygoogle = window.adsbygoogle || []).push({
+			google_ad_client: "ca-pub-1691702211255830",
+			enable_page_level_ads: true
+		});
+	}
+</script>
+</macro>
+
+<macro name="vertical_ad">
+<script async="true" src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
+<!-- wide skyscraper -->
+<ins class="adsbygoogle"
+     style="display:block;width:160px;height:600px"
+     data-ad-client="ca-pub-1691702211255830"
+     data-ad-slot="4171118808"></ins>
+</macro>
+
+<macro name="ad_responsive_top">
+    <script async="true" src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
+    <!-- responsive-top -->
+    <ins class="adsbygoogle"
+    style="display:block"
+    data-ad-client="ca-pub-1691702211255830"
+    data-ad-slot="8608207209"
+    data-ad-format="horizontal"
+    data-full-width-responsive="true"></ins>
+</macro>
+
+<override_macro name="topic_html" requires="servlet">
+    <n.topic_min_html>
+        <head>
+            <n.topic_common_head/>
+            <n.topic_head/>
+            <n.topic_meta/>
+            <n.increment_view_count/>
+						<n.auto_ads/>
+        </head>
+        <body>
+					<div class="ad-responsive-top" style="margin-top:10px;margin-bottom:10px;text-align:center">
+						<n.ad_responsive_top/>
+						<script>
+							var nbl_showAds = !(window.nbl_canHideAds && window.localStorage && localStorage.nbl_noAds);
+							if(nbl_showAds) {
+								(adsbygoogle = window.adsbygoogle || []).push({});
+							}
+						</script>
+					</div>
+            <table width="100%" cellpadding="0" style="border-collapse:collapse">
+                <tr>
+                    <td></td>
+                    <td>
+                        <n.page_node.topic_hardcoded_notices/>
+                        <n.newsflash/>
+                        <n.topic_header/>
+                        <n.topic_controls/>
+                    </td>
+                </tr>
+                <tr valign="top">
+                    <td class="vertical-ad" width="170px" style="padding-top:1em">
+                        <n.vertical_ad/>
+                    </td>
+                    <td>
+                        <n.topic_contents.view_contents.page_node.topic_rows/>
+                        <n.topic_footer/>
+                    </td>
+                </tr>
+            </table>
+             <script>
+                var nbl_showAds = !(window.nbl_canHideAds && window.localStorage && localStorage.nbl_noAds);
+                if( window.innerWidth >= 800 && nbl_showAds) {
+                    (adsbygoogle = window.adsbygoogle || []).push({});
+                } else {
+                    document.querySelector('.vertical-ad ins.adsbygoogle').style.width = 0;
+                    document.querySelector('td.vertical-ad').style.width = 0;
+                }
+            </script>
+       </body>
+    </n.topic_min_html>
+</override_macro>
+
+<override_macro name="view_standard_page">
+    <n.set_app_rows_per_page rows_per_page="[n.forum_topics_per_page/]"/>
+    <n.app_html>
+        <head>
+            <n.app_title />
+            <n.standard_table_stylesheet/>
+						<n.auto_ads/>
+        </head>
+        <body>
+            <div class="ad-responsive-top" style="margin-top:10px;margin-bottom:10px;text-align:center">
+                <n.ad_responsive_top/>
+            </div>
+            <n.new_topic_action_link/>
+            <n.topics_action_link only_if_has_subapps="true"/>
+            <n.people_action_link/>
+            <n.options_action_menu/>
+
+            <n.app_child_pagination margin=".8em .3em .5em 0"/>
+            <n.standard_table.standard_table_columns />
+            <n.app_child_pagination margin=".8em .3em .5em 0"/>
+            <n.forum_footer/>
+						<script>
+							var nbl_showAds = !(window.nbl_canHideAds && window.localStorage && localStorage.nbl_noAds);
+							if(nbl_showAds) {
+								(adsbygoogle = window.adsbygoogle || []).push({});
+							}
+						</script>
+        </body>
+    </n.app_html>
+</override_macro>
+
+<override_macro name="view_topics_page">
+    <n.set_app_rows_per_page rows_per_page="[n.forum_topics_per_page/]"/>
+    <n.app_html>
+        <head>
+            <n.app_title/>
+            <n.topics_table_stylesheet/>
+						<n.auto_ads/>
+        </head>
+        <body>
+						<div class="ad-responsive-top" style="margin-top:10px;margin-bottom:10px;text-align:center">
+							<n.ad_responsive_top/>
+						</div>
+            <n.new_topic_action_link/>
+            <n.subapps_action_link/>
+            <n.people_action_link/>
+            <n.options_action_menu/>
+
+            <n.app_topic_pagination margin=".8em .3em .5em 0"/>
+            <n.topics_table.topics_table_columns />
+            <n.app_topic_pagination margin=".8em .3em .5em 0"/>
+            <n.forum_footer/>
+						<script>
+							var nbl_showAds = !(window.nbl_canHideAds && window.localStorage && localStorage.nbl_noAds);
+							if(nbl_showAds) {
+								(adsbygoogle = window.adsbygoogle || []).push({});
+							}
+						</script>
+        </body>
+    </n.app_html>
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/animeron.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,72 @@
+<!--
+http://forums.animeron.info/
+Disabled "Remove my account" option
+-->
+
+<override_macro name="message_as_html" requires="message">
+	<n.if.is_deleted>
+		<then>
+			<b><t>CONTENTS DELETED</t></b>
+			<div class="weak-color"><t>The author has deleted this message.</t></div>
+		</then>
+		<else>
+			<n.set_var. name="output">
+				<n.as_html_list.process_message_html />
+			</n.set_var.>
+			<n.if.is_imported_mail>
+				<then.set_var. name="output">
+					<n.remove_unsubscription_link.var name="output" />
+				</then.set_var.>
+			</n.if.is_imported_mail>
+			<n.var name="output" />
+		</else>
+	</n.if.is_deleted>
+</override_macro>
+
+<!-- Remove option to deactivate account -->
+<override_macro name="profile_options">
+	<n.put_in_head.>
+		<style type="text/css">
+			table.profile {
+				border-collapse:collapse;
+				width:100%;
+				margin-top:.5em;
+			}
+			table.profile td {
+				padding:.7em .3em;
+			}
+			table.profile td.title-row {
+				background: transparent;
+				padding:.2em .4em;
+				border-bottom-width:2px;
+				border-bottom-style:solid;
+				font-weight:bold;
+			}
+		</style>
+	</n.put_in_head.>
+	<table class="profile">
+		<n.profile_table_header.>
+			My Settings
+		</n.profile_table_header.>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.edit_profile_path/]">Edit Personal Information</a></link>
+			<hint>Change your registered email address, password, and your user name.</hint>
+		</n.profile_option>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.edit_signature_path/]">Edit Your Signature</a></link>
+			<hint>Change the signature text that is displayed at the bottom of your posts.</hint>
+		</n.profile_option>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.change_avatar_path/]">Change Your Picture</a></link>
+			<hint>Change or remove your avatar image.</hint>
+		</n.profile_option>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.advanced_settings_path/]">Advanced Settings</a></link>
+			<hint>Change viewing preferences and your alert settings (when posting a message).</hint>
+		</n.profile_option>
+	</table>
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/banhax.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,131 @@
+<override_macro name="gallery_cell">
+	<n.cell_thumbnail/>
+	<n.cell_link_and_star/>
+</override_macro>
+
+<override_macro name="subapp_section_extra_row" parameters="has_more"></override_macro>
+
+<override_macro name="news_page_layout" requires="app_namespace">
+	<n.column_layout.>
+		<n.column. width="75%">
+			<n.news_sidebar_widget/>
+		</n.column.>
+		<n.column. width="25%">
+			<n.sidebar_widget/>
+	   </n.column.>
+	</n.column_layout.>
+</override_macro>
+
+<!--
+http://forums.animeron.info/
+Disabled "Remove my account" option
+-->
+
+<override_macro name="message_as_html" requires="message">
+	<n.if.is_deleted>
+		<then>
+			<b><t>CONTENTS DELETED</t></b>
+			<div class="weak-color"><t>The author has deleted this message.</t></div>
+		</then>
+		<else>
+			<n.set_var. name="output">
+				<n.as_html_list.process_message_html />
+			</n.set_var.>
+			<n.if.is_imported_mail>
+				<then.set_var. name="output">
+					<n.remove_unsubscription_link.var name="output" />
+				</then.set_var.>
+			</n.if.is_imported_mail>
+			<n.var name="output" />
+		</else>
+	</n.if.is_deleted>
+</override_macro>
+
+<!-- Remove option to deactivate account -->
+<override_macro name="profile_options">
+	<n.put_in_head.>
+		<style type="text/css">
+			table.profile {
+				border-collapse:collapse;
+				width:100%;
+				margin-top:.5em;
+			}
+			table.profile td {
+				padding:.7em .3em;
+			}
+			table.profile td.title-row {
+				background: transparent;
+				padding:.2em .4em;
+				border-bottom-width:2px;
+				border-bottom-style:solid;
+				font-weight:bold;
+			}
+		</style>
+	</n.put_in_head.>
+	<table class="profile">
+		<n.profile_table_header.>
+			My Settings
+		</n.profile_table_header.>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.edit_profile_path/]">Edit Personal Information</a></link>
+			<hint>Change your registered email address, password, and your user name.</hint>
+		</n.profile_option>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.edit_signature_path/]">Edit Your Signature</a></link>
+			<hint>Change the signature text that is displayed at the bottom of your posts.</hint>
+		</n.profile_option>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.change_avatar_path/]">Change Your Picture</a></link>
+			<hint>Change or remove your avatar image.</hint>
+		</n.profile_option>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.advanced_settings_path/]">Advanced Settings</a></link>
+			<hint>Change viewing preferences and your alert settings (when posting a message).</hint>
+		</n.profile_option>
+	</table>
+</override_macro>
+
+
+<override_macro name="nabble_footer">
+	<style type="text/css">
+		table.footer-table td.footer-right img {
+			vertical-align:-45%;
+		}
+	</style>
+	<table class="footer-table shaded-bg-color">
+		<tr>
+			<td class="footer-left">
+				Powered by
+				<a href="[n.nabble_homepage/]" target="_top">Nabble</a>
+			</td>
+			<td class="footer-right">
+				<a href="http://www.e-kavkaz.com/" target="_blank">Кавказ</a> &copy; 2011
+				<!--Openstat-->
+					<span id="openstat2189752"></span>
+					<script type="text/javascript">
+						var openstat = { counter: 2189752, image: 5045, next: openstat, track_links: "ext" };
+						(function(d, t, p) {
+						var j = d.createElement(t); j.async = true; j.type = "text/javascript";
+						j.src = ("https:" == p ? "https:" : "http:") + "//openstat.net/cnt.js";
+						var s = d.getElementsByTagName(t)[0]; s.parentNode.insertBefore(j, s);
+						})(document, "script", document.location.protocol);
+					</script>
+				<!--/Openstat-->
+				<!-- begin of Top100 code -->
+					<script id="top100Counter" type="text/javascript" src="http://counter.rambler.ru/top100.jcn?2505501"></script>
+					<noscript>
+						<a href="http://top100.rambler.ru/navi/2505501/" target="_blank" rel="nofollow" link="external">
+							<img src="http://counter.rambler.ru/top100.cnt?2505501" alt="Rambler's Top100" border="0" />
+						</a>
+					</noscript>
+				<!-- end of Top100 code -->
+				&nbsp;
+				<n.macro_viewer_page_link/>
+			</td>
+		</tr>
+	</table>
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/blackcow.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,19 @@
+<!-- http://deftown.991905.n3.nabble.com/ -->
+
+<override_macro name="classic_big_avatar_cell" requires="node">
+	<div class="avatar-outer">
+		<div class="avatar-inner">
+			<n.owner.avatar size="big" border_class="medium-border-color"/>
+		</div>
+	</div>
+	<div style="font-size:90%">
+		<n.if.owner.is_authenticated>
+			<then>
+				<b>Joined</b> <n.owner.registration_date.date_only/><br/>
+				Member #<n.owner.registration_sequence/>
+			</then>
+			<else>Anonymous User</else>
+		</n.if.owner.is_authenticated>
+		<n.owner.post_count/>
+	</div>
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/content_news_summary.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,59 @@
+<!-- Regex to capture the summary and its tags-->
+<macro name="summary_regex">
+	\s*\{summary_start\}.*\{summary_end\}\s*
+</macro>
+
+<!-- Regex with tags to be removed from the summary -->
+<macro name="summary_cleanup_regex">
+	<![CDATA[
+	(\{summary_start\})|(\{summary_end\})|(<nabble_[^>]*>)
+	]]>
+</macro>
+
+<override_macro name="root_post_message">
+	<n.regex_replace_all. pattern="[n.summary_regex/]" replacement="">
+		<n.overridden/>
+	</n.regex_replace_all.>
+</override_macro>
+
+<override_macro name="search_result_message_fragment" requires="node,search">
+	<n.regex_replace_all. pattern="[n.summary_regex/]" replacement="">
+		<n.overridden/>
+	</n.regex_replace_all.>
+</override_macro>
+
+<override_macro name="news_snippet_row" requires="node">
+	<div class="node-snippet" style="padding:1em 0 .5em;clear:both">
+		<n.set_var. name='message_text'><n.remove_html_tags.message.as_text/></n.set_var.>
+		<n.regex text="[n.message.as_raw/]">
+			<pattern.summary_regex/>
+			<do>
+				<n.if.find>
+					<then>
+						<n.regex_replace_all. pattern="[n.summary_cleanup_regex/]" replacement="">
+							<n.found/>
+						</n.regex_replace_all.>
+					</then>
+					<else>
+						<n.truncate. size="300">
+							<n.var name='message_text'/>
+						</n.truncate.>
+					</else>
+				</n.if.find>
+			</do>
+		</n.regex>
+		<n.news_snippet_read_more_link/>
+	</div>
+</override_macro>
+
+<override_macro name="instant_text" requires="subscription,node_page" unindent="true">
+	<n.regex_replace_all. pattern="[n.summary_regex/]" replacement="">
+		<n.overridden/>
+	</n.regex_replace_all.>
+</override_macro>
+
+<override_macro name="instant_html" requires="subscription,node_page">
+	<n.regex_replace_all. pattern="[n.summary_regex/]" replacement="">
+		<n.overridden/>
+	</n.regex_replace_all.>
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/content_noindex.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,7 @@
+<override_macro name="app_meta" requires="node_page,servlet">
+	<META NAME="robots" CONTENT="noindex,follow"/>
+</override_macro>
+
+<override_macro name="topic_meta" requires="node_page,servlet">
+	<META NAME="robots" CONTENT="noindex,follow"/>
+</override_macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/content_open_links_in_new_window.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,17 @@
+<override_macro name="classic_js">
+	<n.overridden/>
+	<script type="text/javascript">
+		$(document).ready(function() {
+			$('a[link="external"]').each(function() {
+				$(this).attr('target','_blank');
+			});
+		});
+	</script>
+</override_macro>
+
+<override_macro name="ajax_post_custom_scripts">
+	<n.overridden/>
+	$('a[link="external"]', $full).each(function() {
+		$(this).attr('target','_blank');
+	});
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/content_smart_cache.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,7 @@
+Caches the app page only if it doesn't have private subapps.
+	
+<override_macro name="app_caching" requires="node_page">
+  <n.if.not.page_node.has_private_subapps>
+    <then.overridden/>
+  </n.if.not.page_node.has_private_subapps>
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/email_registration_notification.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,48 @@
+<override_macro name="after_registration" requires="user">
+	<n.overridden/>
+	<n.set_local_user.this_user/>
+
+	<n.users_in_group. group="[n.administrators_group/]">
+		<n.loop.>
+			<n.new_email.>
+				<n.send>
+					<to.current_user.user_email/>
+					<to_name.current_user.name/>
+					<from_name.root_node.subject/>
+					<subject><t>New user:</t> <n.local_user.name/></subject>
+					<text_part.local_user.new_user_email_text/>
+					<html_part.local_user.new_user_email_html/>
+				</n.send>
+			</n.new_email.>
+		</n.loop.>
+	</n.users_in_group.>
+</override_macro>
+
+
+<macro name="new_user_email_text" requires="user" unindent="true">
+	<t>New registered user in <t.location.root_node.subject/>!</t>
+
+	<t>Name</t> = <n.name/>
+	<t>Email</t> = <n.user_email/>
+
+	<t>User profile</t>
+	<n.url/>
+
+	_____________________________________
+	<n.root_node.subject/>
+	<n.base_url/>
+</macro>
+
+<macro name="new_user_email_html" requires="user" unindent="true">
+	<h2><t>New registered user in <t.location.root_node.subject/>!</t></h2>
+	<t>Name</t> = <n.name/><br/>
+	<t>Email</t> = <n.user_email/><br/>
+	<br/>
+	<b><t>User profile</t></b><br/>
+	<a href="[n.url/]"><n.url/></a><br/>
+	<br/>
+	<div style="color:#666; font: 11px tahoma,geneva,helvetica,arial,sans-serif;margin-top:.4em">
+		<n.root_node.subject/><br/>
+		<a href="[n.base_url/]"><n.base_url/></a>
+	</div>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/expire_old_threads.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,37 @@
+<override_macro name="reply" requires="servlet">
+	<n.node_page.>
+		<n.if.page_node.when_created.is_older_than days="[n.thread_expiration_days/]">
+			<then.redirect_to.expired_thread_page_path/>
+			<else.overridden/>
+		</n.if.page_node.when_created.is_older_than>
+	</n.node_page.>
+</override_macro>
+
+<macro name="thread_expiration_days">
+	365
+</macro>
+
+<macro name="expired_thread_page_path" requires="node_page">
+	/template/NamlServlet.jtp?macro=expired_thread_page&node=<n.page_node.id/>
+</macro>
+
+<macro name="expired_thread_page" requires="servlet">
+	<n.node_page.>
+		<n.html>
+			<head>
+				<title><t>This thread is read-only</t></title>
+			</head>
+			<body>
+				<h1><t>This thread is read-only</t></h1>
+
+				<p>
+					<t>This topic was created a long time ago and a new reply here would be confusing to a lot of people.</t><br/>
+					<t>If you have questions about this subject, please <a href="[n.page_node.get_app_node.new_topic_path/]">create a new topic</a> and link to this thread if needed.</t>
+				</p>
+				<p>
+					&laquo; <a href="[n.page_node.path/]">Go back</a>
+				</p>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/forexschoolonline.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,2 @@
+<override_macro name="nabble_footer">
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/forum_avatars.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,14 @@
+
+<override_macro name="last_post_column_start">
+    <n.overridden/>
+	<td style="padding:0;vertical-align:top;border:none;">
+		<n.owner.avatar size="small"/>
+	</td>
+</override_macro>
+
+<override_macro name="topics_column_start">
+    <n.overridden/>
+	<td valign="top" style="border:none">
+		<n.owner.avatar size="small"/>
+	</td>
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/invite_subscribers.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,153 @@
+<override_macro name="add_subscribers" requires="node">
+	<n.if.is_submitted_form>
+		<then.process_invited_emails/>
+		<else.invite_subscribers_form/>
+	</n.if.is_submitted_form>
+</override_macro>
+
+<macro name="invite_subscribers_form">
+	<div class="weak-color" style="margin:.5em 0 .5em">
+		<t>Enter one email address or user name per row:</t>
+	</div>
+	<n.form.>
+		<input type="hidden" name="filter" value="invite"/>
+		<textarea name="invited-emails" style="width:40em;height:12em"></textarea>
+
+		<div class="weak-color" style="font-size:80%;margin:1em 0 .5em">
+			<t>Examples:</t>
+			<div style="margin-left:1em">
+			john_smith<br/>
+			john_smith@example.com<br/>
+			John Smith &lt;john_smith@example.com&gt;
+			</div>
+		</div>
+
+		<div class="weak-color" style="margin:1.5em 0 .5em">
+			<t><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list.
+			Users will have to click on a link to confirm their subscription.</t>
+		</div>
+
+		<div style="margin-top:1.4em">
+			<input type="submit" value="[t]Invite Subscribers[/t]" />
+		</div>
+	</n.form.>
+</macro>
+
+<macro name="invited_emails" requires="servlet">
+	<n.get_parameter name="invited-emails"/>
+</macro>
+
+<macro name="process_invited_emails">
+	<div class="second-font field-title">
+		<t>Subscription Results</t>
+	</div>
+	<n.if.not.is_null.invited_emails>
+		<then>
+			<n.string_list. values="[n.invited_emails/]" separator="\n">
+				<table>
+					<n.loop.>
+						<n.set_var. name='email'>
+							<n.get_email_address_from.current_string/>
+						</n.set_var.>
+
+						<n.if.not.is_empty.var name='email'>
+							<then>
+								<n.set_local_subscription.page_node.subscription_for email="[n.var name='email'/]" />
+								<n.if.not.local_subscription.is_subscribed>
+									<then.if.local_subscription.user.can_view.local_subscription.node>
+										<then>
+											<n.local_subscription.send_subscription_invite/>
+											<n.row_success.current_string/>
+										</then>
+										<else.row_failed text="[n.current_string/]" explaination="[t]This user doesn't have permission to view this application (add him/her to a group that allows this and try again)[/t]"/>
+									</then.if.local_subscription.user.can_view.local_subscription.node>
+									<else.row_success text="[n.current_string/]" explaination="[t]Already subscribed[/t]"/>
+								</n.if.not.local_subscription.is_subscribed>
+							</then>
+							<else.row_failed.current_string/>
+						</n.if.not.is_empty.var>
+					</n.loop.>
+				</table>
+			</n.string_list.>
+		</then>
+	</n.if.not.is_null.invited_emails>
+</macro>
+
+<macro name="row_success" dot_parameter="text" parameters="explaination">
+	<tr>
+		<td style="padding:.3em"><n.text/></td>
+		<td>
+			<t>Success</t>
+			<n.if.not.is_empty.explaination>
+				<then> -- <n.explaination/></then>
+			</n.if.not.is_empty.explaination>
+		</td>
+	</tr>
+</macro>
+
+<macro name="row_failed" dot_parameter="text" parameters="explaination">
+	<tr>
+		<td style="padding:.3em"><n.text/></td>
+		<td class="important">
+			<t>Failed</t>
+			<n.if.not.is_empty.explaination>
+				<then> -- <n.explaination/></then>
+			</n.if.not.is_empty.explaination>
+		</td>
+	</tr>
+</macro>
+
+<macro name="send_subscription_invite" requires="subscription,node_page,servlet">
+	<n.set_local_subscription.this_subscription />
+	<n.new_email.>
+		<n.send>
+			<to><n.user.user_email/></to>
+			<subject><t>Subscribe to <t.location.page_node.subject/></t></subject>
+			<text_part>
+				<t>Dear user,</t>
+
+				<t><t.owner_name.page_node.owner.name/> is inviting you to subscribe to <t.location.page_node.subject/>:</t>
+				<n.page_node.url/>
+
+				<t>With your subscription, updates will be sent directly to your email address
+				and you can reply to them to participate in the discussion. Your subscription works the same
+				as a mailing list.</t>
+
+				<t>To confirm your subscription, click on the link below:</t>
+				<n.local_subscription.send_subscription_invite_subscribe_by_code_url/>
+
+				<t>Sincerely,</t>
+				<t>The Nabble team</t>
+				________________________________________
+				<t>Free Embeddable <t.app.page_node.view_name/></t> powered by Nabble
+				<n.nabble_homepage/>
+			</text_part>
+			<html_part>
+				<t>Dear user,</t><br/>
+				<br/>
+				<t><t.owner_name.page_node.owner.name/> is inviting you to subscribe to <t.location.bold.page_node.subject/>:</t><br/>
+				<a href="[n.page_node.url/]"><n.page_node.url/></a><br/>
+				<br/>
+				<t>With your subscription, updates will be sent directly to your email address
+				and you can reply to them to participate in the discussion. Your subscription works the same
+				as a mailing list.</t><br/>
+				<br/>
+				<t>To confirm your subscription, click on the link below:</t>
+				<div style="background-color:#FFFADB;border:#EDDD79 solid 1px;margin:1.2em 0;padding:.5em">
+					<a href="[n.local_subscription.send_subscription_invite_subscribe_by_code_url/]">
+						<n.local_subscription.send_subscription_invite_subscribe_by_code_url/>
+					</a>
+				</div>
+				<t>Sincerely,</t><br/>
+				<t>The Nabble team</t><br/>
+				________________________________________<br/>
+				<t>Free Embeddable <t.app.page_node.view_name/></t> powered by Nabble<br/>
+				<n.nabble_homepage/><br/><br/>
+			</html_part>
+		</n.send>
+	</n.new_email.>
+</macro>
+
+<macro name="send_subscription_invite_subscribe_by_code_url" requires="subscription">
+	<n.subscribe_by_code_url subscription_to="DESCENDANTS"/>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/jonasbirrexblog.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,2 @@
+<override_macro name="archives_widget" requires="node_page,servlet">
+</override_macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/jonaspm.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,15 @@
+<override_macro name="app_topic_filter" requires="node_page,servlet">
+	<n.if.equal value1="[n.page_node.id/]" value2="[n.root_node.id/]">
+		<then>
+			<n.separate>
+				<text1.hide_null.overridden />
+				<separator>and</separator>
+				<text2.page_node.exclude_parent_filter parent_id="1640040" />
+			</n.separate>
+		</then>
+		<else.overridden/>
+	</n.if.equal>
+</override_macro>
+
+<override_macro name="nabble_footer">
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_arabic.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,846 @@
+## Arabic
+
+<macro name="the" dot_parameter="app">
+    <n.switch. value="[n.app/]">
+        <n.case. value="mailing list">القائمة البريدية</n.case.>
+        <n.case. value="mailing list archive">أرشيف القائمة البريدية</n.case.>
+        <n.case. value="forum">المنتدى</n.case.>
+        <n.case. value="category">الفئة</n.case.>
+        <n.case. value="subcategory">الفئة الفرعية</n.case.>
+        <n.case. value="gallery">معرض الصور</n.case.>
+        <n.case. value="newspaper">الصحيفة</n.case.>
+        <n.case. value="blog">المدونة</n.case.>
+        <n.case. value="board">اللوحة</n.case.>
+        <n.case. value="mixed">التطبيق المختلط</n.case.>
+    </n.switch.>
+</macro>
+
+<macro name="this" dot_parameter="app">
+    <n.switch. value="[n.app/]">
+        <n.case. value="mailing list">هذه القائمة البريدية</n.case.>
+        <n.case. value="mailing list archive">أرشيف القائمة البريدية هذا</n.case.>
+        <n.case. value="forum">هذا المنتدى</n.case.>
+        <n.case. value="category">هذه الفئة</n.case.>
+        <n.case. value="subcategory">هذه الفئة الفرعية</n.case.>
+        <n.case. value="gallery">معرض الصور هذا</n.case.>
+        <n.case. value="newspaper">هذه الصحيفة</n.case.>
+        <n.case. value="blog">هذه المدونة</n.case.>
+        <n.case. value="board">هذه اللوحة</n.case.>
+        <n.case. value="mixed">هذا التطبيق المختلط</n.case.>
+    </n.switch.>
+</macro>
+
+<macro name="your_new" dot_parameter="app">
+    <n.switch. value="[n.app/]">
+        <n.case. value="mailing list">قائمتك البريدية الجديدة</n.case.>
+        <n.case. value="mailing list archive">أرشيف قائمتك البريدية الجديد</n.case.>
+        <n.case. value="forum">منتداك الجديد</n.case.>
+        <n.case. value="category">فئتك الجديدة</n.case.>
+        <n.case. value="subcategory">فئتك الفرعية الجديدة</n.case.>
+        <n.case. value="gallery">معرض صورك الجديد</n.case.>
+        <n.case. value="newspaper">صحيفتك الجديدة</n.case.>
+        <n.case. value="blog">مدونتك الجديدة</n.case.>
+        <n.case. value="board">لوحتك الجديدة</n.case.>
+        <n.case. value="mixed">تطبيقك المختلط الجديد</n.case.>
+    </n.switch.>
+</macro>
+
+<macro name="mentioned" dot_parameter="app">
+    <n.switch. value="[n.app/]">
+        <n.case. value="mailing list">القائمة البريدية المذكورة</n.case.>
+        <n.case. value="mailing list archive">أرشيف القائمة البريدية المذكور</n.case.>
+        <n.case. value="forum">المنتدى المذكور</n.case.>
+        <n.case. value="category">الفئة المذكورة</n.case.>
+        <n.case. value="subcategory">الفئة الفرعية المذكورة</n.case.>
+        <n.case. value="gallery">معرض الصور المذكور</n.case.>
+        <n.case. value="newspaper">الصحيفة المذكورة</n.case.>
+        <n.case. value="blog">المدونة المذكورة</n.case.>
+        <n.case. value="board">اللوحة المذكورة</n.case.>
+        <n.case. value="mixed">التطبيق المختلط المذكور</n.case.>
+    </n.switch.>
+</macro>
+
+## MONTHS ##
+
+<translation><from>January</from><to>يناير</to></translation>
+<translation><from>February</from><to>فبراير</to></translation>
+<translation><from>March</from><to>مارس</to></translation>
+<translation><from>April</from><to>أبريل</to></translation>
+<translation><from>May</from><to>مايو</to></translation>
+<translation><from>June</from><to>يونيو</to></translation>
+<translation><from>July</from><to>يوليو</to></translation>
+<translation><from>August</from><to>أغسطس</to></translation>
+<translation><from>September</from><to>سبتمبر</to></translation>
+<translation><from>October</from><to>أكتوبر</to></translation>
+<translation><from>November</from><to>نوفمبر</to></translation>
+<translation><from>December</from><to>ديسمبر</to></translation>
+
+<translation><from>Jan</from><to>يناير</to></translation>
+<translation><from>Feb</from><to>فبراير</to></translation>
+<translation><from>Mar</from><to>مارس</to></translation>
+<translation><from>Apr</from><to>أبريل</to></translation>
+
+<translation><from>Jun</from><to>يونيو</to></translation>
+<translation><from>Jul</from><to>يوليو</to></translation>
+<translation><from>Aug</from><to>أغسطس</to></translation>
+<translation><from>Sep</from><to>سبتمبر</to></translation>
+<translation><from>Oct</from><to>أكتوبر</to></translation>
+<translation><from>Nov</from><to>نوفمبر</to></translation>
+<translation><from>Dec</from><to>ديسمبر</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>إساءة استخدام هذه الميزة تعد تجاوزاً لشروط الاستخدام</to></translation>
+<translation><from>Access Request</from><to>طلب وصول</to></translation>
+<translation><from>Account settings</from><to>إعدادات الحساب</to></translation>
+<translation><from>Account Settings</from><to>إعدادات الحساب</to></translation>
+<translation><from>Action</from><to>إجراء</to></translation>
+<translation><from>Add a link to another page</from><to>إضافة رابط لصفحة أخرى</to></translation>
+<translation><from>Add a new comment</from><to>إضافة تعليق جديد</to></translation>
+<translation><from>Add a new group</from><to>إضافة مجموعة جديدة</to></translation>
+<translation><from>Add an image to your post</from><to>إضافة صورة إلى مشاركتك</to></translation>
+<translation><from>Add another address</from><to>إضافة عنوان آخر</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>إضافة محتوى لا ينبغي ترميزه (code مثلاً)</to></translation>
+<translation><from>Adding Sub-Headers</from><to>إضافة عنوان ثانوي</to></translation>
+<translation><from>Add New Group</from><to>إضافة مجموعة جديدة</to></translation>
+<translation><from>Add / Remove Groups</from><to>إضافة / حذف المجموعات</to></translation>
+<translation><from>Add smileys and funny animations</from><to>إضافة وجوه مبتسمة ورسوم متحركة مرحة</to></translation>
+<translation><from>Add Subscribers</from><to>إضافة مشتركين</to></translation>
+<translation><from>Add this item to your favorites list</from><to>إضافة هذا العنصر إلى قائمتك المفضلة</to></translation>
+<translation><from>Administrator</from><to>المشرف</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity, other sexual content.</from><to>محتوى للكبار فقط، محتوى جنسي، عري.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>قتال للكبار، هجوم جسدي، عنف شباب، إساءة للحيوانات، أو الترويج للإرهاب.</to></translation>
+<translation><from>Advanced Search</from><to>بحث متقدم</to></translation>
+<translation><from>Advanced Settings</from><to>الإعدادات المتقدمة</to></translation>
+<translation><from>Advertisement</from><to>إعلان</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>نبهني باستخدام البريد الإلكتروني عند إضافة مشاركة جديدة إلى هذا الموضوع</to></translation>
+<translation><from>All</from><to>الجميع</to></translation>
+<translation><from>all of the words:</from><to>جميع الكلمات:</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>تم حذف جميع مشاركات <n.author/> بنجاح.</to></translation>
+<translation><from>All posts</from><to>جميع المشاركات</to></translation>
+<translation><from>All users belong to this group</from><to>جميع المستخدمين ينتمون إلى هذه المجموعة</to></translation>
+<translation><from>All Users</from><to>جميع المستخدمين</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>جميع المستخدمين المسجلين في <n.location/>. تم التحقق من عناوين البريد الإلكتروني لهؤلاء المستخدمين، ويمكنهم جميعاً الدخول للنظام.</to></translation>
+<translation><from>Already Subscribed</from><to>مشترك مسبقاً</to></translation>
+<translation><from>An email has been sent to you.</from><to>تم إرسال رسالة إلكترونية إليك.</to></translation>
+<translation><from>Anonymous</from><to>مجهول</to></translation>
+<translation><from>anonymous user</from><to>مستخدم مجهول</to></translation>
+<translation><from>anonymous users</from><to>مستخدمون مجهولون</to></translation>
+<translation><from>Any message part contains</from><to>أي جزء من الرسالة يحوي</to></translation>
+<translation><from>Application</from><to>التطبيق</to></translation>
+<translation><from>Apps</from><to>التطبيقات</to></translation>
+<translation><from>Assignee</from><to>المكلف</to></translation>
+<translation><from>Assign</from><to>تكليف</to></translation>
+<translation><from>Assignment</from><to>تكليف</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>بناءً على طلبك، فإن عنوان البريد الإلكتروني المستخدم لإنشاء مواضيع جديدة تحت <n.app/> هو:</to></translation>
+<translation><from>at least one of the words:</from><to>إحدى هذه الكلمات على الأقل:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to>قوائم (Feeds) <n.location/></to></translation>
+<translation><from>at priority</from><to>الأولوية</to></translation>
+<translation><from>Authorized Users Only</from><to>المستخدمون المصرح لهم فقط</to></translation>
+<translation><from>Author name</from><to>اسم الكاتب</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>تجنب المحادثات الجانبية مثل "شكراً" و "ممتاز"... يمكنك <n.page_node.reply_to_author_link.>إرسال رسالة خاصة</n.page_node.reply_to_author_link.> إذا أردت.</to></translation>
+<translation><from>Banned User</from><to>مستخدم محجوب</to></translation>
+<translation><from>Ban this user</from><to>حجب هذا المستخدم</to></translation>
+<translation><from>Ban User</from><to>حجب المستخدم</to></translation>
+<translation><from>Before deleting this archive...</from><to>قبل حذف هذا الأرشيف...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>أدناه يمكنك إدارة المجموعات والمستخدمين. يمكنك نسخ ولصق المستخدمين لنقلهم من مجموعة إلى أخرى.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>تنبيه</b>: سيقوم Nabble بإرسال دعوة لكل بريد إلكتروني في القائمة. على المستخدمين زيارة رابط لتأكيد رغبتهم في الاشتراك.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>تنبيه:</b> هذا الاشتراك مختلف عن اشتراك القائمة البريدية الأصلية. هذا الاشتراك خاص بالأرشيف، وليس القائمة البريدية. اشتراكك في الأرشيف لايضمن قبول رسائلك في القائمة البريدية.</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>تنبيه</b>: عليك إشراك هذا الأرشيف في القائمة البريدية لتفعيله.</to></translation>
+<translation><from>blog</from><to>مدونة</to></translation>
+<translation><from>Blog</from><to>مدونة</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>ملاحظة</b>: بما أنك مشرف، فيمكنك <n.page_node.change_permissions_link.>تغيير صلاحيات <n.location/></n.page_node.change_permissions_link.> والتأكد من أنه يمكنك إنشاء مواضيع جديدة هنا.</to></translation>
+<translation><from>board</from><to>لوحة</to></translation>
+<translation><from>Board</from><to>لوحة</to></translation>
+<translation><from>Bold</from><to>عريض</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>تحذير:</b> تتم حالياً إعادة بناء فهرس البحث. قد تكون نتائج البحث غير كاملة.</to></translation>
+<translation><from>by <t.author/></from><to>كتبه <n.author/></to></translation>
+<translation><from>Cancel</from><to>إلغاء</to></translation>
+<translation><from>category</from><to>فئة</to></translation>
+<translation><from>Category</from><to>فئة</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>تحذير: لا يمكن التراجع عن هذا الإجراء.</to></translation>
+<translation><from>Change appearance</from><to>تغيير المظهر</to></translation>
+<translation><from>Change application type</from><to>تغيير نوع التطبيق</to></translation>
+<translation><from>Change Application Type</from><to>تغيير نوع التطبيق</to></translation>
+<translation><from>Change code image</from><to>تغيير صورة الرمز</to></translation>
+<translation><from>Change domain name</from><to>تغيير اسم النطاق</to></translation>
+<translation><from>Change language</from><to>تغيير اللغة</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>تغيير أو حذف صورتك الرمزية.</to></translation>
+<translation><from>Change parent</from><to>تغيير الأب</to></translation>
+<translation><from>Change permissions</from><to>تغيير الصلاحيات</to></translation>
+<translation><from>Change Permissions</from><to>تغيير الصلاحيات</to></translation>
+<translation><from>Change post date</from><to>تغيير تاريخ النشر</to></translation>
+<translation><from>Change Post Date</from><to>تغيير تاريخ النشر</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>تغيير نص توقيعك الذي يظهر أسفل مشاركاتك.</to></translation>
+<translation><from>Change User Groups</from><to>تغيير مجموعات المستخدم</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>تغيير تفضيلات العرض وإعدادات أخرى.</to></translation>
+<translation><from>Change Your Picture</from><to>تغيير صورتك</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>تغيير عنوان بريدك الإلكتروني المسجل، وكلمة السر، واسم المستخدم.</to></translation>
+<translation><from>Child abuse</from><to>إساءة معاملة الأطفال</to></translation>
+<translation><from>Choose a subcategory</from><to>اختر فئة فرعية</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>اختر فئة فرعية لنشر مشاركتك</to></translation>
+<translation><from>Choose the best offer for you</from><to>اختر العرض الأفضل بالنسبة إليك</to></translation>
+<translation><from>Classic</from><to>تقليدي</to></translation>
+<translation><from>Clear Log</from><to>مسح السجل</to></translation>
+<translation><from>Click for more options</from><to>انقر للمزيد من الخيارات</to></translation>
+<translation><from>click here</from><to>انقر هنا</to></translation>
+<translation><from>Click here to make your first post</from><to>انقر هنا لتنشئ مشاركتك الأولى</to></translation>
+<translation><from>Click to filter</from><to>انقر للترشيح</to></translation>
+<translation><from>Close</from><to>إغلاق</to></translation>
+<translation><from>Close this message</from><to>إغلاق هذه الرسالة</to></translation>
+<translation><from>comment</from><to>تعليق</to></translation>
+<translation><from>Comment</from><to>تعليق</to></translation>
+<translation><from>comments</from><to>التعليقات</to></translation>
+<translation><from>Comments</from><to>التعليقات</to></translation>
+<translation><from>Confirm Password</from><to>تأكيد كلمة السر</to></translation>
+<translation><from>Confirm Subscription</from><to>تأكيد الاشتراك</to></translation>
+<translation><from>Congratulations!</from><to>تهانينا!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>نهنئك على <n.this.app/>!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to> المحتوى الذي يدعو إلى الكراهية أو العنف، أو الإساءة إلى المستضعفين، أو التسلط، أو التفرقة العرقية أو التأليب ضد شخص أو مجموعة أو منظمة أو السفاهة المفرطة.</to></translation>
+<translation><from>CONTENTS DELETED</from><to>محتوى محذوف</to></translation>
+<translation><from>Continue</from><to>الاستمرار</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>التعدي على الخصوصيات أو حقوق النشر، أو أي دعاوى قانونية أخرى</to></translation>
+<translation><from>Count</from><to>العدد</to></translation>
+<translation><from>Created by <t.author/></from><to>أنشأه <n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>إنشاء <n.element/></to></translation>
+<translation><from>Create <t.element/></from><to>إنشاء <n.element/></to></translation>
+<translation><from>Current Credits</from><to>الرصيد الحالي</to></translation>
+<translation><from>Currently Nabble supports</from><to>تدعم Nabble حالياً</to></translation>
+<translation><from>Current Subscribers</from><to>المشتركون حالياً</to></translation>
+<translation><from>Daily digest</from><to>الملخص اليومي</to></translation>
+<translation><from>Data successfully saved</from><to>تم حفظ البيانات بنجاح</to></translation>
+<translation><from>Date</from><to>التاريخ</to></translation>
+<translation><from>days</from><to>أيام</to></translation>
+<translation><from>Dear <t.name/>,</from><to>العزيز(ة) <n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>عزيزي المستخدم,</to></translation>
+<translation><from>Default</from><to>افتراضي</to></translation>
+<translation><from>Delete all posts from this user</from><to>حذف جميع مشاركات هذا المستخدم</to></translation>
+<translation><from>Delete Application</from><to>حذف التطبيق</to></translation>
+<translation><from>Deleted posts</from><to>المشاركات المحذوفة</to></translation>
+<translation><from>Delete</from><to>حذف</to></translation>
+<translation><from>Delete this post and replies</from><to>حذف هذه المشاركة وردودها</to></translation>
+<translation><from>Delete this post</from><to>حذف هذه المشاركة</to></translation>
+<translation><from>Delete this topic</from><to>حذف هذا الموضوع</to></translation>
+<translation><from>Description is in HTML Format</from><to>الوصف بصيغة HTML</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>لا تكرر مشاركتك. انتظر بضعة أيام حتى يتمكن الآخرون من قراءة مشاركتك من خلال البريد الإلكتروني.</to></translation>
+<translation><from>Don't show this message again</from><to>لا تعرض هذه الرسالة مرة أخرى</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>هل تريد فعلاً حذف هذه المشاركة؟</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>هل تريد فعلاً حذف هذه الرسالة وجميع ردودها نهائياً؟</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>هل تريد فعلاً <n.important.>حذف</n.important.> <n.location/> نهائياً؟</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>هل تريد فعلاً إزالة إعدادات أرشيف القائمة البريدية؟</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>هل تريد فعلاً الاشتراك في <n.location/>؟</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>هل تريد فعلاً إزالة حجب هذا المستخدم؟</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>هل تريد فعلاً إيقاف اشتراكك في <n.location/>؟</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>المحتوى المتعلق بتعاطي المخدرات والأدوية غير المشروعة، وإساءة استخدام النار أو المتفجرات، وبيع البيرة أو الكحول المركزة أو التبغ وما إلى ذلك، أو الأسلحة أو الذخيرة، أو أي أعمال خطيرة أخرى.</to></translation>
+<translation><from>Edit name & description</from><to>تحرير الاسم والوصف</to></translation>
+<translation><from>Edit Name & Description</from><to>تحرير الاسم والوصف</to></translation>
+<translation><from>Editor</from><to>المحرر</to></translation>
+<translation><from>Edit Personal Information</from><to>تحرير المعلومات الشخصية</to></translation>
+<translation><from>Edit post</from><to>تحرير المشاركة</to></translation>
+<translation><from>Edit Subscription</from><to>تحرير الاشتراك</to></translation>
+<translation><from>Edit Your Signature</from><to>تحرير التوقيع</to></translation>
+<translation><from>Email Confirmation</from><to>تأكيد البريد الإلكتروني</to></translation>
+<translation><from>Email for <t.app/></from><to>بريد <n.the.app/></to></translation>
+<translation><from>Email</from><to>البريد الإلكتروني</to></translation>
+<translation><from>Email Subscription</from><to>اشتراك البريد الإلكتروني</to></translation>
+<translation><from>Email this post to...</from><to>إرسال هذه المشاركة إلى...</to></translation>
+<translation><from>Embedding Contents</from><to>تضمين محتوى</to></translation>
+<translation><from>Embedding options</from><to>خيارات التضمين</to></translation>
+<translation><from>Embed</from><to>تضمين</to></translation>
+<translation><from>Embed post</from><to>تضمين المشاركة</to></translation>
+<translation><from>Embed Tags</from><to>علامات التضمين</to></translation>
+<translation><from>Embed this <t.app/></from><to>تضمين <n.this.app/></to></translation>
+<translation><from>Empty</from><to>فارغ</to></translation>
+<translation><from>Enter a valid email address.</from><to>أدخل عنوان بريد إلكتروني صحيح.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>أدخل عنوان بريدك الإلكتروني أدناه وسنرسل إليك رسالة تأكيد.</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>أدخل عنوان بريد إلكتروني أو اسم مستخدم واحد لكل سطر:</to></translation>
+<translation><from>Enter one user per row</from><to>أدخل مستخدماً واحداً لكل سطر</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>أدخل العنوان الثابت <b>للمشاركة</b> أو <b>المنتدى</b> الذي سيصبح الأب الجديد، أو اتركه فارغاً لجعل هذه الرسالة موضوعاً مستقلاً:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>أدخل عنوان الصفحة الرئيسية لهذه القائمة البريدية، حيث يمكن للمستخدمين العثور على المزيد من المعلومات.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>أدخل البادئة التي تستخدمها هذه القائمة البريدية قبل الموضوع. يتم حذف هذه البادئة بشكل تلقائي عند استيراد رسائل البريد الإلكتروني.</to></translation>
+<translation><from>Enter your email address</from><to>أدخل عنوان بريدك الإلكتروني</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>مثال: ahmad@domain.com</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>مثال: listname@listdomain.com</to></translation>
+<translation><from>Examples:</from><to>أمثلة:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>أمثلة: ['قائمة']، ['أ ب ت:']، إلخ.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>اشرح للمشرفين لماذا ترغب في الوصول لهذه المنطقة المحظورة.</to></translation>
+<translation><from>Explanation from this user:</from><to>شرح من هذا المستخدم:</to></translation>
+<translation><from>Extras & add-ons</from><to>الإضافات</to></translation>
+<translation><from>Failed</from><to>فشل</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>المفضلة (انقر لحذف هذا العنصر من قائمتك المفضلة)</to></translation>
+<translation><from>Feeds</from><to>Feeds</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>لم يتم رفع الملف: <n.file/> (الرجاء إعادة الرفع أو حذف الوسم)</to></translation>
+<translation><from>Filter by group</from><to>ترشيح بواسطة المجموعة</to></translation>
+<translation><from>Floating sub-forum</from><to>منتدى فرعي عائم</to></translation>
+<translation><from>Forgot Password?</from><to>هل نسيت كلمة السر؟</to></translation>
+<translation><from>Forgot your password?</from><to>هل نسيت كلمة السر؟</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>لمزيد من المعلومات، راجع: <n.info/></to></translation>
+<translation><from>forum</from><to>منتدى</to></translation>
+<translation><from>Forum</from><to>منتدى</to></translation>
+    <translation><from>Free Embeddable <t.app/></from><to>مجاني يمكن تضمينه <n.app/></to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>من الآن فصاعداً، سيصلك بريد إلكتروني لكل رسالة تنشر تحت <n.location/>.</to></translation>
+<translation><from>gallery</from><to>معرض</to></translation>
+<translation><from>Gallery</from><to>معرض</to></translation>
+<translation><from>Gambling or casino-related content</from><to>القمار أو محتويات ذات صلة</to></translation>
+<translation><from>Go back</from><to>عودة</to></translation>
+<translation><from>Go to next message</from><to>الذهاب للرسالة التالية</to></translation>
+<translation><from>Group Name:</from><to>اسم المجموعة:</to></translation>
+<translation><from>Groups</from><to>المجموعات</to></translation>
+<translation><from>Groups of this user</from><to>مجموعات المستخدم</to></translation>
+<translation><from>Hacking / cracking</from><to>محاولات التسلل أو الاختراق</to></translation>
+<translation><from>Harmful dangerous acts</from><to>أفعال مؤذية وخطيرة</to></translation>
+<translation><from>Hateful or abusive content</from><to>محتوى مسيء أو باعث للكراهية</to></translation>
+<translation><from>Help</from><to>مساعدة</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>يمكنك هنا شراء رصيد لإزالة الإعلانات من تطبيق Nabble الخاص بك. كل نقطة في رصيدك تقابل عرض صفحة واحدة دون إعلانات. لن يتم عرض أي إعلانات لزوارك ما دام لديك رصيد. ستقوم Nabble بعرض إعلانات مجدداً عند نفاذ رصيدك.</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>يمكنك هنا إخفاء معلومات أرشيف القائمة البريدية الحالي عن المستخدمين. يساعدك هذا الخيار على استبدال خادم القائمة البريدية الذي تستخدمه بخدمة الاشتراك بالبريد الإلكتروني التي تقدمها Nabble.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>إخفاء عنوان البريد الإلكتروني(مثلاً، user@host.com ستصبح <n.lt/>بريد إلكتروني مخفي<n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>إخفاء البريد</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>إخفاء معلومات أرشيف القائمة البريدية عن المستخدمين</to></translation>
+<translation><from>Highest</from><to>الأعلى</to></translation>
+<translation><from>High</from><to>عالي</to></translation>
+<translation><from>Hi <t.name/>,</from><to>مرحباً <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>إذا كان هذا هو عنوان بريدك الإلكتروني، فعليك <n.register_link.>التسجيل</n.register_link.> باستخدام العنوان نفسه. بعد التسجيل، ستمتلك هذا الحساب.</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>إذا لم تصلك أي رسائل من Nabble، تأكد أنها ليست في مجلد الرسائل غير المرغوب فيها.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>إذا كنت تنشر سؤالاً، فضلاً جرب البحث عنه أولاً. من الممكن أنه سبقت الإجابه على سؤال مشابه.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>إن لم تكن عضواً بعد، يمكنك<n.register_link.><b>التسجيل الآن</b></n.register_link.>.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>إذا قمت بحجب هذا المستخدم، فلن يتمكن من القيام بأي شيء في <n.location/>.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>إذا لم تقم بطلب هذه الرسالة أو لم تكن لديك فكرة لم تلقيتها، فضلاً تجاهلها. قد تكون قد وصلتك عن طريق الخطأ.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>إذا كنت لا ترغب بالتسجيل بعد، أدخل عنوان البريد الإلكتروني الذي ترغب بالنشر من خلاله، وسيتم إرسال عنوانك الخاص إليك بالبريد الإلكتروني.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>أذا قمت بحذف الاشتراك مسبقاً، فبإمكانك متابعة الإزالة.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>الرجاء اختيار تطبيق خادم القائمة البريدية ورقم إصداره إذا كنت على علم بهما.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>إذا قمت بتسجيل الخروج عن طريق الخطأ، الرجاء <n.login_link.>تسجيل الدخول مجدداً</n.login_link.>.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>إذا قمت بالرد على هذه الرسالة، ستتم إضافة رسالتك إلى النقاش أدناه</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>إذا قمت بإلغاء حجب هذا المستخدم، فسيمكنه النشر في <n.location/> مجدداً.</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>إذا كنت تريد الاشتراك بالقائمة البريدية، <n.mailing_list_options_link.>زُر هذه الصفحة</n.mailing_list_options_link.>.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>تجاهل X-No-Archive</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>قرأت و أوافق على <n.terms_link.>شروط استخدام</n.terms_link.> Nabble.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>لم يتم رفع الصورة: <n.image/> (الرجاء إعادة رفعها أو حذف الوسم)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>أنا مشترك، اسمح لي بالنشر الآن</to></translation>
+<translation><from>Incorrect Login!</from><to>تسجيل دخول غير صحيح!</to></translation>
+<translation><from>Individual emails</from><to>رسائل مفردة</to></translation>
+<translation><from>Inherit</from><to>توريث</to></translation>
+<translation><from>In Reply To</from><to>رداً على</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>رداً على <n.parent_link.>هذا المنشور</n.parent_link.> بواسطة <n.author/></to></translation>
+<translation><from>Insert</from><to>إدراج</to></translation>
+<translation><from>Insert Image</from><to>إدراج صورة</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>بدلاً من النشر باستخدام واجهة الموقع، يمكنك نشر مواضيع جديدة بإرسالها إلى العنوان التالي:</to></translation>
+<translation><from>in <t.location/></from><to>في <n.location/></to></translation>
+<translation><from>Invalid Code</from><to>رمز غير صحيح</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>عنوان بريد إلكتروني غير صحيح: <n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>عدد الأيام غير صحيح، يجب أن يكون العدد عدداً صحيحاً.</to></translation>
+<translation><from>invisible user</from><to>مستخدم غير مرئي</to></translation>
+<translation><from>invisible users</from><to>مستخدمون غير مرئيون</to></translation>
+<translation><from>Invite Subscribers</from><to>دعوة المشتركين</to></translation>
+<translation><from>is:</from><to>هو:</to></translation>
+<translation><from>is not:</from><to>ليس هو:</to></translation>
+<translation><from>is within the last:</from><to>خلال آخر:</to></translation>
+<translation><from>Italic</from><to>مائل</to></translation>
+<translation><from>item</from><to>عنصر</to></translation>
+<translation><from>items</from><to>عناصر</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>لا يمكن استرجاع العناصر المحذوفة لاحقاً.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>الصق الشفرة (المقدمة من المواقع أعلاه) ضمن هذه الوسوم وستكون جاهزة للنشر.</to></translation>
+<translation><from>Last Post</from><to>آخر مشاركة</to></translation>
+<translation><from>Learn more</from><to>تعلم المزيد</to></translation>
+<translation><from>Leave a comment</from><to>اترك تعليقاً</to></translation>
+<translation><from>Link</from><to>رابط</to></translation>
+<translation><from>Link to <t.location/></from><to>رابط لـ <t.location/></to></translation>
+<translation><from>List</from><to>قائمة</to></translation>
+<translation><from>List of Subcategories</from><to>قائمة الفئات الفرعية</to></translation>
+<translation><from>List Server</from><to>خادم القائمة</to></translation>
+<translation><from>List View</from><to>عرض كقائمة</to></translation>
+<translation><from>Loading...</from><to>تحميل...</to></translation>
+<translation><from>Location</from><to>الموقع</to></translation>
+<translation><from>Locked</from><to>مقفل</to></translation>
+<translation><from>Lock topic</from><to>إقفال الموضوع</to></translation>
+<translation><from>Login</from><to>تسجيل الدخول</to></translation>
+<translation><from>Log is empty</from><to>السجل فارغ</to></translation>
+<translation><from>Log out</from><to>تسجيل الخروج</to></translation>
+<translation><from>Lowest</from><to>الأقل</to></translation>
+<translation><from>Low</from><to>قليل</to></translation>
+<translation><from>Mailing List Address</from><to>عنوان القائمة البريدية</to></translation>
+<translation><from>Mailing list archive settings</from><to>إعدادات الأرشيف للقائمة البريدية</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>إعدادات الأرشيف للقائمة البريدية</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>مذكر الاشتراك بالقائمة البريدية</to></translation>
+<translation><from>Mailing List Website</from><to>موقع القائمة البريدية على الإنترنت</to></translation>
+<translation><from>Main Page</from><to>الصفحة الرئيسية</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>تأكد من استخدامك متصفح الإنترنت الذي استخدمته لتعبئة طلب التسجيل.</to></translation>
+<translation><from>Manage banned users</from><to>إدارة المستخدمين المحجوبين</to></translation>
+<translation><from>Manage pinned topics</from><to>إدارة الموضوعات المثبتة</to></translation>
+<translation><from>Manage subscribers</from><to>إدارة المشتركين</to></translation>
+<translation><from>Manage Subscribers</from><to>إدارة المشتركين</to></translation>
+
+<translation><from>Manage users & groups</from><to>إدارة المستخدمين والمجموعات</to></translation>
+<translation><from>Manage Users & Groups</from><to>إدارة المستخدمين والمجموعات</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>إعلانات جماعية، نصوص مضللة، احتيال أو تزوير.</to></translation>
+<translation><from>max. 80 characters</from><to>80 حرفاً كحد أقصى</to></translation>
+<translation><from>Message date</from><to>تاريخ الرسالة</to></translation>
+<translation><from>message</from><to>رسالة</to></translation>
+<translation><from>Message</from><to>رسالة</to></translation>
+<translation><from>Message is in HTML Format</from><to>الرسالة بصيغة HTML</to></translation>
+<translation><from>messages</from><to>رسائل</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>سيتم أرسال الرسائل المنشورة هنا للقائمة البريدية.</to></translation>
+<translation><from>Message subject contains</from><to>موضوع الرسالة يحتوي على</to></translation>
+<translation><from>Message text contains</from><to>نص الرسالة يحتوي على</to></translation>
+<translation><from>Mixed</from><to>مختلط</to></translation>
+<translation><from>Modified</from><to>معدل</to></translation>
+<translation><from>Archives</from><to>الأرشيف</to></translation>
+<translation><from>More Categories</from><to>المزيد من الفئات</to></translation>
+<translation><from>More</from><to>المزيد</to></translation>
+<translation><from>more help</from><to>المزيد من المساعدة</to></translation>
+<translation><from>more options</from><to>المزيد من الخيارات</to></translation>
+<translation><from>Move post</from><to>نقل المشاركة</to></translation>
+<translation><from>Move Post</from><to>نقل المشاركة</to></translation>
+<translation><from>Move topic</from><to>نقل الموضوع</to></translation>
+<translation><from>My Nabble Applications</from><to>تطبيقات Nabble الخاصة بي</to></translation>
+<translation><from>My Pending Posts</from><to>مشاركاتي المثبتة</to></translation>
+<translation><from>My posts</from><to>مشاركاتي</to></translation>
+<translation><from>Nabble Support</from><to>دعم Nabble</to></translation>
+<translation><from>Name</from><to>اسم</to></translation>
+<translation><from>New Post</from><to>مشاركة جديدة</to></translation>
+<translation><from>news</from><to>أخبار</to></translation>
+<translation><from>News</from><to>أخبار</to></translation>
+<translation><from>New Topic</from><to>موضوع جديد</to></translation>
+<translation><from>New topics only</from><to>المواضيع الجديدة فقط</to></translation>
+    <translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>تحذير</n.important.>: سيتم حذف كل شيء تحت <t.location/> للأبد!</to></translation>
+<translation><from>No banned users.</from><to>لايوجد مستخدمون محجوبون.</to></translation>
+<translation><from>No Filter</from><to>لايوجد مرشح</to></translation>
+<translation><from>none of the words:</from><to>ولا كلمة من:</to></translation>
+<translation><from>No registered user found with this email.</from><to>لم يتم العثور على أي مستخدم لهذا البريد الإلكتروني.</to></translation>
+<translation><from>No replies</from><to>لايوجد ردود</to></translation>
+<translation><from>Normal</from><to>عادي</to></translation>
+<translation><from>No sub-forums</from><to>لايوجد منتديات فرعية</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>لاحظ أن هذا العنوان خاص بك وأنه يستقبل الرسائل المرسلة من <n.address/> فقط، وذلك بهدف تجنب الرسائل غير المرغوب فيها.</to></translation>
+    <translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to><n.register_link.>سجل الآن</n.register_link.> إذا أردت تحرير ملفك الشخصي، أو استلام المشاركات عبر البريد الإلكتروني، أو التحكم بعناصرك المفضلة، أو الوصول لملفك الشخصي الشامل.</to></translation>
+<translation><from>one email per input box</from><to>بريد إلكتروني واحد لكل صندوق إدخال</to></translation>
+<translation><from>Online Users</from><to>المستخدمون المتصلون</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>الدخول إلى هذه المنطقة مقصور على المستخدمين المصرح لهم فقط.</to></translation>
+<translation><from>Open this post in classic view</from><to>فتح هذه المشاركة بطريقة العرض التقليدي</to></translation>
+<translation><from>Open this post in list view</from><to>فتح هذه المشاركة بطريقة العرض كقائمة</to></translation>
+<translation><from>Open this post in threaded view</from><to>فتح هذه المشاركة بطريقة العرض المتشعب</to></translation>
+<translation><from>Options</from><to>خيارات</to></translation>
+<translation><from>or</from><to>أو</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>أو يمكنك تجاهل هذه الرسالة إذا كان من الأفضل إقصاء هذا المستخدم عن تلك المنطقة.</to></translation>
+<translation><from>Other Settings</from><to>الإعدادات الأخرى</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>صفحة تجمع التطبيقات الفرعية والنقاشات.</to></translation>
+<translation><from>Page <t.number/></from><to>صفحة <n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>صفحة تحوي قائمة بسيطة من التطبيقات الفرعية والمناقشات.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>صفحة تحوي رسائل كاملة مع التعليقات المرتبطة بها.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>صفحة تحوي عناوين ومشاركات.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>صفحة تحوي مواضيع ومناقشات.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>صفحة تحوي مواضيع مجمعة تحت تطبيقات فرعية. إن لم توجد تطبيقات فرعية، سيتم عرض قائمة المواضيع.</to></translation>
+<translation><from>Password</from><to>كلمة السر</to></translation>
+<translation><from>Password Sent</from><to>تم إرسال كلمة السر</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>الاعتداء على الأطفال، والعنف وغيرها من الانتهاكات.</to></translation>
+<translation><from>People</from><to>المستخدمون</to></translation>
+<translation><from>People in <t.location/></from><to>مستخدمو <n.location/></to></translation>
+<translation><from>Permalink</from><to>العنوان الثابت</to></translation>
+<translation><from>Photo and image gallery.</from><to>معرض للصور والرسومات.</to></translation>
+<translation><from>Pinned sub-forum</from><to>المنتديات الفرعية المثبتة</to></translation>
+<translation><from>Pin topic</from><to>تثبيت الموضوع</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>الرجاء حفظ الرابط أعلاه أو حفظ هذه الرسالة ليسهل عليك العثور على <n.this.app/> في المستقبل.</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>الرجاء مراجعة صندوق الوارد الآن وتفعيل حسابك للوصول لكل الميزات.</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>الرجاء الضغط على رابط التأكيد أدناه لتفعيل حسابك:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>إذا كنت بحاجة  للمساعدة، الرجاء مراسلة دعم Nabble.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>إذا كنت بحاجة للمساعدة، الرجاء مراسلة المشرفين.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>الرجاء إدخال عنوان بريد إلكتروني صحيح وإعادة المحاولة.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>الرجاء اتباع التعليمات المدرجة في الرسالة لإتمام عملية التسجيل.</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>الرجاء اتباع <n.subscribe_instructions_link.>تعليمات الاشتراك</n.subscribe_instructions_link.> لهذا الأرشيف.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>الرجاء تقديم عنوان ثابت صحيح.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>الرجاء إعادة إدخال اسم المستخدم وكلمة السر ثم الضغط على زر تسجيل الدخول.</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>الرجاء احترام آداب القائمة البريدية</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>استطلاعات الرأي من Polldaddy.com (استطلاعات Flash فقط)</to></translation>
+<translation><from>Post by email</from><to>المشاركة عبر البريد الإلكتروني</to></translation>
+<translation><from>Post by Email</from><to>المشاركة عبر البريد الإلكتروني</to></translation>
+<translation><from>Post Count</from><to>عدد المشاركات</to></translation>
+<translation><from>Posted by <t.author/></from><to>نشرت بواسطة <n.author/></to></translation>
+<translation><from>post</from><to>مشاركة</to></translation>
+<translation><from>Post Message</from><to>نشر الرسالة</to></translation>
+<translation><from>Post New Message</from><to>نشر رسالة جديدة</to></translation>
+<translation><from>Post new message in <t.location/></from><to>نشر رسالة جديدة في <n.location/></to></translation>
+<translation><from>posts</from><to>المشاركات</to></translation>
+<translation><from>Posts</from><to>المشاركات</to></translation>
+<translation><from>Posts in <t.location/></from><to>المشاركات في <n.location/></to></translation>
+<translation><from>Preview Message</from><to>معاينة الرسالة</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>الأسعار بالدولار الأمريكي. إن عملية الدفع هذه تستخدم <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>، والتي تمكن عملاء Amazon من استخدام طرق الدفع المعرفة في حساباتهم لدى Amazon.com لدفع مقابل البضائع والخدمات للمواقع الإلكترونية والتطبيقات التي تقبل دفعات Amazon.</to></translation>
+<translation><from>Print post</from><to>طباعة المشاركة</to></translation>
+<translation><from>Priority</from><to>الأولوية</to></translation>
+<translation><from>(private)</from><to>(خاص)</to></translation>
+<translation><from>Profile of <t.author/></from><to>الملف الشخصي ل<n.author/></to></translation>
+<translation><from>Quote</from><to>اقتباس</to></translation>
+<translation><from>Quote the original message</from><to>اقتباس الرسالة الأصلية</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>اقتباس الجزء الذي تقوم بالرد عليه واختصاره ليشمل المحتوى المرتبط بردك. يوفر ذلك سياقاً يسهل قراءة رسالتك على من يستلمها عبر البريد الإلكتروني.</to></translation>
+<translation><from>Raw mail</from><to>رسالة البريد الإلكتروني الأصلية</to></translation>
+<translation><from>Raw text</from><to>النص الاصلي</to></translation>
+<translation><from>read more</from><to>قراءة المزيد</to></translation>
+<translation><from>Read more</from><to>قراءة المزيد</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>قائمة للعرض فقط بجميع المستخدمين الذين لديهم رسائل تحت <n.location/>.</to></translation>
+<translation><from>Receive direct replies only.</from><to>استلام الردود المباشرة فقط.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>استلام جميع الرسائل المنشورة في <n.location/>.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>استلام جميع الردود على هذا الموضوع.</to></translation>
+<translation><from>Receive new topics only.</from><to>استلام المواضيع الجديدة فقط.</to></translation>
+<translation><from>Refresh</from><to>تحديث</to></translation>
+<translation><from>Registered</from><to>مسجل</to></translation>
+<translation><from>Registered Users</from><to>المستخدمون المسجلون</to></translation>
+<translation><from>Register</from><to>تسجيل</to></translation>
+<translation><from>Registering...</from><to>تسجيل...</to></translation>
+<translation><from>Register Now</from><to>سجل الآن</to></translation>
+<translation><from>Register to <t.app/></from><to>تسجيل في <n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>تم تأكيد التسجيل</to></translation>
+<translation><from>Registration Failed</from><to>فشل التسجيل</to></translation>
+<translation><from>Related Help Article</from><to>مقالة مساعدة ذات صلة</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>تذكر أن الحجب ليس فعالاً وذلك بسبب قدرة المستخدم على العودة بحساب جديد.</to></translation>
+<translation><from>Remove Ads</from><to>إزالة الإعلانات</to></translation>
+<translation><from>remove</from><to>حذف</to></translation>
+<translation><from>Remove Settings</from><to>حذف الإعدادات</to></translation>
+<translation><from>Remove Subscription</from><to>حذف الاشتراك</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>حذف حسابك وجميع مشاركاتك من <n.subject/>.</to></translation>
+<translation><from>Remove Your Account</from><to>حذف حسابك</to></translation>
+<translation><from>replies</from><to>الردود</to></translation><!-- noun / plural -->
+<translation><from>Replies</from><to>الردود</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>الرد</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>الرد</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>الرد على الكاتب</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>تبليغ عن محتوى غير ملائم</to></translation>
+<translation><from>Report Now</from><to>إبلاغ الآن</to></translation>
+<translation><from>required</from><to>مطلوب</to></translation>
+<translation><from>Return to <t.location/></from><to>العودة إلى <n.location/></to></translation>
+<translation><from>Save Changes</from><to>حفظ التغييرات</to></translation>
+<translation><from>Save Settings</from><to>حفظ الإعدادات</to></translation>
+<translation><from>Save Subscription</from><to>حفظ الاشتراك</to></translation>
+<translation><from>Search</from><to>بحث</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>اختر الإجرائات التي ترغب بتنفيذها أدناه:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>اختر الفئة التي تعكس تحفظك على محتويات هذه الصفحة.</to></translation>
+<translation><from>Send email to me</from><to>إرسال رسالة إليّ</to></translation>
+<translation><from>Send Email to <t.author/></from><to>إرسال رسالة إلى <n.author/></to></translation>
+<translation><from>Send Request</from><to>أرسل طلباً</to></translation>
+<translation><from>Send To:</from><to>إرسال إلى:</to></translation>
+<translation><from>Sexual content</from><to>محتوى جنسي</to></translation>
+<translation><from>Show</from><to>عرض</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>إظهار ملاحظات Nabble</to></translation>
+<translation><from>Sincerely,</from><to>المخلص,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>لأن هذا التطبيق عبارة عن أرشيف قائمة بريدية، الرجاء إلغاء اشتراك عنوان البريد الإلكتروني أدناه قبل الضغط على زر الحذف.</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>لأنك مستخدم غير مسجل، علينا التأكد من أنك شخص حقيقي.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>تم حذف بعض مشاركاتك من <n.location/> هذه نسخ منها لتحظى بفرصة الاحتفاظ بها.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>عذراً، يمكن للمستخدمين المسجلين فقط المشاركة في <n.app/>.</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>عذراً، لقد تم حجبك من قبل المشرفين.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>عذراً، هذا العنوان غير مخول بمشاهدة أي رسائل في <n.location/>.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>عذراً، لا يمكنك إنشاء موضوع جديد هنا.<br/>لاحظ أنه قد يمكنك الرد على المشاركات الموجودة.</to></translation>
+<translation><from>Sort by date</from><to>ترتيب حسب التاريخ</to></translation>
+<translation><from>Sort by relevance</from><to>ترتيب حسب درجة العلاقة</to></translation>
+<translation><from>Sorted by date</from><to>مرتبة حسب التاريخ</to></translation>
+<translation><from>Sorted by relevance</from><to>مرتبة حسب درجة العلاقة</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[مكتشف الرسائل غير المرغوبة] رسالة غير صالحة تحوي العديد من كلمات '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[مكتشف الرسائل غير المرغوبة] يجب ألا تحتوي الرسالة على  '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[مكتشف الرسائل غير المرغوبة] تحتوي الرسالة على كلمات يكثر تداولها في الرسائل غير المرغوب فيها.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[مكتشف الرسائل غير المرغوبة] يجب ألا يحتوي العنوان على '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[مكتشف الرسائل غير المرغوبة] يحتوي العنوان على كلمات يكثر تداولها في الرسائل غير المرغوب فيها.</to></translation>
+<translation><from>Spam</from><to>الرسائل غير المرغوب فيها</to></translation>
+<translation><from>Stars in <t.location/></from><to>المفضلة في <n.location/></to></translation>
+<translation><from>Structure</from><to>الهيكل</to></translation>
+<translation><from>Subcategories</from><to>الفئات الفرعية</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>الفئات الفرعية تحت <n.location/></to></translation>
+<translation><from>Subcategory</from><to>فئة فرعية</to></translation>
+<translation><from>Sub-Forum</from><to>منتدى فرعي</to></translation>
+<translation><from>Sub-Forums</from><to>المنتديات الفرعية</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>المنتديات الفرعية والمواضيع</to></translation>
+<translation><from>Subject</from><to>الموضوع</to></translation>
+<translation><from>Subscribe</from><to>اشتراك</to></translation>
+<translation><from>subscriber</from><to>المشترك</to></translation>
+<translation><from>subscribers</from><to>المشتركون</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>الاشتراك في <n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>الاشتراك بواسطة البريد الإلكتروني</to></translation>
+<translation><from>Subscription Confirmation</from><to>تأكيد الاشتراك</to></translation>
+<translation><from>Subscription Confirmed</from><to>تم تأكيد الاشتراك</to></translation>
+<translation><from>Subscription Format</from><to>نوع الاشتراك</to></translation>
+<translation><from>Subscription Removed</from><to>تم حذف الاشتراك</to></translation>
+<translation><from>Subscription Results</from><to>نتائج الاشتراك</to></translation>
+<translation><from>Subscription Type</from><to>نوع الاشتراك</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>نجاح: تم إرسال رسالة تأكيد إلى بريدك الإلكتروني.</to></translation>
+<translation><from>Success</from><to>نجاح</to></translation>
+<translation><from>Take Action</from><to>اتخاذ الإجراء</to></translation>
+<translation><from><t.app/> Registration</from><to>التسجيل في <n.the.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to>تم حجب <n.author/> بنجاح.</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to>تم إلغاء حجب <n.author/> بنجاح.</to></translation>
+<translation><from>Tell me more & show examples</from><to>أخبرني بالمزيد وأرني أمثلة</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>شكراً لاشتراكك في <n.the.app/>!</to></translation>
+<translation><from>Thank You</from><to>شكراً لك</to></translation>
+<translation><from>The author has deleted this message.</from><to>لقد قام الكاتب بحذف هذه الرسالة.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>يحتوي العنوان على رمز غير صحيح.</to></translation>
+<translation><from>the exact phrase:</from><to>العبارة بالضبط:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>قد تتطلب القائمة البريدية أن تكون مشتركاً فيها قبل أن يمكنك الإرسال إليها. لاحظ أن تسجيلك في Nabble لا يقتضي اشتراكك في القائمة البريدية تلقائياً. إذا لم تشترك بعد، فضلاً قم بذلك الآن. إذا لم تكن متأكداً من اشتراكك، اشترك مجدداً حيث أنه لا يترتب أي ضرر على ذلك.</to></translation>
+<translation><from>The Nabble team</from><to>فريق Nabble</to></translation>
+<translation><from>The name of the group is not valid.</from><to>اسم المجموعة غير صالح.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>لا يمكن أن تكون المشاركة نفسها هي المشاركة الأم.</to></translation>
+<translation><from>The password fields don't match.</from><to>كلمة السر غير متطابقة.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>يوجد حساب لمستخدم غير مسجل مرتبط بالبريد الإلكتروني <n.email/>.</to></translation>
+<translation><from>The subject is required.</from><to>العنوان مطلوب.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>سيتلقى المستخدم نسخة من جميع المشاركات المحذوفة عبر البريد الإلكتروني ليتمكن من حفظها.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>رمز التحقق لم يتوافق مع الصورة.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>هذا الفرع كبير جداً، لذلك تم حذف بعض المشاركات. استخدم طرق العرض الأخرى لتتمكن من قراءة جميع المشاركات.</to></translation>
+<translation><from>This email is already subscribed.</from><to>هذا البريد الإلكتروني مشترك بالفعل.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>هذا المنتدى أرشيف للقائمة البريدية</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>هذا المنتدى أرشيف وبوابة يقوم بإرسال مشاركاتك إلى القائمة البريدية <b><n.mailing_list_address/></b>.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>يشمل ذلك الفئات الفرعية والمشاركات والصور والملفات وكل ما عدا ذلك.</to></translation>
+<translation><from>This is a mailing list archive</from><to>هذا أرشيف لقائمة بريدية</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>هذه رسالة تلقائية من Nabble لتأكيد إنشاء <n.your_new.app/>. إذا لم تقم بإنشاء <n.mentioned.app/>, فضلاً اتصل بنا عبر منتدى Nabble Support.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>تقبل هذه القائمة رسائل البريد الإلكتروني النصية البسيطة فقط</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>تسرد هذه القائمة المستخدمين المسجلين وغير المسجلين والمحجوبين، ولكنها لا تشمل المستخدمين المجهولين لأنهم ليس لهم عناوين بريد إلكتروني وبالتالي لا يمكن أن ينتموا إلى إي مجموعة.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>سيتم إرسال هذه الرسالة من <b><n.from/></b> إلى القائمة البريدية <b><n.to/></b>.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to>لم يتم قبول هذه المشاركة في القائمة البريدية بعد.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>تم تحديث هذه المشاركة في <n.date/>.</to></translation>
+<translation><from>This topic has been locked.</from><to>تم قفل هذا الموضوع.</to></translation>
+<translation><from>This topic has been pinned.</from><to>تم تثبيت هذا الموضوع.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>تم تثبيت هذا الموضوع في <n.location/>.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>تم إلغاء قفل هذا الموضوع.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>تم إلغاء تثبيت هذا الموضوع.</to></translation>
+<translation><from>This topic has unread posts</from><to>يحتوي هذا الموضوع على مشاركات لم تقرأها بعد</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>هذه الموضوع مسند إليك بأولوية <n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>هذا المستخد غير مصرح له باستعراض هذا التطبيق (أضفه إلى مجموعة مصرح لها بذلك ثم حاول مجدداً)</to></translation>
+<translation><from>This user name is already in use.</from><to>اسم المستخدم هذا غير متاح.</to></translation>
+<translation><from>Threaded</from><to>المتشعب</to></translation>
+<translation><from>Threaded View</from><to>العرض المتشعب</to></translation>
+<translation><from>Time</from><to>الوقت</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>ملاحظة: إذا كان الأرشيف مشتركاً في القائمة البريدية ولكنه لا يعمل رغم ذلك، جرب تجاهل X-No-Archive.</to></translation>
+<translation><from>Tips</from><to>إرشادات</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> طلب الانضمام إلى <n.location/>:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> نقاط</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> صفحات تم استعراضها دون إعلانات.</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>لقبول هذا الطلب، أضف هذا المستخدم لمجموعة واحدة على الأقل مصرح لها بالوصول إلى هذه المنطقة:</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>لإضافة <n.this.app/> إلى موقعك، انسخ والصق الشفرة التالية في صفحة HTML الخاصة بك:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>لتأكيد اشتراكك، انقر على الرابط أدناه:</to></translation>
+<translation><from>topic</from><to>الموضوع</to></translation>
+<translation><from>Topics and replies</from><to>المواضيع والردود</to></translation>
+<translation><from>topics</from><to>المواضيع</to></translation>
+<translation><from>Topics</from><to>المواضيع</to></translation>
+<translation><from>Topics only</from><to>المواضيع فقط</to></translation>
+<translation><from>Topics View</from><to>استعراض المواضيع</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>لتفادي الرسائل غير المرغوب فيها، هناك عنوان بريد إلكتروني <b>فريد</b> لكل مستخدم يمكنه من خلاله نشر مشاركات جديدة عبر البريد الإلكتروني.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>لإزالة مجموعة، أخلِ مساحة الكتابة أدناه واحفظ تغييراتك.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>لرؤية عنوان البريد الإلكتروني الذي يمكنك استخدامه لنشر مشاركاتك، فضلاً <n.login_link.>سجل دخولك</n.login_link.> أو <n.register_link.>أنشئ حساباً</n.register_link.>.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>لإنشاء موضوع جديد تحت <n.location/>, أرسل رسالة إلكترونية إلى <n.p2/></to></translation>
+<translation><from>Total</from><to>المجموع</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>لإلغاء اشتراكك في <n.location/></to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> يدعوك للاشتراك في <n.location/>:</to></translation>
+<translation><from>Turn off highlighting</from><to>تعطيل الإبراز</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/> أنشأ فئة فرعية جديدة</to></translation>
+<translation><from>Unable to Post</from><to>النشر غير ممكن</to></translation>
+<translation><from>Unassigned</from><to>غير مسند</to></translation>
+<translation><from>Unauthorized</from><to>غير مصرح</to></translation>
+<translation><from>Unban this user</from><to>إلغاء حجب هذا المستخدم</to></translation>
+<translation><from>Unban User</from><to>إلغاء حجب مستخدم</to></translation>
+<translation><from>Unknown or Other</from><to>غير معروف</to></translation>
+<translation><from>Unlock topic</from><to>فتح الموضوع</to></translation>
+<translation><from>Unpin topic</from><to>إزالة تثبيت الموضوع</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>غير مسجل / معطل</to></translation>
+<translation><from>Unregistered</from><to>غير مسجل</to></translation>
+<translation><from>Unregistered User</from><to>مستخدم غير مسجل</to></translation>
+<translation><from>Unsubscribe</from><to>إلغاء الاشتراك</to></translation>
+<translation><from>Upload a file</from><to>رفع ملف</to></translation>
+<translation><from>User email:</from><to>البريد الإلكتروني للمستخدم:</to></translation>
+<translation><from>user</from><to>المستخدم</to></translation>
+<translation><from>User is online</from><to>المستخدم متصل</to></translation>
+<translation><from>User Name</from><to>اسم المستخدم</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>المستخدم طلب الانضمام إلى <n.location/></to></translation>
+<translation><from>users</from><to>المستخدمون</to></translation>
+<translation><from>Users</from><to>المستخدمون</to></translation>
+<translation><from>Users & Groups</from><to>المستخدمون والمجموعات</to></translation>
+<translation><from>Users that completed the registration process</from><to>المستخدمون الذين أكملوا عملية التسجيل</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>استخدم وسوماً مثل <n.example1/> أو <n.example2/> لإنشاء عناوين ثانوية.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>استخدم الخيارات أدناه لتعيين معايير البحث بدقة.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>استخدم <n.tag_names/> لتضمين محتوى من مواقع أخرى.</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>عرض جميع الرسائل تحت هذا المنتدى الفرعي</to></translation>
+<translation><from>view</from><to>عرض</to></translation>
+<translation><from>View mailing list website</from><to>عرض موقع القائمة البريدية</to></translation>
+<translation><from>View message</from><to>عرض الرسالة</to></translation>
+<translation><from>View more</from><to>عرض المزيد</to></translation>
+<translation><from>views</from><to>طرق العرض</to></translation>
+<translation><from>Views</from><to>طرق العرض</to></translation>
+<translation><from>Violent or repulsive content</from><to>محتوى عنيف أو منفر</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>زيارة <n.app/> في:</to></translation>
+<translation><from>visit <t.url/></from><to>زيارة <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>أعددنا صفحة توضح <n.unsubscription_instructions_link.>كيفية إلغاء الاشتراك في هذا الأرشيف</n.unsubscription_instructions_link.>.</to></translation>
+<translation><from>We will review your report soon.</from><to>سنقوم بمراجعة تقريرك قريباً.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>أي المجموعات تسمح باستعراض الأعضاء</to></translation>
+<translation><from>Who can ban/unban users</from><to>من يمكنه حجب وإلغاء حجب المستخدمين</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>من يمكن إسناد المواضيع إليه (في مجموعات العمل فقط)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>من يمكنه تغيير تاريخ ووقت الرسائل</to></translation>
+<translation><from>Who can create new topics under this application</from><to>من يمكنه إنشاء مواضيع جديدة في هذا التطبيق</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>من يمكنه إنشاء تطبيقات فرعية (مثلاً، منتديات فرعية، فئات فرعية، إلخ)</to></translation>
+<translation><from>Who can edit any content, both applications and posts. Note: Please only use this feature in extreme circumstances. Most users will not like having their posts edited by someone else.</from><to>من يمكنه تحرير أي محتوى، سواء تطبيقات أو مشاركات. ملاحظة: استخدم هذه الميزة في الحالات القصوى. معظم المستخدمين سيزعجهم تعديل مشاركاتهم بواسطة شخص آخر.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>من يمكنه تحرير التطبيقات (مثلاً، تغيير الاسم، الوصف، إلخ)</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>من يمكنه إغلاق وفتح المواضيع في هذا التطبيق</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>من يمكنه إدارة المشتركين في هذا التطبيق</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>من يمكنه نقل الرسائل إلى أماكن أخرى (مثلاً، إلى مواضيع أو منتديات فرعية أخرى)</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>من يمكنه تثبيت وإزالة تثبيت المواضيع في هذا التطبيق</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>من يمكنه نشر أي محتوى دون قيود (بما في ذلك شفرات javascript، tags &lt;object&gt; و &lt;style&gt;، إلخ). <b>تحذير أمني</b>: اسمح بهذا الخيار فقط للمستخدين الذين تثق بهم حقاً.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>من يمكنه الرد على الرسائل المنشورة في هذا التطبيق</to></translation>
+<translation><from>Who can view this application and its contents</from><to>من يمكنه استعراض هذا التطبيق ومحتوياته</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>عند اشتراكك، سيتم إرسال أي تحديثات إلى عنوان بريدك الإلكتروني مباشرة ويمكنك الرد عليها للمشاركة في النقاش. اشتراكك يعمل كقائمة بريدية.</to></translation>
+<translation><from>Write Your First Headline</from><to>اكتب عنوانك الأول</to></translation>
+<translation><from>Write Your First Post</from><to>اكتب مشاركتك الأولى</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>نعم، احذف <n.location/> للأبد</to></translation>
+<translation><from>Yes, unsubscribe now</from><to>نعم، ألغِ الاشتراك الآن</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>أنت مشترك في <n.location/> مسبقاً.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>أنت غير مشترك في <n.location/>.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>يمكنك كذلك <n.manage_banned_users_link.>إدارة المستخدين المحجوبين</n.manage_banned_users_link.> في <n.location/>.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>يمكنك كذلك <n.root_node.change_permissions_link.>تغيير الصلاحيات</n.root_node.change_permissions_link.> للمجموعات أدناه.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>يمكنك كذلك الترويج ل<n.this.app/> بإرسال الرابط إلى أصدقائك، أو تضمينه في موقعك، أو التحدث عنه في منتديات أخرى.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>لا يمكنك نقل هذه المشاركة إلى تلك الوجهة لأنها لا تسمح لمستخدمين مجهولين بالمشاركة.</to></translation>
+<translation><from>You Cannot Post Here</from><to>لا يمكنك النشر هنا</to></translation>
+<translation><from>(you can reply by email)</from><to>(يمكنك الرد عبر البريد الإلكتروني)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>لا يمكنك نقل المشاركة إلى أي مكان.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>لا يمكنك النشر هنا، ولكن يمكنك النشر في أماكن أخرى.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>يمكنك محاولة <n.register_link.>التسجيل مجدداً</n.register_link.> أو الاتصال بالدعم <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>يمكنك استخدام النموذج أدناه لتقديم طلب إلى المشرفين.</to></translation>
+<translation><from>You have already been registered.</from><to>أنت مسجل مسبقاً.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>أنت مدعو إلى الاشتراك في <n.location/> الموجود هنا:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>تم تسجيلك في <n.subject/>.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>تم إلغاء اشتراكك في <n.location/></to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>لقد قمت بنشر العديد من المشاركات في مدة قصيرة. فضلاً حاول مجدداً في وقت لاحق.</to></translation>
+<translation><from>You logged out</from><to>لقد قمت بتسجيل خروجك</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>ربما تحتاج إلى <n.mailing_list_options_link.>الاشتراك في هذه القائمة البريدية</n.mailing_list_options_link.> ليتم قبول رسالتك.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>يمكنك <n.page_node.unauthorized_link.>طلب الإذن بالنشر</n.page_node.unauthorized_link.> هنا أو الاتصال بـ <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> إذا كان لديك أي أسئلة.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>يجب أن توافق على شروط الاستخدام.</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>يجب أن تدخل عنوان بريد إلكتروني صحيح.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>يجب أن تدخل عنوان موقع إلكتروني صحيحاً لهذه القائمة البريدية.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>يجب أن تملأ جميع الحقول أدناه.</to></translation>
+<translation><from>You must login to view this page.</from><to>يجب أن تسجل دخولك ليمكنك استعراض هذه الصفحة.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>يجب أن تسجل دخولك ليمكنك استعراض <n.subject/>.</to></translation>
+<translation><from>You must login to your account.</from><to>يجب أن تسجل الدخول إلى حسابك.</to></translation>
+<translation><from>You must provide a user name.</from><to>يجب أن تحدد اسم مستخدم.</to></translation>
+<translation><from>You're not a subscriber</from><to>أنت لست مشتركاً</to></translation>
+<translation><from>Your Name</from><to>اسمك</to></translation>
+<translation><from>Your request has been successfully sent.</from><to>تم إرسال طلبك بنجاح.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>تم حفظ اشتراكك بنجاح.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>تمت إزالة اشتراكك في <n.location/>. إذا كان ذلك عن طريق الخطأ، يمكنك معاودة الاشتراك باتباع الرابط أدناه:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>تمت إزالة اشتراكك في <n.location/> بنجاح.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to>تم إنشاء <n.this.app/> بنجاح.</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>ستحتاج إلى تصريح بنشر مواضيع جديدة في <n.location/>، لذلك فبالإضافة إلى تسجيل حسابك، يجب أن تحصل على موافقة المشرفين.</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>ستتلقى رسالة بريد إلكتروني كلما نشرت رسالة جديدة في هذا الموضوع.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>ستتلقى رسالة بريد إلكتروني تحوي رابط تفعيل حسابك.</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>اشتراكك</to></translation>
+<translation><from>edit</from><to>تحرير</to></translation>
+<translation><from>Remove ads</from><to>إزالة الإعلانات</to></translation>
+<translation><from>View profile of <t.author/></from><to>مشاهدة الملف الخاص ب<n.author/></to></translation>
+<translation><from>Description</from><to>الوصف</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>فيديو من Youtube و Vimeo و LiveLeak.</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>المستخدمون المحجوبون في <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>بادئة الموضوع</to></translation>
+
+#NEW 2
+<translation><from>Change title and meta tags</from><to>تغير العنوان والوسوم التعريفية</to></translation>
+<translation><from>Here you can customize the title and meta tags of the <t.location/> page.</from><to>يمكنك هنا تخصيص العنوان والوسوم التعريفية ل <n.location/>.</to></translation>
+<translation><from>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</from><to>المعلومات التعريفية في الأسفل تساعد على تحسين هذه الصفحة لمحركات البحث (Google، Yahoo!، ..الخ)</to></translation>
+<translation><from>Use custom values</from><to>استخدم قيماً مخصصة</to></translation>
+<translation><from>Page Title</from><to>عنوان الصفحة</to></translation>
+<translation><from>Enter a context-rich title that clearly identifies this page.</from><to>أدخل عنواناً معبراً يمكن التعرف من خلاله على هذه الصفحة بوضوح.</to></translation>
+<translation><from>The title should ideally be less than 70 characters in length.</from><to>يجب أن يكون طول العنوان أقل من 70 حرفاً.</to></translation>
+<translation><from>Meta Description</from><to>البيانات التعريفية: الوصف</to></translation>
+<translation><from>Enter a brief and concise summary of your page's content.</from><to>أدخل ملخصاً مختصراً لمحتوى صفحتك.</to></translation>
+<translation><from>Limit your description to 155 characters or 170 characters at most.</from><to>حدد الوصف ب 155 حرفاً أو 170 حرفاً على الأكثر.</to></translation>
+<translation><from>Mailing List Archive</from><to>أرشيف القائمة البريدية</to></translation>
+<translation><from>Filter: priority <t.priority/></from><to>ترشيح: الأولوية <n.priority/></to></translation>
+<translation><from>Filter: assignee <t.author/></from><to>ترشيح: الكاتب <n.author/></to></translation>
+<translation><from>Use Google Analytics</from><to>استخدم Google Analytics</to></translation>
+<translation><from>Analytics Account ID:</from><to>معرف حساب Analytics</to></translation>
+<translation><from>(e.g., UA-12345-0)</from><to>(مثال: UA-12345-0)</to></translation>
+<translation><from>Enter a valid analytics account ID.</from><to>أدخل معرف حساب تحليلات صحيح.</to></translation>
+<translation><from>Here you can use Google Analytics to measure the success of your app.</from><to>يمكنك هنا استخدام Google Analytics لقياس مدى نجاح تطبيقك.</to></translation>
+<translation><from>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</from><to>أدخل أدناه معرف حساب التحليلات الخاص بك والذي سيمكنك من متابعة عدد الزيارات والزوار وإحصائيات مهمة أخرى عن الحركة على موقعك الإلكتروني.</to></translation>
+
+<translation><from>Digest Email</from><to>ملخص البريد الإلكتروني</to></translation>
+<translation><from>on <t.date/></from><to>في <n.date/></to></translation>
+<translation><from>DO NOT REPLY TO THIS EMAIL</from><to>لا تقم بالرد على هذا البريد الإلكتروني</to></translation>
+<translation><from>Replies sent to this address are not read or processed.</from><to>الرسائل المرسلة على هذا العنوان لا تقرأ ولا تعالج.</to></translation>
+<translation><from>If you want to respond to a post for which you received this email, please go to the website: <t.url/></from><to>إذا أردت إجابة منشور وصلك عبر هذه الرسالة، الرجاء زيارة الموقع الإلكتروني: <n.url/></to></translation>
+<translation><from>new post</from><to>منشور جديد</to></translation><!-- usage example: "1 new post"-->
+<translation><from>new posts</from><to>منشورات جديدة</to></translation><!-- usage example: "2 new posts"-->
+
+<translation><from>New registered user in <t.location/>!</from><to>مستخدم جديد مسجل في <n.location/>!</to></translation>
+<translation><from>User profile</from><to>ملف المستخدم</to></translation>
+<translation><from>New user:</from><to>مستخدم جديد:</to></translation>
+
+<translation><from><n.author/> wrote</from><to><n.author/> كتب</to></translation>
+<translation><from>You still have <t.number/> day(s) left without advertising</from><to>لديك <n.number/> أيام دون إعلانات.</to></translation>
+<translation><from>(Only administrators can see this message)</from><to>(يمكن للمشرفين فقط رؤية هذه الرسالة)</to></translation>
+
+<translation><from>Some private items have been omitted because you don't have permission to view them.</from><to>تم حذف بعض العناصر الخاصة لأنك غير مصرح لك باستعراضها.</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Submit". We will email you a link to reset your password.</from><to>فضلاً أدخل عنوان البريد الإلكتروني الذي استخدمته لتسجيل حسابك ثم انقر "تسليم". سنرسل إليك رسالة إلكترونية تحوي رابط إعادة تعيين كلمة السر.</to></translation>
+<translation><from>Submit</from><to>تسليم</to></translation>
+<translation><from>Password Reset Sent</from><to>تم إرسال إعادة تعيين كلمة السر</to></translation>
+<translation><from>We have sent you a link to reset your password. Please check your email now. If you don't receive the instructions in a few minutes, check your spam folder or try to resend the request.</from><to>لقد أرسلنا إليك رابطاً يمكنك من إعادة تعيين كلمة السر. فضلاً تحقق من استلامها الآن. إذا لم تصلك التعليمات خلال بضع دقائق، تفحص مجلد الرسائل غير المرغوب فيها (spam) أو أعد إرسال الطلب مرة أخرى.</to></translation>
+<translation><from>Reset your password / <t.location/></from><to>إعادة تعيين كلمة السر / <n.location/></to></translation>
+<translation><from>We received a request to reset your password in <t.location/>.</from><to>لقد استلمنا طلباً بإعادة تعيين كلمة سرك الخاصة بـ <n.location/>.</to></translation>
+<translation><from>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</from><to>إذا كنت تريد إعادة تعيين كلمة السر، انقر الرابط أدناه (أو انسخة والصق العنوان في متصفحك):</to></translation>
+<translation><from>If you don't want to reset your password, please ignore this message. Your password will not be reset.</from><to>إذا لم تكن تريد إعادة تعيين كلمة السر، تجاهل هذه الرسالة، ولن يتم تغيير كلمة السر.</to></translation>
+
+<translation><from><b>IMPORTANT:</b> If you use this option, messages posted in the archive will NOT be sent to the mailing list.</from><to><b>تنبيه هام:</b> عند استخدام هذا الخيار، فإن الرسائل التي تنشر عبر هذا الأرشيف لن يتم إرسالها إلى القائمة البريدية.</to></translation>
+<translation><from>Message Preview</from><to>استعراض الرسالة</to></translation>
+<translation><from>Your changes will not be sent to the mailing list.</from><to>لن يتم إرسال تعديلاتك إلى القائمة البريدية.</to></translation>
+<translation><from>If you want others in the mailing list to know of your changes, please compose a new message or reply to your original message.</from><to>إذا أردت أن تصل تعديلاتك إلى الجميع، فضلاً اكتب رسالة جدية أو اكتب رداً على رسالتك الأصلية.</to></translation>
+
+<translation><from>Poll</from><to>استطلاع رأي</to></translation>
+<translation><from>Add new poll</from><to>إضافة استطلاع جديد</to></translation>
+<translation><from>Question:</from><to>السؤال:</to></translation>
+<translation><from>Answers:</from><to>الإجابات:</to></translation>
+<translation><from>Add new answer</from><to>إضافة جواب جديد</to></translation>
+<translation><from>1 vote</from><to>صوت واحد</to></translation>
+<translation><from><t.number/> votes</from><to><n.number/> أصوات</to></translation>
+<translation><from>Total votes:</from><to>مجموع الأصوات:</to></translation>
+<translation><from>Vote</from><to>تصويت</to></translation><!-- verb -->
+<translation><from>Your vote has been submitted.</from><to>تم تسليم تصويتك.</to></translation>
+<translation><from>This poll is closed.</from><to>هذا الاستطلاع مغلق.</to></translation>
+<translation><from>This poll ends on <t.date/>.</from><to>ينتهي هذا الاستطلاع في <n.date/></to></translation>
+<translation><from>This poll ended on <t.date/>.</from><to>انتهى هذا الاستطلاع في <n.date/></to></translation>
+<translation><from>Results will be shown only after poll has ended.</from><to>ستظهر النتائج بعد انتهاء الاستطلاع.</to></translation>
+<translation><from>You have to vote before you can see the results.</from><to>يجب أن تصوت قبل أن تتمكن من رؤية النتائج.</to></translation>
+<translation><from>You cannot change your vote after voting.</from><to>لا يمكنك تغيير صوتك.</to></translation>
+<translation><from>You can select up to <t.number/> options.</from><to>يمكنك اختيار <n.number/> خيارات على الأكثر.</to></translation>
+<translation><from>Please select no more than <t.number/> options.</from><to>فضلاً اختر <n.number/> خيارات على الأكثر.</to></translation>
+<translation><from>Please select at least one option.</from><to>فضلاً اختر خياراً واحداً على الأقل.</to></translation>
+<translation><from>Remove Poll</from><to>حذف الاستطلاع</to></translation>
+<translation><from>Invalid poll parameters.</from><to>خيارات الاستطلاع غير صحيحة.</to></translation>
+<translation><from>Poll duration must be a non-negative integer or blank.</from><to>مدة الاستطلاع يجب أن تكون عدداً صحيحاً موجباً أو فراغاً.</to></translation>
+<translation><from>Poll maximum allowed choices must be a non-negative integer or blank.</from><to>العدد الأقصى للاختيارات المسموح بها يجب أن يكون عدداً صحيحاً موجباً أو فراغاً.</to></translation>
+<translation><from>Delete this poll, including all votes?</from><to>حذف هذا الاستطلاع وجميع الأصوات؟</to></translation>
+<translation><from>Poll has been deleted.</from><to>تم حذف الاستطلاع.</to></translation>
+<translation><from>Who can create polls.</from><to>من يمكنه إنشاء استطلاعات رأي.</to></translation>
+<translation><from>Allow vote changes</from><to>السماح بتغيير الأصوات</to></translation>
+<translation><from>Allow viewing results before end date (poll creators can always view the results)</from><to>السماح باستعراض النتائج قبل تاريخ الانتهاء (يمكن لمنشئي الاستطلاع استعراض النتائج دائماً)</to></translation>
+<translation><from>Allow viewing results before vote</from><to>السماح باستعراض النتائج قبل التصويت</to></translation>
+<translation><from>Multiple selections allowed:</from><to>السماح باختيارات متعددة:</to></translation>
+<translation><from>Poll ends after <t.number/> days (leave blank for unlimited).</from><to>سينتهي الاستطلاع بعد <n.number/> أيام (اتركه فارغاً لاختيار فترة غير محدودة).</to></translation>
+<translation><from>Login to vote</from><to>سجل دخولك لتتمكن من التصويت</to></translation>
+<translation><from>This message has a poll</from><to>تحوي هذه الرسالة استطلاعاً للرأي</to></translation>
+<translation><from>Visit the link below if you want to participate:</from><to>زر الرابط أدناه إذا أردت المشاركة:</to></translation>
+
+<translation><from>Current length: <t.number/> characters</from><to>الطول الحالي: <n.number/> حرفاً</to></translation>
+<translation><from>Edit Post</from><to>تحرير المشاركة</to></translation>
+<translation><from><t.location/> - Your credits are running out</from><to><n.location/> - يوشك رصيدك على الانتهاء.</to></translation>
+<translation><from><t.location/> is running out of ad-free credits.</from><to>يوشك رصيد إزالة الإعلانات لـ<n.location/> على الانتهاء.</to></translation>
+<translation><from>If you want to buy more credits, visit:</from><to>إن كنت ترغب بشراء رصيد إضافي، زر:</to></translation>
+<translation><from>only in this topic</from><to>فقط في هذا الموضوع</to></translation>
+<translation><from>everywhere</from><to>في كل مكان</to></translation>
+
+<translation><from>If you want to invite subscribers, please request this feature in the <n.support_link/> forum.</from><to>إذا أردت دعوة مشتركين، فضلاً اطلب هذه الميزة في منتدى الدعم <n.support_link/>.</to></translation>
+<translation><from>We can install this module for you, but the Nabble team must approve this request to prevent abuse and spam.</from><to>يمكننا تنصيب هذه الوحدة لك، ولكن يجب أن يقر فريق Nabble هذا الطلب لتفادي إساءة الاستخدام.</to></translation>
+
+<translation><from>Edit Signature</from><to>تحرير التوقيع</to></translation>
+<translation><from>Current Signature</from><to>التوقيع الحالي</to></translation>
+<translation><from>Save Signature</from><to>حفظ التوقيع</to></translation>
+<translation><from>Signature is in HTML format</from><to>التوقيع بصيغة HTML</to></translation>
+
+<translation><from>Download backup</from><to>تحميل نسخة احتياطية</to></translation>
+<translation><from>Backup</from><to>نسخة احتياطية</to></translation>
+<translation><from>Here you can download a backup of <t.location/>.</from><to>يمكنك هنا تحميل نسخة احتياطية من <n.location/>.</to></translation>
+<translation><from>When you press the button below, the system will start the generation of the backup file and this may take some minutes to finish. You will receive an email with a link to download the file when it is ready.</from><to>عند ضغطك على الزر أدناه، سيبدأ النظام بإنشاء ملف النسخة الاحتياطية مما قد يستغرق عدة دقائق. ستتلقى رسالة بريد إلكتروني تحوي رابط تحميل الملف حال توفره.</to></translation>
+<translation><from>Generate backup file</from><to>إنشاء ملف نسخة احتياطية</to></translation>
+<translation><from>The system is generating the backup now. When it is finished, a link to the backup file will be emailed to you.</from><to>يقوم النظام بإنشاء ملف النسخة الاحتياطية الآن. سيتم إرسال رابط للملف إليك عبر البريد الإلكتروني حال اكتماله.</to></translation>
+<translation><from>The backup file is generated with the <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a> tool, which is an open source project in Java. You should visit the project website if you want to restore your backup into a Postgresql database.</from><to>يتم إنشاء ملف النسخة الاحتياطية باستخدام <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a>، وهو أداة متاحة المصدر مكتوبة بلغة Java. عليك زيارة موقع مشروع هذه الأداة إذا أردت استرجاع نسختك الاحتياطية إلى قاعدة بيانات Postgresql.</to></translation>
+<translation><from>Backup of <t.location/></from><to>نسخة احتياطية من <n.location/></to></translation>
+<translation><from>Here is your backup file:</from><to>ملف النسخة الاحتياطية:</to></translation>
+<translation><from><t.location/> has been deleted</from><to>تم حذف <n.location/></to></translation>
+<translation><from>Your Nabble site "<t.location/>" has been deleted.</from><to>تم حذف موقعك "<n.location/>" من Nabble.</to></translation>
+<translation><from>You can download a backup of this site from the link below. Nabble will try to keep this backup available for a few months, but this is not guaranteed. If this content is important to you, save this copy as soon as possible.</from><to>يمكنك تنزيل نسخة احتياطية من هذا الموقع باستخدام الرابط أدناه. سيحاول Nabble الإبقاء على هذا الملف متاحاً لعدة أشهر، ولكن ذلك ليس مضموناً. إذا كان هذا المحتوى مهماً بالنسبة إليك، احفظ هذه النسخة في أقرب فرصة.</to></translation>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_ch_si.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,672 @@
+## Chinese-simplified
+
+## MONTHS ##
+
+<translation><from>January</from><to>1月</to></translation>
+<translation><from>February</from><to>2月</to></translation>
+<translation><from>March</from><to>3月</to></translation>
+<translation><from>April</from><to>4月</to></translation>
+<translation><from>May</from><to>5月</to></translation>
+<translation><from>June</from><to>6月</to></translation>
+<translation><from>July</from><to>7月</to></translation>
+<translation><from>August</from><to>8月</to></translation>
+<translation><from>September</from><to>9月</to></translation>
+<translation><from>October</from><to>10月</to></translation>
+<translation><from>November</from><to>11月</to></translation>
+<translation><from>December</from><to>12月</to></translation>
+
+<translation><from>Jan</from><to>1月</to></translation>
+<translation><from>Feb</from><to>2月</to></translation>
+<translation><from>Mar</from><to>3月</to></translation>
+<translation><from>Apr</from><to>4月</to></translation>
+<!--translation><from>May</from><to>5月</to></translation-->
+<translation><from>Jun</from><to>6月</to></translation>
+<translation><from>Jul</from><to>7月</to></translation>
+<translation><from>Aug</from><to>8月</to></translation>
+<translation><from>Sep</from><to>9月</to></translation>
+<translation><from>Oct</from><to>10月</to></translation>
+<translation><from>Nov</from><to>11月</to></translation>
+<translation><from>Dec</from><to>12月</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>对此功能的任何不正当使用均属于违反使用条款的行为。</to></translation>
+<translation><from>Access Request</from><to>申请加入</to></translation>
+<translation><from>Account settings</from><to>账户设置</to></translation>
+<translation><from>Account Settings</from><to>账户设置</to></translation>
+<translation><from>Action</from><to>动作</to></translation>
+<translation><from>Add a link to another page</from><to>添加其它页面的链接</to></translation>
+<translation><from>Add a new comment</from><to>添加一条新评论</to></translation>
+<translation><from>Add a new group</from><to>添加一个新用户组</to></translation>
+<translation><from>Add an image to your post</from><to>在回复中添加图片</to></translation>
+<translation><from>Add another address</from><to>添加另一个地址</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>添加不包含编码(例如:计算机代码)的内容</to></translation>
+<translation><from>Adding Sub-Headers</from><to>添加子标题</to></translation>
+<translation><from>Add New Group</from><to>添加一个新用户组</to></translation>
+<translation><from>Add / Remove Groups</from><to>添加/移除用户组</to></translation>
+<translation><from>Add smileys and funny animations</from><to>添加笑脸和有趣的动画</to></translation>
+<translation><from>Add Subscribers</from><to>添加订阅者</to></translation>
+<translation><from>Add this item to your favorites list</from><to>将此项添加到您的收藏列表</to></translation>
+<translation><from>Administrator</from><to>管理员</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity,  other sexual content.</from><to>成人内容,暴露的性行为,裸体,以及其它色情内容</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>成人打斗,人身攻击,青年暴力,动物虐待或者宣扬恐怖主义</to></translation>
+<translation><from>Advanced Search</from><to>高级搜索</to></translation>
+<translation><from>Advanced Settings</from><to>高级设置</to></translation>
+<translation><from>Advertisement</from><to>广告</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>当有人回复主题时向我的电子邮箱发送提示邮件</to></translation>
+<translation><from>All</from><to>所有</to></translation>
+<translation><from>all of the words:</from><to>所有词语</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>所有<n.author/>的帖子已经被全部成功移除。</to></translation>
+<translation><from>All posts</from><to>所有帖子</to></translation>
+<translation><from>All users belong to this group</from><to>所有属于本组的用户</to></translation>
+<translation><from>All Users</from><to>所有用户</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>所有注册到<n.location/>的用户。这些用户已经确认了他们的邮箱地址,能够登录论坛系统了。</to></translation>
+<translation><from>Already Subscribed</from><to>已订阅</to></translation>
+<translation><from>An email has been sent to you.</from><to>一封邮件已经发到了您的邮箱。</to></translation>
+<translation><from>Anonymous</from><to>匿名</to></translation>
+<translation><from>anonymous user</from><to>匿名用户</to></translation>
+<translation><from>anonymous users</from><to>匿名用户</to></translation>
+<translation><from>Any message part contains</from><to>该部分含有的任何信息</to></translation>
+<translation><from>Application</from><to>应用</to></translation>
+<translation><from>Apps</from><to>申请</to></translation>
+<translation><from>Assignee</from><to>受让人</to></translation>
+<translation><from>Assign</from><to>分配</to></translation>
+<translation><from>Assignment</from><to>分配</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>应您的要求,在<n.app/>用于发布新话题的邮箱地址是:</to></translation>
+<translation><from>at least one of the words:</from><to>至少其中一个单词:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to><n.location/>的源</to></translation>
+<translation><from>at priority</from><to>优先</to></translation>
+<translation><from>Authorized Users Only</from><to>仅授权用户</to></translation>
+<translation><from>Author name</from><to>作者姓名</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>避免使用诸如“谢谢”,“好”......等过短回复。如果需要,您可以<n.page_node.reply_to_author_link.>发送一封私人邮件</n.page_node.reply_to_author_link.>。</to></translation>
+<translation><from>Banned User</from><to>禁止用户</to></translation>
+<translation><from>Ban this user</from><to>禁止该用户</to></translation>
+<translation><from>Ban User</from><to>禁止用户</to></translation>
+<translation><from>Before deleting this archive...</from><to>在删除此文档前......</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>您可以在下面管理用户组和用户。您可以对他们进行复制和粘贴操作,以将他们从一个用户组移动到另一个用户组里面。</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>重要</b>: Nabble论坛会向下列所有的电子邮箱地址发送一封邀请信。用户必须点击确认链接来确认他们的订阅。</to></translation>
+<translation><from><b>IMPORTANT</b>: This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>重要</b>:该订阅与订阅邮箱列表无关。总的来说,您将会订阅论坛的存档,而不是邮箱列表本身。存档订阅不能保证您的信息会被邮箱列表所接受。</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>重要</b>: 您必须将该存档订阅给邮箱列表中的人,以将其发挥作用。</to></translation>
+<translation><from>blog</from><to>博客</to></translation>
+<translation><from>Blog</from><to>博客</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>提示</b>: 由于您是管理员,您可以 <n.page_node.change_permissions_link.>改变<n.location/></n.page_node.change_permissions_link.>的权限,确保您可以在这里创建新的主题。</to></translation>
+<translation><from>board</from><to>板块</to></translation>
+<translation><from>Board</from><to>板块</to></translation>
+<translation><from>Bold</from><to>加粗</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>警告:</b>当前搜索栏正在维护中,搜索结果可能不完整。</to></translation>
+<translation><from>by <t.author/></from><to>由 <n.author/> 发表</to></translation>
+<translation><from>Cancel</from><to>取消</to></translation>
+<translation><from>category</from><to>分类</to></translation>
+<translation><from>Category</from><to>分类</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>小心!该操作不能恢复。</to></translation>
+<translation><from>Change appearance</from><to>更改外观</to></translation>
+<translation><from>Change application type</from><to>更改应用样式</to></translation>
+<translation><from>Change Application Type</from><to>更改应用</to></translation>
+<translation><from>Change code image</from><to>更改代码图像</to></translation>
+<translation><from>Change domain name</from><to>更改域名</to></translation>
+<translation><from>Change language</from><to>更改语言</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>更改或移除您的头像</to></translation>
+<translation><from>Change parent</from><to>更改父论坛</to></translation>
+<translation><from>Change permissions</from><to>更改权限</to></translation>
+<translation><from>Change Permissions</from><to>更改权限</to></translation>
+<translation><from>Change post date</from><to>更改发帖日期</to></translation>
+<translation><from>Change Post Date</from><to>更改发帖日期</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>更改显示在您帖子底部的签名。</to></translation>
+<translation><from>Change User Groups</from><to>更改用户组</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>更改自定义外观和其它设置。</to></translation>
+<translation><from>Change Your Picture</from><to>更改您的图片</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>更改您注册的电子邮箱地址,密码,和用户名。</to></translation>
+<translation><from>Child abuse</from><to>虐待儿童</to></translation>
+<translation><from>Choose a subcategory</from><to>选择一个子板块</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>选择发布您帖子的子板块</to></translation>
+<translation><from>Choose the best offer for you</from><to>选择最适合您的选项</to></translation>
+<translation><from>Classic</from><to>经典版</to></translation>
+<translation><from>Clear Log</from><to>清除日志</to></translation>
+<translation><from>Click for more options</from><to>点击以获取更多选项</to></translation>
+<translation><from>click here</from><to>点击这里</to></translation>
+<translation><from>Click here to make your first post</from><to>点击这里发布您的第一帖</to></translation>
+<translation><from>Click to filter</from><to>点击过滤</to></translation>
+<translation><from>Close</from><to>关闭</to></translation>
+<translation><from>Close this message</from><to>关闭这个消息</to></translation>
+<translation><from>comment</from><to>条评论</to></translation>
+<translation><from>Comment</from><to>条评论</to></translation>
+<translation><from>comments</from><to>条评论</to></translation>
+<translation><from>Comments</from><to>条评论</to></translation>
+<translation><from>Confirm Password</from><to>确认密码</to></translation>
+<translation><from>Confirm Subscription</from><to>确认订阅</to></translation>
+<translation><from>Congratulations!</from><to>祝贺!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>祝贺您有了新的<n.app/>!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>含有宣扬仇恨,暴力,虐待弱者,恃强凌弱,种族歧视和反人类反社会的内容。</to></translation>
+<translation><from>CONTENTS DELETED</from><to>内容已删除</to></translation>
+<translation><from>Continue</from><to>继续</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>版权、个人隐私及其它法律权利受到侵犯</to></translation>
+<translation><from>Count</from><to>数</to></translation>
+<translation><from>Created by <t.author/></from><to>由<n.author/>创建</to></translation>
+<translation><from>Create new <t.element/></from><to>创建一个新<n.element/></to></translation>
+<translation><from>Create <t.element/></from><to>创建<n.element/></to></translation>
+<translation><from>Current Credits</from><to>当前积分</to></translation>
+<translation><from>Currently Nabble supports</from><to>当前Nabble支持</to></translation>
+<translation><from>Current Subscribers</from><to>当前订阅者</to></translation>
+<translation><from>Daily digest</from><to>每日文摘</to></translation>
+<translation><from>Data successfully saved</from><to>成功保存数据</to></translation>
+<translation><from>Date</from><to>日期</to></translation>
+<translation><from>days</from><to>天</to></translation>
+<translation><from>Dear <t.name/>,</from><to>亲爱的<n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>亲爱的用户,</to></translation>
+<translation><from>Default</from><to>默认</to></translation>
+<translation><from>Delete all posts from this user</from><to>删除该用户所有帖子</to></translation>
+<translation><from>Delete Application</from><to>删除应用</to></translation>
+<translation><from>Deleted posts</from><to>删除帖子</to></translation>
+<translation><from>Delete</from><to>删除整个论坛</to></translation>
+<translation><from>Delete this post and replies</from><to>删除帖子和回复</to></translation>
+<translation><from>Delete this post</from><to>删除该帖</to></translation>
+<translation><from>Delete this topic</from><to>删除此主题</to></translation>
+<translation><from>Description is in HTML Format</from><to>描述采用HTML格式</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>请不要重复发帖。等待几天,人们将通过电子邮件阅读您的帖子。</to></translation>
+<translation><from>Don't show this message again</from><to>不要再显示此消息</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>您确定要删除该帖吗?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>您确定要永久删除该帖以及所有回复吗?</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>您确认要永久<n.important.>删除</n.important.> <n.location/>?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>您确认要移除电子邮件列表存档设定吗?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>您确定要订阅<n.location/>吗?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>您确定要解禁该会员吗?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>您确定要取消从<n.location/>的订阅吗?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>含有毒品滥用,吸毒及吸毒工具,以及纵火,爆炸,贩卖啤酒或烈酒,香烟以及相关物品,武器,军火及其它危险品的内容。</to></translation>
+<translation><from>Edit name & description</from><to>编辑论坛名称和描述</to></translation>
+<translation><from>Edit Name & Description</from><to>编辑论坛名称和描述</to></translation>
+<translation><from>Editor</from><to>编辑</to></translation>
+<translation><from>Edit Personal Information</from><to>编辑个人信息</to></translation>
+<translation><from>Edit post</from><to>编辑帖子</to></translation>
+<translation><from>Edit Subscription</from><to>编辑订阅</to></translation>
+<translation><from>Edit Your Signature</from><to>编辑您的签名</to></translation>
+<translation><from>Email Confirmation</from><to>用电子邮件发送确认信息</to></translation>
+<translation><from>Email for <t.app/></from><to>用电子邮件发送<n.app/></to></translation>
+<translation><from>Email</from><to>电子邮箱</to></translation>
+<translation><from>Email Subscription</from><to>用电子邮件发送订阅</to></translation>
+<translation><from>Email this post to...</from><to>将此帖用电子邮件发送到......</to></translation>
+<translation><from>Embedding Contents</from><to>嵌入内容</to></translation>
+<translation><from>Embedding options</from><to>嵌入设置及选项</to></translation>
+<translation><from>Embed</from><to>嵌入</to></translation>
+<translation><from>Embed post</from><to>嵌入帖子</to></translation>
+<translation><from>Embed Tags</from><to>嵌入标签</to></translation>
+<translation><from>Embed this <t.app/></from><to>嵌入这个<n.app/></to></translation>
+<translation><from>Empty</from><to>空</to></translation>
+<translation><from>Enter a valid email address.</from><to>输入有效的电子邮箱地址</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>在下方输入您的电子邮箱地址,我们会给您发送确认信。</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>每一行输入一个电子邮箱地址或用户名:</to></translation>
+<translation><from>Enter one user per row</from><to>每一行输入一个用户名</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>输入新的<b>父主题</b>或<b>父论坛</b>的永久链接,或者不输入任何链接,使该消息成为一个独立的主题:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>输入该邮箱列表的主页,该主页能让用户得到更多信息。</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>输入该邮箱列表在主题之前所使用的前缀。在收到的电子邮件中该前将被自动移除。</to></translation>
+<translation><from>Enter your email address</from><to>输入您的邮箱地址</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>例如:johnsmith@domain.com</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>例如:listname@listdomain.com</to></translation>
+<translation><from>Examples:</from><to>例如:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>例如: ‘[列表]',‘Abc:', 等等。</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>请向管理员说明您希望进入此限制区域的原因。</to></translation>
+<translation><from>Explanation from this user:</from><to>该用户的说明:</to></translation>
+<translation><from>Extras & add-ons</from><to>附加项目</to></translation>
+<translation><from>Failed</from><to>失败</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>收藏(单击将此项从您的收藏列表移除)</to></translation>
+<translation><from>Feeds</from><to>源</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>文件上传失败: <n.file/> (por favor, envie-o novamente ou remova a tag)</to></translation>
+<translation><from>Filter by group</from><to>群组过滤</to></translation>
+<translation><from>Floating sub-forum</from><to>非固定子论坛</to></translation>
+<translation><from>Forgot Password?</from><to>忘记密码?</to></translation>
+<translation><from>Forgot your password?</from><to>忘记您的密码?</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>获取更多信息,请浏览: <n.info/></to></translation>
+<translation><from>forum</from><to>论坛</to></translation>
+<translation><from>Forum</from><to>论坛</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to><n.app/> 免费,可嵌入</to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>从现在起,您将会在电子邮箱中收到发表在<n.location/>的每个帖子。</to></translation>
+<translation><from>gallery</from><to>画廊</to></translation>
+<translation><from>Gallery</from><to>画廊</to></translation>
+<translation><from>Gambling or casino-related content</from><to>关于赌博的内容</to></translation>
+<translation><from>Go back</from><to>后退</to></translation>
+<translation><from>Go to next message</from><to>到下一条信息</to></translation>
+<translation><from>Group Name:</from><to>组名:</to></translation>
+<translation><from>Groups</from><to>用户组</to></translation>
+<translation><from>Groups of this user</from><to>该用户的用户组</to></translation>
+<translation><from>Hacking / cracking</from><to>黑客攻击/非法破解</to></translation>
+<translation><from>Harmful dangerous acts</from><to>有害危险行为</to></translation>
+<translation><from>Hateful or abusive content</from><to>含有憎恨和攻击性的内容</to></translation>
+<translation><from>Help</from><to>帮助</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>您可以在这里购买积分,它能够使您的Nabble论坛不出现广告。每个积分可以使一次对论坛页面的浏览不出现广告(计入次数包括论坛页面,主题列表页面和帖子页面,但不包括个人资料页面,登录和注册页面,以及确认页面,等等)。在您仍然有可使用积分的时候,来访者可以看到没有广告的页面。Nabble将会在您的积分用尽时在页面上粘贴广告。</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>现在您可以对用户隐藏当前邮箱列表存档。这个选项可以帮助您在Nabble的邮件订阅功能的帮助下替换您的邮箱列表服务器。</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>隐藏邮件地址(例如, user@host.com 到 <n.lt/>隐藏邮箱<n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>隐藏邮件</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>对用户隐藏邮箱列表存档</to></translation>
+<translation><from>Highest</from><to>最高级</to></translation>
+<translation><from>High</from><to>高级</to></translation>
+<translation><from>Hi <t.name/>,</from><to>Hi,<n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>如果该邮箱地址是您的,您应该<n.register_link.>用这相同的地址注册</n.register_link.>。 注册完成后,您将拥有自己的账户。</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>如果您没有收到Nabble发送的邮件,请检查您的<b>垃圾邮件</b>文件夹。</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>如果您准备发帖提问,请先搜索论坛,您的问题可能已经被回答了。</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>如果您还不是会员,您可以<n.register_link.><b>现在注册</b></n.register_link.>。</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>如果您禁止了该会员,他/她将不能够在<n.location/>中进行任何操作。</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>如果您没有要求接收这封邮件,或者不清楚为什么收到该邮件,请忽略它。这也许是别人犯的错误。</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>如果您现在不想注册,只要输入您用于发帖的邮箱地址,您的个人地址就将会被送到您的邮箱。</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>如果您已经移除了订阅,您就可以继续进行删除操作。</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>如果您知道,请选择您的邮箱列表服务器应用以及它的版本。</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>如果您不小心退出了论坛,请<n.login_link.>再次登录</n.login_link.>。</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>如果您回复了这封邮件,您的邮件信息将会被加入下面的讨论中。</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>如果您解禁该用户,他/她将能够再次在<n.location/>里发帖。</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>如果您打算改为订阅邮箱列表,<n.mailing_list_options_link.>参照该页面</n.mailing_list_options_link.>。</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>忽略X-No-Archive标题</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>我已经阅读过并且同意Nabble的<n.terms_link.>使用条款</n.terms_link.>。</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>图像未上传: <n.image/> (请重新上传或者移除标签)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>我是一位订阅者,请让我现在发帖</to></translation>
+<translation><from>Incorrect Login!</from><to>登录错误!</to></translation>
+<translation><from>Individual emails</from><to>独立邮件</to></translation>
+<translation><from>Inherit</from><to>与父论坛相同</to></translation>
+<translation><from>In Reply To</from><to>答复</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>作为对<n.author/>帖子的答复<n.parent_link.></n.parent_link.></to></translation>
+<translation><from>Insert</from><to>插入</to></translation>
+<translation><from>Insert Image</from><to>插入图片</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>除了在网页上发帖,您也可以通过发送邮件到以下地址来发布新主题:</to></translation>
+<translation><from>in <t.location/></from><to>在<n.location/>内</to></translation>
+<translation><from>Invalid Code</from><to>无效代码</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>无效邮件地址:<n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>无效天数,必须是整数。</to></translation>
+<translation><from>invisible user</from><to>隐身用户</to></translation>
+<translation><from>invisible users</from><to>隐身用户</to></translation>
+<translation><from>Invite Subscribers</from><to>邀请订阅者</to></translation>
+<translation><from>is:</from><to>是:</to></translation>
+<translation><from>is not:</from><to>不是:</to></translation>
+<translation><from>is within the last:</from><to>包含在最近:</to></translation>
+<translation><from>Italic</from><to>斜体</to></translation>
+<translation><from>item</from><to>项目</to></translation>
+<translation><from>items</from><to>项目</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>删除项目<b>不可</b>恢复。</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>只要将上方网页提供的代码粘贴这些标签之间,您就可以发布了。</to></translation>
+<translation><from>Last Post</from><to>最后回复</to></translation>
+<translation><from>Learn more</from><to>了解更多</to></translation>
+<translation><from>Leave a comment</from><to>发表评论</to></translation>
+<translation><from>Link</from><to>链接</to></translation>
+<translation><from>Link to <t.location/></from><to>链接到<n.location/></to></translation>
+<translation><from>List</from><to>列表</to></translation>
+<translation><from>List of Subcategories</from><to>子版块列表</to></translation>
+<translation><from>List Server</from><to>列表服务器</to></translation>
+<translation><from>List View</from><to>列表显示</to></translation>
+<translation><from>Loading...</from><to>载入中......</to></translation>
+<translation><from>Location</from><to>位置</to></translation>
+<translation><from>Locked</from><to>锁定</to></translation>
+<translation><from>Lock topic</from><to>锁定主题</to></translation>
+<translation><from>Login</from><to>登录</to></translation>
+<translation><from>Log is empty</from><to>空日志</to></translation>
+<translation><from>Log out</from><to>退出</to></translation>
+<translation><from>Lowest</from><to>最低的</to></translation>
+<translation><from>Low</from><to>低</to></translation>
+<translation><from>Mailing List Address</from><to>邮箱地址列表</to></translation>
+<translation><from>Mailing list archive settings</from><to>邮箱地址存档设置</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>邮箱地址存档设置</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>邮箱地址订阅提醒</to></translation>
+<translation><from>Mailing List Website</from><to>邮箱地址网页</to></translation>
+<translation><from>Main Page</from><to>主页</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>确保您使用和在填入注册请求信息时相同的浏览器。</to></translation>
+<translation><from>Manage banned users</from><to>管理被禁止用户</to></translation>
+<translation><from>Manage pinned topics</from><to>管理置顶主题</to></translation>
+<translation><from>Manage subscribers</from><to>管理订阅</to></translation>
+<translation><from>Manage Subscribers</from><to>管理订阅</to></translation>
+<translation><from>Manage <t.items/></from><to>管理<n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>管理用户和用户组</to></translation>
+<translation><from>Manage Users & Groups</from><to>管理用户和用户组</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>大量的广告,主旨不明的文章,欺诈信息。</to></translation>
+<translation><from>max. 80 characters</from><to>最多80个字符</to></translation>
+<translation><from>Message date</from><to>信息日期</to></translation>
+<translation><from>message</from><to>内容</to></translation>
+<translation><from>Message</from><to>内容</to></translation>
+<translation><from>Message is in HTML Format</from><to>用HTML格式编辑帖子</to></translation>
+<translation><from>messages</from><to>条帖子</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>此处发表的帖子将会被发送到这个邮箱列表中。</to></translation>
+<translation><from>Message subject contains</from><to>帖子主题包含</to></translation>
+<translation><from>Message text contains</from><to>帖子内容包含</to></translation>
+<translation><from>Mixed</from><to>混合</to></translation>
+<translation><from>Modified</from><to>自定义</to></translation>
+<translation><from>Archives</from><to>存档</to></translation>
+<translation><from>More Categories</from><to>更多类别</to></translation>
+<translation><from>More</from><to>更多</to></translation>
+<translation><from>more help</from><to>更多帮助</to></translation>
+<translation><from>more options</from><to>更多选项</to></translation>
+<translation><from>Move post</from><to>移动帖子</to></translation>
+<translation><from>Move Post</from><to>移动帖子</to></translation>
+<translation><from>Move topic</from><to>移动主题</to></translation>
+<translation><from>My Nabble Applications</from><to>我的Nabble应用</to></translation>
+<translation><from>My Pending Posts</from><to>我的未定帖子</to></translation>
+<translation><from>My posts</from><to>我的帖子</to></translation>
+<translation><from>Nabble Support</from><to>Nabble支持</to></translation>
+<translation><from>Name</from><to>名称</to></translation>、
+<translation><from>New Post</from><to>新帖子</to></translation>
+<translation><from>news</from><to>新闻</to></translation>
+<translation><from>News</from><to>新闻</to></translation>
+<translation><from>New Topic</from><to>新主题</to></translation>
+<translation><from>New topics only</from><to>仅新主题</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>注意</n.important.>: 所有在<n.location/>之下的内容都将被永久删除!</to></translation>
+<translation><from>No banned users.</from><to>没有被禁止用户。</to></translation>
+<translation><from>No Filter</from><to>未过滤</to></translation>
+<translation><from>none of the words:</from><to>没有词语:</to></translation>
+<translation><from>No registered user found with this email.</from><to>此邮件未发现注册用户</to></translation>
+<translation><from>No replies</from><to>没有回复</to></translation>
+<translation><from>Normal</from><to>正常的</to></translation>
+<translation><from>No sub-forums</from><to>没有子论坛</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>请注意该地址是您的唯一地址,只接受从 <n.address/>发来的邮件。这么做的目的是帮助您防范垃圾邮件。</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to>如果您希望编辑个人资料,通过邮箱接收帖子,管理您的星级帖子,或者获得您的浏览权限,<n.register_link.>请现在注册</n.register_link.></to></translation>
+<translation><from>one email per input box</from><to>每个输入框输入一个邮箱地址</to></translation>
+<translation><from>Online Users</from><to>在线用户</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>仅授权用户可以进入这个区域。</to></translation>
+<translation><from>Open this post in classic view</from><to>以经典样式打开这个帖子</to></translation>
+<translation><from>Open this post in list view</from><to>以列表样式打开这个帖子</to></translation>
+<translation><from>Open this post in threaded view</from><to>以树状图样式打开这个帖子</to></translation>
+<translation><from>Options</from><to>选项</to></translation>
+<translation><from>or</from><to>或</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>或者如果将此用户从该区域隔离会更好的话,您可以忽略这封邮件。</to></translation>
+<translation><from>Other Settings</from><to>其它设置</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>将子论坛及相关讨论放在一起的页面样式</to></translation>
+<translation><from>Page <t.number/></from><to>页面 <n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>含有简单子论坛列表和讨论的页面样式</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>含有完整文章和相关评论的页面样式</to></translation>
+<translation><from>Page with headlines and posts.</from><to>含有标题和帖子的页面样式</to></translation>
+<translation><from>Page with topics and discussions.</from><to>含有主题和讨论的页面样式</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>含有按照子论坛分组的主题的页面样式。如果不存在子论坛,所有主题将不被分类而一同列出。</to></translation>
+<translation><from>Password</from><to>密码</to></translation>
+<translation><from>Password Sent</from><to>发送密码</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>恋童癖,暴力和其它虐待行为</to></translation>
+<translation><from>People</from><to>人</to></translation>
+<translation><from>People in <t.location/></from><to><n.location/>之中的人</to></translation>
+<translation><from>Permalink</from><to>永久链接</to></translation>
+<translation><from>Photo and image gallery.</from><to>显示照片和图像,类似画廊的页面样式</to></translation>
+<translation><from>Pinned sub-forum</from><to>被锁定的子论坛</to></translation>
+<translation><from>Pin topic</from><to>置顶</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>请将该链接存入书签栏或者保存这封邮件,将来您可以轻松地找到您的<n.app/>。</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>请现在检查您的收件箱,激活您的账户,以使用所有功能。</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>请点击下面的确认链接激活您的账户:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>如果您需要帮助,请联系Nabble支持。</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>如果您需要帮助,请联系管理员。</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>请输入正确的邮箱地址,再试一次。</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>请按照邮件中的步骤完成注册操作。</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>请按照<n.subscribe_instructions_link.>订阅步骤</n.subscribe_instructions_link.>订阅该存档。</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>请提供一个有效的永久链接。</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>请再次输入您的电子邮箱/密码,点击登录</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>请遵守邮箱列表的使用守则。</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>来自Polldaddy.com的投票功能 (仅支持flash格式)</to></translation>
+<translation><from>Post by email</from><to>通过电子邮箱发布</to></translation>
+<translation><from>Post by Email</from><to>通过电子邮箱发布</to></translation>
+<translation><from>Post Count</from><to>发帖数</to></translation>
+<translation><from>Posted by <t.author/></from><to>由<n.author/>发帖</to></translation>
+<translation><from>post</from><to>条帖子</to></translation>
+<translation><from>Post Message</from><to>发布帖子</to></translation>
+<translation><from>Post New Message</from><to>发布新帖</to></translation>
+<translation><from>Post new message in <t.location/></from><to>在<n.location/>发布新帖</to></translation>
+<translation><from>posts</from><to>条帖子</to></translation>
+<translation><from>Posts</from><to>发布</to></translation>
+<translation><from>Posts in <t.location/></from><to>在<n.location/>发布</to></translation>
+<translation><from>Preview Message</from><to>预览帖子</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>价格以美元计算。结算过程使用<n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, Amazon.com的顾客可以通过自己的Amazon账户,在支持Amazon付款方式的网页上为商品和其它服务付款。</to></translation>
+<translation><from>Print post</from><to>打印帖子</to></translation>
+<translation><from>Priority</from><to>优先</to></translation>
+<translation><from>(private)</from><to>(私密)</to></translation>
+<translation><from>Profile of <t.author/></from><to><n.author/>的个人资料</to></translation>
+<translation><from>Quote</from><to>引用</to></translation>
+<translation><from>Quote the original message</from><to>引用原帖</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>引用您回复的原帖,将它删减到您需要的部分为止。这将为通过邮件阅读您的帖子的人提供相关的语境。</to></translation>
+<translation><from>Raw mail</from><to>原始邮件</to></translation>
+<translation><from>Raw text</from><to>纯文本</to></translation>
+<translation><from>read more</from><to>阅读更多</to></translation>
+<translation><from>Read more</from><to>阅读更多</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>所有邮箱地址在<n.location/>下的用户的只读列表</to></translation>
+<translation><from>Receive direct replies only.</from><to>只接收直接回复。</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>接收所有在<n.location/>内发布的信息。</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>接收所有该主题下的回复。</to></translation>
+<translation><from>Receive new topics only.</from><to>只接收新主题。</to></translation>
+<translation><from>Refresh</from><to>刷新</to></translation>
+<translation><from>Registered</from><to>已注册</to></translation>
+<translation><from>Registered Users</from><to>已注册用户</to></translation>
+<translation><from>Register</from><to>注册</to></translation>
+<translation><from>Registering...</from><to>注册中......</to></translation>
+<translation><from>Register Now</from><to>现在注册</to></translation>
+<translation><from>Register to <t.app/></from><to>注册到<n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>已确认注册</to></translation>
+<translation><from>Registration Failed</from><to>注册失败</to></translation>
+<translation><from>Related Help Article</from><to>相关帮助文章</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>请注意禁止用户的操作不一定有效,因为用户可以用另一个账户重新登录。</to></translation>
+<translation><from>Remove Ads</from><to>移除广告</to></translation>
+<translation><from>remove</from><to>移除</to></translation>
+<translation><from>Remove Settings</from><to>移除设置</to></translation>
+<translation><from>Remove Subscription</from><to>移除订阅</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>移除您的账户和在<n.subject/>的所有发帖</to></translation>
+<translation><from>Remove Your Account</from><to>移除您的账户</to></translation>
+<translation><from>replies</from><to>回复</to></translation><!-- noun / plural -->
+<translation><from>Replies</from><to>回复</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>回复</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>回复</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>回复作者</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>举报不宜内容</to></translation>
+<translation><from>Report Now</from><to>现在举报</to></translation>
+<translation><from>required</from><to>必须</to></translation>
+<translation><from>Return to <t.location/></from><to>退回到<n.location/></to></translation>
+<translation><from>Save Changes</from><to>保存更改</to></translation>
+<translation><from>Save Settings</from><to>保存设置</to></translation>
+<translation><from>Save Subscription</from><to>保存订阅</to></translation>
+<translation><from>Search</from><to>搜索</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>在下面选择您想采取的措施:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>选择最能反映您对本页内容看法的选项。</to></translation>
+<translation><from>Send email to me</from><to>向我发送电子邮件</to></translation>
+<translation><from>Send Email to <t.author/></from><to>向<n.author/>发送电子邮件</to></translation>
+<translation><from>Send Request</from><to>发送请求</to></translation>
+<translation><from>Send To:</from><to>发送到:</to></translation>
+<translation><from>Sexual content</from><to>黄色内容</to></translation>
+<translation><from>Show</from><to>显示</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>显示Nabble提示</to></translation>
+<translation><from>Sincerely,</from><to>真诚的,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>因为这个应用是邮箱列表存档的,请在单击删除按钮之前取消这个邮箱地址的订阅。</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>因为您不是注册用户,我们必须检查一下您是否是个人类,或者是外星生物。</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>您的一些帖子被从<n.location/>删除了,我们向您发送了副本,这样您可以保存它们。</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>抱歉,只有论坛成员才可以在<n.app/>发帖。</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>抱歉,管理员将您禁止了。</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>抱歉,该邮箱地址没有查看<n.location/>中信息的权限。</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>抱歉,您不能在这里发起新主题。<br/>注意:您可能仍然可以在这里回复主题。</to></translation>
+<translation><from>Sort by date</from><to>以时间排序</to></translation>
+<translation><from>Sort by relevance</from><to>以相关度排序</to></translation>
+<translation><from>Sorted by date</from><to>以时间排序</to></translation>
+<translation><from>Sorted by relevance</from><to>以相关度排序</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[垃圾探测器] 含有太多 '<n.text/>'词语,无效信息。</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[垃圾探测器] 信息内不能含有'<n.text/>'。</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[垃圾探测器] 信息内含有常见垃圾词汇。</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[垃圾探测器] 主题不能含有'<n.text/>'。</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[垃圾探测器] 主题含有常见垃圾词汇。</to></translation>
+<translation><from>Spam</from><to>垃圾信息</to></translation>
+<translation><from>Stars in <t.location/></from><to><n.location/>的星级</to></translation>
+<translation><from>Structure</from><to>结构</to></translation>
+<translation><from>Subcategories</from><to>子版块</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>在<n.location/>下的子版块</to></translation>
+<translation><from>Subcategory</from><to>子板块</to></translation>
+<translation><from>Sub-Forum</from><to>子论坛</to></translation>
+<translation><from>Sub-Forums</from><to>子论坛</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>子论坛和主题</to></translation>
+<translation><from>Subject</from><to>主题</to></translation>
+<translation><from>Subscribe</from><to>订阅</to></translation>
+<translation><from>subscriber</from><to>订阅人</to></translation>
+<translation><from>subscribers</from><to>订阅人</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>订阅<n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>通过邮件订阅</to></translation>
+<translation><from>Subscription Confirmation</from><to>订阅确认</to></translation>
+<translation><from>Subscription Confirmed</from><to>已确认订阅</to></translation>
+<translation><from>Subscription Format</from><to>订阅格式</to></translation>
+<translation><from>Subscription Removed</from><to>已取消订阅</to></translation>
+<translation><from>Subscription Results</from><to>订阅结果</to></translation>
+<translation><from>Subscription Type</from><to>订阅样式</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>成功:一封确认信已经发到您的邮箱。</to></translation>
+<translation><from>Success</from><to>成功</to></translation>
+<translation><from>Take Action</from><to>行动</to></translation>
+<translation><from><t.app/> Registration</from><to>注册<n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to><n.author/>已被成功禁止。</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to><n.author/> 已被成功解禁。</to></translation>
+<translation><from>Tell me more & show examples</from><to>告诉我更多信息,展示样例</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>感谢您注册<n.app/>!</to></translation>
+<translation><from>Thank You</from><to>谢谢</to></translation>
+<translation><from>The author has deleted this message.</from><to>作者已经删除了这个信息。</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>URL内的代码无效。</to></translation>
+<translation><from>the exact phrase:</from><to>准确地说:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>邮箱列表在接受您的帖子之前可能要求您的订阅。请注意在Nabble注册并不会让您自动订阅邮箱列表。如果您还没有订阅,请现在订阅。如果您不确定或记不起来了,只要再订阅一遍即可,那将不会对您有损。</to></translation>
+<translation><from>The Nabble team</from><to>Nabble团队</to></translation>
+<translation><from>The name of the group is not valid.</from><to>用户组名称无效。</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>帖子本身不能成为自己的新父层。</to></translation>
+<translation><from>The password fields don't match.</from><to>密码字段不匹配。</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>这一个邮箱地址<n.email/>是未注册用户。</to></translation>
+<translation><from>The subject is required.</from><to>一定要有主题。</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>用户将会通过电子邮件收到被删除帖子的副本,这样他/她可以有机会保存它们。</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>验证码输入错误。</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>该分支过大,一些帖子被省略。要查看所有帖子,请选择其它浏览样式。</to></translation>
+<translation><from>This email is already subscribed.</from><to>该电子邮件已被订阅。</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>这个论坛是一个邮箱列表存档</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>这个论坛是一个存档/入口,它将把你的帖子发送到<b><n.mailing_list_address/></b>邮箱列表。</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>这里包含了子版块,帖子,图像,文件和其它所有东西。</to></translation>
+<translation><from>This is a mailing list archive</from><to>这是邮箱地址存档</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>这是Nabble自动发送的邮件,用于确认您新创建了<n.app/>。如果您未创建上面提到的<n.app/>,请通过Nabble支持论坛联系我们。</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>该列表仅接受纯文本邮件</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>该列表含有注册用户,未注册用户和禁止用户。匿名用户不在其中,因为他们没有电子邮箱地址,因此不能加入任何用户组。</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>该信息会被从<b><n.from/></b> 发送到<b><n.to/></b>邮箱列表。</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to>该帖子<b>还未被</b>邮箱列表所接受。</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>该帖在<n.date/>被更新。</to></translation>
+<translation><from>This topic has been locked.</from><to>该主题已被锁定。</to></translation>
+<translation><from>This topic has been pinned.</from><to>该主题已被置顶。</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>该主题已被置顶在<n.location/>。</to></translation>
+<translation><from>This topic has been unlocked.</from><to>该主题已被解除锁定。</to></translation>
+<translation><from>This topic has been unpinned.</from><to>该主题已被取消置顶。</to></translation>
+<translation><from>This topic has unread posts</from><to>这个主题含有未阅读帖子</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>该主题作为优先被分配给您。<n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>该用户未获得浏览这个应用的许可(将他/她加入许可用户组,再试一次)。</to></translation>
+<translation><from>This user name is already in use.</from><to>该用户名已被使用。</to></translation>
+<translation><from>Threaded</from><to>结构树</to></translation>
+<translation><from>Threaded View</from><to>树状视图</to></translation>
+<translation><from>Time</from><to>时间</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>提示:如果您的存档已订阅到邮箱列表却还没有反应,您应该尝试忽略X-No-Archive标题。</to></translation>
+<translation><from>Tips</from><to>提示</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> 要求授权加入<n.location/>:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> 积分</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> 次无广告浏览页面</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>为接受这个请求,您应该将这个用户加入到可以进入该区域的至少一个用户组里面。</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>为了将<n.app/> 加入您的网页,请将下列代码复制并粘贴到您的HTML页面:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>为确认您的订阅,请点击下面的链接:</to></translation>
+<translation><from>topic</from><to>主题</to></translation>
+<translation><from>Topics and replies</from><to>主题和回复</to></translation>
+<translation><from>topics</from><to>主题</to></translation>
+<translation><from>Topics</from><to>主题</to></translation>
+<translation><from>Topics only</from><to>仅主题</to></translation>
+<translation><from>Topics View</from><to>浏览主题</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>为了防止垃圾邮件,每个用户用于发帖的邮箱地址都是<b>不同的</b>。</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>为了移除用户组,请清空以下文本框,并且保存设置。</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>为了看到哪个是您应当用来发帖的邮箱地址,请<n.login_link.>登录</n.login_link.> 或者<n.register_link.>注册</n.register_link.>。</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>要<n.location/>发布一个新主题,请向<n.p2/>发邮件</to></translation>
+<translation><from>Total</from><to>所有的</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>取消从<n.location/>的订阅</to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> 邀请您订阅<n.location/>:</to></translation>
+<translation><from>Turn off highlighting</from><to>关闭高亮</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/>创建一个新的子版块</to></translation>
+<translation><from>Unable to Post</from><to>无法发帖</to></translation>
+<translation><from>Unassigned</from><to>未指定</to></translation>
+<translation><from>Unauthorized</from><to>未授权</to></translation>
+<translation><from>Unban this user</from><to>解禁该用户</to></translation>
+<translation><from>Unban User</from><to>解禁用户</to></translation>
+<translation><from>Unknown or Other</from><to>未知或其它</to></translation>
+<translation><from>Unlock topic</from><to>将主题解除锁定</to></translation>
+<translation><from>Unpin topic</from><to>将主题取消置顶</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>未注册的/账户注销的</to></translation>
+<translation><from>Unregistered</from><to>未注册的</to></translation>
+<translation><from>Unregistered User</from><to>未注册用户</to></translation>
+<translation><from>Unsubscribe</from><to>取消订阅</to></translation>
+<translation><from>Upload a file</from><to>上传文件</to></translation>
+<translation><from>User email:</from><to>用户电子邮箱:</to></translation>
+<translation><from>user</from><to>用户</to></translation>
+<translation><from>User is online</from><to>用户在线</to></translation>
+<translation><from>User Name</from><to>用户名</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>用户要求授权加入<n.location/></to></translation>
+<translation><from>users</from><to>用户</to></translation>
+<translation><from>Users</from><to>用户</to></translation>
+<translation><from>Users & Groups</from><to>用户和用户组</to></translation>
+<translation><from>Users that completed the registration process</from><to>完成注册的用户</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>使用像<n.example1/>或<n.example2/>的标签来创建子标题。</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>使用下面的选项精确说明您的搜索条件。</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>使用 <n.tag_names/> 嵌入其它网页中的小窗口。</to></translation>
+<translation><from>Videos from LiveLeak.com</from><to>来自LiveLeak.com的视频</to></translation>
+<translation><from>Videos from Youtube.com</from><to>来自Youtube.com的视频</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>查看该子论坛下所有信息</to></translation>
+<translation><from>view</from><to>次浏览</to></translation>
+<translation><from>View mailing list website</from><to>查看邮箱列表网页</to></translation>
+<translation><from>View message</from><to>查看信息</to></translation>
+<translation><from>View more</from><to>查看更多</to></translation>
+<translation><from>views</from><to>次浏览</to></translation>
+<translation><from>Views</from><to>点击数</to></translation>
+<translation><from>Violent or repulsive content</from><to>暴力或可憎的内容</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>访问<n.app/>于:</to></translation>
+<translation><from>visit <t.url/></from><to>访问<n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>我们准备了一个页面,含有<n.unsubscription_instructions_link.>如何取消订阅存档</n.unsubscription_instructions_link.>的步骤。</to></translation>
+<translation><from>We will review your report soon.</from><to>我们将会很快审查您的举报。</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>允许列出所有成员的用户组</to></translation>
+<translation><from>Who can ban/unban users</from><to>可以禁止/解禁用户</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>谁可以被分配主题?(仅在工作组中)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>可以更改信息的发布日期和时间</to></translation>
+<translation><from>Who can create new topics under this application</from><to>可以在该应用下创立新话题</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>可以创建子应用(例如子论坛,子板块,等等)</to></translation>
+<translation><from>Who can edit any content, both applications and posts.  Note: Please only use this feature in extreme circumstances.  Most users will not like having their posts edited by someone else.</from><to>可以编辑任何内容,包括本人和他人的应用和帖子。提示:请仅在特殊情况下使用此功能。大多数用户不会欢迎别人编辑他们的帖子。</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>谁可以编辑应用(例如更改名称和描述)?</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>可以锁定/解除锁定该应用中的主题</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>可以管理该应用的订阅人</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>可以将信息移动到其它地方(例如其它主题或子论坛)</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>可以置顶/取消置顶该应用中的主题</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>可以无限制地发布任何内容(包括javascript代码,&lt;object&gt; and &lt;style&gt; 标签,等等)。<b>安全警告</b>: 仅对您绝对信任的用户开启此功能。</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>可以在该应用下回帖</to></translation>
+<translation><from>Who can view this application and its contents</from><to>可以浏览应用以及其中的内容</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>依据您的订阅,更新会被直接发送到您的邮箱里面,您可以通过回复来参与讨论。您的订阅会和邮箱列表发挥同样的作用。</to></translation>
+<translation><from>Write Your First Headline</from><to>编写您的第一条标题</to></translation>
+<translation><from>Write Your First Post</from><to>发布您的第一个帖子</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>是的,永久删除 <n.location/></to></translation>
+<translation><from>Yes, unsubscribe now</from><to>是的,现在取消订阅</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>您已订阅 <n.location/>。</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>您未订阅 <n.location/>。</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>您也可以<n.manage_banned_users_link.>在<n.location/>中编辑被禁止用户</n.manage_banned_users_link.> 。</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>您也可以<n.root_node.change_permissions_link.>更改下列用户组的权限</n.root_node.change_permissions_link.> 。</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>您也可以通过发送链接给您的朋友,嵌入您自己的网页,或者在其他论坛上宣传来提高您的<n.app/>的关注度。</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>您不可以将帖子移动到那里,因为该父论坛不允许匿名用户发帖。</to></translation>
+<translation><from>You Cannot Post Here</from><to>您不可以在此发帖</to></translation>
+<translation><from>(you can reply by email)</from><to>(您可以通过邮件回复)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>您不可以移动这个帖子。</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>您不可以在这里发布信息,不过您可以在其它地方发布。</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>您可以尝试<n.register_link.>再次注册</n.register_link.> 或联系<n.support_link/>。</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>您可以使用下列表格向管理员递交请求。</to></translation>
+<translation><from>You have already been registered.</from><to>您已注册。</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>您被邀请订阅<n.location/>,该订阅可在如下地方获得:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>您已经注册到了<n.subject/>。</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>您已从<n.location/>取消了订阅</to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>您在短时间内发了大量的帖子,请过一会儿再试。</to></translation>
+<translation><from>You logged out</from><to>您已退出</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>您可能需要<n.mailing_list_options_link.>订阅邮箱列表</n.mailing_list_options_link.> 来让您的信息被接受。</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>您可以<n.page_node.unauthorized_link.>请求发帖许可</n.page_node.unauthorized_link.>,或者如果您有任何疑问,请联系<n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> 。</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>您必须同意用户协议。</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>您必须为这个邮件列表输入一个有效的邮箱地址。</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>您必须为这个邮件列表输入一个有效的网页URL。</to></translation>
+<translation><from>You must fill in all fields below.</from><to>您必须填满下面的所有空白。</to></translation>
+<translation><from>You must login to view this page.</from><to>您必须登录后才可以浏览该页面。</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>您必须登录后才可以浏览<n.subject/>。</to></translation>
+<translation><from>You must login to your account.</from><to>您必须登录您的账户。</to></translation>
+<translation><from>You must provide a user name.</from><to>您必须要提供一个用户名。</to></translation>
+<translation><from>You're not a subscriber</from><to>您不是订阅用户</to></translation>
+<translation><from>Your Name</from><to>您的用户名</to></translation>
+<translation><from>Your request has been successfully sent.</from><to>您的请求已被成功发送。</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>您的订阅已被成功保存。</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>您订阅的<n.location/>已经被移除。如果您进行了错误操作,您可以通过单击以下链接重新订阅:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>您对<n.location/>的订阅已经被成功取消。</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to>您的<n.app/>已经被成功创建。</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>您需要授权才可以在<n.location/>发新主题,所以除了注册之外您还需要获得管理员的许可。</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>您将会收到该主题下的所有新回复的邮件。</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>您将会收到一封含有激活链接的邮件。</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>您的订阅</to></translation>
+<translation><from>edit</from><to>编辑</to></translation>
+<translation><from>Remove ads</from><to>移除广告</to></translation>
+<translation><from>View profile of <t.author/></from><to>查看 <n.author/>的个人资料</to></translation>
+<translation><from>Description</from><to>描述</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>Youtube, Vimeo 和 LiveLeak的视频。</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>在<n.location/>中被禁止的用户</to></translation>
+<translation><from>Subject Prefix</from><to>主题前缀</to></translation>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_ch_tr.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,782 @@
+## Chinese-traditional
+
+## MONTHS ##
+<translation><from>January</from><to>一月</to></translation>
+<translation><from>February</from><to>二月</to></translation>
+<translation><from>March</from><to>三月</to></translation>
+<translation><from>April</from><to>四月</to></translation>
+<translation><from>May</from><to>五月</to></translation>
+<translation><from>June</from><to>六月</to></translation>
+<translation><from>July</from><to>七月</to></translation>
+<translation><from>August</from><to>八月</to></translation>
+<translation><from>September</from><to>九月</to></translation>
+<translation><from>October</from><to>十月</to></translation>
+<translation><from>November</from><to>十一月</to></translation>
+<translation><from>December</from><to>十二月</to></translation>
+
+<translation><from>Jan</from><to>一月</to></translation>
+<translation><from>Feb</from><to>二月</to></translation>
+<translation><from>Mar</from><to>三月</to></translation>
+<translation><from>Apr</from><to>四月</to></translation>
+<!--translation><from>May</from><to>五月</to></translation-->
+<translation><from>Jun</from><to>六月</to></translation>
+<translation><from>Ju</from><to>七月</to></translation>
+<translation><from>Aug</from><to>八月</to></translation>
+<translation><from>Sep</from><to>九月</to></translation>
+<translation><from>Oct</from><to>十月</to></translation>
+<translation><from>Nov</from><to>十一月</to></translation>
+<translation><from>Dec</from><to>十二月</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>濫用此功能也是違反使用條款的行為.</to></translation>
+<translation><from>Access Request</from><to>申請加入</to></translation>
+<translation><from>Account settings</from><to>賬戶設置</to></translation>
+<translation><from>Account Settings</from><to>賬戶設置</to></translation>
+<translation><from>Action</from><to>行動</to></translation>
+<translation><from>Add a link to another page</from><to>將鏈接添加入其他頁面</to></translation>
+<translation><from>Add a new comment</from><to>添加一條新評論</to></translation>
+<translation><from>Add a new group</from><to>添加一個新的用戶組</to></translation>
+<translation><from>Add an image to your post</from><to>在回覆中添加圖片</to></translation>
+<translation><from>Add another address</from><to>添加別一個地址</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>添加不包含編碼(例如: 計算機代碼)的內容</to></translation>
+<translation><from>Adding Sub-Headers</from><to>添加副標題</to></translation>
+<translation><from>Add New Group</from><to>添加一個新用户組添加一個新的用戶組</to></translation>
+<translation><from>Add / Remove Groups</from><to>添加/移除用戶組</to></translation>
+<translation><from>Add smileys and funny animations</from><to>添加笑臉和有趣的動畫</to></translation>
+<translation><from>Add Subscribers</from><to>添加訂閱者</to></translation>
+<translation><from>Add this item to your favorites list</from><to>將此項添加到最感興趣項目列表</to></translation>
+<translation><from>Administrator</from><to>管理員</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity,  other sexual content.</from><to>成人内容, 暴露的性行為, 祼露, 以及其它色情内容.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>成人打鬥, 人身攻擊, 青年暴力, 動物虐待或者宣揚恐怖主義.</to></translation>
+<translation><from>Advanced Search</from><to>高級搜索</to></translation>
+<translation><from>Advanced Settings</from><to>高級設置</to></translation>
+<translation><from>Advertisement</from><to>廣告</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>當有人回覆主題時向我的電子郵箱發送提示郵件</to></translation>
+<translation><from>All</from><to>所有</to></translation>
+<translation><from>all of the words:</from><to>所有詞語:</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>所有 <n.author/> 的帖子已經被全部成功移除.</to></translation>
+<translation><from>All posts</from><to>所有帖子</to></translation>
+<translation><from>All users belong to this group</from><to>所有屬於本組的用户</to></translation>
+<translation><from>All Users</from><to>所有用户</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>所有注冊到 <n.location/> 的用户. 這些用户已經確認了他們的郵箱地址, 能夠登錄論壇系统了.</to></translation>
+<translation><from>Already Subscribed</from><to>已訂閱</to></translation>
+<translation><from>An email has been sent to you.</from><to>一封郵件已經發到了您的郵箱.</to></translation>
+<translation><from>Anonymous</from><to>匿名</to></translation>
+<translation><from>anonymous user</from><to>匿名用户</to></translation>
+<translation><from>anonymous users</from><to>匿名用户</to></translation>
+<translation><from>Any message part contains</from><to>該部分含有的任何信息</to></translation>
+<translation><from>Application</from><to>申請</to></translation>
+<translation><from>Apps</from><to>申請</to></translation>
+<translation><from>Assignee</from><to>受讓人</to></translation>
+<translation><from>Assign</from><to>分配</to></translation>
+<translation><from>Assignment</from><to>分配</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>應您的要求, 在 <n.app/> 用于發佈新話題的郵箱地址是:</to></translation>
+<translation><from>at least one of the words:</from><to>至少其中一個單詞:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to><n.location/>的源</to></translation>
+<translation><from>at priority</from><to>優先</to></translation>
+<translation><from>Authorized Users Only</from><to>僅授權用户</to></translation>
+<translation><from>Author name</from><to>作者姓名</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>避免使用諸如 "謝謝:, "好"..... 等過短回覆. 如果有需要, 您可以 <n.page_node.reply_to_author_link.> 發送一封私人郵件 </n.page_node.reply_to_author_link.>.</to></translation>
+<translation><from>Banned User</from><to>禁止用户</to></translation>
+<translation><from>Banned User in <t.location/></from><to>禁止用户在 <n.location/></to></translation>
+<translation><from>Ban this user</from><to>禁止該用户</to></translation>
+<translation><from>Ban User</from><to>禁止用户</to></translation>
+<translation><from>Before deleting this archive...</from><to>在處理存檔前...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>您可以在下面處理用戶組和用户. 您可以對他們進行複制和粘貼操作, 以將他們從一個用戶組移動到另一個用戶組裡面.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>重要</b>: Nabble 論壇會向下列所有的電子郵箱地址發送一封邀請信. 用户必須點擊確認鏈接來確認他們的訂閱.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>重要</b>: 該訂閱與訂閱郵箱列表無關. 總言之, 您將會訂閱論壇文檔, 而不是郵箱列表本身. 文檔訂閱不能保証您的信息會被郵箱列表所接受.</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>重要</b>: 您必須將這些文檔訂閱给郵箱列表中的人, 以讓其發揮作用. </to></translation>
+<translation><from>blog</from><to>部落格</to></translation>
+<translation><from>Blog</from><to>部落格</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>提示</b>: 由於您是管理員, 您可以 <n.page_node.change_permissions_link.> 改變 <n.location/> </n.page_node.change_permissions_link.> 的權限, 確保您可以在這裡創建新的主題.</to></translation>
+<translation><from>board</from><to>討論板</to></translation>
+<translation><from>Board</from><to>討論板</to></translation>
+<translation><from>Bold</from><to>粗體</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>警告:</b> 當前搜索欄正在維護中, 搜索結果可能不完整.</to></translation>
+<translation><from>by <t.author/></from><to>被 <n.author/></to></translation>
+<translation><from>Cancel</from><to>取消</to></translation>
+<translation><from>category</from><to>分類</to></translation>
+<translation><from>Category</from><to>分類</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>小心: 該操作不能復原.</to></translation>
+<translation><from>Change appearance</from><to>更改外觀</to></translation>
+<translation><from>Change application type</from><to>更改應用</to></translation>
+<translation><from>Change Application Type</from><to>更改應用</to></translation>
+<translation><from>Change code image</from><to>更改代碼圖像</to></translation>
+<translation><from>Change domain name</from><to>更改域名</to></translation>
+<translation><from>Change language</from><to>更改語言</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>更改或移除您的頭像.</to></translation>
+<translation><from>Change parent</from><to>更改父論壇</to></translation>
+<translation><from>Change permissions</from><to>更改權限</to></translation>
+<translation><from>Change Permissions</from><to>更改權限</to></translation>
+<translation><from>Change post date</from><to>更改發帖日期</to></translation>
+<translation><from>Change Post Date</from><to>更改發帖日期</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>更改顯示在您帖文底部的簽名.</to></translation>
+<translation><from>Change User Groups</from><to>更改用戶組</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>更改外觀和其它設置.</to></translation>
+<translation><from>Change Your Picture</from><to>更改您的圖片</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>更改您注冊的電子郵箱地址, 密碼, 和您的用户名.</to></translation>
+<translation><from>Child abuse</from><to>兒童虐待</to></translation>
+<translation><from>Choose a subcategory</from><to>選擇一個副討論板</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>選擇發佈您帖文的副討論板</to></translation>
+<translation><from>Choose the best offer for you</from><to>選擇最適合您的選項</to></translation>
+<translation><from>Classic</from><to>經典</to></translation>
+<translation><from>Clear Log</from><to>清除日誌</to></translation>
+<translation><from>Click for more options</from><to>點擊以獲取更多選項</to></translation>
+<translation><from>click here</from><to>點擊這裡</to></translation>
+<translation><from>Click here to make your first post</from><to>點擊這裡發佈您的第一帖</to></translation>
+<translation><from>Click to filter</from><to>點擊過濾</to></translation>
+<translation><from>Close</from><to>關閉</to></translation>
+<translation><from>Close this message</from><to>關閉這個信息</to></translation>
+<translation><from>comment</from><to>注釋</to></translation>
+<translation><from>Comment</from><to>注釋</to></translation>
+<translation><from>comments</from><to>注釋</to></translation>
+<translation><from>Comments</from><to>注释</to></translation>
+<translation><from>Confirm Password</from><to>確認密碼</to></translation>
+<translation><from>Confirm Subscription</from><to>確認訂閱</to></translation>
+<translation><from>Congratulations!</from><to>祝賀!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>祝賀您的新 <n.app/>!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>含有宣揚仇恨,暴力,虐待弱者,恃强凌弱,種族歧視和反人類反社會的内容。</to></translation>
+<translation><from>CONTENTS DELETED</from><to>内容已刪除</to></translation>
+<translation><from>Continue</from><to>繼續</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>作者擁有版權及其它法律規定權利, 不得侵犯.</to></translation>
+<translation><from>Count</from><to>數</to></translation>
+<translation><from>Created by <t.author/></from><to>由<n.author/>創建</to></translation>
+<translation><from>Create new <t.element/></from><to>創建一個新<n.element/></to></translation>
+<translation><from>Create <t.element/></from><to>創建<n.element/></to></translation>
+<translation><from>Current Credits</from><to>當前積分</to></translation>
+<translation><from>Currently Nabble supports</from><to>當前 Nabble 支持</to></translation>
+<translation><from>Current Subscribers</from><to>當前訂閱者</to></translation>
+<translation><from>Daily digest</from><to>每日文摘</to></translation>
+<translation><from>Data successfully saved</from><to>成功保存數據</to></translation>
+<translation><from>Date</from><to>日期</to></translation>
+<translation><from>days</from><to>天</to></translation>
+<translation><from>Dear <t.name/>,</from><to>親愛的<n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>親愛的用户,</to></translation>
+<translation><from>Default</from><to>點認</to></translation>
+<translation><from>Delete all posts from this user</from><to>刪除該用户所有帖子</to></translation>
+<translation><from>Delete Application</from><to>刪除應用</to></translation>
+<translation><from>Deleted posts</from><to>刪除帖子</to></translation>
+<translation><from>Delete</from><to>刪除</to></translation>
+<translation><from>Delete this post and replies</from><to>刪除帖子和回覆</to></translation>
+<translation><from>Delete this post</from><to>刪除該帖</to></translation>
+<translation><from>Delete this topic</from><to>刪除這個話題</to></translation>
+<translation><from>Description is in HTML Format</from><to>描述採用 HTML 格式</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>請不要重覆發帖. 等待幾天, 人們將通過電子郵件閱讀您的帖子.</to></translation>
+<translation><from>Don't show this message again</from><to>不要再顯示此消息</to></translation>
+<translation><from>Download archives</from><to>下載文檔</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>您確定要刪除該帖嗎?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>您確定要永久刪除該帖以及所有回覆嗎?</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>您確定要永久 <n.important.> 刪除 </n.important.> <n.location/>?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>您確認要移除電子郵件列表存檔設定嗎?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>您確定要訂閱 <n.location/>?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>您確定要解禁該會員嗎?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>您確定要取消從 <n.location/> 的訂閱嗎?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>含有毒品濫用, 吸毒及吸毒工具, 以及縱火, 爆炸, 販賣啤酒或烈酒, 香烟以及相關物品, 武器, 軍火及其它危險品的内容.</to></translation>
+<translation><from>Edit name & description</from><to>編輯論壇名稱和描述</to></translation>
+<translation><from>Edit Name & Description</from><to>編輯論壇名稱和描述</to></translation>
+<translation><from>Editor</from><to>編輯</to></translation>
+<translation><from>Edit Personal Information</from><to>編輯個人信息</to></translation>
+<translation><from>Edit post</from><to>編輯帖文</to></translation>
+<translation><from>Edit Subscription</from><to>編輯訂閱</to></translation>
+<translation><from>Edit Your Signature</from><to>編輯您的簽名</to></translation>
+<translation><from>Email Confirmation</from><to>用電子郵件發送確認信息</to></translation>
+<translation><from>Email for <t.app/></from><to>用電子郵件發送 <n.app/></to></translation>
+<translation><from>Email</from><to>用電子郵件</to></translation>
+<translation><from>Email Subscription</from><to>用電子郵件發送訂閱</to></translation>
+<translation><from>Email this post to...</from><to>將此帖用電子郵件發送到...</to></translation>
+<translation><from>Embedding Contents</from><to>嵌入内容</to></translation>
+<translation><from>Embedding options</from><to>嵌入選項</to></translation>
+<translation><from>Embed</from><to>嵌入</to></translation>
+<translation><from>Embed post</from><to>嵌入帖文</to></translation>
+<translation><from>Embed Tags</from><to>嵌入標簽</to></translation>
+<translation><from>Embed this <t.app/></from><to>嵌入這個 <n.app/></to></translation>
+<translation><from>Empty</from><to>空</to></translation>
+<translation><from>Enter a valid email address.</from><to>輸入有效的電子郵箱地址.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>在下方輸入您的電子郵箱地址, 我們會给您發送確認信.</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>每一行輸入一個電子郵箱地址或用户名:</to></translation>
+<translation><from>Enter one user per row</from><to>每一行輸入一個用户名</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>輸入<b>帖文</b>或<b>論壇</b>的永久鏈接使其成為一個新的父層, 或者不填使該消息成為一個獨立的主題:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>輸入該郵箱列表的主頁, 該主頁能讓用户得到更多信息.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>輸入該郵箱列表在主題之前所使用的前綴, 該前綴會被從收到的電子郵件中自動刪除.</to></translation>
+<translation><from>Enter your email address</from><to>輸入您的郵箱地址</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>例如: johnsmith@domain.com</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>例如: listname@listdomain.com</to></translation>
+<translation><from>Examples:</from><to>例如:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>例如: '[列表]', 'Abc:', 等等.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>請向管理員說明您希望進入限制區域的原因.</to></translation>
+<translation><from>Explanation from this user:</from><to>該用户的說明:</to></translation>
+<translation><from>Extras & add-ons</from><to>附加項目</to></translation>
+<translation><from>Failed</from><to>失敗</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>特别喜愛 (單擊將此項移出特别喜愛項目列表) </to></translation>
+<translation><from>Feeds</from><to>源</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>文件上傳失敗: <n.file/> (請再次上傳或刪除標簽)</to></translation>
+<translation><from>Filter by group</from><to>群組過濾</to></translation>
+<translation><from>Floating sub-forum</from><to>非固定子論壇</to></translation>
+<translation><from>Forgot Password?</from><to>忘記密碼?</to></translation>
+<translation><from>Forgot your password?</from><to>忘記您的密碼?</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>獲取更多信息, 請参考: <n.info/></to></translation>
+<translation><from>forum</from><to>論壇</to></translation>
+<translation><from>Forum</from><to>論壇</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to><n.app/> 免費可嵌入</to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>從現在起,您將會收到關於在 <n.location/> 之中帖子的電子郵件, 每個帖子一封.</to></translation>
+<translation><from>gallery</from><to>畫廊</to></translation>
+<translation><from>Gallery</from><to>畫廊</to></translation>
+<translation><from>Gambling or casino-related content</from><to>關於賭博的内容</to></translation>
+<translation><from>Go back</from><to>後退</to></translation>
+<translation><from>Go to next message</from><to>到下一條信息</to></translation>
+<translation><from>Group Name:</from><to>組名:</to></translation>
+<translation><from>Groups</from><to>用戶組</to></translation>
+<translation><from>Groups of this user</from><to>該用戶的用戶組</to></translation>
+<translation><from>Hacking / cracking</from><to>網絡黑客/崩潰</to></translation>
+<translation><from>Harmful dangerous acts</from><to>有害信息</to></translation>
+<translation><from>Hateful or abusive content</from><to>含有憎恨和虐待的内容</to></translation>
+<translation><from>Help</from><to>幫助</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>您可以在這裡購買積分, 它能夠使您的 Nabble 論壇不出現廣告. 每個積分可以使一個頁面没有廣告. 在您仍然有可使用積分的時候, 來訪者可以看到没有廣告的頁面. Nabble 將會在您的積分用盡時在網頁上粘貼廣告.</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>現在您可以對用户隱藏當前郵箱列表存檔. 這個選擇可以幫助您在 Nabble 的郵件訂閱功能的幫助下替換您的郵箱列表服務器.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>隱藏郵件地址(例如, user@host.com 到 <n.lt/> 隱藏郵箱 <n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>隱藏郵件</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>對用户隱藏郵箱列表存檔</to></translation>
+<translation><from>Highest</from><to>最高級</to></translation>
+<translation><from>High</from><to>高級</to></translation>
+<translation><from>Hi <t.name/>,</from><to>你好, <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>如果該郵箱地址是您的, 您應該 <n.register_link.> 用這相同的地址注冊 </n.register_link.>. 注冊完成后, 您將擁有自己的賬户. </to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>如果您没有收到 Nabble 發送的郵件, 請檢查您的 <b>垃圾郵件</b> 文件夾.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>如果您準備發帖提問, 請先搜索論壇, 您的問題可能已經被回答了.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>如果您還不是會員, 您可以 <n.register_link.> <b>現在注冊</b> </n.register_link.>.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>如果您禁止了該會員, 他/她將不能夠在 <n.location/> 中進行任何操作.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>如果您没有要求得到這封郵件或者不清楚為什么收到該郵件, 請忽略它. 這也許是别人犯的一個錯誤.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>如果您不想注冊, 只要輸入您打算發送的郵箱地址, 您的個人地址就將會被送到您的郵箱.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>如果您已經移除了訂閱, 您就可以繼續進行刪除操作.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>如果您知道, 請選擇您的郵箱列表服务應用以及它的版本.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>login again</n.login_link.>.</from><to>如果您不小心退出了論壇, 請 <n.login_link.> 再次登錄 </n.login_link.>.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>如果您回覆了這封郵件, 您的郵件信息將會被加入下面的討論中.</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>如果您解禁該用户, 他/她將能夠再次在 <n.location/> 裡發帖。</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>如果您打算訂閱郵箱列表取而代之, <n.mailing_list_options_link.> 参照該頁面 </n.mailing_list_options_link.>.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>忽略 X-No-Archive 標題</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>我已經閱讀過并且同意 Nabble 的 <n.terms_link.> 以使用 </n.terms_link.>.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>圖像未上傳: <n.image/> (請重新上傳或者移除標簽)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>我是一位訂閱者, 請讓我現在發帖</to></translation>
+<translation><from>Incorrect Login!</from><to>登錄錯誤!</to></translation>
+<translation><from>Individual emails</from><to>獨立郵件</to></translation>
+<translation><from>Inherit</from><to>繼承</to></translation>
+<translation><from>In Reply To</from><to>作為答覆</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> posted by <t.author/></from><to>作為回答 <n.author/> <n.parent_link.>此帖</n.parent_link.></to></translation>
+<translation><from>Insert</from><to>插入</to></translation>
+<translation><from>Insert Image</from><to>插入圖片</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>作為代替在網頁上發帖的手段,您也可以通過發送郵件到以下地址來進行發帖操作:</to></translation>
+<translation><from>in <t.location/></from><to>内<n.location/></to></translation>
+<translation><from>Invalid Code</from><to>無效代碼</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>無效郵件地址:<n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>無效天數,必須是整數。</to></translation>
+<translation><from>invisible user</from><to>隱身用户</to></translation>
+<translation><from>invisible users</from><to>隱身用户</to></translation>
+<translation><from>Invite Subscribers</from><to>邀請訂閱者</to></translation>
+<translation><from>is:</from><to>是:</to></translation>
+<translation><from>is not:</from><to>不是:</to></translation>
+<translation><from>is within the last:</from><to>包含在最近:</to></translation>
+<translation><from>Italic</from><to>斜體</to></translation>
+<translation><from>item</from><to>項目</to></translation>
+<translation><from>items</from><to>項目</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>刪除項目<b>不可</b>恢復.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>只要將包含了這些標簽的代碼(上方網頁提供)粘貼上去, 您就可以發佈了.</to></translation>
+<translation><from>Last Post</from><to>最后一張貼子</to></translation>
+<translation><from>Learn more</from><to>了解更多</to></translation>
+<translation><from>Leave a comment</from><to>發表評論</to></translation>
+<translation><from>Link</from><to>鏈接</to></translation>
+<translation><from>Link to <t.location/></from><to>鏈接到 <n.location/></to></translation>
+<translation><from>List</from><to>列表</to></translation>
+<translation><from>List of Subcategories</from><to>子版块列表</to></translation>
+<translation><from>List Server</from><to>列表服务器</to></translation>
+<translation><from>List View</from><to>列表預覧</to></translation>
+<translation><from>Loading...</from><to>載入中...</to></translation>
+<translation><from>Location</from><to>位置</to></translation>
+<translation><from>Locked</from><to>鎖定</to></translation>
+<translation><from>Lock topic</from><to>主題鎖定</to></translation>
+<translation><from>Login</from><to>登錄</to></translation>
+<translation><from>Log is empty</from><to>空的日志</to></translation>
+<translation><from>Log out</from><to>退出</to></translation>
+<translation><from>Lowest</from><to>最低的</to></translation>
+<translation><from>Low</from><to>低</to></translation>
+<translation><from>Mailing List Address</from><to>郵箱地址列表</to></translation>
+<translation><from>Mailing list archive settings</from><to>郵箱地址存檔設置</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>郵箱地址存檔設置</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>郵箱地址訂閱提醒</to></translation>
+<translation><from>Mailing List Website</from><to>郵箱地址網頁</to></translation>
+<translation><from>Main Page</from><to>主頁</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>確保您使用和在填入注冊請求信息时相同的瀏覧器.</to></translation>
+<translation><from>Manage banned users</from><to>管理被禁止用户</to></translation>
+<translation><from>Manage pinned topics</from><to>管理置頂主題</to></translation>
+<translation><from>Manage subscribers</from><to>管理訂閱</to></translation>
+<translation><from>Manage Subscribers</from><to>管理訂閱</to></translation>
+<translation><from>Manage <t.items/></from><to>管理<n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>管理用户和用户組</to></translation>
+<translation><from>Manage Users & Groups</from><to>管理用户和用户組</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>大量的廣告, 主旨不明的文章, 欺詐信息.</to></translation>
+<translation><from>max. 80 characters</from><to>最多 80 個字符</to></translation>
+<translation><from>Message date</from><to>信息日期</to></translation>
+<translation><from>message</from><to>信息</to></translation>
+<translation><from>Message</from><to>信息</to></translation>
+<translation><from>Message is in HTML Format</from><to>信息是 HTML 格式的</to></translation>
+<translation><from>messages</from><to>信息</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>此处發表的信息將會被送到這個郵箱列表中.</to></translation>
+<translation><from>Message subject contains</from><to>信息主題包含</to></translation>
+<translation><from>Message text contains</from><to>信息内容包含</to></translation>
+<translation><from>Mixed</from><to>混合的</to></translation>
+<translation><from>Modified</from><to>修正的</to></translation>
+<translation><from>Monthly Archives</from><to>每月存檔</to></translation>
+<translation><from>More Categories</from><to>更多類别</to></translation>
+<translation><from>More</from><to>更多</to></translation>
+<translation><from>more help</from><to>更多幫助</to></translation>
+<translation><from>more options</from><to>更多選項</to></translation>
+<translation><from>More options</from><to>更多選項</to></translation>
+<translation><from>Move post</from><to>移動帖子</to></translation>
+<translation><from>Move Post</from><to>移動帖子</to></translation>
+<translation><from>Move topic</from><to>移動主題</to></translation>
+<translation><from>My Nabble Applications</from><to>更多 Nabble 應用</to></translation>
+<translation><from>My Pending Posts</from><to>我未發表的帖子</to></translation>
+<translation><from>My posts</from><to>我的帖子</to></translation>
+<translation><from>Nabble Support</from><to>Nabble 支持</to></translation>
+<translation><from>Name</from><to>名字</to></translation>
+<translation><from>New Post</from><to>新帖子</to></translation>
+<translation><from>news</from><to>新聞</to></translation>
+<translation><from>News</from><to>新聞</to></translation>
+<translation><from>New Topic</from><to>新主題</to></translation>
+<translation><from>New topics only</from><to>僅新主題</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>小心</n.important.>: 所有在 <t.location/> 之下的内容都將被永久刪除!</to></translation>
+<translation><from>No banned users.</from><to>没有被禁止用户.</to></translation>
+<translation><from>No Filter</from><to>未過濾</to></translation>
+<translation><from>none of the words:</from><to>没有詞語:</to></translation>
+<translation><from>No registered user found with this email.</from><to>此郵件未發現注冊用户</to></translation>
+<translation><from>No replies</from><to>没有回覆</to></translation>
+<translation><from>Normal</from><to>正常的</to></translation>
+<translation><from>No sub-forums</from><to>没有子論壇</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>請注意該地址是您的唯一地址, 只接受從 <n.address/> 發來的郵件. 這麼做的目的是幫助你組織垃圾郵件.</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to>如果您希望編輯頭像, 通過郵箱接收帖子, 標注高亮選項, 或者可以獲得全球概况, <n.register_link.> 請現在注冊</n.register_link.></to></translation>
+<translation><from>one email per input box</from><to>每個輸入框輸入一個郵箱地址</to></translation>
+<translation><from>Online Users</from><to>在綫用户</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>僅授權用户可以進入這個區域.</to></translation>
+<translation><from>Open this post in classic view</from><to>以經典樣式打開這個帖子</to></translation>
+<translation><from>Open this post in list view</from><to>以列表樣式打開這個帖子</to></translation>
+<translation><from>Open this post in threaded view</from><to>以树状图樣式打開這個帖子</to></translation>
+<translation><from>Options</from><to>選項</to></translation>
+<translation><from>or</from><to>或</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>或者如果對將此用户從該區域隔離有幫助的話, 您可以忽略這封郵件.</to></translation>
+<translation><from>Other Settings</from><to>其它設置</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>對那組子應用和討論標注頁碼.</to></translation>
+<translation><from>Page <t.number/></from><to>標注頁碼<n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>為子應用和討論的列表標注頁碼.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>對完整信息和相關評論標注頁碼.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>為標題和帖子標注頁碼.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>為話題和討論標注頁碼.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>為子應用中的主題標注頁碼. 如果没有子應用存在, 將會顯示常規的主題列表.</to></translation>
+<translation><from>Password</from><to>密碼</to></translation>
+<translation><from>Password Sent</from><to>發送密碼</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>戀童癖, 暴力和其它虐待行為.</to></translation>
+<translation><from>People</from><to>人</to></translation>
+<translation><from>People in <t.location/></from><to><n.location/>之中的人</to></translation>
+<translation><from>Permalink</from><to>永久鏈接</to></translation>
+<translation><from>Photo and image gallery.</from><to>照片和圖畫的畫廊.</to></translation>
+<translation><from>Pinned sub-forum</from><to>被鎖定的子論壇</to></translation>
+<translation><from>Pin topic</from><to>被鎖定的主題</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>請將該鏈接存入書簽欄或者保存這封郵件, 將來您可以輕鬆地找到您的 <n.app/>。</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>請現在檢查您的收件箱, 激活您的賬户, 以使用所有功能. </to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>請點擊下面的確認鏈接激活您的賬户:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>如果您需要幫助, 請聯絡 Nabble 支援.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>如果您需要幫助,請聯絡管理員.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>請輸入正確的郵箱地址, 再试一次.</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Send Password". We will email your password to that email address.</from><to>請輸入您在注冊時使用的郵箱地址,點擊"發送密碼", 我們將把密碼發送到您的郵箱.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>請按照郵件中的步骤完成注冊操作.</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>請按照 <n.subscribe_instructions_link.> 訂閱該存檔的步骤 </n.subscribe_instructions_link.>.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>請提供一個有效的永久鏈接.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>請再次輸入您的電子郵箱/密碼, 點擊登錄</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>請遵守郵箱列表的规则</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>來自 Polldaddy.com 的調查</to></translation>
+<translation><from>Post by email</from><to>通過電子郵箱發佈</to></translation>
+<translation><from>Post by Email</from><to>通過電子郵箱發佈</to></translation>
+<translation><from>Post Count</from><to>發帖數</to></translation>
+<translation><from>Posted by <t.author/></from><to>由 <n.author/> 發帖</to></translation>
+<translation><from>post</from><to>發佈</to></translation>
+<translation><from>Post Message</from><to>發佈消息</to></translation>
+<translation><from>Post New Message</from><to>發佈新消息</to></translation>
+<translation><from>Post new message in <t.location/></from><to>在<n.location/>發佈新消息</to></translation>
+<translation><from>posts</from><to>發佈</to></translation>
+<translation><from>Posts</from><to>發佈</to></translation>
+<translation><from>Posts in <t.location/></from><to>在 <n.location/> 發佈</to></translation>
+<translation><from>Preview Message</from><to>預覧信息</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>價格以美元計算. 結算過程使用 <n.amazon_payments_link.> AmazonPayments </n.amazon_payments_link.>, 可以使 Amazon 僱客使用他們在 Amazon.com 上的賬户為货物, 網頁服務和被 Amazon Payment 接受的應用付款.</to></translation>
+<translation><from>Print post</from><to>列印帖子</to></translation>
+<translation><from>Priority</from><to>優先</to></translation>
+<translation><from>(private)</from><to>(私密)</to></translation>
+<translation><from>Profile of <t.author/></from><to>頭像 <n.author/></to></translation>
+<translation><from>Quote</from><to>引用</to></translation>
+<translation><from>Quote the original message</from><to>引用原始信息</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>只為相關部分引用您回覆的和編輯的信息. 這將為通過郵件閱讀您的信息的人提供相關文章. </to></translation>
+<translation><from>Raw mail</from><to>原始郵件</to></translation>
+<translation><from>Raw text</from><to>原始文章</to></translation>
+<translation><from>read more</from><to>閱讀更多</to></translation>
+<translation><from>Read more</from><to>閱讀更多</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>所有郵箱地址在 <n.location/> 下的用户只擁有只讀權限的文章列表</to></translation>
+<translation><from>Receive direct replies only.</from><to>只接收直接回覆.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>接收所有在<n.location/>内發佈的信息.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>接收所有該主題下的回覆.</to></translation>
+<translation><from>Receive new topics only.</from><to>只接收新主題.</to></translation>
+<translation><from>Refresh</from><to>刷新</to></translation>
+<translation><from>Registered</from><to>已注冊</to></translation>
+<translation><from>Registered Users</from><to>已注冊用户</to></translation>
+<translation><from>Register</from><to>注冊</to></translation>
+<translation><from>Registering...</from><to>注冊中...</to></translation>
+<translation><from>Register Now</from><to>現在注冊</to></translation>
+<translation><from>Register to <t.app/></from><to>注冊到 <n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>確認注冊</to></translation>
+<translation><from>Registration Failed</from><to>注冊失敗</to></translation>
+<translation><from>Related Help Article</from><to>相關幫助文檔</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>請注意禁止用户的操作不一定有效, 因為用户可以以另一個賬户重新登錄.</to></translation>
+<translation><from>Remove Ads</from><to>移除廣告</to></translation>
+<translation><from>remove</from><to>移除</to></translation>
+<translation><from>Remove Settings</from><to>移除設置</to></translation>
+<translation><from>Remove Subscription</from><to>移除訂閱</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/></from><to>移除您的賬户和在 <n.subject/> 的所有發帖</to></translation>
+<translation><from>Remove Your Account</from><to>移除您的賬户</to></translation>
+<translation><from>replies</from><to>回覆</to></translation><!-- noun / plural -->
+<translation><from>Replies</from><to>回覆</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>回覆</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>回覆</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>回覆作者</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>報告不恰當内容</to></translation>
+<translation><from>Report Now</from><to>現在報告</to></translation>
+<translation><from>required</from><to>要求</to></translation>
+<translation><from>Return to <t.location/></from><to>退回到<n.location/></to></translation>
+<translation><from>Save Changes</from><to>保存更改</to></translation>
+<translation><from>Save Settings</from><to>保存設置</to></translation>
+<translation><from>Save Subscription</from><to>保存訂閱</to></translation>
+<translation><from>Search</from><to>搜索</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>選擇下面您想進行的動作:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>選擇最能反映您對本頁内容看法的選項.</to></translation>
+<translation><from>Send email to me</from><to>向我發送电子郵件</to></translation>
+<translation><from>Send Email to <t.author/></from><to>向 <n.author/> 發送电子郵件</to></translation>
+<translation><from>Send Password</from><to>發送密碼</to></translation>
+<translation><from>Send Request</from><to>發送請求</to></translation>
+<translation><from>Send To:</from><to>發送到:</to></translation>
+<translation><from>Sexual content</from><to>色情内容</to></translation>
+<translation><from>Show</from><to>顯示</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>顯示 Nabble 提示</to></translation>
+<translation><from>Sincerely,</from><to>真誠的,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>因為這個應用是郵箱列表存檔的, 請在單擊刪除按鈕之前取消這個地址訂閱.</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>因為您不是注冊用户, 我們必須檢查一下您是否是個人類, 或者是外星生物.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>您的一些帖子被從 <n.location/> 刪除了, 我們向您發送了副本, 這樣您可以保存它們.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>抱歉, 只有論壇成員才可以在 <n.app/> 發帖.</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>抱歉, 管理員將您禁止了.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>抱歉, 該郵箱地址没有查看<n.location/> 中信息的授權.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>抱歉, 您不能在這裡發起新主題. <br/>不過您仍可以在這裡回覆主題.</to></translation>
+<translation><from>Sort by date</from><to>以時間排序</to></translation>
+<translation><from>Sort by relevance</from><to>以相關度排序</to></translation>
+<translation><from>Sorted by date</from><to>以時間排序</to></translation>
+<translation><from>Sorted by relevance</from><to>以相關度排序</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[垃圾探測器] 含有太多 '<n.text/>' 詞語, 冇效信息.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[垃圾探測器] 信息内不能含有'<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[垃圾探測器] 信息内含有常見垃圾詞滙.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[垃圾探測器] 主題不能含有'<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[垃圾探測器] 主題含有常見垃圾詞滙.</to></translation>
+<translation><from>Spam</from><to>垃圾信息</to></translation>
+<translation><from>Stars in <t.location/></from><to>在<n.location/>内的關注項目</to></translation>
+<translation><from>Structure</from><to>結構</to></translation>
+<translation><from>Subcategories</from><to>子討論區</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>在<n.location/>下的子版區</to></translation>
+<translation><from>Subcategory</from><to>子討論板</to></translation>
+<translation><from>Sub-Forum</from><to>子論壇</to></translation>
+<translation><from>Sub-Forums</from><to>子論壇</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>子論壇和主題</to></translation>
+<translation><from>Subject</from><to>主題</to></translation>
+<translation><from>Subscribe</from><to>訂閱</to></translation>
+<translation><from>Subscribe me</from><to>訂閱我</to></translation>
+<translation><from>subscriber</from><to>訂閱人</to></translation>
+<translation><from>subscribers</from><to>訂閱人</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>訂閱<n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>通過郵件訂閱</to></translation>
+<translation><from>Subscription Confirmation</from><to>訂閱確認</to></translation>
+<translation><from>Subscription Confirmed</from><to>確認訂閱</to></translation>
+<translation><from>Subscription Format</from><to>訂閱格式</to></translation>
+<translation><from>Subscription Removed</from><to>取消訂閱</to></translation>
+<translation><from>Subscription Results</from><to>訂閱結果</to></translation>
+<translation><from>Subscription Type</from><to>訂閱樣式</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>成功: 一封確認信已經發到您的郵箱.</to></translation>
+<translation><from>Success</from><to>成功</to></translation>
+<translation><from>Take Action</from><to>注意</to></translation>
+<translation><from><t.app/> Registration</from><to>注冊<n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to><n.author/>已被成功禁止.</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to><n.author/> 已被成功解禁.</to></translation>
+<translation><from>Tell me more & show examples</from><to>告訴我更多信息和展示範例</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>感謝您注冊<n.app/>!</to></translation>
+<translation><from>Thank You</from><to>謝謝</to></translation>
+<translation><from>The author has deleted this message.</from><to>作者已經刪除了這個信息.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>URL 内的代碼冇效.</to></translation>
+<translation><from>the exact phrase:</from><to>精確表述:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>郵箱列表在接受您的帖子之前可能要求您的訂閱. 請注意在 Nabble 注冊并不會讓您自動訂閱郵箱列表. 如果您還没有訂閱, 請現在訂閱. 如果您不確定或記不起來了, 只要再訂閱一遍即可, 那將不會對您有損失.</to></translation>
+<translation><from>The Nabble team</from><to>Nabble 小組</to></translation>
+<translation><from>The name of the group is not valid.</from><to>用户組名稱冇效.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>新論壇自己不能成為帖子.</to></translation>
+<translation><from>The password fields don't match.</from><to>密碼字段不匹配.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>這裡有一個郵箱地址是<n.email/>的未注冊用户.</to></translation>
+<translation><from>The subject is required.</from><to>該主題是必須的.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>用户將會通過电子郵件收到被刪除帖子的副本, 因此他/她可以有機會保存它們.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>該確認代碼與圖像不匹配.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>該分支過大, 一些帖子被省略. 用其它瀏覧方式查看所有帖子.</to></translation>
+<translation><from>This email is already subscribed.</from><to>該電子郵件已被訂閱.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>這個論壇是一個郵箱列表存檔</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>這個論壇是一個存檔/入口, 它將歡迎您發帖到 <b><n.mailing_list_address/></b> 郵箱列表.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>這裡包含了子討論區, 帖子, 圖, ,文件和其它所有東西.</to></translation>
+<translation><from>This is a mailing list archive</from><to>這是郵箱地址存檔</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>這是 Nabble 自動發送的郵件, 用於確認您新創建了<n.app/>. 如果您未創建上面提到的<n.app/>, 請通過 Nabble 支援論壇聯絡我們.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>該列表僅接受纯文本郵件</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>該列表含有注冊用户, 未注冊用户和禁止用户. 匿名用户不在其中, 因為他們没有電子郵箱地址, 因此不能加入任何用户組.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>該信息會被從<b><n.from/></b> 發送到<b><n.to/></b>郵箱列表.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to>該帖子<b>還未被</b>郵箱列表所接受.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>該帖在<n.date/>被更新.</to></translation>
+<translation><from>This topic has been locked.</from><to>該主題已被鎖定.</to></translation>
+<translation><from>This topic has been pinned.</from><to>該主題已被固定.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>該主題已被固定在<n.location/>.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>該主題已被解鎖.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>該主題已被解除固定.</to></translation>
+<translation><from>This topic has unread posts</from><to>這個主題含有未閱讀帖子</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>該主題作為優先被分配给您 <n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>該用户未獲得瀏覧這個應用的許可 (將他/她加入許可用户組, 再试一次). </to></translation>
+<translation><from>This user name is already in use.</from><to>該用户名已被使用.</to></translation>
+<translation><from>Threaded</from><to>結構樹</to></translation>
+<translation><from>Threaded View</from><to>樹状視圖</to></translation>
+<translation><from>Time</from><to>時間</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>提示: 如果您的存檔已訂閱到郵箱列表, 而它却還未發挥作用, 您應該嘗試忽略 X-No-Archive 標題.</to></translation>
+<translation><from>Tips</from><to>提示</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> 需要授權才可加入<n.location/>:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> 積分</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> 冇廣告瀏覧頁面</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>為接受這個請求, 您應該將這個用户加入到可以進入該區域的至少一個用户組裡面.</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>為了將 <n.app/> 加入您的網頁, 請將下列代碼復制并粘貼到您的 HTML 頁面:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>為確認您的訂閱, 請點擊下面的鏈接:</to></translation>
+<translation><from>topic</from><to>主題</to></translation>
+<translation><from>Topics and replies</from><to>主題和回覆</to></translation>
+<translation><from>topics</from><to>主題</to></translation>
+<translation><from>Topics</from><to>主題</to></translation>
+<translation><from>Topics only</from><to>僅主題</to></translation>
+<translation><from>Topics View</from><to> 瀏覧主題</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>為了防止垃圾郵件, 用于發帖的郵箱地址對每個用户是<b>唯一的</b>.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>為了移除用户組, 請清空以下文本框, 并且保存設置.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>為了看到哪個是您應當用來發帖的郵箱地址, 請<n.login_link.>登入</n.login_link.> 或者<n.register_link.>注冊</n.register_link.>.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>為了在<n.location/>開展一個新話題, 向 <n.p2/> 發郵件</to></translation>
+<translation><from>Total</from><to>所有的</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>取消從 <n.location/> 的訂閱</to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> 邀請您訂閱 <n.location/>:</to></translation>
+<translation><from>Turn off highlighting</from><to>關閉高亮</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/>創建一個新的子討論區</to></translation>
+<translation><from>Unable to Post</from><to>無法發帖</to></translation>
+<translation><from>Unassigned</from><to>未指定</to></translation>
+<translation><from>Unauthorized</from><to>未認証</to></translation>
+<translation><from>Unban this user</from><to>解禁該用户</to></translation>
+<translation><from>Unban User</from><to>解禁用户</to></translation>
+<translation><from>Unknown or Other</from><to>未知或其它</to></translation>
+<translation><from>Unlock topic</from><to>將主題解鎖</to></translation>
+<translation><from>Unpin topic</from><to>將主題解除固定</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>未注冊 / 無效</to></translation>
+<translation><from>Unregistered</from><to>未注冊的</to></translation>
+<translation><from>Unregistered User</from><to>未注冊用户</to></translation>
+<translation><from>Unsubscribe</from><to>取消訂閱</to></translation>
+<translation><from>Upload a file</from><to>上傳文件</to></translation>
+<translation><from>User email:</from><to>用户電子郵箱:</to></translation>
+<translation><from>user</from><to>用户</to></translation>
+<translation><from>User is online</from><to>在綫用户</to></translation>
+<translation><from>User Name</from><to>用户名</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>需要授權才可加入的用户 <n.location/></to></translation>
+<translation><from>users</from><to>用户</to></translation>
+<translation><from>Users</from><to>用户</to></translation>
+<translation><from>Users & Groups</from><to>用户和用户組</to></translation>
+<translation><from>Users that completed the registration process</from><to>完成注冊的用户</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>使用像 <n.example1/> 或 <n.example2/> 的標簽來創建子標題.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>使用下面的選項精確說明您的搜索條件.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>使用 <n.tag_names/> 嵌入其它網頁中的小窗口.</to></translation>
+<translation><from>Videos from LiveLeak.com</from><to>在 LiveLeak.com 的錄像</to></translation>
+<translation><from>Videos from Youtube.com</from><to>在 Youtube.com 的錄像</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>查看該子論壇下所有信息</to></translation>
+<translation><from>view</from><to>查看</to></translation>
+<translation><from>View mailing list website</from><to>查看郵箱列表網頁</to></translation>
+<translation><from>View message</from><to>查看信息</to></translation>
+<translation><from>View more</from><to>查看更多</to></translation>
+<translation><from>views</from><to>查看</to></translation>
+<translation><from>Views</from><to>查看</to></translation>
+<translation><from>Violent or repulsive content</from><to>暴力或惡心的内容</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>訪問<n.app/>於:</to></translation>
+<translation><from>visit <t.url/></from><to>訪問 <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>我們準備了一個頁面, 含有 <n.unsubscription_instructions_link.> 如何取消訂閱存檔 </n.unsubscription_instructions_link.> 的一些步骤.</to></translation>
+<translation><from>We will review your report soon.</from><to>我們將會很快檢查您的報告.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>哪一個用户組允許列出所有成員?</to></translation>
+<translation><from>Who can ban/unban users</from><to>誰可以禁止 / 解禁用户?</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>誰可以被分配主題? (僅在工作組中)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>誰可以更改信息的發佈日期和時間?</to></translation>
+<translation><from>Who can create new topics under this application</from><to>誰可以在這個應用下創立新話題?</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>誰可以創建子應用 (例如子論壇, 子討論板, 等等) </to></translation>
+<translation><from>Who can edit any content, both applications and posts.  Note: Please only use this feature in extreme circumstances.  Most users will not like having their posts edited by someone else.</from><to>誰可以編輯任何内容, 包括應用和帖子? 提示: 請僅在極端情况下使用此功能. 大多數用户不會歡迎别人編輯他們的帖子.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>誰可以編輯應用 (例如更改名稱和描述) ?</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>誰可以鎖定 / 解鎖該應用中的主題?</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>誰可以編輯該應用下的訂閱?</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>誰可以將信息移動到其它地方去? (例如其它主題或子論壇) </to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>誰可以固定 / 解除固定該應用中的主題?</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>誰可以没有任何限制地發佈任何内容?? (包括 javascript 代碼, &lt;object&gt; 和 &lt;style&gt; 標簽, 等等) .<b>安全警告</b>: 僅對您绝對信任的用户開啟此功能.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>誰可以回覆這個應用之下的帖子?</to></translation>
+<translation><from>Who can view this application and its contents</from><to>誰可以看到這個應用和其中的内容</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>依據您的訂閱, 更新會被直接發送到您的郵箱裡面, 您可以通過回覆來參與討論. 您的訂閱會和郵箱列表發同樣的作用.</to></translation>
+<translation><from>Write Your First Headline</from><to>編寫您的第一條標題</to></translation>
+<translation><from>Write Your First Post</from><to>發佈您的第一個帖子</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>是的, 永久刪除 <n.location/></to></translation>
+<translation><from>Yes, unsubscribe now</from><to>是的, 現在取消訂閱</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>您已訂閱 <n.location/>.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>您未訂閱 <n.location/>.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>您也可以 <n.manage_banned_users_link.> 在 <n.location/> 中編輯被禁止用户 </n.manage_banned_users_link.>.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>您也可以 <n.root_node.change_permissions_link.> 更改下列用户組的許可 </n.root_node.change_permissions_link.>.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>您也可以通過發送鏈接给您的朋友, 嵌入您自己的網頁, 或者在其他論壇上宣傳來提高您 <n.app/> 的關注度.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>您不可以將帖子移動到那裡, 因為新論壇不允許匿名用户進入.</to></translation>
+<translation><from>You Cannot Post Here</from><to>您不可以在此發帖</to></translation>
+<translation><from>(you can reply by email)</from><to>(您可以通過郵件回覆)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>您不可以移動這個帖文.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>您不可以在這裡發佈信息, 不過您可以在其它地方發佈.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>您可以嘗試 <n.register_link.> 再次注冊 </n.register_link.> 或聯絡 <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>您可以使用下列表格向管理員遽交請求.</to></translation>
+<translation><from>You have already been registered.</from><to>您已注冊了.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>您被邀請訂閱 <n.location/>, 該訂閱可在如下地方獲得:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>您已經在 <n.subject/> 注冊了.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>您已從<n.location/>取消了訂閱</to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>您在短時間內發了大量的帖子, 請過一會兒再試.</to></translation>
+<translation><from>You logged out</from><to>您已登出</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>您可能需要 <n.mailing_list_options_link.> 訂閱郵箱列表 </n.mailing_list_options_link.> 來讓您的信息被接納.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>您可能 <n.page_node.unauthorized_link.> 請求許可以發表帖文 </n.page_node.unauthorized_link.> 於此, 或者在您有疑問的時候聯絡 <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.>.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>您必須同意用户協議。</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>您必須為這個郵件列表輸入一個有效的郵箱地址.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>您必須為這個郵件列表輸入一個有效的網頁 URL.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>您必須填滿下面的所有空白.</to></translation>
+<translation><from>You must login to view this page.</from><to>您必須登錄後才可以瀏覧頁面.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>您必須登錄後才可以查閱 <n.subject/>.</to></translation>
+<translation><from>You must login to your account.</from><to>您必須登錄您的賬户.</to></translation>
+<translation><from>You must provide a user name.</from><to>您必須要提供一個用户名.</to></translation>
+<translation><from>You're not a subscriber</from><to>您不是訂閱用户</to></translation>
+<translation><from>Your Name</from><to>您的用户名</to></translation>
+<translation><from>Your password for <t.location/></from><to>您在 <n.location/> 的密碼</to></translation>
+<translation><from>Your password has been sent. Please check your email now.</from><to>您的密碼已經發送, 請查閱您的郵箱.</to></translation>
+<translation><from>Your password is <t.password/></from><to>您的密碼是 <n.password/></to></translation>
+<translation><from>Your request has been successfully sent.</from><to>您的請求已被成功發送.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>您的訂閱已被成功保存.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>您訂閱的 <n.location/> 已經被移除, 如果您進行了錯誤操作, 您可以通過單擊以下鏈接重新訂閱:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>您訂閱的 <n.location/> 已經被成功移除.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to>您的 <n.app/> 已經被成功創建.</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>您需要授權才可以在 <n.location/> 發佈新主題, 所以除了注冊之外您還需要獲得管理員的許可.</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>您將會收到含有回覆於該主題下的所有信息的郵件.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>您將會收到一封含有激活鏈接的郵件.</to></translation>
+<translation><from>Your subscription</from><to>你的訂閱</to></translation>
+<translation><from>edit</from><to>編輯</to></translation>
+<translation><from>Remove ads</from><to>移除廣告</to></translation>
+<translation><from>View profile of <t.author/></from><to>觀看 <n.author/> 的個人資料</to></translation>
+<translation><from>Description</from><to>描寫</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>視訊取自 Youtube, Vimeo 或 LiveLeak.</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>封鎖用戶於 <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>主題前綴</to></translation>
+<translation><from>Change title and meta tags</from><to>修改主題和 meta tags</to></translation>
+<translation><from>Here you can customize the title and meta tags of the <t.location/> page.</from><to>這裡你可以定制 <n.location/>的主題和 meta tags.</to></translation>
+<translation><from>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</from><to>以下的 meta 資訊可以使這網頁更優化支援網絡搜尋引擎 (例如 google, yahoo!,...)</to></translation>
+<translation><from>Use custom values</from><to>使用定制值</to></translation>
+<translation><from>Page Title</from><to>網頁主題</to></translation>
+<translation><from>Enter a context-rich title that clearly identifies this page.</from><to>填入 context-rich 主題來清楚界定此網頁.</to></translation>
+<translation><from>The title should ideally be less than 70 characters in length.</from><to>主題最佳應少於 70 英文字元.</to></translation>
+<translation><from>Meta Description</from><to>Meta 描述</to></translation>
+<translation><from>Enter a brief and concise summary of your page's content.</from><to>填入有關你網頁內容的簡介或撮要.</to></translation>
+<translation><from>Limit your description to 155 characters or 170 characters at most.</from><to>限制你的描述在 155 英文字元或最多 170 英文字元.</to></translation>
+<translation><from>Mailing List Archive</from><to>郵遞名單存檔</to></translation>
+<translation><from>Filter: priority <t.priority/></from><to>過濾: 優先 <n.priority/></to></translation>
+<translation><from>Filter: assignee <t.author/></from><to>過濾: 授理人 <n.author/></to></translation>
+<translation><from>Use Google Analytics</from><to>使用 Google Analytics</to></translation>
+<translation><from>Analytics Account ID:</from><to>Analytics 賬戶身份:</to></translation>
+<translation><from>(e.g., UA-12345-0)</from><to>(列如: UA-12345-0)</to></translation>
+<translation><from>Enter a valid analytics account ID.</from><to>請填入一個有效的 analytics 賬戶身份.</to></translation>
+<translation><from>Here you can use Google Analytics to measure the success of your app.</from><to>這裡你可以使用 Google Analytics 去評估你的 app 關注度.</to></translation>
+<translation><from>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</from><to>在以下填入你的 analytics 賬戶身份, 那麼你將可以追踪誰看過你的網頁, 訪客數以及其他有關你網站流量的重要統計數據.</to></translation>
+<translation><from>Digest Email</from><to>郵件摘要</to></translation>
+<translation><from>on <t.date/></from><to>在 <n.date/></to></translation>
+<translation><from>DO NOT REPLY TO THIS EMAIL</from><to>請勿回覆此電郵</to></translation>
+<translation><from>Replies sent to this address are not read or processed.</from><to>回覆此寄件者地址將不會被查閱或處理.</to></translation>
+<translation><from>If you want to respond to a post for which you received this email, please go to the website: <t.url/></from><to>如果你想回應此電件的寄件者, 請到這網頁: <n.url/></to></translation>
+<translation><from>new post</from><to>新信息</to></translation><!-- usage example: "1 new post"-->
+<translation><from>new posts</from><to>發表主題</to></translation><!-- usage example: "2 new posts"-->
+<translation><from>New registered user in <t.location/>!</from><to>新注冊用戶在 <n.location/>!</to></translation>
+<translation><from>User profile</from><to>用戶個人資料</to></translation>
+<translation><from>New user:</from><to>新用戶:</to></translation>
+<translation><from><n.author/> wrote</from><to><n.author/> 寫</to></translation>
+<translation><from>You still have <t.number/> day(s) left without advertising</from><to>你還有 <n.number/> 天隱藏廣告.</to></translation>
+<translation><from>(Only administrators can see this message)</from><to>(只有管理員才可看見這信息)</to></translation>
+<translation><from>Some private items have been omitted because you don't have permission to view them.</from><to>你沒有足夠權限因此部份私人內容被羼蔽.</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Submit". We will email you a link to reset your password.</from><to>請填寫你曾經用來注冊的電郵地址然後點擊 "提交". 我們將寄一封內含重置密碼連結的電郵給你.</to></translation>
+<translation><from>Submit</from><to>提交</to></translation>
+<translation><from>Password Reset Sent</from><to>密碼重置通知已寄出</to></translation>
+<translation><from>We have sent you a link to reset your password. Please check your email now. If you don't receive the instructions in a few minutes, check your spam folder or try to resend the request.</from><to>我們已寄出一封內含重置密碼連結的電郵給你. 請你現在檢查你的電郵郵箱. 如果你在數分鐘仍然未收到有關的信件, 請檢查閣下的垃圾電郵目錄或嘗試重申向我們發出重置密碼的要求.</to></translation>
+<translation><from>Reset your password / <t.location/></from><to>重置你的密碼 / <n.location/></to></translation>
+<translation><from>We received a request to reset your password in <t.location/>.</from><to>我們接收到一個你在 <n.location/> 發出的重置密碼請求.</to></translation>
+<translation><from>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</from><to>如果你想重置密碼, 點擊以下連結 (或複制和粘貼此連結到你的瀏覽器):</to></translation>
+<translation><from>If you don't want to reset your password, please ignore this message. Your password will not be reset.</from><to>如果你不想重置密碼, 請不必理會此信息. 你的密碼將不會被重置.</to></translation>
+<translation><from><b>IMPORTANT:</b> If you use this option, messages posted in the archive will NOT be sent to the mailing list.</from><to><b>重要:</b> 如果你用此選項, 曾經張貼在存檔的信息將不會轉寄到郵遞名單中.</to></translation>
+<translation><from>Message Preview</from><to>預覽</to></translation>
+<translation><from>Your changes will not be sent to the mailing list.</from><to>你的修改內容將不會轉寄到郵遞名單中.</to></translation>
+<translation><from>If you want others in the mailing list to know of your changes, please compose a new message or reply to your original message.</from><to>如果你希望每位郵遞名單中的成員都知道你修改的內容, 請你撰寫一個新信息或回覆你的原文. </to></translation>
+<translation><from>Poll</from><to>選舉</to></translation>
+<translation><from>Add new poll</from><to>增加一個選舉</to></translation>
+<translation><from>Question:</from><to>問題:</to></translation>
+<translation><from>Answers:</from><to>回答:</to></translation>
+<translation><from>Add new answer</from><to>增加一個答案</to></translation>
+<translation><from>1 vote</from><to>1 票</to></translation>
+<translation><from><t.number/> votes</from><to><n.number/> 選票</to></translation>
+<translation><from>Total votes:</from><to>Total de votos:</to></translation>
+<translation><from>Vote</from><to>投票</to></translation><!-- verb -->
+<translation><from>Your vote has been submitted.</from><to>你的選票已提交了.</to></translation>
+<translation><from>This poll is closed.</from><to>這選舉已關閉.</to></translation>
+<translation><from>This poll ends on <t.date/>.</from><to>這選舉將於 <n.date/> 結束.</to></translation>
+<translation><from>This poll ended on <t.date/>.</from><to>這選舉已於 <n.date/> 結束.</to></translation>
+<translation><from>Results will be shown only after poll has ended.</from><to>選舉完結後才可以檢視選舉結果.</to></translation>
+<translation><from>You have to vote before you can see the results.</from><to>你必需先投票才可以檢視選舉結果.</to></translation>
+<translation><from>You cannot change your vote after voting.</from><to>你不能在投完票之後更改選票內容.</to></translation>
+<translation><from>You can select up to <t.number/> options.</from><to>你可以選最多 <n.number/> 個選項.</to></translation>
+<translation><from>Please select no more than <t.number/> options.</from><to>請必選超過 <n.number/> 個選項.</to></translation>
+<translation><from>Please select at least one option.</from><to>請至少選擇一個選項.</to></translation>
+<translation><from>Remove Poll</from><to>刪除選舉</to></translation>
+<translation><from>Invalid poll parameters.</from><to>冇效的選舉參數</to></translation>
+<translation><from>Poll duration must be a non-negative integer or blank.</from><to>選舉有效時間必需是一個正整數或空白.</to></translation>
+<translation><from>Poll maximum allowed choices must be a non-negative integer or blank.</from><to>選舉最大選票數必需是一個正整數或空白.</to></translation>
+<translation><from>Delete this poll, including all votes?</from><to>刪隨此選舉, 包括所有選票?</to></translation>
+<translation><from>Poll has been deleted.</from><to>選舉已被刪除.</to></translation>
+<translation><from>Who can create polls.</from><to>誰可以投票.</to></translation>
+<translation><from>Allow vote changes</from><to>允許隨時修改選票</to></translation>
+<translation><from>Allow viewing results before end date (poll creators can always view the results)</from><to>允許在選舉限期結束前檢視選舉結果 (選舉發起人任何時候皆可檢視選舉結果)</to></translation>
+<translation><from>Allow viewing results before vote</from><to>允許投票前也可檢視選舉結果</to></translation>
+<translation><from>Multiple selections allowed:</from><to>允許一人多票:</to></translation>
+<translation><from>Poll ends after <t.number/> days (leave blank for unlimited).</from><to>選舉將於 <n.number/> 天後完結 (保持空白代表不設時限).</to></translation>
+<translation><from>Login to vote</from><to>請先登先後投票</to></translation>
+<translation><from>This message has a poll</from><to>這個信息內包含一個選舉</to></translation>
+<translation><from>Visit the link below if you want to participate:</from><to>如果你想參與請訪問以下連結:</to></translation>
+<translation><from>Current length: <t.number/> characters</from><to>目前貼文長度: <n.number/> 個字串</to></translation>
+<translation><from>Edit Post</from><to>編輯貼文</to></translation>
+<translation><from><t.location/> - Your credits are running out</from><to><n.location/> - 你的積分已消耗殆盡</to></translation>
+<translation><from><t.location/> is running out of ad-free credits.</from><to><n.location/> 已經消耗所有隱藏廣告積分了.</to></translation>
+<translation><from>If you want to buy more credits, visit:</from><to>如果你想購買更多積分, 請參觀:</to></translation>
+<translation><from>only in this topic</from><to>只有這個主題</to></translation>
+<translation><from>everywhere</from><to>任何地方</to></translation>
+<translation><from>If you want to invite subscribers, please request this feature in the <n.support_link/> forum.</from><to>如果你希望邀請訂閱者, 請向 <n.support_link/> 提出加載此功能的要求.</to></translation>
+<translation><from>We can install this module for you, but the Nabble team must approve this request to prevent abuse and spam.</from><to>我們可以安裝這個模組給你, 為防止濫用和垃圾電郵因此必需得到 Nabble 團隊授權才可. </to></translation>
+<translation><from>Edit Signature</from><to>修改簽名</to></translation>
+<translation><from>Current Signature</from><to>目前簽名</to></translation>
+<translation><from>Save Signature</from><to>儲存簽名</to></translation>
+<translation><from>Signature is in HTML format</from><to>簽名是 HTML 語法</to></translation>
+<translation><from>Download backup</from><to>下載備份</to></translation>
+<translation><from>Backup</from><to>備份</to></translation>
+<translation><from>Here you can download a backup of <t.location/>.</from><to>按這裡你可以下載 <n.location/> 的備份.</to></translation>
+<translation><from>When you press the button below, the system will start the generation of the backup file and this may take some minutes to finish. You will receive an email with a link to download the file when it is ready.</from><to>當你按以下的按鈕, 系統將會開始產生一個備份檔案而這個程過可能會花數分鐘才完成. 當備份檔案準備妥當時你將會收到一封包含有效下載備份檔案連結的電子郵件.</to></translation>
+<translation><from>Generate backup file</from><to>產生備份檔案</to></translation>
+<translation><from>The system is generating the backup now. When it is finished, a link to the backup file will be emailed to you.</from><to>系統現正產生備份, 當它準備妥當好時, 會把備份檔案的連結寄到你的電郵信箱.</to></translation>
+<translation><from>The backup file is generated with the <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a> tool, which is an open source project in Java. You should visit the project website if you want to restore your backup into a Postgresql database.</from><to>備份檔案是由 <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a> 工具產生, 她是一個 Java 的開源計劃產品. 如果你想把備份檔案轉移入 Postgresql 資料庫你務必要參觀這計劃的官方網站.</to></translation>
+<translation><from>Backup of <t.location/></from><to><n.location/> 的備份</to></translation>
+<translation><from>Here is your backup file:</from><to>這裡是你的備份檔案:</to></translation>
+<translation><from><t.location/> has been deleted</from><to><n.location/> 已被刪除</to></translation>
+<translation><from>Your Nabble site "<t.location/>" has been deleted.</from><to>你的 Nabble 網站 "<n.location/>" 已被刪除.</to></translation>
+<translation><from>You can download a backup of this site from the link below. Nabble will try to keep this backup available for a few months, but this is not guaranteed. If this content is important to you, save this copy as soon as possible.</from><to>你可以在以下連結下載一份此網站的備份. Nabble 會嘗試保留此備份若數個月, 但這並不是個承諾. 如果此網站的內容對你非常重要, 請你盡早儲存一份備份.</to></translation>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_cs_cz.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,769 @@
+## Czech (Czech Republic)
+## Translated by Dave Luv and Sonia from Arsepo Team
+
+## MONTHS ##
+
+<translation><from>January</from><to>Leden</to></translation>
+<translation><from>February</from><to>Únor</to></translation>
+<translation><from>March</from><to>Březen</to></translation>
+<translation><from>April</from><to>Duben</to></translation>
+<translation><from>May</from><to>Květen</to></translation>
+<translation><from>June</from><to>Červen</to></translation>
+<translation><from>July</from><to>Červenec</to></translation>
+<translation><from>August</from><to>Srpen</to></translation>
+<translation><from>September</from><to>Září</to></translation>
+<translation><from>October</from><to>Říjen</to></translation>
+<translation><from>November</from><to>Listopad</to></translation>
+<translation><from>December</from><to>Prosinec</to></translation>
+
+<translation><from>Jan</from><to>Led</to></translation>
+<translation><from>Feb</from><to>Úno</to></translation>
+<translation><from>Mar</from><to>Bře</to></translation>
+<translation><from>Apr</from><to>Dub</to></translation>
+<!--translation><from>May</from><to>Kvě</to></translation-->
+<translation><from>Jun</from><to>Čvn</to></translation>
+<translation><from>Jul</from><to>Čvc</to></translation>
+<translation><from>Aug</from><to>Srp</to></translation>
+<translation><from>Sep</from><to>Zář</to></translation>
+<translation><from>Oct</from><to>Říj</to></translation>
+<translation><from>Nov</from><to>Lis</to></translation>
+<translation><from>Dec</from><to>Pro</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>Zneužívání této funkce je také porušení našich Smluvních podmínek.</to></translation>
+<translation><from>Access Request</from><to>Žádost o přístup</to></translation>
+<translation><from>Account settings</from><to>Nastavení účtu</to></translation>
+<translation><from>Account Settings</from><to>Nastavení účtu</to></translation>
+<translation><from>Action</from><to>Akce</to></translation>
+<translation><from>Add a link to another page</from><to>Přidat odkaz na jinou stránku</to></translation>
+<translation><from>Add a new comment</from><to>Přidat nový komentář</to></translation>
+<translation><from>Add a new group</from><to>Přidání nové skupiny</to></translation>
+<translation><from>Add an image to your post</from><to>Přidat obrázek k příspěvku</to></translation>
+<translation><from>Add another address</from><to>Přidat další adresu</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>Přidat obsah, který by neměl být kódován (např. tag code)</to></translation>
+<translation><from>Adding Sub-Headers</from><to>Přidání podokruhů</to></translation>
+<translation><from>Add New Group</from><to>Přidat novou skupinu</to></translation>
+<translation><from>Add / Remove Groups</from><to>Přidat / Odebrat skupiny</to></translation>
+<translation><from>Add smileys and funny animations</from><to>Přidat smajlíky a vtipné animace</to></translation>
+<translation><from>Add Subscribers</from><to>Přidat odběratele</to></translation>
+<translation><from>Add this item to your favorites list</from><to>Přidat tento příspěvek do seznamu oblíbených položek</to></translation>
+<translation><from>Administrator</from><to>Administrátor</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity, other sexual content.</from><to>Obsah pro dospělé, sexuání aktivita, nahota, ostatní sexuálně zaměřený obsah.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>Souboje dospělých, fyzické útoky, násilí mladých, týrání zvířat nebo propagace terorismu.</to></translation>
+<translation><from>Advanced Search</from><to>Rozšířené hledání</to></translation>
+<translation><from>Advanced Settings</from><to>Pokročilá nastavení</to></translation>
+<translation><from>Advertisement</from><to>Reklama</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>Upozornit mě e-mailem, když někdo přispěje do tohoto tématu</to></translation>
+<translation><from>All</from><to>Vše</to></translation>
+<translation><from>all of the words:</from><to>všechna tato slova:</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>Všechny příspěvky od autora <n.author/> byly úspěšně odstraněny.</to></translation>
+<translation><from>All posts</from><to>Všechny příspěvky</to></translation>
+<translation><from>All users belong to this group</from><to>Všichni uživatelé patří do této skupiny</to></translation>
+<translation><from>All users</from><to>Všichni uživatelé</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>Všichni uživatelé registrování do sekce <n.location/>. Zadali platnou e-mailovou adresu a jsou schopni se přihlásit do systému.</to></translation>
+<translation><from>Already Subscribed</from><to>Už jste odběratel</to></translation>
+<translation><from>An email has been sent to you.</from><to>Byl vám odeslán e-mail.</to></translation>
+<translation><from>Anonymous</from><to>Anonymní</to></translation>
+<translation><from>anonymous user</from><to>anonymní uživatel</to></translation>
+<translation><from>anonymous users</from><to>anonymní uživatelé</to></translation>
+<translation><from>Any message part contains</from><to>Jakákoliv část příspěvku obsahuje</to></translation>
+<translation><from>Application</from><to>Aplikace</to></translation>
+<translation><from>Apps</from><to>Aplikace</to></translation>
+<translation><from>Assignee</from><to>Přiřazeno komu</to></translation>
+<translation><from>Assign</from><to>Přiřadit</to></translation>
+<translation><from>Assignment</from><to>Přiřazení</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>E-mailová adresa, pod kterou zakládáte nová témata do <n.app/> je:</to></translation>
+<translation><from>at least one of the words:</from><to>alespoň jedno ze slov:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to>Číst <n.location/> v Atomu</to></translation>
+<translation><from>at priority</from><to>s prioritou</to></translation>
+<translation><from>Authorized Users Only</from><to>Jen autorizovaní uživatelé</to></translation>
+<translation><from>Author name</from><to>Jméno autora</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>Vyhněte se poznámkám typu "upe kool", "boží"... Jestli chcete, můžete <n.page_node.reply_to_author_link.> poslat soukromou zprávu </n.page_node.reply_to_author_link.> .</to></translation>
+<translation><from>Banned User</from><to>Zabanovaný uživatel</to></translation>
+<translation><from>Ban this user</from><to>Zabanuj tohoto uživatele</to></translation>
+<translation><from>Ban User</from><to>Zabanuj uživatele</to></translation>
+<translation><from>Before deleting this archive...</from><to>Před vymazáním tohoto archivu ...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>Níže můžete spravovat skupiny a uživatele. Můžete kopírovat a vkládat uživatele a přesunovat je z jedné skupiny do druhé.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>DŮLEŽITÉ</b>: Nabble zašle pozvánku na každý e-mail ze seznamu. Uživatelé budou muset kliknout na odkaz pro potvrzení jejich odběru.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>DŮLEŽITÉ:</b> Tento odběr je nezávislý na skutečném mailing listu. Přihlašujete se k odběru archivu fóra, nikoliv k vlastnímu mailing listu. Odběr archivu nezajišťuje, že vaše příspěvky budou přijaty na mailing list.</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>DŮLEŽITÉ</b>: Aby vše fungovalo, musíte tento archiv přihlásit do mailing listu.</to></translation>
+<translation><from>blog</from><to>blog</to></translation>
+<translation><from>Blog</from><to>Blog</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>Poznámka</b>: Jelikož jste administrátor, můžete <n.page_node.change_permissions_link.>měnit úroveň přístupu <n.location/></n.page_node.change_permissions_link.> - zkontrolujte, že zde můžete zakládat nová témata.</to></translation>
+<translation><from>board</from><to>vývěska</to></translation>
+<translation><from>Board</from><to>Vývěska</to></translation>
+<translation><from>Bold</from><to>Tučné</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>Varování:</b> Probíhá indexování obsahu. Výsledky vyhledávání proto mohou být neúplné.</to></translation>
+<translation><from>by <t.author/></from><to>od <n.author/></to></translation>
+<translation><from>Cancel</from><to>Zrušit</to></translation>
+<translation><from>category</from><to>kategorie</to></translation>
+<translation><from>Category</from><to>Kategorie</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>UPOZORNĚNÍ: Tuto akci nelze vrátit.</to></translation>
+<translation><from>Change appearance</from><to>Změna vzhledu</to></translation>
+<translation><from>Change application type</from><to>Změna typu aplikace</to></translation>
+<translation><from>Change Application Type</from><to>Změna typu aplikace</to></translation>
+<translation><from>Change code image</from><to>Změnit základní obrázek (?)</to></translation>
+<translation><from>Change domain name</from><to>Změnit název domény</to></translation>
+<translation><from>Change language</from><to>Změna jazyka</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>Změň nebo odstraň svého avatara.</to></translation>
+<translation><from>Change parent</from><to>Změna adresáře</to></translation>
+<translation><from>Change permissions</from><to>Změnit oprávnění</to></translation>
+<translation><from>Change Permissions</from><to>Změnit oprávnění</to></translation>
+<translation><from>Change post date</from><to>Změnit datum</to></translation>
+<translation><from>Change Post Date</from><to>Změnit datum</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>Změna podpisu, který se zobrazí ve spodní části vašich příspěvků.</to></translation>
+<translation><from>Change User Groups</from><to>Změna uživatelských skupin</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>Změna zobrazení a další nastavení.</to></translation>
+<translation><from>Change Your Picture</from><to>Změna vašeho obrázku</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>Změna vašeho registračního e-mailu, hesla a uživatelského jména.</to></translation>
+<translation><from>Child abuse</from><to>Zneužívání dětí</to></translation>
+<translation><from>Choose a subcategory</from><to>Vyberte si kategorii</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>Vyberte si kategorii k napsání příspěvku</to></translation>
+<translation><from>Choose the best offer for you</from><to>Vyberte si pro Vás nejlepší nabídku</to></translation>
+<translation><from>Classic</from><to>Klasický</to></translation>
+<translation><from>Clear Log</from><to>Vymazat Log</to></translation>
+<translation><from>Click for more options</from><to>Klikněte pro více možností</to></translation>
+<translation><from>click here</from><to>zde klikněte</to></translation>
+<translation><from>Click here to make your first post</from><to>Zde klikněte pro napsání prvního příspěvku</to></translation>
+<translation><from>Click to filter</from><to>Klikněte pro filtrování</to></translation>
+<translation><from>Close</from><to>Zavřít</to></translation>
+<translation><from>Close this message</from><to>Zavřít tento příspěvek</to></translation>
+<translation><from>comment</from><to>komentář</to></translation>
+<translation><from>Comment</from><to>Komentář</to></translation>
+<translation><from>comments</from><to>komentáře</to></translation>
+<translation><from>Comments</from><to>Komentáře</to></translation>
+<translation><from>Confirm Password</from><to>Potvrďte heslo</to></translation>
+<translation><from>Confirm Subscription</from><to>Potvrďte přihlášení</to></translation>
+<translation><from>Congratulations!</from><to>Gratulujeme!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>Gratulujeme k vašemu novému <n.app/>!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>Obsah poporující nenávist či násilí, obtěžování, rasovou nesnášenlivost, štvavou kampaň proti jednotlivci, skupině či organizaci nebo přehnané pomluvy.</to></translation>
+<translation><from>CONTENTS DELETED</from><to>OBSAH BYL VYMAZÁN</to></translation>
+<translation><from>Continue</from><to>Pokračovat</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>Porušení soukromí či autorských práv, případně jiný právní důvod.</to></translation>
+<translation><from>Count</from><to>Počet</to></translation>
+<translation><from>Created by <t.author/></from><to>Vytvořil <n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>Vytvořit nové <n.element/></to></translation>
+<translation><from>Create <t.element/></from><to>Vytvořit <n.element/></to></translation>
+<translation><from>Current Credits</from><to>Zbylý kredit</to></translation>
+<translation><from>Currently Nabble supports</from><to>Nabble supporteři</to></translation>
+<translation><from>Current Subscribers</from><to>Odběratelé</to></translation>
+<translation><from>Daily digest</from><to>Denní přehled</to></translation>
+<translation><from>Data successfully saved</from><to>Data jsou úspěšně uložena</to></translation>
+<translation><from>Date</from><to>Datum</to></translation>
+<translation><from>days</from><to>dny</to></translation>
+<translation><from>Dear <t.name/>,</from><to>Vážený(á) <n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>Vážený uživateli,</to></translation>
+<translation><from>Default</from><to>Výchozí</to></translation>
+<translation><from>Delete all posts from this user</from><to>Odstranit všechny příspěvky od tohoto uživatele</to></translation>
+<translation><from>Delete Application</from><to>Odstranit aplikaci</to></translation>
+<translation><from>Deleted posts</from><to>Odstraněné příspěvky</to></translation>
+<translation><from>Delete</from><to>Odstranit</to></translation>
+<translation><from>Delete this post and replies</from><to>Smazat tento příspěvek a odpovědi</to></translation>
+<translation><from>Delete this post</from><to>Smazat tento příspěvek</to></translation>
+<translation><from>Delete this topic</from><to>Smazat toto téma</to></translation>
+<translation><from>Description is in HTML Format</from><to>Popis je ve formátu HTML</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>Nevkládejte vícekrát stejný příspěvek. Však on už si ho někdo přečte.</to></translation>
+<translation><from>Don't show this message again</from><to>Nezobrazovat znovu tuto zprávu</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>Opravdu chcete odstranit tento příspěvek?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>Opravdu chcete trvale odstranit tento příspěvek a všechny odpovědi?</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>Opravdu chcete trvale <n.important.>odstranit</n.important.> <n.location/>?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>Chcete opravdu odstranit nastavení archivu mailing listu?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>Chcete opravdu přihlásit k odběru <n.location/>?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>Opravdu chcete odbanovat tohoto uživatele?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>Chcete se opravdu odhlásit z odběru <n.location/>?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>Zneužívání drog, nabízení piva, tvrdého alkoholu, kuřiva či podobných výrobků, zneužívání zbraní či výbušnin nebo podobné nebezpečné chování. </to></translation>
+<translation><from>Edit name & description</from><to>Upravit název a popis</to></translation>
+<translation><from>Edit Name & Description</from><to>Upravit název a popis</to></translation>
+<translation><from>Editor</from><to>Editor</to></translation>
+<translation><from>Edit Personal Information</from><to>Upravit osobní údaje</to></translation>
+<translation><from>Edit post</from><to>Upravit příspěvek</to></translation>
+<translation><from>Edit Subscription</from><to>Upravit odběr</to></translation>
+<translation><from>Edit Your Signature</from><to>Upravit podpis</to></translation>
+<translation><from>Email Confirmation</from><to>Potvrzení e-mailu</to></translation>
+<translation><from>Email for <t.app/></from><to>E-mail pro <n.app/></to></translation>
+<translation><from>Email</from><to>E-mail</to></translation>
+<translation><from>Email Subscription</from><to>E-mailový odběr</to></translation>
+<translation><from>Email this post to...</from><to>Pošli e-mailem tento příspěvek na...</to></translation>
+<translation><from>Embedding Contents</from><to>Sdílení obsahu</to></translation>
+<translation><from>Embedding options</from><to>Možnosti sdílení</to></translation>
+<translation><from>Embed</from><to>Sdílet</to></translation>
+<translation><from>Embed post</from><to>Sdílet příspěvek</to></translation>
+<translation><from>Embed Tags</from><to>Sdílet kód</to></translation>
+<translation><from>Embed this <t.app/></from><to>Sdílet tento <n.app/></to></translation>
+<translation><from>Empty</from><to>Prázdný</to></translation>
+<translation><from>Enter a valid email address.</from><to>Vložte platný e-mail.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>Zadejte níže svou e-mailovou adresu a my vám zašleme potvrzovací e-mail .</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>Na (každý) řádek zadejte jednu e-mail adresu nebo jedno uživatelské jméno:</to></translation>
+<translation><from>Enter one user per row</from><to>Zadejte jedno uživatelské jméno na řádek</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>Zadejte trvalý odkaz <b>příspěvku</b> nebo <b>fóra</b>, které se stane nadřazeným, nebo pole ponechejte prázdné, čímž se tato zpráva stane nezávislým tématem:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>Zadejte adresu mailing listu, kde uživatelé mohou najít více informací.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>Zadejte výraz, který mailing list přidává na začátek předmětu. Tento výraz bude automaticky odstaněn z importovaných emailů.</to></translation>
+<translation><from>Enter your email address</from><to>Zadejte váš e-mail</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>Příklad: jan.novak@adresa.cz</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>Příklad: nazev@adresa.cz</to></translation>
+<translation><from>Examples:</from><to>Příklady:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>Příklady: '[seznam]', 'Abc:' atd.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>Vysvětlete administrátorovi, proč chcete přístup do této omezené oblasti.</to></translation>
+<translation><from>Explanation from this user:</from><to>Vysvětlení tohoto uživatele:</to></translation>
+<translation><from>Extras & add-ons</from><to>Speciality a doplňky</to></translation>
+<translation><from>Failed</from><to>Selhal(o)</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>Oblíbené (kliknutím odstraníte tuto položku ze seznamu Oblíbených)</to></translation>
+<translation><from>Feeds</from><to>Čtečky</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>Soubor nebyl nahrán: <n.file/> (nahrejte jej prosím znovu či odstraňte tag)</to></translation>
+<translation><from>Filter by group</from><to>Filtrovat podle skupiny</to></translation>
+<translation><from>Floating sub-forum</from><to>Plovoucí podfórum</to></translation>
+<translation><from>Forgot Password?</from><to>Zapomněli jste heslo?</to></translation>
+<translation><from>Forgot your password?</from><to>Zapomněli jste vaše heslo?</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>Pro více informací viz: <n.info/></to></translation>
+<translation><from>forum</from><to>fórum</to></translation>
+<translation><from>Forum</from><to>Fórum</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to><n.app/>Možné sdílet</to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>Od tohoto okamžiku vám přijde e-mail pro každý příspěvek zadaný v <n.location/>.</to></translation>
+<translation><from>gallery</from><to>galerie</to></translation>
+<translation><from>Gallery</from><to>Galerie</to></translation>
+<translation><from>Gambling or casino-related content</from><to>Gamblerství nebo hazard</to></translation>
+<translation><from>Go back</from><to>Krok zpět</to></translation>
+<translation><from>Go to next message</from><to>Jdi na další příspěvek</to></translation>
+<translation><from>Group Name:</from><to>Název skupiny:</to></translation>
+<translation><from>Groups</from><to>Skupiny</to></translation>
+<translation><from>Groups of this user</from><to>Skupiny tohoto uživatele</to></translation>
+<translation><from>Hacking / cracking</from><to>Hacking / cracking</to></translation>
+<translation><from>Harmful dangerous acts</from><to>Nebezpečné jednání</to></translation>
+<translation><from>Hateful or abusive content</from><to>Nenávistný či obtěžující obsah</to></translation>
+<translation><from>Help</from><to>Nápověda</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>Zde si můžete zakoupit kredity pro zobrazení stránek bez reklam. Každý kredit znamená jedno zobrazení stránky bez reklamy. Návštěvníci uvidí stránky bez reklamy, dokud vám zbývá kredit. Jakmile budete na nule, reklamy se zase začnou zobrazovat.</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>Zde můžete před uživateli skrýt inforamce o archivním mailing listu. Tím nahradíte svůj server pro mailing list výchozím nastavením e-mailových odběrů od Nabble.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>Schovat e-mailovou adresu (např. místo paran@adresa.cz bude vidět <n.lt/>skrytý email<n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>Skrýt e-mail</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>Skrýt informaci o archivním mailing listu před uživateli</to></translation>
+<translation><from>Highest</from><to>Nevyšší</to></translation>
+<translation><from>High</from><to>Vysoká</to></translation>
+<translation><from>Hi <t.name/>,</from><to>Dobrý den <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>Pokud je toto vaše e-mailová adresa, dokončete <n.register_link.>registraci</n.register_link.> za použití stejné adresy. Po dokončení budete vlastníkem tohoto uživatelského účtu.</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>Pokud vám nechodí e-maily od Nabble, prosím zkontrolujte svůj spamový koš.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>Pokud chcete položit otázku, zkuste nejprve prohledat fórum. Je možné, že váš dotaz zde již byl zodpovězen.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>Pokud stále nejste členem, můžete se <n.register_link.><b>nyní zaregistrovat</b></n.register_link.>.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>Pokud zabanujete tohoto uživatele, nebude schopen cokoliv provést v <n.location/>.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>Pokud netušíte, proč jste obdrželi tento registrační e-mail, prosím ignorujte jej. Zřejmě někdo omylem použil vaši e-mailovou adresu.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>Pokud se ještě nechcete zaregistrovat, zadejte alespoň e-mailovou adresu, pod kterou chcete psát příspěvky - vaše osobní adresa vám přijde e-mailem.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>Pokud jste zrušili odběr, můžete začít s mazáním.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>Zadejte aplikaci + verzi serveru pro mailing list, pokud tyto údaje znáte.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>Pokud jste se odhlásili omylem, <n.login_link.>přihlašte se</n.login_link.> prosím znovu.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>Pokud odpovíte na tento e-mail, vaše zpráva bude přidána do diskuze níže</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>Pokud odbanujete tohoto uživatele, bude schopen psát příspěvky do <n.location/> .</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>Pokud se ve skutečnosti chcete přihlásit k odběru mailing listu, <n.mailing_list_options_link.>navštivte tuto stránku</n.mailing_list_options_link.>.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>Ignorovat hlavičku X-No-Archive</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>Přečetl jsem si a souhlasím se <n.terms_link.>Smluvními podmínkami</n.terms_link.> Nabble.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>Obrázek nebyl nahrán: <n.image/> (nahrejte jej prosím znovu nebo odstraňte tag)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>Jsem přihlášen k odběru, umožněte mi psát příspěvky</to></translation>
+<translation><from>Incorrect Login!</from><to>Neplatné přihlášení!</to></translation>
+<translation><from>Individual emails</from><to>E-maily</to></translation>
+<translation><from>Inherit</from><to>Počátek</to></translation>
+<translation><from>In Reply To</from><to>Odpověď na</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>Odpověď na <n.parent_link.>tento příspěvek</n.parent_link.> od <n.author/></to></translation>
+<translation><from>Insert</from><to>Vložit</to></translation>
+<translation><from>Insert Image</from><to>Vložit obrázek</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>Místo vkládání příspěvků přes webové rozhraní můžete nové příspěvky zasílat e-mailem na následující adresu:</to></translation>
+<translation><from>in <t.location/></from><to>v <n.location/></to></translation>
+<translation><from>Invalid Code</from><to>Špatný kód</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>Špatná e-mailová adresa: <n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>Špatný počet dnů - musí to být celé číslo.</to></translation>
+<translation><from>invisible user</from><to>skrytý uživatel</to></translation>
+<translation><from>invisible users</from><to>skrytí uživatelé</to></translation>
+<translation><from>Invite Subscribers</from><to>Pozvat odběratele</to></translation>
+<translation><from>is:</from><to>je:</to></translation>
+<translation><from>is not:</from><to>není:</to></translation>
+<translation><from>is within the last:</from><to>během posledních:</to></translation>
+<translation><from>Italic</from><to>Kurzíva</to></translation>
+<translation><from>item</from><to>položka</to></translation>
+<translation><from>items</from><to>položky</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>Smazané položky nebude možné později obnovit.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>Jednoduše vložte kód (poskytnutý stránkami nahoře) do těchto tagů - a můžete přispívat.</to></translation>
+<translation><from>Last Post</from><to>Poslední příspěvek</to></translation>
+<translation><from>Learn more</from><to>Další informace</to></translation>
+<translation><from>Leave a comment</from><to>Zanechat komentář</to></translation>
+<translation><from>Link</from><to>Odkaz</to></translation>
+<translation><from>Link to <t.location/></from><to>Odkaz na <n.location/></to></translation>
+<translation><from>List</from><to>Seznam</to></translation>
+<translation><from>List of Subcategories</from><to>Seznam kategorií</to></translation>
+<translation><from>List Server</from><to>Obsah serveru</to></translation>
+<translation><from>List View</from><to>Zobrazit seznam</to></translation>
+<translation><from>Loading...</from><to>Nahrávám ....</to></translation>
+<translation><from>Location</from><to>Umístění</to></translation>
+<translation><from>Locked</from><to>Zamčeno</to></translation>
+<translation><from>Lock topic</from><to>Zamknout téma</to></translation>
+<translation><from>Login</from><to>Přihlášení</to></translation>
+<translation><from>Log is empty</from><to>Log je prázdný</to></translation>
+<translation><from>Log out</from><to>Odhlásit</to></translation>
+<translation><from>Lowest</from><to>Nejnižší</to></translation>
+<translation><from>Low</from><to>Nízká</to></translation>
+<translation><from>Mailing List Address</from><to>Adresa mailing listu</to></translation>
+<translation><from>Mailing list archive settings</from><to>Nastavení archivu mailing listu</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>Nastavení archivu mailing listu</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>Připomenutí odběru mailing listu</to></translation>
+<translation><from>Mailing List Website</from><to>Webová stránka mailing listu</to></translation>
+<translation><from>Main Page</from><to>Hlavní stránka</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>Použijte stejný prohlížeč, přes který jste vyplňovali registrační formulář.</to></translation>
+<translation><from>Manage banned users</from><to>Správa zabanovaných uživatelů</to></translation>
+<translation><from>Manage pinned topics</from><to>Správa připnutých témat</to></translation>
+<translation><from>Manage subscribers</from><to>Správa odběratelů</to></translation>
+<translation><from>Manage Subscribers</from><to>Správa odběratelů</to></translation>
+<translation><from>Manage <t.items/></from><to>Správa <n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>Správa uživatelů a skupin</to></translation>
+<translation><from>Manage Users & Groups</from><to>Správa uživatelů a skupin</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>Zadávání reklamy, zavádějící, klamavé či lživé texty.</to></translation>
+<translation><from>max. 80 characters</from><to>max. 80 znaků</to></translation>
+<translation><from>Message date</from><to>Datum příspěvku</to></translation>
+<translation><from>message</from><to>zpráva</to></translation>
+<translation><from>Message</from><to>Zpráva</to></translation>
+<translation><from>Message is in HTML Format</from><to>Příspěvek je v HTML formátu</to></translation>
+<translation><from>messages</from><to>zpráv</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>Zde vložené příspěvky budou zaslány na mailing list.</to></translation>
+<translation><from>Message subject contains</from><to>Předmět zprávy obsahuje</to></translation>
+<translation><from>Message text contains</from><to>Text zprávy obsahuje</to></translation>
+<translation><from>Mixed</from><to>Kombinovaný</to></translation>
+<translation><from>Modified</from><to>Upravený</to></translation>
+<translation><from>Monthly Archives</from><to>Měsíční archiv</to></translation>
+<translation><from>More Categories</from><to>Více kategorií</to></translation>
+<translation><from>More</from><to>Více</to></translation>
+<translation><from>more help</from><to>další nápověda</to></translation>
+<translation><from>more options</from><to>více možností</to></translation>
+<translation><from>Move post</from><to>Přesunout příspěvek</to></translation>
+<translation><from>Move Post</from><to>Přesunout příspěvek</to></translation>
+<translation><from>Move topic</from><to>Přesunout téma</to></translation>
+<translation><from>My Nabble Applications</from><to>Moje Nabble aplikace</to></translation>
+<translation><from>My Pending Posts</from><to>Moje nevyřízené příspěvky</to></translation>
+<translation><from>My posts</from><to>Moje příspěvky</to></translation>
+<translation><from>Nabble Support</from><to>Nabble podpora</to></translation>
+<translation><from>Name</from><to>Jméno</to></translation>
+<translation><from>New Post</from><to>Nový příspěvek</to></translation>
+<translation><from>news</from><to>novinky</to></translation>
+<translation><from>News</from><to>Novinky</to></translation>
+<translation><from>New Topic</from><to>Nové téma</to></translation>
+<translation><from>New topics only</from><to>Jen nová témata</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>UPOZORNĚNÍ</n.important.>: Vše v <n.location/> bude smazáno navždy!</to></translation>
+<translation><from>No banned users.</from><to>Žádní zabanovaní uživatelé.</to></translation>
+<translation><from>No Filter</from><to>Vypnutý filtr</to></translation>
+<translation><from>none of the words:</from><to>žádné z těchto slov:</to></translation>
+<translation><from>No registered user found with this email.</from><to>Žádný registrovaný uživatel nemá tento e-mail.</to></translation>
+<translation><from>No replies</from><to>Žádné odpovědi</to></translation>
+<translation><from>Normal</from><to>Normální</to></translation>
+<translation><from>No sub-forums</from><to>Žádná podfóra</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>Tato adresa je pro každého uživatele jiná a lze na ni zasílat e-maily jen z <n.address/>. Je to tak vyřešeno jako ochrana před spamem.</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to><n.register_link.>Zaregistrujte se</n.register_link.> pokud si chcete vyklikat svůj profil, přijímat příspěvky e-mailem, mít možnost zadat svoje oblíbené položky nebo mít přístup ke svému globálnímu profilu.</to></translation>
+<translation><from>one email per input box</from><to>jeden e-mail na schránku</to></translation>
+<translation><from>Online Users</from><to>Uživatelé online</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>Pouze pro schválené uživatele.</to></translation>
+<translation><from>Open this post in classic view</from><to>Otevřít tento příspěvek v klasickém zobrazení</to></translation>
+<translation><from>Open this post in list view</from><to>Otevřít tento příspěvek v zobrazení seznam</to></translation>
+<translation><from>Open this post in threaded view</from><to>Otevřít tento příspěvěk v zobrazení vlákno </to></translation>
+<translation><from>Options</from><to>Volby</to></translation>
+<translation><from>or</from><to>nebo</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>Nebo můžete tento e-mail ignorovat, pokud nechcete uživateli povolit přístup do této oblasti.</to></translation>
+<translation><from>Other Settings</from><to>Další nastavení</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>Stránka slučující podfóra a diskuze.</to></translation>
+<translation><from>Page <t.number/></from><to>Strana <n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>Stránka s jednoduchým seznamem subfór a diskuzí.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>Stránka s úplnými příspěvky a jejich komentáři.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>Stránka s nadpisy a příspěvky.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>Stránka s tématy a diskuzemi.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>Stránka s tématy sloučenými do podfór. Pokud podfórum neexistuje, je zobrazen běžný seznam témat.</to></translation>
+<translation><from>Password</from><to>Heslo</to></translation>
+<translation><from>Password Sent</from><to>Heslo odesláno</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>Pedofílie, násilí a jiné obtěžování.</to></translation>
+<translation><from>People</from><to>Lidé</to></translation>
+<translation><from>People in <t.location/></from><to>Lidé v <n.location/></to></translation>
+<translation><from>Permalink</from><to>Trvalý odkaz</to></translation>
+<translation><from>Photo and image gallery.</from><to>Fotogalerie.</to></translation>
+<translation><from>Pinned sub-forum</from><to>Připnuté podfórum</to></translation>
+<translation><from>Pin topic</from><to>Připnout téma</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>Přidejte si prosím odkaz výše do oblíbených položek či si tento e-mail uložte - abyste i v budoucnu snadno našli <n.app/>.</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>Prosím podívejte se do své pošty a aktivujte si svůj účet, abyste měli přístup ke všem funkcím.</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>Prosím klikněte na potvrzující odkaz níže, čímž aktivujete váš účet:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>Pokud potřebujete pomoc, kontaktujte prosím podporu Nabble.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>Pokud potřebujete pomoc, kontaktujte prosím administrátory.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>Zadejte prosím platnou e-mailovou adresu a zkuste to znovu.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>Pro dokončení registrace prosím postupujte podle pokynů v e-mailu.</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>Řiďte se prosím <n.subscribe_instructions_link.>pokyny k odběru</n.subscribe_instructions_link.> pro tento archív.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>Zadejte prosím správný trvalý odkaz.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>Zadejte prosím znovu svůj e-mail / heslo a klikněte na Přihlášení.</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>Prosím dodržujte zásady slušnosti při používání mailing listu.</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>Ankety z Polldaddy.com (pouze ankety ve flashi)</to></translation>
+<translation><from>Post by email</from><to>Odeslat e-mailem</to></translation>
+<translation><from>Post by Email</from><to>Odeslat e-mailem</to></translation>
+<translation><from>Post Count</from><to>Počet příspěvků</to></translation>
+<translation><from>Posted by <t.author/></from><to>Vystavil <n.author/></to></translation>
+<translation><from>post</from><to>příspěvek</to></translation>
+<translation><from>Post Message</from><to>Odeslat příspěvek</to></translation>
+<translation><from>Post New Message</from><to>Odeslat nový příspěvek</to></translation>
+<translation><from>Post new message in <t.location/></from><to>Přidat příspěvek do <n.location/></to></translation>
+<translation><from>posts</from><to>příspěvky</to></translation>
+<translation><from>Posts</from><to>Příspěvky</to></translation>
+<translation><from>Posts in <t.location/></from><to>Příspěvky v <n.location/></to></translation>
+<translation><from>Preview Message</from><to>Náhled příspěvku</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>Ceny jsou v USD. Je použita služba <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, která umožňuje zákazníkům Amazonu použít metodu platby uloženou v jejich účtu na Amazon.com.</to></translation>
+<translation><from>Print post</from><to>Vytisknout příspěvek</to></translation>
+<translation><from>Priority</from><to>Priorita</to></translation>
+<translation><from>(private)</from><to>(soukromé)</to></translation>
+<translation><from>Profile of <t.author/></from><to>Profil <n.author/></to></translation>
+<translation><from>Quote</from><to>Citace</to></translation>
+<translation><from>Quote the original message</from><to>Citovat původní příspěvek</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>Zkraťte citaci tak, aby obsahovala jen část, na kterou skutečně odpovídáte - pomůže to lidem, kteří čtou vaši zrávu v e-mailu.</to></translation>
+<translation><from>Raw mail</from><to>Základní e-mail</to></translation>
+<translation><from>Raw text</from><to>Obyčejný text</to></translation>
+<translation><from>read more</from><to>číst dále</to></translation>
+<translation><from>Read more</from><to>Číst dále</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>Seznam jen pro čtení - všichni uživatelé s e-mailem v <n.location/>.</to></translation>
+<translation><from>Receive direct replies only.</from><to>Přijímat jen přímé odpovědi.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>Přijímat všechny zprávy zadané v <n.location/>.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>Přijímat všechny odpovědi v tomto tématu.</to></translation>
+<translation><from>Receive new topics only.</from><to>Přijímat jen nová témata.</to></translation>
+<translation><from>Refresh</from><to>Znovu načíst</to></translation>
+<translation><from>Registered</from><to>Registrovaných</to></translation>
+<translation><from>Registered Users</from><to>Registrovaní uživatelé</to></translation>
+<translation><from>Register</from><to>Registrovat se</to></translation>
+<translation><from>Registering...</from><to>Registruji...</to></translation>
+<translation><from>Register Now</from><to>Zaregistrovat se nyní</to></translation>
+<translation><from>Register to <t.app/></from><to>Registrovat se do <n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>Registrace potvrzena</to></translation>
+<translation><from>Registration Failed</from><to>Registrace selhala</to></translation>
+<translation><from>Related Help Article</from><to>Vztažený článek v nápovědě</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>Banování nemusí být spolehlivé - uživatel se vždy může přihlásit pod jiným účtem.</to></translation>
+<translation><from>Remove Ads</from><to>Odstranit reklamy</to></translation>
+<translation><from>remove</from><to>odstranit</to></translation>
+<translation><from>Remove Settings</from><to>Odstranit nastavení</to></translation>
+<translation><from>Remove Subscription</from><to>Odhlásit se z odběru</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>Odstranit váš účet a všechny vaše příspěvky z <n.subject/>.</to></translation>
+<translation><from>Remove Your Account</from><to>Odstranit váš účet</to></translation>
+<translation><from>replies</from><to>odpovědi</to></translation><!-- noun / plural -->
+<translation><from>Replies</from><to>Odpovědi</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>Odpovědět</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>odpověď</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>Poslat soukromou zprávu</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>Nahlásit nevhodný obsah</to></translation>
+<translation><from>Report Now</from><to>Nahlásit</to></translation>
+<translation><from>required</from><to>vyžadován(o)</to></translation>
+<translation><from>Return to <t.location/></from><to>Vrátit se k <n.location/></to></translation>
+<translation><from>Save Changes</from><to>Uložit změny</to></translation>
+<translation><from>Save Settings</from><to>Uložit nastavení</to></translation>
+<translation><from>Save Subscription</from><to>Potvrdit odběr</to></translation>
+<translation><from>Search</from><to>Najít</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>Níže si vyberte, jaké akce chcete provést:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>Zvolte kategorii, která nejlépe vystihuje obsah této stránky.</to></translation>
+<translation><from>Send email to me</from><to>Odeslat e-mail sám sobě</to></translation>
+<translation><from>Send Email to <t.author/></from><to>Odeslat e-mail uživateli <n.author/></to></translation>
+<translation><from>Send Request</from><to>Odeslat požadavek</to></translation>
+<translation><from>Send To:</from><to>Poslat (komu):</to></translation>
+<translation><from>Sexual content</from><to>Sexuální obsah</to></translation>
+<translation><from>Show</from><to>Zobrazit</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>Ukázat oznámení Nabble</to></translation>
+<translation><from>Sincerely,</from><to>S pozdravem,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>Jelikož tato aplikace je archiv mailing listu, odhlašte prosím níže svou e-mailovou adresu před tím, než kliknete na tlačítko "Odstranit".</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>Jelikož nejste registrovaný uživatel, musíme nejprve ověřit, že nejste robot.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>Některé vaše příspěvky byly odstraněny z <n.location/> - zasíláme vám jejich kopie, abyste si je mohli uložit.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>Bohužel pouze registrovaní členové mohou psát příspěvky v <n.app/>.</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>Bohužel vás někdo z administrátorů zabanoval.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>Bohužel tento e-mail nemá povoleno číst příspěvky v <n.location/>.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>Bohužel zde nemůžete zakládat nová témata.<br/>Nic vám ale nebrání v odpovídání na příspěvky.</to></translation>
+<translation><from>Sort by date</from><to>Třídit dle data</to></translation>
+<translation><from>Sort by relevance</from><to>Třídit dle výskytu</to></translation>
+<translation><from>Sorted by date</from><to>Setřízeno dle data</to></translation>
+<translation><from>Sorted by relevance</from><to>Setřízeno dle výskytu</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[Detektor spamu] V textu příspěvku je příliš mnoho slov '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[Detektor spamu] Příspěvek nesmí obsahovat '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[Detektor spamu] Příspěvek obsahuje výrazy často užívané ve spamu.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[Detektor spamu] Předmět nesmí obsahovat '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[Detektor spamu] Předmět obsahuje výrazy často užívané ve spamu.</to></translation>
+<translation><from>Spam</from><to>Spam</to></translation>
+<translation><from>Stars in <t.location/></from><to>Oblíbené v <n.location/></to></translation>
+<translation><from>Structure</from><to>Struktura</to></translation>
+<translation><from>Subcategories</from><to>Podkategorie</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>Podkategorie v <n.location/></to></translation>
+<translation><from>Subcategory</from><to>Podkategorie</to></translation>
+<translation><from>Sub-Forum</from><to>Podfórum</to></translation>
+<translation><from>Sub-Forums</from><to>Podfóra</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>Podfóra & Témata</to></translation>
+<translation><from>Subject</from><to>Předmět</to></translation>
+<translation><from>Subscribe</from><to>Přihlásit k odběru</to></translation>
+<translation><from>subscriber</from><to>odběratel</to></translation>
+<translation><from>subscribers</from><to>odběratelé</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>Přihlásit se k odběru <n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>Přihlásit se k odběru e-mailem</to></translation>
+<translation><from>Subscription Confirmation</from><to>Potvrzení odběru</to></translation>
+<translation><from>Subscription Confirmed</from><to>Potvrzení odběru</to></translation>
+<translation><from>Subscription Format</from><to>Formát odběru</to></translation>
+<translation><from>Subscription Removed</from><to>Odběr odstraněn</to></translation>
+<translation><from>Subscription Results</from><to>Výsledky odběru</to></translation>
+<translation><from>Subscription Type</from><to>Typ odběru</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>Byl vám úspěšně odeslán potvrzovací e-mail.</to></translation>
+<translation><from>Success</from><to>Úspěch</to></translation>
+<translation><from>Take Action</from><to>Přijmout opatření</to></translation>
+<translation><from><t.app/> Registration</from><to>Registrace do <n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to><n.author/> byl úspěšně zabanován.</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to><n.author/> byl úspěšně odbanován.</to></translation>
+<translation><from>Tell me more & show examples</from><to>Vysvětli více & ukaž příklady</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>Děkujeme Vám za registraci do <n.app/>!</to></translation>
+<translation><from>Thank You</from><to>Děkujeme</to></translation>
+<translation><from>The author has deleted this message.</from><to>Autor tuto zprávu vymazal.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>Kód v URL není platný.</to></translation>
+<translation><from>the exact phrase:</from><to>přesná fráze:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>Mailing list může vyžadovat přihlášení k odběru před přijetím příspěvku. Vezměte prosím na vědomí, že registrací do Nabble nejste automaticky přihlášeni k mailing listu. Pokud nejste přihlášeni k odběru, udělejte to nyní. Pokud si nejste jisti a nepamatujete si údaje, přihlašte se bez obav znovu. </to></translation>
+<translation><from>The Nabble team</from><to>Nabble tým</to></translation>
+<translation><from>The name of the group is not valid.</from><to>Název skupiny je neplatný.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>Příspěvek nemůže být nadřazený sám sobě.</to></translation>
+<translation><from>The password fields don't match.</from><to>Heslo nesouhlasí.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>Existuje neregistrovaný uživatelský účet spojený s e-mailovou adresou <n.email/>.</to></translation>
+<translation><from>The subject is required.</from><to>Musíte zadat předmět.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>Uživatel obdrží e-mailem kopie všech vymazaných příspěvků a má tak šanci je zachránit.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>Ověřovací kód neodpovídá obrázku.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>Tato větev je příliš dlouhá a některé příspěvky byly vynechány. Pokud chcete vidět všechny, přepněte se do jiného zobrazení zobrazení.</to></translation>
+<translation><from>This email is already subscribed.</from><to>Tento e-mail je již přihlášen.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>Toto fórum je archiv mailing listu.</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>Toto fórum je archiv/vstupní stránka, který přesměruje zprávu na <b><n.mailing_list_address/></b>.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>To zahrnuje kategorie, příspěvky, fotografie, soubory a vše ostatní.</to></translation>
+<translation><from>This is a mailing list archive</from><to>Toto je archiv mailing listu</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>Toto je automatický email od Nabble, který potvrzuje úspěšné založení vašeho nového <n.app/>. Pokud jste nevytvářeli zmíněný <n.app/>, kontaktujte nás přes fórum Nabble Support.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>Jsou podporovány jen e-maily ve formátu holého textu.</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>Seznam ukazuje registrované, neregistrované a banované uživatele. Anonymní čtenáři nejsou uvedeni, protože nemají e-mailovou adresu a tak nemohou být součástí skupiny.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>Zpráva bude odeslána od <b><n.from/></b> pro <b><n.to/></b>.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to><b>Mensagem Pendente</b>: Tato zpráva zatím nebyla schválena pro zobrazení v mailing listu.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>Tento příspěvek byl aktualizován <n.date/>.</to></translation>
+<translation><from>This topic has been locked.</from><to>Toto téma bylo zamknuto.</to></translation>
+<translation><from>This topic has been pinned.</from><to>Toto téma bylo připnuto.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>Toto téma bylo připnuto do <n.location/>.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>Toto téma bylo odemčeno.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>Toto téma už není připnuté.</to></translation>
+<translation><from>This topic has unread posts</from><to>Toto téma obsahuje nepřečtené příspěvky</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>Toto téma je vám přiřazeno s prioritou <n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>Tento uživatel nemá práva pro zobrazení této sekce (přidejte ho do skupiny, která tato práva má a zkuste to znovu)</to></translation>
+<translation><from>This user name is already in use.</from><to>Tohle uživatelské jméno už si zvolil někdo jiný.</to></translation>
+<translation><from>Threaded</from><to>Témata</to></translation>
+<translation><from>Threaded View</from><to>Zobrazení dle témat</to></translation>
+<translation><from>Time</from><to>Čas</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>TIP: Pokud je váš archiv přihlášen k mailing listu a pořád nefunguje, zkuste ignorovat hlavičku X-No-Archive.</to></translation>
+<translation><from>Tips</from><to>Tipy</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> vyžaduje ověření pro připojení k <n.location/>:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> Poděkování</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> zobrazení stránky bez reklam.</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>Pro přijetí tohoto požadavku musíte uživatele přidat minimálně do jedné skupiny, která má přístup do této oblasti:</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>Pro přidání <n.app/> do vaší stránky, zkopírujte a vložte následující HTML kód do vaší stránky:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>Pro potvrzení odběru klikněte na odkaz níže:</to></translation>
+<translation><from>topic</from><to>téma</to></translation>
+<translation><from>Topics and replies</from><to>Témata a odpovědi</to></translation>
+<translation><from>topics</from><to>témata</to></translation>
+<translation><from>Topics</from><to>Témata</to></translation>
+<translation><from>Topics only</from><to>Jen témata</to></translation>
+<translation><from>Topics View</from><to>Zobrazení témat</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>Každý uživatel musí mít svou jedinečnou adresu, pokud přispívá e-mailem - je to kvůli omezení spamu.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>Pro odstranění skupiny vymažte textovou oblast níže a uložte změny.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>Abyste viděli, jakou e-mailovou adresu máte použít pro psaní příspěvků, prosím <n.login_link.>přihlašte se</n.login_link.> nebo se <n.register_link.>zaregistrujte</n.register_link.>.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>Pro založení nového tématu v <n.location/>, zašlete e-mail na <n.p2/></to></translation>
+<translation><from>Total</from><to>Celkem</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>Pro odstranění odběru z <n.location/></to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> vás zve, abyste se přihlásili k <n.location/>:</to></translation>
+<translation><from>Turn off highlighting</from><to>Vypnout zvýrazňování</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/> založil novou podkategorii</to></translation>
+<translation><from>Unable to Post</from><to>Nelze zadat příspěvek</to></translation>
+<translation><from>Unassigned</from><to>Nepřiřazený</to></translation>
+<translation><from>Unauthorized</from><to>Neověřený</to></translation>
+<translation><from>Unban this user</from><to>Odbanovat tohoto uživatele</to></translation>
+<translation><from>Unban User</from><to>Odbanovat uživatele</to></translation>
+<translation><from>Unknown or Other</from><to>Neznámý / ostatní</to></translation>
+<translation><from>Unlock topic</from><to>Odemknout téma</to></translation>
+<translation><from>Unpin topic</from><to>Odepnout téma</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>Neregistrovaný / zrušený</to></translation>
+<translation><from>Unregistered</from><to>Neregistrovaný</to></translation>
+<translation><from>Unregistered User</from><to>Neregistrovaný uživatel</to></translation>
+<translation><from>Unsubscribe</from><to>Zrušit odběr</to></translation>
+<translation><from>Upload a file</from><to>Nahrát soubor</to></translation>
+<translation><from>User email:</from><to>E-mail uživatele:</to></translation>
+<translation><from>user</from><to>uživatel</to></translation>
+<translation><from>User is online</from><to>Uživatel je online</to></translation>
+<translation><from>User Name</from><to>Uživatelské jméno</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>Uživatel vyžaduje ověření pro připojení se k <n.location/></to></translation>
+<translation><from>users</from><to>uživatelé</to></translation>
+<translation><from>Users</from><to>Uživatelé</to></translation>
+<translation><from>Users & Groups</from><to>Uživatelé & skupiny</to></translation>
+<translation><from>Users that completed the registration process</from><to>Uživatelé, kteří dokončili proces registrace</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>Použijte tagy jako <n.example1/> nebo <n.example2/> pro vytvoření podnadpisů.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>Použijte možnosti níže pro upřesnění vyhledávacích kritérií.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>Použijte <n.tag_names/> pro vložení widgetů z jiných stránek.</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>Zobrazit všechny zprávy v tomto subfóru</to></translation>
+<translation><from>view</from><to>zobrazit</to></translation>
+<translation><from>View mailing list website</from><to>Zobrazit stránku mailing listu</to></translation>
+<translation><from>View message</from><to>Zobrazit zprávu</to></translation>
+<translation><from>View more</from><to>Ukázat více</to></translation>
+<translation><from>views</from><to>zobrazení</to></translation>
+<translation><from>Views</from><to>Zobrazení</to></translation>
+<translation><from>Violent or repulsive content</from><to>Násilný nebo odpudivý obsah</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>Navštivte <n.app/> na adrese:</to></translation>
+<translation><from>visit <t.url/></from><to>navštivte <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>Připravili jsme stránku s návodem na <n.unsubscription_instructions_link.>odstranění odběru z tohoto archívu</n.unsubscription_instructions_link.>.</to></translation>
+<translation><from>We will review your report soon.</from><to>Brzy prověříme váš podnět.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>Které skupiny umožňují zobrazení seznamu členů</to></translation>
+<translation><from>Who can ban/unban users</from><to>Kdo smí banovat/odbanovat uživatele</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>Komu lze přiřadit témata (jen ve skupinách)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>Kdo může měnit datum a čas příspěvků</to></translation>
+<translation><from>Who can create new topics under this application</from><to>Kdo smí zakládat nová témata v této sekci</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>Kdo smí zakládat vnořené prvky (např. subfóra, podkategorie atd.)</to></translation>
+<translation><from>Who can edit any content, both applications and posts. Note: Please only use this feature in extreme circumstances. Most users will not like having their posts edited by someone else.</from><to>Kdo smí editovat obsah - témata i příspěvky. Pounámka: používejte tuto funkcionalitu, jen když je to nezbytně nutné. Většina uživatelů nemá ráda, když jejich příspěvky upravuje někdo jiný.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>Kdo smí upravovat nastavení (změnit název, popis atd.)</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>Kdo smí zamykat/odemykat témata v tomto fóru</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>Kdo smí ovládat odběratele v tomto fóru</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>Kdo smí přesouvat zprávy (do jiných témat nebo subfór)</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>Kdo smí připínat/odepínat témata v tomto fóru</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>Kdo může psát bez omezení (včetně vkládání javascriptu, tagů &lt;object&gt; a &lt;style&gt; atd.). <b>Bezpečnostní upozornění</b>: Toto povolte jen uživatelům, kterým opravdu věříte.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>Kdo smí odpovídat na příspěvky v tomto fóru</to></translation>
+<translation><from>Who can view this application and its contents</from><to>Kdo smí zobrazit toto fórum a jeho obsah</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>Přihlášením k odběru vám budou chodit nové zprávy na vaši e-mailovou adresu a vy na ně můžete odepisovat - objeví se v diskuzi. Funguje to podobně jako mailing list.</to></translation>
+<translation><from>Write Your First Headline</from><to>Zadejte svůj první nadpis</to></translation>
+<translation><from>Write Your First Post</from><to>Zadejte svůj první příspěvek</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>Ano, smazat <n.location/> navždy</to></translation>
+<translation><from>Yes, unsubscribe now</from><to>Ano, zrušit odběr</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>Již jste přihlášen k odběru <n.location/>.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>Nejste přihlášen k odběru <n.location/>.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>Můžete též <n.manage_banned_users_link.>spravovat banované uživatele</n.manage_banned_users_link.> v <n.location/>.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>Můžete též <n.root_node.change_permissions_link.>měnit oprávnění</n.root_node.change_permissions_link.> skupin níže.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>Můžete rovněž zpropagovat <n.app/> zasláním odkazu svým přátelům, vložením do vašich stránek nebo upozorněním na jiných fórech.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>Nemůžete přesunout příspěvek na požadované místo - nový nadřazený prvek nepodporuje anonymní uživatele.</to></translation>
+<translation><from>You Cannot Post Here</from><to>Zde nemáte práva k psaní příspěvků</to></translation>
+<translation><from>(you can reply by email)</from><to>(můžete odpovědět e-mailem)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>Nemůžete příspěvek přesunout kamkoliv.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>Zde nemůžete vložit zprávu, ale můžete použít jiná místa.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>Můžete se zkusit <n.register_link.>zaregistrovat znovu</n.register_link.> nebo kontaktovat <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>Můžete využít formulář níže pro zaslání požadavku administrátorům.</to></translation>
+<translation><from>You have already been registered.</from><to>Už jste zaregistrován.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>Byl jste pozván k přidání se k <n.location/> na adrese:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>Byl jste zaregistrován do <n.subject/>.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>Byl jste odhlášen z odběru <n.location/>.</to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>Zadal jste příliš mnoho zpráv v krátkém časovém úseku. Prosím zkuste to později.</to></translation>
+<translation><from>You logged out</from><to>Jste odhlášen.</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>Budete se muset <n.mailing_list_options_link.>přidat k mailing listu</n.mailing_list_options_link.>, aby vaše zpráva byla přijata.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>Můžete <n.page_node.unauthorized_link.>požadovat nastavení přístupu pro psaní příspěvků</n.page_node.unauthorized_link.> nebo kontaktovat <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> s případnými dotazy.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>Musíte souhlasit se Smluvními podmínkami.</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>Musíte vložit platný e-mail pro mailing list.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>Musíte vložit platnou adresu stránky pro mailing list.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>Musíte vyplnit všechna políčka níže.</to></translation>
+<translation><from>You must login to view this page.</from><to>Pro zobrazení stránky se musíte přihlásit.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>Pro zobrazení <n.subject/> se musíte přihlásit.</to></translation>
+<translation><from>You must login to your account.</from><to>Musíte se přihlásit ke svému účtu.</to></translation>
+<translation><from>You must provide a user name.</from><to>Musíte zadat uživatelské jméno.</to></translation>
+<translation><from>You're not a subscriber</from><to>Nejste přihlášen k odběru</to></translation>
+<translation><from>Your Name</from><to>Vaše jméno</to></translation>
+<translation><from>Your request has been successfully sent.</from><to>Váš požadavek byl úspěšně odeslán.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>Vaše přihlášení k odběru bylo úspěšně uloženo.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>Váš odběr <n.location/> byl odhlášen. Pokud jste to provedli omylem, můžete se znovu přihlásit kliknutím na odkaz níže:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>Váš odběr <n.location/> byl úspěšně zrušen.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to><n.app/> byl(o) úspěšně založen(o).</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>K zakládání nových témat v <n.location/> potřebujete kromě registrace i ověření + schválení u administrátrů.</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>Dostanete e-mail s každou novou zprávou zadanou do tohoto tématu.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>Obdržíte e-mail s odkazem pro aktivaci svého účtu.</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>Váš odběr</to></translation>
+<translation><from>edit</from><to>upravit</to></translation>
+<translation><from>Remove ads</from><to>Odstranit reklamy</to></translation>
+<translation><from>View profile of <t.author/></from><to>Zobrazit profil uživatele <n.author/></to></translation>
+<translation><from>Description</from><to>Popis</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>Videa z Youtube, Vimeo a LiveLeak.</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>Banovaní uživatelé v <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>Předmět začíná na</to></translation>
+
+#NEW 2
+<translation><from>Change title and meta tags</from><to>Změnit nadpis a meta tagy</to></translation>
+<translation><from>Here you can customize the title and meta tags of the <t.location/> page.</from><to>Zde můžete změnit nadpis a meta tagy <n.location/>.</to></translation>
+<translation><from>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</from><to>Meta tagy níže pomáhají optimalizovat vyhledávání stránky přes Google, Seznam atd.)</to></translation>
+<translation><from>Use custom values</from><to>Použít vlastní hodnoty</to></translation>
+<translation><from>Page Title</from><to>Nadpis stránky (tag Title)</to></translation>
+<translation><from>Enter a context-rich title that clearly identifies this page.</from><to>Zadejte text, který srozumitelně popisuje vaši stránku.</to></translation>
+<translation><from>The title should ideally be less than 70 characters in length.</from><to>Nadpis by měl mít méně než 70 znaků.</to></translation>
+<translation><from>Meta Description</from><to>Meta tag Description</to></translation>
+<translation><from>Enter a brief and concise summary of your page's content.</from><to>Vložte stručné shrnutí obsahu vaší stránky.</to></translation>
+<translation><from>Limit your description to 155 characters or 170 characters at most.</from><to>Omezte popis na 155 znaků (max. přípustných je 170 znaků).</to></translation>
+<translation><from>Mailing List Archive</from><to>Archiv mailing listu</to></translation>
+<translation><from>Filter: priority <t.priority/></from><to>Filtr: priorita <n.priority/></to></translation>
+<translation><from>Filter: assignee <t.author/></from><to>Filtr: přiřazeno <n.author/></to></translation>
+<translation><from>Use Google Analytics</from><to>Použít Google Analytics</to></translation>
+<translation><from>Analytics Account ID:</from><to>ID účtu na Analytics:</to></translation>
+<translation><from>(e.g., UA-12345-0)</from><to>(např. UA-12345-0)</to></translation>
+<translation><from>Enter a valid analytics account ID.</from><to>Vložte platné číslo účtu Analytics.</to></translation>
+<translation><from>Here you can use Google Analytics to measure the success of your app.</from><to>Můžete využít Google Analytics pro zjištění úspěšnosti vašeho fóra.</to></translation>
+<translation><from>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</from><to>Níže vložte své ID účtu na Analytics a budete schopni sledovat návštěvy, návštěvníky a ostatní důležité statistiky internetového provozu.</to></translation>
+
+<translation><from>Digest Email</from><to>Náhled e-mailu</to></translation>
+<translation><from>on <t.date/></from><to>z <n.date/></to></translation>
+<translation><from>DO NOT REPLY TO THIS EMAIL</from><to>NEODPOVÍDEJTE NA TENTO E-MAIL</to></translation>
+<translation><from>Replies sent to this address are not read or processed.</from><to>Odpovědi na tento e-mail nikdo nečte ani nejsou zpracovány.</to></translation>
+<translation><from>If you want to respond to a post for which you received this email, please go to the website: <t.url/></from><to>Jestli chcete reagovat na příspěvek, na který upozorňuje tento e-mail, proveďte to prosím na adrese: <n.url/></to></translation>
+<translation><from>new post</from><to>nová zpráva</to></translation>
+<translation><from>new posts</from><to>nové zprávy</to></translation>
+
+<translation><from>New registered user in <t.location/>!</from><to>Přibyl nový registrovaný uživatel v <n.location/>!</to></translation>
+<translation><from>User profile</from><to>Profil uživatele</to></translation>
+<translation><from>New user:</from><to>Nový uživatel:</to></translation>
+
+<translation><from><n.author/> wrote</from><to><n.author/> napsal(a)</to></translation>
+<translation><from>You still have <t.number/> day(s) left without advertising</from><to>Ještě vám zbývá <n.number/> dnů bez zobrazení reklam.</to></translation>
+<translation><from>(Only administrators can see this message)</from><to>(Pouze administrátoři mohou vidět tuto zprávu)</to></translation>
+
+<translation><from>Some private items have been omitted because you don't have permission to view them.</from><to>Některé soukromé položky byly vynechány, protože nemáte práva k jejich zobrazení.</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Submit". We will email you a link to reset your password.</from><to>Prosím zadejte e-mailovou adresu, kterou jste použili při registaci a klikněte na Odeslat - zašleme vám odkaz pro resetování hesla.</to></translation>
+<translation><from>Submit</from><to>Odeslat</to></translation>
+<translation><from>Password Reset Sent</from><to>Resetování hesla odesláno</to></translation>
+<translation><from>We have sent you a link to reset your password. Please check your email now. If you don't receive the instructions in a few minutes, check your spam folder or try to resend the request.</from><to>Zaslali jsme vám odkaz pro resetování hesla. Zkontrolujte vaši poštu - pokud vám návod nedorazí během několika minut, podívejte se do spamu či zkuste požadavek odeslat znovu.</to></translation>
+<translation><from>Reset your password / <t.location/></from><to>Resetování hesla / <n.location/></to></translation>
+<translation><from>We received a request to reset your password in <t.location/>.</from><to>Přijali jsme váš požadavek pro resetování hesla v <n.location/>.</to></translation>
+<translation><from>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</from><to>Pokud chcete resetovat heslo, klikněte na odkaz níže (nebo zkopírujte a vložte adresu odkazu do webového prohlížeče):</to></translation>
+<translation><from>If you don't want to reset your password, please ignore this message. Your password will not be reset.</from><to>Pokud nechcete heslo resetovat, ignorujte prosím tuto zprávu. Heslo nebude změněno.</to></translation>
+
+<translation><from><b>IMPORTANT:</b> If you use this option, messages posted in the archive will NOT be sent to the mailing list.</from><to><b>DŮLEŽITÉ:</b> Pokud použijete tuto volbu, zprávy uložené v archívu nebudou zasílány na mailing list.</to></translation>
+<translation><from>Message Preview</from><to>Náhled zprávy</to></translation>
+<translation><from>Your changes will not be sent to the mailing list.</from><to>Vaše změny nebudou zaslány na mailing list.</to></translation>
+<translation><from>If you want others in the mailing list to know of your changes, please compose a new message or reply to your original message.</from><to>Pokud chcete, aby ostatní na mailing listu viděli vaše změny, prosím založte novou zprávu nebo odepište na původní příspěvek.</to></translation>
+
+<translation><from>Poll</from><to>Anketa</to></translation>
+<translation><from>Add New Poll</from><to>Přidat novou anketu</to></translation>
+<translation><from>Question:</from><to>Otázka:</to></translation>
+<translation><from>Answers:</from><to>Odpovědi:</to></translation>
+<translation><from>Add new answer</from><to>Přidat novou odpověď</to></translation>
+<translation><from>1 vote</from><to>1 hlas</to></translation>
+<translation><from><t.number/> votes</from><to><n.number/> hlasů</to></translation>
+<translation><from>Total votes:</from><to>Celkem hlasů:</to></translation>
+<translation><from>Vote</from><to>Hlasovat</to></translation>
+<translation><from>Your vote has been submitted.</from><to>Váš hlas byl odeslán.</to></translation>
+<translation><from>This poll is closed.</from><to>Tato anketa je ukončena.</to></translation>
+<translation><from>This poll ends on <t.date/>.</from><to>Tato anketa končí <n.date/>.</to></translation>
+<translation><from>This poll ended on <t.date/>.</from><to>Anketa byla ukončena <n.date/>.</to></translation>
+<translation><from>Results will be shown only after poll has ended.</from><to>Výsledky budou zobrazeny až po ukončení hlasování.</to></translation>
+<translation><from>You have to vote before you can see the results.</from><to>Musíte hlasovat, abyste mohli vidět výsledky.</to></translation>
+<translation><from>You cannot change your vote after voting.</from><to>Po hlasování už nemůžete změnit své rozhodnutí.</to></translation>
+<translation><from>You can select up to <t.number/> options.</from><to>Můžete zvolit až <n.number/> možnosti.</to></translation>
+<translation><from>Please select no more than <t.number/> options.</from><to>Nemůžete zvolit více než <n.number/> možnosti.</to></translation>
+<translation><from>Please select at least one option.</from><to>Prosím zvolte alespoň jednu možnost.</to></translation>
+<translation><from>Remove Poll</from><to>Odstranit anketu</to></translation>
+<translation><from>Invalid poll parameters.</from><to>Špatné parametry ankety.</to></translation>
+<translation><from>Poll duration must be a non-negative integer or blank.</from><to>Délka ankety musí být nezáporné číslo (nebo pole ponechejte prázdné).</to></translation>
+<translation><from>Poll maximum allowed choices must be a non-negative integer or blank.</from><to>Počet povolených možností v anketě musí být nezáporné číslo (nebo pole ponechejte prázdné).</to></translation>
+<translation><from>Delete this poll, including all votes?</from><to>Odstranit tuto anketu, včetně všech hlasů?</to></translation>
+<translation><from>Poll has been deleted.</from><to>Anketa byla odstraněna.</to></translation>
+<translation><from>Who can create polls.</from><to>Kdo může vytvářet ankety.</to></translation>
+<translation><from>Allow vote changes</from><to>Povolit změny hlasování</to></translation>
+<translation><from>Allow viewing results before end date (poll creators can always view the results)</from><to>Povolit zobrazení výsledků před datem ukončení (autoři ankety mohou vždy výsledky zobrazit)</to></translation>
+<translation><from>Allow viewing results before vote</from><to>Povolit zobrazení výsledků před odesláním hlasu</to></translation>
+<translation><from>Multiple selections allowed:</from><to>Povoleno více možností:</to></translation>
+<translation><from>Poll ends after <t.number/> days (leave blank for unlimited).</from><to>Anketa skončí za <n.number/> dnů (ponechejte pole prázdné pro nekonečno).</to></translation>
+<translation><from>Login to vote</from><to>Přihlašte se pro hlasování</to></translation>
+<translation><from>This message has a poll</from><to>Tato zpráva obsahuje anketu</to></translation>
+<translation><from>Visit the link below if you want to participate:</from><to>Klikněte na odkaz níže, pokud se chcete zúčastnit:</to></translation>
+
+<translation><from>Current length: <t.number/> characters</from><to>Aktuální délka: <n.number/> znaků</to></translation>
+<translation><from>Edit Post</from><to>Editovat příspěvek</to></translation>
+<translation><from><t.location/> - Your credits are running out</from><to><n.location/> - Docházejí Vám kredity</to></translation>
+<translation><from><t.location/> is running out of ad-free credits.</from><to><n.location/> docházejí kredity pro provoz bez zobrazování reklam.</to></translation>
+<translation><from>If you want to buy more credits, visit:</from><to>Jestli si chcete zakoupit další kredity, navštivte adresu:</to></translation>
+<translation><from>only in this topic</from><to>jen v tomto tématu</to></translation>
+<translation><from>everywhere</from><to>všude</to></translation>
+
+<translation><from>If you want to invite subscribers, please request this feature in the <n.support_link/> forum.</from><to>Jestli chcete pozvat odběratele, požadujte prosím tuto funkci ve fóru <n.support_link/>.</to></translation>
+<translation><from>We can install this module for you, but the Nabble team must approve this request to prevent abuse and spam.</from><to>Tento modul vám můžeme nainstalovat, ale až po schválení Nabble teamem - prevence proti zneužití a spamu.</to></translation>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_de.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,798 @@
+## Deutsch
+
+
+
+
+
+
+
+
+
+## MONTHS ##
+
+<translation><from>January</from><to>Januar</to></translation>
+<translation><from>February</from><to>Februar</to></translation>
+<translation><from>March</from><to>März</to></translation>
+<translation><from>April</from><to>April</to></translation>
+<translation><from>May</from><to>Mai</to></translation>
+<translation><from>June</from><to>Juni</to></translation>
+<translation><from>July</from><to>Juli</to></translation>
+<translation><from>August</from><to>August</to></translation>
+<translation><from>September</from><to>September</to></translation>
+<translation><from>October</from><to>Oktober</to></translation>
+<translation><from>November</from><to>November</to></translation>
+<translation><from>December</from><to>Dezember</to></translation>
+
+<translation><from>Jan</from><to>Jan</to></translation>
+<translation><from>Feb</from><to>Feb</to></translation>
+<translation><from>Mar</from><to>Mär</to></translation>
+<translation><from>Apr</from><to>Apr</to></translation>
+<!--translation><from>May</from><to>Mai</to></translation-->
+<translation><from>Jun</from><to>Jun</to></translation>
+<translation><from>Jul</from><to>Jul</to></translation>
+<translation><from>Aug</from><to>Aug</to></translation>
+<translation><from>Sep</from><to>Sep</to></translation>
+<translation><from>Oct</from><to>Okt</to></translation>
+<translation><from>Nov</from><to>Nov</to></translation>
+<translation><from>Dec</from><to>Dez</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>Diese Funktion zu missbrauchen ist auch eine Verletzung unserer Nutzungsbedingungen.</to></translation>
+<translation><from>Access Request</from><to>Zugang anfordern</to></translation>
+<translation><from>Account settings</from><to>Kontoeinstellungen</to></translation>
+<translation><from>Account Settings</from><to>Kontoeinstellungen</to></translation>
+<translation><from>Action</from><to>Aktion</to></translation>
+<translation><from>Add a link to another page</from><to>Verweis auf eine andere Seite hinzufügen</to></translation>
+<translation><from>Add a new comment</from><to>Neuen Kommentar hinzufügen</to></translation>
+<translation><from>Add a new group</from><to>Neue Gruppe hinzufügen</to></translation>
+<translation><from>Add an image to your post</from><to>Ein Bild zum Beitrag hinzufügen</to></translation>
+<translation><from>Add another address</from><to>Weitere Adresse hinzufügen</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>Inhalt einfügen, der nicht interpretiert werden soll (z.B. Quelltext)</to></translation>
+<translation><from>Adding Sub-Headers</from><to>Überschrift hinzufügen</to></translation>
+<translation><from>Add New Group</from><to>Neue Gruppe hinzufügen</to></translation>
+<translation><from>Add / Remove Groups</from><to>Gruppen hinzufügen / löschen</to></translation>
+<translation><from>Add smileys and funny animations</from><to>Emoticons und lustige Animationen einfügen</to></translation>
+<translation><from>Add Subscribers</from><to>Abonnenten hinzufügen</to></translation>
+<translation><from>Add this item to your favorites list</from><to>Diesen Beitrag zu den Favoriten hinzufügen</to></translation>
+<translation><from>Administrator</from><to>Administrator</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity, other sexual content.</from><to>Nicht jugendfreier Inhalt, eindeutig sexuelle Aktivitäten, Nacktheit, andere sexuelle Inhalte.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>Kampfhandlungen Erwachsener, physische Gewalt, Jugendgewalt, Tierquälerei oder Unterstützung von Terrorismus.</to></translation>
+<translation><from>Advanced Search</from><to>Erweiterte Suche</to></translation>
+<translation><from>Advanced Settings</from><to>Erweiterte Einstellungen</to></translation>
+<translation><from>Advertisement</from><to>Werbung</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>Benachrichtigung, wenn jemand einen Beitrag zu diesem Thema verfasst</to></translation>
+<translation><from>All</from><to>Alle</to></translation>
+<translation><from>all of the words:</from><to>alle diese Worte:</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>Alle Beiträge von <n.author/> wurden erfolgreich gelöscht.</to></translation>
+<translation><from>All posts</from><to>Alle Beiträge</to></translation>
+<translation><from>All users belong to this group</from><to>Alle Mitglieder dieser Gruppe</to></translation>
+<translation><from>All Users</from><to>Alle Nutzer</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>Alle Nutzer, die sich für <n.location/> registriert haben. Diese Nutzer haben ihre eMail-Adresse bestätigt und können sich im System anmelden.</to></translation>
+<translation><from>Already Subscribed</from><to>Bereits registriert</to></translation>
+<translation><from>An email has been sent to you.</from><to>Es wurde eine eMail verschickt.</to></translation>
+<translation><from>Anonymous</from><to>Anonym</to></translation>
+<translation><from>anonymous user</from><to>anonymer Nutzer</to></translation>
+<translation><from>anonymous users</from><to>anonyme Nutzer</to></translation>
+<translation><from>Any message part contains</from><to>Irgendein Teil der Nachricht enthält</to></translation>
+<translation><from>Application</from><to>Anwendung</to></translation>
+<translation><from>Apps</from><to>Apps</to></translation>
+<translation><from>Assignee</from><to>Beauftragter</to></translation>
+<translation><from>Assign</from><to>Zuweisen</to></translation>
+<translation><from>Assignment</from><to>Zuweisung</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>Gemäß Anforderung, lautet die eMail-Adresse für neue Beträge in der <n.app/>-Applikation nun:</to></translation>
+<translation><from>at least one of the words:</from><to>eines der Worte:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to>Atom Feeds für <n.location/></to></translation>
+<translation><from>at priority</from><to>mit Priorität</to></translation>
+<translation><from>Authorized Users Only</from><to>Nur autorisierte Nutzer</to></translation>
+<translation><from>Author name</from><to>Name des Autor</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>Geplauder wie "vielen Dank", "großartig" ist zu vermeiden... Besser ist es, bei Bedarf <n.page_node.reply_to_author_link.>eine private eMail zu senden</n.page_node.reply_to_author_link.>.</to></translation>
+<translation><from>Banned User</from><to>Verbannter Nutzer</to></translation>
+<translation><from>Ban this user</from><to>Diesen Nutzer verbannen</to></translation>
+<translation><from>Ban User</from><to>Verbannung Nutzer</to></translation>
+<translation><from>Before deleting this archive...</from><to>Bevor dieses Archiv gelöscht wird...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>Hier werden Gruppen und Nutzer verwaltet. Nutzer können dabei durch Kopieren und Einfügen von einer Gruppe in eine andere verschoben werden.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>WICHTIG</b>: Nabble wird an jede eMail-Adresse in dieser Liste eine Einladung versenden. Nutzer müssen dann durch Anklicken des aufgeführten Links ihre Anmeldung bestätigen.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>WICHTIG:</b> Diese Anmeldung ist unabhängig von der Anmeldung bei der tatsächlichen Mailingliste. Im Grunde ist es die Anmeldung zum Archiv des Forums, nicht zur Mailingliste. Die Anmeldung beim Archiv garantiert nicht, dass Beiträge von der Mailingliste akzeptiert werden.</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>WICHTIG</b>: Das Archiv muss bei der Mailingliste angemeldet werden, damit alles funktioniert.</to></translation>
+<translation><from>blog</from><to>bloggen</to></translation>
+<translation><from>Blog</from><to>Blog</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>Hinweis</b>: Dem Administrator ist es nun möglich <n.page_node.change_permissions_link.>die Berechtigungen von <n.location/> bearbeiten</n.page_node.change_permissions_link.> und sicher zu stellen, dass hier neue Beiträge erstellt werden können.</to></translation>
+<translation><from>board</from><to>Gremium</to></translation>
+<translation><from>Board</from><to>Gremium</to></translation>
+<translation><from>Bold</from><to>Fett</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>Warnung:</b> Der Suchindex wird gerade neu erstellt. Suchergebnisse könnten unvollständig sein.</to></translation>
+<translation><from>by <t.author/></from><to>von <n.author/></to></translation>
+<translation><from>Cancel</from><to>Abbrechen</to></translation>
+<translation><from>category</from><to>Kategorie</to></translation>
+<translation><from>Category</from><to>Kategorie</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>ACHTUNG: Diese Aktion kann nicht rückgängig gemacht werden.</to></translation>
+<translation><from>Change appearance</from><to>Aussehen ändern</to></translation>
+<translation><from>Change application type</from><to>Typ der Anwendung ändern</to></translation>
+<translation><from>Change Application Type</from><to>Typ der Anwendung ändern</to></translation>
+<translation><from>Change code image</from><to>Sicherheitsabbildung ändern</to></translation>
+<translation><from>Change domain name</from><to>Domainname ändern</to></translation>
+<translation><from>Change language</from><to>Sprache ändern</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>Profilbild ändern oder löschen.</to></translation>
+<translation><from>Change parent</from><to>Vorgänger ändern</to></translation>
+<translation><from>Change permissions</from><to>Rechte ändern</to></translation>
+<translation><from>Change Permissions</from><to>Rechte ändern</to></translation>
+<translation><from>Change post date</from><to>Beitragsdatum ändern</to></translation>
+<translation><from>Change Post Date</from><to>Beitragsdatum ändern</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>Signatur ändern, die unter eigenen Beiträgen angezeigt wird.</to></translation>
+<translation><from>Change User Groups</from><to>Gruppenzuordnung ändern</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>Anzeigeeigenschaften und andere Einstellungen ändern.</to></translation>
+<translation><from>Change Your Picture</from><to>Profilbild ändern</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>eMail-Adresse, Passwort und Nutzernamen ändern.</to></translation>
+<translation><from>Child abuse</from><to>Kindesmissbrauch</to></translation>
+<translation><from>Choose a subcategory</from><to>Unterkategorie auswählen</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>Unterkategorie für den Beitrag auswählen</to></translation>
+<translation><from>Choose the best offer for you</from><to>Bestes Angebot auswählen</to></translation>
+<translation><from>Classic</from><to>Klassisch</to></translation>
+<translation><from>Clear Log</from><to>Protokoll löschen</to></translation>
+<translation><from>Click for more options</from><to>Für mehr Optionen anklicken</to></translation>
+<translation><from>click here</from><to>hier klicken</to></translation>
+<translation><from>Click here to make your first post</from><to>Hier klicken um den ersten Beitrag zu erstellen</to></translation>
+<translation><from>Click to filter</from><to>Zum Filtern klicken</to></translation>
+<translation><from>Close</from><to>Schließen</to></translation>
+<translation><from>Close this message</from><to>Diese Nachricht schließen</to></translation>
+<translation><from>comment</from><to>kommentieren</to></translation>
+<translation><from>Comment</from><to>Kommentar</to></translation>
+<translation><from>comments</from><to>Kommentare</to></translation>
+<translation><from>Comments</from><to>Kommentare</to></translation>
+<translation><from>Confirm Password</from><to>Bestätigung Passwort</to></translation>
+<translation><from>Confirm Subscription</from><to>Bestätigung Abonnement</to></translation>
+<translation><from>Congratulations!</from><to>Glückwunsch!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>Glückwunsch zur neuen <n.app/>-Applikation!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>Inhalt zur Förderung von Hass oder Gewalt, Missbrauch schutzloser Personen, Mobbing, rassische Intoleranz oder Eintreten gegen jegliche Person, Gruppe oder Organisation oder übermäßige Obszönität.</to></translation>
+<translation><from>CONTENTS DELETED</from><to>INHALTE GELÖSCHT</to></translation>
+<translation><from>Continue</from><to>Weiter</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>Verletzung von Urheberrecht, Privatsphäre oder anderer Rechte.</to></translation>
+<translation><from>Count</from><to>Zähler</to></translation>
+<translation><from>Created by <t.author/></from><to>Erstellt von <n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>Neues <n.element/> erstellen</to></translation>
+<translation><from>Create <t.element/></from><to>Erstelle <n.element/></to></translation>
+<translation><from>Current Credits</from><to>Punktestand</to></translation>
+<translation><from>Currently Nabble supports</from><to>Momentan unterstützt Nabble</to></translation>
+<translation><from>Current Subscribers</from><to>Derzeitige Abonnenten</to></translation>
+<translation><from>Daily digest</from><to>Tägliche Zusammenfassung</to></translation>
+<translation><from>Data successfully saved</from><to>Daten erfolgreich gespeichert</to></translation>
+<translation><from>Date</from><to>Datum</to></translation>
+<translation><from>days</from><to>Tage</to></translation>
+<translation><from>Dear <t.name/>,</from><to>Hallo <n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>Hallo Nutzer,</to></translation>
+<translation><from>Default</from><to>Standard</to></translation>
+<translation><from>Delete all posts from this user</from><to>Lösche alle Beiträge dieses Nutzers</to></translation>
+<translation><from>Delete Application</from><to>Lösche Anwendung</to></translation>
+<translation><from>Deleted posts</from><to>Beiträge gelöscht</to></translation>
+<translation><from>Delete</from><to>Löschen</to></translation>
+<translation><from>Delete this post and replies</from><to>Lösche diesen Beitrag und alle Antworten</to></translation>
+<translation><from>Delete this post</from><to>Lösche diesen Beitrag</to></translation>
+<translation><from>Delete this topic</from><to>Lösche dieses Thema</to></translation>
+<translation><from>Description is in HTML Format</from><to>Beschreibung ist im HTML-Format</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>Bitte nicht wiederholt verfassen. Bitte erst ein paar Tage abwarten, denn Leser erhalten den Beitrag per eMail.</to></translation>
+<translation><from>Don't show this message again</from><to>Diese Nachricht nicht wieder anzeigen.</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>Diesen Beitrag wirklich löschen?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>Diesen Beitrag samt aller Antworten unwiederbringlich löschen? </to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>Soll <n.location/> wirklich unwiederbringlich <n.important.>gelöscht</n.important.> werden?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>Sollen die die Archiv-Einstellungen der Mailing-Liste wirklich gelöscht werden?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>Soll tatsächlich ein Abonnement für <n.location/> eingerichtet werden?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>Soll dieser Nutzer tatsächlich aus der Verbannung befreit werden?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>Soll das Abonnement für <n.location/> tatsächlich beendet werden?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>Drogenmissbrauch, Inhalte über illegale Drogen und entsprechende Ausrüstung, Missbrauch von Feuer und Sprengstoff, Verkauf von Bier oder harten Getränken, Tabak und Rauchwaren, Waffen oder Munition oder andere gefährliche Tätigkeiten.</to></translation>
+<translation><from>Edit name & description</from><to>Namen und Beschreibung bearbeiten</to></translation>
+<translation><from>Edit Name & Description</from><to>Bearbeitung Name & Beschreibung</to></translation>
+<translation><from>Editor</from><to>Bearbeiter</to></translation>
+<translation><from>Edit Personal Information</from><to>Bearbeitung Persönliche Informationen</to></translation>
+<translation><from>Edit post</from><to>Beitrag bearbeiten</to></translation>
+<translation><from>Edit Subscription</from><to>Bearbeitung Abonnement</to></translation>
+<translation><from>Edit Your Signature</from><to>Bearbeitung Signatur</to></translation>
+<translation><from>Email Confirmation</from><to>eMail Bestätigung</to></translation>
+<translation><from>Email for <t.app/></from><to>eMail für die <n.app/>-Applikation</to></translation>
+<translation><from>Email</from><to>eMail</to></translation>
+<translation><from>Email Subscription</from><to>eMail Abonnement</to></translation>
+<translation><from>Email this post to...</from><to>Diesen Beitrag per eMail senden an...</to></translation>
+<translation><from>Embedding Contents</from><to>Inhalte Einbetten</to></translation>
+<translation><from>Embedding options</from><to>Einbettungs-Einstellungen</to></translation>
+<translation><from>Embed</from><to>Einbetten</to></translation>
+<translation><from>Embed post</from><to>Beitrag einbetten</to></translation>
+<translation><from>Embed Tags</from><to>Marker für Einbettung</to></translation>
+<translation><from>Embed this <t.app/></from><to>Einbetten der <n.app/>-Applikation</to></translation>
+<translation><from>Empty</from><to>Leer</to></translation>
+<translation><from>Enter a valid email address.</from><to>Bitte eine gültige eMail-Adresse eingeben.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>Bitte hier die eMail-Adresse angeben, an die wir eine Bestätigung senden sollen.</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>Bitte pro Zeile einen Nutzernamen oder eine eMail-Adresse eingeben:</to></translation>
+<translation><from>Enter one user per row</from><to>Bitte einen Nutzer pro Zeile eingeben</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>Bitte statischen Link für den <b>Beitrag</b> oder das <b>Forum</b> eingeben, welcher der neue Vorgänger sein soll, oder leer lassen, um aus dieser Nachricht ein unabhängiges Thema zu machen:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>Bitte eine Homepage für diese Mailing-Liste eingeben, auf der Nutzer weitere Informationen finden.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>Bitte das Präfix angeben, das für diese Mailing-Liste vor dem Betreff verwendet wird. Dieses Präfix wird automatisch aus dem Betreff der importierten eMails entfernt.</to></translation>
+<translation><from>Enter your email address</from><to>Bitte die eMail-Adresse eingeben</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>Beispiel: maxmustermann@domain.de</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>Beispiel: listenname@listendomain.de</to></translation>
+<translation><from>Examples:</from><to>Beispiele:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>Beispiele: '[ListenName]', 'Abc:', etc.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>Bitte die Begründung für den Zugriffswunsch auf diesen Bereich als kurze Erklärung an die Administratoren formulieren.</to></translation>
+<translation><from>Explanation from this user:</from><to>Begründung dieses Nutzers:</to></translation>
+<translation><from>Extras & add-ons</from><to>Extras & Erweiterungen</to></translation>
+<translation><from>Failed</from><to>Fehlgeschlagen</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>Favorit (klicken, um das Element aus der Favoriten-Liste zu entfernen)</to></translation>
+<translation><from>Feeds</from><to>Feeds</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>Datei wurde nicht hochgeladen: <n.file/> (bitte erneut versuchen oder den Marker entfernen)</to></translation>
+<translation><from>Filter by group</from><to>Nach Gruppe filtern</to></translation>
+<translation><from>Floating sub-forum</from><to>Schwebendes Unterforum</to></translation>
+<translation><from>Forgot Password?</from><to>Passwort Vergessen?</to></translation>
+<translation><from>Forgot your password?</from><to>Wurde das Passwort vergessen?</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>Weitere Informationen unter: <n.info/></to></translation>
+<translation><from>forum</from><to>Forum</to></translation>
+<translation><from>Forum</from><to>Forum</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to>Freie Einbettung <n.app/>-Applikation</to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>Von nun an wird eine persönliche eMail bei jeder neuen Nachricht in <n.location/> versandt.</to></translation>
+<translation><from>gallery</from><to>Galerie</to></translation>
+<translation><from>Gallery</from><to>Galerie</to></translation>
+<translation><from>Gambling or casino-related content</from><to>Glücksspiel oder Inhalte bzgl. Spielbanken</to></translation>
+<translation><from>Go back</from><to>Zurück</to></translation>
+<translation><from>Go to next message</from><to>Weiter zur nächsten Nachricht</to></translation>
+<translation><from>Group Name:</from><to>Gruppenname:</to></translation>
+<translation><from>Groups</from><to>Gruppen</to></translation>
+<translation><from>Groups of this user</from><to>Gruppenzugehörigkeiten dieses Nutzers</to></translation>
+<translation><from>Hacking / cracking</from><to>Hacking / Cracking</to></translation>
+<translation><from>Harmful dangerous acts</from><to>Schädlich gefährliche Tätigkeiten</to></translation>
+<translation><from>Hateful or abusive content</from><to>Hassvolle oder missbrauchende Inhalte</to></translation>
+<translation><from>Help</from><to>Hilfe</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>Hier können Kredit-Punkte erworben werden, die diese Nabble-Anwendung vor Werbeanzeigen schützen. Jeder Kredit-Punkt entspricht dabei einem Seitenansichts-Vorgang ohne Werbeeinblendung. Besucher werden also so lange werbefreie Webseiten sehen, wie Kredit-Punkte vorhanden sind. Nabble wird mit der Einblendung von Werbeanzeigen beginnen/fortfahren, sobald keine Kredit-Punkte mehr vorhanden sind.</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>Hier können die aktuellen Archiv-Informationen der Mailing-Liste von den Nutzern verborgen werden. Diese Einstellung kann dabei unterstützen, den aktuellen Server der Mailing-Liste gegen Nabbles eMail-Abonnierfunktion auszutauschen.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>eMail-Adresse verbergen (Austausch von z.B. nutzer@domain.de gegen <n.lt/>verborgene eMail<n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>eMail verbergen</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>Archiv-Informationen bzgl. der Mailing-Liste vor Nutzern verbergen</to></translation>
+<translation><from>Highest</from><to>Höchste</to></translation>
+<translation><from>High</from><to>Hoch</to></translation>
+<translation><from>Hi <t.name/>,</from><to>Hallo <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>Falls dies die persönliche eMail-Adresse ist, sollte nun eine <n.register_link.>Registrierung</n.register_link.> unter Nutzung der gleichen eMail-Adresse erfolgen. Nach erfolgreicher Registrierung ist das persönliche Nutzerkonto eingerichtet.</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>Falls keine eMail-Nachrichten von Nabble eintreffen, sollte der Ordner <b>Spam</b> überprüft werden.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>Falls eine neue Frage erstellt werden soll, bitte erst aktiv nach einer Antwort suchen. Die Frage könnte bereits beantwortet worden sein.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>Falls noch keine Mitgliedschaft besteht, kann eine <n.register_link.><b>Registrierung</b></n.register_link.> jetzt erfolgen.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>Falls dieser Nutzer verbannt wird, kann er/sie nichts mehr in <n.location/> tun.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>Falls diese eMail nicht angefordert wurde oder keine Kenntnis bzgl. des Hintergrundes besteht, wird darum gebeten, diese eMail einfach zu ignorieren. Es wird sich wahrscheinlich um ein Missverständnis handeln.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>Falls eine Registrierung noch nicht gewünscht ist, sollte hier zumindest die eMail-Adresse eingegeben werden, die als Absenderadresse verwendet werden soll. Die persönlichen Adressdaten werden dann per eMail zugesandt.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>Falls das Abonnement bereits beendet wurde, kann mit der Löschung fortgefahren werden.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>Falls bekannt, bitte die Applikation der Mailing-Liste und ihre Version auswählen.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>Falls die Abmeldung irrtümlich erfolgt ist, bitte wieder <n.login_link.>anmelden</n.login_link.>.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>Durch Versenden einer Antwort auf diese eMail, erfolgt der Eintrag eines entsprechenden Beitrags in folgender Forum-Diskussion</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>Falls dieser Nutzer aus der Verbannung befreit wird, wird er/sie wieder Nachrichten in <n.location/> verfassen können.</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>Falls die Mailing-Liste stattdessen abonniert werden soll, bitte diesem Link für <n.mailing_list_options_link.> Einstellungen</n.mailing_list_options_link.> folgen.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>Ignoriere X-No-Archive Header-Element</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>Ich habe die <n.terms_link.>Nutzungsbedingungen</n.terms_link.> von Nabble gelesen und bin damit einverstanden.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>Bild wurde nicht hochgeladen: <n.image/> (bitte nochmals versuchen oder den Marker entfernen)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>Ich bin ein Abonnent und möchte etwas verfassen</to></translation>
+<translation><from>Incorrect Login!</from><to>Falsche Anmeldedaten!</to></translation>
+<translation><from>Individual emails</from><to>Individuelle eMails</to></translation>
+<translation><from>Inherit</from><to>Erben</to></translation>
+<translation><from>In Reply To</from><to>Als Antwort Auf</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>Als Antwort auf <n.parent_link.>diesen Beitrag</n.parent_link.> von <n.author/></to></translation>
+<translation><from>Insert</from><to>Einfügen</to></translation>
+<translation><from>Insert Image</from><to>Bild Einfügen</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>Anstelle der Nutzung des WEB-Interfaces, kann auch eine eMail an folgende Adresse gesendet werden, um einen neuen Beitrag zu verfassen:</to></translation>
+<translation><from>in <t.location/></from><to>in <n.location/></to></translation>
+<translation><from>Invalid Code</from><to>Ungültiger Code</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>Ungültige eMail-Adresse: <n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>Ungültige Anzahl von Tagen; diese muss eine Ganzzahl sein.</to></translation>
+<translation><from>invisible user</from><to>unsichtbarer Nutzer</to></translation>
+<translation><from>invisible users</from><to>unsichtbare Nutzer</to></translation>
+<translation><from>Invite Subscribers</from><to>Abonnenten Einladen</to></translation>
+<translation><from>is:</from><to>ist:</to></translation>
+<translation><from>is not:</from><to>ist nicht:</to></translation>
+<translation><from>is within the last:</from><to>ist innerhalb der letzten:</to></translation>
+<translation><from>Italic</from><to>Kursiv</to></translation>
+<translation><from>item</from><to>Element</to></translation>
+<translation><from>items</from><to>Elemente</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>Es wird NICHT möglich sein, gelöschte Elemente später wiederherzustellen.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>Bitte einfach den (von den oben erwähnten Seiten erhaltenen) Code innerhalb dieser Marker platzieren, um entsprechend Beiträge zu verfassen.</to></translation>
+<translation><from>Last Post</from><to>Letzter Beitrag</to></translation>
+<translation><from>Learn more</from><to>Mehr lernen</to></translation>
+<translation><from>Leave a comment</from><to>Einen Kommentar hinterlassen</to></translation>
+<translation><from>Link</from><to>Link</to></translation>
+<translation><from>Link to <t.location/></from><to>Link auf <n.location/></to></translation>
+<translation><from>List</from><to>Liste</to></translation>
+<translation><from>List of Subcategories</from><to>Liste der Unterkategorien</to></translation>
+<translation><from>List Server</from><to>Listen-Server</to></translation>
+<translation><from>List View</from><to>Listenansicht</to></translation>
+<translation><from>Loading...</from><to>Ladevorgang...</to></translation>
+<translation><from>Location</from><to>Ort</to></translation>
+<translation><from>Locked</from><to>Gesperrt</to></translation>
+<translation><from>Lock topic</from><to>Thema sperren</to></translation>
+<translation><from>Login</from><to>Anmelden</to></translation>
+<translation><from>Log is empty</from><to>Protokoll ist leer</to></translation>
+<translation><from>Log out</from><to>Abmelden</to></translation>
+<translation><from>Lowest</from><to>Niedrigste</to></translation>
+<translation><from>Low</from><to>Niedrig</to></translation>
+<translation><from>Mailing List Address</from><to>Adresse Mailing-Liste</to></translation>
+<translation><from>Mailing list archive settings</from><to>Archiv-Einstellungen der Mailing-Liste</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>Archiv-Einstellungen Mailing-Liste</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>Mailing-Liste Abonnementerinnerung</to></translation>
+<translation><from>Mailing List Website</from><to>Mailing-Liste Website</to></translation>
+<translation><from>Main Page</from><to>Hauptseite</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>Bitte sicherstellen, dass aktuell der gleiche WEB-Browser verwendet wird, der auch zur Erstellung der Registrieranfrage verwendet wurde.</to></translation>
+<translation><from>Manage banned users</from><to>Verbannte Nutzer verwalten</to></translation>
+<translation><from>Manage pinned topics</from><to>Angeheftete Themen verwalten</to></translation>
+<translation><from>Manage subscribers</from><to>Abonnenten verwalten</to></translation>
+<translation><from>Manage Subscribers</from><to>Verwaltung Abonnenten</to></translation>
+<translation><from>Manage <t.items/></from><to>Verwaltung <n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>Nutzer & Gruppen verwalten</to></translation>
+<translation><from>Manage Users & Groups</from><to>Verwaltung Nutzer & Gruppen</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>Massen-Werbung, irreführende Formulierungen, Betrug und Fälschung.</to></translation>
+<translation><from>max. 80 characters</from><to>max. 80 Zeichen</to></translation>
+<translation><from>Message date</from><to>Nachrichtendatum</to></translation>
+<translation><from>message</from><to>Nachricht</to></translation>
+<translation><from>Message</from><to>Nachricht</to></translation>
+<translation><from>Message is in HTML Format</from><to>Nachricht im HTML-Format</to></translation>
+<translation><from>messages</from><to>Nachrichten</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>Hier verfasste Nachrichten werden an die Mailing-Liste versandt.</to></translation>
+<translation><from>Message subject contains</from><to>Betreff der Nachricht enthält</to></translation>
+<translation><from>Message text contains</from><to>Text der Nachricht enthält</to></translation>
+<translation><from>Mixed</from><to>Gemischt</to></translation>
+<translation><from>Modified</from><to>Verändert</to></translation>
+<translation><from>Archives</from><to>Archive</to></translation>
+<translation><from>More Categories</from><to>Mehr Kategorien</to></translation>
+<translation><from>More</from><to>Mehr</to></translation>
+<translation><from>more help</from><to>mehr Hilfe</to></translation>
+<translation><from>more options</from><to>weitere Einstellungen</to></translation>
+<translation><from>Move post</from><to>Verschiebe Beitrag</to></translation>
+<translation><from>Move Post</from><to>Beitrag Verschieben</to></translation>
+<translation><from>Move topic</from><to>Verschiebe Thema</to></translation>
+<translation><from>My Nabble Applications</from><to>Meine Nabble-Applikationen</to></translation>
+<translation><from>My Pending Posts</from><to>Meine Anstehenden Beiträge</to></translation>
+<translation><from>My posts</from><to>Meine Beiträge</to></translation>
+<translation><from>My starred items</from><to>Meine favorisierten Beiträge</to></translation>
+<translation><from>Nabble Support</from><to>Nabble Support</to></translation>
+<translation><from>Name</from><to>Name</to></translation>
+<translation><from>New Post</from><to>Neue Nachricht</to></translation>
+<translation><from>news</from><to>Neuigkeiten</to></translation>
+<translation><from>News</from><to>Neuigkeiten</to></translation>
+<translation><from>New Topic</from><to>Neues Thema</to></translation>
+<translation><from>New topics only</from><to>Nur neue Themen</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>WICHTIG</n.important.>: Alles unter <n.location/> wird unwiederbringlich gelöscht!</to></translation>
+<translation><from>No banned users.</from><to>Keine verbannten Nutzer.</to></translation>
+<translation><from>No Filter</from><to>Keine Filter</to></translation>
+<translation><from>none of the words:</from><to>keines der Begriffe:</to></translation>
+<translation><from>No registered user found with this email.</from><to>Es wurde kein registrierter Nutzer zu dieser eMail gefunden.</to></translation>
+<translation><from>No replies</from><to>Keine Antworten</to></translation>
+<translation><from>Normal</from><to>Normal</to></translation>
+<translation><from>No sub-forums</from><to>Keine Unterforen</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>Bitte beachten, dass diese Adresse unverwechselbar ist und nur eMails akzeptiert, die von <n.address/> gesendet wurden. Der Zweck dieses Vorgehens ist die Einschränkung von Spam.</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email,control your starred items or have access to your global profile.</from><to><n.register_link.>Jetzt registrieren</n.register_link.>, um das Profil bearbeiten, Nachrichten per eMail empfangen, Favoriten verwalten oder auf das globale Profil zugreifen zu können.</to></translation>
+<translation><from>one email per input box</from><to>eine eMail pro Eingangs-Box</to></translation>
+<translation><from>Online Users</from><to>Nutzer Online</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>Nur autorisierte Nutzer dürfen in diesem Bereich fortfahren.</to></translation>
+<translation><from>Open this post in classic view</from><to>Diese Nachricht in der klassischen Ansicht betrachten</to></translation>
+<translation><from>Open this post in list view</from><to>Diese Nachricht in der Listenansicht betrachten</to></translation>
+<translation><from>Open this post in threaded view</from><to>Diese Nachricht in verschachtelter Ansicht betrachten</to></translation>
+<translation><from>Options</from><to>Einstellungen</to></translation>
+<translation><from>or</from><to>oder</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>Die eMail kann auch ignoriert werden, um damit den Nutzer weiterhin von diesem Bereich fernzuhalten.</to></translation>
+<translation><from>Other Settings</from><to>Andere Einstellungen</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>Seite, die Unter-Applikationen und Diskussionen zusammenfasst.</to></translation>
+<translation><from>Page <t.number/></from><to>Seite <n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>Seite, die Unter-Applikationen und Diskussionen auflistet.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>Seite, die ganze Nachrichten und zugehörige Kommentaren enthält.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>Seite, Überschriften und Beiträge enthält.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>Seite, die Themen und Diskussionen enthält.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>Seite, die nach Unter-Applikationen gruppierte Themen enthält. Falls keine Unter-Applikationen existieren, wird eine einfache Liste der Themen angezeigt.</to></translation>
+<translation><from>Password</from><to>Passwort</to></translation>
+<translation><from>Password Sent</from><to>Passwort Abgeschickt</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>Pädophilie, Gewalt und anderer Missbrauch.</to></translation>
+<translation><from>People</from><to>Personen</to></translation>
+<translation><from>People in <t.location/></from><to>Personen in <n.location/></to></translation>
+<translation><from>Permalink</from><to>Statischer Link</to></translation>
+<translation><from>Photo and image gallery.</from><to>Foto- und Bildgalerie.</to></translation>
+<translation><from>Pinned sub-forum</from><to>Angeheftetes Unterforum</to></translation>
+<translation><from>Pin topic</from><to>Thema anheften</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>Bitte nun ein Lesezeichen für den oben stehenden Link setzen oder diese eMail speichern, so dass diese <n.app/>-Applikation in Zukunft einfach aufgesucht werden kann.</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>Bitte jetzt den Posteingang überprüfen und das Nutzerkonto aktivieren, um Zugriff auf alle Funktionen zu haben.</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>Bitte auf den unten stehenden Bestätigungs-Link klicken, um das Nutzerkonto zu aktivieren:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>Bitte den Nabble Support kontaktieren, falls Hilfe benötigt wird.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>Bitte die Administratoren kontaktieren, falls Hilfe benötigt wird.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>Bitte eine gültige eMail-Adresse eingeben und noch einmal versuchen.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>Bitte die Anweisungen in der eMail befolgen, um die Registrierung abzuschließen.</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>Bitte den <n.subscribe_instructions_link.>Registrierungsanweisungen</n.subscribe_instructions_link.> für dieses Archiv folgen.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>Bitte einen gültigen statischen Link angeben.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>Bitte nochmals eMail-Adresse und Passwort angeben und auf Login klicken.</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>Bitte die Verhaltensregeln für Mailing-Listen respektieren</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>Umfragen von Polldaddy.com (nur Flash-Umfragen)</to></translation>
+<translation><from>Post by email</from><to>Per eMail verfassen</to></translation>
+<translation><from>Post by Email</from><to>Verfassen per eMail</to></translation>
+<translation><from>Post Count</from><to>Anzahl Beiträge</to></translation>
+<translation><from>Posted by <t.author/></from><to>Verfasst von <n.author/></to></translation>
+<translation><from>post</from><to>Beitrag</to></translation>
+<translation><from>Post Message</from><to>Nachricht Verfassen</to></translation>
+<translation><from>Post New Message</from><to>Neue Nachricht Verfassen</to></translation>
+<translation><from>Post new message in <t.location/></from><to>Neue Nachricht in <n.location/> verfassen</to></translation>
+<translation><from>posts</from><to>Beiträge</to></translation>
+<translation><from>Posts</from><to>Beiträge</to></translation>
+<translation><from>Posts in <t.location/></from><to>Beiträge in <n.location/></to></translation>
+<translation><from>Preview Message</from><to>Nachrichtenvorschau</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>Preise sind in US Dollar angegeben. Dieser Abrechnungsvorgang nutzt <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>. Dies ermöglicht es den Kunden von Amazon, diejenigen Zahlungsmethoden zu nutzen, die in ihrem Kundenkonto bei Amazon.com für Güter und WEB-Dienstleistungen bereits verfügbar sind. Dies schließt auch Anwendungen ein, die Amazon-Zahlungen akzeptieren.</to></translation>
+<translation><from>Print post</from><to>Beitrag drucken</to></translation>
+<translation><from>Priority</from><to>Priorität</to></translation>
+<translation><from>(private)</from><to>(privat)</to></translation>
+<translation><from>Profile of <t.author/></from><to>Profil von <n.author/></to></translation>
+<translation><from>Quote</from><to>Zitieren</to></translation>
+<translation><from>Quote the original message</from><to>Die Originalnachricht zitieren</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>Bitte die (ggf. auf wesentliche Inhalte verkürzten) Anteile zitieren, zu denen in der neuen Nachricht Stellung genommen wird. Das ermöglicht ein Nachvollziehen des Sinnzusammenhangs wenn dem Leser nur die eMail-Nachricht zur Verfügung steht.</to></translation>
+<translation><from>Raw mail</from><to>Rohfassung der eMail</to></translation>
+<translation><from>Raw text</from><to>Rohfassung des Texts</to></translation>
+<translation><from>read more</from><to>weiter lesen</to></translation>
+<translation><from>Read more</from><to>Weiter lesen</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>Schreibgeschützte Liste aller Nutzer, die eine eMail unter <n.location/> haben.</to></translation>
+<translation><from>Receive direct replies only.</from><to>Nur direkte Antworten empfangen.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>Jede Nachricht empfangen, die in <n.location/> verfasst wird.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>Jede Antwort zu diesem Thema empfangen.</to></translation>
+<translation><from>Receive new topics only.</from><to>Nur neue Themen empfangen.</to></translation>
+<translation><from>Refresh</from><to>Aktualisieren</to></translation>
+<translation><from>Registered</from><to>Registriert</to></translation>
+<translation><from>Registered Users</from><to>Registrierte Nutzer</to></translation>
+<translation><from>Register</from><to>Registrieren</to></translation>
+<translation><from>Registering...</from><to>Registriere...</to></translation>
+<translation><from>Register Now</from><to>Jetzt Registrieren</to></translation>
+<translation><from>Register to <t.app/></from><to>Für die <n.app/>-Applikation registrieren</to></translation>
+<translation><from>Registration Confirmed</from><to>Registrierung Bestätigt</to></translation>
+<translation><from>Registration Failed</from><to>Registrierung Fehlgeschlagen</to></translation>
+<translation><from>Related Help Article</from><to>Zugeordneter Hilfstext</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>Bitte berücksichtigen, dass die Verbannungsaktion nicht in jedem Fall effektiv ist, da Nutzer immer wieder mit neuen Angaben ein Nutzerkonto einrichten können.</to></translation>
+<translation><from>Remove Ads</from><to>Werbung Ausblenden</to></translation>
+<translation><from>remove</from><to>entfernen</to></translation>
+<translation><from>Remove Settings</from><to>Einstellungen Entfernen</to></translation>
+<translation><from>Remove Subscription</from><to>Abonnement Beenden</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>Persönliches Nutzerkonto sowie alle damit verbundenen Nachrichten bzgl. <n.subject/> löschen.</to></translation>
+<translation><from>Remove Your Account</from><to>Persönliches Nutzerkonto Löschen</to></translation>
+<translation><from>replies</from><to>Antworten</to></translation>
+<translation><from>Replies</from><to>Antworten</to></translation>
+<translation><from>Reply</from><to>Antwort</to></translation>
+<translation><from>reply</from><to>antworten</to></translation>
+<translation><from>Reply to author</from><to>Dem Verfasser antworten</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>Inhalt als Unangemessen Melden</to></translation>
+<translation><from>Report Now</from><to>Jetzt Melden</to></translation>
+<translation><from>required</from><to>benötigt</to></translation>
+<translation><from>Return to <t.location/></from><to>Zurück zu <n.location/></to></translation>
+<translation><from>Save Changes</from><to>Änderungen Sichern</to></translation>
+<translation><from>Save Settings</from><to>Einstellungen Sichern</to></translation>
+<translation><from>Save Subscription</from><to>Abonnement Sichern</to></translation>
+<translation><from>Search</from><to>Suchen</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>Bitte hier die gewünschten Aktivitäten auswählen:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>Bitte die Kategorie auswählen, die bzgl. der Inhalte dieser Seite am ehesten zutrifft.</to></translation>
+<translation><from>Send email to me</from><to>Mir eine eMail senden</to></translation>
+<translation><from>Send Email to <t.author/></from><to><n.author/> eine eMail senden</to></translation>
+<translation><from>Send Request</from><to>Anfrage Versenden</to></translation>
+<translation><from>Send To:</from><to>Senden An:</to></translation>
+<translation><from>Sexual content</from><to>Sexuelle Inhalte</to></translation>
+<translation><from>Show</from><to>Zeigen</to></translation>
+<translation><from>Show Nabble notice</from><to>Zeige Nabble Hinweis</to></translation>
+<translation><from>Sincerely,</from><to>Mit freundlichen Grüßen,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>Da diese Anwendung ein Mailinglisten-Archiv ist, sollte vor dem Druck auf die "Entfernen"-Schaltfläche noch diese eMail-Adresse abgemeldet wird.</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>Bitte die Sicherheitsabfrage zur Überprüfung aller nicht registrierten Nutzer beantworten. Dies ist zur Vermeidung von Spam notwendig.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>Einige der mit diesem Nutzer verbundenen Beiträge wurden von <n.location/> entfernt. Anbei nun Kopien dieser Beiträge zur Ermöglichung einer persönlichen Sicherung.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>Entschuldigung. Nur Mitglieder können in der <n.app/>-Applikation Nachrichten verfassen.</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>Entschuldigung. Es wurde eine persönliche Verbannung von den Administratoren ausgesprochen.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>Entschuldigung. Diese eMail-Adresse ist nicht autorisiert, um Nachrichten unter <n.location/> zu lesen.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>Entschuldigung. Es ist nicht möglich, Themen hier zu erstellen.<br/>Das Antworten auf Nachrichten könnte allerdings unabhängig davon möglich sein.</to></translation>
+<translation><from>Sort by date</from><to>Nach Datum sortieren</to></translation>
+<translation><from>Sort by relevance</from><to>Nach Relevanz sortieren</to></translation>
+<translation><from>Sorted by date</from><to>Nach Datum sortiert</to></translation>
+<translation><from>Sorted by relevance</from><to>Nach Relevanz sortiert</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[Spam Detektor] Ungültige Nachricht mit zu vielen '<n.text/>' Wörtern.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[Spam Detektor] Die Nachricht darf kein '<n.text/>' enthalten.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[Spam Detektor] Die Nachricht beinhaltet allgemeine Spam-Wörter.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[Spam Detektor] Der Betreff darf kein '<n.text/>' enthalten.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[Spam Detektor] Der Betreff enthält zu viele Spam-Wörter.</to></translation>
+<translation><from>Spam</from><to>Spam</to></translation>
+<translation><from>Stars in <t.location/></from><to>Favoriten in <n.location/></to></translation>
+<translation><from>Structure</from><to>Struktur</to></translation>
+<translation><from>Subcategories</from><to>Unterkategorien</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>Unterkategorien unter <n.location/></to></translation>
+<translation><from>Subcategory</from><to>Unterkategorie</to></translation>
+<translation><from>Sub-Forum</from><to>Unterforum</to></translation>
+<translation><from>Sub-Forums</from><to>Unterforen</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>Unterforen & Themen</to></translation>
+<translation><from>Subject</from><to>Betreff</to></translation>
+<translation><from>Subscribe</from><to>Abonniere</to></translation>
+<translation><from>subscriber</from><to>Abonnent</to></translation>
+<translation><from>subscribers</from><to>Abonnenten</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>Abonniere <n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>Abonniere per eMail</to></translation>
+<translation><from>Subscription Confirmation</from><to>Bestätigung des Abonnements</to></translation>
+<translation><from>Subscription Confirmed</from><to>Abonnement Bestätigt</to></translation>
+<translation><from>Subscription Format</from><to>Format des Abonnements</to></translation>
+<translation><from>Subscription Removed</from><to>Abonnement Beendet</to></translation>
+<translation><from>Subscription Results</from><to>Ergebnisse des Abonnements</to></translation>
+<translation><from>Subscription Type</from><to>Typ des Abonnements</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>Eine persönliche Bestätigungs-eMail wurde erfolgreich versandt.</to></translation>
+<translation><from>Success</from><to>Gelungen</to></translation>
+<translation><from>Take Action</from><to>Aktivität Durchführen</to></translation>
+<translation><from><t.app/> Registration</from><to>Anmeldung <n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to><n.author/> wurde erfolgreich verbannt.</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to><n.author/> wurde erfolgreich aus der Verbannung befreit.</to></translation>
+<translation><from>Tell me more & show examples</from><to>Weitere Informationen & Beispiele</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>Vielen Dank für die Anmeldung in der <n.app/>-Applikation!</to></translation>
+<translation><from>Thank You</from><to>Vielen Dank</to></translation>
+<translation><from>The author has deleted this message.</from><to>Der Autor hat diese Nachricht entfernt.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>Der URL-Code ist ungültig.</to></translation>
+<translation><from>the exact phrase:</from><to>die genaue Formulierung:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>Es wird eine persönliche Registrierung bei der Mailing-Liste notwendig sein, bevor eine Nachrichtenannahme erfolgen kann. Bitte beachten, dass eine Registrierung bei Nabble mit KEINER automatischen Registrierung bei dieser Mailing-Liste verbunden ist. Ist die Registrierung noch nicht erfolgt, sollte sie jetzt erfolgen. Besteht Unsicherheit bzgl des persönlichen Registrierungsstatus, so wird eine erneute Registrierung empfohlen.</to></translation>
+<translation><from>The Nabble team</from><to>das Nabble-Team</to></translation>
+<translation><from>The name of the group is not valid.</from><to>Der Name der Gruppe ist ungültig.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>Der übergeordnete Beitrag kann nicht selbst der Beitrag sein.</to></translation>
+<translation><from>The password fields don't match.</from><to>Die Passwort-Felder stimmen nicht überein.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>Ein nicht-registriertes Nutzerkonto ist mit der eMail-Adresse <n.email/> verbunden.</to></translation>
+<translation><from>The subject is required.</from><to>Ein Betreff ist erforderlich.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>Der Nutzer wird eine Kopie aller gelöschten Beiträge erhalten; so hat er/sie die Möglichkeit diese für sich zu sichern.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>Der Überprüfungscode passt nicht zum Bild.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>Dieser Zweig ist zu groß und so wurden einige Beiträge aus der Ansicht entfernt. Bitte die anderen Ansichten verwenden, um alle Beiträge lesen zu können.</to></translation>
+<translation><from>This email is already subscribed.</from><to>Diese eMail ist bereits vergeben und registriert.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>Dieses Forum ist ein Archiv für die Mailing-Liste</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>Dieses Forum ist ein Archiv bzw. ein Portal zur Weiterleitung der entsprechenden Nachrichten nach <b><n.mailing_list_address/></b>.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>Dies schließt Unterkategorien, Beiträge, Bilder, Dateien und weiteres ein.</to></translation>
+<translation><from>This is a mailing list archive</from><to>Dies ist ein Archiv für eine Mailing-Liste</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>Dies ist eine automatisch von Nabble versendete eMail, um das erfolgreiche Anlegen der <n.app/>-Applikation zu bestätigen. Falls diese <n.app/>-Applikation fälschlicherweise angelegt wurde, wird darum gebeten, Nabble schnellstmöglich über das Nabble Support Forum zu kontaktieren.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>Diese Liste akzeptiert nur eMails in einfachem Textformat</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>Diese Liste zeigt registrierte, nicht-registrierte und verbannte Nutzer. Anonyme Nutzer sind nicht aufgeführt, da sie ohne registrierte eMail-Adresse zu keiner Gruppe gehören können.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>Diese Nachricht wird von <b><n.from/></b> zur Mailing-Liste <b><n.to/></b> gesendet.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to>Dieser Beitrag wurde bisher noch NICHT von der Mailing-Liste akzeptiert.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>Dieser Beitrag wurde am <n.date/> aktualisiert.</to></translation>
+<translation><from>This topic has been locked.</from><to>Dieses Thema wurde gesperrt.</to></translation>
+<translation><from>This topic has been pinned.</from><to>Dieses Thema wurde befestigt.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>Dieses Thema wurde in <n.location/> befestigt.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>Dieses Thema wurde entsperrt.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>Dieses Thema wurde losgelöst.</to></translation>
+<translation><from>This topic has unread posts</from><to>Dieses Thema enthält ungelesene Beiträge</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>Dieses Thema wurde persönlich mit der Priorität <n.priority/> zugewiesen.</to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>Dieser Nutzer ist nicht berechtigt, diese Applikation zu sehen (vor einem erfolgreichen Zugriffsversuch ist eine Zuweisung zu einer entsprechenden Gruppe notwendig)</to></translation>
+<translation><from>This user name is already in use.</from><to>Dieser Nutzername wird bereits verwendet.</to></translation>
+<translation><from>Threaded</from><to>Gesprächsfaden</to></translation>
+<translation><from>Threaded View</from><to>Gesprächsfaden-Ansicht</to></translation>
+<translation><from>Time</from><to>Zeit</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>TIPP: Falls das Archiv bei der Mailing-Liste registriert ist, aber trotzdem nicht funktioniert, könnte versucht werden, den X-No-Archive Kopf zu ignorieren.</to></translation>
+<translation><from>Tips</from><to>Tipps</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> beantragt eine entsprechende Autorisierung, um <n.location/> beizutreten:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> Kredit-Punkte</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> Seitenaufrufe ohne Werbung.</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>Um diese Anfrage zu akzeptieren, sollte dieser Nutzer zu mindestens einer Gruppe hinzugefügt werden, die Zugriff auf diesen Bereich hat:</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>Um diese <n.app/>-Applikation einer Website hinzuzufügen, muss der folgende Code kopiert und in die entsprechende HTML-Seite eingefügt werden:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>Um das Abonnement zu bestätigen, ist ein Klick auf unten stehenden Link notwendig:</to></translation>
+<translation><from>topic</from><to>Thema</to></translation>
+<translation><from>Topics and replies</from><to>Themen und Antworten</to></translation>
+<translation><from>topics</from><to>Themen</to></translation>
+<translation><from>Topics</from><to>Themen</to></translation>
+<translation><from>Topics only</from><to>Nur Themen</to></translation>
+<translation><from>Topics View</from><to>Themenansicht</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>Um Spam zu verhindern, werden für Beiträge nur eMails von <b>eindeutig registrierten</b> Nutzer-eMail-Adressen zugelassen.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>Um eine Gruppe zu löschen, muss das Textfeld komplett gelöscht werden. Anschließend speichern.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>Um angezeigt zu bekommen, welche eMail-Adresse für Beiträge genutzt werden können, ist ein <n.login_link.>Anmeldung</n.login_link.> oder eine <n.register_link.>Registrierung</n.register_link.> notwendig.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>Um ein neues Thema unter <n.location/> zu beginnen, ist eine eMail an <n.p2/> zu versenden</to></translation>
+<translation><from>Total</from><to>Gesamt</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>Um das Abonnement bzgl. <n.location/> zu beenden</to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to>dies ist eine Einladung von <n.owner_name/>, <n.location/> zu abonnieren:</to></translation>
+<translation><from>Turn off highlighting</from><to>Hervorhebungen ausschalten</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/> hat eine neue Unterkategorie erzeugt</to></translation>
+<translation><from>Unable to Post</from><to>Beitragen nicht Möglich</to></translation>
+<translation><from>Unassigned</from><to>Nicht Zugewiesen</to></translation>
+<translation><from>Unauthorized</from><to>Nicht Autorisiert</to></translation>
+<translation><from>Unban this user</from><to>Verbannung für Nutzer auflösen</to></translation>
+<translation><from>Unban User</from><to>Verbannung Lösen</to></translation>
+<translation><from>Unknown or Other</from><to>Unbekannt oder Sonstiges</to></translation>
+<translation><from>Unlock topic</from><to>Thema entsperren</to></translation>
+<translation><from>Unpin topic</from><to>Thema loslösen</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>Nicht-Registriert / Deaktiviert</to></translation>
+<translation><from>Unregistered</from><to>Nicht-Registriert</to></translation>
+<translation><from>Unregistered User</from><to>Nicht-Registrierter Nutzer</to></translation>
+<translation><from>Unsubscribe</from><to>Abonnement Beenden</to></translation>
+<translation><from>Upload a file</from><to>Eine Datei hochladen</to></translation>
+<translation><from>User email:</from><to>Nutzer eMail:</to></translation>
+<translation><from>user</from><to>Nutzer</to></translation>
+<translation><from>User is online</from><to>Nutzer ist online</to></translation>
+<translation><from>User Name</from><to>Nutzername</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>Ein Nutzer beantragt Berechtigung, um <n.location/> beizutreten</to></translation>
+<translation><from>users</from><to>Nutzer</to></translation>
+<translation><from>Users</from><to>Nutzer</to></translation>
+<translation><from>Users & Groups</from><to>Nutzer & Gruppen</to></translation>
+<translation><from>Users that completed the registration process</from><to>Nutzer, die den Registrierungsvorgang abgeschlossen haben</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>Tags wie <n.example1/> oder <n.example2/> sind zur Erstellung von Unter-Überschriften zu nutzen.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>Unten stehende Einstellungen sind zu nutzen, um die Suchkriterien klar zu definieren.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to><n.tag_names/> sind zu nutzen, um Widgets anderer Websites einzubetten.</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>Alle Nachrichten dieses Unterforums anzeigen</to></translation>
+<translation><from>view</from><to>Anzeigen</to></translation>
+<translation><from>View mailing list website</from><to>Website der Mailing-List anzeigen</to></translation>
+<translation><from>View message</from><to>Nachricht anzeigen</to></translation>
+<translation><from>View more</from><to>Mehr anzeigen</to></translation>
+<translation><from>views</from><to>Aufrufe</to></translation>
+<translation><from>Views</from><to>Aufrufe</to></translation>
+<translation><from>Violent or repulsive content</from><to>Gewaltverherrlichender oder abstoßender Inhalt</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>Besuche die <n.app/>-Applikation hier:</to></translation>
+<translation><from>visit <t.url/></from><to>besuche <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>Wir haben eine Seite vorbereitet, die <n.unsubscription_instructions_link.>einige Anleitungen bzgl. der Abmeldung von diesem Archiv</n.unsubscription_instructions_link.> enthält.</to></translation>
+<translation><from>We will review your report soon.</from><to>Wir werden die Meldung in Kürze überprüfen.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>Welche Gruppen gestatten eine Auflistung der Mitglieder</to></translation>
+<translation><from>Who can ban/unban users</from><to>Wer ist berechtigt, Nutzer zu verbannen bzw. die Verbannung zu lösen</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>Wem dürfen Themen zugeordnet werden (nur in Arbeitsgruppen)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>Wer ist berechtigt, Datum und Uhrzeit von Nachrichten verändern</to></translation>
+<translation><from>Who can create new topics under this application</from><to>Wer ist berechtigt, neue Themen in dieser Applikation einzustellen</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>Wer ist berechtigt, Unter-Applikationen zu erzeugen (z.B. Unterforen, Unterkategorien, usw.)</to></translation>
+<translation><from>Who can edit any content, both applications and posts. Note: Please only use this feature in extreme circumstances. Most users will not like having their posts edited by someone else.</from><to>Wer ist berechtigt, jeglichen Inhalt sowohl in Applikationen als auch in Beiträgen zu editieren. Anmerkung: Bitte diese Funktion nur bei Notfällen nutzen. Die meisten Nutzer sind über eine Veränderung ihrer Beiträge durch andere Personen nicht erfreut.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>Wer ist berechtigt, Applikationen zu modifizieren (z.B. Bezeichnung, Beschreibung, usw.)</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>Wer ist berechtigt, Themen in dieser Applikation zu sperren bzw. zu entsperren</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>Wer ist berechtigt, Abonnenten dieser Applikation zu verwalten</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>Wer ist berechtigt, Nachrichten zu verschieben (z.B. unter andere Themen oder in andere Unterforen)</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>Wei ist berechtigt, Themen in dieser Applikation zu befestigen / zu lösen</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>Wer ist berechtigt, jeglichen Inhalt ohne Beschränkungen zu verfassen (incl. JavaScript Code, Tags für &lt;object&gt; und &lt;style&gt;, etc.). <b>Sicherheitswarnung</b>: Diese Option sollte nur Nutzern / Gruppen gewährt werden, die volles Vertrauen genießen.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>Wer ist berechtigt, auf Nachrichten zu antworten, die in dieser Applikation verfasst wurden</to></translation>
+<translation><from>Who can view this application and its contents</from><to>Wer ist berechtigt, diese Applikation und ihre Inhalte zu sehen</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>Dieses Abonnement sorgt dafür, dass Aktualisierungen per eMail an den Abonnenten versendet werden. Es ist daraufhin möglich, direkt über den eMail-Client eine Antwort zu verfassen, die wie ein normaler Beitrag im Forum erscheint. Die Wirkungsweise ist die gleiche wie bei einer Mailing-Liste.</to></translation>
+<translation><from>Write Your First Headline</from><to>Den Ersten Betreff Schreiben</to></translation>
+<translation><from>Write Your First Post</from><to>Den Ersten Beitrag Schreiben</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>Ja, bitte <n.location/> unwiederbringlich löschen</to></translation>
+<translation><from>Yes, unsubscribe now</from><to>Ja, das Abonnement beenden</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>Ein Abonnement für <n.location/> besteht bereits.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>Ein Abonnement für <n.location/> besteht nicht.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>Die <n.manage_banned_users_link.>verbannten Nutzer</n.manage_banned_users_link.> können auch über <n.location/> verwaltet werden.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>Für die unten stehenden Gruppen können die <n.root_node.change_permissions_link.>Berechtigungen verändert</n.root_node.change_permissions_link.> werden.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>Eine Bekanntmachung dieser <n.app/>-Applikation kann erfolgen, indem Freunden / Bekannten dieser Link zugesandt wird, durch Einbettung des Links in Websites oder auch durch Verbreitung des Links in anderen Foren.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>Das Verschieben dieses Beitrags an die gewünschte Position ist nicht möglich, denn dort ist die Beteiligung anonymer Nutzer nicht gestattet.</to></translation>
+<translation><from>You Cannot Post Here</from><to>Ein Verfassen Ist Hier Nicht Gestattet</to></translation>
+<translation><from>(you can reply by email)</from><to>(antworten per eMail möglich)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>Eine Verschiebung des Beitrags ist nirgendwo hin möglich.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>Das Verfassen einer Nachricht ist hier nicht möglich. Dafür aber an anderer Stelle.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>Es besteht die Möglichkeit einer <n.register_link.>wiederholten Registrierung</n.register_link.> oder den Kontakt zu <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>Das unten stehende Formular kann genutzt werden, um eine Anfrage an den Administrator zu senden. </to></translation>
+<translation><from>You have already been registered.</from><to>Eine Registrierung ist bereits abgeschlossen.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>Es besteht eine Einladung, <n.location/> zu abonnieren. Dies ist hier möglich:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>Die Registrierung für <n.subject/> ist erfolgt.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>Es ist eine Abmeldung von <n.location/> erfolgt</to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>Es sind zu viele Beiträge in zu kurzer Zeit erfasst worden. Der Versuch zu einem späteren Zeitpunkt wird empfohlen.</to></translation>
+<translation><from>You logged out</from><to>Die Abmeldung ist erfolgt</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>Damit die Nachricht akzeptiert werden kann, ist ein <n.mailing_list_options_link.>Abonnement dieser Mailing-Liste</n.mailing_list_options_link.> erforderlich.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>Hier kann ein <n.page_node.unauthorized_link.>Antrag zur Berechtigung zum Verfassen von Beiträgen</n.page_node.unauthorized_link.> erfolgen oder ein Kontakt zu <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> falls Fragen bestehen.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>Ein Akzeptieren der Nutzungsbedingung ist Voraussetzung.</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>Zur Nutzung dieser Mailing-Liste wird die Angabe einer gültigen eMail-Adresse vorausgesetzt.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>Zur Nutzung dieser Mailing-Liste wird die Angabe einer gültigen URL vorausgesetzt.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>Das Ausfüllen aller unten stehenden Felder ist erforderlich.</to></translation>
+<translation><from>You must login to view this page.</from><to>Zur Ansicht dieser Seite ist eine Anmeldung notwendig.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>Zur Ansicht von <n.subject/> ist eine Anmeldung notwendig.</to></translation>
+<translation><from>You must login to your account.</from><to>Eine Anmeldung im mit persönlichen Nutzerdaten ist erforderlich.</to></translation>
+<translation><from>You must provide a user name.</from><to>Die Eingabe eines Nutzernamens ist erforderlich.</to></translation>
+<translation><from>You're not a subscriber</from><to>Abonnement ist nicht eingerichtet</to></translation>
+<translation><from>Your Name</from><to>Nutzername</to></translation>
+<translation><from>Your request has been successfully sent.</from><to>Die Anfrage wurde erfolgreich versendet.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>Das Abonnement wurde erfolgreich eingerichtet.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>das Abonnement von <n.location/> wurde beendet. Falls dies ein Versehen gewesen sein sollte, ist ein Wieder-Abonnieren über den folgenden Link möglich:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>Das Abonnement von <n.location/> wurde beendet.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to>Die <n.app/>-Applikation wurde erfolgreich erstellt.</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>Zum Verfassen neuer Themen in <n.location/> ist eine entsprechende Autorisierung notwendig. Zusätzlich zur Registrierung muss ein Administrator die entsprechende Berechtigung freischalten.</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>Bei jeder neuen Nachricht zu diesem Thema wird automatisch eine eMail an die hinterlegte eMail-Adresse erfolgen.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>An diese Adresse wird eine eMail verschickt. Hierin befindet sich dann ein Link, bei dessen Betätigung das Konto aktiviert wird.</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>Das Abonnement</to></translation>
+<translation><from>edit</from><to>bearbeiten</to></translation>
+<translation><from>Remove ads</from><to>Werbung ausblenden</to></translation>
+<translation><from>View profile of <t.author/></from><to>Profil von <n.author/> anzeigen</to></translation>
+<translation><from>Description</from><to>Beschreibung</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>Videos auf Youtube, Vimeo und LiveLeak</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>Verbannte Nutzer in <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>Betreff-Präfix</to></translation>
+
+# NEW 2
+<translation><from>Change title and meta tags</from><to>Titel und Meta-Informationen ändern</to></translation>
+<translation><from>Here you can customize the title and meta tags of the <t.location/> page.</from><to>Hier ist die Anpassung des Titels und der Meta-Informationen von <n.location/> möglich.</to></translation>
+<translation><from>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</from><to>Die unten stehenden Meta-Informationen können dabei unterstützen, diese Seite bzgl. der Suchmaschinen (z.B. Google, Yahoo!, usw.) zu optimieren.</to></translation>
+<translation><from>Use custom values</from><to>Kundenspezifische Werte nutzen</to></translation>
+<translation><from>Page Title</from><to>Seitentitel</to></translation>
+<translation><from>Enter a context-rich title that clearly identifies this page.</from><to>Bitte einen aussagekräftigen Titel eingeben, der die Seite klar identifiziert.</to></translation>
+<translation><from>The title should ideally be less than 70 characters in length.</from><to>Der Titel sollte idealerweise eine Länge von 70 Zeichen nicht überschreiten.</to></translation>
+<translation><from>Meta Description</from><to>Meta-Beschreibung</to></translation>
+<translation><from>Enter a brief and concise summary of your page's content.</from><to>Bitte eine kurze und präzise Zusammenfassung des Seiteninhalts eingeben.</to></translation>
+<translation><from>Limit your description to 155 characters or 170 characters at most.</from><to>Bitte die Beschreibung auf 155 Zeichen beschränken. Maximal 170 Zeichen.</to></translation>
+<translation><from>Mailing List Archive</from><to>Archiv für Mailing-Liste</to></translation>
+<translation><from>Filter: priority <t.priority/></from><to>Filter: Priorität <n.priority/></to></translation>
+<translation><from>Filter: assignee <t.author/></from><to>Filter: Zuordnung <n.author/></to></translation>
+<translation><from>Use Google Analytics</from><to>Nutzung von Google Analytics</to></translation>
+<translation><from>Analytics Account ID:</from><to>Analytics Konto ID:</to></translation>
+<translation><from>(e.g., UA-12345-0)</from><to>(z.B. UA-12345-0)</to></translation>
+<translation><from>Enter a valid analytics account ID.</from><to>Bitte eine gültige Analytics-Konto-ID eingeben.</to></translation>
+<translation><from>Here you can use Google Analytics to measure the success of your app.</from><to>Hier kann Google Analytics verwendet werden, um den Erfolg der Applikation zu bestimmen.</to></translation>
+<translation><from>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</from><to>Bitte hier die Analytics-Konto-ID eingeben, um so Besuche, Besucher und andere wichtige Statistiken bzgl. des Datenverkehrs der Seite im Auge zu behalten.</to></translation>
+
+<translation><from>Digest Email</from><to>Zusammenfassungs-eMail</to></translation>
+<translation><from>on <t.date/></from><to>am <n.date/></to></translation>
+<translation><from>DO NOT REPLY TO THIS EMAIL</from><to>BITTE NICHT AUF DIESE EMAIL ANTWORTEN</to></translation>
+<translation><from>Replies sent to this address are not read or processed.</from><to>Antworten an diese Adresse werden weder gelesen noch verarbeitet.</to></translation>
+<translation><from>If you want to respond to a post for which you received this email, please go to the website: <t.url/></from><to>Falls eine Antwort zu einem Beitrag gewünscht ist, auf den sich diese empfangene eMail bezieht, wird darum gebeten, die folgende Website aufzusuchen: <n.url/></to></translation>
+<translation><from>new post</from><to>neue Nachricht</to></translation>
+<translation><from>new posts</from><to>neue Nachrichten</to></translation>
+
+<translation><from>New registered user in <t.location/>!</from><to>Ein neuer registrierter Nutzer in <n.location/>!</to></translation>
+<translation><from>User profile</from><to>Nutzerprofil</to></translation>
+<translation><from>New user:</from><to>Neuer Nutzer:</to></translation>
+
+<translation><from><n.author/> wrote</from><to><n.author/> schrieb</to></translation>
+<translation><from>You still have <t.number/> day(s) left without advertising</from><to>Es sind noch <n.number/> Tag(e) ohne Werbung übrig</to></translation>
+<translation><from>(Only administrators can see this message)</from><to>(Nur Administratoren können diese Nachricht sehen)</to></translation>
+
+<translation><from>Some private items have been omitted because you don't have permission to view them.</from><to>Einige private Elemente wurden ausgeblendet, da keine Berechtigung zur Ansicht besteht.</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Submit". We will email you a link to reset your password.</from><to>Bitte die eMail-Adresse aus dem Registrierungs-Vorgang eingeben und auf "Absenden" klicken. Wir werden dorthin einen Link senden, über den ein Rücksetzen des Passworts erfolgen kann.</to></translation>
+<translation><from>Submit</from><to>Absenden</to></translation>
+<translation><from>Password Reset Sent</from><to>Passwortrücksetzung abgeschickt</to></translation>
+<translation><from>We have sent you a link to reset your password. Please check your email now. If you don't receive the instructions in a few minutes, check your spam folder or try to resend the request.</from><to>Wir haben einen Link zum Zurücksetzen des Passworts geschickt. Wir bitten um kurzfristige Überprüfung des eMail-Eingangs. Falls die eMail nicht in wenigen Minuten ankommt, bitten wir um Überprüfung des mit dem eMail-Konto verbundenen Spam-Ordners.</to></translation>
+<translation><from>Reset your password / <t.location/></from><to>Rücksetzung des Passworts / <n.location/></to></translation>
+<translation><from>We received a request to reset your password in <t.location/>.</from><to>Wir haben eine Anfrage erhalten, das persönliche Passwort in <n.location/> zurückzusetzen.</to></translation>
+<translation><from>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</from><to>Falls eine Rücksetzung des persönlichen Passworts gewünscht sein sollte, bitten wir darum, auf den unten angegebenen Link zu klicken (oder die URL zu kopieren und in die Adresszeile des Internetbrowsers einzufügen):</to></translation>
+<translation><from>If you don't want to reset your password, please ignore this message. Your password will not be reset.</from><to>Falls das persönliche Passwort doch nicht zurückgesetzt werden soll, bitten wir darum, diese Nachricht einfach zu ignorieren. Das Passwort wird dann nicht zurückgesetzt werden.</to></translation>
+
+<translation><from><b>IMPORTANT:</b> If you use this option, messages posted in the archive will NOT be sent to the mailing list.</from><to><b>WICHTIG:</b> Falls diese Option verwendet wird, werden im Archiv angelegte Nachrichten NICHT an die Mailing-Liste weitergeleitet.</to></translation>
+<translation><from>Message Preview</from><to>Nachrichtenvorschau</to></translation>
+<translation><from>Your changes will not be sent to the mailing list.</from><to>Die Änderungen werden nicht an die Mailing-Liste weitergeleitet.</to></translation>
+<translation><from>If you want others in the mailing list to know of your changes, please compose a new message or reply to your original message.</from><to>Falls gewünscht wird, dass andere Empfänger der Mailing-Liste Änderungsbenachrichtigungen erhalten, dann wird darum gebeten, eine neue Nachricht zu erstellen oder auf die Originalnachricht direkt zu antworten.</to></translation>
+
+<translation><from>Poll</from><to>Umfrage</to></translation>
+<translation><from>Add new poll</from><to>Neue Umfrage hinzufügen</to></translation>
+<translation><from>Question:</from><to>Frage:</to></translation>
+<translation><from>Answers:</from><to>Antwort:</to></translation>
+<translation><from>Add new answer</from><to>Neue Antwort hinzufügen</to></translation>
+<translation><from>1 vote</from><to>1 Stimme</to></translation>
+<translation><from><t.number/> votes</from><to><n.number/> Stimmen</to></translation>
+<translation><from>Total votes:</from><to>Gesamtanzahl Stimmen:</to></translation>
+<translation><from>Vote</from><to>Stimme</to></translation>
+<translation><from>Your vote has been submitted.</from><to>Die Stimme wurde registriert.</to></translation>
+<translation><from>This poll is closed.</from><to>Diese Umfrage ist geschlossen.</to></translation>
+<translation><from>This poll ends on <t.date/>.</from><to>Diese Umfrage endet am <n.date/>.</to></translation>
+<translation><from>This poll ended on <t.date/>.</from><to>Diese Umfrage wurde am <n.date/> beendet.</to></translation>
+<translation><from>Results will be shown only after poll has ended.</from><to>Ergebnisse werden nur nach Abschluss der Umfrage gezeigt.</to></translation>
+<translation><from>You have to vote before you can see the results.</from><to>Vor der Ergebniseinsicht ist eine Stimmabgabe notwendig.</to></translation>
+<translation><from>You cannot change your vote after voting.</from><to>Eine Stimmabgabe kann nicht rückgängig gemacht werden.</to></translation>
+<translation><from>You can select up to <t.number/> options.</from><to>Es können bis zu <n.number/> Optionen ausgewählt werden.</to></translation>
+<translation><from>Please select no more than <t.number/> options.</from><to>Bitte nicht mehr als <n.number/> Optionen auswählen.</to></translation>
+<translation><from>Please select at least one option.</from><to>Bitte mindestens eine Option auswählen.</to></translation>
+<translation><from>Remove Poll</from><to>Umfrage Entfernen</to></translation>
+<translation><from>Invalid poll parameters.</from><to>Ungültige Umfrageparameter.</to></translation>
+<translation><from>Poll duration must be a non-negative integer or blank.</from><to>Die Umfragedauer muss entweder eine positive Ganzzahl sein oder leer gelassen werden.</to></translation>
+<translation><from>Poll maximum allowed choices must be a non-negative integer or blank.</from><to>Die Auswahlanzahl für Umfragen muss entweder eine positive Ganzzahl sein oder leer gelassen werden.</to></translation>
+<translation><from>Delete this poll, including all votes?</from><to>Diese Umfrage entfernen mit allen Stimmabgaben?</to></translation>
+<translation><from>Poll has been deleted.</from><to>Die Umfrage wurde gelöscht.</to></translation>
+<translation><from>Who can create polls.</from><to>Wer ist berechtigt, Umfragen zu erzeugen.</to></translation>
+<translation><from>Allow vote changes</from><to>Änderung von Stimmabgaben zulassen</to></translation>
+<translation><from>Allow viewing results before end date (poll creators can always view the results)</from><to>Einsicht in Stimmenstatus erlauben, bevor Umfrage beendet ist (Umfrage-Erzeuger können zu jeder Zeit den Stimmenstatus abfragen)</to></translation>
+<translation><from>Allow viewing results before vote</from><to>Einsicht in Stimmenstatus vor Stimmabgabe erlauben</to></translation>
+<translation><from>Multiple selections allowed:</from><to>Mehrfachauswahl erlaubt:</to></translation>
+<translation><from>Poll ends after <t.number/> days (leave blank for unlimited).</from><to>Umfrage wird nach <n.number/> Tagen beendet (leer lassen für unbegrenzt).</to></translation>
+<translation><from>Login to vote</from><to>Zum Abstimmen bitte einloggen</to></translation>
+<translation><from>This message has a poll</from><to>Diese Nachricht ist eine Umfrage</to></translation>
+<translation><from>Visit the link below if you want to participate:</from><to>Bitte dem Link folgen, um teilzunehmen</to></translation>
+
+<translation><from>Current length: <t.number/> characters</from><to>Aktuelle Länge: <n.number/> Zeichen</to></translation>
+<translation><from>Edit Post</from><to>Beitrag Bearbeiten</to></translation>
+<translation><from><t.location/> - Your credits are running out</from><to><n.location/> - Kredit-Punkte sind fast aufgebraucht</to></translation>
+<translation><from><t.location/> is running out of ad-free credits.</from><to>Die Kredit-Punkte bzgl. Werbefreiheit für <n.location/> sind fast aufgebraucht.</to></translation>
+<translation><from>If you want to buy more credits, visit:</from><to>Zum Erwerb weiterer Kredit-Punkte, bitte besuchen:</to></translation>
+<translation><from>only in this topic</from><to>nur in diesem Thema</to></translation>
+<translation><from>everywhere</from><to>überall</to></translation>
+
+<translation><from>If you want to invite subscribers, please request this feature in the <n.support_link/> forum.</from><to>Falls eine Abonnenten-Einladung erfolgen soll, bitte diese Funktion im <n.support_link/> Forum beantragen.</to></translation>
+<translation><from>We can install this module for you, but the Nabble team must approve this request to prevent abuse and spam.</from><to>Wir können dieses Modul installieren, jedoch muss das Nabble-Team zunächst der Anfrage zustimmen, um Missbrauch und Spam zu verhindern.</to></translation>
+
+<translation><from>Edit Signature</from><to>Signatur Bearbeiten</to></translation>
+<translation><from>Current Signature</from><to>Aktuelle Signatur</to></translation>
+<translation><from>Save Signature</from><to>Signatur Speichern</to></translation>
+<translation><from>Signature is in HTML format</from><to>Signatur ist im HTML-Format</to></translation>
+
+<translation><from>Download backup</from><to>Backup herunterladen</to></translation>
+<translation><from>Backup</from><to>Backup</to></translation>
+<translation><from>Here you can download a backup of <t.location/>.</from><to>Hier kann ein Backup von <n.location/> heruntergeladen werden.</to></translation>
+<translation><from>When you press the button below, the system will start the generation of the backup file and this may take some minutes to finish. You will receive an email with a link to download the file when it is ready.</from><to>Nach dem Betätigen der unten angezeigten Schaltfläche, wird das System die Backup-Datei erzeugen. Dies kann einige Minuten dauern. Im Anschluss wird eine eMail versandt, die einen Link zum Herunterladen der Backup-Datei enthält.</to></translation>
+<translation><from>Generate backup file</from><to>Generiere Backup-Datei</to></translation>
+<translation><from>The system is generating the backup now. When it is finished, a link to the backup file will be emailed to you.</from><to>Das System erzeugt nun das Backup. Nach Beendigung wird ein Link zum Herunterladen der Backup-Datei per eMail versandt.</to></translation>
+<translation><from>The backup file is generated with the <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a> tool, which is an open source project in Java. You should visit the project website if you want to restore your backup into a Postgresql database.</from><to>Die Backup-Datei ist über das <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a> Programm erzeugt worden, welches ein OpenSource-Projekt in JAVA ist. Es wird empfohlen, die Projekt-Website zu besuchen, falls der Wunsch besteht, das Backup in einer PostgreSQL-Datenbank wiederherzustellen.</to></translation>
+<translation><from>Backup of <t.location/></from><to>Backup von <n.location/></to></translation>
+<translation><from>Here is your backup file:</from><to>Hier ist die Backup-Datei zu finden:</to></translation>
+<translation><from><t.location/> has been deleted</from><to><n.location/> wurde gelöscht</to></translation>
+<translation><from>Your Nabble site "<t.location/>" has been deleted.</from><to>Die Nabble-Seite "<n.location/>" wurde gelöscht.</to></translation>
+<translation><from>You can download a backup of this site from the link below. Nabble will try to keep this backup available for a few months, but this is not guaranteed. If this content is important to you, save this copy as soon as possible.</from><to>Das Herunterladen eines Backups dieser Seite ist möglich über den unten stehenden Link. Nabble wird versuchen, dieses Backup für einige Monate verfügbar zu halten, aber es besteht hierfür keine Garantie. Falls es sich um bedeutsame Inhalte handelt, sollte eine schnellstmögliche Sicherung der Backup-Datei erfolgen.</to></translation>
+
+<translation><from>This email was sent by <t.author/> (via Nabble)</from><to>Diese Nachricht wurde von <n.author/> verschickt (über Nabble)</to></translation>
+<translation><from>To receive all replies by email, <t.subscribe_instructions_link.>subscribe to this discussion</t.subscribe_instructions_link.></from><to>Um alle Antworten per eMail zu erhalten, <n.subscribe_instructions_link.>bitte diese Diskussion abonnieren</n.subscribe_instructions_link.></to></translation>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_ell.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,784 @@
+## Ελληνικά (Greek)
+
+## MONTHS ##
+
+<translation><from>January</from><to>Ιανουάριος</to></translation>
+<translation><from>February</from><to>Φεβρουάριος</to></translation>
+<translation><from>March</from><to>Μάρτιος</to></translation>
+<translation><from>April</from><to>Απρίλιος</to></translation>
+<translation><from>May</from><to>Μάιος</to></translation>
+<translation><from>June</from><to>Ιούνιος</to></translation>
+<translation><from>July</from><to>Ιούλιος</to></translation>
+<translation><from>August</from><to>Αύγουστος</to></translation>
+<translation><from>September</from><to>Σεπτέμβριος</to></translation>
+<translation><from>October</from><to>Οκτώβριος</to></translation>
+<translation><from>November</from><to>Νοέμβριος</to></translation>
+<translation><from>December</from><to>Δεκέμβριος</to></translation>
+
+<translation><from>Jan</from><to>Ιαν</to></translation>
+<translation><from>Feb</from><to>Φεβ</to></translation>
+<translation><from>Mar</from><to>Μαρ</to></translation>
+<translation><from>Apr</from><to>Απρ</to></translation>
+<!--translation><from>May</from><to>Μαϊ</to></translation-->
+<translation><from>Jun</from><to>Ιουν</to></translation>
+<translation><from>Jul</from><to>Ιουλ</to></translation>
+<translation><from>Aug</from><to>Αυγ</to></translation>
+<translation><from>Sep</from><to>Σεπτ</to></translation>
+<translation><from>Oct</from><to>Οκτ</to></translation>
+<translation><from>Nov</from><to>Νοε</to></translation>
+<translation><from>Dec</from><to>Δεκ</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>Η κατάχρηση αυτής της δυνατότητας συνιστά παραβίαση των όρων χρήσης.</to></translation>
+<translation><from>Access Request</from><to>Αίτηση πρόσβασης</to></translation>
+<translation><from>Account settings</from><to>Ρυθμίσεις λογαριασμού</to></translation>
+<translation><from>Account Settings</from><to>Ρυθμίσεις λογαριασμού</to></translation>
+<translation><from>Action</from><to>Δράση</to></translation>
+<translation><from>Add a link to another page</from><to>Προσθέστε σύνδεσμο προς άλλη σελίδα</to></translation>
+<translation><from>Add a new comment</from><to>Προσθέστε νέο σχόλιο</to></translation>
+<translation><from>Add a new group</from><to>Προσθέστε νέα ομάδα</to></translation>
+<translation><from>Add an image to your post</from><to>Προσθέστε εικόνα στο μήνυμά σας</to></translation>
+<translation><from>Add another address</from><to>Προσθήκη νέας διεύθυνσης</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>Προσθέστε περιεχόμενο που δεν θα έπρεπε να κωδικοποιηθεί (π.χ, κώδικας)</to></translation>
+<translation><from>Adding Sub-Headers</from><to>Προσθήκη Υπο-Κεφαλίδων</to></translation>
+<translation><from>Add New Group</from><to>Προσθέστε νέα ομάδα</to></translation>
+<translation><from>Add / Remove Groups</from><to>Προσθέστε / Αφαιρέστε Ομάδες</to></translation>
+<translation><from>Add smileys and funny animations</from><to>Προσθέστε φατσούλες και αστεία εικονίδια</to></translation>
+<translation><from>Add Subscribers</from><to>Προσθήκη Συνδρομητών</to></translation>
+<translation><from>Add this item to your favorites list</from><to>Προσθέστε αυτό το στοιχείο στη λίστα των αγαπημένων σας</to></translation>
+<translation><from>Administrator</from><to>Διαχειριστής</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity, other sexual content.</from><to>Απευθύνεται αποκλειστικά σε ενήλικες, σεξουαλική δραστηριότητα, γυμνό, ή άλλο σεξουαλικό περιεχόμενο.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>Μάχες ενηλίκων, σωματική επίθεση, νεανική βία, κακοποίηση ζώων ή προώθηση της τρομοκρατίας.</to></translation>
+<translation><from>Advanced Search</from><to>Σύνθετη Αναζήτηση</to></translation>
+<translation><from>Advanced Settings</from><to>Ρυθμίσεις για προχωρημένους</to></translation>
+<translation><from>Advertisement</from><to>Διαφήμιση</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>Ειδοποίησέ με μέσω email όταν κάποιος απαντήσει σε αυτό το μήνυμα</to></translation>
+<translation><from>All</from><to>Όλα</to></translation>
+<translation><from>all of the words:</from><to>όλες οι λέξεις:</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>Όλες οι δημοσιεύσεις από <n.author/> έχουν καταργηθεί επιτυχώς.</to></translation>
+<translation><from>All posts</from><to>Όλες οι δημοσιεύσεις</to></translation>
+<translation><from>All users belong to this group</from><to>Όλοι οι χρήστες αυτής της ομάδας</to></translation>
+<translation><from>All Users</from><to>Όλοι οι χρήστες</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>Όλοι οι χρήστες που έχουν εγγραφεί στο <n.location/>. Αυτών των χρηστών έχει επιβεβαιωθεί το e-mail και μπορούν να συνδεθούν με το σύστημα.</to></translation>
+<translation><from>Already Subscribed</from><to>Ήδη εγγεγραμένος</to></translation>
+<translation><from>An email has been sent to you.</from><to>Σας έχει σταλεί ένα μήνυμα ηλεκτρονικού ταχυδρομείου.</to></translation>
+<translation><from>Anonymous</from><to>Ανώνυμος</to></translation>
+<translation><from>anonymous user</from><to>ανώνυμος χήστης</to></translation>
+<translation><from>anonymous users</from><to>ανώνυμοι χρήστες</to></translation>
+<translation><from>Any message part contains</from><to>Κάθε μέρος του μηνύματος περιέχει</to></translation>
+<translation><from>Application</from><to>Εφαρμογή</to></translation>
+<translation><from>Apps</from><to>Εφαρμογές</to></translation>
+<translation><from>Assignee</from><to>Εντολοδόχος</to></translation>
+<translation><from>Assign</from><to>Εκχώρηση</to></translation>
+<translation><from>Assignment</from><to>Ανάθεση</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>Όπως ζητήσατε, η διεύθυνση ηλεκτρονικού ταχυδρομείου για να δημοσιεύετε νέα θέματα στο <n.app/> είναι:</to></translation>
+<translation><from>at least one of the words:</from><to>τουλάχιστο μία από τις λέξεις:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to>Ροές δεδομένων(Feeds) για <n.location/></to></translation>
+<translation><from>at priority</from><to>κατά προτεραιότητα</to></translation>
+<translation><from>Authorized Users Only</from><to>Εξουσιοδοτημένοι Χρήστες Μόνο</to></translation>
+<translation><from>Author name</from><to>Όνομα συγγραφέα</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>Αποφύγετε σύντομες φράσεις όπως "Ευχαριστώ", "Μπράβο"... Μπορείτε να <n.page_node.reply_to_author_link.>στείλετε ένα προσωπικό μήνυμα</n.page_node.reply_to_author_link.> αν θέλετε.</to></translation>
+<translation><from>Banned User</from><to>Αποκλεισμένος Συγγραφέας</to></translation>
+<translation><from>Ban this user</from><to>Αποκλεισμός αυτού του χρήστη</to></translation>
+<translation><from>Ban User</from><to>Αποκλεισμός Χρήστη</to></translation>
+<translation><from>Before deleting this archive...</from><to>Πριν διαγράψετε το αρχείο...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>Παρακάτω Μπορείτε να διαχειριστείτε τους χρήστες και ομάδες. Μπορείτε να αντιγράψετε και να επικολλήσετε τους χρήστες, προκειμένου να μετακινούνται από τη μία ομάδα στην άλλη.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>ΣΗΜΑΝΤΙΚΟ</b>:  Το Nabble θα στείλει μια πρόσκληση για κάθε e-mail στη λίστα. Οι χρήστες θα πρέπει να κάνουν κλικ σε ένα σύνδεσμο για να επιβεβαιώσουν την εγγραφή τους.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>ΣΗΜΑΝΤΙΚΟ:</b> Η συνδρομή αυτή είναι ανεξάρτητη από την πραγματική συνδρομή στη λίστα. Βασικά, θα γίνει εγγραφή στο αρχείο του φόρουμ, και όχι στη λίστα αλληλογραφίας την ίδια. Η συνδρομή δεν θα εγγυάται ότι τα μηνύματά σας θα γίνονται αποδεκτά από τη λίστα αλληλογραφίας.</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>ΣΗΜΑΝΤΙΚΟ</b>: Πρέπει να εγγράψετε το αρχείο αυτό στη λίστα αλληλογραφίας για να το κάνετε να λειτουργήσει.</to></translation>
+<translation><from>blog</from><to>ιστολόγιο</to></translation>
+<translation><from>Blog</from><to>Ιστολόγιο</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>Σημείωση</b>: Από τη στιγμή που είστε διαχειριστής μπορείτε <n.page_node.change_permissions_link.>να αλλάξετε τα δικαιώματα <n.location/></n.page_node.change_permissions_link.> και να βεβαιωθείτε ότι έχετε δημιουργήσει νέα θέματα εδώ.</to></translation>
+<translation><from>board</from><to>πίνακας</to></translation>
+<translation><from>Board</from><to>Πίνακας</to></translation>
+<translation><from>Bold</from><to>Έντονα</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>Σημείωση:</b> Το ευρετήριο αναζήτησης είναι υπό ανακατασκευή. Τα αποτελέσματα αναζήτησης ενδέχεται να είναι ελλιπή.</to></translation>
+<translation><from>by <t.author/></from><to>από <n.author/></to></translation>
+<translation><from>Cancel</from><to>Ακύρωση</to></translation>
+<translation><from>category</from><to>κατηγορία</to></translation>
+<translation><from>Category</from><to>Κατηγορία</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>ΠΡΟΕΙΔΟΠΟΙΗΣΗ: αυτή η ενέργεια δεν μπορεί να αναιρεθεί.</to></translation>
+<translation><from>Change appearance</from><to>Αλλαγή εμφάνισης</to></translation>
+<translation><from>Change application type</from><to>Αλλαγή τύπου εφαρμογής</to></translation>
+<translation><from>Change Application Type</from><to>Αλλαγή Τύπου Εφαρμογής</to></translation>
+<translation><from>Change code image</from><to>Αλλαγή κωδικού εικόνας</to></translation>
+<translation><from>Change domain name</from><to>Αλλαγή ονόματος τομέα</to></translation>
+<translation><from>Change language</from><to>Αλλαγή γλώσσας</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>Αλλάξτε ή καταργήστε την εικόνα του προφίλ σας.</to></translation>
+<translation><from>Change parent</from><to>Αλλαγή γονικού τομέα</to></translation>
+<translation><from>Change permissions</from><to>Αλλαγή δικαιωμάτων</to></translation>
+<translation><from>Change Permissions</from><to>Αλλαγή Δικαιωμάτων</to></translation>
+<translation><from>Change post date</from><to>Αλλαγή ημερομηνίας μηνύματος</to></translation>
+<translation><from>Change Post Date</from><to>Αλλαγή Ημερομηνίας Μηνύματος</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>Αλλάξτε το κείμενο της υπογραφής που εμφανίζεται στο κάτω μέρος των μηνυμάτων σας.</to></translation>
+<translation><from>Change User Groups</from><to>Αλλαγή Ομάδων Χρηστών</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>Αλλάξτε τις προτιμήσεις προβολής και άλλες ρυθμίσεις.</to></translation>
+<translation><from>Change Your Picture</from><to>Αλλάξτε την εικόνα σας</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>Αλλάξτε την καταχωρημένη διεύθυνση του ηλεκτρονικού ταχυδρομείου σας, τον κωδικό πρόσβασης και το όνομα χρήστη.</to></translation>
+<translation><from>Child abuse</from><to>Κακοποίηση παιδιών:</to></translation>
+<translation><from>Choose a subcategory</from><to>Επιλέξτε υποκατηγορία</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>Επιλέξτε υποκατηγορία για να δημοσιεύσετε το μήνυμα σας</to></translation>
+<translation><from>Choose the best offer for you</from><to>Επιλέξτε την καλύτερη προσφορά για εσάς</to></translation>
+<translation><from>Classic</from><to>Κλασσικό</to></translation>
+<translation><from>Clear Log</from><to>Καθαρισμός Στοιχείων</to></translation>
+<translation><from>Click for more options</from><to>Κάντε κλικ για περισσότερες επιλογές</to></translation>
+<translation><from>click here</from><to>κάντε κλικ εδώ </to></translation>
+<translation><from>Click here to make your first post</from><to>Κάντε κλικ εδώ για να ξεκινήσετε το πρώτο σας θέμα </to></translation>
+<translation><from>Click to filter</from><to>Κάντε κλικ για να φιλτράρετε</to></translation>
+<translation><from>Close</from><to>Κλείσιμο</to></translation>
+<translation><from>Close this message</from><to>Κλείστε το μήνυμα</to></translation>
+<translation><from>comment</from><to>σχόλιο</to></translation>
+<translation><from>Comment</from><to>Σχόλιο</to></translation>
+<translation><from>comments</from><to>σχόλια</to></translation>
+<translation><from>Comments</from><to>Σχόλια</to></translation>
+<translation><from>Confirm Password</from><to>Επιβεβαίωση Κωδικού πρόσβασης</to></translation>
+<translation><from>Confirm Subscription</from><to>Επιβεβαιώσετε την Εγγραφή</to></translation>
+<translation><from>Congratulations!</from><to>Συγχαρητήρια!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>Συγχαρητήρια για τη νέα σας <n.app/>!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>Περιεχόμενο που προωθεί το μίσος ή τη βία, κατάχρηση σε ευάλωτα άτομα , παρενόχληση, φυλετικές μισαλλοδοξίες ή προπαγάνδα ενάντια σε άτομα, ομάδες ή οργανώσεις ή υπερβολική αισχρολογία. </to></translation>
+<translation><from>CONTENTS DELETED</from><to>ΤΑ ΠΕΡΙΕΧΟΜΕΝΑ ΔΙΑΓΡΑΦΗΚΑΝ</to></translation>
+<translation><from>Continue</from><to>Συνεχίστε</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>Προστασία προσωπικών δεδομένων ή πνευματικών δικαιωμάτων προκαλούν παράβαση ή άλλες νομικές απαιτήσεις.</to></translation>
+<translation><from>Count</from><to>αρίθμηση</to></translation>
+<translation><from>Created by <t.author/></from><to>Δημιουργήθηκε από <n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>Δημιουργήστε νέα <n.element/></to></translation>
+<translation><from>Create <t.element/></from><to>Δημιουργία <n.element/></to></translation>
+<translation><from>Current Credits</from><to>Τρέχουσες Μονάδες</to></translation>
+<translation><from>Currently Nabble supports</from><to>Αυτή την περίοδο το Nabble υποστηρίζει</to></translation>
+<translation><from>Current Subscribers</from><to>Τρέχοντες συνδρομητές</to></translation>
+<translation><from>Daily digest</from><to>Καθημερινή ανασκόπηση</to></translation>
+<translation><from>Data successfully saved</from><to>Οι πληροφορίες αποθηκεύτηκαν επιτυχώς</to></translation>
+<translation><from>Date</from><to>Ημερομηνία</to></translation>
+<translation><from>days</from><to>ημέρες</to></translation>
+<translation><from>Dear <t.name/>,</from><to>Αγαπητέ/ή <n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>Αγαπητέ χρήστη,</to></translation>
+<translation><from>Default</from><to>Προεπιλογή</to></translation>
+<translation><from>Delete all posts from this user</from><to>Διαγραφή όλων των μηνυμάτων από αυτόν τον χρήστη</to></translation>
+<translation><from>Delete Application</from><to>Διαγραφή Εφαρμογής</to></translation>
+<translation><from>Deleted posts</from><to>Διαγραμμένα μηνύματα</to></translation>
+<translation><from>Delete</from><to>Διαγραφή</to></translation>
+<translation><from>Delete this post and replies</from><to>Διαγραφή αυτού του μηνύματος μαζί με τις απαντήσεις</to></translation>
+<translation><from>Delete this post</from><to>Διαγραφή αυτού του μηνύματος</to></translation>
+<translation><from>Delete this topic</from><to>Διαγράψτε αυτό το θέμα</to></translation>
+<translation><from>Description is in HTML Format</from><to>Η περιγραφή είναι σε HTML Format</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>Μην δημοσιεύετε επανειλημμένα. Περιμένετε λίγες μέρες. Οι άνθρωποι θα διαβάσουν τη θέση σας μέσω e-mail.</to></translation>
+<translation><from>Don't show this message again</from><to>Να μην εμφανιστεί ξανά αυτό το μήνυμα</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>Θέλετε πραγματικά να διαγράψετε αυτό το μήνυμα?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>Θέλετε πραγματικά να διαγράψετε οριστικά αυτό το μήνυμα μαζί με τις απαντήσεις;</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>Θέλετε πραγματικά οριστικά να <n.important.>διαγράψετε</n.important.> <n.location/>?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>Θέλετε πραγματικά να καταργήσετε τις ρυθμίσεις του αρχείου λίστας αλληλογραφίας?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>Θέλετε πραγματικά να εγγραφείτε <n.location/>?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>Θέλετε πραγματικά να επαναφέρετε αυτόν τον χρήστη?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>Θέλετε πραγματικά να διαγραφείτε από <n.location/>?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>Η χρήση ναρκωτικών ουσιών και φαρμάκων καθώς και σύνεργα αυτών, κατάχρηση της φωτιάς ή εκρηκτικών, η πώληση της μπύρας ή του σκληρού αλκοόλ, καπνός και συναφή προϊόντα, όπλα ή πυρομαχικά ή άλλες επικίνδυνες πράξεις.</to></translation>
+<translation><from>Edit name & description</from><to>Επεξεργασία ονόματος και περιγραφής</to></translation>
+<translation><from>Edit Name & Description</from><to>Επεξεργασία Ονοματος και Περιγραφής</to></translation>
+<translation><from>Editor</from><to>Συντάκτης</to></translation>
+<translation><from>Edit Personal Information</from><to>Επεξεργασία Προσωπικών Στοιχείων</to></translation>
+<translation><from>Edit post</from><to>Επεξεργασία μηνύματος</to></translation>
+<translation><from>Edit Subscription</from><to>Επεξεργασία Εγγραφής</to></translation>
+<translation><from>Edit Your Signature</from><to>Επεξεργασία Της Υπογραφής σας</to></translation>
+<translation><from>Email Confirmation</from><to>Επιβεβαίωση Email</to></translation>
+<translation><from>Email for <t.app/></from><to>Email για <n.app/></to></translation>
+<translation><from>Email</from><to>Email</to></translation>
+<translation><from>Email Subscription</from><to>Εγγραφή μέσω E-mail</to></translation>
+<translation><from>Email this post to...</from><to>Αποστολή αυτού του μηνύματος στον/στην...</to></translation>
+<translation><from>Embedding Contents</from><to>Ενσωμάτωση Περιεχομένων</to></translation>
+<translation><from>Embedding options</from><to>Επιλογές ενσωμάτωσης</to></translation>
+<translation><from>Embed</from><to>Ενσωμάτωση</to></translation>
+<translation><from>Embed post</from><to>Ενσωμάτωση μηνύματος</to></translation>
+<translation><from>Embed Tags</from><to>Ενσωμάτωση Ετικετών</to></translation>
+<translation><from>Embed this <t.app/></from><to>Ενσωμάτωση αυτού <n.app/></to></translation>
+<translation><from>Empty</from><to>Άδειο</to></translation>
+<translation><from>Enter a valid email address.</from><to>Εισάγετε μια έγκυρη διεύθυνση email.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>Εισάγετε παρακάτω τη διεύθυνση ηλεκτρονικού σας ταχυδρομείου και θα σας στείλουμε ένα email επιβεβαίωσης.</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>Εισάγετε μια διεύθυνση ηλεκτρονικού ταχυδρομείου ή όνομα χρήστη ανά σειρά:</to></translation>
+<translation><from>Enter one user per row</from><to>Πληκτρολογήστε έναν χρήστη ανά σειρά</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>Εισάγετε τον σύνδεσμο <b>μηνύματος</b> ή <b>forum</b> που θα είναι ο νέος τομέας, ή αφήστε το κενό για να γίνει αυτό το μήνυμα ενός ανεξάρτητου θέματος:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>Εισάγετε την αρχική σελίδα της λίστας αλληλογραφίας, όπου οι χρήστες μπορούν να βρουν περισσότερες πληροφορίες.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>Εισάγετε το πρόθεμα που αυτή η λίστα χρησιμοποιεί πριν από το θέμα. Το πρόθεμα θα αφαιρείται αυτόματα από τα εισαγόμενα μηνύματα ηλεκτρονικού ταχυδρομείου.</to></translation>
+<translation><from>Enter your email address</from><to>Εισάγετε τη διεύθυνση ηλεκτρονικού ταχυδρομείου σας</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>Παράδειγμα: johnsmith@domain.com</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>Παράδειγμα: listname@listdomain.com</to></translation>
+<translation><from>Examples:</from><to>Παραδείγματα:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>Παραδείγματα: '[List]', 'Abc:', etc.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>Εξηγήστε στο διαχειριστή(ες) τους λόγους για τους οποίους θέλετε να αποκτήσετε πρόσβαση σε αυτή την απαγορευμένη περιοχή.</to></translation>
+<translation><from>Explanation from this user:</from><to>Εξήγηση από αυτόν το χρήστη:</to></translation>
+<translation><from>Extras & add-ons</from><to>Περισσότερα & πρόσθετα</to></translation>
+<translation><from>Failed</from><to>Απέτυχε</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>Αγαπημένο (κάντε κλικ για να καταργήσετε αυτό το στοιχείο από τη λίστα των αγαπημένων σας)</to></translation>
+<translation><from>Feeds</from><to>Ροές δεδομένων</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>Το αρχείο δεν αναρτήθηκε: <n.file/> (παρακαλούμε ανεβάσετε και πάλι ή αφαιρέστε την ετικέτα)</to></translation>
+<translation><from>Filter by group</from><to>Φίλτρο ανά ομάδα</to></translation>
+<translation><from>Floating sub-forum</from><to>Κινούμενο υπο-φόρουμ</to></translation>
+<translation><from>Forgot Password?</from><to>Ξεχάσατε τον Κωδικό σας;</to></translation>
+<translation><from>Forgot your password?</from><to>Ξεχάσατε τον κωδικό σας;</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>Για περισσότερες πληροφορίες, δείτε: <n.info/></to></translation>
+<translation><from>forum</from><to>κοινότητα (forum)</to></translation>
+<translation><from>Forum</from><to>Κοινότητα (Forum)</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to><n.app/> Δωρεάν Ενσωματώσιμες εφαρμογές </to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>Από τώρα και στο εξής, θα λαμβάνετε ένα e-mail για κάθε μήνυμα που δημοσιεύτηκε υπό <n.location/>.</to></translation>
+<translation><from>gallery</from><to>φωτοθήκη (gallery)</to></translation>
+<translation><from>Gallery</from><to>Φωτοθήκη (Gallery)</to></translation>
+<translation><from>Gambling or casino-related content</from><to>Τυχερά παιχνίδια και περιεχόμενο συσχετιζόμενο με casino</to></translation>
+<translation><from>Go back</from><to>Επιστροφή</to></translation>
+<translation><from>Go to next message</from><to>Μετάβαση στο επόμενο μήνυμα</to></translation>
+<translation><from>Group Name:</from><to>Όνομα Ομάδας:</to></translation>
+<translation><from>Groups</from><to>Ομάδες</to></translation>
+<translation><from>Groups of this user</from><to>Ομάδες του χρήστη</to></translation>
+<translation><from>Hacking / cracking</from><to>Hacking / cracking:</to></translation>
+<translation><from>Harmful dangerous acts</from><to>Βλαβερές ή επικίνδυνες πράξεις:</to></translation>
+<translation><from>Hateful or abusive content</from><to>Απεχθές ή καταχρηστικό περιεχόμενο:</to></translation>
+<translation><from>Help</from><to>Βοήθεια</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>Εδώ μπορείτε να αγοράσετε μονάδες Nabble που θα κρατήσουν τις εφαρμογές σας χωρίς διαφημίσεις. Κάθε μονάδα αντιπροσωπεύει μια προβολή σελίδας, χωρίς διαφήμιση. Οι επισκέπτες θα βλέπουν τις σελίδες χωρίς διαφημίσεις, ενώ εξακολουθείτε να έχετε διαθέσιμες μονάδες. Οι διαφημίσεις Nabble θα αρχίσουν να είναι ορατές όταν έχετε μηδέν μονάδες.</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>Εδώ μπορείτε να αποκρύψετε τις πληροφορίες της τρέχουσας λίστας αλληλογραφίας από τους χρήστες. Αυτή η επιλογή μπορεί να σας βοηθήσει να αντικαταστήσετε τον server με τη λίστα αλληλογραφίας με ένα χαρακτηριστικό γνώρισμα συνδρομής ηλεκτρονικού ταχυδρομείου Nabble.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>Απόκρυψη διεύθυνσης e-mail (π.χ., user@hotmail.com σε <n.lt/>κρυφό email<n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>Απόκρυψη email</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>Απόκρυψη των πηροφοριών της λίστας ηλεκτρονικού ταχυδρομείου από τους χρήστες</to></translation>
+<translation><from>Highest</from><to>Υψηλότατο</to></translation>
+<translation><from>High</from><to>Υψηλό</to></translation>
+<translation><from>Hi <t.name/>,</from><to>Γεια <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>Εάν αυτή η διεύθυνση ηλεκτρονικού ταχυδρομείου είναι δική σας, θα πρέπει να <n.register_link.>εγγραφείτε</n.register_link.> χρησιμοποιώντας την ίδια διεύθυνση. Μετά την εγγραφή σας θα σας ανήκει αυτός ο λογαρισμός χρήστη.</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>Εάν δεν λαμβάνετε emails από Nabble, σας παρακαλούμε να ελέγξετε τον <b>spam</b> φάκελο.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>Εάν στέλνετε μια ερώτηση, παρακαλώ δοκιμάστε την αναζήτηση πρώτα. Η ερώτησή σας ενδέχεται να έχει ήδη απαντηθεί.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>Εάν δεν είστε ακόμα μέλος, μπορείτε να <n.register_link.><b>εγγραφείτε τώρα</b></n.register_link.>.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>Εάν κάνετε απαγόρευση σε αυτόν τον χρήστη, αυτός / αυτή δεν θα είναι σε θέση να κάνει τίποτα στο <n.location/>.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>Εάν δεν ζητήσατε αυτό το μήνυμα ή δεν έχετε ιδέα γιατί το λάβατε, παρακαλούμε αγνοήστε το. Ισως να ήταν ένα λάθος από κάποιον άλλο.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>Εάν δεν θέλετε να εγγραφείτε ακόμα, απλά εισάγετε τη διεύθυνση ηλεκτρονικού ταχυδρομείου από την οποία σκοπεύετε να δημοσιεύσετε, και η προσωπική σας διεύθυνση θα σας αποσταλεί με email.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>Αν έχετε ήδη αφαιρέσει τη συνδρομή σας, τότε μπορείτε να προχωρήσετε με τη διαγραφή.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>Αν γνωρίζετε, επιλέξτε την εφαρμογή του διακομιστή της λίστας ηλεκτρονικού ταχυδρομείου και την έκδοσή της.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>Εάν έχετε αποσυνδεθεί κατά λάθος, παρακαλώ <n.login_link.>συνδεθείτε ξανά</n.login_link.> εκ νέου.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>Αν απαντήσετε σε αυτό το email, το μήνυμά σας θα προστεθεί στη συζήτηση παρακάτω</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>Εάν κάνετε αναίρεση απαγόρευσης αυτού του χρήστη, αυτός/αυτή θα είναι σε θέση να δημοσιεύει ξανά μηνύματα στο <n.location/>.</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>Αν θέλετε να εγγραφείτε στην λίστα αλληλογραφίας αντί αυτού, <n.mailing_list_options_link.>επισκεφτείτε αυτή τη σελίδα</n.mailing_list_options_link.>.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>Αγνοήστε X-No-Archive επικεφαλίδα</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>Έχω διαβάσει και συμφωνώ <n.terms_link.>Με τους όρους χρήσης</n.terms_link.> του Nabble.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>Δεν έγινε μεταφόρτωση της εικόνας: <n.image/> (παρακαλούμε ανεβάστε και πάλι ή αφαιρέστε την ετικέτα)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>Είμαι συνδρομητής, επιτρέψτε μου να γράψω τώρα</to></translation>
+<translation><from>Incorrect Login!</from><to>Λανθασμένη Σύνδεση!</to></translation>
+<translation><from>Individual emails</from><to>Μεμονωμένα emails</to></translation>
+<translation><from>Inherit</from><to>Μεταβίβαση</to></translation>
+<translation><from>In Reply To</from><to>Σε Απάντηση Του</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>Σε απάντηση αυτού <n.parent_link.>του μηνύματος</n.parent_link.> από <n.author/></to></translation>
+<translation><from>Insert</from><to>Εισαγωγή</to></translation>
+<translation><from>Insert Image</from><to>Εισαγωγή Εικόνας</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>Αντί της δημοσίευσης μέσω του διαδικτυακού τόπου, μπορείτε επίσης να δημοσιεύσετε νέα θέματα με την αποστολή e-mail στην ακόλουθη διεύθυνση e-mail: </to></translation>
+<translation><from>in <t.location/></from><to>σε <n.location/></to></translation>
+<translation><from>Invalid Code</from><to>Λανθασμένος Κώδικας</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>Λανθασμένη διεύθυνση email: <n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>Άκυρος αριθμός των ημερών, πρέπει να είναι ακέραιος.</to></translation>
+<translation><from>invisible user</from><to>αόρατoς χρήστης</to></translation>
+<translation><from>invisible users</from><to>αόρατοι χρήστες</to></translation>
+<translation><from>Invite Subscribers</from><to>Προσκαλέστε Συνδρομητές</to></translation>
+<translation><from>is:</from><to>είναι:</to></translation>
+<translation><from>is not:</from><to>δεν είναι:</to></translation>
+<translation><from>is within the last:</from><to>είναι μέσα στις τελευταίες:</to></translation>
+<translation><from>Italic</from><to>Πλάγια</to></translation>
+<translation><from>item</from><to>στοιχείο</to></translation>
+<translation><from>items</from><to>στοιχεία</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>Δεν θα είναι ΔΥΝΑΤΗ η επαναφορά των διαγραμμένων στοιχείων αργότερα.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>Απλά επικολλήστε τον κώδικα (που παρέχεται από τα παραπάνω sites) σε αυτές τις ετικέτες και είστε έτοιμοι να το αποστείλετε.</to></translation>
+<translation><from>Last Post</from><to>Τελευταία Δημοσίευση</to></translation>
+<translation><from>Learn more</from><to>Μάθετε περισσότερα</to></translation>
+<translation><from>Leave a comment</from><to>Αφήστε ένα σχόλιο</to></translation>
+<translation><from>Link</from><to>Σύνδεσμος</to></translation>
+<translation><from>Link to <t.location/></from><to>Σύνδεσμος σε <n.location/></to></translation>
+<translation><from>List</from><to>Λίστα</to></translation>
+<translation><from>List of Subcategories</from><to>Λίστα υποκατηγοριών</to></translation>
+<translation><from>List Server</from><to>Λίστα Διακομιστή</to></translation>
+<translation><from>List View</from><to>Προβολή Λίστας</to></translation>
+<translation><from>Loading...</from><to>Φόρτωση...</to></translation>
+<translation><from>Location</from><to>Τοποθεσία</to></translation>
+<translation><from>Locked</from><to>Κλειδωμένο</to></translation>
+<translation><from>Lock topic</from><to>Κλείδωμα Θέματος </to></translation>
+<translation><from>Login</from><to>Σύνδεση</to></translation>
+<translation><from>Log is empty</from><to>Το αρχείο καταγραφής είναι κενό</to></translation>
+<translation><from>Log out</from><to>Αποσύνδεση</to></translation>
+<translation><from>Lowest</from><to>Χαμηλότατος</to></translation>
+<translation><from>Low</from><to>Χαμηλό</to></translation>
+<translation><from>Mailing List Address</from><to>Διεύθυνση Ταχυδρομικής Λίστας</to></translation>
+<translation><from>Mailing list archive settings</from><to>Ρυθμίσεις αρχείου ταχυδρομικής λίστας</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>Ρυθμίσεις Αρχείου Ταχυδρομικής Λίστας</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>Υπενθύμιση Εγγραφής Ταχυδρομικής Λίστας</to></translation>
+<translation><from>Mailing List Website</from><to>Ιστοσελίδα Ταχυδρομικής Λίστας</to></translation>
+<translation><from>Main Page</from><to>Αρχική Σελίδα</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>Σιγουρευτείτε ότι χρησιμοποιείτε το ίδιο πρόγραμμα περιήγησης που χρησιμοποιήσατε για να συμπληρώσετε την αίτηση εγγραφής.</to></translation>
+<translation><from>Manage banned users</from><to>Διαχείριση αποκλεισμένων χρηστών</to></translation>
+<translation><from>Manage pinned topics</from><to>Διαχείριση καρφιτσωμένων θεμάτων</to></translation>
+<translation><from>Manage subscribers</from><to>Διαχείριση συνδρομητών</to></translation>
+<translation><from>Manage Subscribers</from><to>Διαχείριση Συνδρομητών</to></translation>
+<translation><from>Manage <t.items/></from><to>Διαχειριστείτε <n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>Διαχείριση χρηστών και ομάδων</to></translation>
+<translation><from>Manage Users & Groups</from><to>Διαχείριση Χρηστών και Ομάδων</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>Μαζική διαφήμιση, παραπλανητικό κείμενο, ή απάτη.</to></translation>
+<translation><from>max. 80 characters</from><to>μέγιστο 80 χαρακτήρες</to></translation>
+<translation><from>Message date</from><to>Ημερομηνία μηνύματος</to></translation>
+<translation><from>message</from><to>μήνυμα</to></translation>
+<translation><from>Message</from><to>Μήνυμα</to></translation>
+<translation><from>Message is in HTML Format</from><to>Το μήνυμα είναι σε HTML Format</to></translation>
+<translation><from>messages</from><to>μηνύματα</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>Τα μηνύματα που στέλνετε εδώ θα σταλούν σε αυτή τη λίστα αλληλογραφίας.</to></translation>
+<translation><from>Message subject contains</from><to>Το θέμα του μηνύματος περιέχει</to></translation>
+<translation><from>Message text contains</from><to>Το κείμενο του μηνύματος περιέχει</to></translation>
+<translation><from>Mixed</from><to>Μικτό</to></translation>
+<translation><from>Modified</from><to>Τροποποιημένο</to></translation>
+<translation><from>Archives</from><to>Αρχεία</to></translation>
+<translation><from>More Categories</from><to>Περισσότερες Κατηγορίες</to></translation>
+<translation><from>More</from><to>Περισσότερα</to></translation>
+<translation><from>more help</from><to>περισσότερη βοήθεια</to></translation>
+<translation><from>more options</from><to>περισσότερες επιλογές</to></translation>
+<translation><from>Move post</from><to>Μετακίνηση μηνύματος</to></translation>
+<translation><from>Move Post</from><to>Μετακίνηση Μηνύματος</to></translation>
+<translation><from>Move topic</from><to>Μετακίνηση θέματος</to></translation>
+<translation><from>My Nabble Applications</from><to>Οι Nabble εφαρμογές μου</to></translation>
+<translation><from>My Pending Posts</from><to>Τα Εκκρεμή Μηνύματά Μου</to></translation>
+<translation><from>My posts</from><to>Οι δημοσιεύσεις μου</to></translation>
+<translation><from>Nabble Support</from><to>Υποστήριξη Nabble</to></translation>
+<translation><from>Name</from><to>Όνομα</to></translation>
+<translation><from>New Post</from><to>Νέα Δημοσίευση</to></translation>
+<translation><from>news</from><to>νέα</to></translation>
+<translation><from>News</from><to>Νέα</to></translation>
+<translation><from>New Topic</from><to>Νέο Θέμα</to></translation>
+<translation><from>New topics only</from><to>Νέα θέματα μόνο</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>ΠΡΟΣΟΧΗ</n.important.>: Όλα στο <n.location/> θα διαγραφούν για πάντα!</to></translation>
+<translation><from>No banned users.</from><to>Κανένας αποκλεισμένος χρήστης.</to></translation>
+<translation><from>No Filter</from><to>Χωρίς Φίλτρο</to></translation>
+<translation><from>none of the words:</from><to>καμία από τις λέξεις:</to></translation>
+<translation><from>No registered user found with this email.</from><to>Δε βρέθηκαν εγγεγραμμένοι χρήστες  με αυτό το email.</to></translation>
+<translation><from>No replies</from><to>Δεν υπάρχουν απαντήσεις</to></translation>
+<translation><from>Normal</from><to>Κανονικός</to></translation>
+<translation><from>No sub-forums</from><to>Χωρίς υπο-φόρουμ</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>Σημειώστε ότι αυτή η διεύθυνση είναι μοναδική για εσάς και δέχεται μόνο μηνύματα που στέλνονται από <n.address/>. Ο σκοπός αυτού του σχεδιασμού είναι να βοηθήσει στην πρόληψη ανεπιθύμητης αλληλογραφίας (spam).</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to><n.register_link.>Εγγραφείτε τώρα</n.register_link.> αν θέλετε να επεξεργαστείτε το προφίλ σας, να λαμβάνετε μηνύματα μέσω ηλεκτρονικού ταχυδρομείου, να έχετε τον έλεγχο των στοιχείων σας ή οι άλλοι να έχουν πρόσβαση στο παγκόσμιο προφίλ σας.</to></translation>
+<translation><from>one email per input box</from><to>ένα email ανά πλαίσιο εισαγωγής</to></translation>
+<translation><from>Online Users</from><to>Συνδεδεμένοι Χρήστες</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>Μόνο εξουσιοδοτημένοι χρήστες μπορούν να έχουν πρόσβαση σε αυτόν τον τομέα.</to></translation>
+<translation><from>Open this post in classic view</from><to>Ανοίξτε αυτό το μήνυμα σε κλασική προβολή</to></translation>
+<translation><from>Open this post in list view</from><to>Ανοίξτε αυτό το μήνυμα σε προβολή λίστας</to></translation>
+<translation><from>Open this post in threaded view</from><to>Ανοίξτε αυτό το μήνυμα σε προβολή νήματος</to></translation>
+<translation><from>Options</from><to>Επιλογές</to></translation>
+<translation><from>or</from><to>ή</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>Ή μπορείτε να αγνοήσετε αυτό το μήνυμα, αν είναι καλύτερα να κρατήσετε αυτόν τον χρήστη μακριά από αυτή την περιοχή.</to></translation>
+<translation><from>Other Settings</from><to>Άλλες Ρυθμίσεις</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>Σελίδα που ομαδοποιεί τις υποεφαρμογές και τις συζητήσεις.</to></translation>
+<translation><from>Page <t.number/></from><to>Σελίδα <n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>Σελίδα με μια απλή λίστα υποεφαρμογών και συζητήσεις.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>Σελίδα με πλήρη μηνύματα και τα σχετικά σχόλια.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>Σελίδα με τίτλους ειδήσεων και δημοσιεύσεις.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>Σελίδα με θέματα και συζητήσεις.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>Σελίδα με θέματα που ομαδοποιούνται ανάλογα με τις υποεφαρμογές. Εάν δεν υπάρχουν υποεφαρμογές, μια κανονική λίστα θεμάτων εμφανίζεται.</to></translation>
+<translation><from>Password</from><to>Κωδικός Πρόσβασης</to></translation>
+<translation><from>Password Sent</from><to>Κωδικός Πρόσβασης Εστάλη</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>Παιδεραστία, βία και άλλες καταπατήσεις.</to></translation>
+<translation><from>People</from><to>Άτομα</to></translation>
+<translation><from>People in <t.location/></from><to>Άτομα στο <n.location/></to></translation>
+<translation><from>Permalink</from><to>Μόνιμος σύνδεσμος</to></translation>
+<translation><from>Photo and image gallery.</from><to>Φωτοθήκη φωτογραφιών και εικόνων.</to></translation>
+<translation><from>Pinned sub-forum</from><to>Καρφιτσωμένα υπο-φόρουμ</to></translation>
+<translation><from>Pin topic</from><to>Καρφίτσωμα θέματος</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>Παρακαλώ προσθέστε τον παραπάνω σύνδεσμο στους σελιδοδείκτες ή αποθηκεύσετε αυτό το email, ώστε να μπορείτε εύκολα να τον βρείτε <n.app/> στο μέλλον.</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>Παρακαλούμε ελέγξτε τα εισερχόμενά σας τώρα και ενεργοποιήσετε το λογαριασμό σας, ώστε να έχετε πρόσβαση σε όλα τα χαρακτηριστικά.</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>Παρακαλώ κάντε κλικ στον παρακάτω σύνδεσμο για την επιβεβαίωση και την ενεργοποίηση του λογαριασμού σας:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>Παρακαλούμε επικοινωνήστε με την υποστήριξη Nabble αν χρειάζεστε βοήθεια.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>Παρακαλώ επικοινωνήστε με τους διαχειριστές αν χρειάζεστε βοήθεια.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>Παρακαλώ εισάγετε μια σωστή διεύθυνση e-mail και προσπαθήστε ξανά.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>Παρακαλούμε ακολουθήστε τις οδηγίες στο email για να ολοκληρώσετε τη διαδικασία εγγραφής.</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>Παρακαλούμε ακολουθήστε τις <n.subscribe_instructions_link.>οδηγίες εγγραφής</n.subscribe_instructions_link.> για αυτό το αρχείο.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>Παρακαλώ δώστε έναν έγκυρο μόνιμο σύνδεσμο.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>Παρακαλούμε επανεισάγετε το email/password σας και κάντε κλικ στο Σύνδεση.</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>Παρακαλώ σεβαστείτε την εθιμοτυπία της λίστας αλληλογραφίας</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>Δημοσκοπήσεις από Polldaddy.com (Δημοσκοπήσεις σε μορφή flash μόνο)</to></translation>
+<translation><from>Post by email</from><to>Δημοσίευση μέσω e-mail</to></translation>
+<translation><from>Post by Email</from><to>Δημοσίευση μέσω E-mail</to></translation>
+<translation><from>Post Count</from><to>Αριθμός Δημοσιεύσεων</to></translation>
+<translation><from>Posted by <t.author/></from><to>Δημοσιεύτηκε από <n.author/></to></translation>
+<translation><from>post</from><to>δημοσίευση</to></translation>
+<translation><from>Post Message</from><to>Δημοσίευση Μηνύματος</to></translation>
+<translation><from>Post New Message</from><to>Δημοσίευση Νέου Μηνύματος</to></translation>
+<translation><from>Post new message in <t.location/></from><to>Δημοσίευση Νέου Μηνύματος στο <n.location/></to></translation>
+<translation><from>posts</from><to>δημοσιεύσεις</to></translation>
+<translation><from>Posts</from><to>Δημοσιεύσεις</to></translation>
+<translation><from>Posts in <t.location/></from><to>Δημοσιεύσεις στο <n.location/></to></translation>
+<translation><from>Preview Message</from><to>Προεπισκόπηση Μηνύματος</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>Οι τιμές είναι σε δολάρια ΗΠΑ. Αυτή η διαδικασία πληρωμής χρησιμοποιεί <n.amazon_payments_link.>Πληρωμές μέσω Amazon</n.amazon_payments_link.>,  η οποία επιτρέπει στους πελάτες της Amazon, να χρησιμοποιούν τους τρόπους πληρωμής που αποθηκεύονται στο Amazon.com λογαριασμό τους, για να πληρώσουν για αγαθά και υπηρεσίες στις ιστοσελίδες και εφαρμογές που αποδέχονται πληρωμές μέσω Amazon.</to></translation>
+<translation><from>Print post</from><to>Εκτύπωση μηνύματος</to></translation>
+<translation><from>Priority</from><to>Προτεραιότητα</to></translation>
+<translation><from>(private)</from><to>(προσωπικό)</to></translation>
+<translation><from>Profile of <t.author/></from><to>Προφίλ του <n.author/></to></translation>
+<translation><from>Quote</from><to>Παράθεση</to></translation>
+<translation><from>Quote the original message</from><to>Παράθεση του αρχικού μηνύματος</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>Παραθέστε τι απαντήσατε και κόψτε μόνο τα σχετικά τμήματα. Αυτό παρέχει το πλαίσιο για αυτούς που θα διαβάσουν το μήνυμά σας μέσω e-mail.</to></translation>
+<translation><from>Raw mail</from><to>Ακατέργαστο ταχυδρομείο </to></translation>
+<translation><from>Raw text</from><to>Ακατέργαστο κείμενο</to></translation>
+<translation><from>read more</from><to>διαβάστε περισσότερα</to></translation>
+<translation><from>Read more</from><to>Διάβαστε Περισσότερα</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>Λίστα μόνο για ανάγνωση, με όλους τους χρήστες και το email τους στο: <n.location/>.</to></translation>
+<translation><from>Receive direct replies only.</from><to>Λάβετε άμεσες απαντήσεις μόνο.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>Λάβετε κάθε μήνυμα που δημοσιεύτηκε στο <n.location/>.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>Λάβετε κάθε απάντηση κάτω από αυτό το θέμα.</to></translation>
+<translation><from>Receive new topics only.</from><to>Λάβετε νέα θέματα μόνο.</to></translation>
+<translation><from>Refresh</from><to>Ανανέωση</to></translation>
+<translation><from>Registered</from><to>Εγγεγραμμένος</to></translation>
+<translation><from>Registered Users</from><to>Εγγεγραμμένοι Χρήστες</to></translation>
+<translation><from>Register</from><to>Εγγραφή</to></translation>
+<translation><from>Registering...</from><to>Συνέχεια Εγγραφής...</to></translation>
+<translation><from>Register Now</from><to>Εγγραφή Τώρα</to></translation>
+<translation><from>Register to <t.app/></from><to>Εγγραφή σε <n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>Εγγραφή Επιβεβαιώθηκε</to></translation>
+<translation><from>Registration Failed</from><to>Εγγραφή Απέτυχε</to></translation>
+<translation><from>Related Help Article</from><to>Σχετικό Άρθρο στη Βοήθεια</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>Θυμηθείτε ότι η απαγόρευση της δράσης δεν είναι αποτελεσματική επειδή ο χρήστης μπορεί πάντα να επιστρέψει με ένα διαφορετικό λογαριασμό.</to></translation>
+<translation><from>Remove Ads</from><to>Απομάκρυνση Διαφημίσεων</to></translation>
+<translation><from>remove</from><to>απομάκρυνση</to></translation>
+<translation><from>Remove Settings</from><to>Κατάργηση Ρυθμίσεων</to></translation>
+<translation><from>Remove Subscription</from><to>Κατάργηση Εγγραφής</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>Καταργήστε το λογαριασμό σας και όλες τις αναρτήσεις σας από <n.subject/>.</to></translation>
+<translation><from>Remove Your Account</from><to>Καταργήστε το λογαριασμό σας</to></translation>
+<translation><from>replies</from><to>απαντήσεις</to></translation><!-- noun / plural -->
+<translation><from>Replies</from><to>Απαντήσεις</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>Απάντηση</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>απάντηση</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>Απάντηση στον συντάκτη</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>Αναφορά Περιεχομένου ως Ακατάλληλου</to></translation>
+<translation><from>Report Now</from><to>Αναφορά Τώρα</to></translation>
+<translation><from>required</from><to>απαιτείται</to></translation>
+<translation><from>Return to <t.location/></from><to>Επιστροφή στο <n.location/></to></translation>
+<translation><from>Save Changes</from><to>Αποθήκευση Αλλαγών</to></translation>
+<translation><from>Save Settings</from><to>Αποθήκευση Ρυθμίσεων</to></translation>
+<translation><from>Save Subscription</from><to>Αποθήκευση Εγγραφής</to></translation>
+<translation><from>Search</from><to>Αναζήτηση</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>Επιλέξτε παρακάτω τις ενέργειες που θέλετε να κάνετε:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>Επιλέξτε την κατηγορία που περισσότερο αντικατοπτρίζει την ανησυχία σας σχετικά με το περιεχόμενο αυτής της σελίδας.</to></translation>
+<translation><from>Send email to me</from><to>Στείλτε μου email </to></translation>
+<translation><from>Send Email to <t.author/></from><to>Αποστολή Email προς <n.author/></to></translation>
+<translation><from>Send Request</from><to>Αποστολή Αίτησης </to></translation>
+<translation><from>Send To:</from><to>Αποστολή Προς:</to></translation>
+<translation><from>Sexual content</from><to>Σεξουαλικό περιεχόμενο:</to></translation>
+<translation><from>Show</from><to>Εμφάνιση</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>Εμφάνιση Nabble ειδοποίησης</to></translation>
+<translation><from>Sincerely,</from><to>Ειλικρινά,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>Δεδομένου ότι αυτή η εφαρμογή είναι ένα αρχείο λίστας ηλεκτρονικού ταχυδρομείου, παρακαλούμε καταργήστε τη διεύθυνση ηλεκτρονικού ταχυδρομείου παρακάτω πριν κάνετε κλικ στο κουμπί διαγραφής.</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>Εφόσον δεν είστε εγγεγραμμένος χρήστης, θα πρέπει να ελεγχθεί ότι είστε άνθρωπος και όχι πρόγραμμα υπολογιστή.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>Μερικές από τις δημοσιεύσεις σας έχουν διαγραφεί από <n.location/> και σας στέλνουμε αντίγραφα ώστε να έχετε την ευκαιρία να τις σώσετε.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>Λυπούμαστε, αλλά μόνο τα μέλη μπορούν να αναρτούν μηνύματα κάτω από <n.app/>.</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>Λυπούμαστε, αλλά οι διαχειριστές σας έχουν αποκλείσει.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>Συγνώμη, αλλά αυτό το email δεν είναι εξουσιοδοτημένο να δείτε τα μηνύματα κάτω από <n.location/>.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>Λυπούμαστε, αλλά δεν μπορείτε να δημιουργήσετε νέα θέματα εδώ.<br/>Σημειώστε ότι μπορείτε να εξακολουθείτε να μπορείτε να απαντήσετε σε μηνύματα.</to></translation>
+<translation><from>Sort by date</from><to>Ταξινόμηση κατά ημερομηνία</to></translation>
+<translation><from>Sort by relevance</from><to>Ταξινόμηση κατά συνάφεια</to></translation>
+<translation><from>Sorted by date</from><to>Ταξινομημένα κατά ημερομηνία</to></translation>
+<translation><from>Sorted by relevance</from><to>Ταξινομημένα κατά συνάφεια</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[Detector de Spam] Άκυρο μήνυμα με πάρα πολλές '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[Spam Detector] Το Μήνυμα δεν μπορεί να περιέχει '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[Spam Detector] Το Μήνυμα περιέχει συνηθισμένες λέξεις που συνιστούν spam.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[Spam Detector] Το Θέμα δεν μπορεί να περιέχει '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[Spam Detector] Το Θέμα περιέχει συνηθισμένες λέξεις που συνιστούν spam.</to></translation>
+<translation><from>Spam</from><to>Spam:</to></translation>
+<translation><from>Stars in <t.location/></from><to>Αστέρια στο <n.location/></to></translation>
+<translation><from>Structure</from><to>Δομή</to></translation>
+<translation><from>Subcategories</from><to>Υποκατηγορίες</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>Υποκατηγορίες υπό <n.location/></to></translation>
+<translation><from>Subcategory</from><to>Υποκατηγορία</to></translation>
+<translation><from>Sub-Forum</from><to>Υπο-Φόρουμ</to></translation>
+<translation><from>Sub-Forums</from><to>Υπο-Φόρουμς</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>Υπο-Φόρουμς και θέματα</to></translation>
+<translation><from>Subject</from><to>Θέμα</to></translation>
+<translation><from>Subscribe</from><to>Εγγραφή</to></translation>
+<translation><from>subscriber</from><to>συνδρομητής</to></translation>
+<translation><from>subscribers</from><to>συνδρομητές</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>Συνδρομή στο<n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>Συνδρομή μέσω email</to></translation>
+<translation><from>Subscription Confirmation</from><to>Επιβεβαίωση Εγγραφής</to></translation>
+<translation><from>Subscription Confirmed</from><to>Εγγραφή Επιβεβαιώθηκε</to></translation>
+<translation><from>Subscription Format</from><to>Μορφή Εγγραφής</to></translation>
+<translation><from>Subscription Removed</from><to>Εγγραφή Αφαιρέθηκε</to></translation>
+<translation><from>Subscription Results</from><to>Αποτελέσματα Εγγραφής</to></translation>
+<translation><from>Subscription Type</from><to>Τύπος Εγγραφής</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>Επιτυχία: ένα email επιβεβαίωσης σας έχει σταλεί.</to></translation>
+<translation><from>Success</from><to>Επιτυχία</to></translation>
+<translation><from>Take Action</from><to>Λάβετε Μέτρα</to></translation>
+<translation><from><t.app/> Registration</from><to> Εγγραφή <n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to><n.author/> έχει απαγορευθεί με επιτυχία.</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to><n.author/> έχει αρθεί η απαγόρευση με επιτυχία.</to></translation>
+<translation><from>Tell me more & show examples</from><to>Πες μου περισσότερα και δείξε παραδείγματα</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>Σας ευχαριστούμε για την εγγραφή σας με: <n.app/>!</to></translation>
+<translation><from>Thank You</from><to>Σας ευχαριστούμε</to></translation>
+<translation><from>The author has deleted this message.</from><to>Ο συγγραφέας έχει διαγράψει αυτό το μήνυμα.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>Ο κώδικας στη διεύθυνση URL δεν είναι έγκυρος.</to></translation>
+<translation><from>the exact phrase:</from><to>την ακριβή φράση:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>Η λίστα αλληλογραφίας μπορεί να απαιτεί αποδοχή της συνδρομής σας πριν γίνει δεκτή η δημοσίευσή σας. Παρακαλώ σημειώστε ότι αν είστε εγγεγραμμένος στο Nabble ΔΕΝ εγγράφεστε αυτόματα και σε αυτή τη λίστα. Αν δεν έχετε εγγραφεί ακόμη, παρακαλούμε κάντε το τώρα. Δεν είστε σίγουροι ή αν δεν θυμάστε, απλά εγγραφείτε και πάλι, επειδή δεν υπάρχει καμία ζημιά.</to></translation>
+<translation><from>The Nabble team</from><to>Η ομάδα Nabble</to></translation>
+<translation><from>The name of the group is not valid.</from><to>Το όνομα της ομάδας δεν είναι έγκυρο.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>Ο νέος τομέας δεν μπορεί να είναι η ίδια δημοσίευση.</to></translation>
+<translation><from>The password fields don't match.</from><to>Τα πεδία κωδικών πρόσβασης δεν ταιριάζουν.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>Υπάρχει μη εγγεγραμένος λογαριασμός χρήστη που σχετίζεται με αυτή τη διεύθυνση email <n.email/>.</to></translation>
+<translation><from>The subject is required.</from><to>Το θέμα είναι απαραίτητο.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>Ο χρήστης θα λάβει ένα αντίγραφο όλων των διαγραμμένων δημοσιεύσεων μέσω e-mail, έτσι ώστε αυτός/αυτή να έχει την ευκαιρία να μπορεί να τις σώσει.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>Ο κωδικός επικύρωσης δεν ταιριάζει με την εικόνα.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>Ο κλάδος αυτός είναι πολύ μεγάλος και έτσι μερικές θέσεις έχουν παραλειφθεί. Χρησιμοποιήστε τις άλλες προβολές για να διαβάσετε όλες τις δημοσιεύσεις.</to></translation>
+<translation><from>This email is already subscribed.</from><to>Υπάρχει ήδη συνδρομή σε αυτό το email.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>Το φόρουμ αυτό είναι ένα αρχείο για την λίστα αλληλογραφίας</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>Το φόρουμ αυτό είναι ένα αρχείο/πύλη που θα προωθήσει τα μηνύματά σας στη <b><n.mailing_list_address/></b> λίστα αλληλογραφίας.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>Αυτό περιλαμβάνει υποκατηγορίες, δημοσιεύσεις, εικόνες, αρχεία και οτιδήποτε άλλο.</to></translation>
+<translation><from>This is a mailing list archive</from><to>Αυτό είναι ένα αρχείο λίστας αλληλογραφίας</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>Αυτό είναι ένα αυτόματο email σταλμένο από το Nabble για να επιβεβαιώσετε τη δημιουργία του νέου σας <n.app/>. Αν δεν δημιουργήσατε εσείς <n.app/> που αναφέρεται παραπάνω, παρακαλούμε επικοινωνήστε μαζί μας μέσω του φόρουμ υποστήριξης του Nabble.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>Αυτή η λίστα δέχεται μόνο απλού κειμένου e-mail</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>Αυτή η λίστα εμφανίζει καταχωρημένους, μη καταχωρημένους και τους αποκλεισμένους χρήστες. Οι ανώνυμοι χρήστες δεν καταγράφονται επειδή δεν έχουν ένα e-mail και ως εκ τούτου δεν μπορεί να είναι μέρος μιας ομάδας.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>Αυτό το μήνυμα θα σταλεί από <b><n.from/></b> στη <b><n.to/></b> λίστα αλληλογραφίας.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to><b>Mensagem Pendente</b>: Το μήνυμα αυτό δεν έγινε αποδεκτό από την λίστα ακόμα.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>Αυτή η ανάρτηση ενημερώθηκε στις <n.date/>.</to></translation>
+<translation><from>This topic has been locked.</from><to>Αυτό το θέμα έχει κλειδωθεί.</to></translation>
+<translation><from>This topic has been pinned.</from><to>Αυτό το θέμα έχει καρφιτσωθεί.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>Αυτό το θέμα έχει καρφιτσωθεί στις <n.location/>.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>Αυτό το θέμα έχει ξεκλειδωθεί.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>Αυτό το θέμα έχει ξεκαρφιτσωθεί.</to></translation>
+<translation><from>This topic has unread posts</from><to>Αυτό το θέμα έχει αδιάβαστα μηνύματα</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>Αυτό το θέμα σας έχει ανατεθεί με προτεραιότητα <n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>Αυτός ο χρήστης δεν έχει δικαίωμα να δεί αυτή την εφαρμογή (Προσθέστε αυτόν/αυτήν σε μια ομάδα που το επιτρέπει αυτό και προσπαθήστε ξανά)</to></translation>
+<translation><from>This user name is already in use.</from><to>Αυτό το όνομα χρήστη είναι ήδη σε χρήση.</to></translation>
+<translation><from>Threaded</from><to>Νήμα</to></translation>
+<translation><from>Threaded View</from><to>Προβολή σε νήμα</to></translation>
+<translation><from>Time</from><to>Ώρα</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>Συμβουλή: Αν το αρχείο σας έχει εγγραφεί στην λίστα και δεν λειτουργεί ακόμη, μπορείτε να δοκιμάσετε αγνοώντας το X-No-Αρχείο Header.</to></translation>
+<translation><from>Tips</from><to>Συμβουλές</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> ζήτησε άδεια να του επιτραπεί να ενταχθεί στην <n.location/>:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> Μονάδες</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> προβολές σελίδων χωρίς διαφημίσεις.</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>Για να αποδεχτείτε αυτό το αίτημα, θα πρέπει να προσθέσετε το χρήστη σε τουλάχιστον μία ομάδα που έχει πρόσβαση σε αυτόν τον τομέα:</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>Για να προσθέσετε αυτό <n.app/> στην ιστοσελίδα σας, αντιγράψτε και επικολλήστε τον παρακάτω κώδικα στην html σελίδα:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>Για να επιβεβαιώσετε την εγγραφή σας, κάντε κλικ στον παρακάτω σύνδεσμο:</to></translation>
+<translation><from>topic</from><to>θέμα</to></translation>
+<translation><from>Topics and replies</from><to>Θέματα και απαντήσεις</to></translation>
+<translation><from>topics</from><to>θέματα</to></translation>
+<translation><from>Topics</from><to>Θέματα</to></translation>
+<translation><from>Topics only</from><to>Θέματα μόνο</to></translation>
+<translation><from>Topics View</from><to>Προβολή Θεμάτων</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>Για την αποφυγή spam, η διεύθυνση ηλεκτρονικού ταχυδρομείου που πρέπει να χρησιμοποιείται όταν δημοσιεύετε μέσω email πρέπει να είναι <b>μοναδική</b> για κάθε χρήστη.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>Για να αφαιρέσετε μια ομάδα, σβήστε το παρακάτω κείμενο και αποθηκεύσετε τις αλλαγές.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>Για να δείτε ποια διεύθυνση ηλεκτρονικού ταχυδρομείου πρέπει να χρησιμοποιήσετε για να δημοσιεύσετε, παρακαλώ <n.login_link.>συνδεθείτε</n.login_link.> ή <n.register_link.>εγγραφείτε</n.register_link.>.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>Για να ξεκινήσετε ένα νέο θέμα κάτω από <n.location/>, στείλτε email <n.p2/></to></translation>
+<translation><from>Total</from><to>Σύνολο</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>Για να διαγραφείτε από <n.location/></to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> σας προσκαλεί να εγγραφείτε <n.location/>:</to></translation>
+<translation><from>Turn off highlighting</from><to>Απενεργοποίηση επισήμανσης</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/> δημιουργήθηκε μια νέα υποκατηγορία</to></translation>
+<translation><from>Unable to Post</from><to>Δεν είναι δυνατή η Δημοσίευση</to></translation>
+<translation><from>Unassigned</from><to>Δεν αντιστοιχίστηκε</to></translation>
+<translation><from>Unauthorized</from><to>Μη εξουσιοδοτημένο</to></translation>
+<translation><from>Unban this user</from><to>Αναίρεση αποκλεισμού αυτού του χρήστη</to></translation>
+<translation><from>Unban User</from><to>Αναίρεση αποκλεισμού χρήστη</to></translation>
+<translation><from>Unknown or Other</from><to>Άγνωστο ή Άλλο</to></translation>
+<translation><from>Unlock topic</from><to>Ξεκλείδωμα θέματος</to></translation>
+<translation><from>Unpin topic</from><to>Ξεκαρφίτσωμα θέματος</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>Μη εγγεγραμμένος / Απενεργοποιημένος</to></translation>
+<translation><from>Unregistered</from><to>Μη εγγεγραμμένος</to></translation>
+<translation><from>Unregistered User</from><to>Μη εγγεγραμμένος Χρήστης</to></translation>
+<translation><from>Unsubscribe</from><to>Κατάργηση Εγγραφής</to></translation>
+<translation><from>Upload a file</from><to>Ανεβάστε ένα αρχείο</to></translation>
+<translation><from>User email:</from><to>Email χρήστη:</to></translation>
+<translation><from>user</from><to>χρήστης</to></translation>
+<translation><from>User is online</from><to>Ο χρήστης είναι συνδεδεμένος</to></translation>
+<translation><from>User Name</from><to>Όνομα Χρήστη</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>Χρήστης ζήτησε την άδεια να συμμετάσχει<n.location/></to></translation>
+<translation><from>users</from><to>χρήστες</to></translation>
+<translation><from>Users</from><to>Χρήστες</to></translation>
+<translation><from>Users & Groups</from><to>Χρήστες και Ομάδες</to></translation>
+<translation><from>Users that completed the registration process</from><to>Χρήστες που έχουν ολοκληρώσει τη διαδικασία εγγραφής</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>Χρησιμοποιήστε ετικέτες όπως <n.example1/> ή <n.example2/> για τη δημιουργία υπο-κεφαλίδων.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>Χρησιμοποιήστε τις παρακάτω επιλογές για να καθοριστούν επακριβώς τα κριτήρια της αναζήτησής σας.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>Χρησιμοποιείστε <n.tag_names/> για να ενσωματώσετε widgets από άλλες ιστοσελίδες.</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>Δείτε όλα τα μηνύματα από αυτό το υπο-φόρουμ</to></translation>
+<translation><from>view</from><to>προβολή</to></translation>
+<translation><from>View mailing list website</from><to>Προβολή ιστοσελίδας λίστας αλληλογραφίας</to></translation>
+<translation><from>View message</from><to>Προβολή μηνύματος</to></translation>
+<translation><from>View more</from><to>Δείτε περισσότερα</to></translation>
+<translation><from>views</from><to>εμφανίσεις</to></translation>
+<translation><from>Views</from><to>Εμφανίσεις</to></translation>
+<translation><from>Violent or repulsive content</from><to>Βίαιο ή αποκρουστικό περιεχόμενο:</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>Επίσκεψη <n.app/> σε:</to></translation>
+<translation><from>visit <t.url/></from><to>επίσκεψη <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>Έχουμε ετοιμάσει μια σελίδα με <n.unsubscription_instructions_link.>μερικές οδηγίες για το πώς να διαγραφεί από τη συνδρομή αυτό το αρχείο</n.unsubscription_instructions_link.>.</to></translation>
+<translation><from>We will review your report soon.</from><to>Θα εξετάσουμε την αναφορά σας σύντομα.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>Ομάδες οι οποίες επιτρέπουν στα μέλη να εμφανίζονται</to></translation>
+<translation><from>Who can ban/unban users</from><to>Ποιος μπορεί να αποκλείσει/αναιρέσει αποκλεισμό χρηστών</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>Ποιος μπορεί να αναθέσει θέματα (σε ομάδες εργασίας μόνο) </to></translation>
+<translation><from>Who can change the date and time of messages</from><to>Ποιος μπορεί να αλλάξει την ημερομηνία και την ώρα των μηνυμάτων</to></translation>
+<translation><from>Who can create new topics under this application</from><to>Ποιος μπορεί να δημιουργήσει νέα θέματα σε αυτή την εφαρμογή</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>Ποιος μπορεί να δημιουργήσει δευτερεύουσες εφαρμογές (π.χ., υπο-φόρουμ, υποκατηγορίες, κτλ.)</to></translation>
+<translation><from>Who can edit any content, both applications and posts. Note: Please only use this feature in extreme circumstances. Most users will not like having their posts edited by someone else.</from><to>Ποιος μπορεί να επεξεργαστεί οποιεσδήποτε εφαρμογές και περιεχόμενο; Σημείωση: Παρακαλούμε να χρησιμοποιήσετε αυτή τη δυνατότητα μόνο σε ακραίες περιπτώσεις. Στους περισσότερους χρήστες δεν αρέσει να επεξεργαζονται οι αναρτήσεις τους από κάποιον άλλο.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>Ποιος μπορεί να επεξεργαστεί τις εφαρμογές; (π.χ., να αλλάξει όνομα, περιγραφή, κλπ.)</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>Ποιος μπορεί να κλειδώσει/ξεκλειδώσει Θέματα σ'αυτή την εφαρμογή</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>Ποιος μπορεί να διαχειριστεί τους συνδρομητές αυτής της εφαρμογής</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>Ποιος μπορεί να μετακινήσει μηνύματα κάτω από άλλους προορισμούς (π.χ., σε άλλα θέματα ή υπο-φόρουμ)</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>Ποιος μπορεί να καρφιτσώσει/ξεκαρφιτσώσει Θέματα σε αυτή την εφαρμογή</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>Ποιος μπορεί να δημοσιεύσει οποιοδήποτε περιεχόμενο χωρίς κανένα περιορισμό; (εμπεριεχομένου κώδικα javascript, ετικετών &lt;object&gt; και &lt;style&gt;, κτλ.) <b>Προειδοποίηση ασφαλείας</b>: Επιτρέψτε αυτή την επιλογή μόνο για τους χρήστες που εμπιστεύεστε.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>Ποιος μπορεί να απαντήσει σε μηνύματα που αποστέλλονται στο πλαίσιο της παρούσας εφαρμογής</to></translation>
+<translation><from>Who can view this application and its contents</from><to>Ποιος μπορεί να δει αυτή την εφαρμογή και τα περιεχόμενά της</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>Με τη συνδρομή σας, οι ενημερώσεις θα αποστέλονται απευθείας στη διεύθυνση ηλεκτρονικού ταχυδρομείου σας και μπορείτε να απαντήσετε και να συμμετάσχετε στη συζήτηση. Η συνδρομή σας λειτουργεί το ίδιο ως λίστα αλληλογραφίας.</to></translation>
+<translation><from>Write Your First Headline</from><to>Γράψτε Τον Πρώτο Σας Τίτλο</to></translation>
+<translation><from>Write Your First Post</from><to>Γράψτε Την Πρώτη Σας Ανάρτηση</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>Ναι, διαγράψτε το <n.location/> για πάντα</to></translation>
+<translation><from>Yes, unsubscribe now</from><to>Ναι, ξεγράψου τώρα</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>Έχετε ήδη εγγραφεί σε <n.location/>.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>Δεν έχετε εγγραφεί σε <n.location/>.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>Μπορείτε επίσης να <n.manage_banned_users_link.>διαχειριστείτε τους αποκλεισμένους χρήστες</n.manage_banned_users_link.> σε <n.location/>.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>Μπορείτε επίσης να <n.root_node.change_permissions_link.>αλλάξετε τα δικαιώματα</n.root_node.change_permissions_link.> από τις παρακάτω ομάδες.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>Μπορείτε επίσης να προωθήσετε την εφαρμογή <n.app/> στέλνοντας το σύνδεσμο στους φίλους σας, ή ενσωματώνοντάς την στον ιστότοπό σας ή μιλήστε γι΄αυτό σε άλλα φόρουμς.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>Δεν μπορείτε να μετακινήσετε το μήνυμα προς τον προορισμό αυτό, διότι ο νέος τομέας δεν επιτρέπει ανώνυμους χρήστες. </to></translation>
+<translation><from>You Cannot Post Here</from><to>Δεν Μπορείτε να Δημοσιεύσετε Εδώ</to></translation>
+<translation><from>(you can reply by email)</from><to>(μπορείτε να απαντήσετε με email)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>Δεν μπορείτε να μετακινήσετε το μήνυμα οπουδήποτε.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>Δεν μπορείτε να δημοσιεύσετε ένα μήνυμα εδώ, αλλά μπορείτε να δημοσιεύσετε σε άλλες περιοχές.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>Μπορείτε να δοκιμάσετε <n.register_link.>να εγγραφείτε ξανά</n.register_link.> ή επικοινωνήστε <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>Μπορείτε να χρησιμοποιήσετε την παρακάτω φόρμα για να στείλετε ένα αίτημα στους διαχειριστές.</to></translation>
+<translation><from>You have already been registered.</from><to>Έχετε ήδη εγγραφεί.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>Έχετε προσκληθεί να εγγραφείτε στο <n.location/>, που είναι διαθέσιμο στην:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>Έχετε εγγραφεί σε <n.subject/>.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>Έχετε διαγραφεί από <n.location/></to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>Έχετε κάνει πάρα πολλές δημοσιεύσεις σε σύντομο χρονικό διάστημα. Παρακαλώ δοκιμάστε ξανά αργότερα.</to></translation>
+<translation><from>You logged out</from><to>Έχετε αποσυνδεθεί</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>Μπορεί να χρειαστεί να <n.mailing_list_options_link.>εγγραφείτε σε αυτή τη λίστα αλληλογραφίας</n.mailing_list_options_link.> για να γίνει αποδεκτό το μήνυμά σας.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>Μπορείτε <n.page_node.unauthorized_link.>να ζητήσετε άδεια για να δημοσιεύσετε</n.page_node.unauthorized_link.> εδώ ή επικοινωνήστε <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> αν έχετε απορίες.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>Πρέπει να συμφωνήσετε με τους Όρους Χρήσης.</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>Πρέπει να εισάγετε μια έγκυρη διεύθυνση ηλεκτρονικού ταχυδρομείου για αυτή τη λίστα αλληλογραφίας.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>Πρέπει να εισάγετε μια έγκυρη διεύθυνση URL ιστοσελίδας για αυτή τη λίστα αλληλογραφίας.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>Πρέπει να συμπληρώσετε όλα τα παρακάτω πεδία.</to></translation>
+<translation><from>You must login to view this page.</from><to>Πρέπει να συνδεθείτε για να δείτε αυτή τη σελίδα.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>Πρέπει να συνδεθείτε για να δείτε <n.subject/>.</to></translation>
+<translation><from>You must login to your account.</from><to>Πρέπει να συνδεθείτε στο λογαριασμό σας.</to></translation>
+<translation><from>You must provide a user name.</from><to>Πρέπει να δώσετε ένα όνομα χρήστη.</to></translation>
+<translation><from>You're not a subscriber</from><to>Δεν είστε συνδρομητής</to></translation>
+<translation><from>Your Name</from><to>Το Όνομά Σας</to></translation>
+<translation><from>Your request has been successfully sent.</from><to>Η αίτησή σας έχει αποσταλεί με επιτυχία.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>Η συνδρομή σας έχει αποθηκευτεί επιτυχώς.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>Η εγγραφή σας στο <n.location/> έχει αφαιρεθεί. Εάν αυτό ήταν ένα λάθος, μπορείτε να επανεγγραφείτε ακολουθώντας την παρακάτω σύνδεσμο:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>Η εγγραφή σας στο <n.location/> έχει αφαιρεθεί επιτυχώς.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to>Η <n.app/> έχει δημιουργηθεί με επιτυχία.</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>Θα χρειαστείτε την άδεια για να δημοσιεύσετε νέα Θέματα στο <n.location/>, έτσι εκτός από την εγγραφή σας, θα πρέπει να εγκριθεί από τους διαχειριστές.</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>Θα λάβετε ένα μήνυμα ηλεκτρονικού ταχυδρομείου για κάθε νέο μήνυμα που δημοσιεύτηκε σε αυτό το θέμα.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>Θα λάβετε ένα email με ένα σύνδεσμο για την ενεργοποίηση του λογαριασμού σας.</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>Η συνδρομή σας</to></translation>
+<translation><from>edit</from><to>επεξεργασία</to></translation>
+<translation><from>Remove ads</from><to>Απομάκρυνση διαφημίσεων</to></translation>
+<translation><from>View profile of <t.author/></from><to>Δείτε το προφίλ του <n.author/></to></translation>
+<translation><from>Description</from><to>Περιγραφή</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>Videos από το Youtube, Vimeo και LiveLeak.</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>Αποκλεισμένοι χρήστες σε <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>Υπαγόμενο Πρόθεμα</to></translation>
+
+#NEW 2
+<translation><from>Change title and meta tags</from><to>Αλλαγή τίτλου και μετα-ετικετών</to></translation>
+<translation><from>Here you can customize the title and meta tags of the <t.location/> page.</from><to>Εδώ μπορείτε να προσαρμόσετε τον τίτλο και τις μετα-ετικέτες <n.location/>.</to></translation>
+<translation><from>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</from><to>Οι μετα-πληροφορίες παρακάτω θα σας βοηθήσουν να βελτιστοποιήσετε αυτή τη σελίδα για τις μηχανές αναζήτησης (π.χ. google, yahoo!, κτλ.)</to></translation>
+<translation><from>Use custom values</from><to>Χρήση προσαρμοσμένων αξιών</to></translation>
+<translation><from>Page Title</from><to>Τίτλος Σελίδας</to></translation>
+<translation><from>Enter a context-rich title that clearly identifies this page.</from><to>Εισάγετε μια περιγραφή που θα προσδιορίζει με σαφήνεια αυτή τη σελίδα.</to></translation>
+<translation><from>The title should ideally be less than 70 characters in length.</from><to>Ο ιδανικός τίτλος θα πρέπει να είναι μικρότερος από 70 χαρακτήρες σε μήκος.</to></translation>
+<translation><from>Meta Description</from><to>Μετα Περιγραφή</to></translation>
+<translation><from>Enter a brief and concise summary of your page's content.</from><to>Πληκτρολογήστε μια σύντομη και περιεκτική περίληψη του περιεχομένου της σελίδας σας.</to></translation>
+<translation><from>Limit your description to 155 characters or 170 characters at most.</from><to>Περιορίστε την περιγραφή σας σε 155 χαρακτήρες ή 170 χαρακτήρες το πολύ.</to></translation>
+<translation><from>Mailing List Archive</from><to>Αρχείο Ηλεκτρονικής Αλληλογραφίας</to></translation>
+<translation><from>Filter: priority <t.priority/></from><to>Φίλτρο: προτεραιότητα <n.priority/></to></translation>
+<translation><from>Filter: assignee <t.author/></from><to>Φίλτρο: εντολοδόχος <n.author/></to></translation>
+<translation><from>Use Google Analytics</from><to>Χρησιμοποιήστε το Google Analytics</to></translation>
+<translation><from>Analytics Account ID:</from><to>Αναγνωριστικό Λογαριασμού Google Analytics:</to></translation>
+<translation><from>(e.g., UA-12345-0)</from><to>(παράδειγμα: UA-12345-0)</to></translation>
+<translation><from>Enter a valid analytics account ID.</from><to>Εισάγετε ένα έγκυρο αναγνωριστικό λογαριασμού Analytics.</to></translation>
+<translation><from>Here you can use Google Analytics to measure the success of your app.</from><to>Εδώ μπορείτε να χρησιμοποιήσετε το Google Analytics για τη μέτρηση της επιτυχίας της εφαρμογής σας.</to></translation>
+<translation><from>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</from><to>Εισάγετε παρακάτω το αναγνωριστικό του λογαριασμού σας στο Google Analytics και θα μπορείτε να παρακολουθείτε τις επισκέψεις, τους επισκέπτες και άλλα σημαντικά στατιστικά στοιχεία σχετικά με την κυκλοφορία του ιστού σας.</to></translation>
+<translation><from>Digest Email</from><to>Συνοπτικό Email</to></translation>
+<translation><from>on <t.date/></from><to>σε <n.date/></to></translation>
+<translation><from>DO NOT REPLY TO THIS EMAIL</from><to>ΝΑ ΜΗΝ ΔΟΘΕΊ ΑΠΆΝΤΗΣΗ ΣΕ ΑΥΤΌ ΤΟ ΜΉΝΥΜΑ</to></translation>
+<translation><from>Replies sent to this address are not read or processed.</from><to>Οι απαντήσεις που αποστέλλονται σε αυτή τη διεύθυνση δεν διαβάζονται ή υποβάλλονται σε επεξεργασία.</to></translation>
+<translation><from>If you want to respond to a post for which you received this email, please go to the website: <t.url/></from><to>Αν θέλετε να ανταποκριθείτε σε μια δημοσίευση για την οποία λάβατε αυτό το email, παρακαλούμε επισκεφθείτε το δικτυακό τόπο: <n.url/></to></translation>
+<translation><from>new post</from><to>νέο μήνυμα</to></translation><!-- usage example: "1 new post"-->
+<translation><from>new posts</from><to>νέα μηνύματα</to></translation><!-- usage example: "2 new posts"-->
+
+<translation><from>New registered user in <t.location/>!</from><to>Νέος εγγεγραμένος χρήστης σε <n.location/>!</to></translation>
+<translation><from>User profile</from><to>Προφίλ χρήστη</to></translation>
+<translation><from>New user:</from><to>Νέος χρήστης:</to></translation>
+
+<translation><from><n.author/> wrote</from><to><n.author/> έγραψε</to></translation>
+<translation><from>You still have <t.number/> day(s) left without advertising</from><to>Έχετε ακόμα <n.number/> μέρες χωρίς διαφημίσεις.</to></translation>
+<translation><from>(Only administrators can see this message)</from><to>(Μόνο οι διαχειριστές μπορούν να βλέπουν αυτό το μήνυμα)</to></translation>
+
+<translation><from>Some private items have been omitted because you don't have permission to view them.</from><to>Ορισμένα ιδιωτικά στοιχεία έχουν παραλειφθεί, διότι δεν έχετε άδεια για να τα δείτε</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Submit". We will email you a link to reset your password.</from><to>Παρακαλώ εισάγετε τη διεύθυνση ηλεκτρονικού ταχυδρομείου που χρησιμοποιήσατε για να εγγραφείτε και κάντε κλικ στο "Υποβολή". Εμείς θα σας στείλουμε ένα σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασής σας.</to></translation>
+<translation><from>Submit</from><to>Υποβολή</to></translation>
+<translation><from>Password Reset Sent</from><to>Eπαναφορά Κωδικού Πρόσβασης Εστάλη</to></translation>
+<translation><from>We have sent you a link to reset your password. Please check your email now. If you don't receive the instructions in a few minutes, check your spam folder or try to resend the request.</from><to>Έχουμε στείλει ένα σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασής σας. Παρακαλώ ελέγξτε το email σας τώρα. Εάν δεν λάβετε τις οδηγίες μέσα σε λίγα λεπτά, ελέγξτε το spam folder σας ή προσπαθήστε να ξαστείλετε το αίτημα.</to></translation>
+<translation><from>Reset your password / <t.location/></from><to>Επαναφορά του κωδικού πρόσβασης / <n.location/></to></translation>
+<translation><from>We received a request to reset your password in <t.location/>.</from><to>Έχουμε λάβει ένα αίτημα για επαναφορά του κωδικού πρόσβασής σας στο <n.location/>.</to></translation>
+<translation><from>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</from><to>Εάν θέλετε να επαναφέρετε τον κωδικό πρόσβασής σας, κάντε κλικ στον παρακάτω σύνδεσμο (ή αντιγράψτε και επικολλήστε το URL στον browser σας):</to></translation>
+<translation><from>If you don't want to reset your password, please ignore this message. Your password will not be reset.</from><to>Εάν δεν θέλετε να επαναφέρετε τον κωδικό πρόσβασής σας, παρακαλούμε αγνοήστε αυτό το μήνυμα. Δεν θα γίνει η επαναφορά του κωδικού πρόσβασης.</to></translation>
+
+<translation><from><b>IMPORTANT:</b> If you use this option, messages posted in the archive will NOT be sent to the mailing list.</from><to><b>ΣΗΜΑΝΤΙΚΟ:</b> Εάν χρησιμοποιήσετε αυτήν την επιλογή, τα μηνύματα που αποστέλλονται στο αρχείο δεν θα σταλούν στη λίστα.</to></translation>
+<translation><from>Message Preview</from><to>Προεπισκόπηση μηνύματος</to></translation>
+<translation><from>Your changes will not be sent to the mailing list.</from><to>Οι αλλαγές σας δεν θα σταλούν στη λίστα.</to></translation>
+<translation><from>If you want others in the mailing list to know of your changes, please compose a new message or reply to your original message.</from><to>Εάν θέλετε άλλα άτομα στη λίστα αλληλογραφίας να γνωρίζουν τις αλλαγές σας, παρακαλούμε συνθέσετε ένα νέο μήνυμα ή απαντήστε στο αρχικό μήνυμα. </to></translation>
+
+<translation><from>Poll</from><to>Ψηφοφορία</to></translation>
+<translation><from>Add New Poll</from><to>Προσθήκη νέας δημοσκόπησης</to></translation>
+<translation><from>Question:</from><to>Ερώτηση:</to></translation>
+<translation><from>Answers:</from><to>Απαντήσεις:</to></translation>
+<translation><from>Add new answer</from><to>Προσθήκη νέας απάντησης</to></translation>
+<translation><from>1 vote</from><to>1 ψήφος</to></translation>
+<translation><from><t.number/> votes</from><to><n.number/> ψήφοι</to></translation>
+<translation><from>Total votes:</from><to>Συνολικές ψήφοι:</to></translation>
+<translation><from>Vote</from><to>Ψηφίστε</to></translation><!-- verb -->
+<translation><from>Your vote has been submitted.</from><to>Η ψήφος σας έχει υποβληθεί.</to></translation>
+<translation><from>This poll is closed.</from><to>Αυτή η δημοσκόπηση είναι κλειστή.</to></translation>
+<translation><from>This poll ends on <t.date/>.</from><to>Αυτή η δημοσκόπηση λήγει στις <n.date/></to></translation>
+<translation><from>This poll ended on <t.date/>.</from><to>Η δημοσκόπηση έληξε στις <n.date/></to></translation>
+<translation><from>Results will be shown only after poll has ended.</from><to>Τα αποτελέσματα θα είναι ορατά μόνο μετά την λήξη της ψηφοφορίας.</to></translation>
+<translation><from>You have to vote before you can see the results.</from><to>Θα πρέπει να ψηφίσετε για να μπορέσετε να δείτε τα αποτελέσματα.</to></translation>
+<translation><from>You cannot change your vote after voting.</from><to>Δεν μπορείτε να αλλάξετε την ψήφο σας μετά την ψηφοφορία.</to></translation>
+<translation><from>You can select up to <t.number/> options.</from><to>Μπορείτε να επιλέξετε μέχρι <n.number/> επιλογές.</to></translation>
+<translation><from>Please select no more than <t.number/> options.</from><to>Παρακαλούμε επιλέξτε όχι περισσότερες από <n.number/> επιλογές.</to></translation>
+<translation><from>Please select at least one option.</from><to>Παρακαλούμε επιλέξτε τουλάχιστον μία επιλογή.</to></translation>
+<translation><from>Remove Poll</from><to>Κατάργηση δημοσκόπησης</to></translation>
+<translation><from>Invalid poll parameters.</from><to>Μη έγκυροι παραμέτροι δημοσκόπησης.</to></translation>
+<translation><from>Poll duration must be a non-negative integer or blank.</from><to>Η διάρκεια δημοσκόπησης πρέπει να είναι μη αρνητικός ακέραιος αριθμός ή κενό.</to></translation>
+<translation><from>Poll maximum allowed choices must be a non-negative integer or blank.</from><to>Οι μέγιστες επιτρεπόμενες επιλογές δημοσκόπησης πρέπει να είναι μη αρνητικός ακέραιος αριθμός ή κενό.</to></translation>
+<translation><from>Delete this poll, including all votes?</from><to>Διαγραφή αυτής της δημοσκόπησης, συμπεριλαμβανομένων όλων των ψήφων;</to></translation>
+<translation><from>Poll has been deleted.</from><to>Η δημοσκόπηση έχει διαγραφεί.</to></translation>
+<translation><from>Who can create polls.</from><to>Ποιος μπορεί να δημιουργήσει δημοσκοπήσεις.</to></translation>
+<translation><from>Allow vote changes</from><to>Επιτρέπoνται αλλαγές ψηφοφορίας</to></translation>
+<translation><from>Allow viewing results before end date (poll creators can always view the results)</from><to>Επιτρέπεται η προβολή των αποτελεσμάτων πριν από την ημερομηνία λήξης (Οι δημιουργοί της δημοσκόπησης μπορούν πάντα να προβάλλουν τα αποτελέσματα)</to></translation>
+<translation><from>Allow viewing results before vote</from><to>Επιτρέπεται η προβολή των αποτελεσμάτων πριν από την ψηφοφορία</to></translation>
+<translation><from>Multiple selections allowed:</from><to>Πολλαπλές επιλογές επιτρέπονται:</to></translation>
+<translation><from>Poll ends after <t.number/> days (leave blank for unlimited).</from><to>Η δημοσκόπηση τελειώνει μετά <n.number/> ημέρες (αφήστε κενό για απεριόριστο).</to></translation>
+<translation><from>Login to vote</from><to>Συνδεθείτε για να ψηφίσετε</to></translation>
+<translation><from>This message has a poll</from><to>Αυτό το μήνυμα έχει μια δημοσκόπηση</to></translation>
+<translation><from>Visit the link below if you want to participate:</from><to>Επισκεφθείτε τον παρακάτω σύνδεσμο αν θέλετε να συμμετάσχετε:</to></translation>
+
+<translation><from>Current length: <t.number/>characters</from><to>Τρέχον μήκος: <n.number/>χαρακτήρες</to></translation>
+<translation><from>Edit Post</from><to>Επεξεργασία μηνύματος</to></translation>
+<translation><from><t.location/> - Your credits are running out</from><to><n.location/> - Οι μονάδες σας τελειώνουν</to></translation>
+<translation><from><t.location/> is running out of ad-free credits.</from><to><n.location/> τελειώνουν οι χωρίς διαφημίσεις μονάδες σας.</to></translation>
+<translation><from>If you want to buy more credits,visit:</from><to>Αν θέλετε να αγοράσετε περισσότερες μονάδες, επισκεφτείτε:</to></translation>
+<translation><from>only in this topic</from><to>μόνο σε αυτό το θέμα</to></translation>
+<translation><from>everywhere</from><to>παντού</to></translation>
+
+<translation><from>If you want to invite subscribers, please request this feature in the <n.support_link/> forum.</from><to>Αν θέλετε να προσκαλέσετε συνδρομητές, παρακαλώ ζητήστε αυτό το χαρακτηριστικό στο <n.support_link/> forum.</to></translation>
+<translation><from>We can install this module for you, but the Nabble team must approve this request to prevent abuse and spam.</from><to>Μπορούμε να εγκαταστήσουμε αυτό το χαρακτηριστικό για εσάς, αλλά η ομάδα Nabble πρέπει πρώτα να εγκρίνει την αίτηση για την αποφυγή κατάχρησης και spam.</to></translation>
+
+<translation><from>Edit Signature</from><to>Επεξεργασία υπογραφής</to></translation>
+<translation><from>Current Signature</from><to>Τρέχουσα υπογραφή</to></translation>
+<translation><from>Save Signature</from><to>Αποθήκευση υπογραφής</to></translation>
+<translation><from>Signature is in HTML format</from><to>Η υπογραφή είναι σε μορφή HTML</to></translation>
+<translation><from>Download backup</from><to>Κατέβασμα αντιγράφου ασφαλείας</to></translation>
+<translation><from>Backup</from><to>Αντίγραφο ασφαλείας</to></translation>
+<translation><from>Here you can download a backup of <t.location/>.</from><to>Εδώ μπορείτε να κατεβάσετε ένα αντίγραφο ασφαλείας του: <n.location/>.</to></translation>
+<translation><from>When you press the button below, the system will start the generation of the backup file and this may take some minutes to finish. You will receive an email with a link to download the file when it is ready.</from><to> Όταν πατήσετε το κουμπί παρακάτω, το σύστημα θα ξεκινήσει την παραγωγή του αρχείου backup και αυτό μπορεί να πάρει μερικά λεπτά για να ολοκληρωθεί. Θα λάβετε ένα email με ένα σύνδεσμο για να κατεβάσετε το αρχείο όταν θα είναι έτοιμο.</to></translation>
+<translation><from>Generate backup file</from><to>Δημιουργία αντιγράφου</to></translation>
+<translation><from>The system is generating the backup now. When it is finished, a link to the backup file will be emailed to you.</from><to>Το σύστημα δημιουργεί το αντίγραφο ασφαλείας αυτή την στιγμή. Όταν τελειώσει, ένας σύνδεσμος προς το αντίγραφο ασφαλείας θα σας σταλεί μέσω email για να το κατεβάσετε. </to></translation>
+<translation><from>The backup file is generated with the <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a> tool, which is an open source project in Java. You should visit the project website if you want to restore your backup into a Postgresql database.</from><to>Το αντίγραφο ασφαλείας δημιουργείται με το εργαλείο <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a>, που είναι ένα πρόγραμμα ανοιχτού κώδικα σε Java. Θα πρέπει να επισκεφτείτε την ιστοσελίδα του προγράμματος αν θέλετε να επαναφέρετε το αντίγραφο ασφαλείας στην Postgresql βάση δεδομένων.</to></translation>
+<translation><from>Backup of <t.location/></from><to>Αντίγραφο ασφαλείας του: <n.location/></to></translation>
+<translation><from>Here is your backup file:</from><to>Εδώ είναι το αντίγραφο ασφαλείας σας:</to></translation>
+<translation><from><t.location/> has been deleted</from><to><n.location/> έχει διαγραφεί</to></translation>
+<translation><from>Your Nabble site "<t.location/>" has been deleted.</from><to>Η ιστοσελίδα σας στο Nabble: "<n.location/>" έχει διαγραφεί.</to></translation>
+<translation><from>You can download a backup of this site from the link below. Nabble will try to keep this backup available for a few months, but this is not guaranteed. If this content is important to you, save this copy as soon as possible.</from><to> Μπορείτε να κατεβάσετε ένα αντίγραφο ασφαλείας αυτού του site από τον παρακάτω σύνδεσμο. Η Nabble θα προσπαθήσει να κρατήσει αυτό το αντίγραφο ασφαλείας διαθέσιμο για μερικούς μήνες, αλλά αυτό δεν είναι εγγυημένο. Αν το περιεχόμενο είναι σημαντικό για εσάς, σώστε αυτό το αντίγραφο το συντομότερο δυνατό.</to></translation>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_es.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,768 @@
+## Spanish (Neuter)
+
+<macro name="is_masculine" dot_parameter="noun">
+	<n.not.regex_matches text="[n.to_lower_case.noun/]" pattern="galería|subcategoría|categoría"/>
+</macro>
+
+<macro name="el" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>el</then>
+		<else>la</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="nuevo" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>nuevo</then>
+		<else>nueva</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="este" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>este</then>
+		<else>esta</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="tu_ha_sido_creado" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>Tu <n.what/> ha sido creado</then>
+		<else>Tu <n.what/> ha sido creada</else>
+	</n.if.is_masculine.what>
+</macro>
+
+<macro name="de_tu_nuevo" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>de tu nuevo</then>
+		<else>de tu nueva</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="por_tu_nuevo" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>por tu nuevo</then>
+		<else>por tu nueva</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+## MONTHS ##
+
+<translation><from>January</from><to>Enero</to></translation>
+<translation><from>February</from><to>Febrero</to></translation>
+<translation><from>March</from><to>Marzo</to></translation>
+<translation><from>April</from><to>Abril</to></translation>
+<translation><from>May</from><to>Mayo</to></translation>
+<translation><from>June</from><to>Junio</to></translation>
+<translation><from>July</from><to>Julio</to></translation>
+<translation><from>August</from><to>Agosto</to></translation>
+<translation><from>September</from><to>Septiembre</to></translation>
+<translation><from>October</from><to>Octubre</to></translation>
+<translation><from>November</from><to>Noviembre</to></translation>
+<translation><from>December</from><to>Diciembre</to></translation>
+
+<translation><from>Jan</from><to>Ene</to></translation>
+<translation><from>Feb</from><to>Feb</to></translation>
+<translation><from>Mar</from><to>Mar</to></translation>
+<translation><from>Apr</from><to>Abr</to></translation>
+<!--translation><from>May</from><to>May</to></translation-->
+<translation><from>Jun</from><to>Jun</to></translation>
+<translation><from>Jul</from><to>Jul</to></translation>
+<translation><from>Aug</from><to>Ago</to></translation>
+<translation><from>Sep</from><to>Sep</to></translation>
+<translation><from>Oct</from><to>Oct</to></translation>
+<translation><from>Nov</from><to>Nov</to></translation>
+<translation><from>Dec</from><to>Dic</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>El uso abusivo de este recurso es también una violación de las Condiciones de Uso.</to></translation>
+<translation><from>Access Request</from><to>Solicitud de acceso</to></translation>
+<translation><from>Account settings</from><to>Configuración de la cuenta</to></translation>
+<translation><from>Account Settings</from><to>Configuración de la Cuenta</to></translation>
+<translation><from>Action</from><to>Acción</to></translation>
+<translation><from>Add a link to another page</from><to>Añadir un link a otra página</to></translation>
+<translation><from>Add a new comment</from><to>Añadir un comentario nuevo</to></translation>
+<translation><from>Add a new group</from><to>Añadir un grupo nuevo</to></translation>
+<translation><from>Add an image to your post</from><to>Añadir una imagen a tu mensaje</to></translation>
+<translation><from>Add another address</from><to>Añadir otra dirección</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>Añadir contenido que no debe ser codificado (p.ej., código fuente)</to></translation>
+<translation><from>Adding Sub-Headers</from><to>Añadiendo Subtítulos</to></translation>
+<translation><from>Add New Group</from><to>Añadir un Grupo Nuevo</to></translation>
+<translation><from>Add / Remove Groups</from><to>Añadir / Eliminar Grupos</to></translation>
+<translation><from>Add smileys and funny animations</from><to>Añadir emoticones y animaciones divertidas</to></translation>
+<translation><from>Add Subscribers</from><to>Añadir Suscriptores</to></translation>
+<translation><from>Add this item to your favorites list</from><to>Añadir este elemento a tu lista de favoritos</to></translation>
+<translation><from>Administrator</from><to>Administrador</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity, other sexual content.</from><to>Contenido para adultos, actividad sexual explícita, desnudos, otro contenido sexual.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>Adultos peleando, ataque físico, violencia juvenil, abuso de los animales o apoyo al terrorismo.</to></translation>
+<translation><from>Advanced Search</from><to>Búsqueda Avanzada</to></translation>
+<translation><from>Advanced Settings</from><to>Configuración Avanzada</to></translation>
+<translation><from>Advertisement</from><to>Publicidad</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>Avísame por email cuando alguien publique un mensaje en este tema</to></translation>
+<translation><from>All</from><to>Todos</to></translation>
+<translation><from>all of the words:</from><to>todas las palabras:</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>Todos los mensajes de <n.author/> han sido eliminados correctamente.</to></translation>
+<translation><from>All posts</from><to>Todos los mensajes</to></translation>
+<translation><from>All users belong to this group</from><to>Todos los usuarios pertenecen a este grupo</to></translation>
+<translation><from>All Users</from><to>Todos los Usuarios</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>Todos los usuarios que se han registrado en <n.location/>. Estos usuarios han confirmado su dirección de email y pueden iniciar sesión en el sistema.</to></translation>
+<translation><from>Already Subscribed</from><to>Ya suscrito</to></translation>
+<translation><from>An email has been sent to you.</from><to>Se te ha enviado un email.</to></translation>
+<translation><from>Anonymous</from><to>Anónimo</to></translation>
+<translation><from>anonymous user</from><to>usario anónimo</to></translation>
+<translation><from>anonymous users</from><to>usuarios anónimos</to></translation>
+<translation><from>Any message part contains</from><to>Cualquier parte del mensaje contiene</to></translation>
+<translation><from>Application</from><to>Aplicación</to></translation>
+<translation><from>Apps</from><to>Aplicaciones</to></translation>
+<translation><from>Assignee</from><to>Asignado</to></translation>
+<translation><from>Assign</from><to>Asignar</to></translation>
+<translation><from>Assignment</from><to>Asignación</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>Tal y como has solicitado, la dirección de email para publicar temas nuevos en <n.app/> es:</to></translation>
+<translation><from>at least one of the words:</from><to>al menos una de las palabras:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to>Listas (Feeds) para <n.location/></to></translation>
+<translation><from>at priority</from><to>con prioridad</to></translation>
+<translation><from>Authorized Users Only</from><to>Solamente Usuarios Autorizados</to></translation>
+<translation><from>Author name</from><to>Nombre del autor</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>Evita pequeñas frases como “Gracias”, “Estupendo”… Puedes<n.page_node.reply_to_author_link.>enviar un email privado </n.page_node.reply_to_author_link.> si lo deseas.</to></translation>
+<translation><from>Banned User</from><to>Usuario Bloqueado</to></translation>
+<translation><from>Ban this user</from><to>Bloquear a este usuario</to></translation>
+<translation><from>Ban User</from><to>Bloquear Usuario</to></translation>
+<translation><from>Before deleting this archive...</from><to>Antes de eliminar este archivo...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>Abajo puedes administrar grupos y usuarios. Puedes copiar y pegar usuarios para moverlos de un grupo a otro.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>IMPORTANTE</b>: Nabble enviará una invitación a cada email de la lista. Los usuarios deberán hacer click en un link para confirmar su suscripción.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>IMPORTANTE:</b> Esta suscripción es independiente de la suscripción real a la lista de correo. Básicamnte, te suscribirás al archivo del foro, no a la lista de correo propiamente dicha. La suscripción al archivo no garantizará que tus mensajes sean aceptados por la lista de correo.</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>IMPORTANTE</b>: Debes suscribirte a este archivo de la lista de correo para hacer que funcione.</to></translation>
+<translation><from>blog</from><to>blog</to></translation>
+<translation><from>Blog</from><to>Blog</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>Nota</b>: Como eres un administrador, puedes <n.page_node.change_permissions_link.>cambiar los permisos de <n.location/></n.page_node.change_permissions_link.> y asegurarte de que puedas crear nuevos temas aquí.</to></translation>
+<translation><from>board</from><to>tablón</to></translation>
+<translation><from>Board</from><to>Tablón</to></translation>
+<translation><from>Bold</from><to>Negrita</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>Alerta:</b> El índice de búsqueda está siendo reconstruido. Los resultados de la búsqueda pueden estar incompletos.</to></translation>
+<translation><from>by <t.author/></from><to>por <n.author/></to></translation>
+<translation><from>Cancel</from><to>Cancelar</to></translation>
+<translation><from>category</from><to>categoría</to></translation>
+<translation><from>Category</from><to>Categoría</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>PRECAUCIÓN: esta acción no se puede deshacer.</to></translation>
+<translation><from>Change appearance</from><to>Cambiar apariencia</to></translation>
+<translation><from>Change application type</from><to>Cambiar el tipo de aplicación</to></translation>
+<translation><from>Change Application Type</from><to>Cambiar el Tipo de Aplicación</to></translation>
+<translation><from>Change code image</from><to>Cambiar el código de la imagen</to></translation>
+<translation><from>Change domain name</from><to>Cambiar el nombre del dominio</to></translation>
+<translation><from>Change language</from><to>Cambiar idioma</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>Cambiar o eliminar tu imagen de perfil.</to></translation>
+<translation><from>Change parent</from><to>Cambiar padre</to></translation>
+<translation><from>Change permissions</from><to>Cambiar permisos</to></translation>
+<translation><from>Change Permissions</from><to>Cambiar Permisos</to></translation>
+<translation><from>Change post date</from><to>Cambiar la fecha del mensaje</to></translation>
+<translation><from>Change Post Date</from><to>Cambiar la Fecha del Mensaje</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>Cambiar el texto de la firma que se muestra al final de tus mensajes.</to></translation>
+<translation><from>Change User Groups</from><to>Cambiar Grupos de Usuarios</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>Cambiar los ajustes de visualización y otras configuraciones.</to></translation>
+<translation><from>Change Your Picture</from><to>Cambiar tu fotografía</to></translation>
+<translation><from> Change your registered email address, password, and your user name.</from><to> Cambia tu email registrado, contraseña y nombre de usuario.</to></translation>
+<translation><from>Child abuse</from><to>Abuso de menores</to></translation>
+<translation><from>Choose a subcategory</from><to>Escoge una subcategoría</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>Escoge una subcategoría para publicar tu mensaje</to></translation>
+<translation><from>Choose the best offer for you</from><to>Escoge la mejor oferta para ti</to></translation>
+<translation><from>Classic</from><to>Clásica</to></translation>
+<translation><from>Clear Log</from><to>Limpiar el Registro (Log)</to></translation>
+<translation><from>Click for more options</from><to>Haz click para más opciones</to></translation>
+<translation><from>click here</from><to>haz click aquí</to></translation>
+<translation><from>Click here to make your first post</from><to>Haz clik aquí para publicar tu primer mensaje</to></translation>
+<translation><from>Click to filter</from><to>Haz click para filtrar</to></translation>
+<translation><from>Close</from><to>Cerrar</to></translation>
+<translation><from>Close this message</from><to>Cerrar este mensaje</to></translation>
+<translation><from>comment</from><to>comentario</to></translation>
+<translation><from>Comment</from><to>Comentario</to></translation>
+<translation><from>comments</from><to>comentarios</to></translation>
+<translation><from>Comments</from><to>Comentarios</to></translation>
+<translation><from>Confirm Password</from><to>Confirmar Contraseña</to></translation>
+<translation><from>Confirm Subscription</from><to>Confirmar Suscripción</to></translation>
+<translation><from>Congratulations!</from><to>¡Felicidades!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>Felicidades <n.por_tu_nuevo.app/>!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>Contenido promocionando el odio o la violencia, el abuso de la vulnerabilidad de los individuos, la intimidación, la intolerancia racial o proclamas contra un individuo, grupo u organización, o el insulto excesivo.</to></translation>
+<translation><from>CONTENTS DELETED</from><to>CONTENIDOS ELIMINADOS</to></translation>
+<translation><from>Continue</from><to>Continuar</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>Violación del copyright, la privacidad u otra reclamación legal.</to></translation>
+<translation><from>Count</from><to>Cantidad</to></translation>
+<translation><from>Created by <t.author/></from><to>Creado por <n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>Crear <n.nuevo.element/></to></translation>
+<translation><from>Create <t.element/></from><to>Crear <n.element/></to></translation>
+<translation><from>Current Credits</from><to>Créditos actuales</to></translation>
+<translation><from>Currently Nabble supports</from><to>Actualmente Nabble soporta</to></translation>
+<translation><from>Current Subscribers</from><to>Suscriptores Actuales</to></translation>
+<translation><from>Daily digest</from><to>Resumen diario</to></translation>
+<translation><from>Data successfully saved</from><to>Datos guardados correctamente</to></translation>
+<translation><from>Date</from><to>Fecha</to></translation>
+<translation><from>days</from><to>días</to></translation>
+<translation><from>Dear <t.name/>,</from><to>Estimado/a <n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>Estimado usuario</to></translation>
+<translation><from>Default</from><to>Por Defecto</to></translation>
+<translation><from>Delete all posts from this user</from><to>Elimnar todos los mensajes de este usuario</to></translation>
+<translation><from>Delete Application</from><to>Eliminar la Aplicación</to></translation>
+<translation><from>Deleted posts</from><to>Mensajes eliminados</to></translation>
+<translation><from>Delete</from><to>Eliminar</to></translation>
+<translation><from>Delete this post and replies</from><to>Eliminar este mensaje y sus respuestas</to></translation>
+<translation><from>Delete this post</from><to>Eliminar este mensaje</to></translation>
+<translation><from>Delete this topic</from><to>Eliminar este tema</to></translation>
+<translation><from>Description is in HTML Format</from><to>La descripción está en formato HTML</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>No publiques mensajes repetidamente. Espera unos días. La gente leerá tus mesajes por email.</to></translation>
+<translation><from>Don't show this message again</from><to>No mostrar más este mensaje</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>¿Quieres realmente eliminar este mensaje?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>¿Quieres realmente eliminar de forma permanente este mensaje y todas sus respuestas?</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>¿Deseas realmente <n.important.>borrar</n.important.> <n.location/>de forma permanente?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>¿Quieres realmente eliminar la configuración del archivo de la lista de correo?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>¿Deseas realmente suscribirte a <n.location/>?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>¿Deseas realmente desbloquear a este usuario?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>¿Deseas realmente cancelar tu suscripción de <n.location/>?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>Abuso de drogas, uso de drogas prohibidas y contenidos relacionados con la incitación al consumo y la cultura del consumo de drogas, uso inadecuado del fuego o explosivos, venta de cerveza u otras bebidas alcohólicas, el tabaco u otros productos relacionados, las armas y munición o cualquier otro acto peligroso.</to></translation>
+<translation><from>Edit name & description</from><to>Modificar nombre y descripción</to></translation>
+<translation><from>Edit Name & Description</from><to>Modificar Nombre y Descripción</to></translation>
+<translation><from>Editor</from><to>Editor</to></translation>
+<translation><from>Edit Personal Information</from><to>Modificar la Información Personal</to></translation>
+<translation><from>Edit post</from><to>Modificar mensaje</to></translation>
+<translation><from>Edit Subscription</from><to>Modificar suscripción</to></translation>
+<translation><from>Edit Your Signature</from><to>Modificar tu firma</to></translation>
+<translation><from>Email Confirmation</from><to>Confirmación del email</to></translation>
+<translation><from>Email for <t.app/></from><to>Email para <n.app/></to></translation>
+<translation><from>Email</from><to>Email</to></translation>
+<translation><from>Email Subscription</from><to>Suscripción por Email</to></translation>
+<translation><from>Email this post to...</from><to>Enviar por email este mensaje a...</to></translation>
+<translation><from>Embedding Contents</from><to>Integrando Contenidos</to></translation>
+<translation><from>Embedding options</from><to>Opciones de Integración</to></translation>
+<translation><from>Embed</from><to>Integrar</to></translation>
+<translation><from>Embed post</from><to>Insertar mensaje</to></translation>
+<translation><from>Embed Tags</from><to>Insertar etiquetas</to></translation>
+<translation><from>Embed this <t.app/></from><to>Integrar <n.este.app/></to></translation>
+<translation><from>Empty</from><to>Vacío</to></translation>
+<translation><from>Enter a valid email address.</from><to>Introduce un email válido.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>Introduce abajo tu dirección de email y te enviarmos un email de confirmación.</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>Introduce una dirección de email o un nombre de usuario por fila:</to></translation>
+<translation><from>Enter one user per row</from><to>Introduce un usuario por fila</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>Introduce el permalik del <b>foro</b> o <b>mensaje</b> que será el nuevo padre, o déjalo en blanco para hacer de este mensaje un tema independiente:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>Introduce la página de inicio de esta lista de correo, donde los usuarios pueden encontrar más información.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>Introduce, antes del asunto, el prefijo que utiliza esta lista de correo. El prefijo se eliminará automáticamente de los emails importados.</to></translation>
+<translation><from>Enter your email address</from><to>Introduce tu dirección de email</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>Ejemplo: julio_garcia@dominio.com</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>Ejemplo: nombre_de_la_lista@dominio_de_la_lista.com</to></translation>
+<translation><from>Examples:</from><to>Ejemplos:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>Ejemplo: '[la-lista]', 'Abc:', etc.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>Explica a los administradores por qué quieres tener acceso a este área restringida.</to></translation>
+<translation><from>Explanation from this user:</from><to>Explicación de este usuario:</to></translation>
+<translation><from>Extras & add-ons</from><to>Extras y complementos</to></translation>
+<translation><from>Failed</from><to>Fallo</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>Favorito (haz click para eliminar este elemento de tu lista de favoritos)</to></translation>
+<translation><from>Feeds</from><to>Feeds</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>El archivo no se cargó: <n.file/> (por favor, adjúntalo de nuevo o elimina esta etiqueta)</to></translation>
+<translation><from>Filter by group</from><to>Filtrar por grupo</to></translation>
+<translation><from>Floating sub-forum</from><to>Subforo flotante</to></translation>
+<translation><from>Forgot Password?</from><to>¿Has olvidado tu Contraseña?</to></translation>
+<translation><from>Forgot your password?</from><to>¿Has olvidado tu contraseña?</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>Para más información, mira: <n.info/></to></translation>
+<translation><from>forum</from><to>foro</to></translation>
+<translation><from>Forum</from><to>Foro</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to><n.app/> integrable gratuito</to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>A partir de ahora, recibirás un email por cada mensaje publicado en <n.location/>.</to></translation>
+<translation><from>gallery</from><to>galería</to></translation>
+<translation><from>Gallery</from><to>Galería</to></translation>
+<translation><from>Gambling or casino-related content</from><to>Contenido relacionado con los juegos de azar o casinos</to></translation>
+<translation><from>Go back</from><to>Volver</to></translation>
+<translation><from>Go to next message</from><to>Ir al siguiente mensaje</to></translation>
+<translation><from>Group Name:</from><to>Nombre del Grupo:</to></translation>
+<translation><from>Groups</from><to>Grupos</to></translation>
+<translation><from>Groups of this user</from><to>Grupos de este usuario</to></translation>
+<translation><from>Hacking / cracking</from><to>Hackers o crímenes digitales</to></translation>
+<translation><from>Harmful dangerous acts</from><to>Actos peligrosos</to></translation>
+<translation><from>Hateful or abusive content</from><to>Contenido abusivo u odioso</to></translation>
+<translation><from>Help</from><to>Ayuda</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>Aquí puedes comprar créditos para mantener tu aplicación de Nabble libre de publicidad. Cada crédito se correponde a una visualización de página sin publicidad. Los visitantes verán las páginas sin publicidad siempre que aún te queden créditos disponibles. Nabble comenzará a mostrar publicidad cuando tus créditos lleguen a cero.</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>Aquí puedes ocultar de la vista de los usuarios la información actual relativa al archivo de la lista de correo. Esta opción te puede ayudar a sustituir el servidor de tu lista de correo con la función de Nable de suscripción de emails.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to> Ocultar dirección de email (p.ej., user@host.com a <n.lt/>email oculto<n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>Ocultar email</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>Ocultar de la vista de los usuarios la información relativa al archivo de la lista de correo</to></translation>
+<translation><from>Highest</from><to>Altísima</to></translation>
+<translation><from>High</from><to>Alta</to></translation>
+<translation><from>Hi <t.name/>,</from><to>¡Hola! <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>Si esta dirección de email es tuya, debes <n.register_link.>registarte </n.register_link.> usando esa misma dirección de email. Después del registro, tendrás esta cuenta de usuario.</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>Si no estás recibiendo emails de Nabble, por favor, comprueba tu carpeta de correo no deseado o spam.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>Si vas a publicar una pregunta, por favor realiza antes una búsqueda. Puede que tu preogunta ya haya sido respondida.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>Si aún no eres un miembro, puedes <n.register_link.><b>registrarte ahora </b></n.register_link.>.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>Si bloqueas a este usuario, no podrá hacer nada en <n.location/>.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>Si no solicitaste este email o si no tienes idea de porque lo has recibido, por favor ignóralo. Puede que haya sido un error de otra persona.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>Si aún no quieres registrarte, simplemente introduce la dirección de email desde la cual deseas publicar, y se te enviará tu dirección personal por email.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>Si ya has cancelado la suscripción, podrás avanzar hacia el borrado.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>Si lo conoces, por favor selecciona el software del servidor de la lista de correo y su versión.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>Si has salido de tu cuenta de usuario por error, por favor <n.login_link.>identifícate </n.login_link.> de nuevo.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>Si respondes a este email, tu mensaje sera añadido a la conversación de abajo</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>Si desbloqueas a este usuario, podrá publicar mensajes en <n.location/> de nuevo.</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>Si en lugar de ello deseas suscribirte a la lista de correo, <n.mailing_list_options_link.>visita esta página</n.mailing_list_options_link.>.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>Ignorar el encabezado X-No-Archive</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>He leído y estoy de acuerdo con las <n.terms_link.>Condiciones de Uso </n.terms_link.> de Nabble.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>La imagen no se cargó: <n.image/> (por favor, cárgala de nuevo o elimina esta etiqueta)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>Soy un suscriptor, déjame publicar ahora</to></translation>
+<translation><from>Incorrect Login!</from><to>¡Datos incorrectos!</to></translation>
+<translation><from>Individual emails</from><to>Emails individuales</to></translation>
+<translation><from>Inherit</from><to>Heredado</to></translation>
+<translation><from>In Reply To</from><to>En respuesta a</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>En respuesta a <n.parent_link.>este mensaje</n.parent_link.> publicado por <n.author/></to></translation>
+<translation><from>Insert</from><to>Insertar</to></translation>
+<translation><from>Insert Image</from><to>Insertar Imagen</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>En lugar de publicar a través del interface web, también puedes publicar nuevos temas enviando emails a la siguiente dirección:</to></translation>
+<translation><from>in <t.location/></from><to>en <n.location/></to></translation>
+<translation><from>Invalid Code</from><to>El código no es válido</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>La dirección de email no es correcta: <n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>El número de días no es válido. Debe ser un número entero.</to></translation>
+<translation><from>invisible user</from><to>usuario invisible</to></translation>
+<translation><from>invisible users</from><to>usuarios invisibles</to></translation>
+<translation><from>Invite Subscribers</from><to>Invitar a Suscriptores</to></translation>
+<translation><from>is:</from><to>es:</to></translation>
+<translation><from>is not:</from><to>no es:</to></translation>
+<translation><from>is within the last:</from><to>está dentro de los últimos:</to></translation>
+<translation><from>Italic</from><to>Cursiva</to></translation>
+<translation><from>item</from><to>elemento</to></translation>
+<translation><from>items</from><to>elementos</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>No sera possible restaurar los elementos borrados más tarde.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>Simplemente pega el código (aportado por los sitios de arriba) entre esas etiquetas y estarás listo para publicarlo.</to></translation>
+<translation><from>Last Post</from><to>Último mensaje</to></translation>
+<translation><from>Learn more</from><to>Saber más</to></translation>
+<translation><from>Leave a comment</from><to>Déjanos un comentario</to></translation>
+<translation><from>Link</from><to>Link</to></translation>
+<translation><from>Link to <t.location/></from><to>Link a <n.location/></to></translation>
+<translation><from>List</from><to>Lista</to></translation>
+<translation><from>List of Subcategories</from><to>Lista de Subcategorias</to></translation>
+<translation><from>List Server</from><to>Servidor de la Lista</to></translation>
+<translation><from>List View</from><to>Visión en Lista</to></translation>
+<translation><from>Loading...</from><to>Cargando...</to></translation>
+<translation><from>Location</from><to>Localización</to></translation>
+<translation><from>Locked</from><to>Cerrado</to></translation>
+<translation><from>Lock topic</from><to>Cerrar tema</to></translation>
+<translation><from>Login</from><to>Entrar</to></translation>
+<translation><from>Log is empty</from><to>El Registro (Log) está vacío</to></translation>
+<translation><from>Log out</from><to>Salir</to></translation>
+<translation><from>Lowest</from><to>Bajísima</to></translation>
+<translation><from>Low</from><to>Baja</to></translation>
+<translation><from>Mailing List Address</from><to>Dirección de la Lista de Correo</to></translation>
+<translation><from>Mailing list archive settings</from><to>Configuración del archivo de la lista de correo</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>Configuración del Archivo de Lista de Correo</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>Recordatorio de la Suscripción a la Lista de Correo</to></translation>
+<translation><from>Mailing List Website</from><to>Sitio web de la Lista de Correo</to></translation>
+<translation><from>Main Page</from><to>Página Principal</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>Asegúrate de que estás usando el mismo navegador que usaste al rellenar los datos de registro.</to></translation>
+<translation><from>Manage banned users</from><to>Administrar usuarios bloqueados</to></translation>
+<translation><from>Manage pinned topics</from><to>Administrar temas fijados</to></translation>
+<translation><from>Manage subscribers</from><to>Administrar suscriptores</to></translation>
+<translation><from>Manage Subscribers</from><to>Administrar suscriptores</to></translation>
+<translation><from>Manage <t.items/></from><to>Administar <n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>Administrar usuarios y grupos</to></translation>
+<translation><from>Manage Users & Groups</from><to>Administrar Usuarios y Grupos</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>Publicidad en masa, textos engañosos, timos o fraudes.</to></translation>
+<translation><from>max. 80 characters</from><to>máx. 80 carácteres</to></translation>
+<translation><from>Message date</from><to>Fecha del mensaje</to></translation>
+<translation><from>message</from><to>mensaje</to></translation>
+<translation><from>Message</from><to>Mensaje</to></translation>
+<translation><from>Message is in HTML Format</from><to>El mensaje está en formato HTML</to></translation>
+<translation><from>messages</from><to>mensajes</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>Los mensajes publicados aquí serán enviados a esta lista de correo.</to></translation>
+<translation><from>Message subject contains</from><to>El asunto del mensaje contiene</to></translation>
+<translation><from>Message text contains</from><to>El texto del mensaje contiene</to></translation>
+<translation><from>Mixed</from><to>Mixto</to></translation>
+<translation><from>Modified</from><to>Modificado</to></translation>
+<translation><from>Archives</from><to>Archivos</to></translation>
+<translation><from>More Categories</from><to>Más Categorías</to></translation>
+<translation><from>More</from><to>Más</to></translation>
+<translation><from>more help</from><to>más ayuda</to></translation>
+<translation><from>more options</from><to>más opciones</to></translation>
+<translation><from>Move post</from><to>Mover mensaje</to></translation>
+<translation><from>Move Post</from><to>Mover Mensaje</to></translation>
+<translation><from>Move topic</from><to>Mover tema</to></translation>
+<translation><from>My Nabble Applications</from><to>Mis aplicaciones de Nabble</to></translation>
+<translation><from>My Pending Posts</from><to>Mis Mensajes Pendientes</to></translation>
+<translation><from>My posts</from><to>Mis mensajes</to></translation>
+<translation><from>Nabble Support</from><to>Soporte de Nabble</to></translation>
+<translation><from>Name</from><to>Nombre</to></translation>
+<translation><from>New Post</from><to>Mensaje Nuevo</to></translation>
+<translation><from>news</from><to>noticias</to></translation>
+<translation><from>News</from><to>Noticias</to></translation>
+<translation><from>New Topic</from><to>Tema nuevo</to></translation>
+<translation><from>New topics only</from><to>Solamente temas nuevos</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>CUIDADO</n.important.>: ¡Todo lo que haya en <n.location/> será borrado para siempre!</to></translation>
+<translation><from>No banned users.</from><to>Ningún usuario bloquedo.</to></translation>
+<translation><from>No Filter</from><to>Ningún filtro</to></translation>
+<translation><from>none of the words:</from><to>ninguna de las palabras:</to></translation>
+<translation><from>No registered user found with this email.</from><to>Ningún usuario registrado encontrado con este email.</to></translation>
+<translation><from>No replies</from><to>Ninguna respuesta</to></translation>
+<translation><from>Normal</from><to>Normal</to></translation>
+<translation><from>No sub-forums</from><to>Ningún subforo</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>Ten en cuenta que esta dirección es única y que tú sólo aceptas emails enviados desde <n.address/>. El objetivo de este diseño es ayudar a prevenir el spam.</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to><n.register_link.>Regístrate ahora</n.register_link.> si quieres modificar tu perfil, recibir mensajes por email o tener acceso a tu perfil global.</to></translation>
+<translation><from>one email per input box</from><to>un email por cada casilla</to></translation>
+<translation><from>Online Users</from><to>Usuarios conectados</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>Solamente los usuarios autorizados pueden entrar en este área.</to></translation>
+<translation><from>Open this post in classic view</from><to>Abrir este mensaje con la vista clásica</to></translation>
+<translation><from>Open this post in list view</from><to>Abrir este mensaje con la vista de lista</to></translation>
+<translation><from>Open this post in threaded view</from><to>Abrir este mensaje con la vista en árbol</to></translation>
+<translation><from>Options</from><to>Opciones</to></translation>
+<translation><from>or</from><to>o</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>O puedes ignorar este email, si es mejor mantener a este usuario lejos de ese área.</to></translation>
+<translation><from>Other Settings</from><to>Otras configuraciones</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>Página que agrupa subaplicaciones y conversaciones.</to></translation>
+<translation><from>Page <t.number/></from><to>Página <n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>Página con una lista simple de subaplicaciones y conversaciones.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>Página con mensajes completos y comentarios relacionados.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>Página con titulares y mensajes.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>Página con temas y conversaciones.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>Página con temas agrupados en subaplicaciones. Si no existe ninguna subaplicación, se mostrará una lista normal de temas.</to></translation>
+<translation><from>Password</from><to>Contraseña</to></translation>
+<translation><from>Password Sent</from><to>Contraseña Enviada</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>Pedofilia, violencia contra menores y otros abusos.</to></translation>
+<translation><from>People</from><to>Gente</to></translation>
+<translation><from>People in <t.location/></from><to>Gente en <n.location/></to></translation>
+<translation><from>Permalink</from><to>Permalink</to></translation>
+<translation><from>Photo and image gallery.</from><to>Galería de fotografías e imágenes.</to></translation>
+<translation><from>Pinned sub-forum</from><to>Subforo fijado</to></translation>
+<translation><from>Pin topic</from><to>Fijar tema</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>Por favor, guarda el link de arriba o salva este email para poder encontrar fácilmente tu <n.app/> en el futuro.</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>Por favor, comprueba ahora tu bandeja de entrada y activa tu cuenta para poder tener acceso a todas las opciones.</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>Por favor, haz click en el link de confirmación de abajo para activar tu cuenta:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>Por favor, contacta con el Soporte de Nabble si necesitas ayuda.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>Por favor, contacta con los administradores si necesitas ayuda.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>Por favor, introduce una dirección de email válida e inténtalo de nuevo.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>Por favor, sigue las instrucciones del email para finalizar el proceso de registro.</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>Por favor, sigue las <n.subscribe_instructions_link.>instrucciones de suscripción </n.subscribe_instructions_link.> para este archivo.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>Por favor, aporta un permalink válido.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>Por favor, introduce nuevamente tu email/contraseña y haz click en Entrar.</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>Por favor, respeta las normas de las listas de correo</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>Encuestas de Polldaddy.com (solamente encuestas flash)</to></translation>
+<translation><from>Post by email</from><to>Publicar por email</to></translation>
+<translation><from>Post by Email</from><to>Publicar por Email</to></translation>
+<translation><from>Post Count</from><to>Número de mensajes</to></translation>
+<translation><from>Posted by <t.author/></from><to>Publicado por <n.author/></to></translation>
+<translation><from>post</from><to>mensaje</to></translation>
+<translation><from>Post Message</from><to>Publicar Mensaje</to></translation>
+<translation><from>Post New Message</from><to>Publicar Mensaje Nuevo</to></translation>
+<translation><from>Post new message in <t.location/></from><to>Publicar mensaje nuevo en <n.location/></to></translation>
+<translation><from>posts</from><to>mensajes</to></translation>
+<translation><from>Posts</from><to>Mensajes</to></translation>
+<translation><from>Posts in <t.location/></from><to>Mensajes en <n.location/></to></translation>
+<translation><from>Preview Message</from><to>Previsualizar mensaje</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>Los precios están en dólares americanos (USD). Este proceso de compra usa <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, que permite a los clientes de Amazon pagar por productos y servicios en páginas web y aplicaciones que acepten Amazon Payments.</to></translation>
+<translation><from>Print post</from><to>Imprimir mensaje</to></translation>
+<translation><from>Priority</from><to>Prioridad</to></translation>
+<translation><from>(private)</from><to>(privado)</to></translation>
+<translation><from>Profile of <t.author/></from><to>Perfil de <n.author/></to></translation>
+<translation><from>Quote</from><to>Citar</to></translation>
+<translation><from>Quote the original message</from><to>Citar el mensaje original</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>Cita lo que estás repondiendo, borrando las partes no relevantes. Así se aporta contexto para aquellos que lean tu mensaje por email.</to></translation>
+<translation><from>Raw mail</from><to>Email Original</to></translation>
+<translation><from>Raw text</from><to>Texto puro</to></translation>
+<translation><from>read more</from><to>leer más</to></translation>
+<translation><from>Read more</from><to>Leer más</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>Lista solamente de lectura con todos los usuarios que poseen un email en<n.location/>.</to></translation>
+<translation><from>Receive direct replies only.</from><to>Recibir solamente respuestas directas.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>Recibir cada mensaje publicado en <n.location/>.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>Recibir cada respuesta de este tema.</to></translation>
+<translation><from>Receive new topics only.</from><to>Recibir solamente temas nuevos.</to></translation>
+<translation><from>Refresh</from><to>Actualizar</to></translation>
+<translation><from>Registered</from><to>Registrado</to></translation>
+<translation><from>Registered Users</from><to>Ususarios Registrados</to></translation>
+<translation><from>Register</from><to>Registrarse</to></translation>
+<translation><from>Registering...</from><to>Registrando...</to></translation>
+<translation><from>Register Now</from><to>Regístrate Ahora</to></translation>
+<translation><from>Register to <t.app/></from><to>Registarse en <n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>Registro Confirmado</to></translation>
+<translation><from>Registration Failed</from><to>El registro ha fallado</to></translation>
+<translation><from>Related Help Article</from><to>Artículos de ayuda relacionados</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>Recuerda que la acción de bloqueo no es eficiente, porque el usuario siempre podrá regresar con una cuenta diferente.</to></translation>
+<translation><from>Remove Ads</from><to>Eliminar Publicidad</to></translation>
+<translation><from>remove</from><to>eliminar</to></translation>
+<translation><from>Remove Settings</from><to>Eliminar Configuraciones</to></translation>
+<translation><from>Remove Subscription</from><to>Cancelar suscripción</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>Borrar tu cuenta y todos tus mensajes de <n.subject/>.</to></translation>
+<translation><from>Remove Your Account</from><to>Borrar Tu Cuenta</to></translation>
+<translation><from>replies</from><to>respuestas</to></translation><!-- noun / plural -->
+<translation><from>Replies</from><to>Respuestas</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>Responder</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>responder</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>Responder al autor</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>Denunciar Contenido inapropiado</to></translation>
+<translation><from>Report Now</from><to>Denunciar Ahora</to></translation>
+<translation><from>required</from><to>obligatorio</to></translation>
+<translation><from>Return to <t.location/></from><to>Volver a <n.location/></to></translation>
+<translation><from>Save Changes</from><to>Salvar Cambios</to></translation>
+<translation><from>Save Settings</from><to>Salvar Configuraciones</to></translation>
+<translation><from>Save Subscription</from><to>Salvar Suscripción</to></translation>
+<translation><from>Search</from><to>Buscar</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>Selecciona abajo las acciones que desas tomar:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>Selecciona la categoría que mejor refleje tu preocupación sobre los contenidos de esta página.</to></translation>
+<translation><from>Send email to me</from><to>Enviarme email</to></translation>
+<translation><from>Send Email to <t.author/></from><to>Enviar email a <n.author/></to></translation>
+<translation><from>Send Request</from><to>Enviar Solicitud</to></translation>
+<translation><from>Send To:</from><to>Enviar a:</to></translation>
+<translation><from>Sexual content</from><to>Contenido sexual</to></translation>
+<translation><from>Show</from><to>Mostrar</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>Mostrar aviso de Nabble</to></translation>
+<translation><from>Sincerely,</from><to>Atentamente,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>Por ser esta aplicación un archivo de lista de email, por favor cancela la suscripción a la dirección de email de abajo antes de hacer click en botón de eliminar.</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>Como no eres un usuario registrado, debemos comprobar que eres humano.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>Algunos mensajes han sido borrado de <n.location/> y te estamos enviando copias para que tengas la oportunidad de salvarlos.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>Lo siento, pero solamente los miembros pueden publicar mensajes en <n.app/>.</to></translation>
+<translation><from> Sorry, but the administrators have banned you. </from><to> Lo siento, pero los administradores te ha bloqueado.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>Lo siento, pero este email no está autorizado para ver mensajes en <n.location/>.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>Lo siento, pero no puedes crear temas nuevos aquí.<br/>Ten en cuenta que tal vez aún puedas responder a mensajes.</to></translation>
+<translation><from>Sort by date</from><to>Ordenar por fecha</to></translation>
+<translation><from>Sort by relevance</from><to>Ordenar por relevancia</to></translation>
+<translation><from>Sorted by date</from><to>Ordenado por fecha</to></translation>
+<translation><from>Sorted by relevance</from><to>Ordenado por relevancia</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[Detector de Spam] Mensaje invalido con muchas palabras '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[Detector de Spam] El mensaje no puede contener '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[Detector de Spam] El mensaje contiene palabras habituales en los spam.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[Detector de Spam] El Asunto no puede contender '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[Detector de Spam] El Asunto contiene palabras habituales en los spam.</to></translation>
+<translation><from>Spam</from><to>Spam</to></translation>
+<translation><from>Stars in <t.location/></from><to>Favoritos en <n.location/></to></translation>
+<translation><from>Structure</from><to>Estrutura</to></translation>
+<translation><from>Subcategories</from><to>Subcategorías</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>Subcategorías en <n.location/></to></translation>
+<translation><from>Subcategory</from><to>Subcategoría</to></translation>
+<translation><from>Sub-Forum</from><to>Subforo</to></translation>
+<translation><from>Sub-Forums</from><to>Subforos</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>Subforos y Temas</to></translation>
+<translation><from>Subject</from><to>Asunto</to></translation>
+<translation><from>Subscribe</from><to>Suscripción</to></translation>
+<translation><from>subscriber</from><to>suscriptor</to></translation>
+<translation><from>subscribers</from><to>suscriptores</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>Suscripción a <n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>Suscripción por email</to></translation>
+<translation><from>Subscription Confirmation</from><to>Confirmación de la suscripción</to></translation>
+<translation><from>Subscription Confirmed</from><to>Suscripción Confirmada</to></translation>
+<translation><from>Subscription Format</from><to>Formato de la Suscripción</to></translation>
+<translation><from>Subscription Removed</from><to>Suscripción Cancelada</to></translation>
+<translation><from>Subscription Results</from><to>Resultados de la Suscripción</to></translation>
+<translation><from>Subscription Type</from><to>Tipo de Suscripción</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>Hecho: se te ha enviado un email de confirmación.</to></translation>
+<translation><from>Success</from><to>Éxito</to></translation>
+<translation><from>Take Action</from><to>Tomar Acción</to></translation>
+<translation><from><t.app/> Registration</from><to>Registro en <n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to><n.author/> ha sido bloqueado correctamente.</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to><n.author/> ha sido desbloqueado correctamente.</to></translation>
+<translation><from>Tell me more & show examples</from><to>Cuéntame más y muéstrame ejemplos</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>Gracias por registrarte en <n.app/>!</to></translation>
+<translation><from>Thank You</from><to>Gracias</to></translation>
+<translation><from>The author has deleted this message.</from><to>El autor ha borrado este mensaje.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>El código en el URL no es válido.</to></translation>
+<translation><from>the exact phrase:</from><to>la frase exacta:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>Es posible que la lista de correo requiere que te suscribas antes de aceptar tu mensaje. Por favor, ten en cuenta que estar registrado con Nabble NO te suscribe automáticamente a la lista de correo. Si aún no te has suscrito, por favor, hazlo ahora. Si no estás seguro o no lo recuerdas, simplemente vuelve a suscribirte, ya que está acción no produce ningún efecto negativo.</to></translation>
+<translation><from>The Nabble team</from><to>El equipo de Nabble</to></translation>
+<translation><from>The name of the group is not valid.</from><to>El nombre del grupo no es válido.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>El nuevo padre no puede ser el propio mensaje.</to></translation>
+<translation><from>The password fields don't match.</from><to>Los campos de la contraseña no coinciden.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>Existe una cuenta de un usuario no registrado asociada a la dirección de email <n.email/>.</to></translation>
+<translation><from>The subject is required.</from><to>El asunto es obligatorio.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>El usuario recibirá por email una copia de todos los mensajes borrados, para que tenga la oportunidad de salvarlos.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>El código de validación no coincide con la imagen.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>Esta rama es muy larga y algunos mensajes han sido omitidos. Usa otra vista para leer todos los mensajes.</to></translation>
+<translation><from>This email is already subscribed.</from><to>Este email ya está suscrito.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>Este foro es un archivo de la lista de correo</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>Este foro es un archivo/gateway que reenviará tus mensajes a la lista de correo <b><n.mailing_list_address/></b>.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>Esto incluye subcategorías, mensajes, imágenes, archivos y resto de elementos.</to></translation>
+<translation><from>This is a mailing list archive</from><to>Esto es un archivo de lista de correo</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>Esto es un email automatico enviado por Nabble para confirmar la creación <n.de_tu_nuevo.app/>. Si tú no has creado <n.el.app/> que se menciona arriba, por favor, contáctanos a través del foro de Soporte de Nabble.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>Esta lista acepta solamente texto simple</to></translation>
+<translation><from> This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>Esta lista muestra usuario registrados, no registrados y bloqueados. Los usuarios anónimos no aparecen en la lista porque no tiene un email y por ello no pueden formar parte de ningún grupo.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>Este mensaje se enviará desde <b><n.from/></b> para la lista de correo <b><n.to/></b>.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to><b>Mensaje Pendiente</b>: Este mensaje aún NO ha sido aceptado por la lista de correo.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>Este mensaje fue actualizado el <n.date/>.</to></translation>
+<translation><from>This topic has been locked.</from><to>Este tema ha sido cerrado.</to></translation>
+<translation><from>This topic has been pinned.</from><to>Este tema ha sido fijado.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>Este tema ha sido fijado en <n.location/>.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>Este tema ha sido desbloqueado.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>Este tema ya no está fijado.</to></translation>
+<translation><from>This topic has unread posts</from><to>Este tema tiene mensajes sin leer</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>Este tema se te ha asignado con prioridad <n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>Este usuario no tiene permiso para ver esta aplicación (añádelo a un grupo que lo permita e inténtalo de nuevo)</to></translation>
+<translation><from>This user name is already in use.</from><to>Este nombre de usuario ya está siendo utilizado.</to></translation>
+<translation><from>Threaded</from><to>En Árbol</to></translation>
+<translation><from>Threaded View</from><to>Vista en Árbol</to></translation>
+<translation><from>Time</from><to>Hora</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>CONSEJO: si tu archivo está suscrito a la lista de correo y aún no está funcionando, puedes tratar de ignorar el encabezado X-No-Archive.</to></translation>
+<translation><from>Tips</from><to>Consejos</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> ha solicitado autorización para entrar en <n.location/>:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> Créditos</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> visualización de páginas sin publicidad.</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>Para aceptar esta petición, debes añadir a este usuario al menos a uno de los grupos con acceso a este área:</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>Para añadir <n.este.app/> a tu sitio web, copia y pega el siguiente código a tu página en HTML:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>Para confirmar tu suscripción, haz click en el link de abajo:</to></translation>
+<translation><from>topic</from><to>tema</to></translation>
+<translation><from>Topics and replies</from><to>Temas y respuestas</to></translation>
+<translation><from>topics</from><to>temas</to></translation>
+<translation><from>Topics</from><to>Temas</to></translation>
+<translation><from>Topics only</from><to>Sólamente temas</to></translation>
+<translation><from>Topics View</from><to>Vista de Temas</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>Para prevenir el smap, la dirección de email usada al publicar por email es <b>única</b> para cada usuario.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>Para eliminar un grupo, vacía el area de texto de abajo y salva los cambios.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>Para ver qué dirección de email debes usar para publicar, por favor <n.login_link.>identifícate</n.login_link.> o <n.register_link.>regístrate</n.register_link.>.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>Para empezar un tema nuevo en <n.location/>, envía un email a <n.p2/></to></translation>
+<translation><from>Total</from><to>Total</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>Para cancelar tu suscripción a <n.location/></to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> te está invitando a suscribirte a <n.location/>:</to></translation>
+<translation><from>Turn off highlighting</from><to>Desactivar el destacar palabras</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/> creó una nueva subcategoría</to></translation>
+<translation><from>Unable to Post</from><to>Incapaz de Publicar</to></translation>
+<translation><from>Unassigned</from><to>No Asignado</to></translation>
+<translation><from>Unauthorized</from><to>No Autorizado</to></translation>
+<translation><from>Unban this user</from><to>Desbloquear a este usuario</to></translation>
+<translation><from>Unban User</from><to>Desbloquear Usuario</to></translation>
+<translation><from>Unknown or Other</from><to>Desconocido u otro</to></translation>
+<translation><from>Unlock topic</from><to>Abrir tema</to></translation>
+<translation><from>Unpin topic</from><to>Desfijar tema</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>No Registrado / Desactivado</to></translation>
+<translation><from>Unregistered</from><to>No Registrado</to></translation>
+<translation><from>Unregistered User</from><to>Usuario No Registrado</to></translation>
+<translation><from>Unsubscribe</from><to>Cancelar suscripción</to></translation>
+<translation><from>Upload a file</from><to>Adjuntar archivo</to></translation>
+<translation><from>User email:</from><to>Email del usuario:</to></translation>
+<translation><from>user</from><to>usuario</to></translation>
+<translation><from>User is online</from><to>El usuario está conectado</to></translation>
+<translation><from>User Name</from><to>Nombre de usuario</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>El usuario solicita autorización para entrar en <n.location/></to></translation>
+<translation><from>users</from><to>usuarios</to></translation>
+<translation><from>Users</from><to>Usuarios</to></translation>
+<translation><from>Users & Groups</from><to>Usuarios y Grupos</to></translation>
+<translation><from>Users that completed the registration process</from><to>Usuarios que han completado el proceso de registro</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>Usa etiquetas como <n.example1/> o <n.example2/> para crear subtítulos.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>Usa las opciones de abajo para especificar de un modo preciso tus criterios de búsqueda.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>Usa <n.tag_names/> para para insertar widgets de otros sitios web.</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>Ver todos los mensajes de este subforo</to></translation>
+<translation><from>view</from><to>vista</to></translation>
+<translation><from>View mailing list website</from><to>Ver el sitio web de la lista de correo</to></translation>
+<translation><from>View message</from><to>Ver mensaje</to></translation>
+<translation><from>View more</from><to>Ver más</to></translation>
+<translation><from>views</from><to>vistas</to></translation>
+<translation><from>Views</from><to>Vistas</to></translation>
+<translation><from>Violent or repulsive content</from><to>Contenido violento o repulsivo</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>Visita <n.app/> en:</to></translation>
+<translation><from>visit <t.url/></from><to>visita <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>Hemos creado una página con <n.unsubscription_instructions_link.>instrucciones sobre cómo cancelar la suscripción a este archivo</n.unsubscription_instructions_link.>.</to></translation>
+<translation><from>We will review your report soon.</from><to>Tu denuncia sera revisada pronto.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>Qué grupos permiten que sus miembros estén en una lista</to></translation>
+<translation><from>Who can ban/unban users</from><to>Quién puede bloquear/desbloquear usuarios</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>A quién se le pueden asignar temas (solamente en grupos de trabajo)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>Quién puede cambiar la fecha y hora de los mensajes</to></translation>
+<translation><from>Who can create new topics under this application</from><to>Quién puede crear temas nuevos en esta aplicación</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>Quién puede crear subabplicaciones (p.ej., subforos, subcategorias, etc.)</to></translation>
+<translation><from>Who can edit any content, both applications and posts. Note: Please only use this feature in extreme circumstances. Most users will not like having their posts edited by someone else.</from><to>Quién puede modificar cualquier contenido, tanto de aplicaciones como de mensajes. Nota: Por favor, usa esta acción en circunstancias extremas. A la mayoría de los usuarios no les gustará que sus mensajes sean editados por otra persona.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>Quién puede modificar las aplicaciones (p.ej., cambiar el nombre, la descripción, etc.)</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>Quién puede cerrar/abrir temas en esta aplicación</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>Quién puede administrar los suscriptores de esta aplicación</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>Quién puede mover mensajes a otros lugares (p.ej., a otros temas o subforos).</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>Quién puede fijar/desfijar temas en esta aplicación</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>Quién puede publicar cualquier contenido sin restricciones (incluyendo código javascript, etiquetas como &lt;object&gt; y &lt;style&gt;, etc.). <b>Alerta de Seguridad</b>: Habilita esta opción solamente para usuarios en los que realmente puedas confiar.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>Quién puede responder a mensajes publicados en esta aplicación</to></translation>
+<translation><from>Who can view this application and its contents</from><to>Quién puede ver esta aplicación y su contenido</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>Con tu suscripción, las actualizaciones se te enviarán directamente a tu dirección de email y podrás responderlas para participar en la conversación. Tu suscripción funciona del mismo modo que una lista de correo.</to></translation>
+<translation><from>Write Your First Headline</from><to>Escribe Tu Primer Titular</to></translation>
+<translation><from>Write Your First Post</from><to>Escribe Tu Primer Mensaje</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>Si, elimina <n.location/> para siempre</to></translation>
+<translation><from>Yes, unsubscribe now</from><to>Si, cancela la suscripción ahora</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>Ya estás suscrito a <n.location/>.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>No estás suscrito a <n.location/>.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>También puedes <n.manage_banned_users_link.>administrar los usuarios bloquedados</n.manage_banned_users_link.> en <n.location/>.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>También puedes <n.root_node.change_permissions_link.>cambiar los permisos</n.root_node.change_permissions_link.> de los grupos de abajo.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>También puedes promocionar tu <n.app/> enviando el link a tus amigos, añadiéndolo a tu sitio web o hablando sobre ello en otros foros.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>No puedes mover este mensaje a ese destino porque el nuevo padre no permite usuarios anónimos.</to></translation>
+<translation><from>You Cannot Post Here</from><to>No Puedes Publicar Aquí</to></translation>
+<translation><from>(you can reply by email)</from><to>(puedes responder por email)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>Puedes mover el mensaje a cualquier lugar.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>No puedes publicar un mensaje aquí, pero puedes publicar en otras partes.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>Puedes probar a <n.register_link.>registrarte de nuevo</n.register_link.> o contactar con <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>Puedes usar el formulario de abajo para enviar una solicitud a los administradores.</to></translation>
+<translation><from>You have already been registered.</from><to>Ya estás registrado.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>Has sido invitado a suscribirte a <n.location/>, que está disponible en:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>Te has registrado en <n.subject/>.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>Tu suscripción a <n.location/> ha sido cancelada</to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>Has publicado demasiados mensajes en muy poco tiempo. Por favor, vuelve a intentarlo más tarde.</to></translation>
+<translation><from>You logged out</from><to>Has salido de tu cuenta</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>Tal vez necesites <n.mailing_list_options_link.>suscribirte a esta lista de correo </n.mailing_list_options_link.> para que tu mensaje sea aceptado.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>Puedes <n.page_node.unauthorized_link.>solicitar permiso para publicar </n.page_node.unauthorized_link.> aquí o contactar con <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> si tienes alguna pregunta.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>Debes estar de acuerdo con las Condiciones de Uso.</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>Debes indicar una dirección de email válida para esta lista de correo.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>Debes indicar un URL válido para esta lista de correo.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>Debes rellenar todos los campos de abajo.</to></translation>
+<translation><from>You must login to view this page.</from><to>Debes identificarte para ver esta página.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>Debes identificarte para ver <n.subject/>.</to></translation>
+<translation><from>You must login to your account.</from><to>Debes entrar a tu cuenta.</to></translation>
+<translation><from>You must provide a user name.</from><to>Debes indicar un nombre de usuario.</to></translation>
+<translation><from>You're not a subscriber</from><to>No estás suscrito</to></translation>
+<translation><from>Your Name</from><to>Tu Nombre</to></translation>
+<translation><from>Your request has been successfully sent.</from><to>Tu petición ha sido enviada correctamente.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>Tu suscripción ha sido guardada correctamente.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>Tu suscripción a <n.location/> ha sido cancelada. Si ha sido un error, puedes volver a suscribirte siguiendo el link de abajo:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>Tu suscripción a <n.location/> ha sido cancelada correctamente.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to><n.tu_ha_sido_creado.app/> correctamente.</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>Necesitarás autorización para publicar temas nuevos en <n.location/>, por lo tanto, además de registrarte, tendrás que ser aprobado por uno de los administradores.</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>Recibirás un email por cada mensaje nuevo publicado en este tema.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>Recibirás un email con un link para activar tu cuenta.</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>Tu suscripción</to></translation>
+<translation><from>edit</from><to>modificar</to></translation>
+<translation><from>Remove ads</from><to>Eliminar publicidad</to></translation>
+<translation><from>View profile of <t.author/></from><to>Ver el perfil de <n.author/></to></translation>
+<translation><from>Description</from><to>Descripción</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>Videos de Youtube, Vimeo y LiveLeak.</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>Ususarios bloqueados en <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>Prefijo del Asunto</to></translation>
+
+#NEW 2
+<translation><from>Change title and meta tags</from><to>Cambiar título y meta tags</to></translation>
+<translation><from>Here you can customize the title and meta tags of the <t.location/> page.</from><to>Aquí puedes personalizar el título y meta tags de <n.location/>.</to></translation>
+<translation><from>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</from><to>La meta información de abajo te puede ayudar a optimizar esta página para motores de búsqueda (por ej. google, yahoo!, etc.)</to></translation>
+<translation><from>Use custom values</from><to>Usar valores personalizados</to></translation>
+<translation><from>Page Title</from><to>Título de la Página</to></translation>
+<translation><from>Enter a context-rich title that clearly identifies this page.</from><to>Introduce un título con un contexto bien definido que identifique claramente esta página.</to></translation>
+<translation><from>The title should ideally be less than 70 characters in length.</from><to>Idealmente, el título debería tener menos de 70 caracteres.</to></translation>
+<translation><from>Meta Description</from><to>Meta Descripción</to></translation>
+<translation><from>Enter a brief and concise summary of your page's content.</from><to>Introduce un resumen breve y conciso del contenido de tu página.</to></translation>
+<translation><from>Limit your description to 155 characters or 170 characters at most.</from><to>Limita tu descripción a 155 caracteres o a 170 como máximo.</to></translation>
+<translation><from>Mailing List Archive</from><to>Archivo de la Lista de Correo</to></translation>
+<translation><from>Filter: priority <t.priority/></from><to>Filtro: prioridad <n.priority/></to></translation>
+<translation><from>Filter: assignee <t.author/></from><to>Filtro: asignado <n.author/></to></translation>
+<translation><from>Use Google Analytics</from><to>Usa Google Analytics</to></translation>
+<translation><from>Analytics Account ID:</from><to>Número de la cuenta de Analytics:</to></translation>
+<translation><from>(e.g., UA-12345-0)</from><to>(por ej. UA-12345-0)</to></translation>
+<translation><from>Enter a valid analytics account ID.</from><to>Introduce un número de cuenta de analytics válido.</to></translation>
+<translation><from>Here you can use Google Analytics to measure the success of your app.</from><to>Aquí puedes usar Google Analytics para medir el éxito de tu aplicación.</to></translation>
+<translation><from>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</from><to>Introduce abajo el número (ID) de tu cuenta de Analytics y podrás rastrear visitas, visitantes y otras estadísticas importantes relacionadas con tu tráfico web.</to></translation>
+
+<translation><from>Digest Email</from><to>Boletín vía email</to></translation>
+<translation><from>on <t.date/></from><to>en <n.date/></to></translation>
+<translation><from>DO NOT REPLY TO THIS EMAIL</from><to>NO RESPONDA A ESTE EMAIL</to></translation>
+<translation><from>Replies sent to this address are not read or processed.</from><to>Las respuestas enviadas a esta dirección no son leídas o procesadas.</to></translation>
+<translation><from>If you want to respond to a post for which you received this email, please go to the website: <t.url/></from><to>Si deseas responder al mensaje por el cual has recibido este email, por favor visite la web: <n.url/></to></translation>
+<translation><from>new post</from><to>nuevo mensaje</to></translation>
+<translation><from>new posts</from><to>nuevos mensajes</to></translation>
+
+<translation><from>New registered user in <t.location/>!</from><to>Nuevo usuario registrado en <n.location/>!</to></translation>
+<translation><from>User profile</from><to>Perfil del usuario</to></translation>
+<translation><from>New user:</from><to>Nuevo usuario:</to></translation>
+
+<translation><from><n.author/> wrote</from><to><n.author/> escribió</to></translation>
+<translation><from>You still have <t.number/> day(s) left without advertising</from><to>Aún te quedan <n.number/> dia(s) días sin anuncios.</to></translation>
+<translation><from>(Only administrators can see this message)</from><to>(Solamente los administradores pueden ver este mensaje)</to></translation>
+
+<translation><from>Some private items have been omitted because you don't have permission to view them.</from><to>Algunos elementos privados has sido omitidos porque no tienes permiso para verlos.</to></translation>
+ <translation><from>Please enter the email address you used to register and click on "Submit". We will email you a link to reset your password.</from><to>Por favor, introduce la dirección de email que has usado para registrarte y haz click en "Enviar". Te enviaremos un link para cambiar tu contraseña.</to></translation>
+<translation><from>Submit</from><to>Enviar</to></translation>
+<translation><from>Password Reset Sent</from><to>Cambio de contraseña enviado</to></translation>
+<translation><from>We have sent you a link to reset your password. Please check your email now. If you don't receive the instructions in a few minutes, check your spam folder or try to resend the request.</from><to>Te hemos enviado un link para cambiar tu contraseña. Por favor, comprueba ahora tu email. Si no recibes las instrucciones en unos minutos, comprueba tu bandeja de correo no deseado o trata de enviar la solicitud de nuevo.</to></translation>
+<translation><from>Reset your password / <t.location/></from><to>Cambiar la contraseña / <n.location/></to></translation>
+<translation><from>We received a request to reset your password in <t.location/>.</from><to>Hemos recibido una solicitud para cambiar tu contraseña en<n.location/>.</to></translation>
+<translation><from>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</from><to>Si quieres cambiar tu contraseña, haz click en el botón de abajo (o copia y pega el link en tu navegador):</to></translation>
+<translation><from>If you don't want to reset your password, please ignore this message. Your password will not be reset.</from><to>Si no quieres cambiar tu contraseña, por favor, ignora este mensaje. Tu contraseña no será cambiada.</to></translation>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_fr_fr.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,729 @@
+## French (France)
+
+<macro name="is_masculine" dot_parameter="noun">
+	<n.not.regex_matches text="[n.to_lower_case.noun/]" pattern="galerie|sous-catégorie|catégorie"/>
+</macro>
+
+<macro name="le" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>le</then>
+		<else>la</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="nouveau" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>nouveau</then>
+		<else>nouvelle</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="ce" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>ce</then>
+		<else>cette</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="un_nouveau" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>un nouveau</then>
+		<else>une nouvelle</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="un" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>un</then>
+		<else>une</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="le_mentionne" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>le <n.what/> mentionné</then>
+		<else>la <n.what/> mentionnée</else>
+	</n.if.is_masculine.what>
+</macro>
+
+<macro name="a_ete_cree" dot_parameter="what">
+	<n.what/>
+	<n.if.is_masculine.what>
+		<then>a été créé</then>
+		<else>a été créée</else>
+	</n.if.is_masculine.what>
+</macro>
+
+## MONTHS ##
+
+<translation><from>January</from><to>Janvier</to></translation>
+<translation><from>February</from><to>Février</to></translation>
+<translation><from>March</from><to>Mars</to></translation>
+<translation><from>April</from><to>Avril</to></translation>
+<translation><from>May</from><to>Mai</to></translation>
+<translation><from>June</from><to>Juin</to></translation>
+<translation><from>July</from><to>Juillet</to></translation>
+<translation><from>August</from><to>Août</to></translation>
+<translation><from>September</from><to>Septembre</to></translation>
+<translation><from>October</from><to>Octobre</to></translation>
+<translation><from>November</from><to>Novembre</to></translation>
+<translation><from>December</from><to>Décembre</to></translation>
+
+<translation><from>Jan</from><to>Jan</to></translation>
+<translation><from>Feb</from><to>Fév</to></translation>
+<translation><from>Mar</from><to>Mar</to></translation>
+<translation><from>Apr</from><to>Avr</to></translation>
+<!--translation><from>May</from><to>Mai</to></translation-->
+<translation><from>Jun</from><to>Juin</to></translation>
+<translation><from>Jul</from><to>Juil</to></translation>
+<translation><from>Aug</from><to>Aoû</to></translation>
+<translation><from>Sep</from><to>Sep</to></translation>
+<translation><from>Oct</from><to>Oct</to></translation>
+<translation><from>Nov</from><to>Nov</to></translation>
+<translation><from>Dec</from><to>Déc</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>L'usage abusif de cette fonction est également une violation des conditions d'utilisation.</to></translation>
+<translation><from>Access Request</from><to>Demande d'Accès</to></translation>
+<translation><from>Account settings</from><to>Configurations du compte</to></translation>
+<translation><from>Account Settings</from><to>Configurations du Compte</to></translation>
+<translation><from>Action</from><to>Action</to></translation>
+<translation><from>Add a link to another page</from><to>Ajouter un lien vers une autre page</to></translation>
+<translation><from>Add a new comment</from><to>Ajouter un nouveau commentaire</to></translation>
+<translation><from>Add a new group</from><to>Ajouter un nouveau groupe</to></translation>
+<translation><from>Add an image to your post</from><to>Ajouter une image à votre message</to></translation>
+<translation><from>Add another address</from><to>Ajouter une autre adresse</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>Ajouter du contenu qui ne doit pas être encodé (par ex., du code)</to></translation>
+<translation><from>Adding Sub-Headers</from><to>Ajout des Sous-En-Têtes</to></translation>
+<translation><from>Add New Group</from><to>Ajouter un Nouveau Groupe</to></translation>
+<translation><from>Add / Remove Groups</from><to>Ajouter / Supprimer des Groupes</to></translation>
+<translation><from>Add smileys and funny animations</from><to>Ajouter des smileys et des animations amusantes</to></translation>
+<translation><from>Add Subscribers</from><to>Ajouter des Abonnés</to></translation>
+<translation><from>Add this item to your favorites list</from><to>Ajouter cet élément à votre liste de favoris</to></translation>
+<translation><from>Administrator</from><to>Administrateur</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity,  other sexual content.</from><to>Contenu adulte ou mature, activité sexuelle explicite, nudité ou autre contenu à caractère sexuel.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>Bagarre entre adultes, agression physique, violence impliquant des jeunes, cruauté animale ou promotion du terrorisme.</to></translation>
+<translation><from>Advanced Search</from><to>Recherche Avancée</to></translation>
+<translation><from>Advanced Settings</from><to>Configurations Avancées</to></translation>
+<translation><from>Advertisement</from><to>Publicité</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>M'avertir par email quand quelqu'un poste un message dans ce fil</to></translation>
+<translation><from>All</from><to>Tous</to></translation>
+<translation><from>all of the words:</from><to>tous les mots :</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>Tous les messages de <n.author/> ont été supprimés avec succès.</to></translation>
+<translation><from>All posts</from><to>Tous les messages</to></translation>
+<translation><from>All users belong to this group</from><to>Tous les utilisateurs appartiennent à ce groupe</to></translation>
+<translation><from>All Users</from><to>Tous les Utilisateurs</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>Tous les utilisateurs qui se sont inscrit sur <n.location/>. Ces utilisateurs ont confirmé leur adresse email et peuvent se connecter au système.</to></translation>
+<translation><from>Already Subscribed</from><to>Déjà Abonné</to></translation>
+<translation><from>An email has been sent to you.</from><to>Un email vous a été envoyé.</to></translation>
+<translation><from>Anonymous</from><to>Anonyme</to></translation>
+<translation><from>anonymous user</from><to>utilisateur anonyme</to></translation>
+<translation><from>anonymous users</from><to>utilisateurs anonymes</to></translation>
+<translation><from>Any message part contains</from><to>N'importe quelle partie du message contient</to></translation>
+<translation><from>Application</from><to>Application</to></translation>
+<translation><from>Apps</from><to>Applications</to></translation>
+<translation><from>Assignee</from><to>Personne Assignée</to></translation>
+<translation><from>Assign</from><to>Assigner</to></translation>
+<translation><from>Assignment</from><to>Assignation</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>Comme vous l'avez demandé, l'adresse email pour poster de nouveaux sujets dans <n.app/> est :</to></translation>
+<translation><from>at least one of the words:</from><to>au moins l'un des mots :</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to>Flux Atom de <n.location/></to></translation>
+<translation><from>at priority</from><to>avec la priorité</to></translation>
+<translation><from>Authorized Users Only</from><to>Utilisateurs Autorisés Seulement</to></translation>
+<translation><from>Author name</from><to>Nom de l'auteur</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>Évitez les phrases courtes telles que "Merci", "Génial",... Vous pouvez <n.page_node.reply_to_author_link.>envoyer un message privé</n.page_node.reply_to_author_link.> si vous le voulez.</to></translation>
+<translation><from>Banned User</from><to>Utilisateur Banni</to></translation>
+<translation><from>Ban this user</from><to>Bannir cet utilisateur</to></translation>
+<translation><from>Ban User</from><to>Bannir l'Utilisateur</to></translation>
+<translation><from>Before deleting this archive...</from><to>Avant de supprimer cette archive...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>Ci-dessous, vous pouvez gérer les groupes et les utilisateurs. Vous pouvez copier et coller des utilisateurs afin de les déplacer d'un groupe à un autre.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>IMPORTANT</b> : Nabble enverra une invitation à chaque adresse email de cette liste. Ces utilisateurs devront cliquer sur un lien afin de confirmer leur abonnement.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>IMPORTANT :</b> Cet abonnement est indépendant du véritable abonnement à la liste de diffusion. En fait, vous serez abonné à l'archive du forum, pas à la liste de diffusion elle-même. L'abonnement à l'archive ne garantit pas que vos messages seront acceptés par la liste de diffusion.</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>IMPORTANT</b> : Vous devez abonner cette archive à la liste de diffusion afin de la faire fonctionner.</to></translation>
+<translation><from>blog</from><to>blog</to></translation>
+<translation><from>Blog</from><to>Blog</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>Note</b> : Puisque vous êtes un administrateur, vous pouvez <n.page_node.change_permissions_link.>changer les permissions de <n.location/></n.page_node.change_permissions_link.> et vous assurer que vous pouvez créer de nouveaux sujets ici.</to></translation>
+<translation><from>board</from><to>table</to></translation>
+<translation><from>Board</from><to>Table</to></translation>
+<translation><from>Bold</from><to>Gras</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>Avertissement :</b> L'index de recherche est en cours de reconstruction. Les résultats de la recherche peuvent être incomplets.</to></translation>
+<translation><from>by <t.author/></from><to>par <n.author/></to></translation>
+<translation><from>Cancel</from><to>Annuler</to></translation>
+<translation><from>category</from><to>catégorie</to></translation>
+<translation><from>Category</from><to>Catégorie</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>ATTENTION : cette action est irréversible.</to></translation>
+<translation><from>Change appearance</from><to>Changer l'apparence</to></translation>
+<translation><from>Change application type</from><to>Changer le type d'application</to></translation>
+<translation><from>Change Application Type</from><to>Changer le Type d'Application</to></translation>
+<translation><from>Change code image</from><to>Changer l'image code</to></translation>
+<translation><from>Change domain name</from><to>Changer le nom de domaine</to></translation>
+<translation><from>Change language</from><to>Changer la langue</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>Changer ou supprimer votre avatar.</to></translation>
+<translation><from>Change parent</from><to>Changer le parent</to></translation>
+<translation><from>Change permissions</from><to>Changer les permissions</to></translation>
+<translation><from>Change Permissions</from><to>Changer les Permissions</to></translation>
+<translation><from>Change post date</from><to>Changer la date du message</to></translation>
+<translation><from>Change Post Date</from><to>Changer la Date du Message</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>Changer le texte de la signature qui est affichée en bas de vos messages.</to></translation>
+<translation><from>Change User Groups</from><to>Changer les Groupes d'Utilisateurs</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>Changer les préférences de visualisation et autres configurations.</to></translation>
+<translation><from>Change Your Picture</from><to>Changer Votre Photo</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>Changer votre adresse email, mot de passe et votre pseudo.</to></translation>
+<translation><from>Child abuse</from><to>Maltraitance sur mineur</to></translation>
+<translation><from>Choose a subcategory</from><to>Choisir une sous-catégorie</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>Choisissez une sous-catégorie pour poster votre message</to></translation>
+<translation><from>Choose the best offer for you</from><to>Choisissez la meilleure option pour vous</to></translation>
+<translation><from>Classic</from><to>Classique</to></translation>
+<translation><from>Clear Log</from><to>Effacer l'Historique</to></translation>
+<translation><from>Click for more options</from><to>Cliquez pour plus d'options</to></translation>
+<translation><from>click here</from><to>cliquez ici</to></translation>
+<translation><from>Click here to make your first post</from><to>Cliquez ici pour poster votre premier message</to></translation>
+<translation><from>Click to filter</from><to>Cliquez pour filtrer</to></translation>
+<translation><from>Close</from><to>Fermer</to></translation>
+<translation><from>Close this message</from><to>Fermer ce message</to></translation>
+<translation><from>comment</from><to>commentaire</to></translation>
+<translation><from>Comment</from><to>Commentaire</to></translation>
+<translation><from>comments</from><to>commentaires</to></translation>
+<translation><from>Comments</from><to>Commentaires</to></translation>
+<translation><from>Confirm Password</from><to>Confirmez le Mot de Passe</to></translation>
+<translation><from>Confirm Subscription</from><to>Confirmer l'Abonnement</to></translation>
+<translation><from>Congratulations!</from><to>Félicitations !</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>Félicitations pour votre <n.nouveau.app/> !</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>Contenu incitant à la haine ou à la violence, abus de la vulnérabilité d'individus, harcèlement, intolérance raciale ou manifestation contre une personne, un groupe ou une organisation, ou vulgarité excessive.</to></translation>
+<translation><from>CONTENTS DELETED</from><to>CONTENU SUPPRIMÉ</to></translation>
+<translation><from>Continue</from><to>Continuer</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>Atteinte aux droits d'auteur ou à la vie privée, ou autre réclamation légale.</to></translation>
+<translation><from>Count</from><to>Total</to></translation>
+<translation><from>Created by <t.author/></from><to>Créé par <n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>Créer <n.un_nouveau.element/></to></translation>
+<translation><from>Create <t.element/></from><to>Créer <n.un.element/></to></translation>
+<translation><from>Current Credits</from><to>Crédits Actuels</to></translation>
+<translation><from>Currently Nabble supports</from><to>Actuellement, Nabble supporte</to></translation>
+<translation><from>Current Subscribers</from><to>Abonnés Actuels</to></translation>
+<translation><from>Daily digest</from><to>Résumé journalier</to></translation>
+<translation><from>Data successfully saved</from><to>Données enregistrées avec succès</to></translation>
+<translation><from>Date</from><to>Date</to></translation>
+<translation><from>days</from><to>derniers jours</to></translation>
+<translation><from>Dear <t.name/>,</from><to>Cher <n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>Cher utilisateur,</to></translation>
+<translation><from>Default</from><to>Par défaut</to></translation>
+<translation><from>Delete all posts from this user</from><to>Supprimer tous les messages de cet utilisateur</to></translation>
+<translation><from>Delete Application</from><to>Supprimer l'Application</to></translation>
+<translation><from>Deleted posts</from><to>Messages supprimés</to></translation>
+<translation><from>Delete</from><to>Supprimer</to></translation>
+<translation><from>Delete this post and replies</from><to>Supprimer ce message et les réponses</to></translation>
+<translation><from>Delete this post</from><to>Supprimer ce message</to></translation>
+<translation><from>Delete this topic</from><to>Supprimer ce sujet</to></translation>
+<translation><from>Description is in HTML Format</from><to>Description au Format HTML</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>Ne postez pas de manière répétitive. Attendez quelques jours. Les gens liront votre message par email.</to></translation>
+<translation><from>Don't show this message again</from><to>Ne plus montrer ce message</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>Voulez-vous vraiment supprimer ce message ?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>Voulez-vous vraiment supprimer de manière permanente ce message et toutes les réponses ?</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>Voulez-vous vraiment <n.important.>supprimer</n.important.> de manière permanente <n.location/> ?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>Voulez-vous vraiment supprimer les configurations d'archivage de la liste de diffusion ?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>Voulez-vous réellement vous abonner à <n.location/> ?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>Voulez-vous vraiment débannir cet utilisateur ?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>Voulez-vous vraiment vous désabonner de <n.location/> ?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>Toxicomanie, substances illicites et objets associés, utilisation abusive du feu ou d'explosifs, vente de bière ou d'alcool fort, tabac et produits dérivés, armes ou munitions, ou autres actes dangereux.</to></translation>
+<translation><from>Edit name & description</from><to>Éditer le nom & la description</to></translation>
+<translation><from>Edit Name & Description</from><to>Éditer le Nom & la Description</to></translation>
+<translation><from>Editor</from><to>Editeur</to></translation>
+<translation><from>Edit Personal Information</from><to>Éditer les Informations Personnelles</to></translation>
+<translation><from>Edit post</from><to>Éditer le message</to></translation>
+<translation><from>Edit Subscription</from><to>Modifier l'Abonnement</to></translation>
+<translation><from>Edit Your Signature</from><to>Changer Votre Signature</to></translation>
+<translation><from>Email Confirmation</from><to>Confirmation de l'Adresse Email</to></translation>
+<translation><from>Email for <t.app/></from><to>Email pour <n.app/></to></translation>
+<translation><from>Email</from><to>Email</to></translation>
+<translation><from>Email Subscription</from><to>Abonnement par Email</to></translation>
+<translation><from>Email this post to...</from><to>Envoyer ce message à...</to></translation>
+<translation><from>Embedding Contents</from><to>Intégration du Contenu</to></translation>
+<translation><from>Embedding options</from><to>Options d'intégration</to></translation>
+<translation><from>Embed</from><to>Intégrer</to></translation>
+<translation><from>Embed post</from><to>Intégrer le message</to></translation>
+<translation><from>Embed Tags</from><to>Balises d'Intégration</to></translation>
+<translation><from>Embed this <t.app/></from><to>Intégrer <n.ce.app/></to></translation>
+<translation><from>Empty</from><to>Vide</to></translation>
+<translation><from>Enter a valid email address.</from><to>Entrez une adresse email valide.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>Entrez votre adresse email ci-dessous et nous vous enverront un email de confirmation.</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>Entrez une adresse email ou un nom d'utilisateur par ligne :</to></translation>
+<translation><from>Enter one user per row</from><to>Entrez un utilisateur par ligne</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>Entrez le permalien du <b>message</b> ou du <b>forum</b> qui sera le nouveau parent, ou laissez l'entrée vide pour faire de ce message un sujet indépendant :</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>Entrez la page d'accueil de cette liste de diffusion, où les utilisateurs peuvent trouver plus d'information.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>Entrez le préfixe que cette liste de diffusion utilise avant le sujet. Le préfixe sera automatiquement retiré des emails importés.</to></translation>
+<translation><from>Enter your email address</from><to>Entrez votre adresse email</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>Exemple : monsieur_tartempion@domaine.fr</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>Exemple : nom_de_liste@domaine_de_liste.com</to></translation>
+<translation><from>Examples:</from><to>Exemples :</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>Exemples : '[la-liste]', 'Abc :', etc.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>Expliquez aux administrateurs pourquoi vous voulez avoir accès à cette partie privée.</to></translation>
+<translation><from>Explanation from this user:</from><to>Explication de cet utilisateur :</to></translation>
+<translation><from>Extras & add-ons</from><to>Extensions & modules</to></translation>
+<translation><from>Failed</from><to>Échec</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>Favori (cliquez pour retirer cet élément de votre liste de favoris)</to></translation>
+<translation><from>Feeds</from><to>Flux</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>Le fichier n'a pas été ajouté : <n.file/> (veuillez télécharger de nouveau ou retirer la balise)</to></translation>
+<translation><from>Filter by group</from><to>Filtrer par groupe</to></translation>
+<translation><from>Floating sub-forum</from><to>Sous-forum flottant</to></translation>
+<translation><from>Forgot Password?</from><to>Mot de Passe Oublié ?</to></translation>
+<translation><from>Forgot your password?</from><to>Mot de passe oublié ?</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>Pour plus d'informations, consultez : <n.info/></to></translation>
+<translation><from>forum</from><to>forum</to></translation>
+<translation><from>Forum</from><to>Forum</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to><n.app/> Intégrable Gratuit</to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>À partir de maintenant, vous recevrez un email à chaque message posté dans <n.location/>.</to></translation>
+<translation><from>gallery</from><to>galerie</to></translation>
+<translation><from>Gallery</from><to>Galerie</to></translation>
+<translation><from>Gambling or casino-related content</from><to>Contenu relatif aux jeux de hasard ou au casino</to></translation>
+<translation><from>Go back</from><to>Retour</to></translation>
+<translation><from>Go to next message</from><to>Aller au prochain message</to></translation>
+<translation><from>Group Name:</from><to>Nom du Groupe :</to></translation>
+<translation><from>Groups</from><to>Groupes</to></translation>
+<translation><from>Groups of this user</from><to>Groupes de cet utilisateur</to></translation>
+<translation><from>Hacking / cracking</from><to>Hacking / cracking</to></translation>
+<translation><from>Harmful dangerous acts</from><to>Actes dangereux</to></translation>
+<translation><from>Hateful or abusive content</from><to>Contenu abusif ou incitant à la haine</to></translation>
+<translation><from>Help</from><to>Aide</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>Ici, vous pouvez acheter des crédits qui garderont vos applications Nabble sans publicités. Chaque crédit représente une visualisation sans publicités. Les visiteurs verront des pages sans pubs tant qu'il vous reste des crédits. Nabble commencera à afficher des publicités quand vous n'aurez plus de crédits.</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>Ici, vous pouvez cacher aux utilisateurs les informations actuelles d'archivage de la liste de diffusion. Cette option peut vous aider à remplacer votre serveur de liste de diffusion avec la fonction d'abonnement par email de Nabble.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>Masquer l'adresse email (par ex., user@host.com par un lien <n.lt/>adresse masquée<n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>Masquer l'adresse email</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>Cacher aux utilisateurs les informations d'archivage de la liste de diffusion</to></translation>
+<translation><from>Highest</from><to>Maximale</to></translation>
+<translation><from>High</from><to>Haute</to></translation>
+<translation><from>Hi <t.name/>,</from><to>Salut <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>Si cette adresse email est la vôtre, vous devriez <n.register_link.>vous inscrire</n.register_link.> en utilisant cette même adresse. Après inscription, vous possèderez ce compte utilisateur.</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>Si vous ne recevez pas d'emails de la part de Nabble, veuillez vérifier votre dossier <b>spam</b> ou <b>courrier indésirable</b>.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>Si vous comptez poster une question, merci d'effectuer une recherche avant. Votre question pourrait déjà avoir été répondue.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>Si vous n'êtes toujours pas membre, vous pouvez <n.register_link.><b>vous inscrire dès maintenant</b></n.register_link.>.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>Si vous bannissez cet utilisateur, il/elle ne pourra plus rien faire dans <n.location/>.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>Si vous n'avez pas demandé cet email ou si vous n'avez aucune idée de pourquoi vous l'avez reçu, veuillez l'ignorer. Il peut s'agir d'une erreur commise par une autre personne.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>Si vous ne voulez pas encore vous inscrire, entrez juste l'adresse email à partir de laquelle vous comptez poster et votre adresse personnelle vous sera envoyée.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>Si vous avez déjà annulé l'abonnement, alors vous pouvez procéder à la suppression.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>Si vous le connaissez, veuillez sélectionner l'application du serveur de liste de diffusion et sa version.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>Si vous vous êtes déconnecté par erreur, veuillez <n.login_link.>vous connecter</n.login_link.> à nouveau.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>Si vous répondez à cet email, votre message sera ajouté à la discussion ci-dessous</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>Si vous débannissez cet utilisateur, il/elle pourra de nouveau poster des messages dans <n.location/>.</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>Si vous voulez plutôt vous abonner à la liste de diffusion, <n.mailing_list_options_link.>allez à cette page</n.mailing_list_options_link.>.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>Ignorer l'en-tête X-No-Archive</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>J'ai lu and j'accepte les <n.terms_link.>Conditions d'Utilisation</n.terms_link.> de Nabble.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>L'image n'a pas été ajoutée : <n.image/> (veuillez télécharger de nouveau ou retirer la balise)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>Je suis un abonné, laissez-moi poster maintenant</to></translation>
+<translation><from>Incorrect Login!</from><to>Identifiant Incorrect !</to></translation>
+<translation><from>Individual emails</from><to>Emails individuels</to></translation>
+<translation><from>Inherit</from><to>Héritée</to></translation>
+<translation><from>In Reply To</from><to>En Réponse À</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>En réponse à <n.parent_link.>ce message</n.parent_link.> posté par <n.author/></to></translation>
+<translation><from>Insert</from><to>Insérer</to></translation>
+<translation><from>Insert Image</from><to>Insérer une Image</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>Au lieu de poster via l'interface web, vous pouvez également poster de nouveaux sujets en envoyant des emails à l'adresse suivante :</to></translation>
+<translation><from>in <t.location/></from><to>dans <n.location/></to></translation>
+<translation><from>Invalid Code</from><to>Code Invalide</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>Adresse email invalide : <n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>Nombre de jours invalide, doit être entier.</to></translation>
+<translation><from>invisible user</from><to>utilisateur invisible</to></translation>
+<translation><from>invisible users</from><to>utilisateurs invisibles</to></translation>
+<translation><from>Invite Subscribers</from><to>Inviter des Abonnés</to></translation>
+<translation><from>is:</from><to>est :</to></translation>
+<translation><from>is not:</from><to>n'est pas :</to></translation>
+<translation><from>is within the last:</from><to>se situe durant les :</to></translation>
+<translation><from>Italic</from><to>Italique</to></translation>
+<translation><from>item</from><to>élément</to></translation>
+<translation><from>items</from><to>éléments</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>Il ne sera PAS possible de restaurer les éléments supprimés ultérieurement.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>Collez simplement le code (fourni par les sites ci-dessus) entre ces balises et vous êtes prêt à le poster.</to></translation>
+<translation><from>Last Post</from><to>Dernier Message</to></translation>
+<translation><from>Learn more</from><to>En savoir plus</to></translation>
+<translation><from>Leave a comment</from><to>Laisser un commentaire</to></translation>
+<translation><from>Link</from><to>Lien</to></translation>
+<translation><from>Link to <t.location/></from><to>Lien vers <n.location/></to></translation>
+<translation><from>List</from><to>Liste</to></translation>
+<translation><from>List of Subcategories</from><to>Liste de Sous-Catégories</to></translation>
+<translation><from>List Server</from><to>Serveur de Liste</to></translation>
+<translation><from>List View</from><to>Vue Listée</to></translation>
+<translation><from>Loading...</from><to>Chargement...</to></translation>
+<translation><from>Location</from><to>Localisation</to></translation>
+<translation><from>Locked</from><to>Verrouillé</to></translation>
+<translation><from>Lock topic</from><to>Verrouiller le sujet</to></translation>
+<translation><from>Login</from><to>Connexion</to></translation>
+<translation><from>Log is empty</from><to>L'historique est vide</to></translation>
+<translation><from>Log out</from><to>Déconnexion</to></translation>
+<translation><from>Lowest</from><to>Minimale</to></translation>
+<translation><from>Low</from><to>Basse</to></translation>
+<translation><from>Mailing List Address</from><to>Adresse de la Liste de Diffusion</to></translation>
+<translation><from>Mailing list archive settings</from><to>Configurations d'archivage de la liste de diffusion</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>Configurations d'Archivage de la Liste de Diffusion</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>Rappel d'Abonnement à la Liste de Diffusion</to></translation>
+<translation><from>Mailing List Website</from><to>Site Internet de la Liste de Diffusion</to></translation>
+<translation><from>Main Page</from><to>Page Principale</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>Assurez-vous que vous êtes en train d'utiliser le même navigateur que vous avez utilisé pour remplir la demande d'inscription.</to></translation>
+<translation><from>Manage banned users</from><to>Gérer les utilisateurs bannis</to></translation>
+<translation><from>Manage pinned topics</from><to>Gérer les sujets épinglés</to></translation>
+<translation><from>Manage subscribers</from><to>Gérer les abonnés</to></translation>
+<translation><from>Manage Subscribers</from><to>Gérer les Abonnés</to></translation>
+<translation><from>Manage <t.items/></from><to>Gérer les <n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>Gérer les utilisateurs & les groupes</to></translation>
+<translation><from>Manage Users & Groups</from><to>Gérer les Utilisateurs & les Groupes</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>Publicité massive, texte mensonger, arnaques ou fraudes.</to></translation>
+<translation><from>max. 80 characters</from><to>max. 80 caractères</to></translation>
+<translation><from>Message date</from><to>Date du message</to></translation>
+<translation><from>message</from><to>message</to></translation>
+<translation><from>Message</from><to>Message</to></translation>
+<translation><from>Message is in HTML Format</from><to>Message au format HTML</to></translation>
+<translation><from>messages</from><to>messages</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>Les messages postés ici seront envoyés à la liste de diffusion.</to></translation>
+<translation><from>Message subject contains</from><to>Le sujet du message contient</to></translation>
+<translation><from>Message text contains</from><to>Le texte du message contient</to></translation>
+<translation><from>Mixed</from><to>Mixte</to></translation>
+<translation><from>Modified</from><to>Modifiée</to></translation>
+<translation><from>Archives</from><to>Archives</to></translation>
+<translation><from>More Categories</from><to>Plus de Catégories</to></translation>
+<translation><from>More</from><to>Plus</to></translation>
+<translation><from>more help</from><to>plus d'aide</to></translation>
+<translation><from>more options</from><to>plus d'options</to></translation>
+<translation><from>Move post</from><to>Déplacer le message</to></translation>
+<translation><from>Move Post</from><to>Déplacer le Message</to></translation>
+<translation><from>Move topic</from><to>Déplacer le sujet</to></translation>
+<translation><from>My Nabble Applications</from><to>Mes Applications Nabble</to></translation>
+<translation><from>My Pending Posts</from><to>Mes Messages en Attente</to></translation>
+<translation><from>My posts</from><to>Mes messages</to></translation>
+<translation><from>Nabble Support</from><to>Centre d'Aide Nabble</to></translation>
+<translation><from>Name</from><to>Nom</to></translation>
+<translation><from>New Post</from><to>Nouveau message</to></translation>
+<translation><from>news</from><to>journal</to></translation>
+<translation><from>News</from><to>Journal</to></translation>
+<translation><from>New Topic</from><to>Nouveau Sujet</to></translation>
+<translation><from>New topics only</from><to>Nouveaux sujets uniquement</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>ATTENTION</n.important.> : Tous les éléments de <n.location/> seront supprimés pour toujours !</to></translation>
+<translation><from>No banned users.</from><to>Aucun utilisateur banni.</to></translation>
+<translation><from>No Filter</from><to>Aucun Filtre</to></translation>
+<translation><from>none of the words:</from><to>aucun des mots :</to></translation>
+<translation><from>No registered user found with this email.</from><to>Aucun utilisateur enregistré n'a été trouvé avec cette adresse.</to></translation>
+<translation><from>No replies</from><to>Aucune réponse</to></translation>
+<translation><from>Normal</from><to>Normale</to></translation>
+<translation><from>No sub-forums</from><to>Aucun sous-forum</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>Notez que cette adresse est unique pour vous et n'accepte que les emails envoyés depuis <n.address/>. L'objectif de ce système est de limiter les courriers indésirables (spam).</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to><n.register_link.>Inscrivez-vous dès maintenant</n.register_link.> si vous souhaitez éditer votre profil, recevoir des messages par email, gérer vos favoris ou avoir accès à votre profil global.</to></translation>
+<translation><from>one email per input box</from><to>un email par boîte de réception</to></translation>
+<translation><from>Online Users</from><to>Utilisateurs Connectés</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>Seuls les utilisateurs autorisés ont accès à cette partie.</to></translation>
+<translation><from>Open this post in classic view</from><to>Ouvrir ce message en vue classique</to></translation>
+<translation><from>Open this post in list view</from><to>Ouvrir ce message en vue listée</to></translation>
+<translation><from>Open this post in threaded view</from><to>Ouvrir ce message en vue arborescente</to></translation>
+<translation><from>Options</from><to>Options</to></translation>
+<translation><from>or</from><to>ou</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>Ou vous pouvez ignorer cet email s'il vaut mieux garder cet utilisateur en-dehors de cette partie.</to></translation>
+<translation><from>Other Settings</from><to>Autres Configurations</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>Page qui regroupe sous-applications et discussions.</to></translation>
+<translation><from>Page <t.number/></from><to>Page <n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>Page avec une simple liste de sous-applications et discussions.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>Page avec messages complets et commentaires associés.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>Page avec titres et messages.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>Page avec sujets et discussions.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>Page avec sujets groupés par sous-applications. Si aucune sous-application n'existe, une liste normale de sujets est affichée.</to></translation>
+<translation><from>Password</from><to>Mot de Passe</to></translation>
+<translation><from>Password Sent</from><to>Mot de Passe Envoyé</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>Pédophilie, violence et autres abus sur mineur.</to></translation>
+<translation><from>People</from><to>Personnes</to></translation>
+<translation><from>People in <t.location/></from><to>Personnes sur <n.location/></to></translation>
+<translation><from>Permalink</from><to>Permalien</to></translation>
+<translation><from>Photo and image gallery.</from><to>Galerie de photos et d'images.</to></translation>
+<translation><from>Pinned sub-forum</from><to>Sous-forum épinglé</to></translation>
+<translation><from>Pin topic</from><to>Épingler le sujet</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>Pensez à marquer le lien ci-dessus ou à sauvegarder cet email afin que vous puissiez retrouver facilement votre <n.app/> dans le futur.</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>Veuillez dès à présent vérifier votre boîte de réception et activer votre compte afin d'avoir accès à toutes les fonctionnalités.</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>Veuillez cliquer sur le lien de confirmation ci-dessous pour activer votre compte :</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>Veuillez contacter le Centre d'Aide Nabble en cas de besoin.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>Veuillez contacter les administrateurs si vous avez besoin d'aide.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>Veuillez entrer une adresse email correcte et réessayer.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>Veuillez suivre les instructions indiquées dans l'email afin de compléter la procédure d'inscription.</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>Veuillez suivre les <n.subscribe_instructions_link.>instructions d'abonnement</n.subscribe_instructions_link.> pour cette archive.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>Veuillez entrer un permalien valide.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>Veuillez entrer de nouveau votre adresse email/mot de passe et cliquer sur Connexion.</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>Veuillez respecter l'étiquette des listes de diffusions</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>Sondages de Polldaddy.com (sondages flash uniquement)</to></translation>
+<translation><from>Post by email</from><to>Poster par email</to></translation>
+<translation><from>Post by Email</from><to>Poster par Email</to></translation>
+<translation><from>Post Count</from><to>Nombre de messages</to></translation>
+<translation><from>Posted by <t.author/></from><to>Posté par <n.author/></to></translation>
+<translation><from>post</from><to>message</to></translation>
+<translation><from>Post Message</from><to>Poster le Message</to></translation>
+<translation><from>Post New Message</from><to>Poster un Nouveau Message</to></translation>
+<translation><from>Post new message in <t.location/></from><to>Poster un nouveau message dans <n.location/></to></translation>
+<translation><from>posts</from><to>messages</to></translation>
+<translation><from>Posts</from><to>Messages</to></translation>
+<translation><from>Posts in <t.location/></from><to>Messages dans <n.location/></to></translation>
+<translation><from>Preview Message</from><to>Prévisualiser le Message</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>Les prix sont en dollars américains. Cette procédure de paiement utilise <n.amazon_payments_link.>Amazon Payments</n.amazon_payments_link.>, qui permet aux clients d'Amazon d'utiliser les méthodes de versement définies sur leur compte Amazon.com pour payer les biens et les services sur les sites Internet et applications acceptant Amazon Payments.</to></translation>
+<translation><from>Print post</from><to>Imprimer ce message</to></translation>
+<translation><from>Priority</from><to>Priorité</to></translation>
+<translation><from>(private)</from><to>(privé)</to></translation>
+<translation><from>Profile of <t.author/></from><to>Profil de <n.author/></to></translation>
+<translation><from>Quote</from><to>Citation</to></translation>
+<translation><from>Quote the original message</from><to>Citer le message d'origine</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>Citez le message auquel vous répondez et ne gardez que les parties pertinentes. Ceci fournit un contexte à ceux qui liront votre message par email.</to></translation>
+<translation><from>Raw mail</from><to>Source</to></translation>
+<translation><from>Raw text</from><to>Texte brut</to></translation>
+<translation><from>read more</from><to>en lire plus</to></translation>
+<translation><from>Read more</from><to>En lire plus</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>Liste en lecture seule avec tous les utilisateurs possédant un email dans <n.location/>.</to></translation>
+<translation><from>Receive direct replies only.</from><to>Recevoir uniquement les réponses directes.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>Recevoir tous les messages postés dans <n.location/>.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>Recevoir toutes les réponses de ce sujet.</to></translation>
+<translation><from>Receive new topics only.</from><to>Recevoir uniquement les nouveaux sujets.</to></translation>
+<translation><from>Refresh</from><to>Actualiser</to></translation>
+<translation><from>Registered</from><to>Inscrit</to></translation>
+<translation><from>Registered Users</from><to>Utilisateurs Inscrits</to></translation>
+<translation><from>Register</from><to>S'inscrire</to></translation>
+<translation><from>Registering...</from><to>Inscription...</to></translation>
+<translation><from>Register Now</from><to>Inscrivez-Vous dès Maitenant</to></translation>
+<translation><from>Register to <t.app/></from><to>Inscrivez-vous à <n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>Inscription Confirmée</to></translation>
+<translation><from>Registration Failed</from><to>Inscription Échouée</to></translation>
+<translation><from>Related Help Article</from><to>Article d'Aide Connexe</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>Rappelez-vous que l'action de bannir n'est pas efficace car l'utilisateur pourra toujours revenir avec un compte différent.</to></translation>
+<translation><from>Remove Ads</from><to>Retirer la Pub</to></translation>
+<translation><from>remove</from><to>retirer</to></translation>
+<translation><from>Remove Settings</from><to>Effacer les Configurations</to></translation>
+<translation><from>Remove Subscription</from><to>Annuler l'Abonnement</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>Supprimer votre compte et tous vos messages de <n.subject/>.</to></translation>
+<translation><from>Remove Your Account</from><to>Supprimer Votre Compte</to></translation>
+<translation><from>replies</from><to>réponses</to></translation><!-- noun / plural -->
+<translation><from>Replies</from><to>Réponses</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>Répondre</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>réponse</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>Répondre à l'auteur</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>Signaler un Contenu Inapproprié</to></translation>
+<translation><from>Report Now</from><to>Signaler maintenant</to></translation>
+<translation><from>required</from><to>obligatoire</to></translation>
+<translation><from>Return to <t.location/></from><to>Retour à <n.location/></to></translation>
+<translation><from>Save Changes</from><to>Enregistrer les Modifications</to></translation>
+<translation><from>Save Settings</from><to>Enregistrer les Configurations</to></translation>
+<translation><from>Save Subscription</from><to>Enregistrer l'Abonnement</to></translation>
+<translation><from>Search</from><to>Rechercher</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>Sélectionnez ci-dessous les actions que vous souhaitez effectuer :</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>Sélectionnez la catégorie qui décrit le mieux votre problème avec le contenu de cette page.</to></translation>
+<translation><from>Send email to me</from><to>Envoyez-moi un email</to></translation>
+<translation><from>Send Email to <t.author/></from><to>Envoyer un Email à <n.author/></to></translation>
+<translation><from>Send Request</from><to>Envoyer une Demande</to></translation>
+<translation><from>Send To:</from><to>Envoyer à :</to></translation>
+<translation><from>Sexual content</from><to>Contenu à caractère sexuel</to></translation>
+<translation><from>Show</from><to>Afficher</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>Afficher la notification de Nabble</to></translation>
+<translation><from>Sincerely,</from><to>Cordialement,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>Puisque cette application est un archivage de liste de diffusion, veuillez désabonner l'adresse email ci-dessous avant de cliquer sur le bouton supprimer.</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>Puisque vous n'êtes pas un utilisateur enregistré, nous devons vérifier que vous êtes humain.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>Quelques uns de vos messages ont été supprimés de <n.location/> et nous vous en envoyons une copie pour que vous ayez une chance de les sauvegarder.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>Désolé, mais seuls les membres peuvent poster des messages sur <n.app/>.</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>Désolé, mais les administrateurs vous ont banni.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>Désolé, mais cette adresse email n'est pas autorisée à voir les messages de <n.location/>.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>Désolé, mais vous ne pouvez pas créer de nouveaux sujets ici.<br/>Notez que vous devriez quand même pouvoir répondre aux messages.</to></translation>
+<translation><from>Sort by date</from><to>Trier par date</to></translation>
+<translation><from>Sort by relevance</from><to>Trier par pertinence</to></translation>
+<translation><from>Sorted by date</from><to>Trié par date</to></translation>
+<translation><from>Sorted by relevance</from><to>Trié par pertinence</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[Détecteur de Spam] Message invalide contenant trop de '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[Detector de Spam] Le message ne peut contenir '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[Détecteur de Spam] Le message contient des mots de spam courants.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[Détecteur de Spam] Le sujet ne peut contenir '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[Détecteur de Spam] Le sujet contient des mots de spam courants.</to></translation>
+<translation><from>Spam</from><to>Spam</to></translation>
+<translation><from>Stars in <t.location/></from><to>Favoris dans <n.location/></to></translation>
+<translation><from>Structure</from><to>Structure</to></translation>
+<translation><from>Subcategories</from><to>Sous-Catégories</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>Sous-catégories de <n.location/></to></translation>
+<translation><from>Subcategory</from><to>Sous-Catégorie</to></translation>
+<translation><from>Sub-Forum</from><to>Sous-Forum</to></translation>
+<translation><from>Sub-Forums</from><to>Sous-Forums</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>Sous-Forums & Sujets</to></translation>
+<translation><from>Subject</from><to>Sujet</to></translation>
+<translation><from>Subscribe</from><to>S'abonner</to></translation>
+<translation><from>subscriber</from><to>abonné</to></translation>
+<translation><from>subscribers</from><to>abonnés</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>S'abonner à <n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>S'abonner par email</to></translation>
+<translation><from>Subscription Confirmation</from><to>Confirmation d'Abonnement</to></translation>
+<translation><from>Subscription Confirmed</from><to>Abonnement Confirmé</to></translation>
+<translation><from>Subscription Format</from><to>Format d'Abonnement</to></translation>
+<translation><from>Subscription Removed</from><to>Abonnement Annulé</to></translation>
+<translation><from>Subscription Results</from><to>Résultats des Abonnements</to></translation>
+<translation><from>Subscription Type</from><to>Type d'Abonnement</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>Succès : un email de confirmation vous a été envoyé.</to></translation>
+<translation><from>Success</from><to>Succès</to></translation>
+<translation><from>Take Action</from><to>Prendre les mesures</to></translation>
+<translation><from><t.app/> Registration</from><to>Inscription à <n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to><n.author/> a été banni avec succès.</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to><n.author/> a été débanni avec succès.</to></translation>
+<translation><from>Tell me more & show examples</from><to>M'en dire plus & montrer des exemples</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>Merci de votre inscription à <n.app/>!</to></translation>
+<translation><from>Thank You</from><to>Merci</to></translation>
+<translation><from>The author has deleted this message.</from><to>L'auteur a supprimé ce message.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>Le code de l'URL n'est pas valide.</to></translation>
+<translation><from>the exact phrase:</from><to>l'expression exacte :</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>La liste de diffusion pourrait requérir votre abonnement avant d'accepter votre message. Veuillez noter qu'être inscrit sur Nabble n'implique PAS automatiquement que vous soyez abonné à la liste de diffusion. Si vous n'êtes pas encore abonné, vous pouvez le faire dès maintenant. Si vous n'êtes pas sûr ou si vous ne vous rappelez pas, il n'y a aucun mal à tenter de vous réabonner.</to></translation>
+<translation><from>The Nabble team</from><to>L'équipe de Nabble</to></translation>
+<translation><from>The name of the group is not valid.</from><to>Le nom du groupe n'est pas valide.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>Le nouveau parent ne peut pas être le message lui-même.</to></translation>
+<translation><from>The password fields don't match.</from><to>Vos mots de passe ne correspondent pas.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>Il existe un compte utilisateur non enregistré associé à l'adresse email <n.email/>.</to></translation>
+<translation><from>The subject is required.</from><to>Le sujet est obligatoire.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>L'utilisateur recevra une copie de tous ses messages supprimés par email afin qu'il/elle ait une chance de les sauvegarder.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>Le code de validation ne correspond pas à l'image.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>Cette branche est trop grande et des messages ont été omis. Utilisez les autres vues afin de lire tous les messages.</to></translation>
+<translation><from>This email is already subscribed.</from><to>Cette adresse email est déjà abonnée.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>Ce forum est une archive pour la liste de diffusion</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>Ce forum est une archive/passerelle qui transfèrera votre message à la liste de diffusion <b><n.mailing_list_address/></b>.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>Ceci inclut les sous-catégories, les messages, les images, les fichiers et tout le reste.</to></translation>
+<translation><from>This is a mailing list archive</from><to>Ceci est une archive de liste de diffusion</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>Ceci est un email automatique envoyé par Nabble afin de confirmer la création de votre <n.nouveau.app/>. Si vous n'avez pas créé <n.le_mentionne.app/> ci-dessus, veuillez nous contacter via le forum du Centre d'Aide Nabble.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>Cette liste accepte uniquement les emails au format texte</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>Cette liste répertorie les utilisateurs inscrits, non enregistrés et bannis. Les utilisateurs anonymes ne sont pas listés car ils n'ont pas d'adresse email et ne peuvent ainsi pas faire partie d'un groupe.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>Ce message sera envoyé depuis <b><n.from/></b> à la liste de diffusion <b><n.to/></b>.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to>Ce message n'a PAS ENCORE été accepté par la liste de diffusion.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>Ce message a été mis à jour le <n.date/>.</to></translation>
+<translation><from>This topic has been locked.</from><to>Ce sujet a été verrouillé.</to></translation>
+<translation><from>This topic has been pinned.</from><to>Ce sujet a été épinglé.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>Ce sujet a été épinglé dans <n.location/>.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>Ce sujet a été déverrouillé.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>Ce sujet a été détaché.</to></translation>
+<translation><from>This topic has unread posts</from><to>Ce sujet a des messages non lus</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>Ce sujet vous a été assigné à la priorité <n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>Cet utilisateur n'a pas la permission de voir cette application (ajoutez-le (la) à un groupe qui le permet et recommencez)</to></translation>
+<translation><from>This user name is already in use.</from><to>Ce nom d'utilisateur est déjà utilisé.</to></translation>
+<translation><from>Threaded</from><to>Arborescence</to></translation>
+<translation><from>Threaded View</from><to>Vue Arborescente</to></translation>
+<translation><from>Time</from><to>Heure</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>CONSEIL : Si votre archive est abonnée à la liste de diffusion et si elle ne marche toujours pas, vous pouvez essayer d'ignorer l'en-tête X-No-Archive.</to></translation>
+<translation><from>Tips</from><to>Conseils</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> demande l'autorisation de rejoindre <n.location/> :</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> Crédits</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> visualisations sans publicité.</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>Pour accepter cette demande, vous devriez ajouter cet utilisateur à au moins un groupe ayant accès à cette partie :</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>Pour ajouter <n.ce.app/> à votre site Internet, copier et coller le code suivant dans votre page HTML :</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>Pour confirmer votre abonnement, cliquez sur le lien ci-dessous :</to></translation>
+<translation><from>topic</from><to>sujet</to></translation>
+<translation><from>Topics and replies</from><to>Sujets et réponses</to></translation>
+<translation><from>topics</from><to>sujets</to></translation>
+<translation><from>Topics</from><to>Sujets</to></translation>
+<translation><from>Topics only</from><to>Sujets seulement</to></translation>
+<translation><from>Topics View</from><to>Aperçu des Sujets</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>Pour prévenir les courriers indésirables (spam), l'adresse email à utiliser pour poster un message est <b>unique</b> pour chaque utilisateur.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>Pour supprimer un groupe, videz l'espace de texte ci-dessous et enregistrez les modifications.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>Pour voir quelle adresse email vous devez utiliser pour poster, veuillez <n.login_link.>vous connecter</n.login_link.> ou <n.register_link.>vous inscrire</n.register_link.>.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>Pour créer un nouveau sujet dans <n.location/>, envoyez un email à <n.p2/></to></translation>
+<translation><from>Total</from><to>Total</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>Pour vous désabonner de <n.location/></to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> vous invite à vous abonner à <n.location/> :</to></translation>
+<translation><from>Turn off highlighting</from><to>Arrêter le surlignage</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/> a créé une nouvelle sous-catégorie</to></translation>
+<translation><from>Unable to Post</from><to>Impossible de Poster</to></translation>
+<translation><from>Unassigned</from><to>Non Assigné</to></translation>
+<translation><from>Unauthorized</from><to>Non Autorisé</to></translation>
+<translation><from>Unban this user</from><to>Débannir cet utilisateur</to></translation>
+<translation><from>Unban User</from><to>Débannir l'Utilisateur</to></translation>
+<translation><from>Unknown or Other</from><to>Inconnu ou Autre</to></translation>
+<translation><from>Unlock topic</from><to>Déverrouiller le sujet</to></translation>
+<translation><from>Unpin topic</from><to>Détacher le sujet</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>Non Enregistré / Désactivé</to></translation>
+<translation><from>Unregistered</from><to>Non Enregistré</to></translation>
+<translation><from>Unregistered User</from><to>Utilisateur Non Enregistré</to></translation>
+<translation><from>Unsubscribe</from><to>Se Désabonner</to></translation>
+<translation><from>Upload a file</from><to>Ajouter un fichier</to></translation>
+<translation><from>User email:</from><to>Adresse email de l'utilisateur :</to></translation>
+<translation><from>user</from><to>utilisateur</to></translation>
+<translation><from>User is online</from><to>L'utilisateur est connecté</to></translation>
+<translation><from>User Name</from><to>Nom d'Utilisateur</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>Un utilisateur demande l'autorisation de rejoindre <n.location/></to></translation>
+<translation><from>users</from><to>utilisateurs</to></translation>
+<translation><from>Users</from><to>Utilisateurs</to></translation>
+<translation><from>Users & Groups</from><to>Utilisateurs & Groupes</to></translation>
+<translation><from>Users that completed the registration process</from><to>Utilisateurs qui ont complété le processus d'inscription</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>Utilisez des balises telles que <n.example1/> ou <n.example2/> pour créer des en-têtes.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>Utilisez les options ci-dessous pour affiner vos critères de recherche.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>Utilisez <n.tag_names/> afin d'intégrer les widgets d'autres sites Internet.</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>Voir tous les messages de ce sous-forum</to></translation>
+<translation><from>view</from><to>visualisation</to></translation>
+<translation><from>View mailing list website</from><to>Voir le site Internet de la liste de diffusion</to></translation>
+<translation><from>View message</from><to>Voir le message</to></translation>
+<translation><from>View more</from><to>En voir plus</to></translation>
+<translation><from>views</from><to>visualisations</to></translation>
+<translation><from>Views</from><to>Visualisations</to></translation>
+<translation><from>Violent or repulsive content</from><to>Contenu violent ou repoussant</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>Visitez <n.app/> à :</to></translation>
+<translation><from>visit <t.url/></from><to>visitez <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>Nous avons préparé une page avec <n.unsubscription_instructions_link.>quelques instructions sur comment désabonner cette archive</n.unsubscription_instructions_link.>.</to></translation>
+<translation><from>We will review your report soon.</from><to>Nous examinerons votre rapport très bientôt.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>Quels groupes permettent aux membres d'être listés</to></translation>
+<translation><from>Who can ban/unban users</from><to>Qui peut bannir/débannir des utilisateurs</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>Qui peut être assigné à des sujets (uniquement dans les groupes de travail)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>Qui peut changer la date et l'heure des messages</to></translation>
+<translation><from>Who can create new topics under this application</from><to>Qui peut créer de nouveaux sujets dans cette application</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>Qui peut créer des sous-applications (par ex., des sous-forums, des sous-catégories, etc.)</to></translation>
+<translation><from>Who can edit any content, both applications and posts.  Note: Please only use this feature in extreme circumstances.  Most users will not like having their posts edited by someone else.</from><to>Qui peut éditer n'importe quel contenu, à la fois les applications et les messages. Note : Utilisez uniquement cette fonctionnalité dans des circonstances extrêmes. La plupart des utilisateurs n'aimeront pas voir leurs messages édités par quelqu'un d'autre.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>Qui peut éditer des applications (par ex., changer le nom, la description, etc.)</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>Qui peut verrouiller/déverrouiller les sujets dans cette application</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>Qui peut gérer les abonnés de cette application</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>Qui peut déplacer les messages vers d'autres destinations (par ex., vers d'autres sujets ou sous-forums)</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>Qui peut épingler/détacher les sujets dans cette application</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>Qui peut poster n'importe quel contenu sans restriction (y compris du code javascript, des balises &lt;object&gt; et &lt;style&gt;, etc.). <b>Alerte de Sécurité</b> : Autorisez uniquement cette option aux utilisateurs à qui vous faites réellement confiance.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>Qui peut répondre aux messages postés dans cette application</to></translation>
+<translation><from>Who can view this application and its contents</from><to>Qui peut voir cette application et son contenu</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>Avec votre abonnement, les mises à jour seront envoyées directement à votre adresse email et vous pouvez y répondre afin de participer à la discussion. Votre abonnement fonctionne de la même manière qu'une liste de diffusion.</to></translation>
+<translation><from>Write Your First Headline</from><to>Écrivez Votre Premier Titre</to></translation>
+<translation><from>Write Your First Post</from><to>Écrivez Votre Premier Message</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>Oui, supprimez <n.location/> pour toujours</to></translation>
+<translation><from>Yes, unsubscribe now</from><to>Oui, se désabonner maintenant</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>Vous êtes déjà abonné à <n.location/>.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>Vous n'êtes pas abonné à <n.location/>.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>Vous pouvez aussi <n.manage_banned_users_link.>gérer les utilisateurs bannis</n.manage_banned_users_link.> de <n.location/>.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>Vous pouvez aussi <n.root_node.change_permissions_link.>changer les permissions</n.root_node.change_permissions_link.> des groupes ci-dessous.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>Vous pouvez aussi promouvoir votre <n.app/> en envoyant le lien à vos amis, en l'intégrant à votre site Internet ou en en parlant sur d'autres forums.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>Vous ne pouvez pas déplacer ce message vers cette destination car le nouveau parent n'autorise pas les utilisateurs anonymes. </to></translation>
+<translation><from>You Cannot Post Here</from><to>Vous Ne Pouvez Pas Poster Ici</to></translation>
+<translation><from>(you can reply by email)</from><to>(vous pouvez répondre par email)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>Vous ne pouvez déplacer le message nulle part.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>Vous ne pouvez pas poster de message ici, mais vous pouvez en poster à d'autres endroits.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>Vous pouvez tenter de <n.register_link.>vous inscrire à nouveau</n.register_link.> ou contacter <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>Vous pouvez utiliser le formulaire ci-dessous pour envoyer votre demande aux administrateurs.</to></translation>
+<translation><from>You have already been registered.</from><to>Vous êtes déjà inscrit.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>Vous avez été invité à vous inscrire dans  <n.location/>, qui est disponible à :</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>Vous avez été inscrit dans <n.subject/>.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>Vous avez été désabonné de <n.location/></to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>Vous avez posté beaucoup trop de messages en un court laps de temps. Merci de réessayer plus tard.</to></translation>
+<translation><from>You logged out</from><to>Vous vous êtes déconnecté</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>Vous pourriez avoir besoin de <n.mailing_list_options_link.>vous abonner à cette liste de diffusion</n.mailing_list_options_link.> pour que vos messages soient acceptés.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>Vous pouvez  <n.page_node.unauthorized_link.>demander la permission de poster</n.page_node.unauthorized_link.> ici ou contacter <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> si vous avez des questions.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>Vous devez accepter les Conditions d'Utilisation.</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>Vous devez entrer une adresse email valide pour cette liste de diffusion.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>Vous devez entrer une URL de site Internet valide pour cette liste de diffusion.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>Vous devez remplir tous les champs ci-dessous.</to></translation>
+<translation><from>You must login to view this page.</from><to>Vous devez vous connecter pour voir cette page.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>Vous devez vous connecter pour voir <n.subject/>.</to></translation>
+<translation><from>You must login to your account.</from><to>Vous devez vous connecter à votre compte.</to></translation>
+<translation><from>You must provide a user name.</from><to>Vous devez entrer un nom d'utilisateur.</to></translation>
+<translation><from>You're not a subscriber</from><to>Vous n'êtes pas un abonné</to></translation>
+<translation><from>Your Name</from><to>Votre Nom</to></translation>
+<translation><from>Your request has been successfully sent.</from><to>Votre demande a été envoyée avec succès.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>Votre abonnement a été enregistré avec succès.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>Votre abonnement à <n.location/> a été annulé. S'il s'agit d'une erreur, vous pouvez vous réabonner en suivant le lien ci-dessous :</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>Votre abonnement à <n.location/> a été annulé avec succès.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to>Votre <n.a_ete_cree.app/> avec succès.</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>Vous aurez besoin d'une autorisation pour poster de nouveaux sujets dans <n.location/>, ainsi, en plus de votre inscription, vous devrez être approuvé par les administrateurs.</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>Vous recevrez un email pour chaque nouveau message posté dans ce sujet.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>Vous recevrez un email avec un lien pour activer votre compte.</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>Votre abonnement</to></translation>
+<translation><from>edit</from><to>éditer</to></translation>
+<translation><from>Remove ads</from><to>Retirer la pub</to></translation>
+<translation><from>View profile of <t.author/></from><to>Voir le profil de <n.author/></to></translation>
+<translation><from>Description</from><to>Description</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>Vidéos de Youtube, Vimeo et LiveLeak.</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>Utilisateurs Bannis de <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>Préfixe du Sujet</to></translation>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_pl.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,783 @@
+## Polish (Polski)
+
+## MONTHS ##
+
+<translation><from>January</from><to>styczeń</to></translation>
+<translation><from>February</from><to>luty</to></translation>
+<translation><from>March</from><to>marzec</to></translation>
+<translation><from>April</from><to>kwiecień</to></translation>
+<translation><from>May</from><to>maj</to></translation>
+<translation><from>June</from><to>czerwiec</to></translation>
+<translation><from>July</from><to>lipiec</to></translation>
+<translation><from>August</from><to>sierpień</to></translation>
+<translation><from>September</from><to>wrzesień</to></translation>
+<translation><from>October</from><to>pażdziernik</to></translation>
+<translation><from>November</from><to>listopad</to></translation>
+<translation><from>December</from><to>grudzień</to></translation>
+
+<translation><from>Jan</from><to>sty</to></translation>
+<translation><from>Feb</from><to>lut</to></translation>
+<translation><from>Mar</from><to>mar</to></translation>
+<translation><from>Apr</from><to>kwi</to></translation>
+<!--translation><from>May</from><to>maj</to></translation-->
+<translation><from>Jun</from><to>cze</to></translation>
+<translation><from>Jul</from><to>lip</to></translation>
+<translation><from>Aug</from><to>sie</to></translation>
+<translation><from>Sep</from><to>wrz</to></translation>
+<translation><from>Oct</from><to>paż</to></translation>
+<translation><from>Nov</from><to>lis</to></translation>
+<translation><from>Dec</from><to>gru</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>Nadużywanie tej funkcji jest także naruszeniem warunków użytkowania.</to></translation>
+<translation><from>Access Request</from><to>Żądanie dostępu</to></translation>
+<translation><from>Account settings</from><to>Ustawienia konta</to></translation>
+<translation><from>Account Settings</from><to>Ustawienia Konta</to></translation>
+<translation><from>Action</from><to>Działanie</to></translation>
+<translation><from>Add a link to another page</from><to>Dodaj link do innej strony</to></translation>
+<translation><from>Add a new comment</from><to>Dodaj nowy komentarz</to></translation>
+<translation><from>Add a new group</from><to>Dodaj nową grupę</to></translation>
+<translation><from>Add an image to your post</from><to>Dodaj obrazek do posta</to></translation>
+<translation><from>Add another address</from><to>Dodaj inny adres</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>Dodaj zawartość, która nie powinna być kodowana (np. kod)</to></translation>
+<translation><from>Adding Sub-Headers</from><to>Dodawanie podtytułów</to></translation>
+<translation><from>Add New Group</from><to>Dodaj Nową Grupę</to></translation>
+<translation><from>Add / Remove Groups</from><to>Dodaj / Usuń grupy</to></translation>
+<translation><from>Add smileys and funny animations</from><to>Dodaj buźki i śmieszne animacje</to></translation>
+<translation><from>Add Subscribers</from><to>Dodaj subskrybentów</to></translation>
+<translation><from>Add this item to your favorites list</from><to>Dodaj ten element do ulubionych</to></translation>
+<translation><from>Administrator</from><to>Administrator</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity, other sexual content.</from><to>Zawartość dla dorosłych, wyuzdane sceny seksualne, nagość, inna zawartość seksualna.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>Walki dorosłych, atak fizyczny, przemoc młodocianych, wykorzystywanie zwierząt lub promocja terroryzmu.</to></translation>
+<translation><from>Advanced Search</from><to>Wyszukiwanie zaawansowane</to></translation>
+<translation><from>Advanced Settings</from><to>Ustawienia zaawansowane</to></translation>
+<translation><from>Advertisement</from><to>Reklama</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>Powiadom mnie przez email, jeżeli ktoś napisze w tym wątku</to></translation>
+<translation><from>All</from><to>Wszystkie</to></translation>
+<translation><from>all of the words:</from><to>wszystkie słowa:</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>Wszystkie posty napisane przez <n.author/> zostały pomyślnie usunięte.</to></translation>
+<translation><from>All posts</from><to>Wszystkie posty</to></translation>
+<translation><from>All users belong to this group</from><to>Wszyscy użytkownicy należą do tej grupy</to></translation>
+<translation><from>All Users</from><to>Wszyscy użytkownicy</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>Wszyscy użytkownicy zarejestrowani na <n.location/>. Ci użytkownicy potwierdzili swoje adresy email i mogą się zalogować.</to></translation>
+<translation><from>Already Subscribed</from><to>Już to subskrybujesz</to></translation>
+<translation><from>An email has been sent to you.</from><to>Wysłaliśmy do Ciebie email.</to></translation>
+<translation><from>Anonymous</from><to>Anonimowy</to></translation>
+<translation><from>anonymous user</from><to>anonimowy użytkownik</to></translation>
+<translation><from>anonymous users</from><to>anonimowi użytkownicy</to></translation>
+<translation><from>Any message part contains</from><to>Każda z części wiadomości zawiera </to></translation>
+<translation><from>Application</from><to>Aplikacja</to></translation>
+<translation><from>Apps</from><to>Aplikacje</to></translation>
+<translation><from>Assignee</from><to>Beneficjent</to></translation>
+<translation><from>Assign</from><to>Przydzielać</to></translation>
+<translation><from>Assignment</from><to>Przydział</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>Zgodnie z prośbą, nowy adres email do publikowania nowych tematów w  <n.app/> to:</to></translation>
+<translation><from>at least one of the words:</from><to>co najmniej jedno ze słów:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to>Kanał postów dla <n.location/></to></translation>
+<translation><from>at priority</from><to>wg. priorytetu</to></translation>
+<translation><from>Authorized Users Only</from><to>Tylko uprawnieni użytkownicy</to></translation>
+<translation><from>Author name</from><to>Imię i nazwisko autora</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>Unikaj fraz typu "Dziękuję", "Świetnie"... Możesz <n.page_node.reply_to_author_link.>wysłać prywatną wiadomość</n.page_node.reply_to_author_link.> jeżeli sobie życzysz.</to></translation>
+<translation><from>Banned User</from><to>Zabanowany</to></translation>
+<translation><from>Ban this user</from><to>Banuj tego  użytkownika</to></translation>
+<translation><from>Ban User</from><to>Banuj użytkownika</to></translation>
+<translation><from>Przed usunięciem tego archiwum...</from><to>Antes de apagar este arquivo...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>Poniżej można zarządzać grupami i użytkownikami. Można kopiować i wklejać użytkowników w celu przeniesienia ich z grupy do grupy.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>WAŻNE</b>: Nabble wyśle zaproszenie na każdy adres email na liście. Użytkownicy będą musieli kliknąć link, aby potwierdzić subskrypcję.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>WAŻNE:</b> Ta subskrypcja różni się od zwykłej listy subskrypcji. Generalnie, subskrypcja dotyczy archiwum forum, a nie listy mailingowej. Subskrypcja archiwum nie gwarantuje, że wiadomości będą akceptowane przez listę mailingową.</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>WAŻNE</b>: Aby to archiwum działało należy je zasubskrybować na liście mailingowej.</to></translation>
+<translation><from>blog</from><to>blog</to></translation>
+<translation><from>Blog</from><to>Blog</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>Uwaga</b>: Będąc administratorem, możesz <n.page_node.change_permissions_link.>zmieniać uprawnienia do <n.location/></n.page_node.change_permissions_link.>, upewnij się też, że możesz tworzyć tematy.</to></translation>
+<translation><from>board</from><to>panel</to></translation>
+<translation><from>Board</from><to>Panel</to></translation>
+<translation><from>Bold</from><to>Pogr.</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>Ostrzeżenie:</b> Indeks wyszukiwania jest właśnie odbudowywany. Wyniki wyszukiwania mogą być niekompletne.</to></translation>
+<translation><from>by <t.author/></from><to>napisany przez <n.author/></to></translation>
+<translation><from>Cancel</from><to>Anuluj</to></translation>
+<translation><from>category</from><to>kategoria</to></translation>
+<translation><from>Category</from><to>Kategoria</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>OSTROŻNIE: to działanie jest nieodwracalne.</to></translation>
+<translation><from>Change appearance</from><to>Zmień wygląd</to></translation>
+<translation><from>Change application type</from><to>Zmień rodzaj aplikacji</to></translation>
+<translation><from>Change Application Type</from><to>Zmień rodzaj aplikacji</to></translation>
+<translation><from>Change code image</from><to>Zmień kod obrazka</to></translation>
+<translation><from>Change domain name</from><to>Zmień nazwę domeny</to></translation>
+<translation><from>Change language</from><to>Zmień język</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>Zmień lub usuń awatar.</to></translation>
+<translation><from>Change parent</from><to>Zmień element nadrzędny</to></translation>
+<translation><from>Change permissions</from><to>Zmień uprawnienia</to></translation>
+<translation><from>Change Permissions</from><to>Zmień uprawnienia</to></translation>
+<translation><from>Change post date</from><to>Zmień datę posta</to></translation>
+<translation><from>Change Post Date</from><to>Zmień datę posta</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>Zmień sygnaturkę wyświetlaną pod Twoimi postami.</to></translation>
+<translation><from>Change User Groups</from><to>Zmień grupę użytkowników</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>Zmień preferencje wyświetlania i inne ustawienia.</to></translation>
+<translation><from>Change Your Picture</from><to>Zmień swój obrazek</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>Zmień zarejestrowany adres email.</to></translation>
+<translation><from>Child abuse</from><to>Wykorzystywanie dzieci</to></translation>
+<translation><from>Choose a subcategory</from><to>Wybierz podkategorię</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>Wybierz podkategorię, aby opublikować posta</to></translation>
+<translation><from>Choose the best offer for you</from><to>Wybierz najlepszą ofertę dla siebie</to></translation>
+<translation><from>Classic</from><to>Klasyczny</to></translation>
+<translation><from>Clear Log</from><to>Wyczyść dziennik</to></translation>
+<translation><from>Click for more options</from><to>Kliknij, aby uzyskać więcej opcji</to></translation>
+<translation><from>click here</from><to>kliknij tutaj</to></translation>
+<translation><from>Click here to make your first post</from><to>Kliknij tutaj, aby napisać swój pierwszy post</to></translation>
+<translation><from>Click to filter</from><to>kliknij, aby przefiltrować</to></translation>
+<translation><from>Close</from><to>Zamknij</to></translation>
+<translation><from>Close this message</from><to>Zamknij tę wiadomość</to></translation>
+<translation><from>comment</from><to>komentarz</to></translation>
+<translation><from>Comment</from><to>Komentarz</to></translation>
+<translation><from>comments</from><to>komentarze</to></translation>
+<translation><from>Confirm Password</from><to>Potwierdź hasło</to></translation>
+<translation><from>Congratulations!</from><to>Gratulacje!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>Gratulujemy nowej<n.app/>!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>Zawartość promująca nienawiść lub przemoc, wykorzystywanie narażonych osób, atakowanie, nietolerancje rasową lub świadczenie przeciwko osobie, grupie, firmie, a także nadmierną świeckość. </to></translation>
+<translation><from>CONTENTS DELETED</from><to>ZAWARTOŚCI USUNIĘTE</to></translation>
+<translation><from>Continue</from><to>Kontynuuj</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>Naruszenie praw autorskich, prywatności lub inne zażalenie prawne.</to></translation>
+<translation><from>Count</from><to>Liczba</to></translation>
+<translation><from>Created by <t.author/></from><to>Utworzone przez <n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>Utwórz nowej<n.element/></to></translation>
+<translation><from>Create <t.element/></from><to>Utwórz <n.element/></to></translation>
+<translation><from>Current Credits</from><to>Aktualnie przypisane</to></translation>
+<translation><from>Currently Nabble supports</from><to>Aktualnie Nabble wspiera</to></translation>
+<translation><from>Current Subscribers</from><to>Aktualni subskrybenci</to></translation>
+<translation><from>Daily digest</from><to>Codzienny przegląd</to></translation>
+<translation><from>Data successfully saved</from><to>Dane zapisane pomyślnie</to></translation>
+<translation><from>Date</from><to>Data</to></translation>
+<translation><from>days</from><to>dni</to></translation>
+<translation><from>Dear <t.name/>,</from><to>Drogi(a) <n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>Użytkowniku,</to></translation>
+<translation><from>Default</from><to>Domyślny</to></translation>
+<translation><from>Delete all posts from this user</from><to>Usuń wszystkie posty tego użytkownika</to></translation>
+<translation><from>Delete Application</from><to>Usuń aplikację</to></translation>
+<translation><from>Deleted posts</from><to>Usunięte posty</to></translation>
+<translation><from>Delete</from><to>Usuń</to></translation>
+<translation><from>Delete this post and replies</from><to>Usuń ten post i odpowiedzi</to></translation>
+<translation><from>Delete this post</from><to>Usuń ten post</to></translation>
+<translation><from>Delete this topic</from><to>Usuń ten temat</to></translation>
+<translation><from>Description is in HTML Format</from><to>Opis w HTML</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>Nie postuj raz za razem. Poczekaj kilka dni. Użytkownicy dostaną Twoją wiadomość emailem.</to></translation>
+<translation><from>Don't show this message again</from><to>Nie pokazuj więcej tej wiadomości</to></translation>
+<translation><from>Download archives</from><to>Archiwum pobrań</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>Czy naprawdę chcesz usnąć tę wiadomość?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>Czy naprawdę chcesz usunąć tę wiadomość i odpowiedzi na stałe?</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>Czy naprawdę chcesz <n.important.>usunąć</n.important.> <n.location/>?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>Czy naprawdę chcesz usunąć ustawienia archiwum listy mailingowej?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>Czy naprawdę chcesz subskrybować <n.location/>?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>Czy naprawdę chcesz usunąć bana tego użytkownika?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>Czy naprawdę chcesz usunąć subskrypcję <n.location/>?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>Używanie narkotyków oraz paranarkotyków, używanie ognia i materiałów wybuchowych, sprzedaż tytoniu, alkoholu i produktów powiązanych, broni, amunicji lub inne działania niebezpieczne.</to></translation>
+<translation><from>Edit name & description</from><to>Edytuj nazwę i opis</to></translation>
+<translation><from>Edit Name & Description</from><to>Edytuj Nazwę i Opis</to></translation>
+<translation><from>Editor</from><to>Edytor</to></translation>
+<translation><from>Edit Personal Information</from><to>Edytuj dane osobowe</to></translation>
+<translation><from>Edit post</from><to>Edytuj post</to></translation>
+<translation><from>Edit Subscription</from><to>Edytuj subskrypcję</to></translation>
+<translation><from>Edit Your Signature</from><to>Edytuj sygnaturkę</to></translation>
+<translation><from>Email Confirmation</from><to>Wyślij potwierdzenie</to></translation>
+<translation><from>Email for <t.app/></from><to>Email dla <n.app/></to></translation>
+<translation><from>Email</from><to>Email</to></translation>
+<translation><from>Email Subscription</from><to>Subskrypcja Email</to></translation>
+<translation><from>Email this post to...</from><to>Wyślij ten post do...</to></translation>
+<translation><from>Embedding Contents</from><to>Osadzanie zawartości</to></translation>
+<translation><from>Embedding options</from><to>Opcje osadzania</to></translation>
+<translation><from>Embed</from><to>Osadź</to></translation>
+<translation><from>Embed post</from><to>Osadź post</to></translation>
+<translation><from>Embed Tags</from><to>Tagi osadzania</to></translation>
+<translation><from>Embed this <t.app/></from><to>Osadź łącze do <n.app/></to></translation>
+<translation><from>Empty</from><to>Pusty</to></translation>
+<translation><from>Enter a valid email address.</from><to>Wprowadź prawidłowy adres email.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>Wprowadź poniżej adres email, a wyślemy Ci wiadomość potwierdzającą.</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>Wprowadź jeden adres email lub nazwę użytkownika na wiersz:</to></translation>
+<translation><from>Enter one user per row</from><to>Wprowadź jedną nazwę użytkownika na wiersz</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>Wprowadź odnośnik bezpośredni dla <b>posta</b> lub <b>forum</b>, czyli nowego elementu nadrzędnego, lub zostaw pole puste, a wiadomość zostanie niezależnym tematem:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>Wprowadź stronę główną dla tej listy mailingowej, na której użytkownicy znajdą więcej informacji.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>Przed tematem wprowadź prefiks używany dla tej listy mailingowej. Prefiks zostanie automatycznie usunięty z importowanych wiadomości email.</to></translation>
+<translation><from>Enter your email address</from><to>Wpisz swój adres email</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>Przykład: jan_kowalski@domena.pl</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>Przykład: nazwa_listy@domena_listy.pl</to></translation>
+<translation><from>Examples:</from><to>Przykłady:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>Przykłady: '[lista]', 'Abc:', itd.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>Wyjaśnij administratorowi dlaczego chcesz uzyskać dostęp do obszaru zastrzeżonego.</to></translation>
+<translation><from>Explanation from this user:</from><to>Wyjaśnienie od użytkownika:</to></translation>
+<translation><from>Extras & add-ons</from><to>Dodatki i wtyczki</to></translation>
+<translation><from>Failed</from><to>Niepowodzenie</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>Ulubione (kliknij, żeby usunąć ten element z listy ulubionych)</to></translation>
+<translation><from>Feeds</from><to>Kanały</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>Plik nie został przesłany: <n.file/> (prześlij go ponownie lub usuń tag)</to></translation>
+<translation><from>Filter by group</from><to>Filtruj wg. grup</to></translation>
+<translation><from>Floating sub-forum</from><to>Pływający dział forum</to></translation>
+<translation><from>Forgot Password?</from><to>Nie pamiętasz hasła?</to></translation>
+<translation><from>Forgot your password?</from><to>Zapomniane hasło?</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>Więcej informacji, patrz: <n.info/></to></translation>
+<translation><from>forum</from><to>forum</to></translation>
+<translation><from>Forum</from><to>Forum</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to><n.app/> Darmowe osadzanie</to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>Od teraz będziesz otrzymywać email po każdej wiadomości opublikowanej w <n.location/>.</to></translation>
+<translation><from>gallery</from><to>galeria</to></translation>
+<translation><from>Gallery</from><to>Galeria</to></translation>
+<translation><from>Gambling or casino-related content</from><to>Hazard lub zawartość powiązana z kasynem</to></translation>
+<translation><from>Go back</from><to>Wróć</to></translation>
+<translation><from>Go to next message</from><to>Przejdź do kolejnej wiadomości</to></translation>
+<translation><from>Group Name:</from><to>Nazwa grupy:</to></translation>
+<translation><from>Groups</from><to>Grupy</to></translation>
+<translation><from>Groups of this user</from><to>Grupy tego użytkownika</to></translation>
+<translation><from>Hacking / cracking</from><to>Hackowanie / Przestępstwa cyfrowe</to></translation>
+<translation><from>Harmful dangerous acts</from><to>Szkodliwe, niebezpieczne działania</to></translation>
+<translation><from>Hateful or abusive content</from><to>Zawartość promująca nienawiść i uwłaczanie</to></translation>
+<translation><from>Help</from><to>Pomoc</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>Tutaj można zakupić kredyty, które umożliwią przeglądanie aplikacji Nabble bez reklam. Każdy kredyt to odsłona bez reklamy. Odwiedzający będą oglądać strony bez reklam jezeli na koncie będą znajdować się kredyty. Nabble zacznie ponownie wyświetlać reklamy, kiedy liczba kredytów spadnie do zera.</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>Tutaj można ukryć obecna listę mailingową przed użytkownikami. Opcja ta umozliwia zastapienie serwera z lista mailingową funkcją Nabble.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>Ukryj adres email (na przykład, użytkownik@host.com <n.lt/>ikryty email<n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>Ukryj email</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>Ukryj archiwum danych listy mailingowej przed użytkownikami</to></translation>
+<translation><from>Highest</from><to>Najwyższy</to></translation>
+<translation><from>High</from><to>Wysoki</to></translation>
+<translation><from>Hi <t.name/>,</from><to>Cześć <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>Jezli ten adres email należy do Ciebie musisz się <n.register_link.>zarejestrować</n.register_link.> używając tego samego adresu. Po rejestracji będziesz właścicielem konta użytkownika.</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>Jezli nie otrzymujesz mailingu z Nabble, prosimy sprawdzić folder SPAM.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>Jeżeli publikujesz pytanie, prosimy najpierw spróbować wyszukiwania. Odpowiedź na Twoje pytanie mogła już zostać opublikowana.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>Jeżeli nie jesteś użytkownikiem możesz <n.register_link.><b>zarejestrować się teraz</b></n.register_link.>.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>Jeżeli zabanujesz tego użytkownika nie będzie mógł nic zrobić w <n.location/>.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>Jeżeli nie zgłaszano prośby o przysłanie tej wiadomości email lub nie było powodu, aby ją otrzymać należy ją zignorować. Może być to błąd zgłoszenia przez kogoś innego.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>Jeżeli nie chcesz się jeszcze rejestrować, wprowadź po prostu email, z którego chcesz publikować, a wszystkie dane zostaną przesłane pocztą email.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>Po usunięciu subskrypcji, można dokonać pełnego usunięcia.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>Jeżeli posiadasz takie informacje, wprowadź aplikację listy mailingowej i jej wersję.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>Jeżeli wylogowanie nastąpiło przez pomyłkę <n.login_link.>zaloguj się ponownie</n.login_link.> novamente.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>Jeżeli odpowiesz na ten email, Twoja odpowiedź zostanie dodana do poniższej dyskusji.</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>Jeżeli usuniesz blokadę użytkownika ponownie będzie w stanie publikować w <n.location/>.</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>Jeżeli chcesz się zgłosić do listy mailingowej, <n.mailing_list_options_link.>odwiedź tę stronę</n.mailing_list_options_link.>.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>Ignoruj nagłówek X-No-Archive</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>Zapoznałe(a)m się i zgadzam z <n.terms_link.>Warunkami użytkowania</n.terms_link.> Nabble.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>Obraz nie został przesłany: <n.image/> (prześlij go ponownie lub usuń tag)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>Mam subskrypcję pozwól mi na publikowanie teraz</to></translation>
+<translation><from>Incorrect Login!</from><to>Niewłaściwe dane logowania!</to></translation>
+<translation><from>Individual emails</from><to>Indywidualne emaile</to></translation>
+<translation><from>Inherit</from><to>Odziedzicz</to></translation>
+<translation><from>In Reply To</from><to>W odpowiedzi na</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>W odpowiedzi na <n.parent_link.>pojawiła się wiadomość</n.parent_link.> opublikowana przez <n.author/></to></translation>
+<translation><from>Insert</from><to>Wstaw</to></translation>
+<translation><from>Insert Image</from><to>Wstaw obraz</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>Zamiast publikować przez interfejs sieciowy, można publikować wiadomości wysyłając je na ten adres email :</to></translation>
+<translation><from>in <t.location/></from><to>w <n.location/></to></translation>
+<translation><from>Invalid Code</from><to>Niewłaściwy kod</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>Niewłaściwy adres email: <n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>Niewłaściwa liczba dni, musi być to liczba całkowita.</to></translation>
+<translation><from>invisible user</from><to>użytkownik niewidoczny</to></translation>
+<translation><from>invisible users</from><to>użytkownicy niewidoczni</to></translation>
+<translation><from>Invite Subscribers</from><to>Zapraszaj subskrybentów</to></translation>
+<translation><from>is:</from><to>jest:</to></translation>
+<translation><from>is not:</from><to>nie jest:</to></translation>
+<translation><from>is within the last:</from><to>w ciągu ostatnich:</to></translation>
+<translation><from>Italic</from><to>Pochyły</to></translation>
+<translation><from>item</from><to>element</to></translation>
+<translation><from>items</from><to>elementy</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>Późniejsze odzyskanie usuniętych elementów NIE jest możliwe.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>Po prostu wklej kod (podany powyżej) pomiędzy tagami i jesteś gotowy do publikowania.</to></translation>
+<translation><from>Last Post</from><to>Ostatni post</to></translation>
+<translation><from>Learn more</from><to>Dowiedz się więcej</to></translation>
+<translation><from>Leave a comment</from><to>Zostaw komentarz</to></translation>
+<translation><from>Link</from><to>Link</to></translation>
+<translation><from>Link to <t.location/></from><to>Link do <n.location/></to></translation>
+<translation><from>List</from><to>Lista</to></translation>
+<translation><from>List of Subcategories</from><to>Lista podkategorii</to></translation>
+<translation><from>List Server</from><to>Lista serwerów</to></translation>
+<translation><from>List View</from><to>Widok listy</to></translation>
+<translation><from>Loading...</from><to>ładowanie...</to></translation>
+<translation><from>Location</from><to>lokalizacja</to></translation>
+<translation><from>Locked</from><to>Zablokowano</to></translation>
+<translation><from>Lock topic</from><to>Zablokuj temat</to></translation>
+<translation><from>Login</from><to>Zaloguj</to></translation>
+<translation><from>Log is empty</from><to>Dziennik jest pusty</to></translation>
+<translation><from>Log out</from><to>Wyloguj</to></translation>
+<translation><from>Lowest</from><to>Najniższy</to></translation>
+<translation><from>Low</from><to>Niski</to></translation>
+<translation><from>Mailing List Address</from><to>Adres listy mailingowej</to></translation>
+<translation><from>Mailing list archive settings</from><to>Ustawienia archiwum listy mailingowej</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>Ustawienia archiwum listy mailingowej</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>Przypomnienie o subskrypcji listy mailingowej</to></translation>
+<translation><from>Mailing List Website</from><to>Strona internetowa listy mailingowej</to></translation>
+<translation><from>Main Page</from><to>Strona główna</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>Upewnij się, że używasz tej samej przeglądarki co podczas uzupełniania rejestracji.</to></translation>
+<translation><from>Manage banned users</from><to>Zarządzaj użytkownikami zabanowanymi</to></translation>
+<translation><from>Manage pinned topics</from><to>Zarządzaj tematami przypiętymi</to></translation>
+<translation><from>Manage subscribers</from><to>Zarządzaj subskrybentami</to></translation>
+<translation><from>Manage Subscribers</from><to>Zarządzaj subskrybentami</to></translation>
+<translation><from>Manage <t.items/></from><to>Zarządzaj<n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>Zarządzaj użytkownikami i grupami</to></translation>
+<translation><from>Manage Users & Groups</from><to>Zarządzaj użytkownikami i grupami</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>Masowa reklama, wprowadzające w błąd teksty, oszustwa i defraudacje.</to></translation>
+<translation><from>max. 80 characters</from><to>maks. 80 znaków</to></translation>
+<translation><from>Message date</from><to>Data wiadomości</to></translation>
+<translation><from>message</from><to>wiadomość</to></translation>
+<translation><from>Message</from><to>Wiadomość</to></translation>
+<translation><from>Message is in HTML Format</from><to>Wiadomość w formacie HTML</to></translation>
+<translation><from>messages</from><to>wiadomości</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>Opublikowanie tu wiadomości będą wysyłane do listy mailingowej.</to></translation>
+<translation><from>Message subject contains</from><to>Temat wiadomości zawiera</to></translation>
+<translation><from>Message text contains</from><to>Tekst wiadomości zawiera</to></translation>
+<translation><from>Mixed</from><to>Różne</to></translation>
+<translation><from>Modified</from><to>Zmodyfikowano</to></translation>
+<translation><from>Archives</from><to>Archiwum</to></translation>
+<translation><from>More Categories</from><to>Więcej kategorii</to></translation>
+<translation><from>More</from><to>Więcej</to></translation>
+<translation><from>more help</from><to>dodatkowa pomoc</to></translation>
+<translation><from>more options</from><to>więcej opcji</to></translation>
+<translation><from>Move post</from><to>Przenieś post</to></translation>
+<translation><from>Move Post</from><to>Przenieś post</to></translation>
+<translation><from>Move topic</from><to>Przenieś temat</to></translation>
+<translation><from>My Nabble Applications</from><to>Moje aplikacje Nabble</to></translation>
+<translation><from>My Pending Posts</from><to>Nieopublikowane posty</to></translation>
+<translation><from>My posts</from><to>Moje posty</to></translation>
+<translation><from>Nabble Support</from><to>Pomoc Nabble</to></translation>
+<translation><from>Name</from><to>Nazwa</to></translation>
+<translation><from>New Post</from><to>Nowy post</to></translation>
+<translation><from>news</from><to>aktualności</to></translation>
+<translation><from>News</from><to>Aktualności</to></translation>
+<translation><from>New Topic</from><to>Nowy temat</to></translation>
+<translation><from>New topics only</from><to>Tylko nowe tematy</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>OSTROŻNIE</n.important.>: Wszystkie elementy lokalizacji <n.location/> zostaną usunięte na zawsze!</to></translation>
+<translation><from>No banned users.</from><to>Brak zabanowanych użytkowników.</to></translation>
+<translation><from>No Filter</from><to>Brak filtra</to></translation>
+<translation><from>none of the words:</from><to>żadne ze słów:</to></translation>
+<translation><from>No registered user found with this email.</from><to>W wiadomości email nie znaleziono zarejestrowanego użytkownika.</to></translation>
+<translation><from>No replies</from><to>Brak odpowiedzi</to></translation>
+<translation><from>Normal</from><to>Zwykły</to></translation>
+<translation><from>No sub-forums</from><to>Brak forum podległego</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>Proszę zauważyć, że ten adres email jest unikalny dla użytkownika i przyjmuje wyłącznie wiadomości email <n.address/>. Powodem takiego projektu jest zapobieganie rozsyłaniu spamu.</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to><n.register_link.>Zarejestruj teraz</n.register_link.> jeżeli chcesz edytować profil, otrzymywać posty pocztą, kontrolować ulubione lub mieć dostęp do swojego globalnego profilu.</to></translation>
+<translation><from>one email per input box</from><to>jeden email na skrzynkę odbiorczą</to></translation>
+<translation><from>Online Users</from><to>Użytkownicy online</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>Tylko zarejestrowani użytkownicy mają dostęp do tej części.</to></translation>
+<translation><from>Open this post in classic view</from><to>Otwórz ten post w widoku klasycznym</to></translation>
+<translation><from>Open this post in list view</from><to>Otwórz ten post w widoku listy</to></translation>
+<translation><from>Open this post in threaded view</from><to>Otwórz ten post w widoku wątku</to></translation>
+<translation><from>Options</from><to>Opcje</to></translation>
+<translation><from>or</from><to>lub</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>Można też zignorować ten email, jeżeli lepiej jest trzymać tego użytkownika z dala od tej części.</to></translation>
+<translation><from>Other Settings</from><to>Inne ustawienia</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>Strona, która grupuje aplikacje podrzędne i dyskusje.</to></translation>
+<translation><from>Page <t.number/></from><to>Strona<n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>Strona z prostą listą aplikacji podrzędnych i dyskusji.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>Strona pełna wiadomości i powiązanych komentarzy.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>Strona z nagłówkami i postami.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>Strona z tematami i dyskusjami.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>Strona z tematami pogrupowanymi według aplikacji podrzędnych. Jeżeli nie istnieją aplikacje podrzędne wyświetlana jest zwykła lista tematów.</to></translation>
+<translation><from>Password</from><to>Hasło</to></translation>
+<translation><from>Password Sent</from><to>Hasło wysłane</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>Pedofilia, przemoc i inne nadużycia.</to></translation>
+<translation><from>People</from><to>Osoby</to></translation>
+<translation><from>People in <t.location/></from><to>Osoby w <n.location/></to></translation>
+<translation><from>Permalink</from><to>Link stały</to></translation>
+<translation><from>Photo and image gallery.</from><to>Galeria obrazów i zdjęć.</to></translation>
+<translation><from>Pinned sub-forum</from><to>Przypięte forum podrzędne</to></translation>
+<translation><from>Pin topic</from><to>Temat przypięty</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>Dodaj ten link do zakładek lub zachowaj link, aby z latwością odszukac swoja aplikację <n.app/> w przyszłości.</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>Sprawdź skrzynkę odbiorczą i aktywuj konto, aby mieć dostęp do wszystkich funkcji.</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>Kliknij poniższy link aktywacyjny, aby aktywować konto:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>Jeżeli potrzebujesz pomocy skontaktuj się ze wparciem Nabble.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>Jeżeli potrzebujesz pomocy skontaktuj się z administratorami.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>Wprowadź prawidłowy adres email i spróbuj jeszcze raz.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>Postępuj według instrukcji w wiadomości email, aby dokończyć proces rejestracji .</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>Proszę postępować zgodnie z <n.subscribe_instructions_link.>instrukcjami subskrypcji</n.subscribe_instructions_link.> dla tego archiwum.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>Proszę podać właściwy link stały.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>Proszę ponownie wpisać swój email lub hasło i klinąc Zaloguj.</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>Proszę postępować zgodnie z etykietą listy mailingowej. </to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>Ankiety z Polldaddy.com (tylko ankiety flash)</to></translation>
+<translation><from>Post by email</from><to>Publikuj przez email</to></translation>
+<translation><from>Post by Email</from><to>Publikuj przez Email</to></translation>
+<translation><from>Post Count</from><to>Ilość postów</to></translation>
+<translation><from>Posted by <t.author/></from><to>Opublikowane przez <n.author/></to></translation>
+<translation><from>post</from><to>post</to></translation>
+<translation><from>Post Message</from><to>Opublikuj wiadomość</to></translation>
+<translation><from>Post New Message</from><to>Opublikuj nową wiadomość</to></translation>
+<translation><from>Post new message in <t.location/></from><to>Opublikuj nową wiadomość w  <n.location/></to></translation>
+<translation><from>posts</from><to>posty</to></translation>
+<translation><from>Posts</from><to>Posty</to></translation>
+<translation><from>Posts in <t.location/></from><to>Posty w <n.location/></to></translation>
+<translation><from>Preview Message</from><to>Podgląd wiadomości</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>Ceny w dolarach amerykańskich. Transakcje sprzedaży realizowane są poprzez <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, co pozwala użytkownikom Amazon na wykorzystywanie metod płatności powiązanych z ich kontami Amazon.com i płacenie za towary i usługi na stronach internetowych i w aplikacjach obsługujących Amazon Payments.</to></translation>
+<translation><from>Print post</from><to>Drukuj wiadomość</to></translation>
+<translation><from>Priority</from><to>Priorytet</to></translation>
+<translation><from>(private)</from><to>(prywatna)</to></translation>
+<translation><from>Profile of <t.author/></from><to>Profil  <n.author/></to></translation>
+<translation><from>Quote</from><to>Cytuj</to></translation>
+<translation><from>Quote the original message</from><to>Cytuj wiadomość oryginalną</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>Cytuj tylko części, na które odpowiadasz i przcinaj tekst do odpowiednich rozmiarów. Pozwala to na zachowanie kontekstu dla osób czytajacych te wiadomości przez email.</to></translation>
+<translation><from>Raw mail</from><to>Oryginalny email</to></translation>
+<translation><from>Raw text</from><to>Tekst bez formatowania</to></translation>
+<translation><from>Read more</from><to>Czytaj dalej</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>Lista tylko do odczytu dla użytkowników z pocztą email pod  <n.location/>.</to></translation>
+<translation><from>Receive direct replies only.</from><to>Otrzymuj tylko odpowiedzi bezpośrednie.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>Otrzymuj wszystkie wiadomości publikowane w  <n.location/>.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>Otrzymuj wszystkie wiadomości publikowane w tym temacie.</to></translation>
+<translation><from>Receive new topics only.</from><to>Orztymuj tylko nowe tematy.</to></translation>
+<translation><from>Refresh</from><to>Odśwież</to></translation>
+<translation><from>Registered</from><to>Zarejestrowany</to></translation>
+<translation><from>Registered Users</from><to>Zarejestrowani użytkownicy</to></translation>
+<translation><from>Register</from><to>Zarejestruj</to></translation>
+<translation><from>Registering...</from><to>Rejestrowanie...</to></translation>
+<translation><from>Register Now</from><to>Zarejestruj teraz</to></translation>
+<translation><from>Register to <t.app/></from><to>Zarejestruj na <n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>Rejestracja potwierdzona</to></translation>
+<translation><from>Registration Failed</from><to>Rejestracja nieudana</to></translation>
+<translation><from>Related Help Article</from><to>Powiązany temat pomocy</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>Pamiętaj, że banowanie nie jest efektywne z uwagi na to, że użytkownik może zawsze powrócić pod inną nazwą .</to></translation>
+<translation><from>Remove Ads</from><to>Usuń reklamy</to></translation>
+<translation><from>remove</from><to>usuń</to></translation>
+<translation><from>Remove Settings</from><to>Usuń ustawienia</to></translation>
+<translation><from>Remove Subscription</from><to>Usuń subskrypcję</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>Usuń konto i wszystkie posty z tematu <n.subject/>.</to></translation>
+<translation><from>Remove Your Account</from><to>Usuń swoje konto</to></translation>
+<translation><from>replies</from><to>odpowiedzi</to></translation><!-- noun / plural -->
+<translation><from>Replies</from><to>Odpowiedzi</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>Odpowiedz</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>odpowiedź</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>Odpowiedz autorowi</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>Zgłośc zawartość jako nieodpowiednią</to></translation>
+<translation><from>Report Now</from><to>Zgłoś teraz</to></translation>
+<translation><from>required</from><to>wymagane</to></translation>
+<translation><from>Return to <t.location/></from><to>Wróć do <n.location/></to></translation>
+<translation><from>Save Changes</from><to>Zachowaj zmiany</to></translation>
+<translation><from>Save Settings</from><to>Zachowaj ustawienia</to></translation>
+<translation><from>Save Subscription</from><to>Zachowaj subskrypcję</to></translation>
+<translation><from>Search</from><to>Szukaj</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>Poniżej wybierz działania, które chcesz podjąć:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>Wybierz kategorię, która najpełniej odzwierciedla Twoje obawy dotyczące tej zawartości.</to></translation>
+<translation><from>Send email to me</from><to>Wyślij mi email</to></translation>
+<translation><from>Send Email to <t.author/></from><to>Wyslij email do <n.author/></to></translation>
+<translation><from>Send Request</from><to>Wyślij prośbę</to></translation>
+<translation><from>Send To:</from><to>Wyślij do:</to></translation>
+<translation><from>Sexual content</from><to>Zawartość seksualna</to></translation>
+<translation><from>Show</from><to>Pokaż</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>Pokaż informację od Nabble</to></translation>
+<translation><from>Sincerely,</from><to>Z poważaniem,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>Ponieważ ta aplikacja to archiwum listy mailingowej, prosimy usunąć subskrypcję adresu email przed wciśnięciem przycisku usuń.</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>Z uwagi na to, że nie jesteś zarejestrowanym użytkownikiem, musimy sprawdzić czy jesteś człowiekiem.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>Nektóre z Twoich postów zostały usuniete z  <n.location/> stąd wysyłamy Ci kopie, aby je zachować.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>Przepraszamy tylko użytkownicy mogą publikować pod <n.app/>.</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>Przepraszamy, ale został nałożony ban przez administratorów.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>Przepraszamy, ten email nie ma dostępu do przeglądania widomości w <n.location/>.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>Przepraszamy, ale nie posiadasz uprawnień do zakładania tematów w tej części.<br/>Zwróć jednak uwagę, że nadal możesz publikować odpowiedzi.</to></translation>
+<translation><from>Sort by date</from><to>Sortuj wg. dat </to></translation>
+<translation><from>Sort by relevance</from><to>Sortuj wg. związku</to></translation>
+<translation><from>Sorted by date</from><to>Posortowane wg. daty</to></translation>
+<translation><from>Sorted by relevance</from><to>Posortowane wg. związku</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[Detektor spamu] Niewłaściwa wiadomość zawierająca zbyt wiele '<n.text/>' słów.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[Detektor spamu] Wiadomość nie może zawierać '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[Detektor spamu] Wiadomość zawiera typowe dla spamu słownictwo.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[Detektor spamu] Temat nie może zawierać '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[Detektor spamu] Temat zawiera typowe słowa używane w spamie.</to></translation>
+<translation><from>Spam</from><to>Spam</to></translation>
+<translation><from>Stars in <t.location/></from><to>Ulubione w <n.location/></to></translation>
+<translation><from>Structure</from><to>Struktura</to></translation>
+<translation><from>Subcategories</from><to>Podkategorie</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>Podkategorie w <n.location/></to></translation>
+<translation><from>Subcategory</from><to>Subkategoria</to></translation>
+<translation><from>Sub-Forum</from><to>Forum podrzędne</to></translation>
+<translation><from>Sub-Forums</from><to>Fora podrzędne</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>Fora podrzędne i tematy</to></translation>
+<translation><from>Subject</from><to>Temat</to></translation>
+<translation><from>Subscribe</from><to>Subskrybuj</to></translation>
+<translation><from>subscriber</from><to>subskrybent</to></translation>
+<translation><from>subscribers</from><to>subskrybenci</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>Subskrybuj <n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>Subsrybuj przez email</to></translation>
+<translation><from>Subscription Confirmation</from><to>Potwierdzenie subskrypcji</to></translation>
+<translation><from>Subscription Confirmed</from><to>Subskrypcja potwierdzona</to></translation>
+<translation><from>Subscription Format</from><to>Format subskrypcji</to></translation>
+<translation><from>Subscription Removed</from><to>Subskrypcja usunięta</to></translation>
+<translation><from>Subscription Results</from><to>Wyniki subskrypcji</to></translation>
+<translation><from>Subscription Type</from><to>Typ subskrypcji</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>Powodzenie: wysłano Ci email potwierdzający.</to></translation>
+<translation><from>Success</from><to>Powodzenie</to></translation>
+<translation><from>Take Action</from><to>Podejmij działanie</to></translation>
+<translation><from><t.app/> Registration</from><to>Zarejestruj w <n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to>Użytkownik <n.author/> został z powodzeniem zabanowany.</to></translation>
+<translation><from>Tell me more & show examples</from><to>Powiedz mi więcej i pokaż przykłady</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>Dziękujemy za rejestrację w <n.app/>!</to></translation>
+<translation><from>Thank You</from><to>Dziękujemy</to></translation>
+<translation><from>The author has deleted this message.</from><to>Autor usunął wiadomość.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>Kod  w  URL jest niepoprawny.</to></translation>
+<translation><from>the exact phrase:</from><to>dokładna fraza:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>Lista mailingowa może wymagać subskrypcji przed zaakceptowaniem posta. Proszę pamiętać, że sama rejestracja w Nabble NIE oznacza automatycznej subskrypcji na liście mailingowej. Jeżeli nie posiadasz subskrypcji, zrób to teraz. Jeżeli nie masz pewności lub po prostu nie pamiętasz zapisz się jeszcze raz bo to nic nie szkodzi.</to></translation>
+<translation><from>The Nabble team</from><to>Zespół Nabble</to></translation>
+<translation><from>The name of the group is not valid.</from><to>Nazwa grupy jest niepoprawna.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>Nowy element nadrzędny nie może być postem.</to></translation>
+<translation><from>The password fields don't match.</from><to>Pola haseł nie pasują do siebie.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>Istnieje wyrejestrowane konto użytkownika powiazane z adresem <n.email/>.</to></translation>
+<translation><from>The subject is required.</from><to>Wpisanie tematu jest obowiązkowe.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>Użytkownik otrzyma kopię usuniętych postów w wiadomości email, aby mógł je zachować.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>Kod potwierdzający nie pasuje do obrazka.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>Ta gałąź jest zbyt duża i niektóre posty zostały pominięte. Użyj innego widoku, aby przeczytać wszystkie posty.</to></translation>
+<translation><from>This email is already subscribed.</from><to>Ten email jest już na liście subskrypcji.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>To forum to archiwum listy mailingowej</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>To forum jest archiwum/bramą, która przekieruje Twoje posty na adres <b><n.mailing_list_address/></b>.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>Dotyczy to podkategorii, postów, obrazów, plików i innych elementów.</to></translation>
+<translation><from>This is a mailing list archive</from><to>To jest archiwum listy mailingowej</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>To jest automatyczna wiadomość email wysłana przez Nabble potwierdzająca utworzenie nowej aplikacji <n.app/>. Jeżeli nie utworzyłeś aplikacji <n.app/>, o której mowa powyżej prosimy o kontakt poprzez forum wsparcia Nabble.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>Ta lista przyjmuje wyłącznie wiadomości email bez formatowania</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>Lista pokazuje wyłącznie zarejestrowanych, wyrejestrowanych oraz banowanych użytkowników. Anonimowi użytkownicy nie znajdują się na liście, ponieważ nie maja adresu email, stąd nie mogą się na niej znaleźć.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>Ta wiadomość zostanie wysłana z listy mailingowej <b><n.from/></b> do listy <b><n.to/></b>.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to>Ten post NIE został jeszcze zaakceptowany przez listę mailingową.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>Ten post był aktualizowany <n.date/>.</to></translation>
+<translation><from>This topic has been locked.</from><to>Temat został zablokowany.</to></translation>
+<translation><from>This topic has been pinned.</from><to>Temat został przypięty.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>Temat został przypięty w <n.location/>.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>Temat został odblokowany.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>Temat został odpięty.</to></translation>
+<translation><from>This topic has unread posts</from><to>Temat zawiera nieczytane posty</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>Temat jest przypisany Tobie jako priorytetowy <n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>Użytkownik nie ma uprawnień do przeglądania aplikacji (dodaj go/ją do grupy, która na to pozwala i spróbuj jeszcze raz)</to></translation>
+<translation><from>This user name is already in use.</from><to>Nazwa użytkownika już istnieje.</to></translation>
+<translation><from>Threaded</from><to>Wątki</to></translation>
+<translation><from>Threaded View</from><to>Widok wątków</to></translation>
+<translation><from>Time</from><to>Czas</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>WSKAZÓWKA: Jezeli Twoje archiwum jest zasubskrybowane do listy mailingowej, ale wciąż nie działa spróbuj zignorować nagłówek X-No-Archive.</to></translation>
+<translation><from>Tips</from><to>Wskazówki</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> wymagana autoryzacja do przyłaczenia się do <n.location/>:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> Kredyty</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> odświeżeń stron bez reklam.</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>Aby zaakceptować tę prośbę, należy dodać użytkownika do przynajmniej jednej grupy z dostępem do tej części:</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>Aby dodać tę aplikację <n.app/> do swojej strony, skopiuj i wklej na nią następujący kod HTML:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>Aby potwierdzić subskrypcję kliknij poniższy link:</to></translation>
+<translation><from>topic</from><to>temat</to></translation>
+<translation><from>Topics and replies</from><to>Tematy i odpowiedzi</to></translation>
+<translation><from>topics</from><to>tematy</to></translation>
+<translation><from>Topics</from><to>Tematy</to></translation>
+<translation><from>Topics only</from><to>Tylko tematy</to></translation>
+<translation><from>Topics View</from><to>Widok tematów</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>Aby zapobiegać spamowaniu email używany do publikowania postów przez email jest <b>unikalny</b> dla każdego użytkownika.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>Aby usunąć grupę opróżnij poniższe pole tekstowe i zachowaj zmiany.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>Aby dowiedzieć się, którego adresu email nalezy używać do publikowania  <n.login_link.>zaloguj się</n.login_link.> lub <n.register_link.>zarejestruj</n.register_link.>.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>Aby rozpocząć nowy temat w <n.location/>, lub przez email <n.p2/></to></translation>
+<translation><from>Total</from><to>Suma</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>Aby usunąć subskrypcję z <n.location/></to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> zaprasza Cię do subskrypcji w <n.location/>:</to></translation>
+<translation><from>Turn off highlighting</from><to>Wyłącz podświetlenie</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/> utworzył nową podkategorię</to></translation>
+<translation><from>Unable to Post</from><to>NIe można publikować</to></translation>
+<translation><from>Unassigned</from><to>Nieprzypisany</to></translation>
+<translation><from>Unauthorized</from><to>Brak uprawnień</to></translation>
+<translation><from>Unban this user</from><to>Odblokuj tego użytkownika</to></translation>
+<translation><from>Unban User</from><to>Odblokuj użytkownika</to></translation>
+<translation><from>Unknown or Other</from><to>Nieznany lub inny</to></translation>
+<translation><from>Unlock topic</from><to>Odblokuj temat</to></translation>
+<translation><from>Unpin topic</from><to>Odepnij temat</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>Wyrejestrowany / Dezaktywowany</to></translation>
+<translation><from>Unregistered</from><to>Wyrejestrowany</to></translation>
+<translation><from>Unregistered User</from><to>Wyrejestrowany użytkownik</to></translation>
+<translation><from>Unsubscribe</from><to>Usuń subskrypcję</to></translation>
+<translation><from>Upload a file</from><to>Przeslij plik</to></translation>
+<translation><from>User email:</from><to>Email użytkownika:</to></translation>
+<translation><from>user</from><to>użytkownik</to></translation>
+<translation><from>User is online</from><to>Użytkownik jest online</to></translation>
+<translation><from>User Name</from><to>Nazwa użytkownika</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>Użytkownik prosi o upoważnienie do przyłączenia się do<n.location/></to></translation>
+<translation><from>users</from><to>użytkownicy</to></translation>
+<translation><from>Users</from><to>Użytkownicy</to></translation>
+<translation><from>Users & Groups</from><to>Użytkownicy i grupy</to></translation>
+<translation><from>Users that completed the registration process</from><to>Użytkownicy, którzy dokończyli proces rejestracji</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>Używaj tagów takich jak <n.example1/> lub <n.example2/> aby tworzyć podtytuły.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>Użyj poniższych opcji, aby precyzyjnie określić kryteria wyszukiwania.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>Użyj <n.tag_names/>, aby wstawić widgety z innych stron.</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>Przeglądaj wszystkie wiadomości w tym subforum</to></translation>
+<translation><from>view</from><to>przeglądaj</to></translation>
+<translation><from>View mailing list website</from><to>Przeglądaj stronę z listą mailingową</to></translation>
+<translation><from>View message</from><to>Otwórz wiadomość</to></translation>
+<translation><from>View more</from><to>Przeglądaj dalej</to></translation>
+<translation><from>views</from><to>wyświetleń</to></translation>
+<translation><from>Views</from><to>Wyświetleń</to></translation>
+<translation><from>Violent or repulsive content</from><to>Zawartość odpychająca i pełna przemocy</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>Odwiedź <n.app/> pod:</to></translation>
+<translation><from>visit <t.url/></from><to>odwiedź <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>Przygotowaliśmy stronę z <n.unsubscription_instructions_link.>instrukcjami, jak wyłączyć subskrypcję tego archiwum</n.unsubscription_instructions_link.>.</to></translation>
+<translation><from>We will review your report soon.</from><to>Raport zostanie wkrótce przejrzany.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>Które grupy pozwalają członkom na dodawanie do list </to></translation>
+<translation><from>Who can ban/unban users</from><to>Kto może banować/usuwać bany użytkowników</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>Kto może być przypisany do tematów (tylko osoby w grupach roboczych)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>Kto może zmieniać datę i czas wiadomości </to></translation>
+<translation><from>Who can create new topics under this application</from><to>Kto może tworzyć tematy w ramach tej aplikacji</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>Kto może tworzyć aplikacje pochodne (np. sub-fora, sub-kategorie, itp.)</to></translation>
+<translation><from>Who can edit any content, both applications and posts. Note: Please only use this feature in extreme circumstances. Most users will not like having their posts edited by someone else.</from><to>Kto może edytować wszelkie zasoby, zarówno aplikacje jak i posty. Ważne: Prosimy korzystać z tej funkcji tylko w sytuacjach ekstremalnych. Większość użytkowników nie lubi edytowania ich postów przez osoby postronne.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>Kto może edytować aplikacje (np. zmieniać nazwę, opis, itp.)</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>Kto może blokować/odblokowywać tematy w tej aplikacji</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>Kto może zarządzać subskrybentami tej aplikacji</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>Kto może przenosic tematy w inne miejsca (np. pod inne tematy, sub-fora)</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>Kto może przypinać/odpinać tematy w tej aplikacji</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>Kto może publikować zawartość bez żadnych ograniczeń (włącznie z kodem javascript, tagami &lt;object&gt; oraz &lt;style&gt;, itp.). <b>Ostrzeżenie dotyczące bezpieczeństwa</b>: Zezwalaj na wykorzystanie tej opcji wyłącznie przez zaufanych użytkowników.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>Kto może odpowiadać na wiadomości zawarte w tej aplikacji</to></translation>
+<translation><from>Who can view this application and its contents</from><to>Kto może przeglądać aplikację i jej zawartość</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>W ramach tej subskrypcji, aktualizacje będą wysyłane bezpośrednio na adres email i istnieje możliwość odpowiadania na wiadomości email i brania udziału w dyskusji. Twoja subskrypcja działa tak samo jak lista mailingowa.</to></translation>
+<translation><from>Write Your First Headline</from><to>Napisz swój pierwszy nagłówek</to></translation>
+<translation><from>Write Your First Post</from><to>Napisz swój pierwszy post</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>Tak, usuń <n.location/> na zawsze</to></translation>
+<translation><from>Yes, unsubscribe now</from><to>Tak, teraz usuń subskrypcję</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>Posiadasz już subskrypcję <n.location/>.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>Nie posiadasz subskrypcji <n.location/>.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>Możesz także <n.manage_banned_users_link.>zarządzać banowanymi użytkownikami</n.manage_banned_users_link.> w <n.location/>.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>Możesz także <n.root_node.change_permissions_link.>zmieniać uprawnienia</n.root_node.change_permissions_link.> poniższych grup.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>Możesz także promować swoje <n.app/> wysyłając link znajomym, publikując go na swojej stronie lub pisząc o nim na innych forach.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>Nie możesz przenieść tego posta do tej lokalizacji, ponieważ element nadrzędny nie pozwala na anonimowych użytkowników. </to></translation>
+<translation><from>You Cannot Post Here</from><to>Nie możesz tu publikować</to></translation>
+<translation><from>(you can reply by email)</from><to>(nie możesz odpowiedzieć przez email)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>Nie możesz przenieść tego posta do innej lokalizacji.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>Nie możesz publikować w tym miejscu, ale możesz publikować w innych miejscach.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>Możesz spróbować <n.register_link.>zarejestrować się</n.register_link.> lub skontaktować z <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>Mozesz skorzystać z poniższego formularza, aby wysłać prośbę do administracji.</to></translation>
+<translation><from>You have already been registered.</from><to>Jesteś już zarejestrowany/a.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>Pojawiło się zaproszenie do subskrypcji <n.location/>, która jest dostępna pod:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>Zarejestrowano cię w <n.subject/>.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>Subskrypcja do <n.location/> została usunięta</to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>Piszesz zbyt wiele postów w zbyt krótkim czasie. Spróbuj ponownie później.</to></translation>
+<translation><from>You logged out</from><to>Wylogowano</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>Może być konieczne <n.mailing_list_options_link.>zasubskrybowanie tej listy mailingowej</n.mailing_list_options_link.>, aby wiadomość została zaakceptowana.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>Możesz <n.page_node.unauthorized_link.>poprosić o pozwolenie postowania</n.page_node.unauthorized_link.> tutaj lub skontaktować się z <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> jeżeli masz pytania.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>Musisz wyrazić zgodę na Warunki użytkowania.</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>Dla tej listy mailingowej należy wprowadzić aktualny adres email.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>Dla tej listy mailingowej musisz wprowadzić adres URL strony internetowej.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>Należy wypełnić wszystkie poniższe pola.</to></translation>
+<translation><from>You must login to view this page.</from><to>Aby przeglądać tę stronę należy się zalogować.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>Należy się zalogować, aby przeglądać <n.subject/>.</to></translation>
+<translation><from>You must login to your account.</from><to>Musisz zalogować się na koncie.</to></translation>
+<translation><from>You must provide a user name.</from><to>Musisz wprowadzić nazwę użytkownika.</to></translation>
+<translation><from>You're not a subscriber</from><to>Nie posiadasz subskrypcji</to></translation>
+<translation><from>Your Name</from><to>Twoje imię i nazwisko </to></translation>
+<translation><from>Your request has been successfully sent.</from><to>Wysłanie prośby zakończone sukcesem.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>Subskrypcja została prawidłowo zapisana.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>Sunskrypcja <n.location/> została usunięta. Jeżeli uważasz, że to pomyłka możesz ponownie zasubskrybować  uzywając poniższego linka:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>Subskrypcja <n.location/> została prawidłowo usunięta.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to>Tworzenie <n.app/> zakończone sukcesem.</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>Aby publikować nowe tematy w <n.location/> będą potrzebne uprawnienia, więc oprócz rejestracji należy skontaktować się z administracją.</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>Publikacja każdej wiadomości w tym temacie spowoduje wysłanie emaila.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>Otrzymasz wiadomość z linkiem aktywującym konto.</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>Twoja subskrypcja</to></translation>
+<translation><from>edit</from><to>edytuj</to></translation>
+<translation><from>Remove ads</from><to>Usuń reklamy</to></translation>
+<translation><from>View profile of <t.author/></from><to>Przeglądaj profil <n.author/></to></translation>
+<translation><from>Description</from><to>Opis</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>Filmy z Youtube, Vimeo i LiveLeak.</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>Użytkownicy banowani w  <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>Prefix tematu</to></translation>
+
+#NEW 2
+<translation><from>Change title and meta tags</from><to>Zmień tytuł i meta tagi</to></translation>
+<translation><from>Here you can customize the title and meta tags of the <t.location/> page.</from><to>W tym miejscu możesz modyfikować tytuł i meta tagi strony <n.location/>.</to></translation>
+<translation><from>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</from><to>Dane meta poniżej mogą pomóc w optymalizacji tej strony dla wyszukiwarek (tj. google, yahoo!, itp.)</to></translation>
+<translation><from>Use custom values</from><to>Użyj własnych wartości </to></translation>
+<translation><from>Page Title</from><to>Tytuł strony</to></translation>
+<translation><from>Enter a context-rich title that clearly identifies this page.</from><to>Wprowadź poszerzony tytuł, który z łatwością zidentyfikuje tę stronę.</to></translation>
+<translation><from>The title should ideally be less than 70 characters in length.</from><to>Tytuł idealnie nie powinien przekraczać 70 znaków długości.</to></translation>
+<translation><from>Meta Description</from><to>Meta opis</to></translation>
+<translation><from>Enter a brief and concise summary of your page's content.</from><to>Wprowadź krótki i treściwy opis zawartości swojej strony.</to></translation>
+<translation><from>Limit your description to 155 characters or 170 characters at most.</from><to>Ogranicz opis do długości od 155 do maksymalnie 170 znaków.</to></translation>
+<translation><from>Mailing List Archive</from><to>Archiwum listy mailingowej</to></translation>
+<translation><from>Filter: priority <t.priority/></from><to>Filtr: priorytet <n.priority/></to></translation>
+<translation><from>Filter: assignee <t.author/></from><to>Filtr: przydział dla <n.author/></to></translation>
+<translation><from>Use Google Analytics</from><to>Użyj Google Analytics</to></translation>
+<translation><from>Analytics Account ID:</from><to>ID konta Analytics:</to></translation>
+<translation><from>(e.g., UA-12345-0)</from><to>(np.: UA-12345-0)</to></translation>
+<translation><from>Enter a valid analytics account ID.</from><to>Wprowadź aktualny identyfikator konta analytics.</to></translation>
+<translation><from>Here you can use Google Analytics to measure the success of your app.</from><to>W tym miejcsu mozna użyć Google Analytics, aby zmierzyć sukces odniesiony przez app..</to></translation>
+<translation><from>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</from><to>Wprowadź poniżej ID konta analytics, a będziesz w stanie śledzić odwiedziny, odwiedzających oraz inne statystyki dotyczące ruchu na stronie.</to></translation>
+
+<translation><from>Digest Email</from><to>Email z codziennym przeglądem</to></translation>
+<translation><from>on <t.date/></from><to>dnia<n.date/></to></translation>
+<translation><from>DO NOT REPLY TO THIS EMAIL</from><to>NIE ODPOWIADAJ NA TEN EMAIL</to></translation>
+<translation><from>Replies sent to this address are not read or processed.</from><to>Odpowiedzi przesyłane na ten adres nie są czytanie czy przetwarzane.</to></translation>
+<translation><from>If you want to respond to a post for which you received this email, please go to the website: <t.url/></from><to>Jeżeli chcesz odpowiedzieć na posta w tej wiadomości przejdź na stronę: <n.url/></to></translation>
+<translation><from>new post</from><to>nowy post</to></translation><!-- przykład użycia: "1 new post"-->
+<translation><from>new posts</from><to>nowe posty</to></translation><!-- usage example: "2 new posts"-->
+
+<translation><from>New registered user in <t.location/>!</from><to>Nowy uźytkownik zarejestrowany w <n.location/>!</to></translation>
+<translation><from>User profile</from><to>Profil użytkownika</to></translation>
+<translation><from>New user:</from><to>Nowy użytkownik:</to></translation>
+
+<translation><from><n.author/> wrote</from><to><n.author/> napisał/a</to></translation>
+<translation><from>You still have <t.number/> day(s) left without advertising</from><to>Ciągle masz <n.number/> dni bez reklam.</to></translation>
+<translation><from>(Only administrators can see this message)</from><to>(Tylko administratorzy widzą tę wiadomość)</to></translation>
+
+<translation><from>Some private items have been omitted because you don't have permission to view them.</from><to>Niektóre elementy prywatne zostały pominięte, ponieważ nie posiadasz uprawnień do ich przeglądania.</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Submit". We will email you a link to reset your password.</from><to>Wprowadź adres email użyty przy rejestracji i wciśnij "Prześlij". Prześlemy Ci link do resetowania hasła.</to></translation>
+<translation><from>Submit</from><to>Prześlij</to></translation>
+<translation><from>Password Reset Sent</from><to>Link resetujący hasło wysłany</to></translation>
+<translation><from>We have sent you a link to reset your password. Please check your email now. If you don't receive the instructions in a few minutes, check your spam folder or try to resend the request.</from><to>Przesłaliśmy Ci link resetujący hasło. Sprawdź skrzynkę email. Jezeli w ciągu kliku minut nie otrzymasz instrukcji, sprawdź folder spam lub spróbuj ponownie przesłać prośbę.</to></translation>
+<translation><from>Reset your password / <t.location/></from><to>Resetuj hasło / <n.location/></to></translation>
+<translation><from>We received a request to reset your password in <t.location/>.</from><to>otrzymaliśmy prośbę o resetowanie hasła z <n.location/>.</to></translation>
+<translation><from>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</from><to>Jeżeli chcesz zresetować hasło, kliknij poniższy link (lub skopiuj i wklej URL w pasek adresowy przeglądarki):</to></translation>
+<translation><from>If you don't want to reset your password, please ignore this message. Your password will not be reset.</from><to>Jeżeli nie chcesz resetować hasła, zignoruj tę wiadomość. Hasło nie zostanie zresetowane.</to></translation>
+
+<translation><from><b>IMPORTANT:</b> If you use this option, messages posted in the archive will NOT be sent to the mailing list.</from><to><b>WAŻNE:</b> Jeżeli użyjesz tej opcji, wiadomości publikowane w archiwum NIE będą wysyłane pocztą email.</to></translation>
+<translation><from>Message Preview</from><to>Podgląd wiadomości</to></translation>
+<translation><from>Your changes will not be sent to the mailing list.</from><to>Zmiany nie zostaną wysłane w ramach listy mailingowej.</to></translation>
+<translation><from>If you want others in the mailing list to know of your changes, please compose a new message or reply to your original message.</from><to>Jeżeli chcesz, żeby inni użytkownicy zapoznali się ze zmianami utwórz nową wiadomość lub odpowiedz na wiadomość oryginalną. </to></translation>
+
+<translation><from>Poll</from><to>Ankieta</to></translation>
+<translation><from>Add new poll</from><to>Dodaj nową ankietę</to></translation>
+<translation><from>Question:</from><to>Pytanie:</to></translation>
+<translation><from>Answers:</from><to>Odpowiedzi:</to></translation>
+<translation><from>Add new answer</from><to>Dodaj nową odpowiedź</to></translation>
+<translation><from>1 vote</from><to>1 głos</to></translation>
+<translation><from><t.number/> votes</from><to><n.number/> głosów</to></translation>
+<translation><from>Total votes:</from><to>Wszystkich głosów:</to></translation>
+<translation><from>Vote</from><to>Głosuj</to></translation><!-- verb -->
+<translation><from>Your vote has been submitted.</from><to>Głos został przesłany.</to></translation>
+<translation><from>This poll is closed.</from><to>Ta ankieta jest zamknięta.</to></translation>
+<translation><from>This poll ends on <t.date/>.</from><to>Ankieta kończy się w dniu <n.date/></to></translation>
+<translation><from>This poll ended on <t.date/>.</from><to>Ankieta zakończyła się <n.date/></to></translation>
+<translation><from>Results will be shown only after poll has ended.</from><to>Wyniki zostaną opublikowane po zakończeniu ankiety.</to></translation>
+<translation><from>You have to vote before you can see the results.</from><to>Zanim zobaczysz wyniki musisz zagłosować.</to></translation>
+<translation><from>You cannot change your vote after voting.</from><to>Nie można zmienić oddanego głosu.</to></translation>
+<translation><from>You can select up to <t.number/> options.</from><to>Możesz wybrać do <n.number/> opcji.</to></translation>
+<translation><from>Please select no more than <t.number/> options.</from><to>Nie wybieraj więcej niż <n.number/> opcji.</to></translation>
+<translation><from>Please select at least one option.</from><to>Wybierz przynajmniej jedną opcję.</to></translation>
+<translation><from>Remove Poll</from><to>Usuń ankietę</to></translation>
+<translation><from>Invalid poll parameters.</from><to>Niewłaściwe parametry ankiety</to></translation>
+<translation><from>Poll duration must be a non-negative integer or blank.</from><to>Długość ankiety musi być całkowitą liczbą dodatnią lub pustym polem.</to></translation>
+<translation><from>Poll maximum allowed choices must be a non-negative integer or blank.</from><to>Maksymalna liczba odpowiedzi musi być całkowitą liczbą dodatnią lub pustym polem.</to></translation>
+<translation><from>Delete this poll, including all votes?</from><to>Czy usunąć ankietę razem z głosami?</to></translation>
+<translation><from>Poll has been deleted.</from><to>Ankieta została usunięta.</to></translation>
+<translation><from>Who can create polls.</from><to>Kto może tworzyć ankiety.</to></translation>
+<translation><from>Allow vote changes</from><to>Pozwalaj na zmianę głosów</to></translation>
+<translation><from>Allow viewing results before end date (poll creators can always view the results)</from><to>Pozwalaj na przeglądanie wyników przez zakończeniem ankiety (twórca ankiety może zawsze przeglądać wyniki)</to></translation>
+<translation><from>Allow viewing results before vote</from><to>Pozwalaj na przeglądanie wyników przed głosowaniem</to></translation>
+<translation><from>Multiple selections allowed:</from><to>Dozwolony jest wybór wielokrotny:</to></translation>
+<translation><from>Poll ends after <t.number/> days (leave blank for unlimited).</from><to>Ankieta kończy się po <n.number/> dniach (puste pole oznacza nieskończoność).</to></translation>
+<translation><from>Login to vote</from><to>Zaloguj by oddać głos</to></translation>
+<translation><from>This message has a poll</from><to>Ta wiadomość zawiera ankietę</to></translation>
+<translation><from>Visit the link below if you want to participate:</from><to>Odwiedź poniższy link, aby wziąć w niej udział:</to></translation>
+
+<translation><from>Current length: <t.number/> characters</from><to>Obecna długość: <n.number/> znaków</to></translation>
+<translation><from>Edit Post</from><to>Edytuj post</to></translation>
+<translation><from><t.location/> - Your credits are running out</from><to><n.location/> - Kończą się kredyty</to></translation>
+<translation><from><t.location/> is running out of ad-free credits.</from><to><n.location/> kończy się pula kredytów bez reklam.</to></translation>
+<translation><from>If you want to buy more credits, visit:</from><to>Jeżeli chcesz zakupić więcej kredytów odwiedź:</to></translation>
+<translation><from>only in this topic</from><to>tylko w tym temacie</to></translation>
+<translation><from>everywhere</from><to>wszędzie</to></translation>
+
+<translation><from>If you want to invite subscribers, please request this feature in the <n.support_link/> forum.</from><to>Jeżeli chcesz zapraszać subskrybentów, poproś o włączenie tej funkcji <n.support_link/>.</to></translation>
+<translation><from>We can install this module for you, but the Nabble team must approve this request to prevent abuse and spam.</from><to>Możemy zainstalować ten moduł dla Ciebie, ale zespół Nabble musi zatwierdzić tę prośbę, aby zapobiegać naduzyciom i spamowi.</to></translation>
+
+<translation><from>Edit Signature</from><to>Edytuj podpis</to></translation>
+<translation><from>Current Signature</from><to>Obecny podpis</to></translation>
+<translation><from>Save Signature</from><to>Zapisz podpis</to></translation>
+<translation><from>Signature is in HTML format</from><to>Podpis jest w formacie HTML</to></translation>
+
+<translation><from>Download backup</from><to>Pobierz kopię zapasową</to></translation>
+<translation><from>Backup</from><to>Kopia zapasowa</to></translation>
+<translation><from>Here you can download a backup of <t.location/>.</from><to>W tym miejscu możesz pobrać kopię zapasową następującego zasobu <n.location/>.</to></translation>
+<translation><from>When you press the button below, the system will start the generation of the backup file and this may take some minutes to finish. You will receive an email with a link to download the file when it is ready.</from><to>Po naciśnięciu ponizszego przycisku system rozpocznie generowanie pliku kopii zapasowej, a może to potrwać kilka minut. Po zakończeniu generowania kopii zapasowej otrzymasz email z linkiem do pobrania pliku.</to></translation>
+<translation><from>Generate backup file</from><to>Generuj kopię zapasową</to></translation>
+<translation><from>The system is generating the backup now. When it is finished, a link to the backup file will be emailed to you.</from><to>System właśnie generuje kopię zapasową. Po zakończeniu link do pliku kopii zapasowej otrzymasz w wiadomości email. </to></translation>
+<translation><from>The backup file is generated with the <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a> tool, which is an open source project in Java. You should visit the project website if you want to restore your backup into a Postgresql database.</from><to>Kopia zapasowa jest generowana za pomocą narzędzia <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a>, które jest projektem Open source w Java. Aby odzyskać kopię zapasową do bazy danych Postgresql, należy odwiedzić stronę projektu.</to></translation>
+<translation><from>Backup of <t.location/></from><to>Kopia zapasowa <n.location/></to></translation>
+<translation><from>Here is your backup file:</from><to>Oto Twój plik kopii zapasowej:</to></translation>
+<translation><from><t.location/> has been deleted</from><to>Usunięto <n.location/></to></translation>
+<translation><from>Your Nabble site "<t.location/>" has been deleted.</from><to>Twoja strona Nabble "<n.location/>" została usunięta.</to></translation>
+<translation><from>You can download a backup of this site from the link below. Nabble will try to keep this backup available for a few months, but this is not guaranteed. If this content is important to you, save this copy as soon as possible.</from><to>Kopię zapasową strony można pobrać za pomocą poniższego linku. Nabble spróbuje zachować ten plik przez kilka miesięcy, ale nie jest to gwarantowane. Jeżeli zawartość jest dla Cibie istotna zachowaj te kopię tak szybko jak to możliwe.</to></translation>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_pt_br.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,852 @@
+## Portuguese (Brasil)
+
+<macro name="is_masculine" dot_parameter="noun">
+	<n.not.regex_matches text="[n.to_lower_case.noun/]" pattern="galeria|subcategoria|categoria"/>
+</macro>
+
+<macro name="novo" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>novo</then>
+		<else>nova</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="esse" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>esse</then>
+		<else>essa</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="o_seu" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>o seu</then>
+		<else>a sua</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="Seu_foi_criado" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>Seu <n.what/> foi criado</then>
+		<else>Sua <n.what/> foi criada</else>
+	</n.if.is_masculine.what>
+</macro>
+
+<macro name="do_seu_novo" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>do seu novo</then>
+		<else>da sua nova</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="pelo_seu_novo" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>pelo seu novo</then>
+		<else>pela sua nova</else>
+	</n.if.is_masculine.what>
+	<n.what/>
+</macro>
+
+<macro name="o_mencionado" dot_parameter="what">
+	<n.if.is_masculine.what>
+		<then>o <n.what/> mencionado</then>
+		<else>a <n.what/> mencionada</else>
+	</n.if.is_masculine.what>
+</macro>
+
+## MONTHS ##
+
+<translation><from>January</from><to>Janeiro</to></translation>
+<translation><from>February</from><to>Fevereiro</to></translation>
+<translation><from>March</from><to>Março</to></translation>
+<translation><from>April</from><to>Abril</to></translation>
+<translation><from>May</from><to>Maio</to></translation>
+<translation><from>June</from><to>Junho</to></translation>
+<translation><from>July</from><to>Julho</to></translation>
+<translation><from>August</from><to>Agosto</to></translation>
+<translation><from>September</from><to>Setembro</to></translation>
+<translation><from>October</from><to>Outubro</to></translation>
+<translation><from>November</from><to>Novembro</to></translation>
+<translation><from>December</from><to>Dezembro</to></translation>
+
+<translation><from>Jan</from><to>Jan</to></translation>
+<translation><from>Feb</from><to>Fev</to></translation>
+<translation><from>Mar</from><to>Mar</to></translation>
+<translation><from>Apr</from><to>Abr</to></translation>
+<!--translation><from>May</from><to>Mai</to></translation-->
+<translation><from>Jun</from><to>Jun</to></translation>
+<translation><from>Jul</from><to>Jul</to></translation>
+<translation><from>Aug</from><to>Ago</to></translation>
+<translation><from>Sep</from><to>Set</to></translation>
+<translation><from>Oct</from><to>Out</to></translation>
+<translation><from>Nov</from><to>Nov</to></translation>
+<translation><from>Dec</from><to>Dez</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>Usar este recurso de forma excessiva também é uma violação dos nossos Termos de Uso.</to></translation>
+<translation><from>Access Request</from><to>Pedido de Acesso</to></translation>
+<translation><from>Account settings</from><to>Configurações da conta</to></translation>
+<translation><from>Account Settings</from><to>Configurações da Conta</to></translation>
+<translation><from>Action</from><to>Ação</to></translation>
+<translation><from>Add a link to another page</from><to>Adicionar um link para outra página</to></translation>
+<translation><from>Add a new comment</from><to>Adicionar um novo comentário</to></translation>
+<translation><from>Add a new group</from><to>Adicionar um novo grupo</to></translation>
+<translation><from>Add an image to your post</from><to>Adicionar uma imagem a sua mensagem</to></translation>
+<translation><from>Add another address</from><to>Adicionar outro endereço</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>Adicionar conteúdo que não deve ser alterado (por exemplo, código-fonte)</to></translation>
+<translation><from>Adding Sub-Headers</from><to>Adicionando Sub-títulos</to></translation>
+<translation><from>Add New Group</from><to>Adicionar Novo Grupo</to></translation>
+<translation><from>Add / Remove Groups</from><to>Adicionar / Remover Grupos</to></translation>
+<translation><from>Add smileys and funny animations</from><to>Adicionar carinhas e animações engraçadas</to></translation>
+<translation><from>Add Subscribers</from><to>Adicionar Assinante</to></translation>
+<translation><from>Add this item to your favorites list</from><to>Adicionar esse item à sua lista de favoritos</to></translation>
+<translation><from>Administrator</from><to>Administrador</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity, other sexual content.</from><to>Conteúdo adulto ou maduro, atividade sexual explícita, nudez ou outro conteúdo sexual.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>Adultos brigando, ataque físico, violência entre jovens, abuso de animais ou promove terrorismo.</to></translation>
+<translation><from>Advanced Search</from><to>Pesquisa Avançada</to></translation>
+<translation><from>Advanced Settings</from><to>Configurações Avançadas</to></translation>
+<translation><from>Advertisement</from><to>Anúncio</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>Notifique-me por email quando alguém postar uma mensagem neste tópico</to></translation>
+<translation><from>All</from><to>Todos</to></translation>
+<translation><from>all of the words:</from><to>todas as palavras:</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>Todas as mensagens de <n.author/> foram removidas com sucesso.</to></translation>
+<translation><from>All posts</from><to>Todas as mensagens</to></translation>
+<translation><from>All users belong to this group</from><to>Todos os usuários pertencem a este grupo</to></translation>
+<translation><from>All Users</from><to>Todos os Usuários</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>Todos os usuários que se registraram em <n.location/>. Esses usuários confirmaram os seus endereços de email e são capazes de efetuar o login.</to></translation>
+<translation><from>Already Subscribed</from><to>Inscrição Já Efetuada</to></translation>
+<translation><from>An email has been sent to you.</from><to>Um email foi enviado para você.</to></translation>
+<translation><from>Anonymous</from><to>Anônimo</to></translation>
+<translation><from>anonymous user</from><to>usuário anônimo</to></translation>
+<translation><from>anonymous users</from><to>usuários anônimos</to></translation>
+<translation><from>Any message part contains</from><to>Qualquer parte da mensagem contém</to></translation>
+<translation><from>Application</from><to>Aplicação</to></translation>
+<translation><from>Apps</from><to>Aplicações</to></translation>
+<translation><from>Assignee</from><to>Encarregado</to></translation>
+<translation><from>Assign</from><to>Atribuir</to></translation>
+<translation><from>Assignment</from><to>Atribuição</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>Como você pediu, o endereço de email para postar novas mensagens em <n.app/> é:</to></translation>
+<translation><from>at least one of the words:</from><to>pelo menos uma das palavras:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to>Listas (Feeds) para <n.location/></to></translation>
+<translation><from>at priority</from><to>com prioridade</to></translation>
+<translation><from>Authorized Users Only</from><to>Somente Usuários Autorizados</to></translation>
+<translation><from>Author name</from><to>Nome do autor</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>Evite pequenas frases como "Obrigado", "Valeu"... Você pode <n.page_node.reply_to_author_link.>enviar um email privado</n.page_node.reply_to_author_link.> se quiser.</to></translation>
+<translation><from>Banned User</from><to>Usuário Banido</to></translation>
+<translation><from>Ban this user</from><to>Banir este usuário</to></translation>
+<translation><from>Ban User</from><to>Banir Usuário</to></translation>
+<translation><from>Before deleting this archive...</from><to>Antes de apagar este arquivo...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>Abaixo você pode gerenciar usuários e grupos. Você pode copiar e colar usuários para movê-los de um grupo para outro.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>IMPORTANTE</b>: A Nabble enviará um convite para cada email na lista. Os usuários terão que clicar em um link para confirmar a inscrição.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>IMPORTANTE:</b> Esta inscrição é independente da verdadeira inscrição da lista de emails. Basicamente, você irá se inscrever no arquivo do fórum, não na lista de emails propriamente dita. A inscrição no arquivo não garantirá que as suas mensagens sejam aceitas pela lista de emails.</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>IMPORTANTE</b>: Você precisa inscrever este arquivo à lista de emails para que o arquivamento funcione.</to></translation>
+<translation><from>blog</from><to>blog</to></translation>
+<translation><from>Blog</from><to>Blog</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>Nota</b>: Como você é um administrador, você pode <n.page_node.change_permissions_link.>mudar as permissões de <n.location/></n.page_node.change_permissions_link.> e garantir que você pode criar novos tópicos aqui.</to></translation>
+<translation><from>board</from><to>mural</to></translation>
+<translation><from>Board</from><to>Mural</to></translation>
+<translation><from>Bold</from><to>Negrito</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>Alerta:</b> O índice de busca está sendo reconstruído. Os resultados da busca podem estar incompletos.</to></translation>
+<translation><from>by <t.author/></from><to>por <n.author/></to></translation>
+<translation><from>Cancel</from><to>Cancelar</to></translation>
+<translation><from>category</from><to>categoria</to></translation>
+<translation><from>Category</from><to>Categoria</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>CUIDADO: essa ação não pode ser desfeita.</to></translation>
+<translation><from>Change appearance</from><to>Mudar aparência</to></translation>
+<translation><from>Change application type</from><to>Mudar tipo da aplicação</to></translation>
+<translation><from>Change Application Type</from><to>Mudar Tipo da Aplicação</to></translation>
+<translation><from>Change code image</from><to>Mudar código da imagem</to></translation>
+<translation><from>Change domain name</from><to>Mudar nome do domínio</to></translation>
+<translation><from>Change language</from><to>Mudar idioma</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>Mude ou remova sua imagem de perfil.</to></translation>
+<translation><from>Change parent</from><to>Mudar pai</to></translation>
+<translation><from>Change permissions</from><to>Mudar permissões</to></translation>
+<translation><from>Change Permissions</from><to>Mudar Permissões</to></translation>
+<translation><from>Change post date</from><to>Mudar data da mensagem</to></translation>
+<translation><from>Change Post Date</from><to>Mudar Data da Mensagem</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>Mude o texto da assinatura que é mostrado embaixo das suas mensagens.</to></translation>
+<translation><from>Change User Groups</from><to>Mudar Grupos de Usuário</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>Mude preferências de visualização e outras configurações.</to></translation>
+<translation><from>Change Your Picture</from><to>Mude Sua Foto</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>Mude o seu email registrado, senha e nome de usuário.</to></translation>
+<translation><from>Child abuse</from><to>Abuso infantil</to></translation>
+<translation><from>Choose a subcategory</from><to>Escolha uma categoria</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>Escolha uma subcategoria para postar a sua mensagem</to></translation>
+<translation><from>Choose the best offer for you</from><to>Escolha a melhor oferta para você</to></translation>
+<translation><from>Classic</from><to>Clássica</to></translation>
+<translation><from>Clear Log</from><to>Apagar o Log</to></translation>
+<translation><from>Click for more options</from><to>Clique para mais opções</to></translation>
+<translation><from>click here</from><to>clique aqui</to></translation>
+<translation><from>Click here to make your first post</from><to>Clique aqui para postar a sua primeira mensagem</to></translation>
+<translation><from>Click to filter</from><to>Clique para filtrar</to></translation>
+<translation><from>Close</from><to>Fechar</to></translation>
+<translation><from>Close this message</from><to>Fechar esta mensagem</to></translation>
+<translation><from>comment</from><to>comentário</to></translation>
+<translation><from>Comment</from><to>Comentário</to></translation>
+<translation><from>comments</from><to>comentários</to></translation>
+<translation><from>Comments</from><to>Comentários</to></translation>
+<translation><from>Confirm Password</from><to>Confirme a Senha</to></translation>
+<translation><from>Confirm Subscription</from><to>Confirmar Inscrição</to></translation>
+<translation><from>Congratulations!</from><to>Parabéns!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>Parabéns <n.pelo_seu_novo.app/>!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>Conteúdo promovendo ódio ou violência, abuso de indivíduos vuneráveis, intimidação, intolerância racial ou contra algum indivíduo, grupo ou organização, ou profanação excessiva. </to></translation>
+<translation><from>CONTENTS DELETED</from><to>CONTEÚDO APAGADO</to></translation>
+<translation><from>Continue</from><to>Continuar</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>Violação de direitos ou privacidade, ou outras reivindicações legais.</to></translation>
+<translation><from>Count</from><to>Quantidade</to></translation>
+<translation><from>Created by <t.author/></from><to>Criado por <n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>Criar <n.novo.element/></to></translation>
+<translation><from>Create <t.element/></from><to>Criar <n.element/></to></translation>
+<translation><from>Current Credits</from><to>Créditos Atuais</to></translation>
+<translation><from>Currently Nabble supports</from><to>Atualmente a Nabble aceita</to></translation>
+<translation><from>Current Subscribers</from><to>Atuais Assinantes</to></translation>
+<translation><from>Daily digest</from><to>Resumo diário</to></translation>
+<translation><from>Data successfully saved</from><to>Dados salvos com sucesso</to></translation>
+<translation><from>Date</from><to>Data</to></translation>
+<translation><from>days</from><to>dias</to></translation>
+<translation><from>Dear <t.name/>,</from><to>Prezado(a) <n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>Prezado usuário,</to></translation>
+<translation><from>Default</from><to>Padrão</to></translation>
+<translation><from>Delete all posts from this user</from><to>Apagar todas as mensagens deste usuário</to></translation>
+<translation><from>Delete Application</from><to>Apagar Aplicação</to></translation>
+<translation><from>Deleted posts</from><to>Mensagens apagadas</to></translation>
+<translation><from>Delete</from><to>Apagar</to></translation>
+<translation><from>Delete this post and replies</from><to>Apagar esta mensagem e respostas</to></translation>
+<translation><from>Delete this post</from><to>Apagar esta mensagem</to></translation>
+<translation><from>Delete this topic</from><to>Apagar este tópico</to></translation>
+<translation><from>Description is in HTML Format</from><to>Descrição está no formato HTML</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>Não poste repetidamente. Espera alguns dias. As pessoas lerão sua mensagem por email.</to></translation>
+<translation><from>Don't show this message again</from><to>Não mostrar essa mensagem novamente</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>Você quer realmente apagar esta mensagem?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>Você quer realmente apagar esta mensagem e todas as suas respostas?</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>Você realmente deseja <n.important.>apagar</n.important.> <n.location/>?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>Você realmente deseja remover as configurações de arquivo de lista de emails?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>Você realmente deseja se inscrever em <n.location/>?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>Você realmente deseja liberar este usuário?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>Você realmente deseja remover a sua inscrição de <n.location/>?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>Consumo excessivo de drogas, abuso de fogo ou explosivos, venda de cerveja ou outras bebidas alcóolicas, tabaco e produtos relacionados, armas, munição ou outros atos perigosos.</to></translation>
+<translation><from>Edit name & description</from><to>Mudar nome & descrição</to></translation>
+<translation><from>Edit Name & Description</from><to>Mudar Nome & Descrição</to></translation>
+<translation><from>Editor</from><to>Editor</to></translation>
+<translation><from>Edit Personal Information</from><to>Editar Informações Pessoais</to></translation>
+<translation><from>Edit post</from><to>Editar mensagem</to></translation>
+<translation><from>Edit Subscription</from><to>Alterar Inscrição</to></translation>
+<translation><from>Edit Your Signature</from><to>Mudar sua Assinatura</to></translation>
+<translation><from>Email Confirmation</from><to>Confirmação de Email</to></translation>
+<translation><from>Email for <t.app/></from><to>Email para <n.app/></to></translation>
+<translation><from>Email</from><to>Email</to></translation>
+<translation><from>Email Subscription</from><to>Inscrição por Email</to></translation>
+<translation><from>Email this post to...</from><to>Enviar esta mensagem para...</to></translation>
+<translation><from>Embedding Contents</from><to>Embutindo Conteúdo</to></translation>
+<translation><from>Embedding options</from><to>Opções de embutir</to></translation>
+<translation><from>Embed</from><to>Embutir</to></translation>
+<translation><from>Embed post</from><to>Embutir mensagem</to></translation>
+<translation><from>Embed Tags</from><to>Tags de Embutir</to></translation>
+<translation><from>Embed this <t.app/></from><to>Embutindo <n.esse.app/></to></translation>
+<translation><from>Empty</from><to>Vazio</to></translation>
+<translation><from>Enter a valid email address.</from><to>Entre um endereço de email válido.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>Digite o seu email abaixo e nós enviaremos um email de confirmação para você.</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>Informe um endereço de email ou nome de usuário por linha:</to></translation>
+<translation><from>Enter one user per row</from><to>Informe um usuário por linha</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>Informe o link permanente do <b>fórum</b> ou <b>mensagem</b> que será o novo pai, ou deixe em branco para fazer dessa mensagem um tópico independente:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>Informe a homepage da lista de emails, onde os usuários podem encontrar mais informações.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>Informe o prefixo que esta lista de emails utiliza antes do assunto. O prefixo será automaticamente retirado dos emails importados.</to></translation>
+<translation><from>Enter your email address</from><to>Digite o seu endereço de email</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>Exemplo: joao_da_silva@dominio.com.br</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>Exemplo: nome_da_lista@dominio_da_lista.com</to></translation>
+<translation><from>Examples:</from><to>Exemplos:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>Exemplos: '[a-lista]', 'Abc:', etc.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>Explique para os administradores porque você deseja ter acesso à essa área restrita.</to></translation>
+<translation><from>Explanation from this user:</from><to>Explicação deste usuário:</to></translation>
+<translation><from>Extras & add-ons</from><to>Extras & complementos</to></translation>
+<translation><from>Failed</from><to>Falhou</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>Favorito (clique para remover esse item da lista de favoritos)</to></translation>
+<translation><from>Feeds</from><to>Feeds</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>Arquivo não foi enviado: <n.file/> (por favor, envie-o novamente ou remova a tag)</to></translation>
+<translation><from>Filter by group</from><to>Filtrar por grupo</to></translation>
+<translation><from>Floating sub-forum</from><to>Sub-fórum flutuante</to></translation>
+<translation><from>Forgot Password?</from><to>Esqueceu a Senha?</to></translation>
+<translation><from>Forgot your password?</from><to>Esqueceu sua senha?</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>Para mais informações, veja: <n.info/></to></translation>
+<translation><from>forum</from><to>fórum</to></translation>
+<translation><from>Forum</from><to>Fórum</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to><n.app/> Embutível Gratis</to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>De agora em diante, você receberá um email para cada mensagem postada em <n.location/>.</to></translation>
+<translation><from>gallery</from><to>galeria</to></translation>
+<translation><from>Gallery</from><to>Galeria</to></translation>
+<translation><from>Gambling or casino-related content</from><to>Jogos de azar ou cassino</to></translation>
+<translation><from>Go back</from><to>Voltar</to></translation>
+<translation><from>Go to next message</from><to>Ir para a próxima mensagem</to></translation>
+<translation><from>Group Name:</from><to>Nome do Grupo:</to></translation>
+<translation><from>Groups</from><to>Grupos</to></translation>
+<translation><from>Groups of this user</from><to>Grupos deste usuário</to></translation>
+<translation><from>Hacking / cracking</from><to>Hackers ou crimes digitais</to></translation>
+<translation><from>Harmful dangerous acts</from><to>Atos perigosos</to></translation>
+<translation><from>Hateful or abusive content</from><to>Conteúdo abominável ou abusivo</to></translation>
+<translation><from>Help</from><to>Ajuda</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>Aqui você pode esconder dos usuários as informações atuais de arquivo de emails. Essa opção pode ajudar você a substituir o seu servidor de lista de emails pela funcionalidade de assinatura de emails oferecida pela Nabble.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>Esconder endereço de email (por exemplo, user@host.com vira um link <n.lt/>email escondido<n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>Esconder email</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>Esconder dos usuários as informações de arquivo da lista de emails</to></translation>
+<translation><from>Highest</from><to>Altíssima</to></translation>
+<translation><from>High</from><to>Alta</to></translation>
+<translation><from>Hi <t.name/>,</from><to>Olá <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>Se esse endereço de email é seu, você deve <n.register_link.>se registrar</n.register_link.> usando esse mesmo endereço. Depois do registro, você será o dono desta conta de usuário.</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>Se você não está recebendo emails da Nabble, por favor verifique a sua pasta spam/lixo eletrônico.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>Se você está postando uma pergunta, por favor procure primeiro. A sua pergunta talvez já tenha sido respondida.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>Se você ainda não é um membro, você pode <n.register_link.><b>registrar-se agora</b></n.register_link.>.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>Se você banir este usuário, ele/ela não poderá fazer mais nada em <n.location/>.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>Se você não pediu esse email ou não tem idéia porque recebeu isso, então ignore-o. Pode ter sido um erro de outra pessoa.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>Se você não quer se registrar por agora, informe o endereço de email com o qual você pretende postar e o seu endereço personalizado será enviado para você.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>Se você já removeu a inscrição, então você pode avançar para a deleção.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>Se você souber, por favor selecione o software do servidor da lista de emails e a sua versão.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>Se você saiu da conta por engano, por favor <n.login_link.>efetue o login</n.login_link.> novamente.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>Se você responder a este email, a sua mensagem será adicionada à discussão abaixo</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>Se você liberar este usuário, ele/ela será capaz de postar mensagens em <n.location/> novamente.</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>Se, ao invés disso, você quiser se inscrever na lista de emails, <n.mailing_list_options_link.>visite esta página</n.mailing_list_options_link.>.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>Ignorar o cabeçalho X-No-Archive</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>Eu já li e concordo com os <n.terms_link.>Termos de Uso</n.terms_link.> da Nabble.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>A imagem não foi enviada: <n.image/> (por favor, envie-a novamente ou remova a tag)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>Eu sou um assinante, deixe-me postar agora</to></translation>
+<translation><from>Incorrect Login!</from><to>Dados incorretos!</to></translation>
+<translation><from>Individual emails</from><to>Emails individuais</to></translation>
+<translation><from>Inherit</from><to>Herdado</to></translation>
+<translation><from>In Reply To</from><to>Em Resposta a</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>Em resposta à <n.parent_link.>esta mensagem</n.parent_link.> postada por <n.author/></to></translation>
+<translation><from>Insert</from><to>Inserir</to></translation>
+<translation><from>Insert Image</from><to>Inserir Imagem</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>Ao invés de postar pela interface web, você também pode postar novos tópicos enviando emails para o seguinte endereço eletrônico:</to></translation>
+<translation><from>in <t.location/></from><to>em <n.location/></to></translation>
+<translation><from>Invalid Code</from><to>Código Inválido</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>Endereço de email inválido: <n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>Número inválido de dias, precisa ser inteiro.</to></translation>
+<translation><from>invisible user</from><to>usuário invisível</to></translation>
+<translation><from>invisible users</from><to>usuários invisíveis</to></translation>
+<translation><from>Invite Subscribers</from><to>Convidar Assinantes</to></translation>
+<translation><from>is:</from><to>é:</to></translation>
+<translation><from>is not:</from><to>não é:</to></translation>
+<translation><from>is within the last:</from><to>está dentro dos últimos:</to></translation>
+<translation><from>Italic</from><to>Itálico</to></translation>
+<translation><from>item</from><to>item</to></translation>
+<translation><from>items</from><to>itens</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>NÃO será possível restaurar esses itens apagados depois.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>Apenas cole o código (fornecido pelos sites acima) entre essas tags e você está pronto para postá-lo.</to></translation>
+<translation><from>Last Post</from><to>Última Mensagem</to></translation>
+<translation><from>Learn more</from><to>Saiba mais</to></translation>
+<translation><from>Leave a comment</from><to>Deixe um comentário</to></translation>
+<translation><from>Link</from><to>Link</to></translation>
+<translation><from>Link to <t.location/></from><to>Link para <n.location/></to></translation>
+<translation><from>List</from><to>Lista</to></translation>
+<translation><from>List of Subcategories</from><to>Lista de Subcategorias</to></translation>
+<translation><from>List Server</from><to>Servidor da Lista</to></translation>
+<translation><from>List View</from><to>Visão em Lista</to></translation>
+<translation><from>Loading...</from><to>Carregando...</to></translation>
+<translation><from>Location</from><to>Localização</to></translation>
+<translation><from>Locked</from><to>Travado</to></translation>
+<translation><from>Lock topic</from><to>Travar tópico</to></translation>
+<translation><from>Login</from><to>Entrar</to></translation>
+<translation><from>Log is empty</from><to>O Log está vazio</to></translation>
+<translation><from>Log out</from><to>Sair</to></translation>
+<translation><from>Lowest</from><to>Baixíssima</to></translation>
+<translation><from>Low</from><to>Baixa</to></translation>
+<translation><from>Mailing List Address</from><to>Endereço da Lista de Emails</to></translation>
+<translation><from>Mailing list archive settings</from><to>Configurações de arquivo de lista de emails</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>Configurações de Arquivo de Lista de Emails</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>Lembrete de Assinatura da Lista de Emails</to></translation>
+<translation><from>Mailing List Website</from><to>Página da Lista de Emails na Internet</to></translation>
+<translation><from>Main Page</from><to>Página Principal</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>Tenha certeza que você está usando o mesmo navegador que você usou quando preencheu os dados de registro.</to></translation>
+<translation><from>Manage banned users</from><to>Gerenciar usuários banidos</to></translation>
+<translation><from>Manage pinned topics</from><to>Gerenciar tópicos fixos</to></translation>
+<translation><from>Manage subscribers</from><to>Gerenciar assinantes</to></translation>
+<translation><from>Manage Subscribers</from><to>Gerenciar Assinantes</to></translation>
+<translation><from>Manage <t.items/></from><to>Gerenciar <n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>Gerenciar usuários & grupos</to></translation>
+<translation><from>Manage Users & Groups</from><to>Gerenciar Usuários & Grupos</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>Publicidade em massa, texto enganoso, miniatura enganosa, golpes ou fraudes.</to></translation>
+<translation><from>max. 80 characters</from><to>máx. 80 caracteres</to></translation>
+<translation><from>Message date</from><to>Data da mensagem</to></translation>
+<translation><from>message</from><to>mensagem</to></translation>
+<translation><from>Message</from><to>Mensagem</to></translation>
+<translation><from>Message is in HTML Format</from><to>Mensagem está no formato HTML</to></translation>
+<translation><from>messages</from><to>mensagens</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>Mensagens postadas aqui serão enviadas para a lista de emails.</to></translation>
+<translation><from>Message subject contains</from><to>Assunto da mensagem contém</to></translation>
+<translation><from>Message text contains</from><to>Texto da mensagem contém</to></translation>
+<translation><from>Mixed</from><to>Variado</to></translation>
+<translation><from>Modified</from><to>Modificado</to></translation>
+<translation><from>Archives</from><to>Arquivos</to></translation>
+<translation><from>More Categories</from><to>Mais categorias</to></translation>
+<translation><from>More</from><to>Mais</to></translation>
+<translation><from>more help</from><to>mais ajuda</to></translation>
+<translation><from>more options</from><to>mais opções</to></translation>
+<translation><from>Move post</from><to>Mover mensagem</to></translation>
+<translation><from>Move Post</from><to>Mover Mensagem</to></translation>
+<translation><from>Move topic</from><to>Mover tópico</to></translation>
+<translation><from>My Nabble Applications</from><to>Meus aplicativos da Nabble</to></translation>
+<translation><from>My Pending Posts</from><to>Minhas Mensagens Pendentes</to></translation>
+<translation><from>My posts</from><to>Minhas mensagens</to></translation>
+<translation><from>Nabble Support</from><to>Suporte da Nabble</to></translation>
+<translation><from>Name</from><to>Nome</to></translation>
+<translation><from>New Post</from><to>Nova Mensagem</to></translation>
+<translation><from>news</from><to>jornal</to></translation>
+<translation><from>News</from><to>Jornal</to></translation>
+<translation><from>New Topic</from><to>Novo Tópico</to></translation>
+<translation><from>New topics only</from><to>Somente novos tópicos</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>CUIDADO</n.important.>: Tudo em <n.location/> será apagado para sempre!</to></translation>
+<translation><from>No banned users.</from><to>Nenhum usuário banido.</to></translation>
+<translation><from>No Filter</from><to>Nenhum Filtro</to></translation>
+<translation><from>none of the words:</from><to>nenhuma das palavras:</to></translation>
+<translation><from>No registered user found with this email.</from><to>Nenhum usuário registrado foi achado com esse email.</to></translation>
+<translation><from>No replies</from><to>Nenhuma resposta</to></translation>
+<translation><from>Normal</from><to>Normal</to></translation>
+<translation><from>No sub-forums</from><to>Nenhum sub-fórum</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>Note que esse endereço é único para você e aceita apenas emails enviados por <n.address/>. O objetivo deste recurso é prevenir lixo eletrônico (spam).</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to><n.register_link.>Registre-se agora</n.register_link.> se você quer editar o seu perfil, receber mensagens por email, controlar seus itens favoritos ou acessar o seu perfil global.</to></translation>
+<translation><from>one email per input box</from><to>um email por caixa de entrada</to></translation>
+<translation><from>Online Users</from><to>Usuários Conectados</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>Somente usuários autorizados podem entrar nesta área.</to></translation>
+<translation><from>Open this post in classic view</from><to>Abra essa mensagem na visão clássica</to></translation>
+<translation><from>Open this post in list view</from><to>Abra essa mensagem na visão em lista</to></translation>
+<translation><from>Open this post in threaded view</from><to>Abra essa mensagem na visão em árvore</to></translation>
+<translation><from>Options</from><to>Opções</to></translation>
+<translation><from>or</from><to>ou</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>Ou você pode ignorar esse email se é melhor manter esse usuário longe daquela área.</to></translation>
+<translation><from>Other Settings</from><to>Outras Configurações</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>Página que agrupa sub-aplicações e discussões.</to></translation>
+<translation><from>Page <t.number/></from><to>Página <n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>Página com uma lista simples de sub-aplicações e discussões.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>Página com mensagens inteiras e comentários relacionados.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>Página com notícias e mensagens.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>Página com tópicos e discussões.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>Página com tópicos agrupados por sub-aplicações. Se nenhuma sub-aplicação existir, uma lista normal de tópicos é mostrada.</to></translation>
+<translation><from>Password</from><to>Senha</to></translation>
+<translation><from>Password Sent</from><to>Senha Enviada</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>Pedofilia, violencia contra menores ou outros abusos.</to></translation>
+<translation><from>People</from><to>Pessoas</to></translation>
+<translation><from>People in <t.location/></from><to>Pessoas em <n.location/></to></translation>
+<translation><from>Permalink</from><to>Link permanente</to></translation>
+<translation><from>Photo and image gallery.</from><to>Galeria de foto e imagem.</to></translation>
+<translation><from>Pinned sub-forum</from><to>Sub-fórum fixo</to></translation>
+<translation><from>Pin topic</from><to>Fixar tópico</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>Por favor guarde o link acima ou salve este email para que você possa encontrar facilmente <n.o_seu.app/> no futuro.</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>Por favor verifique o seu email agora e ative a sua conta para que você tenha acesso a todas as funcionalidades.</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>Por favor, clique no link de confirmação abaixo para ativar a sua conta:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>Por favor, entre em contato com o suporte da Nabble se você precisar de ajuda.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>Por favor, entre em contato com os administradores caso você precise de ajuda.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>Por favor, informe um endereço de email correto e tente de novo.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>Por favor, siga as instruções no email para completar o processo de registro.</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>Por favor, siga as <n.subscribe_instructions_link.>instruções de inscrição</n.subscribe_instructions_link.> para esse arquivo.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>Por favor, informe um link permanente válido.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>Por favor, entre novamente seu email e senha e aperte Entrar.</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>Por favor, respeite a etiqueta das listas de emails</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>Enquetes do Polldaddy.com (apenas enquetes em flash)</to></translation>
+<translation><from>Post by email</from><to>Postar por email</to></translation>
+<translation><from>Post by Email</from><to>Postar por Email</to></translation>
+<translation><from>Post Count</from><to>Número de Mensagens</to></translation>
+<translation><from>Posted by <t.author/></from><to>Postado por <n.author/></to></translation>
+<translation><from>post</from><to>mensagem</to></translation>
+<translation><from>Post Message</from><to>Postar Mensagem</to></translation>
+<translation><from>Post New Message</from><to>Postar Nova Mensagem</to></translation>
+<translation><from>Post new message in <t.location/></from><to>Postar nova mensagem em <n.location/></to></translation>
+<translation><from>posts</from><to>mensagens</to></translation>
+<translation><from>Posts</from><to>Mensagens</to></translation>
+<translation><from>Posts in <t.location/></from><to>Mensagens em <n.location/></to></translation>
+<translation><from>Preview Message</from><to>Prever Mensagem</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>Os preços estão em Dólares dos Estados Unidos. Esse processo de compra utiliza o <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, que permite aos clientes da Amazon usar os métodos de pagamento definidos em suas contas na Amazon.com para pagar por produtos ou serviços em websites e aplicações que aceitam o Amazon Payments.</to></translation>
+<translation><from>Print post</from><to>Imprimir mensagem</to></translation>
+<translation><from>Priority</from><to>Prioridade</to></translation>
+<translation><from>(private)</from><to>(privativo)</to></translation>
+<translation><from>Profile of <t.author/></from><to>Perfil de <n.author/></to></translation>
+<translation><from>Quote</from><to>Citar</to></translation>
+<translation><from>Quote the original message</from><to>Citar a mensagem original</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>Cite os textos que você está respondendo e reduza-os para partes relevantes apenas. Isso oferece contexto para aqueles que irão ler sua mensagem por email.</to></translation>
+<translation><from>Raw mail</from><to>Email Original</to></translation>
+<translation><from>Raw text</from><to>Texto puro</to></translation>
+<translation><from>read more</from><to>leia mais</to></translation>
+<translation><from>Read more</from><to>Leia mais</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>Lista somente-leitura com todos os usuários que possuem email em <n.location/>.</to></translation>
+<translation><from>Receive direct replies only.</from><to>Receber apenas respostas diretas.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>Receber cada mensagem postada em <n.location/>.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>Receber cada resposta sob este tópico.</to></translation>
+<translation><from>Receive new topics only.</from><to>Receber apenas novos tópicos.</to></translation>
+<translation><from>Refresh</from><to>Recarregar</to></translation>
+<translation><from>Registered</from><to>Registrado</to></translation>
+<translation><from>Registered Users</from><to>Usuários Registrados</to></translation>
+<translation><from>Register</from><to>Registre-se</to></translation>
+<translation><from>Registering...</from><to>Registrando...</to></translation>
+<translation><from>Register Now</from><to>Registre-se Agora</to></translation>
+<translation><from>Register to <t.app/></from><to>Registre-se em <n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>Registro Confirmado</to></translation>
+<translation><from>Registration Failed</from><to>O Registro Falhou</to></translation>
+<translation><from>Related Help Article</from><to>Artigo de Ajuda Relacionado</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>Lembre-se que a ação de banir não é eficiente porque o usuário pode sempre voltar com uma conta diferente.</to></translation>
+<translation><from>Remove Ads</from><to>Remover Anúncios</to></translation>
+<translation><from>remove</from><to>remover</to></translation>
+<translation><from>Remove Settings</from><to>Remover Configurações</to></translation>
+<translation><from>Remove Subscription</from><to>Remover Inscrição</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>Remova sua conta e todos os seus posts de <n.subject/>.</to></translation>
+<translation><from>Remove Your Account</from><to>Remover Sua Conta</to></translation>
+<translation><from>replies</from><to>respostas</to></translation><!-- noun / plural -->
+<translation><from>Replies</from><to>Respostas</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>Responder</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>resposta</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>Responder ao autor</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>Denunciar Conteúdo como Inapropriado</to></translation>
+<translation><from>Report Now</from><to>Denunciar Agora</to></translation>
+<translation><from>required</from><to>obrigatório</to></translation>
+<translation><from>Return to <t.location/></from><to>Retornar para <n.location/></to></translation>
+<translation><from>Save Changes</from><to>Salvar Alterações</to></translation>
+<translation><from>Save Settings</from><to>Salvar Configurações</to></translation>
+<translation><from>Save Subscription</from><to>Salvar Inscrição</to></translation>
+<translation><from>Search</from><to>Procurar</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>Selecione abaixo a ação que você deseja tomar:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>Selecione a categoria que reflete melhor a sua preocupação sobre o conteúdo desta página.</to></translation>
+<translation><from>Send email to me</from><to>Envie email para mim</to></translation>
+<translation><from>Send Email to <t.author/></from><to>Enviar email para <n.author/></to></translation>
+<translation><from>Send Request</from><to>Enviar Pedido</to></translation>
+<translation><from>Send To:</from><to>Enviar para:</to></translation>
+<translation><from>Sexual content</from><to>Conteúdo sexual</to></translation>
+<translation><from>Show</from><to>Mostrar</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>Mostrar recado da Nabble</to></translation>
+<translation><from>Sincerely,</from><to>Atenciosamente,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>Como esta aplicação é um arquivo de lista de emails, por favor remova a inscrição do email abaixo antes de clicar no botão de apagar.</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>Como você não é um usuário registrado, precisamos checar que você é humano.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>Algumas mensagens suas foram apagadas de <n.location/> e nós estamos lhe enviando cópias para que você tenha uma chance de salvá-las.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>Desculpe, mas somente membros podem postar mensagens em <n.app/>.</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>Desculpe, mas os administradores baniram você.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>Desculpe, mas este email não está autorizado a ver mensagens em <n.location/>.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>Desculpe, mas você não pode criar novos tópicos aqui.<br/>Note que você talvez ainda possa responder mensagens.</to></translation>
+<translation><from>Sort by date</from><to>Ordenar por data</to></translation>
+<translation><from>Sort by relevance</from><to>Ordenar por relevância</to></translation>
+<translation><from>Sorted by date</from><to>Ordenado por data</to></translation>
+<translation><from>Sorted by relevance</from><to>Ordenado por relevância</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[Detector de Spam] Mensagem inválida com muitas palavras '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[Detector de Spam] Mensagem não pode conter '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[Detector de Spam] Mensagem contém palavras comuns de spam.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[Detector de Spam] Assunto não pode conter '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[Detector de Spam] Assunto contém palavras comuns de spam.</to></translation>
+<translation><from>Spam</from><to>Spam</to></translation>
+<translation><from>Stars in <t.location/></from><to>Favoritos em <n.location/></to></translation>
+<translation><from>Structure</from><to>Estrutura</to></translation>
+<translation><from>Subcategories</from><to>Subcategorias</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>Subcategorias abaixo de <n.location/></to></translation>
+<translation><from>Subcategory</from><to>Subcategoria</to></translation>
+<translation><from>Sub-Forum</from><to>Sub-fórum</to></translation>
+<translation><from>Sub-Forums</from><to>Sub-fórums</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>Sub-fórums & Tópicos</to></translation>
+<translation><from>Subject</from><to>Assunto</to></translation>
+<translation><from>Subscribe</from><to>Inscrever</to></translation>
+<translation><from>subscriber</from><to>assinante</to></translation>
+<translation><from>subscribers</from><to>assinantes</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>Inscreva-se em <n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>Inscreva-se via email</to></translation>
+<translation><from>Subscription Confirmation</from><to>Confirmação de Inscrição</to></translation>
+<translation><from>Subscription Confirmed</from><to>Inscrição Confirmada</to></translation>
+<translation><from>Subscription Format</from><to>Formato da Inscrição</to></translation>
+<translation><from>Subscription Removed</from><to>Inscrição Removida</to></translation>
+<translation><from>Subscription Results</from><to>Resultados da Inscrição</to></translation>
+<translation><from>Subscription Type</from><to>Tipo de Inscrição</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>Sucesso: um email de confirmação foi enviado para você.</to></translation>
+<translation><from>Success</from><to>Sucesso</to></translation>
+<translation><from>Take Action</from><to>Tomar Ação</to></translation>
+<translation><from><t.app/> Registration</from><to>Registro em <n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to><n.author/> foi banido com sucesso.</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to><n.author/> foi liberado com sucesso.</to></translation>
+<translation><from>Tell me more & show examples</from><to>Fale-me mais & mostre exemplos</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>Obrigado por se registrar em <n.app/>!</to></translation>
+<translation><from>Thank You</from><to>Obrigado</to></translation>
+<translation><from>The author has deleted this message.</from><to>O autor apagou esta mensagem.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>O código na URL não é válido.</to></translation>
+<translation><from>the exact phrase:</from><to>a frase exata:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>Esta lista de emails talvez exija a sua inscrição antes de aceitar a sua mensagem. Por favor note que o registro no site da Nabble NÃO inscreve você automaticamente nesta lista de email. Se você ainda não se inscreveu, faça-o agora. Se você não sabe ou não se lembra, inscreva-se novamente porque não há nada a perder.</to></translation>
+<translation><from>The Nabble team</from><to>A equipe da Nabble</to></translation>
+<translation><from>The name of the group is not valid.</from><to>O nome do grupo não é valido.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>O novo pai não pode ser a própria mensagem.</to></translation>
+<translation><from>The password fields don't match.</from><to>Os campos com senha não estão iguais.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>Existe uma conta de usuário não registrado associada com o endereço de email <n.email/>.</to></translation>
+<translation><from>The subject is required.</from><to>O assunto é obrigatório.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>O usuário vai receber uma cópia de todas as mensagens apagadas por email para que ele tenha uma chance de salvá-las.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>O código de validação não corresponde com a imagem.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>Este ramo é muito grande e algumas mensagens foram omitidas. Utilize as outras visões para ler todas as mensagens.</to></translation>
+<translation><from>This email is already subscribed.</from><to>Este email já está inscrito.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>Este fórum é um arquivo para a lista de emails</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>Este fórum é um arquivo/acesso que irá redirecionar sua mensagem para a lista de emails <b><n.mailing_list_address/></b>.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>Isso inclui sub-categorias, mensagens, imagens, arquivos e tudo mais.</to></translation>
+<translation><from>This is a mailing list archive</from><to>Isto é um arquivo de lista de emails</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>Esse é um email automático enviado pela Nabble para confirmar a criação <n.do_seu_novo.app/>. Se você não criou <n.o_mencionado.app/> acima, por favor entre em contato conosco através do fórum de suporte da Nabble.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>Esta lista aceita somente emails de texto simples</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>Essa lista mostra usuários registrados, não registrados e banidos. Usuários anônimos não são listados porque eles não possuem um email e, portanto, não podem fazer parte de um grupo.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>Esta mensagem será enviada de <b><n.from/></b> para a lista de emails <b><n.to/></b>.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to><b>Mensagem Pendente</b>: Esta mensagem ainda não foi aceita pela lista de emails.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>Esta mensagem foi atualizada em <n.date/>.</to></translation>
+<translation><from>This topic has been locked.</from><to>Esse tópico foi travado.</to></translation>
+<translation><from>This topic has been pinned.</from><to>Esse tópico foi fixado.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>Esse tópico foi fixado em <n.location/>.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>Esse tópico foi destravado.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>Esse tópico foi desfixado.</to></translation>
+<translation><from>This topic has unread posts</from><to>Esse tópico possui mensagens não lidas</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>Este tópico está atribuído a você com prioridade <n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>Este usuário não tem permissão para ver esta aplicação (adicione ele/ela a um grupo que permita isso e tente novamente)</to></translation>
+<translation><from>This user name is already in use.</from><to>Esse nome de usuário já está em uso.</to></translation>
+<translation><from>Threaded</from><to>Em Árvore</to></translation>
+<translation><from>Threaded View</from><to>Visão em Árvore</to></translation>
+<translation><from>Time</from><to>Hora</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>DICA: Se o seu arquivo está inscrito na lista de emails e não está funcionando, você pode tentar ignorar o cabeçalho X-No-Archive.</to></translation>
+<translation><from>Tips</from><to>Dicas</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> pediu autorização para entrar em <n.location/>:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> Créditos</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> visualizações de páginas sem anúncios.</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>Para aceitar esse pedido, você deve adicionar o usuário a, pelo menos, um grupo que possua acesso à essa área:</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>Para adicionar <n.esse.app/> ao seu site, copie e cole o seguinte código na sua página HTML:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>Para confirmar a sua inscrição, clique no link abaixo:</to></translation>
+<translation><from>topic</from><to>tópico</to></translation>
+<translation><from>Topics and replies</from><to>Tópicos e respostas</to></translation>
+<translation><from>topics</from><to>tópicos</to></translation>
+<translation><from>Topics</from><to>Tópicos</to></translation>
+<translation><from>Topics only</from><to>Apenas tópicos</to></translation>
+<translation><from>Topics View</from><to>Visão de Tópicos</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>Para prevenir lixo eletrônico (spam), o endereço de email para postar mensagens é <b>único</b> para cada usuário.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>Para remover um grupo, esvazie a área de texto abaixo e salve as mudanças.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>Para ver qual endereço de email você deve usar para postar, por favor <n.login_link.>efetue o login</n.login_link.> ou <n.register_link.>registre-se</n.register_link.>.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>Para criar um novo tópico em <n.location/>, mande um email para <n.p2/></to></translation>
+<translation><from>Total</from><to>Total</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>Para remover sua inscrição de <n.location/></to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> está convidando você a se inscrever em <n.location/>:</to></translation>
+<translation><from>Turn off highlighting</from><to>Desligar marcação</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/> criou uma nova subcategoria</to></translation>
+<translation><from>Unable to Post</from><to>Impedido de Postar</to></translation>
+<translation><from>Unassigned</from><to>Não Atribuído</to></translation>
+<translation><from>Unauthorized</from><to>Não Autorizado</to></translation>
+<translation><from>Unban this user</from><to>Liberar este usuário</to></translation>
+<translation><from>Unban User</from><to>Liberar Usuário</to></translation>
+<translation><from>Unknown or Other</from><to>Desconhecido ou Outro</to></translation>
+<translation><from>Unlock topic</from><to>Destravar tópico</to></translation>
+<translation><from>Unpin topic</from><to>Desfixar tópico</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>Não Registrado / Desativado</to></translation>
+<translation><from>Unregistered</from><to>Não Registrado</to></translation>
+<translation><from>Unregistered User</from><to>Usuário Não Registrado</to></translation>
+<translation><from>Unsubscribe</from><to>Remover inscrição</to></translation>
+<translation><from>Upload a file</from><to>Anexar um arquivo</to></translation>
+<translation><from>User email:</from><to>Email do usuário:</to></translation>
+<translation><from>user</from><to>usuário</to></translation>
+<translation><from>User is online</from><to>Usuário está conectado</to></translation>
+<translation><from>User Name</from><to>Nome do Usuário</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>Usuário pediu autorização para entrar em <n.location/></to></translation>
+<translation><from>users</from><to>usuários</to></translation>
+<translation><from>Users</from><to>Usuários</to></translation>
+<translation><from>Users & Groups</from><to>Usuários & Grupos</to></translation>
+<translation><from>Users that completed the registration process</from><to>Usuários que completaram o processo de registro</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>Use tags como <n.example1/> ou <n.example2/> para criar sub-títulos.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>Use as opções abaixo para especificar precisamente os critérios de busca.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>Use <n.tag_names/> para embutir aplicativos de outros sites.</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>Ver todas as mensagens neste sub-fórum</to></translation>
+<translation><from>view</from><to>visualização</to></translation>
+<translation><from>View mailing list website</from><to>Ver site da lista de emails</to></translation>
+<translation><from>View message</from><to>Ver mensagem</to></translation>
+<translation><from>View more</from><to>Veja mais</to></translation>
+<translation><from>views</from><to>visualizações</to></translation>
+<translation><from>Views</from><to>Visualizações</to></translation>
+<translation><from>Violent or repulsive content</from><to>Conteúdo violento ou repulsivo</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>Visite <n.app/> em:</to></translation>
+<translation><from>visit <t.url/></from><to>visite <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>Nós preparamos uma página com <n.unsubscription_instructions_link.>algumas instruções sobre como remover a inscrição deste arquivo</n.unsubscription_instructions_link.>.</to></translation>
+<translation><from>We will review your report soon.</from><to>Nós vamos revisar a sua denúncia em breve.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>Quais grupos podem ter seus membros listados</to></translation>
+<translation><from>Who can ban/unban users</from><to>Quem pode banir/liberar usuários</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>A quem se pode atribuir tópicos (somente em grupos de trabalho)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>Quem pode mudar a data e a hora das mensagens</to></translation>
+<translation><from>Who can create new topics under this application</from><to>Quem pode criar novos tópicos nesta aplicação</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>Quem pode criar sub aplicações (e.g., sub-fóruns, sub-categorias, etc.)</to></translation>
+<translation><from>Who can edit any content, both applications and posts. Note: Please only use this feature in extreme circumstances. Most users will not like having their posts edited by someone else.</from><to>Quem pode editar qualquer conteúdo, tanto de aplicações ou mensagens. Nota: Use esta funcionalidade somente em circunstâncias extremas. Muitos usuários não vão gostar de ter suas mensagens editadas por outra pessoa.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>Quem pode editar aplicações (e.g., mudar nome, descrição, etc.)</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>Quem pode travar/destravar tópicos nesta aplicação</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>Quem pode gerenciar assinantes desta aplicação</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>Quem pode mover mensagens para outros destinos (e.g., para outros tópicos ou sub-fóruns)</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>Quem pode fixar/desfixar tópicos nesta aplicação</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>Quem pode postar qualquer conteúdo sem restrição (incluindo código javascript, tags &lt;object&gt; and &lt;style&gt;, etc.). <b>Alerta de Segurança</b>: Habilite esta opção somente para usuários que você realmente confia.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>Quem pode responder às mensagens postadas nesta aplicação</to></translation>
+<translation><from>Who can view this application and its contents</from><to>Quem pode visualizar essa aplicação e o seu conteúdo</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>Com a sua inscrição, atualizações serão enviadas diretamente para o seu endereço de email e você poderá respondê-las para participar das discussões. A sua inscrição funciona da mesma forma que uma lista de emails.</to></translation>
+<translation><from>Write Your First Headline</from><to>Escreva a Sua Primeira Notícia</to></translation>
+<translation><from>Write Your First Post</from><to>Escreva a Sua Primeira Mensagem</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>Sim, apague <n.location/> para sempre</to></translation>
+<translation><from>Yes, unsubscribe now</from><to>Sim, remover inscrição agora</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>Você já está inscrito em <n.location/>.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>Você não está inscrito em <n.location/>.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>Você também pode <n.manage_banned_users_link.>gerenciar usuários banidos</n.manage_banned_users_link.> em <n.location/>.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>Você também pode <n.root_node.change_permissions_link.>mudar as permissões</n.root_node.change_permissions_link.> dos grupos abaixo.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>Você também pode promover <n.o_seu.app/> enviando o link para os seus amigos, embutindo-o no seu website ou conversando sobre isso em outros fóruns.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>Você não pode mover essa mensagem para esse destino porque o novo pai não aceita usuários anônimos. </to></translation>
+<translation><from>You Cannot Post Here</from><to>Você Não Pode Postar Aqui</to></translation>
+<translation><from>(you can reply by email)</from><to>(você pode responder por email)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>Você não pode mover a mensagem para lugar algum.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>Você não pode postar mensagens aqui, mas pode postar em outros lugares.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>Você pode tentar <n.register_link.>registrando-se novamente</n.register_link.> ou entrar em contato com <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>Você pode usar o formulário abaixo para enviar um pedido para os administradores.</to></translation>
+<translation><from>You have already been registered.</from><to>Você já está registrado.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>Você foi convidado para se inscrever em <n.location/>, que está disponível em:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>Você foi registrado em <n.subject/>.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>Sua inscrição em <n.location/> foi removida</to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>Você criou muitos posts em um período muito curto de tempo. Por favor, tente mais tarde.</to></translation>
+<translation><from>You logged out</from><to>Você saiu da sua conta</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>Você talvez tenha que <n.mailing_list_options_link.>inscrever-se nesta lista de emails</n.mailing_list_options_link.> para que a sua mensagem seja aceita.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>Você pode <n.page_node.unauthorized_link.>pedir permissão para postar</n.page_node.unauthorized_link.> aqui ou contactar <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> se você possui perguntas.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>Você precisa concordar com os Termos de Uso.</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>Informe um email válido para esta lista de emails.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>Informe um website válido para esta lista de emails.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>Você precisa preencher todos os campos abaixo.</to></translation>
+<translation><from>You must login to view this page.</from><to>Você precisa efetuar o login para visualizar esta página.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>Você precisa efetuar o login para visualizar <n.subject/>.</to></translation>
+<translation><from>You must login to your account.</from><to>Você precisar entrar com a sua conta.</to></translation>
+<translation><from>You must provide a user name.</from><to>Você precisa fornecer um nome de usuário.</to></translation>
+<translation><from>You're not a subscriber</from><to>Você não é um assinante</to></translation>
+<translation><from>Your Name</from><to>Seu Nome</to></translation>
+<translation><from>Your request has been successfully sent.</from><to>Seu pedido foi enviado com sucesso.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>A sua inscrição foi salva com sucesso.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>Sua inscrição em <n.location/> foi removida. Se isso foi um engano, você pode se inscrever novamente usando o link abaixo:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>Sua inscrição em <n.location/> foi removida com sucesso.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to><n.Seu_foi_criado.app/> com sucesso.</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>Você precisará de autorização para postar novos tópicos em <n.location/>, portanto, além do registro, você terá que ser aprovado pelos administradores.</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>Você vai receber um email para cada nova mensagem postada neste tópico.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>Você vai receber um email com um link para ativar a sua conta.</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>Sua inscrição</to></translation>
+<translation><from>edit</from><to>editar</to></translation>
+<translation><from>Remove ads</from><to>Remover anúncios</to></translation>
+<translation><from>View profile of <t.author/></from><to>Ver o perfil de <n.author/></to></translation>
+<translation><from>Description</from><to>Descrição</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>Videos do Youtube, Vimeo e LiveLeak.</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>Usuários Banidos em <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>Prefixo do Assunto</to></translation>
+
+#NEW 2
+<translation><from>Change title and meta tags</from><to>Mudar título e meta tags</to></translation>
+<translation><from>Here you can customize the title and meta tags of the <t.location/> page.</from><to>Aqui você pode customizar o título e as meta tags de <n.location/>.</to></translation>
+<translation><from>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</from><to>As meta informações abaixo podem ajudar você a otimizar esta página para sites de busca (tipo google, yahoo!, etc.)</to></translation>
+<translation><from>Use custom values</from><to>Usar valores customizados</to></translation>
+<translation><from>Page Title</from><to>Título da Página</to></translation>
+<translation><from>Enter a context-rich title that clearly identifies this page.</from><to>Informe um título rico em contexto que claramente identifique essa página.</to></translation>
+<translation><from>The title should ideally be less than 70 characters in length.</from><to>O título idealmente deve possuir um comprimento menor que 70 caracteres.</to></translation>
+<translation><from>Meta Description</from><to>Meta Descrição</to></translation>
+<translation><from>Enter a brief and concise summary of your page's content.</from><to>Informe um resumo breve e conciso sobre o conteúdo desta página.</to></translation>
+<translation><from>Limit your description to 155 characters or 170 characters at most.</from><to>Limite a sua descrição em 155 caracteres ou 170 caracteres no máximo.</to></translation>
+<translation><from>Mailing List Archive</from><to>Arquivo de Lista de Emails</to></translation>
+<translation><from>Filter: priority <t.priority/></from><to>Filtro: prioridade <n.priority/></to></translation>
+<translation><from>Filter: assignee <t.author/></from><to>Filtro: encarregado <n.author/></to></translation>
+<translation><from>Use Google Analytics</from><to>Usar o Google Analytics</to></translation>
+<translation><from>Analytics Account ID:</from><to>ID da Conta do Analytics:</to></translation>
+<translation><from>(e.g., UA-12345-0)</from><to>(exemplo: UA-12345-0)</to></translation>
+<translation><from>Enter a valid analytics account ID.</from><to>Informe um ID válido para a conta do analytics.</to></translation>
+<translation><from>Here you can use Google Analytics to measure the success of your app.</from><to>Aqui você pode usar o Google Analytics para medir o sucesso da sua aplicação.</to></translation>
+<translation><from>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</from><to>Informe abaixo o ID da sua conta no analytics e você poderá acompanhar visitas, visitantes e outras estatísticas importantes sobre o seu tráfego da web.</to></translation>
+
+<translation><from>Digest Email</from><to>Email de Resumo Diário</to></translation>
+<translation><from>on <t.date/></from><to>em <n.date/></to></translation>
+<translation><from>DO NOT REPLY TO THIS EMAIL</from><to>NÃO RESPONDA A ESTE EMAIL</to></translation>
+<translation><from>Replies sent to this address are not read or processed.</from><to>Respostas enviadas para este endereço não são lidas ou processadas.</to></translation>
+<translation><from>If you want to respond to a post for which you received this email, please go to the website: <t.url/></from><to>Se você quiser responder a alguma mensagem recebida neste email, por favor visite o website: <n.url/></to></translation>
+<translation><from>new post</from><to>nova mensagem</to></translation><!-- usage example: "1 new post"-->
+<translation><from>new posts</from><to>novas mensagens</to></translation><!-- usage example: "2 new posts"-->
+
+<translation><from>New registered user in <t.location/>!</from><to>Novo usuário registrado em <n.location/>!</to></translation>
+<translation><from>User profile</from><to>Perfil do usuário</to></translation>
+<translation><from>New user:</from><to>Novo usuário:</to></translation>
+
+<translation><from><n.author/> wrote</from><to><n.author/> escreveu</to></translation>
+<translation><from>You still have <t.number/> day(s) left without advertising</from><to>Você ainda tem <n.number/> dia(s) sem anúncios.</to></translation>
+<translation><from>(Only administrators can see this message)</from><to>(Somente administradores podem ver esta mensagem)</to></translation>
+
+<translation><from>Some private items have been omitted because you don't have permission to view them.</from><to>Alguns itens privados foram omitidos porque você não possui permissão para vê-los.</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Submit". We will email you a link to reset your password.</from><to>Por favor, informe o endereço de email que você usou para se registrar e clique em "Enviar". Nós enviaremos por email um link para você redefinir a sua senha.</to></translation>
+<translation><from>Submit</from><to>Enviar</to></translation>
+<translation><from>Password Reset Sent</from><to>Redefinição de Senha Enviada</to></translation>
+<translation><from>We have sent you a link to reset your password. Please check your email now. If you don't receive the instructions in a few minutes, check your spam folder or try to resend the request.</from><to>Nós enviamos para você um link para redefinir a sua senha. Por favor, verifique o seu email agora. Se você não receber as instruções em alguns minutos, verifique a pasta de spam ou lixo eletrônico, ou tente reenviar a solicitação.</to></translation>
+<translation><from>Reset your password / <t.location/></from><to>Redefinir sua senha / <n.location/></to></translation>
+<translation><from>We received a request to reset your password in <t.location/>.</from><to>Nós recebemos uma solicitação para redefinir sua senha em <n.location/>.</to></translation>
+<translation><from>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</from><to>Se você quer mudar a sua senha, clique no link abaixo (ou cole e copie o link no seu navegador):</to></translation>
+<translation><from>If you don't want to reset your password, please ignore this message. Your password will not be reset.</from><to>Se você não quer mudar a sua senha, por favor ignore esta mensagem. A senha não será mudada.</to></translation>
+
+<translation><from><b>IMPORTANT:</b> If you use this option, messages posted in the archive will NOT be sent to the mailing list.</from><to><b>IMPORTANTE:</b> Se você usar essa opção, mensagens postadas no arquivo NÃO serão enviadas para a lista de emails.</to></translation>
+<translation><from>Message Preview</from><to>Visualização da Mensagem</to></translation>
+<translation><from>Your changes will not be sent to the mailing list.</from><to>As suas mudanças não serão enviadas para a lista de emails.</to></translation>
+<translation><from>If you want others in the mailing list to know of your changes, please compose a new message or reply to your original message.</from><to>Se você quiser que outros saibam das suas mudanças, por favor escreva uma nova mensagem ou responda à sua mensagem original. </to></translation>
+
+<translation><from>Poll</from><to>Enquete</to></translation>
+<translation><from>Add new poll</from><to>Adicionar nova enquete</to></translation>
+<translation><from>Question:</from><to>Pergunta:</to></translation>
+<translation><from>Answers:</from><to>Respostas:</to></translation>
+<translation><from>Add new answer</from><to>Adicionar nova resposta</to></translation>
+<translation><from>1 vote</from><to>1 voto</to></translation>
+<translation><from><t.number/> votes</from><to><n.number/> votos</to></translation>
+<translation><from>Total votes:</from><to>Total de votos:</to></translation>
+<translation><from>Vote</from><to>Votar</to></translation><!-- verb -->
+<translation><from>Your vote has been submitted.</from><to>O seu voto foi enviado.</to></translation>
+<translation><from>This poll is closed.</from><to>Essa enquete está fechada.</to></translation>
+<translation><from>This poll ends on <t.date/>.</from><to>Essa enquete termina em <n.date/></to></translation>
+<translation><from>This poll ended on <t.date/>.</from><to>Essa enquete terminou em <n.date/></to></translation>
+<translation><from>Results will be shown only after poll has ended.</from><to>Os resultados só serão revelados depois que a enquete terminar.</to></translation>
+<translation><from>You have to vote before you can see the results.</from><to>Você precisa votar antes de ver os resultados.</to></translation>
+<translation><from>You cannot change your vote after voting.</from><to>Você não pode mudar o seu voto depois de votar.</to></translation>
+<translation><from>You can select up to <t.number/> options.</from><to>Você pode selecionar até <n.number/> opções.</to></translation>
+<translation><from>Please select no more than <t.number/> options.</from><to>Por favor, selecione não mais do que <n.number/> opções.</to></translation>
+<translation><from>Please select at least one option.</from><to>Por favor, selecione pelo menos uma opção.</to></translation>
+<translation><from>Remove Poll</from><to>Remover Enquete</to></translation>
+<translation><from>Invalid poll parameters.</from><to>Parâmetros inválidos da enquete</to></translation>
+<translation><from>Poll duration must be a non-negative integer or blank.</from><to>A duração da enquete deve ser um número inteiro não-negativo ou um valor em branco.</to></translation>
+<translation><from>Poll maximum allowed choices must be a non-negative integer or blank.</from><to>O número máximo de escolhas permitidas deve ser um número inteiro não-negativo ou um valor em branco.</to></translation>
+<translation><from>Delete this poll, including all votes?</from><to>Apagar esta enquete, incluindo todos os votos?</to></translation>
+<translation><from>Poll has been deleted.</from><to>A enquete foi apagada.</to></translation>
+<translation><from>Who can create polls.</from><to>Quem pode criar enquetes.</to></translation>
+<translation><from>Allow vote changes</from><to>Permitir mudanças de votos</to></translation>
+<translation><from>Allow viewing results before end date (poll creators can always view the results)</from><to>Permitir visualização dos resultados antes da data to término (criadores de enquetes sempre podem ver os resultados)</to></translation>
+<translation><from>Allow viewing results before vote</from><to>Permitir visualização dos resultados antes do voto</to></translation>
+<translation><from>Multiple selections allowed:</from><to>Multiplas seleções permitidas:</to></translation>
+<translation><from>Poll ends after <t.number/> days (leave blank for unlimited).</from><to>Enquete termina depois de <n.number/> dias (deixe em branco para ilimitado).</to></translation>
+<translation><from>Login to vote</from><to>Efetue o login para votar</to></translation>
+<translation><from>This message has a poll</from><to>Esta mensagem possui uma enquete</to></translation>
+<translation><from>Visit the link below if you want to participate:</from><to>Visite o link abaixo se você quiser participar:</to></translation>
+
+<translation><from>Current length: <t.number/> characters</from><to>Comprimento atual: <n.number/> caracteres</to></translation>
+<translation><from>Edit Post</from><to>Editar Mensagem</to></translation>
+<translation><from><t.location/> - Your credits are running out</from><to><n.location/> - Seus créditos estão acabando</to></translation>
+<translation><from><t.location/> is running out of ad-free credits.</from><to><n.location/> está ficando sem créditos para remoção de anúncios.</to></translation>
+<translation><from>If you want to buy more credits, visit:</from><to>Se você quiser comprar mais créditos, visite:</to></translation>
+<translation><from>only in this topic</from><to>apenas neste tópico</to></translation>
+<translation><from>everywhere</from><to>em todos os lugares</to></translation>
+
+<translation><from>If you want to invite subscribers, please request this feature in the <n.support_link/> forum.</from><to>Se você quiser convidar assinantes, por favor peça essa funcionalidade no fórum de <n.support_link/>.</to></translation>
+<translation><from>We can install this module for you, but the Nabble team must approve this request to prevent abuse and spam.</from><to>Nós podemos instalar esse módulo para você, mas a equipe da Nabble precisa aprovar o pedido para evitar abusos e envio de spam.</to></translation>
+
+<translation><from>Edit Signature</from><to>Editar Assinatura</to></translation>
+<translation><from>Current Signature</from><to>Assinatura Atual</to></translation>
+<translation><from>Save Signature</from><to>Salvar Assinatura</to></translation>
+<translation><from>Signature is in HTML format</from><to>Assinatura está no formato HTML</to></translation>
+
+<translation><from>Download backup</from><to>Baixar cópia de segurança</to></translation>
+<translation><from>Backup</from><to>Cópia de Segurança</to></translation>
+<translation><from>Here you can download a backup of <t.location/>.</from><to>Aqui você pode baixar uma cópia de segurança com os dados de <n.location/>.</to></translation>
+<translation><from>When you press the button below, the system will start the generation of the backup file and this may take some minutes to finish. You will receive an email with a link to download the file when it is ready.</from><to>Quando você apertar o botão abaixo, o sistema vai iniciar a geração de uma cópia de segurança e isso pode levar alguns minutos. Você vai receber um email com o link do download quando o arquivo estiver pronto.</to></translation>
+<translation><from>Generate backup file</from><to>Gerar cópia de segurança</to></translation>
+<translation><from>The system is generating the backup now. When it is finished, a link to the backup file will be emailed to you.</from><to>O sistema está gerando a cópia de segurança agora. Quando ela estiver pronta, o link para download será enviado para você por email. </to></translation>
+<translation><from>The backup file is generated with the <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a> tool, which is an open source project in Java. You should visit the project website if you want to restore your backup into a Postgresql database.</from><to>A cópia de segurança é gerada com a ferramenta <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a>, que é um projeto open source em Java. Você deve visitar a página do projeto se quiser restaurar a sua cópia de segurança em um banco de dados Postgresql.</to></translation>
+<translation><from>Backup of <t.location/></from><to>Cópia de segurança de <n.location/></to></translation>
+<translation><from>Here is your backup file:</from><to>Aqui está a sua cópia de segurança:</to></translation>
+<translation><from><t.location/> has been deleted</from><to><n.location/> foi apagado</to></translation>
+<translation><from>Your Nabble site "<t.location/>" has been deleted.</from><to>O seu site da Nabble "<n.location/>" foi apagado.</to></translation>
+<translation><from>You can download a backup of this site from the link below. Nabble will try to keep this backup available for a few months, but this is not guaranteed. If this content is important to you, save this copy as soon as possible.</from><to>Você pode baixar uma cópia de segurança desse site no link abaixo. A Nabble tentará manter essa cópia disponível por alguns meses, mas isso não é garantido. Se esse conteúdo é importante para você, salve essa cópia o mais rápido possível.</to></translation>
+
+<translation><from>Buy Credits</from><to>Comprar Créditos</to></translation>
+<translation><from>Buy credits</from><to>Comprar créditos</to></translation>
+<translation><from>Saving, Please wait...</from><to>Salvando, Por favor aguarde...</to></translation>
+<translation><from>How should the credits be used?</from><to>Como os créditos devem ser usados?</to></translation>
+<translation><from>Use credits to hide ads from all users</from><to>Usar créditos para esconder os anúncios de todos os usuários</to></translation>
+<translation><from>Use credits to hide ads from registered users only</from><to>Usar créditos para esconder os anúncios apenas de usuários registrados</to></translation>
+<translation><from>Here you can buy credits for <t.location/>. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>Aqui você pode comprar créditos para <n.location/>. Cada crédito representa uma visualização de página sem publicidade. Os visitantes irão ver páginas sem anúncios enquanto você ainda tiver créditos disponíveis. A Nabble começará a mostrar os anúncios quando você ficar com zero créditos.</to></translation>
+<translation><from>Here you can buy credits for <t.location/>, which has been classified as an <b>adult site</b>. Each credit represents a page view on this site. Visitors will be able to visit this site while it still has credits available. Nabble will block this site when it reaches zero credits.</from><to>Aqui você pode comprar créditos para <n.location/>, que foi classificado como sendo um <b>site adulto</b>. Cada crédito representa uma uma visualização de página nesse site. Visitantes poderão visitar o site enquanto este possuir créditos disponíveis. A Nabble vai bloquear este site quando o número de créditos chegar a zero.</to></translation>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_rus_ru.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,843 @@
+## Portuguese (Brasil)
+
+<macro name="is_feminine" dot_parameter="noun">
+	<n.not.regex_matches text="[n.to_lower_case.noun/]" pattern="галерея|подкатегория|категория"/>
+</macro>
+
+<macro name="new" dot_parameter="what">
+	<n.if.is_feminine.what>
+		<then>новая</then>
+		<else>новый</else>
+	</n.if.is_feminine.what>
+	<n.what/>
+</macro>
+
+<macro name="this" dot_parameter="what">
+	<n.if.is_feminine.what>
+		<then>это</then>
+		<else>этот</else>
+	</n.if.is_feminine.what>
+	<n.what/>
+</macro>
+
+<macro name="your" dot_parameter="what">
+	<n.if.is_feminine.what>
+		<then>её</then>
+		<else>его</else>
+	</n.if.is_feminine.what>
+	<n.what/>
+</macro>
+
+<macro name="Your_was_created" dot_parameter="what">
+	<n.if.is_feminine.what>
+		<then>Была <n.what/> создана</then>
+		<else>Был <n.what/> создан</else>
+	</n.if.is_feminine.what>
+</macro>
+
+<macro name="of_your_new" dot_parameter="what">
+	<n.if.is_feminine.what>
+		<then>в новую</then>
+		<else>в новый</else>
+	</n.if.is_feminine.what>
+	<n.what/>
+</macro>
+
+<macro name="on_your_new" dot_parameter="what">
+	<n.if.is_feminine.what>
+		<then>ваша новая</then>
+		<else>ваш новый</else>
+	</n.if.is_feminine.what>
+	<n.what/>
+</macro>
+
+<macro name="mentioned" dot_parameter="what">
+	<n.if.is_feminine.what>
+		<then>o <n.what/> указанная</then>
+		<else>a <n.what/> указанный</else>
+	</n.if.is_feminine.what>
+</macro>
+
+## MONTHS ##
+
+<translation><from>January</from><to>Январь</to></translation>
+<translation><from>February</from><to>Февраль</to></translation>
+<translation><from>March</from><to>Март</to></translation>
+<translation><from>April</from><to>Апрель</to></translation>
+<translation><from>May</from><to>Май</to></translation>
+<translation><from>June</from><to>Июнь</to></translation>
+<translation><from>July</from><to>Июль</to></translation>
+<translation><from>August</from><to>Август</to></translation>
+<translation><from>September</from><to>Сентябрь</to></translation>
+<translation><from>October</from><to>Октябрь</to></translation>
+<translation><from>November</from><to>Ноябрь</to></translation>
+<translation><from>December</from><to>Декабрь</to></translation>
+
+<translation><from>Jan</from><to>Янв</to></translation>
+<translation><from>Feb</from><to>Фев</to></translation>
+<translation><from>Mar</from><to>Мар</to></translation>
+<translation><from>Apr</from><to>Апр</to></translation>
+<!--translation><from>May</from><to>Май</to></translation-->
+<translation><from>Jun</from><to>Июн</to></translation>
+<translation><from>Jul</from><to>Июл</to></translation>
+<translation><from>Aug</from><to>Авг</to></translation>
+<translation><from>Sep</from><to>Сен</to></translation>
+<translation><from>Oct</from><to>Окт</to></translation>
+<translation><from>Nov</from><to>Ноя</to></translation>
+<translation><from>Dec</from><to>Дек</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>Нарушение этой статьи является нарушением наших Условий Соглашения.</to></translation>
+<translation><from>Access Request</from><to>Запрос доступа</to></translation>
+<translation><from>Account Settings</from><to>Настройки аккаунта</to></translation>
+<translation><from>Action</from><to>Действия</to></translation>
+<translation><from>Add a link to another page</from><to>Вставить ссылку на другую страницу</to></translation>
+<translation><from>Add a new comment</from><to>Добавить новый комментарий</to></translation>
+<translation><from>Add a new group</from><to>Добавить новую группу</to></translation>
+<translation><from>Add an image to your post</from><to>Вставить изображение в сообщение</to></translation>
+<translation><from>Add another address</from><to>Добавить другой адрес</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>Добавить контент, который не должден быть закодирован (например, код)</to></translation>
+<translation><from>Adding Sub-Headers</from><to>Добавить подзаголовок</to></translation>
+<translation><from>Add New Group</from><to>Добавить новую группу</to></translation>
+<translation><from>Add / Remove Groups</from><to>Добавить / Удалить группу</to></translation>
+<translation><from>Add smileys and funny animations</from><to>Добавить смайлики</to></translation>
+<translation><from>Add Subscribers</from><to>Добавить подписчиков</to></translation>
+<translation><from>Add this item to your favorites list</from><to>Добавить в избранное</to></translation>
+<translation><from>Administrator</from><to>Администратор</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity, other sexual content.</from><to>Содержание для взрослых и пожилых, явные сексульные дейстия, нагота, другое сексуальное содержание.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to> Взрослые драки, физические нападения, насилие над подростками, жестокое обращение с животными или реклама терроризма.</to></translation>
+<translation><from>Advanced Search</from><to>Расширенный поиск</to></translation>
+<translation><from>Advanced Settings</from><to>Дополнительные настройки</to></translation>
+<translation><from>Advertisement</from><to>Реклама</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>Уведомлять по почте, при новом сообщении в этой ветке</to></translation>
+<translation><from>All</from><to>Все</to></translation>
+<translation><from>all of the words:</from><to>все слова:</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>Все сообщения <n.author/> были удалены.</to></translation>
+<translation><from>All posts</from><to>Все сообщения</to></translation>
+<translation><from>All users belong to this group</from><to>Все пользователи принадлежащие этой группе</to></translation>
+<translation><from>All Users</from><to>Все пользователи</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>Все пользователи зарегистрированные на <n.location/>. Эти пользователи подтвердили свои адреса электронной почты и могут войти в систему.</to></translation>
+<translation><from>Already Subscribed</from><to>Уже подписанные</to></translation>
+<translation><from>An email has been sent to you.</from><to>Письмо отправлено Вам на емайл.</to></translation>
+<translation><from>Anonymous</from><to>Анонимные</to></translation>
+<translation><from>anonymous user</from><to>анонимный пользователь</to></translation>
+<translation><from>anonymous users</from><to>анонимные пользователи</to></translation>
+<translation><from>Any message part contains</from><to>Содержит в любой части сообщения</to></translation>
+<translation><from>Application</from><to>Приложение</to></translation>
+<translation><from>Apps</from><to>Приложения</to></translation>
+<translation><from>Assignee</from><to>Авторизованный</to></translation>
+<translation><from>Assign</from><to>Назначить права</to></translation>
+<translation><from>Assignment</from><to>Назначенные права</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>Вы просили уведомлять Вас при создании новой темы <n.app/> в:</to></translation>
+<translation><from>at least one of the words:</from><to>по крайней мере одно из слов:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to>Канал(Feeds) для <n.location/></to></translation>
+<translation><from>at priority</from><to>приоритет</to></translation>
+<translation><from>Authorized Users Only</from><to>Только авторизованные пользователи</to></translation>
+<translation><from>Author name</from><to>Имя автора</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>Не используйте короткие сообщения, такие как "Спасибо" "Класс"... Лучше <n.page_node.reply_to_author_link.>напишите личное сообщение в приват</n.page_node.reply_to_author_link.> если захотите.</to></translation>
+<translation><from>Banned User</from><to>Забанить пользователя</to></translation>
+<translation><from>Ban this user</from><to>Забанить этого пользователя</to></translation>
+<translation><from>Ban User</from><to>Пользователь забанен</to></translation>
+<translation><from>Before deleting this archive...</from><to>Прежде чем удалить этот архив...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>Ниже Вы может управлять группами и пользователями. Можно копировать и вставлять пользователей, и перемещать их из одной группы в другую.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>ВАЖНО</b>:  Nabble отправляет приглашение на каждую электронную почту в списке. Пользователи должны будут щелкнуть по ссылке, чтобы подтвердить их подписку.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>ВАЖНО:</b> Эта подписка независит от реальной подписки на рассылку. В основном Вы подпишетесь на архив форума, не на список рассылки непосредственно. Подписка на архив не гарантирует, что Ваши сообщения будут приняты списком рассылки. </to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>ВАЖНО</b>: следует подписать этот архив на список рассылки, чтобы это заработало.</to></translation>
+<translation><from>blog</from><to>блог</to></translation>
+<translation><from>Blog</from><to>Блог</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>Примечание</b>: Вы являетесь Администратором и можете <n.page_node.change_permissions_link.>изменять права доступа <n.location/></n.page_node.change_permissions_link.> и создавать здесь новые темы.</to></translation>
+<translation><from>board</from><to>доска</to></translation>
+<translation><from>Board</from><to>Доска</to></translation>
+<translation><from>Bold</from><to>Жирный</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>Предупреждение:</b> Индексация поиска в настоящее время восстанавливается. Результаты могут быть неполными.</to></translation>
+<translation><from>by <t.author/></from><to>от <n.author/></to></translation>
+<translation><from>Cancel</from><to>Отменить</to></translation>
+<translation><from>category</from><to>категория</to></translation>
+<translation><from>Category</from><to>Категория</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>Предостережение: Это действие не может быть отменено.</to></translation>
+<translation><from>Change appearance</from><to>Изменить оформление</to></translation>
+<translation><from>Change application type</from><to>Сменить тип приложения</to></translation>
+<translation><from>Change Application Type</from><to>Сменить Тип Приложения</to></translation>
+<translation><from>Change code image</from><to>Изменить графический объект</to></translation>
+<translation><from>Change domain name</from><to>Изменить имя домена</to></translation>
+<translation><from>Change language</from><to>Изменить язык</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>Изменить или удалить Ваш аватар.</to></translation>
+<translation><from>Change parent</from><to>Изменить родителя</to></translation>
+<translation><from>Change permissions</from><to>Изменить разрешения</to></translation>
+<translation><from>Change Permissions</from><to>Изменить Разрешение</to></translation>
+<translation><from>Change post date</from><to>Изменить дату сообщения</to></translation>
+<translation><from>Change Post Date</from><to>Изменить Дату Сообщения</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>Изменить текст подписи под Вашими сообщениями.</to></translation>
+<translation><from>Change User Groups</from><to>Изменение Группы Пользователя</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>Изменение свойств отображения и другие настройки.</to></translation>
+<translation><from>Change Your Picture</from><to>Изменить Ваше изображение</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>Изменить Ваш е-майл адрес, пароль, и имя.</to></translation>
+<translation><from>Child abuse</from><to>Жестокое обращение с детьми</to></translation>
+<translation><from>Choose a subcategory</from><to>Выбор подгатегории</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>Выберите подгатегорию для Вашего сообщения</to></translation>
+<translation><from>Choose the best offer for you</from><to>Выберите желаемое отображение</to></translation>
+<translation><from>Classic</from><to>Классический</to></translation>
+<translation><from>Clear Log</from><to>Очистить журнал</to></translation>
+<translation><from>Click for more options</from><to>Нажмите для дополнительных настроек</to></translation>
+<translation><from>click here</from><to>нажмите здесь</to></translation>
+<translation><from>Click here to make your first post</from><to>Нажмите здесь, для создания своего первого сообщения</to></translation>
+<translation><from>Click to filter</from><to>Включить фильтр</to></translation>
+<translation><from>Close</from><to>Закрыть</to></translation>
+<translation><from>Close this message</from><to>Закрыть это сообщение</to></translation>
+<translation><from>comment</from><to>комментарий</to></translation>
+<translation><from>Comment</from><to>Комментарий</to></translation>
+<translation><from>comments</from><to>комментарии</to></translation>
+<translation><from>Comments</from><to>Комментарии</to></translation>
+<translation><from>Confirm Password</from><to>Подтвердите пароль</to></translation>
+<translation><from>Confirm Subscription</from><to>Подтвердите подписку</to></translation>
+<translation><from>Congratulations!</from><to>Поздравляем!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>Поздравляем с <n.on_your_new.app/>!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>Содержание пропагандируюещее ненависть или насилие, оскорбление беззащитных людей, запугивание, расовую нетерпимость или защиту против любого человека, группы или организации или мошенничества. </to></translation>
+<translation><from>CONTENTS DELETED</from><to>СОДЕРЖАНИЕ УДАЛЕНО</to></translation>
+<translation><from>Continue</from><to>Продолжить</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>Нарушения Авторского права или конфиденциальности, или другого юридического требования.</to></translation>
+<translation><from>Count</from><to>Количество</to></translation>
+<translation><from>Created by <t.author/></from><to>Создано:<n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>Создать <n.new.element/></to></translation>
+<translation><from>Create <t.element/></from><to>Создать <n.element/></to></translation>
+<translation><from>Current Credits</from><to>Текущий кредит</to></translation>
+<translation><from>Currently Nabble supports</from><to>Текущая поддержка Nabble</to></translation>
+<translation><from>Current Subscribers</from><to>Текущие подписки</to></translation>
+<translation><from>Daily digest</from><to>Едеждневная сводка новостей</to></translation>
+<translation><from>Data successfully saved</from><to>Данные успешно сохранены</to></translation>
+<translation><from>Date</from><to>Дата</to></translation>
+<translation><from>days</from><to>дни</to></translation>
+<translation><from>Dear <t.name/>,</from><to>Уважаемый(ая) <n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>Уважаемый пользователь,</to></translation>
+<translation><from>Default</from><to>По умолчанию</to></translation>
+<translation><from>Delete all posts from this user</from><to>Удалить все сообщения этого пользователя</to></translation>
+<translation><from>Delete Application</from><to>Удалить приложение</to></translation>
+<translation><from>Deleted posts</from><to>Удаленные сообщения</to></translation>
+<translation><from>Delete</from><to>Удалить</to></translation>
+<translation><from>Delete this post and replies</from><to>Удалить это сообщение и ответы</to></translation>
+<translation><from>Delete this post</from><to>Удалить это сообщение</to></translation>
+<translation><from>Delete this topic</from><to>Удалить эту тему</to></translation>
+<translation><from>Description is in HTML Format</from><to>Описание не соответствует формату HTML</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>Не пишите так часто. Подождите несколько дней. Люди прочитают Ваше сообщение в своем почтовом ящике.</to></translation>
+<translation><from>Don't show this message again</from><to>Не показывать это сообщение снова</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>Вы действительно хотите удалить это сообщение?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>Вы действительно хотите окончательно удалить это сообщение и все ответы?</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>Вы действительно хотите окончательно <n.important.>удалить</n.important.> <n.location/>?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>Вы действительно хотите удалить список подписки на архив?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>Вы действительно хотите подписаться на <n.location/>?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>Вы действительно хотите снять бан с этого пользователя?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>Вы действительно хотите отписаться от <n.location/>?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>Злоупотребления наркотиками, незаконное содержание наркотиков и атрибутов наркомана, злоупотребления огнем или взрывчатыми веществами, продажа пива или крепкого алкоголя, табака и связанных продуктов, оружия или боеприпасов или других опасных действий.</to></translation>
+<translation><from>Edit name & description</from><to>Редактировать имя & описание</to></translation>
+<translation><from>Edit Name & Description</from><to>Редактировать Имя & Описание</to></translation>
+<translation><from>Editor</from><to>Редактор</to></translation>
+<translation><from>Edit Personal Information</from><to>Изменить личную информацию</to></translation>
+<translation><from>Edit post</from><to>Редактировать сообщение</to></translation>
+<translation><from>Edit Subscription</from><to>Редактировать подписку</to></translation>
+<translation><from>Edit Your Signature</from><to>Редактировать Вашу подпись</to></translation>
+<translation><from>Email Confirmation</from><to>Подтверждения е-майл</to></translation>
+<translation><from>Email for <t.app/></from><to>Е-майл для<n.app/></to></translation>
+<translation><from>Email</from><to>Е-майл</to></translation>
+<translation><from>Email Subscription</from><to>Подписка на Е-майл</to></translation>
+<translation><from>Email this post to...</from><to>Отправить это сообщение на Е-майл...</to></translation>
+<translation><from>Embedding Contents</from><to>Встроенное содержание</to></translation>
+<translation><from>Embedding options</from><to>Настройки встраивания</to></translation>
+<translation><from>Embed</from><to>Встроить</to></translation>
+<translation><from>Embed post</from><to>Встроить сообщение</to></translation>
+<translation><from>Embed Tags</from><to>Встроить тэги</to></translation>
+<translation><from>Embed this <t.app/></from><to>Встроить <n.this.app/></to></translation>
+<translation><from>Empty</from><to>Пусто</to></translation>
+<translation><from>Enter a valid email address.</from><to>Введите действительный е-майл адрес.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>Введите ниже Ваш е-майл адрес, и мы отравим письмо с подтверждением для Вас.</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>Введите е-майл адрес или имя пользователя в строке:</to></translation>
+<translation><from>Enter one user per row</from><to>Введите имя пользователя в строке</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>Создайте постоянную ссылку на этот <b>форму</b> или <b>сообщение</b> чтобы создать новую ветку, или оставьте незаполненным, чтобы сделать это сообщение независимой темой:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>Введите домашнюю страницу рассылки, где пользователи могут найти подробную информацию.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>Введите префикс, который используется для рассылки перед заголовком сообщения.</to></translation>
+<translation><from>Enter your email address</from><to>Введите свой е-майл адрес</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>Например: joao_da_silva@dominio.com.br</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>Например: nome_da_lista@dominio_da_lista.com</to></translation>
+<translation><from>Examples:</from><to>Образцы:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>Образцы '[a-lista]', 'Abc:', etc.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>Объясните Администратору, почему Вы хотите получить доступ в закрытую зону.</to></translation>
+<translation><from>Explanation from this user:</from><to>Объяснение от этого пользователя:</to></translation>
+<translation><from>Extras & add-ons</from><to>Дополнения & Расширения</to></translation>
+<translation><from>Failed</from><to>Неудача</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>Избранное (нажмите, чтобы удалить этот элемент из избранного)</to></translation>
+<translation><from>Feeds</from><to>Фиды</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>Файл не был загружен: <n.file/> (пожалуйста, загрузите снова или удалите этот тэг)</to></translation>
+<translation><from>Filter by group</from><to>Фильтровать по группе</to></translation>
+<translation><from>Floating sub-forum</from><to>Плавающий под-форум</to></translation>
+<translation><from>Forgot Password?</from><to>Забыли пароль?</to></translation>
+<translation><from>Forgot your password?</from><to>Забыли Ваш пароль?</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>Для подробной информации смотри: <n.info/></to></translation>
+<translation><from>forum</from><to>форум</to></translation>
+<translation><from>Forum</from><to>Форум</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to><n.app/> Свободно встраиваемый</to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>С этого момента Вы будете получать на свой е-майл письмо на каждое сообщение написанное в <n.location/>.</to></translation>
+<translation><from>gallery</from><to>галерея</to></translation>
+<translation><from>Gallery</from><to>Галерея</to></translation>
+<translation><from>Gambling or casino-related content</from><to>Азартные игры или казино </to></translation>
+<translation><from>Go back</from><to>Назад</to></translation>
+<translation><from>Go to next message</from><to>Перейти к следующему сообщению</to></translation>
+<translation><from>Group Name:</from><to>Название группы:</to></translation>
+<translation><from>Groups</from><to>Группы</to></translation>
+<translation><from>Groups of this user</from><to>Группы этого пользователя</to></translation>
+<translation><from>Hacking / cracking</from><to>Хакерство или преступный взлом</to></translation>
+<translation><from>Harmful dangerous acts</from><to>Опасные действия</to></translation>
+<translation><from>Hateful or abusive content</from><to>Содержит ненависть и оскорбления</to></translation>
+<translation><from>Help</from><to>Справка</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to> Здесь можно произвести оплату, чтобы удалить рекламу с Вашего форума на определенный срок. Посетители будут видеть страницы без объявлений, пока не истечет срок оплаты. Nabble начнет показывать объявления, когда Ваш кредит на оплату закончится.</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>Здесь Вы можете скрыть текущую информацию о списке рассылок архива от пользователей. Эта опция может помочь Вам заменить свой сервер списка рассылки функцией подписки через электронную почту Nabble.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>Скрыть е-майл адрес (например, user@host.com на <n.lt/>скрытая е-майл<n.gt/>ссылка)</to></translation>
+<translation><from>Hide email</from><to>Скрыть е-майл</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>Скрыть информацию о списке е-майл рассылки архива от пользователей</to></translation>
+<translation><from>Highest</from><to>Наивысший</to></translation>
+<translation><from>High</from><to>Высокий</to></translation>
+<translation><from>Hi <t.name/>,</from><to>Привет <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>Если это Ваш е-майл адрес, Вы должны <n.register_link.>зарегистрироваться</n.register_link.> используя тот же самый адрес. После регистрации, вы должны использовать это аккаунт пользователя.</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>Если вы не получили письмо на е-майл от Nabble, проверьте папку СПАМ.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>Если вы хотите задать вопрос, пожалуйста используйте поиск сначала. Возможно на ваш вопрос уже есть ответ.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>Если вы не зарегистрированный пользователь, вы можете <n.register_link.><b>зарегистрироваться сейчас</b></n.register_link.>.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>Если вы забаните этого пользователя, он/она будут не в состоянии что-либо делать на <n.location/>.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>Если вы не запрашивали эту электронну почту, и не знаете, что это такое, проигнорируйте это. Возможно это ошибка или сообщение было отправлено кем-то другим.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>Если Вы не хотите регистрироваться сейчас,просто введите адрес электронной почты, с которого вы хотите отправлять сообщения, и Ваш персональный адрес будет послан по электронной почте Вам.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>Если Вы уже удалили подписку, тогда можно продолжить процесс удаления.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>Если вам известно серверное приложение списка рассылки архива и его версия, выберите из списка.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>Если вы вышли из системы по ошибке, пожалуйста <n.login_link.>войдите снова</n.login_link.>.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>Если вы ответите на это письмо, ваше сообщение будет добавлено к обсуждению ниже</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>Если вы снимите бан с этого пользователя, она/она смогут создавать сообщения <n.location/>.</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>Если вы хотите подписаться на е-майл рассылку вместо этого, <n.mailing_list_options_link.>посетите страницу</n.mailing_list_options_link.>.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>Игнорировать заголовок X-No-Archive</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>Я прочитал и согласен с <n.terms_link.>Соглашением Nabble's</n.terms_link.>.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>Изображение не было загружено: <n.image/> (пожалуйста загрузите снова, или удалите этот тэг)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>Я - подписчик, разрешите мне создавать сообщения сейчас</to></translation>
+<translation><from>Incorrect Login!</from><to>Неверный логин!</to></translation>
+<translation><from>Individual emails</from><to>Личный е-майл</to></translation>
+<translation><from>Inherit</from><to>Наследовать</to></translation>
+<translation><from>In Reply To</from><to>В ответ на a</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>В ответ на <n.parent_link.>это сообщение</n.parent_link.> от <n.author/></to></translation>
+<translation><from>Insert</from><to>Вставить</to></translation>
+<translation><from>Insert Image</from><to>Вставить изображение</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>Помимо создания тем через веб-интерфейс, можно также создать новые темы, посылая электронные письма по следующему адресу электронной почты:</to></translation>
+<translation><from>in <t.location/></from><to>на <n.location/></to></translation>
+<translation><from>Invalid Code</from><to>Недопустимый код</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>Неверный е-майл адрес: <n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>Неверное число дней, должно быть целое число.</to></translation>
+<translation><from>invisible user</from><to>Невидимый пользователь</to></translation>
+<translation><from>invisible users</from><to>Невидимые пользователи</to></translation>
+<translation><from>Invite Subscribers</from><to>Приглашение подписчиков</to></translation>
+<translation><from>is:</from><to>это:</to></translation>
+<translation><from>is not:</from><to>это не:</to></translation>
+<translation><from>is within the last:</from><to>за последние:</to></translation>
+<translation><from>Italic</from><to>Курсив</to></translation>
+<translation><from>item</from><to>элемент</to></translation>
+<translation><from>items</from><to>элементы</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>НЕВОЗМОЖНО будет в последствии восстановить удаленные элементы.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>Вставьте код (поддерживаемый сайтами указанными выше) внутри этих тэгов, и вы можете опубликовать это.</to></translation>
+<translation><from>Last Post</from><to>Последняя новость</to></translation>
+<translation><from>Learn more</from><to>Узнать больше</to></translation>
+<translation><from>Leave a comment</from><to>Оставить комментарий</to></translation>
+<translation><from>Link</from><to>Ссылка</to></translation>
+<translation><from>Link to <t.location/></from><to>Ссылка на <n.location/></to></translation>
+<translation><from>List</from><to>Список</to></translation>
+<translation><from>List of Subcategories</from><to>Список подкатегорий</to></translation>
+<translation><from>List Server</from><to>Список серверов</to></translation>
+<translation><from>List View</from><to>Вид списка</to></translation>
+<translation><from>Loading...</from><to>Загружается...</to></translation>
+<translation><from>Location</from><to>размещение</to></translation>
+<translation><from>Locked</from><to>Заблокированно</to></translation>
+<translation><from>Lock topic</from><to>Тема закрыта</to></translation>
+<translation><from>Login</from><to>Войти</to></translation>
+<translation><from>Log is empty</from><to>Логин пустой</to></translation>
+<translation><from>Log out</from><to>Выход</to></translation>
+<translation><from>Lowest</from><to>Самый низкий</to></translation>
+<translation><from>Low</from><to>Низкий</to></translation>
+<translation><from>Mailing List Address</from><to>Адреса списка рассылки</to></translation>
+<translation><from>Mailing list archive settings</from><to>Настройки списка рассылки архива</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>Настройки Списка Рассылки Архива</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>Напоминание о подписке на список рассылки</to></translation>
+<translation><from>Mailing List Website</from><to>Веб-сайт списка рассылки</to></translation>
+<translation><from>Main Page</from><to>Форумы и под-форумы</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>Убедитесь, что Вы используете тот же самый браузер, через который вы заполняли регистрационный запрос.</to></translation>
+<translation><from>Manage banned users</from><to>Управлепние забаненными пользователями</to></translation>
+<translation><from>Manage pinned topics</from><to>Управление приклееными темами</to></translation>
+<translation><from>Manage subscribers</from><to>Управление подписчиками</to></translation>
+<translation><from>Manage Subscribers</from><to>Управление Подписчиками</to></translation>
+<translation><from>Manage <t.items/></from><to>Управление <n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>Управление пользователями & группами</to></translation>
+<translation><from>Manage Users & Groups</from><to>Управление Пользователями & Группами</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>Массовая реклама, вводящий в заблуждение текст, жульничество или мошенничество.</to></translation>
+<translation><from>max. 80 characters</from><to>макс. 80 знаков</to></translation>
+<translation><from>Message date</from><to>Дата сообщения</to></translation>
+<translation><from>message</from><to>сообщение</to></translation>
+<translation><from>Message</from><to>Сообщение</to></translation>
+<translation><from>Message is in HTML Format</from><to>Сообщение в формате HTML</to></translation>
+<translation><from>messages</from><to>сообщения</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>Сообщение опубликованное здесь, будет отправлено на этот список рассылки.</to></translation>
+<translation><from>Message subject contains</from><to>Содержание темы сообщения</to></translation>
+<translation><from>Message text contains</from><to>Содержание текста сообщения</to></translation>
+<translation><from>Mixed</from><to>Смешанный</to></translation>
+<translation><from>Modified</from><to>Модифицированный</to></translation>
+<translation><from>Archives</from><to>Архивы</to></translation>
+<translation><from>More Categories</from><to>Доп. категории</to></translation>
+<translation><from>More</from><to>Дополнительно</to></translation>
+<translation><from>more help</from><to>доп. помощь</to></translation>
+<translation><from>more options</from><to>доп. настройки</to></translation>
+<translation><from>Move post</from><to>Переместить сообщение</to></translation>
+<translation><from>Move Post</from><to>Переместить Сообщение</to></translation>
+<translation><from>Move topic</from><to>Переместить тему</to></translation>
+<translation><from>My Nabble Applications</from><to>Мое приложение Nabble</to></translation>
+<translation><from>My Pending Posts</from><to>Мои публикации в ожидании</to></translation>
+<translation><from>My posts</from><to>Мои публикации</to></translation>
+<translation><from>Nabble Support</from><to>Поддержка Nabble</to></translation>
+<translation><from>Name</from><to>Название</to></translation>
+<translation><from>New Post</from><to>Новая публикация</to></translation>
+<translation><from>news</from><to>новости</to></translation>
+<translation><from>News</from><to>Новости</to></translation>
+<translation><from>New Topic</from><to>Новая Тема</to></translation>
+<translation><from>New topics only</from><to>Только новые темы</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>ПРЕДУПРЕЖДЕНИЕ</n.important.>: Все находящее в <n.location/> будет удалено навсегда!</to></translation>
+<translation><from>No banned users.</from><to>Нет забаненных пользователей.</to></translation>
+<translation><from>No Filter</from><to>Нет фильтра</to></translation>
+<translation><from>none of the words:</from><to>ни одно из слов:</to></translation>
+<translation><from>No registered user found with this email.</from><to>Зарегистрированных пользователей с таким е-майл не найдено.</to></translation>
+<translation><from>No replies</from><to>Нет ответов</to></translation>
+<translation><from>Normal</from><to>Нормальный</to></translation>
+<translation><from>No sub-forums</from><to>Нет под-форумов</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>Обратите внимание, что этот адрес уникален для Вас и принимает электронные письма, посланные только от <n.address/>. Цель этого проекта состоит в том, чтобы помочь предотвратить спам.</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to><n.register_link.>Зарегистрируйтесь сейчас</n.register_link.> если Вы хотите редактировать свой профиль, получать сообщения по электронной почте, управлять избранными статьями или иметь доступ к своему общему профилю.</to></translation>
+<translation><from>one email per input box</from><to>один е-майл в поле ввода</to></translation>
+<translation><from>Online Users</from><to>Пользователи он-лайн</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>Только авторизованные пользователи могут совершать действия в этой области.</to></translation>
+<translation><from>Open this post in classic view</from><to>Открыть эту статью в классическом виде</to></translation>
+<translation><from>Open this post in list view</from><to>Окрыть эту статью в виде списка</to></translation>
+<translation><from>Open this post in threaded view</from><to>Открыть эту статью в виде дерева</to></translation>
+<translation><from>Options</from><to>Инструменты</to></translation>
+<translation><from>or</from><to>или</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>Или можно проигнорировать эту электронную почту, если необходимо держать этого пользователя отдельно от той области.</to></translation>
+<translation><from>Other Settings</from><to>Другие настройки</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>Страница групп субприложений и обсуждений.</to></translation>
+<translation><from>Page <t.number/></from><to>Страница <n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>Страница с простым списком субприложений и обсуждений.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>Страница с полными сообщениями и связанными комментариями.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>Страница с заголовками и сообщениями.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>Страница с темами и обсуждениями.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>Страница с темами сгруппированными по субприложениям. Если субприложений не существует, на экран выводится обычный список.</to></translation>
+<translation><from>Password</from><to>Пароль</to></translation>
+<translation><from>Password Sent</from><to>Пароль отправлен</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>Педофилия, насилие и другое жестокое обращение.</to></translation>
+<translation><from>People</from><to>Пользователи</to></translation>
+<translation><from>People in <t.location/></from><to>Пользователи на <n.location/></to></translation>
+<translation><from>Permalink</from><to>Постоянная ссылка</to></translation>
+<translation><from>Photo and image gallery.</from><to>Галерея фото и изображений.</to></translation>
+<translation><from>Pinned sub-forum</from><to>Закрепленный суб-форум</to></translation>
+<translation><from>Pin topic</from><to>Закрепить тему</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>Занесите ссылку указанную выше в закладки или сохраните эту электронную почту, чтобы было легче найти её <n.your.app/> в будущем.</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>Проверьте свой е-майл сейчас и активируйте Вашу учетную запись, чтобы иметь доступ ко всем функциям.</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>Нажмите на ссылку подтверждения ниже, для активации Вашего аккаунта:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>Свяжитесь с поддержкой Nabble, если Вам необходима помощь.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>Свяжитесь с администраторами, если Вам необходима помощь.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>Пожалуйста введите правильно е-майл и попробуйте снова.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>Пожалуйста, следуйте за инструкциями в этом письме, для завершения процесса регистрации.</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>Пожалуйста, следуйте за <n.subscribe_instructions_link.>инструкциями подписки</n.subscribe_instructions_link.> этого архива.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>Пожалуйства вводите действительную постоянную ссылку.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>Пожалуйста введите снова ваш е-майл/пароль и нажмите Войти.</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>Соблюдайте этикет списка рассылок</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>Опрос от Polldaddy.com (только быстрый опрос)</to></translation>
+<translation><from>Post by email</from><to>Сообщения по е-майл</to></translation>
+<translation><from>Post by Email</from><to>Сообщения по Е-майл</to></translation>
+<translation><from>Post Count</from><to>Количество сообщений</to></translation>
+<translation><from>Posted by <t.author/></from><to>Разместил <n.author/></to></translation>
+<translation><from>post</from><to>отправить</to></translation>
+<translation><from>Post Message</from><to>Отправить сообщение</to></translation>
+<translation><from>Post New Message</from><to>Отправить новое сообщение</to></translation>
+<translation><from>Post new message in <t.location/></from><to>Отправить новое сообщение в <n.location/></to></translation>
+<translation><from>posts</from><to>сообщения</to></translation>
+<translation><from>Posts</from><to>Сообщения</to></translation>
+<translation><from>Posts in <t.location/></from><to>Сообщения в <n.location/></to></translation>
+<translation><from>Preview Message</from><to>Предпросмотр сообщения</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>Цены в Долларах США. Этот процесс контроля использует <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, который позволяет клиентам Amazon использовать способы оплаты, сохраненные на своем аккаунте Amazon.com .</to></translation>
+<translation><from>Print post</from><to>Распечатать сообщение</to></translation>
+<translation><from>Priority</from><to>Приоритет</to></translation>
+<translation><from>(private)</from><to>(личное)</to></translation>
+<translation><from>Profile of <t.author/></from><to>Профиль <n.author/></to></translation>
+<translation><from>Quote</from><to>Цитата</to></translation>
+<translation><from>Quote the original message</from><to>Цитировать оригинальное сообщение</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>Цитировать только ту часть на которую вы отвечаете. Это обеспечивает содержание тем, кто читает ваше сообщение через е-майл.</to></translation>
+<translation><from>Raw mail</from><to>Необработанная почта</to></translation>
+<translation><from>Raw text</from><to>Необработанный текст</to></translation>
+<translation><from>read more</from><to>читать далее</to></translation>
+<translation><from>Read more</from><to>Читать далее</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>Список только для чтения, со всеми пользователями с е-майл <n.location/>.</to></translation>
+<translation><from>Receive direct replies only.</from><to>Получать только прямые ответы.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>Получать каждое сообщение размещенное в <n.location/>.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>Получать каждый ответ в этой теме.</to></translation>
+<translation><from>Receive new topics only.</from><to>Получать только новые темы.</to></translation>
+<translation><from>Refresh</from><to>Обновить</to></translation>
+<translation><from>Registered</from><to>Зарегистрирован</to></translation>
+<translation><from>Registered Users</from><to>Зарегистрированные пользователи</to></translation>
+<translation><from>Register</from><to>Регистрация</to></translation>
+<translation><from>Registering...</from><to>Идет регистрация...</to></translation>
+<translation><from>Register Now</from><to>Зарегистрироваться сейчас</to></translation>
+<translation><from>Register to <t.app/></from><to>Зарегистрироваться в <n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>Регистрация подтверждена</to></translation>
+<translation><from>Registration Failed</from><to>Регистрация не удалась</to></translation>
+<translation><from>Related Help Article</from><to>Помощь. Связанные статьи</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>Помните, что БАН не эффективное наказание, потому что пользователь может вернуться под другим аккаунтом.</to></translation>
+<translation><from>Remove Ads</from><to>Удалить рекламу</to></translation>
+<translation><from>remove</from><to>удалить</to></translation>
+<translation><from>Remove Settings</from><to>Удалить настройки</to></translation>
+<translation><from>Remove Subscription</from><to>Удалить подписку</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>Удалить ваш аккаунт и все ваши сообщения из <n.subject/>.</to></translation>
+<translation><from>Remove Your Account</from><to>Удалить Ваш Аккаунт</to></translation>
+<translation><from>replies</from><to>ответы</to></translation><!-- noun/ plural -->
+<translation><from>Replies</from><to>Ответы</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>Ответить</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>ответить</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>Ответить автору</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>Содержание отчета как несоответствующее</to></translation>
+<translation><from>Report Now</from><to>Создать отчет сейчас</to></translation>
+<translation><from>required</from><to>требуется</to></translation>
+<translation><from>Return to <t.location/></from><to>Вернуться к <n.location/></to></translation>
+<translation><from>Save Changes</from><to>Сохранить изменения</to></translation>
+<translation><from>Save Settings</from><to>Сохранить настройки</to></translation>
+<translation><from>Save Subscription</from><to>Сохранить подписку</to></translation>
+<translation><from>Search</from><to>Поиск</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>Выберите ниже действие, которое хотите применить:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>Выберите категорию, которая наиболее отвечает вашим интересам.</to></translation>
+<translation><from>Send email to me</from><to>Отправить е-майл мне</to></translation>
+<translation><from>Send Email to <t.author/></from><to>Отправить е-майл <n.author/></to></translation>
+<translation><from>Send Request</from><to>Отправить запрос</to></translation>
+<translation><from>Send To:</from><to>Отправить на:</to></translation>
+<translation><from>Sexual content</from><to>Сексуальное содержание</to></translation>
+<translation><from>Show</from><to>Показать</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>Показать уведомление Nabble</to></translation>
+<translation><from>Sincerely,</from><to>С уважением,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>То что вы хотите удалить, является списком рассылки архива, пожалуйста, откажитесь от подписки по адресу электронной почты ниже прежде, чем нажать на кнопку УДАЛИТЬ.</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>Так как вы не зарегистрированный пользователь, мы должны убедиться, что вы человек.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>Некоторые ваши сообщения были удалены из <n.location/> и мы отправили вам копии, чтобы вы имели возможность сохранить их.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>Извините, но только зарегистрированные пользователи могут создавать сообщения на <n.app/>.</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>Очень жаль, но администраторы вас забанили.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>Извините, но этот е-майл не подтвержден для просмотра сообщений на <n.location/>.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>Извините, но вы не можете создавать новые темы здесь.<br/>Обратите внимание, что вы можете отвечать на сообщения.</to></translation>
+<translation><from>Sort by date</from><to>Сортировать по дате</to></translation>
+<translation><from>Sort by relevance</from><to>Сортировать по соответствию запроса</to></translation>
+<translation><from>Sorted by date</from><to>Отсортировано по дате</to></translation>
+<translation><from>Sorted by relevance</from><to>Отсортировано по соответствию запрос</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[СПАМ-детектор] Сообщение содержит слишком много слов'<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[СПАМ-детектор] Сообщение не может содержать '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[СПАМ-детектор] Сообщение содержит общие спам-слова.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[СПАМ-детектор] Тема сообщения не может содержать '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[СПАМ-детектор] Тема сообщения содержит общие спам-слова.</to></translation>
+<translation><from>Spam</from><to>Спам</to></translation>
+<translation><from>Stars in <t.location/></from><to>в Избранное <n.location/></to></translation>
+<translation><from>Structure</from><to>Структура</to></translation>
+<translation><from>Subcategories</from><to>Подкатегории</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>Подкатегории в <n.location/></to></translation>
+<translation><from>Subcategory</from><to>Подкатегория</to></translation>
+<translation><from>Sub-Forum</from><to>Под-форум</to></translation>
+<translation><from>Sub-Forums</from><to>Под-форумы</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>Под-форумы & Темы</to></translation>
+<translation><from>Subject</from><to>Тема</to></translation>
+<translation><from>Subscribe</from><to>Подписаться</to></translation>
+<translation><from>subscriber</from><to>подписчик</to></translation>
+<translation><from>subscribers</from><to>подписчики</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>Подписаться на <n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>Подписаться через е-майл</to></translation>
+<translation><from>Subscription Confirmation</from><to>Подтверждение подписки</to></translation>
+<translation><from>Subscription Confirmed</from><to>Подписка подтверждена</to></translation>
+<translation><from>Subscription Format</from><to>Формат подписки</to></translation>
+<translation><from>Subscription Removed</from><to>Подписка удалена</to></translation>
+<translation><from>Subscription Results</from><to>Результаты подписки</to></translation>
+<translation><from>Subscription Type</from><to>Тип подписки</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>Успешно: письмо-подтвержение было выслано вам на е-майл.</to></translation>
+<translation><from>Success</from><to>Успешно</to></translation>
+<translation><from>Take Action</from><to>Принять меры</to></translation>
+<translation><from><t.app/> Registration</from><to>Регистрация <n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to><n.author/> был успешно забанен.</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to><n.author/> бан был снят.</to></translation>
+<translation><from>Tell me more & show examples</from><to>Сообщить мне больше & показать примеры</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>Спасибо за регистрацию на <n.app/>!</to></translation>
+<translation><from>Thank You</from><to>Спасибо</to></translation>
+<translation><from>The author has deleted this message.</from><to>Автор удалил это сообщение.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>Код URL неверный.</to></translation>
+<translation><from>the exact phrase:</from><to>точная фраза:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>Для списка рассылки может потребоватья ваша подписка прежде, чем принять ваше сообщение. Пожалуйста, обратите внимание, что регистрация в Nabble автоматически не подписывает Вас на этот список рассылки. Если Вы еще не подписались, пожалуйста, сделайте это сейчас. Если Вы не уверены или не помните, подпишитесь снова, в этом нет никакого вреда.</to></translation>
+<translation><from>The Nabble team</from><to>Коллектив Nabble</to></translation>
+<translation><from>The name of the group is not valid.</from><to>Название группы неверное.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>Новый родитель не может сам размещать сообщения.</to></translation>
+<translation><from>The password fields don't match.</from><to>Пароли в области ввода не соответствуют.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>Это незарегистрированная учетная запись пользователя, связанна с е-майл адресом <n.email/>.</to></translation>
+<translation><from>The subject is required.</from><to>Введите тему сообщения.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>Пользователь получит копию всех удаленных сообщений на е-майл, чтобы иметь возможность сохранить их.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>Код не соответствует изображению.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>Эта ветка слишком большая и некоторые сообщения были скрыты. Используйте другой вид просмотра чтобы их просмотреть.</to></translation>
+<translation><from>This email is already subscribed.</from><to>Эта электронная почта уже в подписке.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>Этот форум для е-майл рассылки архива</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>Этот форум - архив/ворота через которые  направит ваше сообщение в список рассылки <b><n.mailing_list_address/></b>.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>Это включает подкатегории, сообщения, изображения, файлы и все остальное.</to></translation>
+<translation><from>This is a mailing list archive</from><to>Это список рассылки архива</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to> Это - автоматическое электронное письмо, посланное Nabble, чтобы подтвердить создание Вашего нового  <n.of_your_new.app/>. Если Вы не создавали  <n.mentioned.app/> упомянутое выыше, свяжитесь с нами через форум поддержки Nabble.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>Этот список поддерживает только е-майл с простым текстом</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>Этот список отображает зарегистрированных, незарегистрированных и забаненых пользователей. Анонимные пользователи не отображаются в списке, потому что не имеют е-майл адреса и поэтому не могут быть частью группы.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>Это сообщение будет отправлено из <b><n.from/></b> в список рассылки <b><n.to/></b>.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to><b>Сообщение в ожидании</b>: Это сообщение НЕ было принято списком рассылки.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>Это сообщение было обновлено <n.date/>.</to></translation>
+<translation><from>This topic has been locked.</from><to>Эта тема была обновлена.</to></translation>
+<translation><from>This topic has been pinned.</from><to>Эта тема была закреплена.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>Эта тема была закреплена в <n.location/>.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>Эта тема была открыта.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>Эта тема была откреплена.</to></translation>
+<translation><from>This topic has unread posts</from><to>В этой теме есть непрочитанные сообщения</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>Эта тема отмечена как важная для вас <n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>Этот пользователь не имеет прав для просмотра этого приложения (добавьте его/её в группу, в которой это разрешено и попробуйте снова)</to></translation>
+<translation><from>This user name is already in use.</from><to>Это имя пользователя уже используется.</to></translation>
+<translation><from>Threaded</from><to>Древовидный</to></translation>
+<translation><from>Threaded View</from><to>Древовидный просмотр</to></translation>
+<translation><from>Time</from><to>Время</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>СОВЕТ: Если ваш архив подписан на е-майл рассылку и не работает, мы можете игнорировать заголовок X-No-Archive.</to></translation>
+<translation><from>Tips</from><to>Советы</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> просит разрешения присоединиться <n.location/>:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> Кредиты</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> просмотр страниц без рекламы.</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>Чтобы запрос был принят, следует добавить этого пользователя по крайней мере к одной из групп, у которой есть доступ к этой области:</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>Добавьте это <n.this.app/> на ваш сайт, скопируйте и вставьте следующий код на вашу страницу HTML:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>Для подтверждения вашей подписки, нажмите на ссылку ниже:</to></translation>
+<translation><from>topic</from><to>тема</to></translation>
+<translation><from>Topics and replies</from><to>Темы и ответы</to></translation>
+<translation><from>topics</from><to>темы</to></translation>
+<translation><from>Topics</from><to>Темы</to></translation>
+<translation><from>Topics only</from><to>Только темы</to></translation>
+<translation><from>Topics View</from><to>Просмотр темы</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>Для предотвращения спама, отправляйте сообщения только на  <b>уникальный адрес</b> для каждого пользователя.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>Для удаления группы, очистите текстовое поле ниже и сохраните изменения.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>Чтобы увидеть, какой е-майл адрес вы должны использовать для сообшений, пожалуйста <n.login_link.>войдите</n.login_link.> или <n.register_link.>зарегистрируйтесь</n.register_link.>.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>Начать новую тему в <n.location/>, электронная почта <n.p2/></to></translation>
+<translation><from>Total</from><to>Полностью</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>Отказаться от подписка на <n.location/></to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> приглашает вас подписаться на <n.location/>:</to></translation>
+<translation><from>Turn off highlighting</from><to>Отключить подсветку</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/> создал новую подкатегорию</to></translation>
+<translation><from>Unable to Post</from><to>Невозможно отправить</to></translation>
+<translation><from>Unassigned</from><to>Неинициализированный</to></translation>
+<translation><from>Unauthorized</from><to>Не авторизованный</to></translation>
+<translation><from>Unban this user</from><to>Разбанить этого пользователя</to></translation>
+<translation><from>Unban User</from><to>Разбанить пользователя</to></translation>
+<translation><from>Unknown or Other</from><to>Неизвестный или другое</to></translation>
+<translation><from>Unlock topic</from><to>Разблокировать тему</to></translation>
+<translation><from>Unpin topic</from><to>Открепить тему</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>Отменить регистрацию / Дезактивировать</to></translation>
+<translation><from>Unregistered</from><to>Не зарегистрирован</to></translation>
+<translation><from>Unregistered User</from><to>Не зарегистрированный пользователь</to></translation>
+<translation><from>Unsubscribe</from><to>Отказаться от подписки</to></translation>
+<translation><from>Upload a file</from><to>Загрузить файл</to></translation>
+<translation><from>User email:</from><to>Е-майл пользователя:</to></translation>
+<translation><from>user</from><to>пользователь</to></translation>
+<translation><from>User is online</from><to>Пользователь он-лайн</to></translation>
+<translation><from>User Name</from><to>Имя пользователя</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>Пользователь запрашивает разрешение присоединиться <n.location/></to></translation>
+<translation><from>users</from><to>пользователи</to></translation>
+<translation><from>Users</from><to>Пользователи</to></translation>
+<translation><from>Users & Groups</from><to>Пользователи & Группы</to></translation>
+<translation><from>Users that completed the registration process</from><to>Пользователи, завершившие процесс регистрации</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>Используйте такие тэги, как <n.example1/> или <n.example2/> для создания подзаголовка.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>Используйте опции поиска ниже, чтобы точно указать Ваши критерии поиска.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>Используте <n.tag_names/> для встраивания виджетов с других сайтов.</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>Просмотреть все сообщения в этом под-форуме</to></translation>
+<translation><from>view</from><to>просмотр</to></translation>
+<translation><from>View mailing list website</from><to>Просмотреть вебсайт списка рассылки</to></translation>
+<translation><from>View message</from><to>Просмотреть сообщение</to></translation>
+<translation><from>View more</from><to>Просмотреть подробнее</to></translation>
+<translation><from>views</from><to>просмотры</to></translation>
+<translation><from>Views</from><to>Просмотры</to></translation>
+<translation><from>Violent or repulsive content</from><to>Жестокое или отталкивающее содержание</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>Посетите <n.app/> на:</to></translation>
+<translation><from>visit <t.url/></from><to>посетите <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>Мы подготовили страницу с <n.unsubscription_instructions_link.>некоторыми инструкциями, относительно того, как отказаться от подписки на этот архив</n.unsubscription_instructions_link.>.</to></translation>
+<translation><from>We will review your report soon.</from><to>Мы рассмотрим ваш отчет в ближайшее время.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>Группа позволяет пользователям быть в списке</to></translation>
+<translation><from>Who can ban/unban users</from><to>Кто может забанить/разбанить пользователей</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>Кто может устанавливать топики (только в Рабочей группе)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>Кто может менять дату и время сообщений</to></translation>
+<translation><from>Who can create new topics under this application</from><to>Кто может создавать новые темы в этом приложении</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>Кто может создавать приложения (напр., под-форумы, подкатегории, итд.)</to></translation>
+<translation><from>Who can edit any content, both applications and posts. Note: Please only use this feature in extreme circumstances. Most users will not like having their posts edited by someone else.</from><to>Кто может редактировать любое содержание, приложения и сообщения. Примечание: Пожалуйста, используйте эту функцию только в экстренных случаях. Многие пользователи не любят, когда их сообщения кто-то редактирует.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>Кто может редактировать приложения (напр.,изменять имя, описание, итд.)</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>Кто может блокировать/разблокировать темы в этом приложении</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>Кто может управлять подписчиками в этом приложении</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>Кто может перемещать сообщения в другое место (напр., например в другую тему, или под-форум)</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>Кто может закрепить/открепить темы в этом приложении</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>Кто может размещать любое содержание без ограничений (включая код javascript, тэги &lt;object&gt; и &lt;style&gt;, итд.). <b>Предупреждение безопасности</b>: Разрешайте эту опцию только тем пользователям, которым вы действительно доверяете.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>Кто может отвечать на сообщения размещенные в этом приложении</to></translation>
+<translation><from>Who can view this application and its contents</from><to>Кто может просматривать это приложение и это содержание</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>С Вашей подпиской, обновления будут высланы на ваш е-майл, и можно ответить им, чтобы участвовать в обсуждении. Ваша подписка работает в том же самом списке рассылки.</to></translation>
+<translation><from>Write Your First Headline</from><to>Напишите Ваш Первый Заголовок</to></translation>
+<translation><from>Write Your First Post</from><to>Напишите Ваше Первое Сообщение</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>Да, удалить <n.location/> навсегда</to></translation>
+<translation><from>Yes, unsubscribe now</from><to>Да, отказаться от подписки сейчас</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>Вы действительно хотите подписаться на <n.location/>.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>Вы не подписаны на <n.location/>.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>Также вы можете <n.manage_banned_users_link.>управлять забанеными пользователями</n.manage_banned_users_link.> на <n.location/>.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>Также вы можете <n.root_node.change_permissions_link.>изменять права доступа</n.root_node.change_permissions_link.> группам указанным ниже.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>Вы можете продвигать свой <n.your.app/> отправляя ссылку своим друзьям, встраивая это на свой сайт или рассказать об этом на других форумах.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>Вы не можете переместить это сообщение в это место, потому что новый родитель не разрешает это анонимным пользователям. </to></translation>
+<translation><from>You Cannot Post Here</from><to>Вы не можете публиковать здесь</to></translation>
+<translation><from>(you can reply by email)</from><to>(вы можете ответить по е-майл)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>Вы не можете переместить сообщение в никуда.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>Вы не можете размещать сообщение здесь, но вы можете разместить его в другом месте.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>Попробуйте <n.register_link.>зарегистрироваться снова</n.register_link.> или свяжитесь с <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>Вы можете использовать форму ниже, для отправки запроса администраторам.</to></translation>
+<translation><from>You have already been registered.</from><to>Вы уже зарегистрированы.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>Вы приглашены подписаться на <n.location/>, которая доступна на:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>Вы уже зарегистрированы на <n.subject/>.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>Ваша подписка <n.location/> была отменена</to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>Вы создали слишком много постов за короткое время. Пожалуйста попробуйте позже.</to></translation>
+<translation><from>You logged out</from><to>Вы вышли из системы</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>Вам может быть нужно <n.mailing_list_options_link.>подписаться на этот список рассылки</n.mailing_list_options_link.> чтобы ваше сообщение было принято.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>Вы можете <n.page_node.unauthorized_link.>запросить права на публикацию</n.page_node.unauthorized_link.> здесь или отравить сообщение <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> если у вас есть вопросы.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>Вы должны принять Условия и Соглашения.</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>Вы должны ввести реальный е-майл адрес для этого списка рассылки.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>Вы должны ввести правильный адрес веб-сайта для этого списка рассылки.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>Вы должны заполнить все поля ниже.</to></translation>
+<translation><from>You must login to view this page.</from><to>Вы должны войти, чтобы просмотреть эту страницу.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>Вы должны войти, чтобы просмотреть <n.subject/>.</to></translation>
+<translation><from>You must login to your account.</from><to>Вы должны войти в свой аккаунт.</to></translation>
+<translation><from>You must provide a user name.</from><to>Вы должны ввести имя пользователя.</to></translation>
+<translation><from>You're not a subscriber</from><to>Вы не являетесь подписчиком</to></translation>
+<translation><from>Your Name</from><to>Ваше имя</to></translation>
+<translation><from>Your request has been successfully sent.</from><to>Ваш запрос был успешно отправлен.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>Ваша подписка была успешно сохранена.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>Ваша подписка на <n.location/> была удалена. Если это была ошибка, вы можете переподписаться перейдя по ссылке ниже:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>Ваша подписка <n.location/> была успешно удалена.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to><n.Your_was_created.app/> была успешно создана.</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>Вам необходимо разрешение для создания новых тем  <n.location/>, в дополнение к регистрации, это должно быть одобрено администраторами.</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>Вы получите письмо на каждое новое сообщение созданное в этой теме.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>Вы получите письмо со ссылкой для активации вашего аккаунта.</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>Ваша подписка</to></translation>
+<translation><from>edit</from><to>редактировать</to></translation>
+<translation><from>Remove ads</from><to>Удалить рекламу</to></translation>
+<translation><from>View profile of <t.author/></from><to>Просмотреть профиль <n.author/></to></translation>
+<translation><from>Description</from><to>Описание</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>Видео с Youtube, Vimeo и LiveLeak.</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>Забаненные пользователи на <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>Префикс заголовка</to></translation>
+
+#NEW 2
+<translation><from>Change title and meta tags</from><to>Изменить название и мета-тэги</to></translation>
+<translation><from>Here you can customize the title and meta tags of the <t.location/> page.</from><to>Здесь вы можете установить название и мета-тэги страницы <n.location/>.</to></translation>
+<translation><from>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</from><to>Информация о мета-тэгах ниже, поможет вам оптимизировать страницу для поисковых движков (таких как google, yahoo!, итд.)</to></translation>
+<translation><from>Use custom values</from><to>Использовать выборочное значение</to></translation>
+<translation><from>Page Title</from><to>Название страницы</to></translation>
+<translation><from>Enter a context-rich title that clearly identifies this page.</from><to>Введите описание, которое максимально соответствует содержанию этой страницы.</to></translation>
+<translation><from>The title should ideally be less than 70 characters in length.</from><to>Название в идеале должно быть меньше 70 знаков.</to></translation>
+<translation><from>Meta Description</from><to>Мета-описание</to></translation>
+<translation><from>Enter a brief and concise summary of your page's content.</from><to>Введите краткое описание содержания вашей страницы.</to></translation>
+<translation><from>Limit your description to 155 characters or 170 characters at most.</from><to>Описание ограничено 155 знаками или не более 170.</to></translation>
+<translation><from>Mailing List Archive</from><to>Список рассылки архива</to></translation>
+<translation><from>Filter: priority <t.priority/></from><to>Фильтр: важный <n.priority/></to></translation>
+<translation><from>Filter: assignee <t.author/></from><to>Фильтр: правоприемник <n.author/></to></translation>
+<translation><from>Use Google Analytics</from><to>Использовать Google Analytics</to></translation>
+<translation><from>Analytics Account ID:</from><to>Analytics Account ID :</to></translation>
+<translation><from>(e.g., UA-12345-0)</from><to>(например: UA-12345-0)</to></translation>
+<translation><from>Enter a valid analytics account ID.</from><to>Введите действительн аккаунт ID от Гугл аналитикс.</to></translation>
+<translation><from>Here you can use Google Analytics to measure the success of your app.</from><to>Здесь вы можете использовать Google Analytics для улучшения своего приложения.</to></translation>
+<translation><from>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</from><to>Введите ниже ваш Гугл-аналитикс ID и вы сможете следить за посещениями, посетителями и получите другую важную статистику о вашем веб-траффике.</to></translation>
+
+<translation><from>Digest Email</from><to>Дайджест новостей</to></translation>
+<translation><from>on <t.date/></from><to>на <n.date/></to></translation>
+<translation><from>DO NOT REPLY TO THIS EMAIL</from><to>НЕ ОТВЕЧАЙТЕ НА ЭТОТ Е-МАЙЛ</to></translation>
+<translation><from>Replies sent to this address are not read or processed.</from><to>Ответ посланный по этому адресу не прочитан или необработан.</to></translation>
+<translation><from>If you want to respond to a post for which you received this email, please go to the website: <t.url/></from><to>Если Вы хотите ответить на сообщение, которое получили с этим писмом, пожалуйста посетите веб-сайт: <n.url/></to></translation>
+<translation><from>new post</from><to>новое сообщение</to></translation><!-- usage example: "1 new post"-->
+<translation><from>new posts</from><to>новые сообщения</to></translation><!-- usage example: "2 new posts"-->
+
+<translation><from>New registered user in <t.location/>!</from><to>Новый зарегистрированный пользователь на <n.location/>!</to></translation>
+<translation><from>User profile</from><to>Профиль пользователя</to></translation>
+<translation><from>New user:</from><to>Новый пользователь:</to></translation>
+
+<translation><from><n.author/> wrote</from><to><n.author/> написал</to></translation>
+<translation><from>You still have <t.number/> day(s) left without advertising</from><to>У вас осталось <n.number/> дня(ей) без отображения рекламы.</to></translation>
+<translation><from>(Only administrators can see this message)</from><to>(Только администраторы могут видеть это сообщение)</to></translation>
+
+<translation><from>Some private items have been omitted because you don't have permission to view them.</from><to>Некоторые личные данные были скрыты, потому что у вас нет прав для их просмотра.</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Submit". We will email you a link to reset your password.</from><to>Пожалуйства введите е-майл адрес, который вы использовали при регистрации и нажмите "Подтвердить". Мы вышлем вам писмьо со ссылкой, чтобы сбросить ваш пароль.</to></translation>
+<translation><from>Submit</from><to>Подтвердить</to></translation>
+<translation><from>Password Reset Sent</from><to>Сообщение о сбросе пароля выслано</to></translation>
+<translation><from>We have sent you a link to reset your password. Please check your email now. If you don't receive the instructions in a few minutes, check your spam folder or try to resend the request.</from><to>Мы выслали вам ссылку для сброса вашего пароля. Пожалуйста проверьте вашу почту сейчас. Если вы не получили инструкцию в течении нескольких минут, проверьте вашу папку  СПАМ, или повторите запрос на сброс пароля.</to></translation>
+<translation><from>Reset your password / <t.location/></from><to>Сбросить ваш пароль / <n.location/></to></translation>
+<translation><from>We received a request to reset your password in <t.location/>.</from><to>Мы получили запрос на сброс вашего пароля на <n.location/>.</to></translation>
+<translation><from>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</from><to>Если вы хотите сбросить ваш пароль, нажмите на ссылку ниже (или скопируйте и вставьте ссылку в ваш браузер):</to></translation>
+<translation><from>If you don't want to reset your password, please ignore this message. Your password will not be reset.</from><to>Если вы не хотите сбрасывать ваш пароль, пожалуйста игнорируйте это сообщение. Ваша пароль не будет сброшен.</to></translation>
+
+<translation><from><b>IMPORTANT:</b> If you use this option, messages posted in the archive will NOT be sent to the mailing list.</from><to><b>ВАЖНО:</b> Если вы используете эту опцию, сообщения размещенные в архиве НЕ БУДУТ отправлены в список рассылки.</to></translation>
+<translation><from>Message Preview</from><to>Предпросмотр сообщения</to></translation>
+<translation><from>Your changes will not be sent to the mailing list.</from><to>Ваши изменения не будут отправлены в список рассылки.</to></translation>
+<translation><from>If you want others in the mailing list to know of your changes, please compose a new message or reply to your original message.</from><to>Если вы хотите, чтобы другие в списке рассылок знали о ваших изменениях, пожалуйста напишите новое сообщение, или ответьте на ваше исходное сообщение. </to></translation>
+
+<translation><from>Poll</from><to>Опрос</to></translation>
+<translation><from>Add new poll</from><to>Добавить новый опрос</to></translation>
+<translation><from>Question:</from><to>Вопрос:</to></translation>
+<translation><from>Answers:</from><to>Ответ:</to></translation>
+<translation><from>Add new answer</from><to>Добавить новый ответ</to></translation>
+<translation><from>1 vote</from><to>проголосовало 1</to></translation>
+<translation><from><t.number/> votes</from><to><n.number/> проголосовало</to></translation>
+<translation><from>Total votes:</from><to>Всего проголосовавших:</to></translation>
+<translation><from>Vote</from><to>Проголосовать</to></translation><!-- verb -->
+<translation><from>Your vote has been submitted.</from><to>Ваш голос был принят.</to></translation>
+<translation><from>This poll is closed.</from><to>Опрос закрыт.</to></translation>
+<translation><from>This poll ends on <t.date/>.</from><to>Опрос завершится <n.date/></to></translation>
+<translation><from>This poll ended on <t.date/>.</from><to>Опрос завершён <n.date/></to></translation>
+<translation><from>Results will be shown only after poll has ended.</from><to>Результыты будут видны, только после завершения опроса.</to></translation>
+<translation><from>You have to vote before you can see the results.</from><to>Вы должны проголосовать, прежде, чем увидите результат.</to></translation>
+<translation><from>You cannot change your vote after voting.</from><to>Вы не можете отменить свой голос после голосования.</to></translation>
+<translation><from>You can select up to <t.number/> options.</from><to>Вы можете выбрать варианты <n.number/> выше.</to></translation>
+<translation><from>Please select no more than <t.number/> options.</from><to>Пожалуйста выберите не больше чем <n.number/> вариантов ответа.</to></translation>
+<translation><from>Please select at least one option.</from><to>Пожалуйста выберите по крайней мере один вариант.</to></translation>
+<translation><from>Remove Poll</from><to>Удалить опрос</to></translation>
+<translation><from>Invalid poll parameters.</from><to>Неверный параметр опроса</to></translation>
+<translation><from>Poll duration must be a non-negative integer or blank.</from><to>Продолжительность опроса может быть только целым числом или оставьте поле пустым.</to></translation>
+<translation><from>Poll maximum allowed choices must be a non-negative integer or blank.</from><to>Максимальное количество вариантов опроса, может быть только целым числом или оставьте поле пустым.</to></translation>
+<translation><from>Delete this poll, including all votes?</from><to>Удалить опрос, включая все голоса?</to></translation>
+<translation><from>Poll has been deleted.</from><to>Опрос удалён.</to></translation>
+<translation><from>Who can create polls.</from><to>Кто может создавать опросы.</to></translation>
+<translation><from>Allow vote changes</from><to>Разрешить изменять голосование</to></translation>
+<translation><from>Allow viewing results before end date (poll creators can always view the results)</from><to>Разрешить просматривать результаты до окончания опроса (создатель опроса, всегда может просматривать результаты)</to></translation>
+<translation><from>Allow viewing results before vote</from><to>Разрешить просматривать результаты  до окончания голосования</to></translation>
+<translation><from>Multiple selections allowed:</from><to>Разрешить выбор нескольких вариантов:</to></translation>
+<translation><from>Poll ends after <t.number/> days (leave blank for unlimited).</from><to>Опрос завершится через <n.number/> дня (ей) (оставьте пустым для неограниченного действия).</to></translation>
+<translation><from>Login to vote</from><to>Войди и проголосуй</to></translation>
+<translation><from>This message has a poll</from><to>Это сообщение опрос</to></translation>
+<translation><from>Visit the link below if you want to participate:</from><to>Посетите ссылку ниже, если вы хотите учавствовать:</to></translation>
+
+<translation><from>Current length: <t.number/> characters</from><to>Текущая длинна: <n.number/> знаков</to></translation>
+<translation><from>Edit Post</from><to>Редактировать сообщение</to></translation>
+<translation><from><t.location/> - Your credits are running out</from><to><n.location/> - Ваши кредиты заканчиваются</to></translation>
+<translation><from><t.location/> is running out of ad-free credits.</from><to><n.location/> Заканчивается бесплатное отображение без рекламы.</to></translation>
+<translation><from>If you want to buy more credits, visit:</from><to>Если вы хотите купить больше кредитов, посетите:</to></translation>
+<translation><from>only in this topic</from><to>только в этой теме</to></translation>
+<translation><from>everywhere</from><to>везде</to></translation>
+
+<translation><from>If you want to invite subscribers, please request this feature in the <n.support_link/> forum.</from><to>Если вы хотите пригласить подписчиков, пожалуйста запросите эту возможность в <n.support_link/>.</to></translation>
+<translation><from>We can install this module for you, but the Nabble team must approve this request to prevent abuse and spam.</from><to>Мы можем установить этот модуль для вас, но коллектив Nabble должен утвердить этот запрос во избежание злоупотреблений и спама.</to></translation>
+
+<translation><from>Edit Signature</from><to>Редактировать подпись</to></translation>
+<translation><from>Current Signature</from><to>Текущая подпись</to></translation>
+<translation><from>Save Signature</from><to>Сохранить подпись</to></translation>
+<translation><from>Signature is in HTML format</from><to>Подпись в формате HTML</to></translation>
+
+<translation><from>Download backup</from><to>Загрузить резервную копию</to></translation>
+<translation><from>Backup</from><to>Зарезервировать</to></translation>
+<translation><from>Here you can download a backup of <t.location/>.</from><to>Здесь вы можете загрузить резервную копию <n.location/>.</to></translation>
+<translation><from>When you press the button below, the system will start the generation of the backup file and this may take some minutes to finish. You will receive an email with a link to download the file when it is ready.</from><to>После нажатия на кнопку ниже, система начнет создавать резервный файл и это может занять несколько минут до завершения. Вы получите на е-майл ссылку для загрузки этого файла, когда он будет готов.</to></translation>
+<translation><from>Generate backup file</from><to>Создание резервного файла</to></translation>
+<translation><from>The system is generating the backup now. When it is finished, a link to the backup file will be emailed to you.</from><to>Сейчас система создаёт резервный файл. После завершения, ссылка на резервный файл будет выслана вам на почту. </to></translation>
+<translation><from>The backup file is generated with the <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a> tool, which is an open source project in Java. You should visit the project website if you want to restore your backup into a Postgresql database.</from><to>Резервный файл создан с помощью <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a>, инструмента, на открытом коде языка Java. Посетите сайт проекта, если вы хотите восстановить вашу резервную копию в формате базы Postgresql.</to></translation>
+<translation><from>Backup of <t.location/></from><to>Резервная копия <n.location/></to></translation>
+<translation><from>Here is your backup file:</from><to>Здесь находится ваш резервный файл:</to></translation>
+<translation><from><t.location/> has been deleted</from><to><n.location/> был удалён</to></translation>
+<translation><from>Your Nabble site "<t.location/>" has been deleted.</from><to>Ваш сайт Nabble "<n.location/>" был удалён.</to></translation>
+<translation><from>You can download a backup of this site from the link below. Nabble will try to keep this backup available for a few months, but this is not guaranteed. If this content is important to you, save this copy as soon as possible.</from><to>Вы можете скачать резервный файл на этом сайте по ссылке внизу. Nabble будет хранить этот резервный файл несколько месяцев,  но это не гарантируется. Если содержание важное для вас, сохраните копию если это возможно.</to></translation>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_sv.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,338 @@
+## Swedish
+
+<!-- new -->
+<translation><from>The validation code did not match the picture.</from><to>Valideringskoden matchade inte bilden.</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>Ogiltig e-postadress: <n.email/></to></translation>
+<translation><from>You must provide a user name.</from><to>Du måste ange ett användarnamn.</to></translation>
+<translation><from>The subject is required.</from><to>Ämne krävs.</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>Filen laddades inte upp: <n.file/> (var god försök igen eller ta bort taggen)</to></translation>
+<translation><from>Authorized Users Only</from><to>Endast auktoriserade användare</to></translation>
+<translation><from>You Cannot Post Here</from><to>Du kan inte skriva här</to></translation>
+<translation><from>You must login to your account.</from><to>Du måste logga in på ditt konto.</to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>Du har gjort för många inlägg på kort tid. Vänligen försök senare.</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[Spam Detector] Ogiltigt meddelande med för många '<n.text/>' ord.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[Spam Detector] Ämnet innehåller kända spamord.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[Spam Detector] Meddelandet innehåller kända spamord.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[Spam Detector] Ämnet kan inte innehålla '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[Spam Detector] Meddelandet kan inte bestå av '<n.text/>'.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>Bilden laddades inte upp: <n.image/> (var god försök igen eller ta bort taggen)</to></translation>
+<!-- End new -->
+
+<translation><from>Post new message in <t.location/></from><to>Skriv ett nytt meddelande i <n.location/></to></translation>
+<translation><from>New Topic</from><to>Ny tråd</to></translation>
+<translation><from>Last Post</from><to>Sista svaret</to></translation>
+<translation><from>Main Page</from><to>Huvudsida</to></translation>
+<translation><from>Sub-Forums</from><to>Underforum</to></translation>
+<translation><from>Count</from><to>Antal</to></translation>
+<translation><from>Location</from><to>Läge</to></translation>
+<translation><from>Pin topic</from><to>Pin topic</to></translation>
+<translation><from>Unpin topic</from><to>Unpin topic</to></translation>
+<translation><from>Lock topic</from><to>Lock topic</to></translation>
+<translation><from>Unlock topic</from><to>Unlock topic</to></translation>
+<translation><from>This topic has unread posts</from><to>This topic has unread posts</to></translation>
+<translation><from>Current Subscribers</from><to>Current Subscribers</to></translation>
+<translation><from>Add Subscribers</from><to>Add Subscribers</to></translation>
+<translation><from>Priority</from><to>Priority</to></translation>
+<translation><from>Highest</from><to>Highest</to></translation>
+<translation><from>High</from><to>High</to></translation>
+<translation><from>Normal</from><to>Normal</to></translation>
+<translation><from>Low</from><to>Low</to></translation>
+<translation><from>Lowest</from><to>Lowest</to></translation>
+<translation><from>Assignee</from><to>Assignee</to></translation>
+<translation><from>Click to filter</from><to>Click to filter</to></translation>
+<translation><from>Topics View</from><to>Topics View</to></translation>
+<translation><from>My posts in <t.location/></from><to>My posts in <n.location/></to></translation>
+<translation><from>View more</from><to>Visa fler</to></translation>
+<translation><from>Date</from><to>Datum</to></translation>
+<translation><from>Editor</from><to>Editor</to></translation>
+<translation><from>Structure</from><to>Structure</to></translation>
+<translation><from>Users</from><to>Användare</to></translation>
+<translation><from>Subject</from><to>Ämne</to></translation>
+<translation><from>Options</from><to>Alternativ</to></translation>
+<translation><from>More</from><to>Fler</to></translation>
+<translation><from>Insert Embed Tags</from><to>Insert Embed Tags</to></translation>
+<translation><from>Click for more options</from><to>Click for more options</to></translation>
+<translation><from>More Categories</from><to>More Categories</to></translation>
+<translation><from>Time</from><to>Tid</to></translation>
+<translation><from>Write Your First Post</from><to>Write Your First Post</to></translation>
+<translation><from>Click here to make your first post</from><to>Klicka här för att göra ditt första inlägg</to></translation>
+<translation><from>No sub-forums</from><to>No sub-forums</to></translation>
+<translation><from>No replies</from><to>No replies</to></translation>
+<translation><from>Empty</from><to>Töm</to></translation>
+<translation><from>Archives</from><to>Arkiv</to></translation>
+<translation><from>Advanced Search</from><to>Avancerad sökning</to></translation>
+<translation><from>message</from><to>meddelande</to></translation>
+<translation><from>messages</from><to>meddelanden</to></translation>
+<translation><from>subscriber</from><to>prenumerant</to></translation>
+<translation><from>subscriberer</from><to>prenumeranter</to></translation>
+<translation><from>max. 80 characters</from><to>max. 80 characters</to></translation>
+<translation><from>Sort by relevance</from><to>Sort by relevance</to></translation>
+<translation><from>Sorted by relevance</from><to>Sorted by relevance</to></translation>
+<translation><from>Sort by date</from><to>Sort by date</to></translation>
+<translation><from>Sorted by date</from><to>Sorted by date</to></translation>
+<translation><from>Change Post Date</from><to>Ändra Inläggets Datum</to></translation>
+<translation><from>Change post date</from><to>Ändra inläggets datum</to></translation>
+<translation><from>Message text contains</from><to>Message text contains</to></translation>
+<translation><from>Message subject contains</from><to>Message subject contains</to></translation>
+<translation><from>Author name</from><to>Author name</to></translation>
+<translation><from>Any message part contains</from><to>Any message part contains</to></translation>
+<translation><from>Message date</from><to>Message date</to></translation>
+<translation><from>Login</from><to>Login</to></translation>
+<translation><from>Add / Remove Groups</from><to>Lägg till / Ta bort grupper</to></translation>
+<translation><from>All</from><to>Alla</to></translation>
+<translation><from>Anonymous</from><to>Anonym</to></translation>
+<translation><from>Cancel</from><to>Avbryt</to></translation>
+<translation><from>Advertisement</from><to>Annons</to></translation>
+<translation><from>Ban this user</from><to>Stäng av denna användaren</to></translation>
+<translation><from>Unban this user</from><to>Häv avstängningen på användaren</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>Notifiera mig via e-post när någon svarar på denna tråden</to></translation>
+<translation><from>Buy ad-free credits</from><to>Köp reklamfria krediter</to></translation>
+<translation><from>by <t.author/></from><to>av <n.author/></to></translation>
+<translation><from>Change appearance</from><to>Ändra utseende</to></translation>
+<translation><from>Change application type</from><to>Ändra applikationstyp</to></translation>
+<translation><from>Change code image</from><to>Ändra bildkod</to></translation>
+<translation><from>Change domain name</from><to>Ändra domännamn</to></translation>
+<translation><from>Change parent</from><to>Ändra överordnade</to></translation>
+<translation><from>Change permissions</from><to>Ändra behörigheter</to></translation>
+<translation><from>Choose a subcategory</from><to>Välj en underkategori</to></translation>
+<translation><from>comment</from><to>kommentar</to></translation>
+<translation><from>comments</from><to>kommentarer</to></translation>
+<translation><from>Help</from><to>Hjälp</to></translation>
+<translation><from>Groups</from><to>Grupper</to></translation>
+<translation><from>Feeds</from><to>RSS-flöden</to></translation>
+<translation><from>replies</from><to>svar</to></translation>
+<translation><from>Replies</from><to>Svar</to></translation>
+<translation><from>reply</from><to>svara</to></translation>
+<translation><from>Reply</from><to>Svara</to></translation>
+<translation><from>Related Help Article</from><to>Related Help Article</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>Välj en underkategori för att skicka ditt meddelande</to></translation>
+<translation><from>Created by <t.author/></from><to>Skapad av <n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>Skapa nytt <n.element/></to></translation>
+<translation><from>Create <t.element/></from><to>Skapa <n.element/></to></translation>
+<translation><from>Delete</from><to>Radera</to></translation>
+<translation><from>Delete this post</from><to>Radera detta inlägg</to></translation>
+<translation><from>Delete this topic</from><to>Radera detta tråd</to></translation>
+<translation><from>Don't show this message again</from><to>Visa inte detta meddelande igen</to></translation>
+<translation><from>Edit name & description</from><to>Redigera namn och beskrivning</to></translation>
+<translation><from>Edit post</from><to>Redigera inlägg</to></translation>
+<translation><from>Embedding options</from><to>Inbäddnings alternativ</to></translation>
+<translation><from>Embed post</from><to>Bädda in inlägg</to></translation>
+<translation><from>Filter by group</from><to>Filtrera efter grupp</to></translation>
+<translation><from>Forum</from><to>Forum</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to>Gratis inbäddningsbar <n.app/></to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>Jag är en prenumerant, låt mig skicka nu</to></translation>
+<translation><from>in <t.location/></from><to>i <n.location/></to></translation>
+<translation><from>In Reply To</from><to>Svar till</to></translation>
+<translation><from>Learn more</from><to>Lär dig mer</to></translation>
+<translation><from>Leave a comment</from><to>Lämna en kommentar</to></translation>
+<translation><from>This is a mailing list archive</from><to>This is a mailing list archive</to></translation>
+<translation><from>Mailing list archive settings</from><to>E-postlistans inställningar</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>E-postlistans prenumerationspåminnelse</to></translation>
+<translation><from>Manage banned users</from><to>Hantera avstängda användare</to></translation>
+<translation><from>Manage <t.items/></from><to>Hantera <n.items/></to></translation>
+<translation><from>Manage pinned topics</from><to>Hantera klistrade trådar</to></translation>
+<translation><from>Manage subscribers</from><to>Hantera prenumeranter</to></translation>
+<translation><from>Manage users & groups</from><to>Hantera användare och grupper</to></translation>
+<translation><from>Message is in HTML Format</from><to>Meddelande är i HTML-format</to></translation>
+<translation><from>Description is in HTML Format</from><to>Description is in HTML Format</to></translation>
+<translation><from>Message</from><to>Meddelande</to></translation>
+<translation><from>more options</from><to>fler alternativ</to></translation>
+<translation><from>Move post</from><to>Flytta inlägget</to></translation>
+<translation><from>Move topic</from><to>Flytta tråd</to></translation>
+<translation><from>My Nabble Applications</from><to>Mina Nabble-applikationer</to></translation>
+<translation><from>Name</from><to>Namn</to></translation>
+<translation><from>New Post</from><to>Nytt inlägg</to></translation>
+<translation><from>No Filter</from><to>Inget filter</to></translation>
+<translation><from>Online Users</from><to>Användare online</to></translation>
+<translation><from>or</from><to>eller</to></translation>
+<translation><from>People in <t.location/></from><to>Personer i <n.location/></to></translation>
+<translation><from>People</from><to>Personer</to></translation>
+<translation><from>Permalink</from><to>Permalänk</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>Var god respektera e-postlistans etikett</to></translation>
+<translation><from>Post by email</from><to>Skicka som e-post</to></translation>
+<translation><from>Post Count</from><to>Antal inlägg</to></translation>
+<translation><from>Post Message</from><to>Skicka meddelande</to></translation>
+<translation><from>Post New Message</from><to>Skicka nytt meddelande</to></translation>
+<translation><from>Posts</from><to>Inlägg</to></translation>
+<translation><from>Preview Message</from><to>Förhandsgranska meddelande</to></translation>
+<translation><from>Print post</from><to>Skriv ut inlägg</to></translation>
+<translation><from>Raw mail</from><to>Oformaterat e-post</to></translation>
+<translation><from>read more</from><to>läs mer</to></translation>
+<translation><from>Read more</from><to>Läs mer</to></translation>
+<translation><from>Registered</from><to>Registrerad</to></translation>
+<translation><from>Register</from><to>Registrera</to></translation>
+<translation><from>Reply to author</from><to>Svara författaren</to></translation>
+<translation><from>Return to <t.location/></from><to>Återgå till <n.location/></to></translation>
+<translation><from>Send Email to <t.author/></from><to>Skicka e-post till <n.author/></to></translation>
+<translation><from>Subcategories</from><to>Underkategorier</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>Underkategorier nedan <n.location/></to></translation>
+<translation><from>Subcategory</from><to>Underkategori</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>Underforum och trådar</to></translation>
+<translation><from>Sub-Forum</from><to>Underforum</to></translation>
+<translation><from>Subscribe</from><to>Prenumerera</to></translation>
+<translation><from>Subscribe via email</from><to>Prenumerera via e-post</to></translation>
+<translation><from>topics</from><to>trådar</to></translation>
+<translation><from>Topics</from><to>Trådar</to></translation>
+<translation><from>topic</from><to>tråd</to></translation>
+<translation><from>Unable to Post</from><to>Det går inte att göra inlägg</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>Oregistrerad / Avaktiverad</to></translation>
+<translation><from>Unregistered</from><to>Oregistrerad</to></translation>
+<translation><from>Unregistered User</from><to>Oregistrerad användare</to></translation>
+<translation><from>Unsubscribe</from><to>Avsluta prenumeration</to></translation>
+<translation><from>Users & Groups</from><to>Användare & grupper</to></translation>
+<translation><from>views</from><to>visningar</to></translation>
+<translation><from>Views</from><to>Visningar</to></translation>
+<translation><from>view</from><to>visa</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>Visa alla meddelanden i detta underforum</to></translation>
+<translation><from>View mailing list website</from><to>Visa e-postlistans hemsida</to></translation>
+<translation><from>Write Your First Headline</from><to>Skriv din första rubrik</to></translation>
+<translation><from>Your Name</from><to>Ditt namn</to></translation>
+<translation><from>Insert Image</from><to>Insert Image</to></translation>
+<translation><from>List of Subcategories</from><to>Lista av underkategorier</to></translation>
+<translation><from>Add an image to your post</from><to>Add an image to your post</to></translation>
+<translation><from>Link</from><to>Link</to></translation>
+<translation><from>Embed</from><to>Embed</to></translation>
+<translation><from>Upload a file</from><to>Upload a file</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>Add content that should not be encoded (e.g., code)</to></translation>
+<translation><from>Raw text</from><to>Raw text</to></translation>
+<translation><from>Hide email</from><to>Hide email</to></translation>
+<translation><from>Currently Nabble supports</from><to>Currently Nabble supports</to></translation>
+<translation><from>Videos from Youtube.com</from><to>Videos from Youtube.com</to></translation>
+<translation><from>Videos from LiveLeak.com</from><to>Videos from LiveLeak.com</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>Polls from Polldaddy.com (flash polls only)</to></translation>
+<translation><from>Quote</from><to>Quote</to></translation>
+<translation><from>Quote the original message</from><to>Quote the original message</to></translation>
+<translation><from>Bold</from><to>Bold</to></translation>
+<translation><from>Italic</from><to>Italic</to></translation>
+<translation><from>click here</from><to>click here</to></translation>
+<translation><from>visit <t.url/></from><to>visit <n.url/></to></translation>
+<translation><from>View message</from><to>View message</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>To unsubscribe from <n.location/></to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>This topic is assigned to you at priority <n.priority/></to></translation>
+
+<translation>
+	<from>If you reply to this email, your message will be added to the discussion below</from>
+	<to>Om du svarar på detta e-postmeddelande kommer ditt meddelande att läggas till i diskussionen nedan</to>
+</translation>
+
+<translation>
+	<from>To start a new topic under <t.location/>, email <t.p2/></from>
+	<to>To start a new topic under <n.location/>, email <n.p2/></to>
+</translation>
+
+<translation>
+	<from><t.username/> created a new subcategory</from>
+	<to><n.username/> created a new subcategory</to>
+</translation>
+
+<translation>
+	<from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from>
+	<to>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</to>
+</translation>
+
+<translation>
+	<from>Sorry, but the administrators have banned you.</from>
+	<to>Ursäkta men administratörerna har stäng av dig.</to>
+</translation>
+
+<translation>
+	<from>You can't post a message here, but you can post in other places.</from>
+	<to>Du kan inte göra ett inlägg här, men du kan göra inlägg på andra ställen.</to>
+</translation>
+
+<translation>
+	<from>This forum is an archive for the mailing list</from>
+	<to>Detta forum är ett arkiv för e-postlistan</to>
+</translation>
+
+<translation>
+	<from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from>
+	<to>Detta forum är ett arkiv/gateway som kommer vidarebefordra ditt inlägg till <b><n.mailing_list_address/></b> e-postlistan.</to>
+</translation>
+
+
+<translation>
+	<from>If you are posting a question, please try search first. Your question may have already been answered.</from>
+	<to>Vänligen sök först innan du ställer en fråga eftersom din fråga kanske redan har blivit besvarad.</to>
+</translation>
+
+<translation>
+	<from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from>
+	<to>Skicka inte upprepade gånger. Vänta ett par dagar. Folk kommer att läsa ditt inlägg via e-post.</to>
+</translation>
+
+<translation>
+	<from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from>
+	<to>Citera vad du svarar på och trimma ner det till endast de relevanta delarna. Detta ger sammanhang för dem som kommer att läsa ditt meddelande via e-post.</to>
+</translation>
+
+<translation>
+	<from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from>
+	<to>Undvik småprat som: "Tack", "Bra"... Du kan <n.page_node.reply_to_author_link.>skicka ett privat e-post</n.page_node.reply_to_author_link.> om du vill.</to>
+</translation>
+
+<translation>
+	<from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from>
+	<to>Du kan <n.page_node.unauthorized_link.>fråga efter tillåtelse till att göra ett inlägg</n.page_node.unauthorized_link.> här eller kontakta <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> om du har frågor.</to>
+</translation>
+
+<translation>
+	<from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from>
+	<to>Du kanske behöver <n.mailing_list_options_link.>prenumerera på denna e-postlistan</n.mailing_list_options_link.> för att ditt meddelande ska bli godkänt.</to>
+</translation>
+
+<translation>
+	<from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from>
+	<to>E-postlistan kan kräva att du prenumererar innan ditt inlägg blir godkänt. Notera: att vara registrerad hos Nabble innebär inte att du kommer automatiskt att vara abonnent till denna e-postlista. Om du inte har prenumererat än, gör det nu. Om du inte kommer ihåg eller du inte är säker, bara prenumerera igen eftersom det inte gör någon skada.</to>
+</translation>
+
+<translation>
+	<from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from>
+	<to>Tyvärr, men du kan inte skapa nya trådar här.<br/>Notera: du kanske fortfarande kan svara på inlägg.</to>
+</translation>
+
+<translation>
+	<from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from>
+	<to><n.register_link.>Registrera nu</n.register_link.> om du vill ändra din profil, få inlägg via e-post, kontrollera dina stjärnmärkta objekt eller ha tillgång till din globala profil.</to>
+</translation>
+
+<translation>
+	<from>Messages posted here will be sent to this mailing list.</from>
+	<to>Meddelanden skrivna här kommer att skickas till denna e-postlista.</to>
+</translation>
+
+<translation>
+	<from>Since you are not a registered user, we must check that you are a human.</from>
+	<to>Eftersom du inte är en registrerad användare så måste vi kolla så att du är en person.</to>
+</translation>
+
+<translation>
+	<from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from>
+	<to><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</to>
+</translation>
+
+<translation>
+	<from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from>
+	<to><b>Notera</b>: Eftersom du är en administratör, så kan du ändra <n.page_node.change_permissions_link.>rättigheterna för <n.location/></n.page_node.change_permissions_link.> så du kan vara säker på att du kan skapa nya trådar här.</to>
+</translation>
+
+<translation>
+	<from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from>
+	<to>Detta meddelande kommer att skickas från <b><n.from/></b> till <b><n.to/></b> e-postlistan.</to>
+</translation>
+
+
+## MONTHS ##
+
+<translation><from>January</from><to>Januari</to></translation>
+<translation><from>February</from><to>Februari</to></translation>
+<translation><from>March</from><to>Mars</to></translation>
+<translation><from>April</from><to>April</to></translation>
+<translation><from>May</from><to>Maj</to></translation>
+<translation><from>June</from><to>Juni</to></translation>
+<translation><from>July</from><to>Juli</to></translation>
+<translation><from>August</from><to>Augusti</to></translation>
+<translation><from>September</from><to>September</to></translation>
+<translation><from>October</from><to>Oktober</to></translation>
+<translation><from>November</from><to>November</to></translation>
+<translation><from>December</from><to>December</to></translation>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/lang_tu.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,770 @@
+## Türkçe(Translater By MuYuTa)
+
+## MONTHS ##
+
+<translation><from>January</from><to>Ocak</to></translation>
+<translation><from>February</from><to>Şubat</to></translation>
+<translation><from>March</from><to>Mart</to></translation>
+<translation><from>April</from><to>Nisan</to></translation>
+<translation><from>May</from><to>Mayıs</to></translation>
+<translation><from>June</from><to>Haziran</to></translation>
+<translation><from>July</from><to>Temmuz</to></translation>
+<translation><from>August</from><to>Ağustos</to></translation>
+<translation><from>September</from><to>Eylül</to></translation>
+<translation><from>October</from><to>Ekim</to></translation>
+<translation><from>November</from><to>Kasım</to></translation>
+<translation><from>December</from><to>Aralık</to></translation>
+
+<translation><from>Jan</from><to>Oca</to></translation>
+<translation><from>Feb</from><to>Şub</to></translation>
+<translation><from>Mar</from><to>Mar</to></translation>
+<translation><from>Apr</from><to>Nis</to></translation>
+<!--translation><from>May</from><to>May</to></translation-->
+<translation><from>Jun</from><to>Haz</to></translation>
+<translation><from>Jul</from><to>Tem</to></translation>
+<translation><from>Aug</from><to>Ağu</to></translation>
+<translation><from>Sep</from><to>Eyl</to></translation>
+<translation><from>Oct</from><to>Eki</to></translation>
+<translation><from>Nov</from><to>Kas</to></translation>
+<translation><from>Dec</from><to>Ara</to></translation>
+
+## SENTENCES ##
+
+<translation><from>Abusing this feature is also a violation of our Terms of Use.</from><to>Bu özellik Kullanım Şartlarına aykırıdır.</to></translation>
+<translation><from>Access Request</from><to>Erişim İsteği</to></translation>
+<translation><from>Account settings</from><to>Hesap ayarları</to></translation>
+<translation><from>Account Settings</from><to>Hesap Ayarları</to></translation>
+<translation><from>Action</from><to>Eylem</to></translation>
+<translation><from>Add a link to another page</from><to>Başka bir sayfaya bağlantı ekle</to></translation>
+<translation><from>Add a new comment</from><to>Yeni yorum ekle</to></translation>
+<translation><from>Add a new group</from><to>Yeni gurup ekle</to></translation>
+<translation><from>Add an image to your post</from><to>Mesajına resim ekle</to></translation>
+<translation><from>Add another address</from><to>Başka bir adres ekle</to></translation>
+<translation><from>Add content that should not be encoded (e.g., code)</from><to>Kodlanmış içerik eklemeyin (örnek, kod)</to></translation>
+<translation><from>Adding Sub-Headers</from><to>Alt Başlık ekle</to></translation>
+<translation><from>Add New Group</from><to>Yeni Gurup ekle</to></translation>
+<translation><from>Add / Remove Groups</from><to>Gurupları Ekle/Kaldır</to></translation>
+<translation><from>Add smileys and funny animations</from><to>ifade ve animasyon ekle</to></translation>
+<translation><from>Add Subscribers</from><to>Abone ekle</to></translation>
+<translation><from>Add this item to your favorites list</from><to>Favori Listene ekle</to></translation>
+<translation><from>Administrator</from><to>Admin</to></translation>
+<translation><from>Adult or mature content, explicit sexual activity, nudity, other sexual content.</from><to>cinsel içerikli illegal içerik.</to></translation>
+<translation><from>Adults fighting, physical attack, youth violence, animal abuse or promotion of terrorism.</from><to>yetişkin içerikli,terör propagandası , psikopatlık,hayvanlık.</to></translation>
+<translation><from>Advanced Search</from><to>Gelişmiş Arama</to></translation>
+<translation><from>Advanced Settings</from><to>Gelişmiş Ayarlar</to></translation>
+<translation><from>Advertisement</from><to>Reklam</to></translation>
+<translation><from>Alert me by email when someone posts to this thread</from><to>Bu konuya mesaj yollandığında bana e-posta gönder</to></translation>
+<translation><from>All</from><to>Tümü</to></translation>
+<translation><from>all of the words:</from><to>Tüm Kelimeler:</to></translation>
+<translation><from>All posts from <t.author/> have been successfully removed.</from><to>Tüm Gönderiler <n.author/> Başarıyla Silindi.</to></translation>
+<translation><from>All posts</from><to>Tüm Gönderiler</to></translation>
+<translation><from>All users belong to this group</from><to>Tüm Kullanıcılar Bu Guruba Dahil</to></translation>
+<translation><from>All Users</from><to>Tüm Kullanıcılar</to></translation>
+<translation><from>All users that have registered to <t.location/>. These users have confirmed their email addresses and are able to login to the system.</from><to>Tüm Kayıtlı Kullanıcılar <n.location/>. Bu kullanıcılar e-posta adreslerini onaylayıp giriş yapar.</to></translation>
+<translation><from>Already Subscribed</from><to>Zaten Abone</to></translation>
+<translation><from>An email has been sent to you.</from><to>Sana Bir e-posta Gönderdik.</to></translation>
+<translation><from>Anonymous</from><to>Anonim</to></translation>
+<translation><from>anonymous user</from><to>Anonim Kullanıcı</to></translation>
+<translation><from>anonymous users</from><to>Anonim Kullanıcılar</to></translation>
+<translation><from>Any message part contains</from><to>Mesaj Bölümü</to></translation>
+<translation><from>Application</from><to>Uygulama</to></translation>
+<translation><from>Apps</from><to>Uygulamalar</to></translation>
+<translation><from>Assignee</from><to>Bağlan</to></translation>
+<translation><from>Assign</from><to>Bağlandı</to></translation>
+<translation><from>Assignment</from><to>Bağlanma</to></translation>
+<translation><from>As you requested, the email address to post new topics under <t.app/> is:</from><to>Talebiniz doğrultusunda, yeni başlık altında e-posta göndermek için <n.app/> bu:</to></translation>
+<translation><from>at least one of the words:</from><to>Kelimelerden en az biri:</to></translation>
+<translation><from>Atom feeds for <t.location/></from><to>için atom beslemeleri <n.location/></to></translation>
+<translation><from>at priority</from><to>öncelikte</to></translation>
+<translation><from>Authorized Users Only</from><to>Sadece Yetkili Kullanıcılar</to></translation>
+<translation><from>Author name</from><to>Yetkili Adı</to></translation>
+<translation><from>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</from><to>kısa yorumlardan kaçının mesela: "Teşekkürler", "Mükemmel"... yapabilirsin <n.page_node.reply_to_author_link.>özel bir e-posta gönder</n.page_node.reply_to_author_link.> eğer istersen.</to></translation>
+<translation><from>Banned User</from><to>Yasaklı Kullanıcı</to></translation>
+<translation><from>Ban this user</from><to>Bu kullanıcıyı Yasakla</to></translation>
+<translation><from>Ban User</from><to>Kullanıcı yasakla</to></translation>
+<translation><from>Before deleting this archive...</from><to>Bu arşivi silmeden önce...</to></translation>
+<translation><from>Below you can manage groups and users. You can copy and paste users in order to move them from one group to another.</from><to>Aşağıdaki gurupları ve kullanıcıları yönetebilirsiniz.kullanıcıları guruplara kopyalayıp yapıştırabilirsiniz.</to></translation>
+<translation><from><b>IMPORTANT</b>: Nabble will send an invitation to each email in the list. Users will have to click on a link to confirm their subscription.</from><to><b>ÖNEMLİ</b>: Nabble listesindeki her e-postaya bir davetiye gönderir.kullanıcılar aboneliği onaylamak için bir linke tıklamak zorunda.</to></translation>
+<translation><from><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription. Basically, you will subscribe to the forum archive, not to the mailing list itself. The archive subscription won't guarantee that your messages will be accepted by mailing list.</from><to><b>ÖNEMLİ:</b> bu abonelik gerçek e-posta abonelik listesinden bağımsızdır. Temelede forum arşivi aboneliğidir. Arşiv listesi aboneliği e-posta tarafından kabul edileceği garanti değildir.</to></translation>
+<translation><from><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</from><to><b>ÖNEMLİ</b>: Arşiv listesinin e-postanıza gönderilmsesi için abone olmalısınız.</to></translation>
+<translation><from>blog</from><to>blog</to></translation>
+<translation><from>Blog</from><to>Blog</to></translation>
+<translation><from><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</from><to><b>Nota</b>: Admin olduğunuzdan itibaren, yapabilirsiniz <n.page_node.change_permissions_link.>izinleri değiştirebilirsiniz <n.location/></n.page_node.change_permissions_link.> ve burada yeni konular açabilir sabitleyebilirsiniz.</to></translation>
+<translation><from>board</from><to>pano</to></translation>
+<translation><from>Board</from><to>Pano</to></translation>
+<translation><from>Bold</from><to>Kalın</to></translation>
+<translation><from><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</from><to><b>Alerta:</b> Arama dizini şu anda yeniden inşa ediliyor. Arama sonuçları eksik olabilir.</to></translation>
+<translation><from>by <t.author/></from><to>tarafından oluşturuldu <n.author/></to></translation>
+<translation><from>Cancel</from><to>Çıkış</to></translation>
+<translation><from>category</from><to>kategori</to></translation>
+<translation><from>Category</from><to>Kategori</to></translation>
+<translation><from>CAUTION: this action cannot be reverted.</from><to>DİKKAT: Bu eylem geri alınamaz.</to></translation>
+<translation><from>Change appearance</from><to>Görünümü değiştir</to></translation>
+<translation><from>Change application type</from><to>Uygulama türünü değiştir</to></translation>
+<translation><from>Change Application Type</from><to>Uygulama Türünü Değiştirme</to></translation>
+<translation><from>Change code image</from><to>Resim kodunu değiştir</to></translation>
+<translation><from>Change domain name</from><to>Alan adını değiştir</to></translation>
+<translation><from>Change language</from><to>Dili değiştir</to></translation>
+<translation><from>Change or remove your avatar image.</from><to>Avatarını değiştir yada kaldır.</to></translation>
+<translation><from>Change parent</from><to>Kaynak değiştir</to></translation>
+<translation><from>Change permissions</from><to>İzinleri değiştir</to></translation>
+<translation><from>Change Permissions</from><to>İzinleri Değiştir</to></translation>
+<translation><from>Change post date</from><to>Gönderi tarihini değiştir</to></translation>
+<translation><from>Change Post Date</from><to>Gönderi Tarihini Değiştir</to></translation>
+<translation><from>Change the signature text that is displayed at the bottom of your posts.</from><to>Mesajınızı alt kısmında görüntülenen imza metni değiştirin..</to></translation>
+<translation><from>Change User Groups</from><to>Kullanıcı Guruplarını Değiştir</to></translation>
+<translation><from>Change viewing preferences and other settings.</from><to>Görüntüleme tercihleri ??ve diğer ayarları değiştirin.</to></translation>
+<translation><from>Change Your Picture</from><to>Resmini Değiştir</to></translation>
+<translation><from>Change your registered email address, password, and your user name.</from><to>E-posta, şifre ve kullanıcı adını değiştir.</to></translation>
+<translation><from>Child abuse</from><to>Çocuk istismarı</to></translation>
+<translation><from>Choose a subcategory</from><to>Alt kategori seç</to></translation>
+<translation><from>Choose a subcategory to post your message</from><to>Lütfen mesaj göndermek için bir alt kategori seçin</to></translation>
+<translation><from>Choose the best offer for you</from><to>Sizin için en iyi teklifi seçin</to></translation>
+<translation><from>Classic</from><to>Klasik</to></translation>
+<translation><from>Clear Log</from><to>Çerezleri temizle</to></translation>
+<translation><from>Click for more options</from><to>Diğer ayarlar için tıklayın</to></translation>
+<translation><from>click here</from><to>buraya tıklayın</to></translation>
+<translation><from>Click here to make your first post</from><to>İlk gönderinizi göndermek için tıklayın</to></translation>
+<translation><from>Click to filter</from><to>Filtrelemek için tıklayın</to></translation>
+<translation><from>Close</from><to>kapat</to></translation>
+<translation><from>Close this message</from><to>Bu mesajı kapat</to></translation>
+<translation><from>comment</from><to>yorum yap</to></translation>
+<translation><from>Comment</from><to>Yorum yap</to></translation>
+<translation><from>comments</from><to>yorumlar</to></translation>
+<translation><from>Comments</from><to>Yorumlar</to></translation>
+<translation><from>Confirm Password</from><to>Şifreyi onayla</to></translation>
+<translation><from>Confirm Subscription</from><to>Aboneliği onayla</to></translation>
+<translation><from>Congratulations!</from><to>Tebrikler!</to></translation>
+<translation><from>Congratulations on your new <t.app/>!</from><to>Yeni <n.app/> için tebrik ederiz!</to></translation>
+<translation><from>Content promoting hatred or violence, abusing vulnerable individuals, bullying, racial intolerance or advocacy against any individual, group or organisation or excessive profanity.</from><to>Herhangi bir birey, grup veya kuruluşa ya da aşırı küfür karşı İçerik, nefret veya şiddete teşvik savunmasız bireylerin taciz, zorbalık, ırk ayrımcılığı veya savunmanlık. </to></translation>
+<translation><from>CONTENTS DELETED</from><to>İÇERİK SİLİNDİ</to></translation>
+<translation><from>Continue</from><to>Devam</to></translation>
+<translation><from>Copyright or privacy infringement or other legal claim.</from><to>Telif hakkı veya gizlilik ihlali veya diğer yasal hak.</to></translation>
+<translation><from>Count</from><to>Hesap</to></translation>
+<translation><from>Created by <t.author/></from><to>Oluşturdu by <n.author/></to></translation>
+<translation><from>Create new <t.element/></from><to>Yeni oluştur <n.element/></to></translation>
+<translation><from>Create <t.element/></from><to>Oluştur <n.element/></to></translation>
+<translation><from>Current Credits</from><to>Geçerli Krediler</to></translation>
+<translation><from>Currently Nabble supports</from><to>Şimdiki Nabble Destek</to></translation>
+<translation><from>Current Subscribers</from><to>Şimdiki Abonelikler</to></translation>
+<translation><from>Daily digest</from><to>Günlük yayın</to></translation>
+<translation><from>Data successfully saved</from><to>Veriler başarıyla kaydedildi</to></translation>
+<translation><from>Date</from><to>Tarih</to></translation>
+<translation><from>days</from><to>günler</to></translation>
+<translation><from>Dear <t.name/>,</from><to>Sevgili <n.name/>,</to></translation>
+<translation><from>Dear user,</from><to>Sevgili kullanıcı,</to></translation>
+<translation><from>Default</from><to>Varsayılan</to></translation>
+<translation><from>Delete all posts from this user</from><to>Bu kullanıcının tüm gönderilerini sil</to></translation>
+<translation><from>Delete Application</from><to>Uygulamayı sil</to></translation>
+<translation><from>Deleted posts</from><to>Gönderiler silindi</to></translation>
+<translation><from>Delete</from><to>Sil</to></translation>
+<translation><from>Delete this post and replies</from><to>Bu gönderiyi ve cevapları sil</to></translation>
+<translation><from>Delete this post</from><to>Bu gönderiyi sil</to></translation>
+<translation><from>Delete this topic</from><to>Bu konuyu sil</to></translation>
+<translation><from>Description is in HTML Format</from><to>Açıklama HTML formatında</to></translation>
+<translation><from>Don't post repeatedly. Wait for a few days. People will read your post by email.</from><to>Defalarca göndermeyin. Birkaç gün bekleyin. İnsanlar e-posta ile yazılan okuyacak.</to></translation>
+<translation><from>Don't show this message again</from><to>Bu mesajı tekrar gösterme</to></translation>
+<translation><from>Do you really want to delete this post?</from><to>Gerçekten bu gönderiyi silmek istiyor musunuz?</to></translation>
+<translation><from>Do you really want to permanently delete this message and all replies?</from><to>Gerçekten bu mesajı ve cevaplarını silmek istiyor musunuz?</to></translation>
+<translation><from>Do you really want to permanently <n.important.>delete</n.important.> <t.location/>?</from><to>Gerçekten kalıcı olarak <n.important.>silmek</n.important.> <n.location/>?</to></translation>
+<translation><from>Do you really want to remove the mailing list archive settings?</from><to>Gerçekten e-posta listesi arşivi ayarlarını kaldırmak istiyorumusunuz?</to></translation>
+<translation><from>Do you really want to subscribe to <t.location/>?</from><to>Gerçekten abone olmak istiyor musunuz <n.location/>?</to></translation>
+<translation><from>Do you really want to unban this user?</from><to>Gerçekten bu kullanıcıyı yasaklı listeden çıkarmak istiyormusunuz?</to></translation>
+<translation><from>Do you really want to unsubscribe from <t.location/>?</from><to>Gerçekten aboneliğinden çıkmak istiyor musunuz <n.location/>?</to></translation>
+<translation><from>Drug abuse, illicit drugs and drug paraphernalia content, abuse of fire or explosives, selling of beer or hard alcohol, tobacco and related products, weapons or ammunition or other dangerous acts.</from><to>Uyuşturucu, yasadışı uyuşturucu ve uyuşturucu gereçlerinin içerik, yangın ya da patlayıcı madde kötüye kullanımı, satan bira veya sert alkol, tütün ürünleri ve ilgili ürünler, silah veya cephane ya da diğer tehlikeli eylemler.</to></translation>
+<translation><from>Edit name & description</from><to>İsim & açıklamayı düzenle</to></translation>
+<translation><from>Edit Name & Description</from><to>İsim & Açıklamayı Düzenle</to></translation>
+<translation><from>Editor</from><to>Düzenleyici</to></translation>
+<translation><from>Edit Personal Information</from><to>Profil bilgilerini düzenle</to></translation>
+<translation><from>Edit post</from><to>Gönderiyi düzenle</to></translation>
+<translation><from>Edit Subscription</from><to>Aboneliği Düzenle</to></translation>
+<translation><from>Edit Your Signature</from><to>İmzanı Düzenle</to></translation>
+<translation><from>Email Confirmation</from><to>E-posta Onayı</to></translation>
+<translation><from>Email for <t.app/></from><to>için E-posta <n.app/></to></translation>
+<translation><from>Email</from><to>E-posta</to></translation>
+<translation><from>Email Subscription</from><to>E-posta aboneliği</to></translation>
+<translation><from>Email this post to...</from><to>Bu gönderiyi E-postala...</to></translation>
+<translation><from>Embedding Contents</from><to>İçeriği Göm</to></translation>
+<translation><from>Embedding options</from><to>Gömme ayarları</to></translation>
+<translation><from>Embed</from><to>Göm</to></translation>
+<translation><from>Embed post</from><to>Gönderiyi göm</to></translation>
+<translation><from>Embed Tags</from><to>Etiketleri göm</to></translation>
+<translation><from>Embed this <t.app/></from><to>Bunu göm <n.app/></to></translation>
+<translation><from>Empty</from><to>Boş</to></translation>
+<translation><from>Enter a valid email address.</from><to>Geçerli bir e-posta adresi girin.</to></translation>
+<translation><from>Enter below your email address and we will send a confirmation email to you.</from><to>E-posta adresinizi aşağıya girin, size bir onay e-postası göndereceğiz.</to></translation>
+<translation><from>Enter one email address or user name per row:</from><to>Satır başına bir e-posta adresi veya kullanıcı adı girin:</to></translation>
+<translation><from>Enter one user per row</from><to>Satır başına bir kullanıcı girin</to></translation>
+<translation><from>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent, or leave blank to make this message an independent topic:</from><to>permalink girin <b>forum</b> veya <b>gönderi</b> Bu yeni üst, ya da bu mesajı bağımsız bir başlık yapmak için boş bırakın:</to></translation>
+<translation><from>Enter the homepage of this mailing list, where users can find more information.</from><to>Bu e-posta listesi ana sayfasına girin, kullanıcılara daha fazla bilgi bulabilir.</to></translation>
+<translation><from>Enter the prefix that this mailing list uses before the subject. The prefix will be automatically removed from the imported emails.</from><to>Bu e-posta listesi konu daha önce kullandığı öneki girin.Önek otomatik olarak ithal edilen e-postalar silinecektir.</to></translation>
+<translation><from>Enter your email address</from><to>E-posta adresinizi girin</to></translation>
+<translation><from>Example: johnsmith@domain.com</from><to>Örnek: joao_da_silva@dominio.com.br</to></translation>
+<translation><from>Example: listname@listdomain.com</from><to>Örnek: nome_da_lista@dominio_da_lista.com</to></translation>
+<translation><from>Examples:</from><to>Örnekler:</to></translation>
+<translation><from>Examples: '[the-list]', 'Abc:', etc.</from><to>Örnekler: '[bir-liste]', 'Abc:', etc.</to></translation>
+<translation><from>Explain to the administrator(s) why you want to access this restricted area.</from><to>Bu yasak bölgeye girmek istiyorum neden yönetici (leri) açıklayın.</to></translation>
+<translation><from>Explanation from this user:</from><to>Bu kullanıcıdan açıklama:</to></translation>
+<translation><from>Extras & add-ons</from><to>Ekstralar ve eklentileri</to></translation>
+<translation><from>Failed</from><to>Başarısız</to></translation>
+<translation><from>Favorite (click to remove this item from your favorites list)</from><to>Favori (favori listenizden bu maddeyi kaldırmak için tıklayın)</to></translation>
+<translation><from>Feeds</from><to>Yayın Takibi</to></translation>
+<translation><from>File was not uploaded: <t.file/> (please upload again or remove the tag)</from><to>Dosya yüklenmedi: <n.file/> (lütfen tekrar yükleyin yada etiketi kaldırın)</to></translation>
+<translation><from>Filter by group</from><to>Gurp filtresi</to></translation>
+<translation><from>Floating sub-forum</from><to>Alt forum ekleniyor</to></translation>
+<translation><from>Forgot Password?</from><to>Şifreni Unuttunmu?</to></translation>
+<translation><from>Forgot your password?</from><to>Şifreni mi unuttun?</to></translation>
+<translation><from>For more info, see: <t.info/></from><to>Daha fazla bilgi için, bkz: <n.info/></to></translation>
+<translation><from>forum</from><to>forum</to></translation>
+<translation><from>Forum</from><to>Forum</to></translation>
+<translation><from>Free Embeddable <t.app/></from><to><n.app/> Ücretsiz Gömme</to></translation>
+<translation><from>From now on, you will receive an email for each message posted under <t.location/>.</from><to>Şu andan itibaren, sen altında gönderilen her mesaj için bir e-posta alacaksınız <n.location/>.</to></translation>
+<translation><from>gallery</from><to>galeri</to></translation>
+<translation><from>Gallery</from><to>Galeri</to></translation>
+<translation><from>Gambling or casino-related content</from><to>Kumar veya kumarhane ile ilgili içerik</to></translation>
+<translation><from>Go back</from><to>Geri</to></translation>
+<translation><from>Go to next message</from><to>Sonraki mesaja geç</to></translation>
+<translation><from>Group Name:</from><to>Gurup adı:</to></translation>
+<translation><from>Groups</from><to>Guruplar</to></translation>
+<translation><from>Groups of this user</from><to>Bu kullanıcının gurupları</to></translation>
+<translation><from>Hacking / cracking</from><to>Hackleme / cracking</to></translation>
+<translation><from>Harmful dangerous acts</from><to>Zararlı tehlikeli eylemler</to></translation>
+<translation><from>Hateful or abusive content</from><to>Nefret dolu ya da olumsuz içerik</to></translation>
+<translation><from>Help</from><to>Yardım</to></translation>
+<translation><from>Here you can buy credits that will keep your Nabble application free of ads. Each credit represents a page view without advertisement. Visitors will see pages without ads while you still have credits available. Nabble will start to show ads when you have zero credits.</from><to>Burada reklamlarını Nabble Uygulama serbest tutar kredi satın alabilirsiniz. Her kredi reklam olmadan bir sayfa görünümü temsil eder. Hala mevcut kredilerin varken Ziyaretçiler reklamlar olmadan sayfalarında göreceksiniz. Nabble sıfır kredisi varken reklamları göstermeye başlayacaktır.</to></translation>
+<translation><from>Here you can hide the current mailing list archive information from the users. This option can help you replace your mailing list server with Nabble's email subscription feature.</from><to>Burada kullanıcıların mevcut posta listesi arşiv bilgileri gizleyebilirsiniz. Bu seçenek Nabble e-posta aboneliği özelliği ile posta listenizdeki sunucu yerine yardımcı olabilir.</to></translation>
+<translation><from>Hide email address (e.g., user@host.com to <n.lt/>hidden email<n.gt/> link)</from><to>E-posta adresini gizle (örnek, user@host.com vira um link <n.lt/>E-posta adresi gizlendi<n.gt/>)</to></translation>
+<translation><from>Hide email</from><to>E-posta adresini gizle</to></translation>
+<translation><from>Hide mailing list archive information from users</from><to>Kullanıcılardan gelen e-posta listesi arşiv bilgilerini gizle</to></translation>
+<translation><from>Highest</from><to>en yüksek</to></translation>
+<translation><from>High</from><to>Yüksek</to></translation>
+<translation><from>Hi <t.name/>,</from><to>Merhaba <n.name/>,</to></translation>
+<translation><from>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address. After registration, you will own this user account.</from><to>Eğer bu e-posta adresi size ait ise, gerekli <n.register_link.>kayıt</n.register_link.> Bu aynı adresi kullanarak. Kayıttan sonra, bu kullanıcı kendi hesabın olacaktır.</to></translation>
+<translation><from>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</from><to>Eğer Nabble gelen e-postaları alamıyorsanız, kontrol lütfen spam klasörü.</to></translation>
+<translation><from>If you are posting a question, please try search first. Your question may have already been answered.</from><to>Size bir soru gönderme yapıyorsanız, ilk arama deneyin. Sizin sorunuzu zaten yanıtlanmış olabilir.</to></translation>
+<translation><from>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</from><to>Eğer hala üye değilseniz,<n.register_link.><b>Kayıt olun</b></n.register_link.>.</to></translation>
+<translation><from>If you ban this user, he/she won't be able to do anything in <t.location/>.</from><to>Eğer bu kullanıcıyı yasaklarsanız hiç bir şey yapamayacaktır. <n.location/>.</to></translation>
+<translation><from>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</from><to>Bu e-posta talep veya bunu neden aldığınızı hiçbir fikrim yok olmadıysa, lütfen göz ardı edin. Başka birinden bir hata olmuş olabilir.</to></translation>
+<translation><from>If you don't want to register yet, just enter the email address from which you intend to post, and your personal address will be emailed to you.</from><to>Eğer henüz kayıt olmak istemiyorsanız, sadece göndermek için planladığınız e-posta adresini girin ve kişisel adres size gönderilecektir.</to></translation>
+<translation><from>If you have already removed the subscription, then you can proceed with the deletion.</from><to>Zaten abonelik kaldırdık, o zaman silme ile devam edebilirsiniz.</to></translation>
+<translation><from>If you know, please select the mailing list server application and its version.</from><to>Eğer biliyorsanız, posta listesi sunucu uygulaması ve sürümü seçin lütfen.</to></translation>
+<translation><from>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</from><to>Yanlışlıkla oturumu ise, lütfen <n.login_link.>Tekrar giriş yapın</n.login_link.> tekrar.</to></translation>
+<translation><from>If you reply to this email, your message will be added to the discussion below</from><to>Bu e-postayı yanıtlamak durumunda, mesajın altında tartışmaya eklenecektir</to></translation>
+<translation><from>If you unban this user, he/she will be able to post messages in <t.location/> again.</from><to>Bu kullanıcı yasağın kaldırılması durumunda, o / o iletileri göndermek mümkün olacak <n.location/> tekrar.</to></translation>
+<translation><from>If you want to subscribe to the mailing list instead, <n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</from><to>Eğer, bunun yerine posta listesine abone olmak istiyorsanız, <n.mailing_list_options_link.>Bu web sayfasını ziyaret edin</n.mailing_list_options_link.>.</to></translation>
+<translation><from>Ignore X-No-Archive header</from><to>X-No-Arşiv başlığını yoksay</to></translation>
+<translation><from>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</from><to>Okudum ve Kabul ediyorum,Nabblenin <n.terms_link.>Kullanım Şartlarını</n.terms_link.> Nabble'nin.</to></translation>
+<translation><from>Image was not uploaded: <t.image/> (please upload again or remove the tag)</from><to>Resim yüklenemedi: <n.image/> (lütfen, tekrar yükleyin yada etiketi kaldırın)</to></translation>
+<translation><from>I'm a subscriber, let me post now</from><to>Ben bir aboneyim,bırakın göndereyim</to></translation>
+<translation><from>Incorrect Login!</from><to>Hatalı Giriş!</to></translation>
+<translation><from>Individual emails</from><to>Bireysel e-postalar</to></translation>
+<translation><from>Inherit</from><to>Devral</to></translation>
+<translation><from>In Reply To</from><to>Cevap olarak</to></translation>
+<translation><from>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author/></from><to>Cevap olarak  <n.parent_link.>bu gönderi</n.parent_link.> By <n.author/></to></translation>
+<translation><from>Insert</from><to>Ekle</to></translation>
+<translation><from>Insert Image</from><to>Resim ekle</to></translation>
+<translation><from>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</from><to>Bunun yerine web arayüzü üzerinden yayınladığı, ayrıca aşağıdaki e-posta adresine e-posta göndererek yeni konular açabilirsiniz:</to></translation>
+<translation><from>in <t.location/></from><to>içinde <n.location/></to></translation>
+<translation><from>Invalid Code</from><to>Geçersiz Kod</to></translation>
+<translation><from>Invalid email address: <n.email/></from><to>Geçersiz E-posta Adresi: <n.email/></to></translation>
+<translation><from>Invalid number of days, must be integer.</from><to>Geçersiz gün sayısı, tamsayı olmalıdır.</to></translation>
+<translation><from>invisible user</from><to>görünmez kullanıcı</to></translation>
+<translation><from>invisible users</from><to>görünmez kullanıcılar</to></translation>
+<translation><from>Invite Subscribers</from><to>Abonelere Davetiye</to></translation>
+<translation><from>is:</from><to>dir:</to></translation>
+<translation><from>is not:</from><to>değildir:</to></translation>
+<translation><from>is within the last:</from><to>Sonda:</to></translation>
+<translation><from>Italic</from><to>İtalik</to></translation>
+<translation><from>item</from><to>öğre</to></translation>
+<translation><from>items</from><to>öğeler</to></translation>
+<translation><from>It will NOT be possible to restore deleted items later.</from><to>Bu silinen öğeleri daha sonra geri yüklemek mümkün değil.</to></translation>
+<translation><from>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</from><to>Sadece bu etiketleri içinde kodu (yukarıdaki siteler tarafından sağlanan) yapıştırın ve onu göndermeye hazır hale getirin.</to></translation>
+<translation><from>Last Post</from><to>Son Gönderi</to></translation>
+<translation><from>Learn more</from><to>Daha fazla öğren</to></translation>
+<translation><from>Leave a comment</from><to>Bir yorum bırak</to></translation>
+<translation><from>Link</from><to>Link</to></translation>
+<translation><from>Link to <t.location/></from><to>Linki <n.location/></to></translation>
+<translation><from>List</from><to>Liste</to></translation>
+<translation><from>List of Subcategories</from><to>Alt kategori listesi</to></translation>
+<translation><from>List Server</from><to>Liste Sunucusu</to></translation>
+<translation><from>List View</from><to>Liste önizleme</to></translation>
+<translation><from>Loading...</from><to>Yükleniyor...</to></translation>
+<translation><from>Location</from><to>Konum</to></translation>
+<translation><from>Locked</from><to>Kilitli</to></translation>
+<translation><from>Lock topic</from><to>Konuyu kilitle</to></translation>
+<translation><from>Login</from><to>Giriş</to></translation>
+<translation><from>Log is empty</from><to>Çerezler boş</to></translation>
+<translation><from>Log out</from><to>Çıkış</to></translation>
+<translation><from>Lowest</from><to>En düşük</to></translation>
+<translation><from>Low</from><to>Düşük</to></translation>
+<translation><from>Mailing List Address</from><to>E-posta Listesi Adresi</to></translation>
+<translation><from>Mailing list archive settings</from><to>E-posta listesi arşivi ayarları</to></translation>
+<translation><from>Mailing List Archive Settings</from><to>E-posta Listesi Arşivi Ayarları</to></translation>
+<translation><from>Mailing List Subscription Reminder</from><to>E-posta Listesi Aboneliği Hatırlatıcı</to></translation>
+<translation><from>Mailing List Website</from><to>Sitenin E-posta Listesi</to></translation>
+<translation><from>Main Page</from><to>Ana Sayfa</to></translation>
+<translation><from>Make sure you are using the same browser that you used to fill in the registration request.</from><to>Kayıt isteği doldurmak için aynı tarayıcıyı kullandığınızdan emin olun.</to></translation>
+<translation><from>Manage banned users</from><to>Yasaklı kullanıcıları düzenle</to></translation>
+<translation><from>Manage pinned topics</from><to>Sabit konuları düzenle</to></translation>
+<translation><from>Manage subscribers</from><to>Aboneleri düzenle</to></translation>
+<translation><from>Manage Subscribers</from><to>Aboneleri Düzenle</to></translation>
+<translation><from>Manage <t.items/></from><to>Düzenle <n.items/></to></translation>
+<translation><from>Manage users & groups</from><to>Kullanıcıları & gurupları düzenle</to></translation>
+<translation><from>Manage Users & Groups</from><to>Kullanıcıları & gurupları düzenle</to></translation>
+<translation><from>Mass advertising, misleading text, scams or fraud.</from><to>Kitlesel reklam, yanıltıcı metin, sahtekarlık veya dolandırıcılık.</to></translation>
+<translation><from>max. 80 characters</from><to>max. 80 karakter</to></translation>
+<translation><from>Message date</from><to>Mesaj tarihi</to></translation>
+<translation><from>message</from><to>mesaj</to></translation>
+<translation><from>Message</from><to>Mesaj</to></translation>
+<translation><from>Message is in HTML Format</from><to>Mesaj HTML formatında</to></translation>
+<translation><from>messages</from><to>mesajlar</to></translation>
+<translation><from>Messages posted here will be sent to this mailing list.</from><to>Burada yayınlanan mesajlar bu posta listesi gönderilecektir.</to></translation>
+<translation><from>Message subject contains</from><to>Mesaj konusunu içeriği</to></translation>
+<translation><from>Message text contains</from><to>Mesaj metni içeriği</to></translation>
+<translation><from>Mixed</from><to>Karışık</to></translation>
+<translation><from>Modified</from><to>Modifiye</to></translation>
+<translation><from>Archives</from><to>Arşivler</to></translation>
+<translation><from>More Categories</from><to>Daha Fazla Kategori</to></translation>
+<translation><from>More</from><to>Daha Fazla</to></translation>
+<translation><from>more help</from><to>daha fazla yardım</to></translation>
+<translation><from>more options</from><to>daha fazla ayarlar</to></translation>
+<translation><from>Move post</from><to>Gönderiyi taşı</to></translation>
+<translation><from>Move Post</from><to>Gönderiyi Taşı</to></translation>
+<translation><from>Move topic</from><to>Konuyu taşı</to></translation>
+<translation><from>My Nabble Applications</from><to>Nabble Uygulamalarım</to></translation>
+<translation><from>My Pending Posts</from><to>Bekleyen Mesajlarım</to></translation>
+<translation><from>My posts</from><to>Gönderilerim</to></translation>
+<translation><from>Nabble Support</from><to>Nabble Destek</to></translation>
+<translation><from>Name</from><to>İsim</to></translation>
+<translation><from>New Post</from><to>Yeni Mesaj</to></translation>
+<translation><from>news</from><to>haberler</to></translation>
+<translation><from>News</from><to>Haberler</to></translation>
+<translation><from>New Topic</from><to>Yeni Konu</to></translation>
+<translation><from>New topics only</from><to>Sadece yeni konular</to></translation>
+<translation><from><n.important.>CAUTION</n.important.>: Everything under <t.location/> will be deleted forever!</from><to><n.important.>DİKKAT</n.important.>: altındaki her şey <n.location/> sonsuza kadar silinecek!</to></translation>
+<translation><from>No banned users.</from><to>Yasaksız kullanıcılar.</to></translation>
+<translation><from>No Filter</from><to>Filtre Yok</to></translation>
+<translation><from>none of the words:</from><to>kelimelerin hiçbiri:</to></translation>
+<translation><from>No registered user found with this email.</from><to>NBu e-posta ile kayıtlı kullanıcı yok.</to></translation>
+<translation><from>No replies</from><to>Cevaplar yok</to></translation>
+<translation><from>Normal</from><to>Normal</to></translation>
+<translation><from>No sub-forums</from><to>Alt forumlar yok</to></translation>
+<translation><from>Note that this address is unique to you and only accepts emails sent from <t.address/>. The purpose of this design is to help prevent spam.</from><to>Bu adres size özel olduğunu unutmayın ve sadece gönderilen e-postaları kabul <n.address/>. Bu tasarımın amacı spamları önlemek için yardımcı olmaktır.</to></translation>
+<translation><from><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</from><to><n.register_link.>Şimdi kayıt olun</n.register_link.> Eğer profilinizi düzenlemek istiyorsanız, e-posta yoluyla mesaj aldığınızda, yıldızlı öğeleri kontrol edin ya da küresel profile erişim sağlayın.</to></translation>
+<translation><from>one email per input box</from><to>Gelen kutusunda bir e-postanız var</to></translation>
+<translation><from>Online Users</from><to>Çevirimiçi Kullanıcılar</to></translation>
+<translation><from>Only authorized users can proceed in this area.</from><to>Sadece yetkili kullanıcılar bu alanda devam edebilir.</to></translation>
+<translation><from>Open this post in classic view</from><to>Klasik görünümünde bu yazıyı aç</to></translation>
+<translation><from>Open this post in list view</from><to>Bu gönderiyi liste görünümünde aç</to></translation>
+<translation><from>Open this post in threaded view</from><to>Bu gönderiyi diğer görünümünde aç</to></translation>
+<translation><from>Options</from><to>Seçenekler</to></translation>
+<translation><from>or</from><to>veya</to></translation>
+<translation><from>Or you can ignore this email if it is better to keep this user away from that area.</from><to>O bölgeden uzak tutmak için bu kullanım daha iyi olup olmadığını ya da bu e-postayı yok sayabilirsiniz.</to></translation>
+<translation><from>Other Settings</from><to>Diğer ayarlar</to></translation>
+<translation><from>Page that groups sub-applications and discussions.</from><to>Sayfa gruplara alt-uygulamalar ve tartışmalar.</to></translation>
+<translation><from>Page <t.number/></from><to>Sayfa <n.number/></to></translation>
+<translation><from>Page with a simple list of sub-applications and discussions.</from><to>Alt-uygulamalar ve tartışmalar basit bir liste ile Sayfa.</to></translation>
+<translation><from>Page with full messages and related comments.</from><to>Tam mesajları ve ilgili yorumlarla Sayfa.</to></translation>
+<translation><from>Page with headlines and posts.</from><to>Başlıklar ve mesajlar Sayfa.</to></translation>
+<translation><from>Page with topics and discussions.</from><to>Konular ve tartışmalar ile Sayfa.</to></translation>
+<translation><from>Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.</from><to>Alt-uygulamalar tarafından gruplandırılmış konularda Sayfa. Herhangi bir alt-apps varsa, konu normal bir liste görüntülenir.</to></translation>
+<translation><from>Password</from><to>Şifre</to></translation>
+<translation><from>Password Sent</from><to>Şifre Gönderildi</to></translation>
+<translation><from>Pedophilia, violence and other abuses.</from><to>Pedofili, şiddet ve diğer kötüye.</to></translation>
+<translation><from>People</from><to>Kişiler</to></translation>
+<translation><from>People in <t.location/></from><to>Kişiler içinde <n.location/></to></translation>
+<translation><from>Permalink</from><to>Sayfa Bağlantısı</to></translation>
+<translation><from>Photo and image gallery.</from><to>Fotoğraf ve resim galerisi.</to></translation>
+<translation><from>Pinned sub-forum</from><to>Sabitlenmiş alt forum</to></translation>
+<translation><from>Pin topic</from><to>Konuyu sabitle</to></translation>
+<translation><from>Please bookmark the link above or save this email so you can easily find your <t.app/> in the future.</from><to>Lütfen aşağıdaki linki yer imlerine ekleyin yada bu e-postayı kaydedin. Böylece gelecekte <n.app/> daha kolay bulabilirsiniz.</to></translation>
+<translation><from>Please check your inbox now and activate your account in order to have access to all features.</from><to>Tüm özelliklere erişim için artık gelen kutunuzu kontrol edin ve hesabınızı aktive edin.</to></translation>
+<translation><from>Please click on the confirmation link below to activate your account:</from><to>Hesabınızı etkinleştirmek için aşağıdaki onay linki tıklayınız:</to></translation>
+<translation><from>Please contact Nabble Support if you need help.</from><to>Yardıma ihtiyacınız olduğunda lütfen Nabble Destek ile iletişime geçiniz.</to></translation>
+<translation><from>Please contact the administrators if you need help.</from><to>Yardıma ihtiyacınız olduğunda lütfen Adminler ile iletişime geçiniz.</to></translation>
+<translation><from>Please enter a correct email address and try again.</from><to>Doğru bir e-posta adresinizi girin ve tekrar deneyin.</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Send Password". We will email your password to that email address.</from><to>Kayıt olmak için kullandığınız e-posta adresini girin ve "Şifreyi Gönder" e tıklayınız. Bu e-posta adresine şifrenizi göndereceğiz.</to></translation>
+<translation><from>Please follow the instructions in the email to complete the registration process.</from><to>Kayıt işlemini tamamlamak için lütfen e-postadaki talimatları takip edin.</to></translation>
+<translation><from>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</from><to>Lütfen takip ediniz <n.subscribe_instructions_link.>abonelik talimatları</n.subscribe_instructions_link.> bu arşiv için.</to></translation>
+<translation><from>Please provide a valid permalink.</from><to>Geçerli bir permalink verin lütfen.</to></translation>
+<translation><from>Please re-enter your email/password and click Login.</from><to>E-posta / şifrenizi tekrar girin ve Girişe tıklayınız.</to></translation>
+<translation><from>Please respect mailing list etiquette</from><to>E-posta listesine saygı gösterelim lütfen</to></translation>
+<translation><from>Polls from Polldaddy.com (flash polls only)</from><to>Polldaddy.com (flaş anketler) den Anketler</to></translation>
+<translation><from>Post by email</from><to>E-posta ile gönder</to></translation>
+<translation><from>Post by Email</from><to>E-posta ile Gönder</to></translation>
+<translation><from>Post Count</from><to>Ziyareti</to></translation>
+<translation><from>Posted by <t.author/></from><to>Gönderdi <n.author/></to></translation>
+<translation><from>post</from><to>gönder</to></translation>
+<translation><from>Post Message</from><to>Mesaj Gönder</to></translation>
+<translation><from>Post New Message</from><to>Yeni Mesaj Gönder</to></translation>
+<translation><from>Post new message in <t.location/></from><to>İçinde yeni mesaj gönder <n.location/></to></translation>
+<translation><from>posts</from><to>gönderiler</to></translation>
+<translation><from>Posts</from><to>Gönderiler</to></translation>
+<translation><from>Posts in <t.location/></from><to>İçindeki gönderiler <n.location/></to></translation>
+<translation><from>Preview Message</from><to>Mesaj Önizleme</to></translation>
+<translation><from>Prices are in US Dollars. This checkout process uses <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, which enables Amazon customers to use the payment methods stored in their Amazon.com account to pay for goods and services on websites and applications accepting Amazon Payments.</from><to>Fiyatlar ABD Doları vardır. Bu ödeme işlemi kullanır <n.amazon_payments_link.>AmazonPayments</n.amazon_payments_link.>, Hangi Amazon müşterileri Amazon Payments kabul web siteleri ve uygulamalar mal ve hizmetler için ödeme kendi Amazon.com hesabına saklanan ödeme yöntemleri kullanmanızı sağlar.</to></translation>
+<translation><from>Print post</from><to>Gönderiyi yazdır</to></translation>
+<translation><from>Priority</from><to>Kıdem</to></translation>
+<translation><from>(private)</from><to>(özel)</to></translation>
+<translation><from>Profile of <t.author/></from><to>Profili <n.author/></to></translation>
+<translation><from>Quote</from><to>Alıntı</to></translation>
+<translation><from>Quote the original message</from><to>Orijinal mesajı alıntı yap</to></translation>
+<translation><from>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</from><to>Size cevap ve sadece ilgili bölümlerine terimleyebilirisniz ne Alıntı. Bu e-posta ile mesaj okuyacak olanlar için içerik sağlar.</to></translation>
+<translation><from>Raw mail</from><to>Original e-posta</to></translation>
+<translation><from>Raw text</from><to>Orjinal metin</to></translation>
+<translation><from>read more</from><to>devamını oku</to></translation>
+<translation><from>Read more</from><to>Devamını oku</to></translation>
+<translation><from>Read-only list with all users with an email under <t.location/>.</from><to>Altında bir e-posta ile tüm kullanıcıların sahip Salt okunur listesi <n.location/>.</to></translation>
+<translation><from>Receive direct replies only.</from><to>Sadece doğrudan cevapları al.</to></translation>
+<translation><from>Receive every message posted in <t.location/>.</from><to>Yollanan her mesajı alma <n.location/>.</to></translation>
+<translation><from>Receive every reply under this topic.</from><to>Bu konu altındaki tüm cevapları al.</to></translation>
+<translation><from>Receive new topics only.</from><to>Sadece yeni konuları al.</to></translation>
+<translation><from>Refresh</from><to>Yenile</to></translation>
+<translation><from>Registered</from><to>Kayıt Yapıldı</to></translation>
+<translation><from>Registered Users</from><to>Kayıtlı Kullanıcılar</to></translation>
+<translation><from>Register</from><to>Kayıt Yap</to></translation>
+<translation><from>Registering...</from><to>Kayıt Yapılıyor...</to></translation>
+<translation><from>Register Now</from><to>Şimdi Kayıt Yap</to></translation>
+<translation><from>Register to <t.app/></from><to>Kayıt yap <n.app/></to></translation>
+<translation><from>Registration Confirmed</from><to>Kaydı Onayla</to></translation>
+<translation><from>Registration Failed</from><to>Kayıt Yapılamadı</to></translation>
+<translation><from>Related Help Article</from><to>İlgili Yardım al</to></translation>
+<translation><from>Remember that the banning action isn't efficient because the user can always come back with a different account.</from><to>Kullanıcı her zaman farklı bir hesap ile geri gelebilir, çünkü yasaklayan eylem etkili olmadığını unutmayın.</to></translation>
+<translation><from>Remove Ads</from><to>Reklamları Kaldır</to></translation>
+<translation><from>remove</from><to>kaldır</to></translation>
+<translation><from>Remove Settings</from><to>Kaldırma Ayarları</to></translation>
+<translation><from>Remove Subscription</from><to>Aboneliği Kaldır</to></translation>
+<translation><from>Remove your account and all your posts from <t.subject/>.</from><to>Hesabını ve tüm gönderilerini kaldır <n.subject/>.</to></translation>
+<translation><from>Remove Your Account</from><to>Hesabını Kaldır</to></translation>
+<translation><from>replies</from><to>cevaplar</to></translation><!-- noun / plural -->
+<translation><from>Replies</from><to>Cevaplar</to></translation><!-- noun / plural -->
+<translation><from>Reply</from><to>Cevap</to></translation><!-- verb / infinitive -->
+<translation><from>reply</from><to>cevap</to></translation><!-- noun / singular -->
+<translation><from>Reply to author</from><to>Yazara cevap ver</to></translation>
+<translation><from>Report Content as Inappropriate</from><to>Uygunsuz içeiği bildirin</to></translation>
+<translation><from>Report Now</from><to>Şimdi raporla</to></translation>
+<translation><from>required</from><to>gerekli</to></translation>
+<translation><from>Return to <t.location/></from><to><n.location/> Forumuna Geri Dön</to></translation>
+<translation><from>Save Changes</from><to>Değişiklikleri Kaydet</to></translation>
+<translation><from>Save Settings</from><to>Ayarları Kaydet</to></translation>
+<translation><from>Save Subscription</from><to>Aboneliği Kaydet</to></translation>
+<translation><from>Search</from><to>Arama</to></translation>
+<translation><from>Select below the actions you want to take:</from><to>İstediğiniz faaliyetleri aşağıdan seçin:</to></translation>
+<translation><from>Select the category that most closely reflects your concern about the contents of this page.</from><to>En çok bu sayfanın içeriği hakkında endişeyi yansıtıyor kategori seçin.</to></translation>
+<translation><from>Send email to me</from><to>Bana e-posta gönder</to></translation>
+<translation><from>Send Email to <t.author/></from><to>e-posta gönder <n.author/></to></translation>
+<translation><from>Send Password</from><to>Şifreyi Gönder</to></translation>
+<translation><from>Send Request</from><to>İstek Gönder</to></translation>
+<translation><from>Send To:</from><to>Gönder:</to></translation>
+<translation><from>Sexual content</from><to>Müstehcen içerik</to></translation>
+<translation><from>Show</from><to>Görüntüle</to></translation><!-- verb -->
+<translation><from>Show Nabble notice</from><to>Nabble haberini görüntüle</to></translation>
+<translation><from>Sincerely,</from><to>İçtenlikle,</to></translation>
+<translation><from>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</from><to>Bu uygulama bir posta listesi arşivi olduğundan, sil butonuna basmadan önce aşağıdaki e-posta adresini aboneliğini kaldırın lütfen.</to></translation>
+<translation><from>Since you are not a registered user, we must check that you are a human.</from><to>Kayıtlı bir kullanıcı olmadığınızdan bot olmadığınız kontrol etmemiz gerekiyor.</to></translation>
+<translation><from>Some of your posts have been deleted from <t.location/> and we are sending you copies so that you have a chance to save them.</from><to>Mesajlarınızdan bazıları silinmiş <n.location/> biz size o silinen mesajlarınızın kopyasını gönderdik.</to></translation>
+<translation><from>Sorry, but only members can post messages under <t.app/>.</from><to>Üzgünüz, sadece üyeler ileti gönderebilir <n.app/>.</to></translation>
+<translation><from>Sorry, but the administrators have banned you.</from><to>Üzgünüz, adminler sizi yasakladı.</to></translation>
+<translation><from>Sorry, but this email is not authorized to view messages under <t.location/>.</from><to>Üzgünüz, ancak bu e-posta altındaki mesajları görmek için yetkiniz yok <n.location/>.</to></translation>
+<translation><from>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</from><to>Üzgünüz, burada yeni konu açamazsınız.<br/>Ama konulara cevap yazabilirsiniz.</to></translation>
+<translation><from>Sort by date</from><to>Tarihe göre sırala</to></translation>
+<translation><from>Sort by relevance</from><to>Alakaya göre sırala</to></translation>
+<translation><from>Sorted by date</from><to>Tarihe göre sıralandı</to></translation>
+<translation><from>Sorted by relevance</from><to>Alakaya göre sıralandı</to></translation>
+<translation><from>[Spam Detector] Invalid message with too many '<n.text/>' words.</from><to>[Spam Dedektörü] Mesajda geçersiz kelimeler bulunuyor '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message cannot contain '<n.text/>'.</from><to>[Spam Dedektörü] Mesaj içeremezr '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Message contains common spam words.</from><to>[Spam Dedektörü] Mesaj spam kelimeler içeremez.</to></translation>
+<translation><from>[Spam Detector] Subject cannot contain '<n.text/>'.</from><to>[Spam Dedektörü] Ders içeremez '<n.text/>'.</to></translation>
+<translation><from>[Spam Detector] Subject contains common spam words.</from><to>[Spam Dedektörü] Ders spam kelimeler içeremez.</to></translation>
+<translation><from>Spam</from><to>Spam</to></translation>
+<translation><from>Stars in <t.location/></from><to>içindeki Yıldızlar <n.location/></to></translation>
+<translation><from>Structure</from><to>Yapı</to></translation>
+<translation><from>Subcategories</from><to>Alt Kategoriler</to></translation>
+<translation><from>Subcategories under <t.location/></from><to>altındaki Alt Kategoriler <n.location/></to></translation>
+<translation><from>Subcategory</from><to>Alt Kategori</to></translation>
+<translation><from>Sub-Forum</from><to>Alt Forum</to></translation>
+<translation><from>Sub-Forums</from><to>Alt Forumlar</to></translation>
+<translation><from>Sub-Forums & Topics</from><to>Alt Forumlar & Konular</to></translation>
+<translation><from>Subject</from><to>Başlık</to></translation>
+<translation><from>Subscribe</from><to>Abonelik</to></translation>
+<translation><from>subscriber</from><to>abone</to></translation>
+<translation><from>subscribers</from><to>aboneler</to></translation>
+<translation><from>Subscribe to <t.location/></from><to>Abone  <n.location/></to></translation>
+<translation><from>Subscribe via email</from><to>E-posta yoluyla abone ol</to></translation>
+<translation><from>Subscription Confirmation</from><to>Abonelik Onayı</to></translation>
+<translation><from>Subscription Confirmed</from><to>Abonelik Onaylandı</to></translation>
+<translation><from>Subscription Format</from><to>Abonelik Biçimi</to></translation>
+<translation><from>Subscription Removed</from><to>Abonelik kaldırıldı</to></translation>
+<translation><from>Subscription Results</from><to>Abonelik Sonuçları</to></translation>
+<translation><from>Subscription Type</from><to>Abonelik Türü</to></translation>
+<translation><from>Success: a confirmation email has been sent to you.</from><to>Başarılı: bir onay e-postası size gönderildi.</to></translation>
+<translation><from>Success</from><to>başarılı</to></translation>
+<translation><from>Take Action</from><to>Harekete Geçin</to></translation>
+<translation><from><t.app/> Registration</from><to>Kayıt <n.app/></to></translation>
+<translation><from><t.author/> has been successfully banned.</from><to><n.author/> başarılı bir şekilde yasaklandı.</to></translation>
+<translation><from><t.author/> has been successfully unbanned.</from><to><n.author/> başarılı bir şekilde yasak kaldırıldı.</to></translation>
+<translation><from>Tell me more & show examples</from><to>Daha fazla & örnekleri göster</to></translation>
+<translation><from>Thank you for registering with <t.app/>!</from><to>ile kayıt olduğunuz için teşekkürler <n.app/>!</to></translation>
+<translation><from>Thank You</from><to>Teşekkürler</to></translation>
+<translation><from>The author has deleted this message.</from><to>Yazar bu mesajı sildi.</to></translation>
+<translation><from>The code in the URL is not valid.</from><to>URL kodu geçerli değil.</to></translation>
+<translation><from>the exact phrase:</from><to>tam sözcük:</to></translation>
+<translation><from>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</from><to>Posta listenizdeki yazılan kabul etmeden önce abonelik gerektirebilir. Nabble'a kayıtlı olma otomatik olarak bu e-posta listesine abone DEĞIL unutmayın. Eğer henüz üye olmadıysanız, şimdi yapın. Eğer emin değilseniz ya da hatırlamıyorsanız hiç bir sakınca yoktur, çünkü sadece tekrar abone.</to></translation>
+<translation><from>The Nabble team</from><to>Nabble ekibi</to></translation>
+<translation><from>The name of the group is not valid.</from><to>Gurup adı geçerli değil.</to></translation>
+<translation><from>The new parent cannot be the post itself.</from><to>Kendine mesaj gönderemez.</to></translation>
+<translation><from>The password fields don't match.</from><to>Şifre alanları uyuşmuyor.</to></translation>
+<translation><from>There is an unregistered user account associated with the email address <t.email/>.</from><to>E-posta adresi ile ilişkili bir kayıtsız kullanıcı hesabı var <n.email/>.</to></translation>
+<translation><from>The subject is required.</from><to>Ders gereklidir.</to></translation>
+<translation><from>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</from><to>Kullanıcı, onları kurtarmak için bir şansı olabilir, böylece e-posta bütün silinen mesajların bir kopyasını alacaksınız.</to></translation>
+<translation><from>The validation code did not match the picture.</from><to>Doğrulama kodu resmi eşleşmedi.</to></translation>
+<translation><from>This branch is too big and some posts were omitted. Use the other views to read all posts.</from><to>Bu dal çok büyüktür ve bazı mesajlar ihmal edildi. Bütün mesajları okumak için diğer görünümleri kullanın.</to></translation>
+<translation><from>This email is already subscribed.</from><to>Bu e-posta zaten abone.</to></translation>
+<translation><from>This forum is an archive for the mailing list</from><to>Bu forum e-posta listesi için arşivdir</to></translation>
+<translation><from>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</from><to>Bu forum e-posta listesi için yazılan iletecek bir arşiv / kapısıdır <b><n.mailing_list_address/></b>.</to></translation>
+<translation><from>This includes subcategories, posts, images, files and everything else.</from><to>Bu alt kategori, mesaj, görüntü, dosya ve her şeyi içerir.</to></translation>
+<translation><from>This is a mailing list archive</from><to>Bu bir posta listesi arşividir</to></translation>
+<translation><from>This is an automatic email sent by Nabble to confirm the creation of your new <t.app/>. If you didn't create the <t.app/> mentioned above, please contact us through the Nabble Support forum.</from><to>Nabble tarafından otomatik olarak gönderilen bu e-posta yeni oluşturduğunuz <n.app/> onaylamak içindir.Eğer siz <n.app/> oluşturmadıysanız yukarıda belirtildiği gibi, lütfen Nabble Destek forumu ile bizimle iletişime geçiniz.</to></translation>
+<translation><from>This list accepts only plain-text emails</from><to>Bu liste sadece düz metin e-postaları kabul eder</to></translation>
+<translation><from>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</from><to>Bu listede kayıtlı, kayıt dışı ve yasaklı kullanıcıları gösterir. Anonim kullanıcılar onlar bir e-posta olmadığı için yer almayan ve bu nedenle bir grubun parçası olamaz.</to></translation>
+<translation><from>This message will be sent from <b><t.from/></b> to the <b><t.to/></b> mailing list.</from><to>den bu mesaj gönderilecektir <b><n.from/></b> e-posta listesi <b><n.to/></b>.</to></translation>
+<translation><from>This post has NOT been accepted by the mailing list yet.</from><to><b>Mensagem Pendente</b>: Bu gönderi henüz e-posta listesi tarafından kabul edilmedi.</to></translation>
+<translation><from>This post was updated on <t.date/>.</from><to>Bu konu güncellendi de <n.date/>.</to></translation>
+<translation><from>This topic has been locked.</from><to>Bu konu kilitlendi.</to></translation>
+<translation><from>This topic has been pinned.</from><to>Bu konu sabitlendi.</to></translation>
+<translation><from>This topic has been pinned in <t.location/>.</from><to>Bu konu sabitlendi de <n.location/>.</to></translation>
+<translation><from>This topic has been unlocked.</from><to>Bu konunun kilidi kaldırıldı.</to></translation>
+<translation><from>This topic has been unpinned.</from><to>Bu konu sabitten çıkarıldı.</to></translation>
+<translation><from>This topic has unread posts</from><to>Bu konu okunmamış gönderiler içeriyor</to></translation>
+<translation><from>This topic is assigned to you at priority <t.priority/></from><to>Bu konu öncelikli olarak size atanır <n.priority/></to></translation>
+<translation><from>This user doesn't have permission to view this application (add him/her to a group that allows this and try again)</from><to>Bu kullanıcının (bu olanak sağlayan bir gruba / onu ekleyin ve yeniden deneyin) bu uygulama görüntülemek için izni yok</to></translation>
+<translation><from>This user name is already in use.</from><to>Bu kullanıcı adı zaten kullanımda.</to></translation>
+<translation><from>Threaded</from><to>Çizikli</to></translation>
+<translation><from>Threaded View</from><to>Çizikli Görünüm</to></translation>
+<translation><from>Time</from><to>Zaman</to></translation>
+<translation><from>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</from><to>İPUCU: arşivi posta listesine abone ve hala çalışmıyorsa, size X-No-Arşiv başlık görmezden deneyebilirsiniz.</to></translation>
+<translation><from>Tips</from><to>İpuçları</to></translation>
+<translation><from><t.name/> requested authorization to join the <t.location/>:</from><to><n.name/> katılmak için istenen izni <n.location/>:</to></translation>
+<translation><from><t.number/> Credits</from><to><n.number/> Krediler</to></translation>
+<translation><from><t.number/> page views without ads.</from><to><n.number/> reklamsız sayfa görünümleri.</to></translation>
+<translation><from>To accept this request, you should add this user to at least one group that has access to this area:</from><to>Bu isteği kabul etmek, bu alana erişim sahip en az bir gruba bu kullanıcı eklemek gerekir:</to></translation>
+<translation><from>To add this <t.app/> to your website, copy and paste the following code on your html page:</from><to>Bu eklemek için <n.app/> Aşağıdaki kodu kopyalayıp websitenize yapıştırın:</to></translation>
+<translation><from>To confirm your subscription, click on the link below:</from><to>Aboneliğinizi onaylamak için aşağıdaki linki tıklayınız:</to></translation>
+<translation><from>topic</from><to>konu</to></translation>
+<translation><from>Topics and replies</from><to>Konular ve cevaplar</to></translation>
+<translation><from>topics</from><to>konular</to></translation>
+<translation><from>Topics</from><to>Konular</to></translation>
+<translation><from>Topics only</from><to>Sadece konular</to></translation>
+<translation><from>Topics View</from><to>Konuları Gör</to></translation>
+<translation><from>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</from><to>Spam önlemek için, e-posta ile gönderme kullanılacak e-posta adresi her kullanıcı için.</to></translation>
+<translation><from>To remove a group, empty the text area below and save the changes.</from><to>Bir grubu kaldırmak için, aşağıdaki yazılı alanı boşaltmak ve değişiklikleri kaydetmek gerekir.</to></translation>
+<translation><from>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</from><to>Eğer göndermek için kullanması gereken e-posta adresini görmek için, lütfen <n.login_link.>giriş yapın login</n.login_link.> veya <n.register_link.>kayıt olun</n.register_link.>.</to></translation>
+<translation><from>To start a new topic under <t.location/>, email <t.p2/></from><to>Yeni bir konu başlatmak için <n.location/>, e-posta <n.p2/></to></translation>
+<translation><from>Total</from><to>Toplam</to></translation>
+<translation><from>To unsubscribe from <t.location/></from><to>Aboneliğinden çıkmak <n.location/></to></translation>
+<translation><from><t.owner_name/> is inviting you to subscribe to <t.location/>:</from><to><n.owner_name/> aboneliğine davetiye <n.location/>:</to></translation>
+<translation><from>Turn off highlighting</from><to>Vurgulayarak kapatın</to></translation>
+<translation><from><t.username/> created a new subcategory</from><to><n.username/>Yeni bir alt kategori oluşturuldu</to></translation>
+<translation><from>Unable to Post</from><to>Mesaj Açılamıyor</to></translation>
+<translation><from>Unassigned</from><to>atanmamış</to></translation>
+<translation><from>Unauthorized</from><to>Yetkisiz</to></translation>
+<translation><from>Unban this user</from><to>Bu kullanıcının yasağını kaldır</to></translation>
+<translation><from>Unban User</from><to>Yasaksız Kullanıcı</to></translation>
+<translation><from>Unknown or Other</from><to>Bilinmeyen yada Diğer</to></translation>
+<translation><from>Unlock topic</from><to>Konu kilidini kaldır</to></translation>
+<translation><from>Unpin topic</from><to>Konuyu sabitlikten kaldır</to></translation>
+<translation><from>Unregistered / Deactivated</from><to>Devre Dışı / Kayıtsız</to></translation>
+<translation><from>Unregistered</from><to>Kayıtsız</to></translation>
+<translation><from>Unregistered User</from><to>Kayıtsız kullanıcı</to></translation>
+<translation><from>Unsubscribe</from><to>Aboneliksiz</to></translation>
+<translation><from>Upload a file</from><to>Bir dosya yükle</to></translation>
+<translation><from>User email:</from><to>Kullanıcı E-posta adresi:</to></translation>
+<translation><from>user</from><to>kullanıcı</to></translation>
+<translation><from>User is online</from><to>Kullanıcı çevirim içi</to></translation>
+<translation><from>User Name</from><to>Kullanıcı adı</to></translation>
+<translation><from>User requested authorization to join <t.location/></from><to>Kullanıcı yetkilendirme katılmak istedi <n.location/></to></translation>
+<translation><from>users</from><to>kullanıcılar</to></translation>
+<translation><from>Users</from><to>Kullanıcılar</to></translation>
+<translation><from>Users & Groups</from><to>Kullanıcılar & Guruplar</to></translation>
+<translation><from>Users that completed the registration process</from><to>Kullanıcıların kayıt işlemleri tamamlandı</to></translation>
+<translation><from>Use tags like <t.example1/> or <t.example2/> to create sub-headers.</from><to>Etiket kullan <n.example1/> veya <n.example2/> alt başlık oluştur.</to></translation>
+<translation><from>Use the options below to precisely specify your search criteria.</from><to>Tam olarak arama kriterleri belirtmek için aşağıdaki seçenekleri kullanın.</to></translation>
+<translation><from>Use <t.tag_names/> to embed widgets from other websites.</from><to>Kullan <n.tag_names/> diğer web siteleri için gömme widgetleri.</to></translation>
+<translation><from>View all messages under this sub-forum</from><to>Bu alt forumdaki tüm mesajları gör</to></translation>
+<translation><from>view</from><to>gör</to></translation>
+<translation><from>View mailing list website</from><to>Sitenin e-posta listesinin gör</to></translation>
+<translation><from>View message</from><to>Mesajı gör</to></translation>
+<translation><from>View more</from><to>Daha fazla gör</to></translation>
+<translation><from>views</from><to>görüntüleme</to></translation>
+<translation><from>Views</from><to>Görüntüleme</to></translation>
+<translation><from>Violent or repulsive content</from><to>Şiddet veya itici içerik</to></translation>
+<translation><from>Visit <t.app/> at:</from><to>Ziyaret et <n.app/> em:</to></translation>
+<translation><from>visit <t.url/></from><to>ziyaret et <n.url/></to></translation>
+<translation><from>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</from><to>ile biz bir sayfa hazırladık <n.unsubscription_instructions_link.>Bu arşiv çıkmak için bazı talimatlar</n.unsubscription_instructions_link.>.</to></translation>
+<translation><from>We will review your report soon.</from><to>Biz en kısa zamanda raporunuzu okuyacağız.</to></translation>
+<translation><from>Which groups allow members to be listed</from><to>Grup üyeleri listelenen izinleri</to></translation>
+<translation><from>Who can ban/unban users</from><to>Kimler kullanıcıları yasaklayabilir ve yasağı kaldırabilir</to></translation>
+<translation><from>Who can be assigned topics (in workgroups only)</from><to>Kim konuları atayabilir (sadece çalışma guruplarında)</to></translation>
+<translation><from>Who can change the date and time of messages</from><to>Kimler mesajların tarih ve zamanını değiştirebilir</to></translation>
+<translation><from>Who can create new topics under this application</from><to>Kimler bu uygulama altında yeni konu açabilir</to></translation>
+<translation><from>Who can create sub applications (e.g., sub-forums, subcategories, etc.)</from><to>Kimler alt uygulamalar oluşturabilir (örnek, alt forum, alt kategori, vb.)</to></translation>
+<translation><from>Who can edit any content, both applications and posts. Note: Please only use this feature in extreme circumstances. Most users will not like having their posts edited by someone else.</from><to>Kim, her iki uygulama ve mesajları herhangi bir içerik düzenleyebilirsiniz. Not: Bu ​​özelliği sadece aşırı durumlarda kullanın. Çoğu kullanıcı, mesaj başka biri tarafından düzenleniyor olması gibi olmayacak.</to></translation>
+<translation><from>Who can edit applications (e.g., change name, description, etc.)</from><to>Kimler uygulamaları (örneğin, isim değiştirme, açıklama, vs) düzenleyebilir</to></translation>
+<translation><from>Who can lock/unlock topics in this application</from><to>Kimler bu uygulama altındaki konuları kilitleyip açabilir</to></translation>
+<translation><from>Who can manage subscribers of this application</from><to>Kimler bu uygulamanın abonelerini düzenleyebilir</to></translation>
+<translation><from>Who can move messages under other destinations (e.g., under other topics or sub-forums)</from><to>Kimler (Örneğin, başka konular ya da alt-forumlar altında) diğer yerler altında mesajları taşıyabilir</to></translation>
+<translation><from>Who can pin/unpin topics in this application</from><to>Kimler bu uygulamadaki konuları sabitleyebilir</to></translation>
+<translation><from>Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). <b>Security Warning</b>: Allow this option only for users that you really trust.</from><to>Kimler kısıtlama (javascript kodu, &lt;object&gt; ve &lt;style&gt; etiketleri, vb dahil) herhangi bir içerik ekleyebilir. <b>Güvenlik Uyarısı</b>: İzin gerçekten sadece kullanıcılar için bu seçeneği güvendiğiniz.</to></translation>
+<translation><from>Who can reply to messages posted under this application</from><to>Kimler bu uygulama altındaki konulara cevap yazabilir</to></translation>
+<translation><from>Who can view this application and its contents</from><to>Kimler bu uygulama ve içeriğini görebilir</to></translation>
+<translation><from>With your subscription, updates will be sent directly to your email address and you can reply to them to participate in the discussion. Your subscription works the same as a mailing list.</from><to>Abonelik ile güncellemeleri e-posta adresinizi doğrudan gönderilecek ve tartışmaya katılmak için bunları yanıtlayabilirsiniz. Aboneliğiniz bir posta listesi aynı şekilde çalışır.</to></translation>
+<translation><from>Write Your First Headline</from><to>İlk Yazını Yaz</to></translation>
+<translation><from>Write Your First Post</from><to>İlk mesajını yaz</to></translation>
+<translation><from>Yes, delete <t.location/> forever</from><to>Evet, sil <n.location/> kalıcı olarak</to></translation>
+<translation><from>Yes, unsubscribe now</from><to>Evet, şimdi abonelikten çık</to></translation>
+<translation><from>You are already subscribed to <t.location/>.</from><to>Siz zaten abonesiniz <n.location/>.</to></translation>
+<translation><from>You are not subscribed to <t.location/>.</from><to>Siz abone değilsiniz <n.location/>.</to></translation>
+<translation><from>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location/>.</from><to>Ayrıca şunları da yapabilirsiniz <n.manage_banned_users_link.>Yasaklı kullanıcıları düzenlemeyi</n.manage_banned_users_link.> içinde <n.location/>.</to></translation>
+<translation><from>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</from><to>Ayrıca şunları da yapabilirsiniz <n.root_node.change_permissions_link.>izinlerini değiştirebilirsiniz</n.root_node.change_permissions_link.> Aşağıdaki grupların.</to></translation>
+<translation><from>You can also promote your <t.app/> by sending the link to your friends, embedding it onto your website or talking about it on other forums.</from><to>Ayrıca sizin <n.app/> linkini arkadaşlarınıza göndererek, websitenize gömerek, diğer forumlarda ve sitelerde paylaşarak daha ilgi çekici hale getirebilirsiniz.</to></translation>
+<translation><from>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</from><to>Yeni üst anonim kullanıcılara izin verilmez, çünkü o hedefe bu yazıyı taşıyamazsınız. </to></translation>
+<translation><from>You Cannot Post Here</from><to>Buraya gönderi yazamazsınız</to></translation>
+<translation><from>(you can reply by email)</from><to>(e-posta ile cevap yazabilirsiniz)</to></translation>
+<translation><from>You can't move the post to anywhere.</from><to>Gönderiyi her yere taşıyamazsınız.</to></translation>
+<translation><from>You can't post a message here, but you can post in other places.</from><to>Buraya mesaj yazamazsınız, ama diğer yerlere yazabilirsiniz.</to></translation>
+<translation><from>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</from><to>Deneyebilirsiniz <n.register_link.>Tekrar kayıt yapmayı</n.register_link.> yada iletişime geçebilirsiniz <n.support_link/>.</to></translation>
+<translation><from>You can use the form below to send a request to the administrators.</from><to>Yöneticilere istek göndermek için aşağıdaki formu kullanabilirsiniz.</to></translation>
+<translation><from>You have already been registered.</from><to>Zaten kayıtlısınız.</to></translation>
+<translation><from>You have been invited to subscribe to <t.location/>, which is available at:</from><to>Siz abone olmak için davet edildiniz <n.location/>, mevcut olanlar içinde:</to></translation>
+<translation><from>You have been registered to <t.subject/>.</from><to>Kayıt yaptınız <n.subject/>.</to></translation>
+<translation><from>You have been unsubscribed from <t.location/></from><to>aboneliğinden <n.location/> çıktınız</to></translation>
+<translation><from>You have made too many posts in a short time. Please try again later.</from><to>Kısa bir süre içinde çok mesaj yazdınız. Daha sonra tekrar deneyiniz.</to></translation>
+<translation><from>You logged out</from><to>Çıkış yaptınız</to></translation>
+<translation><from>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</from><to>Yapmanız gerekenler <n.mailing_list_options_link.>Bu mail listesine üye</n.mailing_list_options_link.> Mesajınızı kabul edilebilmesi için.</to></translation>
+<translation><from>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</from><to>Yapabilecekleriniz <n.page_node.unauthorized_link.>gönderemeye izin</n.page_node.unauthorized_link.> burada yada iletişime geçin<n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> Eğer sorularınız varsa.</to></translation>
+<translation><from>You must agree to the Terms of Use.</from><to>Kullanım şartlarınız kabul etmelisiniz.</to></translation>
+<translation><from>You must enter a valid email address for this mailing list.</from><to>Bu e-posta listesi için geçerli bir e-posta adresi girmelisiniz.</to></translation>
+<translation><from>You must enter a valid website URL for this mailing list.</from><to>Bu e-posta listesi için geçerli bir web site URL'si girmelisiniz.</to></translation>
+<translation><from>You must fill in all fields below.</from><to>Aşağıdaki tüm alanları doldurmalısınız.</to></translation>
+<translation><from>You must login to view this page.</from><to>Bu sayfayı görmek için giriş yapmalısınız.</to></translation>
+<translation><from>You must login to view <t.subject/>.</from><to>Görmek için giriş yapmalısınız <n.subject/>.</to></translation>
+<translation><from>You must login to your account.</from><to>Hesabınıza giriş yapmalısınız.</to></translation>
+<translation><from>You must provide a user name.</from><to>Bir kullanıcı adı seçmelisiniz.</to></translation>
+<translation><from>You're not a subscriber</from><to>Siz abone değilsiniz</to></translation>
+<translation><from>Your Name</from><to>Adınız</to></translation>
+<translation><from>Your password for <t.location/></from><to>için şifreniz <n.location/></to></translation>
+<translation><from>Your password has been sent. Please check your email now.</from><to>Şifreniz gönderildi,lütfen e-postanızı kontrol edin.</to></translation>
+<translation><from>Your password is <t.password/></from><to>Şifreniz<n.password/></to></translation>
+<translation><from>Your request has been successfully sent.</from><to>Talebiniz başarıyla iletildi.</to></translation>
+<translation><from>Your subscription has been successfully saved.</from><to>Aboneliğiniz başarıyla kaydedildi.</to></translation>
+<translation><from>Your subscription to <t.location/> has been removed. If this was a mistake, you can re-subscribe by following the link below:</from><to>Aboneliğiniz <n.location/> başarıyla kaldırıldı. eğer yanlışlıkla kaldırıldıysa aşağıdaki bağlantıyı izleyerek tekrar abone olunuz:</to></translation>
+<translation><from>Your subscription to <t.location/> has been successfully removed.</from><to>Aboneliğiniz <n.location/> başarıyla kaldırıldı.</to></translation>
+<translation><from>Your <t.app/> has been successfully created.</from><to><n.app/> sizin başarıyla kuruldu.</to></translation>
+<translation><from>You will need authorization to post new topics in <t.location/>, so in addition to registering you will have to be approved by the administrators.</from><to>Yeni konu göndermek için yetkiye ihtiyacınız var <n.location/>, böylece açtığınız yeni konular adminler tarafından onaylandıktan sonra yayınlanır .</to></translation>
+<translation><from>You will receive an email for each new message posted under this topic.</from><to>Bu konuya yazılan her cevap için size e-posta gönderilecektir.</to></translation>
+<translation><from>You will receive an email with a link to activate your account.</from><to>Hesabınızı etkinleştirmek için bir bağlantı içeren bir e-posta alacaksınız.</to></translation>
+
+# NEW
+<translation><from>Your subscription</from><to>Aboneliğiniz</to></translation>
+<translation><from>edit</from><to>Düzenle</to></translation>
+<translation><from>Remove ads</from><to>Reklamları Kaldır</to></translation>
+<translation><from>View profile of <t.author/></from><to>Profilini görüntüle <n.author/></to></translation>
+<translation><from>Description</from><to>Açıklama</to></translation>
+<translation><from>Videos from Youtube, Vimeo and LiveLeak.</from><to>Youtube,Vimeo ve LiveLeak videoları.</to></translation>
+<translation><from>Banned Users in <t.location/></from><to>Yasaklı Kullanıcılar <n.location/></to></translation>
+<translation><from>Subject Prefix</from><to>Konu Önüne Koyulan</to></translation>
+
+#NEW 2
+<translation><from>Change title and meta tags</from><to>Title ve meta etiketlerini değiştir</to></translation>
+<translation><from>Here you can customize the title and meta tags of the <t.location/> page.</from><to>Burada title ve meta etiketlerini organize edebilirsiniz <n.location/>sayfa.</to></translation>
+<translation><from>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</from><to>Meta etiketlerini optimize ederek arama motorlarında görülmesini sağlayın (örneğin google, yahoo!, etc.)</to></translation>
+<translation><from>Use custom values</from><to>Özel değerler kullanın</to></translation>
+<translation><from>Page Title</from><to>Sayfa Başlığı</to></translation>
+<translation><from>Enter a context-rich title that clearly identifies this page.</from><to>Bu sayfayı tanımlayan zengin bir başlık girin.</to></translation>
+<translation><from>The title should ideally be less than 70 characters in length.</from><to>İdeal başlık karakter sayısı 70'ten az olmalı.</to></translation>
+<translation><from>Meta Description</from><to>Meta Açıklaması</to></translation>
+<translation><from>Enter a brief and concise summary of your page's content.</from><to>Sayfanızın içeriğini kısa bir özet halinde girin.</to></translation>
+<translation><from>Limit your description to 155 characters or 170 characters at most.</from><to>Açıklama karakter sayısını 155 yada 170 karakter ile sınırlayın.</to></translation>
+<translation><from>Mailing List Archive</from><to>E-posta arşivi</to></translation>
+<translation><from>Filter: priority <t.priority/></from><to>Filtre: kıdem <n.priority/></to></translation>
+<translation><from>Filter: assignee <t.author/></from><to>Filtre: Vekil <n.author/></to></translation>
+<translation><from>Use Google Analytics</from><to>Google Analytics kullan</to></translation>
+<translation><from>Analytics Account ID:</from><to>Analytics Hesap Numarası:</to></translation>
+<translation><from>(e.g., UA-12345-0)</from><to>(örnek: UA-12345-0)</to></translation>
+<translation><from>Enter a valid analytics account ID.</from><to>Analytics hesap numarasını girin.</to></translation>
+<translation><from>Here you can use Google Analytics to measure the success of your app.</from><to>Burada istetistikleri ölçmek için Google Analytics'i kullanabilirsiniz.</to></translation>
+<translation><from>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</from><to>Lütfen analitik hesap numaranızı aşağıya girin ve web trafiği hakkında ziyaretler, ziyaretçiler ve diğer önemli istatistikleri izlemek mümkün olacak.</to></translation>
+
+<translation><from>Digest Email</from><to>E-posta Yayını</to></translation>
+<translation><from>on <t.date/></from><to>aktif <n.date/></to></translation>
+<translation><from>DO NOT REPLY TO THIS EMAIL</from><to>BU E-POSTAYA CEVAP YAZMAYIN</to></translation>
+<translation><from>Replies sent to this address are not read or processed.</from><to>Bu adrese gönderilen Cevaplar okunmaz veya işlenmez.</to></translation>
+<translation><from>If you want to respond to a post for which you received this email, please go to the website: <t.url/></from><to>Bu e-postayı aldığı bir mesaja yanıt vermek istiyorsanız, web sitesine gidin: <n.url/></to></translation>
+<translation><from>new post</from><to>Yeni gönderi</to></translation><!-- usage example: "1 new post"-->
+<translation><from>new posts</from><to>yeni gönderiler</to></translation><!-- usage example: "2 new posts"-->
+
+<translation><from>New registered user in <t.location/>!</from><to>Yeni kayıtlı kullanıcı <n.location/>!</to></translation>
+<translation><from>User profile</from><to>Kullanıcı profili</to></translation>
+<translation><from>New user:</from><to>Yeni kullanıcı:</to></translation>
+
+<translation><from><n.author/> wrote</from><to><n.author/> yazdı</to></translation>
+<translation><from>You still have <t.number/> day(s) left without advertising</from><to>Kaldı <n.number/> gün(ler) reklamlasız.</to></translation>
+<translation><from>(Only administrators can see this message)</from><to>(Bu mesajı sadece adminler görebilir)</to></translation>
+
+<translation><from>Some private items have been omitted because you don't have permission to view them.</from><to>Bunları görmeye izniniz yok.</to></translation>
+<translation><from>Please enter the email address you used to register and click on "Submit". We will email you a link to reset your password.</from><to>Lütfen kayıt yaparken kullandığınız e-posta adresinizi giriniz ve ''Gönder'' butonuna tıklayınız.Size şifrenizi sıfırlamak için bir e-posta göndereceğiz.</to></translation>
+<translation><from>Submit</from><to>Gönder</to></translation>
+<translation><from>Password Reset Sent</from><to>Şifre sıfırlama gönderildi</to></translation>
+<translation><from>We have sent you a link to reset your password. Please check your email now. If you don't receive the instructions in a few minutes, check your spam folder or try to resend the request.</from><to>Şifrenizi sıfırlamanız için size bir e-posta gönderdik. Lütfen e-postanızı kontrol ediniz. Eğer bir kaç dakika içinde e-posta almadıysanız, spam klasörüne bakınız yada tekrar şifre sıfırlama isteği gönderiniz.</to></translation>
+<translation><from>Reset your password / <t.location/></from><to>Şifreni sıfırla / <n.location/></to></translation>
+<translation><from>We received a request to reset your password in <t.location/>.</from><to>içinde biz şifrenizi sıfırlamak için bir istek aldık <n.location/>.</to></translation>
+<translation><from>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</from><to>Eğer şifrenizi sıfırlamak istiyorsanız, aşağıdaki linke tıklayın (yada linki kopyalayıp tarayıcınıza yapıştırın):</to></translation>
+<translation><from>If you don't want to reset your password, please ignore this message. Your password will not be reset.</from><to>Eğer şifrenizi sıfırlamak istemiyorsanız, lütfen bu mesajı görmezden gelin. Şifreniz sıfırlanmayacaktır.</to></translation>
+
+<translation><from>IMPORTANT: If you use this option, messages posted in the archive will NOT be sent to the mailing list.</from><to>ÖNEMLİ: Eğer bu seçeneği kullanırsanız arşiv içindeki mesajlar mail listesine gönderilmeyecektir.</to></translation>
+<translation><from>Message Preview</from><to>Mesaj Önizleme</to></translation>
+<translation><from>Your changes will not be sent to the mailing list.</from><to>değişiklikleriniz mail listesine gönderilmeyecek.</to></translation>
+<translation><from>Poll</from><to>Anket</to></translation>
+<translation><from>Add new poll</from><to>Yeni anket ekle</to></translation>
+<translation><from>Question:</from><to>Soru:</to></translation>
+<translation><from>Answers:</from><to>Cevaplar:</to></translation>
+<translation><from>Add new answer</from><to>Yeni cevap ekle</to></translation>
+<translation><from>1 vote</from><to>1 oy</to></translation>
+<translation><from><t.number/> votes</from><to><n.number/> oylar</to></translation>
+<translation><from>Total votes:</from><to>Toplam oylamalar:</to></translation>
+<translation><from>Vote</from><to>Oyla</to></translation>
+<translation><from>Your vote has been submitted.</from><to>Oyunuz kaydedildi.</to></translation>
+<translation><from>This poll is closed.</from><to>Bu anket kapandı.</to></translation>
+<translation><from>This poll ends on <t.date/>.</from><to>Bu anket <n.date/> tarihinde sonlanır</to></translation>
+<translation><from>This poll ended on <t.date/>.</from><to>Bu anket <n.date/>tarihinde sonlandı</to></translation>
+
+<translation><from>Results will be shown only after poll has ended.</from><to>Anket Sonlandıktan sonra sonuçlar gösterilecektir.</to></translation>
+
+<translation><from>You have to vote before you can see the results.</from><to>Oyunuzu kullandıktan sonra sonuçları görebilirsiniz.</to></translation>
+<translation><from>You cannot change your vote after voting.</from><to>Oyunuzu değiştiremezsiniz.</to></translation>
+
+
+<translation><from>Please select no more than <t.number/> options.</from><to>lütfen<n.number/> den fazla seçenek işaretlemeyin.</to></translation>
+<translation><from>Please select at least one option.</from><to>Lütfen en az bir seçenek belirtin.</to></translation>
+
+<translation><from>Remove Poll</from><to>Anketi kaldır</to></translation>
+<translation><from>Invalid poll parameters.</from><to>Geçersiz anket parametreleri.</to></translation>
+
+<translation><from>Delete this poll, including all votes?</from><to>Tüm oylamalar dahil bu anket silmek istiyor musunuz?</to></translation>
+<translation><from>Poll has been deleted.</from><to>Anket silindi.</to></translation>
+<translation><from>Who can create polls.</from><to>Kimler anket oluşturabilir.</to></translation>
+<translation><from>Allow vote changes</from><to>Oy değişikliğine izin ver</to></translation>
+
+
+<translation><from>Allow viewing results before vote</from><to>Sonuçları görebilmek için oyunuzu kullanın</to></translation>
+
+<translation><from>Login to vote</from><to>oylamak için giriş yapın</to></translation>
+<translation><from>This message has a poll</from><to>Bu gönderide bir anket var</to></translation>
+
+<translation><from>Current length: <t.number/>karakterler</from><to>mevcut boyutlar: <n.number/> caracteres</to></translation>
+<translation><from>Edit Post</from><to>Gönderiyi düzenle</to></translation>
+
+<translation><from>If you want to buy more credits, visit:</from><to>Eğer daha fazla kredi istiyorsanız ziyaret edin:</to></translation>
+<translation><from>only in this topic</from><to>sadece bu konu içinde</to></translation>
+<translation><from>everywhere</from><to>heryerde</to></translation>
+<translation><from>We can install this module for you, but the Nabble team must approve this request to prevent abuse and spam.</from><to>Bu modülü sizin için yükleyebiliriz, fakat nabble ekibinin spam tehlikesine karşı onaylaması gerekir.</to></translation>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/mobile.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,336 @@
+<namespace name="mobile"/>
+
+<macro name="html5" parameters="head,body">
+    <n.html5_impl>
+        <head>
+            <n.head/>
+        </head>
+        <body>
+            <n.html5_top_bar/>
+            <n.body/>
+            <n.html5_nabble_footer/>
+        </body>
+    </n.html5_impl>
+</macro>
+
+<macro name="html5_impl" parameters="head,body" requires="servlet">
+    <n.page_start/>
+    <n.nabble_html>
+        <do>
+            <n.put_in_head.head/>
+            <n.body/>
+            <n.load_call_later_script/>
+        </do>
+        <output>
+            <![CDATA[<!DOCTYPE html>]]>
+            <html>
+                <head>
+                    <![CDATA[<meta charset="utf-8">
+                    <meta name="viewport" content="width=device-width, initial-scale=1.0">]]>
+                    <n.html5_stylesheets/>
+                    <n.html5_javascript_libraries/>
+                    <n.html_head_content/>
+                </head>
+                <body>
+                    <div class="nabble macro_[n.page_template/]" id="nabble">
+                        <n.apply_filters.html_body_content/>
+                    </div>
+                    <n.bottom_scripts/>
+                    <n.as_html_comments.site_information/>
+                </body>
+            </html>
+        </output>
+    </n.nabble_html>
+</macro>
+
+<override_macro name="nabble_shared_scripts">
+    <n.overridden/>
+    <script>
+        (function(a){(jQuery.browser=jQuery.browser||{}).mobile=/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))})(navigator.userAgent||navigator.vendor||window.opera);
+
+            $(document).ready(function() {
+                if (jQuery.browser.mobile) {
+                    $('a').each(function(){
+                        var href = $(this).attr('href');
+                        if (href.indexOf('/template/NamlServlet.jtp?macro=new_topic&') >= 0) {
+                            $(this).attr('href',href.replace(/=new_topic&/, '=new_topic5&')).attr('target','top');
+                        } else if (href.indexOf('/template/NamlServlet.jtp?macro=reply&') >= 0) {
+                            $(this).attr('href',href.replace(/=reply&/, '=reply5&')).attr('target','top');;
+                        }
+                    });
+                }
+            });
+    </script>
+</override_macro>
+
+<macro name="html5_stylesheets">
+    <link rel="stylesheet" href="/assets/bootstrap/css/bootstrap.min.css"/>
+    <style>body {background:transparent}</style>
+    <link rel="stylesheet" href="/nabble.css?v=[n.css_version/]" type="text/css" />
+    <link rel="stylesheet" href="/template/NamlServlet.jtp?macro=site_style" type="text/css" />
+</macro>
+
+<macro name="html5_javascript_libraries">
+    <script src="/assets/jquery/jquery-1.9.1.min.js"></script>
+    <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
+    <script src="[n.html5_js_path/]" type="text/javascript"></script>
+</macro>
+
+<macro name="html5_top_bar">
+    <div class="top-bar">
+        <div class="breadcrumbs pull-left">
+            <n.breadcrumbs/>
+        </div>
+        <div class="pull-right">
+            <n.html5_user_header/>
+        </div>
+    </div>
+</macro>
+
+<macro name="html5_nabble_footer">
+    <n.nabble_footer/>
+</macro>
+
+<macro name="html5_user_header">
+    <span style="white-space:nowrap;" id="nabble-user-header"></span>
+    <script type="text/javascript">Nabble.userHeader();</script>
+</macro>
+
+<macro name="html5_js">
+    var NabbleDropdown = {};
+    <n.javascript_library/>
+    <n.html5_js_fix/>
+</macro>
+
+<macro name="html5_js_fix">
+    Nabble.userDropdown = function() {
+        var $t = $('#user-dd');
+        if ($t.size() == 0)
+            return;
+        $t.empty();
+        var elem = '<div class="dropdown user-menu">';
+        elem += '<a class="dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">'+Nabble.escapeHTML(Nabble.username)+' <img src="/images/more.png" width="10" height="10"/></a>';
+        elem += '<ul class="dropdown-menu light-bg-color" role="menu" aria-labelledby="dLabel" style="right:0;left:auto">';
+        elem += '<li><a tabindex="-1" href="/template/NamlServlet.jtp?macro=user_nodes&user='+Nabble.userId+'">'+t_my_posts+'</a></li>';
+        elem += '<li><a tabindex="-1" href="'+user_profile_path+'">'+t_account_settings+'</a></li>';
+        elem += '<li><a tabindex="-1" href="javascript: void Nabble.logout()">'+t_logout+'</a></li>';
+        elem += '</ul></div>';
+        $t.html(elem);
+    };
+</macro>
+
+<macro name="html5_js_path">
+    <n.encode_url.>
+        /template/NamlServlet.jtp?macro=html5_js&v=<n.javascript_version/>
+    </n.encode_url.>
+</macro>
+
+<macro name="new_post5" parameters="page_name,mailing_list_etiquette,bottom,focus" requires="servlet">
+    <n.mobile.>
+        <n.node_page.>
+            <n.handle_new_node_permission_error/>
+            <n.if.not.is_submitted_form>
+                <then>
+                    <n.subject_field.set_value value="[n.page_node.default_reply_subject/]" />
+                    <n.alert_field.set_value value="[n.page_node.alert_default_value/]" />
+                    <n.init_new_post_custom_fields/>
+                </then>
+                <else>
+                    <n.catch_exception. id="save-block">
+                        <n.handle_anonymous_submit/>
+                        <n.check_antispam_submit bypass="preview"/>
+                        <n.check_recent_post_limit/>
+                        <n.create_child_of_page_node commit="[n.not.is_preview/]">
+                            <subject><n.subject_field.value/></subject>
+                            <message><n.message_field.value/></message>
+                            <is_html><n.html_format_field.value/></is_html>
+                            <type><n.type_field.value/></type>
+                            <kind>post</kind>
+                            <do>
+                                <n.remember_new_node/>
+                                <n.if.not.is_preview>
+                                    <then>
+                                        <n.save_post/>
+                                        <n.save_new_post_custom_fields/>
+                                        <n.new_node.send_node_as_email/>
+                                    </then>
+                                </n.if.not.is_preview>
+                            </do>
+                        </n.create_child_of_page_node>
+                        <n.if.not.is_preview>
+                            <then>
+                                <n.new_node.save_alert_field/>
+                                <n.redirect_to.new_node.url/>
+                            </then>
+                        </n.if.not.is_preview>
+                    </n.catch_exception.>
+                </else>
+            </n.if.not.is_submitted_form>
+            <n.html5>
+                <head>
+                    <META NAME="robots" CONTENT="noindex,nofollow"/>
+                    <n.title.><n.page_name/></n.title.>
+                    <n.focus/>
+                    <style>
+                        .title-row {
+                        padding:.6em .8em;
+                        font-weight:bold;
+                        }
+                        div.field-title { margin-top: 0; }
+                        label { display:inline;vertical-align:-15%; }
+                        #subject { width:90% }
+                    </style>
+                </head>
+                <body>
+                    <n.edit_header first_text="[n.page_name/]" second_text="[n.truncate. size='80'][n.page_node.subject/][/n.truncate.]" />
+
+                    <n.if.is_submitted_form>
+                        <then>
+                            <n.if.has_exception for="save-block">
+                                <then.show_new_node_error/>
+                                <else>
+                                    <n.if.is_preview>
+                                        <then.new_node.preview/>
+                                    </n.if.is_preview>
+                                </else>
+                            </n.if.has_exception>
+                        </then>
+                    </n.if.is_submitted_form>
+
+                    <n.subscription_reminder/>
+
+                    <n.form. onsubmit="return singleSubmit(this)">
+                        <n.type_field.hidden/>
+                        <n.mailing_list_notice.mailing_list_etiquette/>
+
+                        <n.reply_form />
+
+                        <div style="margin-top:1em">
+                            <n.antispam_submit_button class="toolbar action-button" value="[t]Post Message[/t]"/>
+                            <input type="submit" class="toolbar action-button" name="preview" value="[t]Preview Message[/t]"/>
+                            <t>or</t>
+                            <a href="[n.page_node.url /]"><t>Cancel</t></a>
+                        </div>
+                    </n.form.>
+
+                    <n.hide_null.bottom/>
+                </body>
+            </n.html5>
+        </n.node_page.>
+    </n.mobile.>
+</macro>
+
+<macro name="reply5" requires="servlet">
+    <n.new_post5>
+        <page_name>
+            <t>Reply</t>
+        </page_name>
+        <focus>
+            <n.message_field.focus/>
+        </focus>
+        <mailing_list_etiquette>
+            <li><t>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</t></li>
+            <li><t>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</t></li>
+        </mailing_list_etiquette>
+        <bottom>
+            <n.in_reply_to/>
+        </bottom>
+    </n.new_post5>
+</macro>
+
+<macro name="editor_toolbar" parameters="textarea_id, original_text, node_id" requires="mobile">
+    <n.put_in_head.>
+        <n.editor_stylesheet/>
+        <style>
+            .nabble div.toolbar { min-width: auto; max-width: none; }
+        </style>
+        <n.editor_shared_scripts textarea_id="[n.textarea_id/]" node_id="[n.node_id/]"/>
+        <script>
+            <![CDATA[
+            $.browser = $.browser || {};
+
+            $(document).ready(function() {
+                $('div.toolbar').removeClass('shaded-bg-color');
+                $('#message').removeAttr('style').css({width:'90%'}).attr('rows',15);
+
+                $('div.toolbar button').each(function() {
+                    var $btn = $(this);
+                    if ($btn.text().length > 0) {
+                        var img = $btn.html();
+                        $btn.html(img.match(/<img[^>]+>/));
+                        $btn.css('width','2.5em');
+                    }
+                });
+            });
+            ]]>
+        </script>
+    </n.put_in_head.>
+
+    <div class="toolbar rounded-top shaded-bg-color">
+        <table class="toobar">
+            <tr>
+                <n.editor_quote_button original_text="[n.original_text/]"/>
+                <n.editor_insert_image_button/>
+                <n.editor_bold_button/>
+                <n.editor_italic_button/>
+                <n.editor_link_button/>
+                <n.editor_file_button/>
+                <n.editor_smiley_button/>
+            </tr>
+        </table>
+    </div>
+</macro>
+
+<macro name="editor_file_button">
+    <n.put_in_head.>
+        <script type="text/javascript">
+            <![CDATA[
+            Nabble.uploadFile = function() {
+                Nabble.closeWindows();
+                var $fileDiv = $('#file-upload');
+                var isOpen = $fileDiv.css("display") != 'none';
+                var alreadyLoaded = window.fileuploader && $('#file-upload-form', window.fileuploader.document).size() == 1;
+                if (isOpen)
+                    return;
+                else if (alreadyLoaded)
+                    $fileDiv.show();
+                else {
+                    var f = '';
+                    if ($.browser && $.browser.msie)
+                        f += '<br style="line-height:1px"/>';
+                    f += "<iframe id='fileuploader' name='fileuploader' src='/forum/UploadFile.jtp?node=" + nodeId + "' width='380' height='100' frameBorder='0' scrolling='no' allowtransparency='true'>";
+                    $fileDiv.html(f).show();
+                }
+            };
+            Nabble.uploadedFile = function(name) {
+                var textarea = Nabble.get(textareaID);
+                this.setSelection( textarea, '<nabble_a href="'+name+'">'+name+'</nabble_a>' );
+                textarea.focus();
+                Nabble.closeWindows();
+            };
+            ]]>
+        </script>
+    </n.put_in_head.>
+    <td class="nowrap has-dropdown">
+        <div id="file-upload" class="editor-dropdown file-upload medium-border-color light-bg-color drop-shadow"></div>
+        <button type="button" onclick="Nabble.uploadFile()" class="toolbar file-upload" title="[t]Upload a file[/t]">
+            <img src="/images/paperclip.png" border="0" height="12" alt="File" style="vertical-align:middle"/>
+        </button>
+        <n.tooltip use_title="true"/>
+    </td>
+</macro>
+
+<macro name="new_topic5" requires="servlet">
+    <n.new_post5>
+        <page_name>
+            <t>Post New Message</t>
+        </page_name>
+        <focus>
+            <n.subject_field.focus/>
+        </focus>
+        <mailing_list_etiquette>
+            <li><t>If you are posting a question, please try search first. Your question may have already been answered.</t></li>
+            <li><t>Don't post repeatedly. Wait for a few days. People will read your post by email.</t></li>
+        </mailing_list_etiquette>
+    </n.new_post5>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/more_ads.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,8 @@
+<!-- Show more ads across the site
+(Only for sites that we own) -->
+
+<override_macro name="javascript_library" requires="servlet">
+	window.has_more_ads = true;
+	window.nbl_disableAdsLink = window.nbl_disableAdsLink || "Disable Popup Ads";
+	<n.overridden/>
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/pedxing.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,20 @@
+<!-- http://forums.landertalk.com/ -->
+
+<macro name="atom_topics_by_popularity" requires="servlet" unindent="true">
+	<n.node_page.>
+		<n.page_node.as_atom_feeds.>
+			<n.page_node.topics_list.
+				start="0"
+				length="10"
+				sort="popularity"
+				filter="[n.atom_topics_by_popularity_filter/]"
+			>
+				<n.loop.current_node.as_atom_entry/>
+			</n.page_node.topics_list.>
+		</n.page_node.as_atom_feeds.>
+	</n.node_page.>
+</macro>
+
+<macro name="atom_topics_by_popularity_filter">
+	<n.page_node.date_range_filter from_date="[n.get_parameter name='from_date'/]" to_date="[n.get_parameter name='to_date'/]"/>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/ppc.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,153 @@
+<!--
+Used in
+http://work.ppcassociates.com/
+http://foreverarts.3255.n6.nabble.com/
+http://horse-barns.11180.n6.nabble.com/ (GregChapman)
+-->
+
+<override_macro name="manage_subscribers_controls" requires="node_page">
+	<n.horizontal_tab_control.>
+		<n.current_subscribers_horizontal_tab/>
+		<n.add_subscribers_horizontal_tab/>
+		<n.members_subscribers_horizontal_tab/>
+	</n.horizontal_tab_control.>
+</override_macro>
+
+<macro name="members_subscribers_horizontal_tab">
+	<n.add_horizontal_tab
+		url="[n.page_node.manage_subscribers_path filter='members'/]"
+		text="[t]Add Subscribers[/t]"
+		selected="[n.is_subscriber_filter value='members'/]"
+		details="[n.page_node.subscribe_members/]"
+	/>
+</macro>
+
+<macro name="subscribe_members" requires="node">
+	<n.if.is_submitted_form>
+		<then.process_member_subscriptions/>
+	</n.if.is_submitted_form>
+
+	<n.unsubscribed_members.>
+		<n.if.has_more_elements>
+			<then>
+				<div class="weak-color" style="margin:.5em 0 .5em">
+					Unsubscribed members are listed below.
+					Select who you would like to be subscribed:
+				</div>
+				<n.form.>
+					<input type="hidden" name="filter" value="members"/>
+					<div class="medium-border-color border1" style="padding:.5em;height:25em;overflow:auto">
+						<table>
+							<n.loop.>
+								<n.page_node.subscription_for. email="[n.current_user.user_email/]">
+									<n.if.not.is_subscribed>
+										<then>
+											<n.current_user.member_field.>
+												<tr>
+													<td><n.checkbox/></td>
+													<td><n.current_user.avatar/></td>
+													<td><label for="[n.name/]"><n.current_user.name/></label></td>
+												</tr>
+											</n.current_user.member_field.>
+										</then>
+									</n.if.not.is_subscribed>
+								</n.page_node.subscription_for.>
+							</n.loop.>
+						</table>
+					</div>
+					<div style="margin-top:1.4em">
+						<input type="submit" value="Add Subscribers" />
+					</div>
+				</n.form.>
+			</then>
+			<else>
+				<div class="weak-color" style="margin:.5em 0 .5em">
+					No unsubscribed members found.
+				</div>
+			</else>
+		</n.if.has_more_elements>
+	</n.unsubscribed_members.>
+</macro>
+
+<macro name="unsubscribed_members" dot_parameter="do">
+	<n.site_users. length="99999">
+		<n.filter_by.current_user.can_view.page_node/>
+		<n.filter_by.>
+			<n.page_node.subscription_for. email="[n.current_user.user_email/]">
+				<n.not.is_subscribed/>
+			</n.page_node.subscription_for.>
+		</n.filter_by.>
+		<n.do/>
+	</n.site_users.>
+</macro>
+
+<macro name="member_field" dot_parameter="do" requires="user">
+	<n.field. name="member-[n.id/]"><n.do/></n.field.>
+</macro>
+
+<macro name="process_member_subscriptions">
+	<n.unsubscribed_members.>
+		<n.loop.>
+			<n.page_node.subscription_for. email="[n.current_user.user_email/]">
+				<n.if.both
+						condition1="[n.not.is_subscribed/]"
+						condition2="[n.current_user.member_field.is_checked/]"
+				>
+					<then>
+						<n.save to="DESCENDANTS" type="INSTANT"/>
+						<n.send_subscription_notification/>
+					</then>
+				</n.if.both>
+			</n.page_node.subscription_for.>
+		</n.loop.>
+	</n.unsubscribed_members.>
+</macro>
+
+<macro name="send_subscription_notification" requires="subscription,node_page,servlet">
+	<n.set_local_subscription.this_subscription />
+	<n.new_email.>
+		<n.send>
+			<to><n.user.user_email/></to>
+			<subject>Nabble subscription to <n.page_node.subject/></subject>
+			<text_part>
+				Dear user,
+
+				<n.page_node.owner.name/> subscribed you to <n.page_node.subject/>:
+				<n.page_node.url/>
+
+				With your subscription, posts and replies will be sent directly to your email
+				and you can reply to them to participate. Your subscription works as a mailing list.
+
+				If you don't want to be a subscriber, please click on the link below:
+				<n.local_subscription.unsubscribe_by_code_url/>
+
+				Sincerely,
+				The Nabble team
+				________________________________________
+				Free <n.to_lower_case.page_node.view_name/> powered by Nabble
+				<n.nabble_homepage/>
+			</text_part>
+			<html_part>
+				Dear user,<br/>
+				<br/>
+				<n.page_node.owner.name/> subscribed you to <n.page_node.subject/>:<br/>
+				<n.page_node.url/><br/>
+				<br/>
+				With your subscription, posts and replies will be sent directly to your email
+				and you can reply to them to participate. Your subscription works as a mailing list.<br/>
+				<br/>
+				If you don't want to be a subscriber, please click on the link below:<br/>
+				<div style="background-color:#FFFADB;border:#EDDD79 solid 1px;margin:1.2em 0;padding:.5em">
+					<a href="[n.local_subscription.unsubscribe_by_code_url/]">
+						<n.local_subscription.unsubscribe_by_code_url/>
+					</a>
+				</div>
+				Sincerely,<br/>
+				The Nabble team<br/>
+				________________________________________<br/>
+				Free <n.to_lower_case.page_node.view_name/> powered by Nabble<br/>
+				<n.nabble_homepage/><br/><br/>
+			</html_part>
+		</n.send>
+	</n.new_email.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/responsive.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,236 @@
+<override_macro name="jquery_library">
+	<script src="/assets/jquery/jquery-1.9.1.min.js"></script>
+	<script>$.browser = {}</script>
+</override_macro>
+
+<override_macro name="html_impl" parameters="head,body" requires="servlet">
+	<n.page_start/>
+	<n.update_default_permissions/>
+	<n.nabble_html>
+		<do>
+			<n.embedding_redirection_js/>
+			<n.put_in_head.head/>
+			<n.body/>
+			<n.load_call_later_script/>
+		</do>
+		<output>
+			<![CDATA[<!DOCTYPE html>]]>
+			<html>
+				<head>
+					<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
+					<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+					<n.nabble_stylesheets/>
+					<n.nabble_javascript_libraries/>
+					<n.html_head_content/>
+					<n.nabble_shared_scripts/>
+				</head>
+				<body>
+					<div id="notice" class="notice rounded-bottom"></div>
+					<div class="nabble macro_[n.page_template/]" id="nabble">
+						<n.apply_filters.html_body_content/>
+					</div>
+					<n.bottom_scripts/>
+					<n.as_html_comments.site_information/>
+				</body>
+			</html>
+		</output>
+	</n.nabble_html>
+</override_macro>
+
+<override_macro name="classic_stylesheet_big_avatar">
+	<n.overridden/>
+	<style type="text/css">
+		@media (max-width: 600px) {
+			#topic-search-box{float:none}
+			img.avatar{width:30%;height:30%}
+			td.classic-author{width:55px;font-size:11px;overflow:hidden}
+			div.avatar-inner{margin:5px}
+			div.classic-author-name{width:auto}
+			div.ad > div,div.ad > ins{float:none !important;margin-left:-70px !important}
+			div.classic-header{overflow:visible}
+			div.classic-bar{height:3.5em}
+			div.classic-subject-line{margin:2em 0;left:0;overflow-x:hidden;overflow-y:visible}
+			table.classic-body{margin-top:2em}
+		}
+	</style>
+</override_macro>
+
+<override_macro name="last_post_column" parameters="title,width,white_space">
+	<n.table_column>
+		<head>
+			<td class="[n.column_default_border/] nowrap last-post-column" style="[n.width_style.width/]">
+				<n.default. to="[t]Last Post[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<n.current_node.>
+				<td class="[n.column_default_border/] weak-color">
+					<n.if.either condition1="[n.is_post/]" condition2="[n.has_topics/]">
+						<then>
+							<n.last_node.>
+								<table class="avatar-table">
+									<tr>
+										<n.last_post_column_start/>
+										<td class="column" style="width:100%;padding:0;border:none;[n.style. property='white-space'][n.white_space/][/n.style.]">
+											<n.smart_post_link>
+												<text.when_created.short_format/>
+											</n.smart_post_link>
+											<span class="weak-color column nowrap" style="padding-left:.2em">
+												<t>by <t.author.owner.name truncate="20"/></t>
+											</span>
+										</td>
+									</tr>
+								</table>
+							</n.last_node.>
+						</then>
+						<else>
+							<span style="padding-left:.4em">
+								<t>Empty</t>
+							</span>
+						</else>
+					</n.if.either>
+				</td>
+			</n.current_node.>
+		</body>
+	</n.table_column>
+</override_macro>
+
+<override_macro name="standard_table_stylesheet">
+	<style type="text/css">
+	table.main {
+		width:100%;
+		border-width: 1px;
+		border-style: solid;
+		border-collapse:collapse;
+	}
+	table.main td {
+		padding:.1em;
+		height:2.2em;
+	}
+	tr.header-row td {
+		font-weight:bold;
+		padding: .1em .2em;
+		border-bottom-width: 1px;
+		border-bottom-style: solid;
+	}
+	@media (max-width: 600px) {
+		table.main td{padding-left:0 !important;font-size:95%}
+		tr.header-row td{font-size:80%}
+		td.column{white-space:normal !important}
+		span.column.nowrap{white-space:normal}
+		span.pages{margin: 2em 0}
+		span.pages a{padding:.2em .6em}
+		img.avatar{display:none}
+		img.online{opacity:0}
+	}
+	</style>
+</override_macro>
+
+<override_macro name="topics_table_stylesheet">
+	<style type="text/css">
+		table.main {
+			width:99.9%;
+			border-width: 1px;
+			margin:0 1px;
+			border-style: solid;
+			border-collapse:collapse;
+		}
+		table.main td {
+			padding:.1em;
+			height:2.2em;
+		}
+		tr.header-row td {
+			font-weight:bold;
+			padding: .1em .2em;
+			border-bottom-width: 1px;
+			border-bottom-style: solid;
+		}
+		@media (max-width: 600px) {
+			table.main td{padding-left:0 !important;font-size:95%}
+			tr.header-row td{font-size:80%}
+			td.column{white-space:normal !important}
+			span.column.nowrap{white-space:normal}
+			span.pages{margin: 2em 0}
+			span.pages a{padding:.2em .6em}
+		}
+	</style>
+</override_macro>
+
+<override_macro name="topic_common_head">
+	<n.search_highlight_js/>
+	<n.message_width_js/>
+	<style>
+		@media (max-width: 600px) {
+			div.topics-controls-wrapper{margin:1.2em 0 7em !important}
+			div.root-text img,div.message-text img{max-width:100%}
+		}
+	</style>
+</override_macro>
+
+<override_macro name="topic_controls" requires="forum_topic_namespace">
+	<div class="topics-controls-wrapper" style="margin:1.2em 0 5em">
+		<div id="topics-controls-left" class="float-left nowrap">
+			<n.topic_controls_left/>
+		</div>
+		<div id="topics-controls-right" class="float-right nowrap" style="padding-top:.3em">
+			<n.topic_controls_right/>
+		</div>
+	</div>
+</override_macro>
+
+<override_macro name="category_table_stylesheet">
+	<n.overridden/>
+	<style type="text/css">
+		@media (max-width: 600px) {
+			table.main td{padding-left:0 !important;font-size:95%}
+			tr.header-row td{font-size:80%}
+			tr.main-row img[width]{display:none}
+			td.column{white-space:normal !important}
+			span.column.nowrap{white-space:normal}
+			span.pages{margin:2em 0}
+			span.pages a{padding:.2em .6em}
+		}
+	</style>
+</override_macro>
+
+<override_macro name="nabble_stylesheets" unindent="true">
+	<n.overridden/>
+	<style type="text/css">
+		@media (max-width: 600px) {
+			#search-box,#topic-search-box{margin:1em 0}
+			td.pin-column img{display:none}
+		}
+	</style>
+</override_macro>
+
+<override_macro name="gallery_table_stylesheet">
+	<n.overridden/>
+	<style type="text/css">
+		table.gallery {clear:both}
+		@media (max-width: 780px) {
+			table.gallery td {
+				float:left;
+				width:45%!important;
+				text-align:center;
+				word-break:break-all;
+			}
+		}
+		@media (max-width: 600px) {
+			#columns div.column{width:100%!important}
+			table.gallery td {
+				float:none;
+				display:block;
+				width: 100%!important;
+			}
+		}
+	</style>
+</override_macro>
+
+<override_macro name="mixed_table_stylesheet">
+	<n.overridden/>
+	<style type="text/css">
+		@media (max-width: 600px) {
+			.nowrap{white-space:normal}
+		}
+	</style>
+</override_macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/social_dropdown_links.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,111 @@
+<override_macro name="post_dropdown" requires="node">
+	<n.dropdown.
+		id="postdropdown[n.id/]"
+		text="[t]More[/t]"
+		title="[t]Click for more options[/t]"
+		loadOnClick="/template/NamlServlet.jtp?macro=post_dropdown_later&node=[n.id/]"
+	>
+		<n.menu_reply_to_author/>
+		<n.menu_edit_post/>
+		<n.menu_move_post/>
+		<n.menu_delete_post/>
+		<n.menu_change_post_date/>
+		<n.menu_print_post/>
+		<n.menu_permalink/>
+		<n.menu_raw_mail/>
+		<n.menu_social/>
+	</n.dropdown.>
+</override_macro>
+
+<override_macro name="root_post_dropdown" requires="node">
+	<n.dropdown.
+		id="rootdropdown[n.id/]"
+		text="[t]Options[/t]"
+		title="[t]Click for more options[/t]"
+		loadOnClick="/template/NamlServlet.jtp?macro=root_post_dropdown_later&node=[n.id/]"
+	>
+		<n.menu_topic_subscription/>
+		<n.menu_reply_to_author/>
+		<n.menu_edit_post/>
+		<n.menu_move_post/>
+		<n.menu_delete_topic/>
+		<n.menu_pin_topic/>
+		<n.menu_unpin_topic/>
+		<n.menu_lock_topic/>
+		<n.menu_unlock_topic/>
+		<n.menu_change_post_date/>
+		<n.menu_change_title_and_meta_tags/>
+		<n.menu_embed_post/>
+		<n.menu_print_post/>
+		<n.menu_permalink/>
+		<n.menu_raw_mail/>
+		<n.menu_social/>
+	</n.dropdown.>
+</override_macro>
+
+<macro name="menu_social" requires="node">
+	<n.if.not.app_or_root.is_private>
+		<then>
+			<n.set_var. name='links'>
+				<n.regex_replace_all. pattern="src=" replacement="s'+'rc=">
+					<n.javascript_string_encode.compress.>
+						<n.social_twitter/>
+						<n.social_facebook/>
+						<n.social_delicious/>
+						<n.social_google/>
+						<n.social_stumbleupon/>
+						<n.social_linkedin/>
+						<n.social_digg/>
+					</n.javascript_string_encode.compress.>
+				</n.regex_replace_all.>
+			</n.set_var.>
+			dropdown.add('social<n.id/>', '<n.var name='links'/>','white-space:nowrap');
+		</then>
+	</n.if.not.app_or_root.is_private>
+</macro>
+	
+<macro name="social_img_style">
+	width:16px;height:16px;margin-top:.2em;border:none;
+</macro>
+
+<macro name="social_facebook" requires="node">
+	<a href="[n.encode_url.]http://www.facebook.com/share.php?v=4&src=bm&u=[n.url/]&t=[n.subject/][/n.encode_url.]" title="Facebook" target="_blank" ignore="y">
+		<img src="/images/social/facebook.png" style="[n.social_img_style/]"/>
+	</a>
+</macro>
+
+<macro name="social_twitter" requires="node">
+	<a href="[n.encode_url.]http://twitter.com/share?text=[n.subject/]&related=[n.root_node.subject/]&url=[n.url/][/n.encode_url.]" title="Twitter" target="_blank" ignore="y">
+		<img src="/images/social/twitter.png" style="[n.social_img_style/]"/>
+	</a>
+</macro>
+
+<macro name="social_delicious" requires="node">
+	<a href="[n.encode_url.]http://del.icio.us/post?url=[n.url/]&title=[n.subject/][/n.encode_url.]" title="Delicious" target="_blank" ignore="y">
+		<img src="/images/social/delicious.png" style="[n.social_img_style/]"/>
+	</a>
+</macro>
+
+<macro name="social_digg" requires="node">
+	<a href="[n.encode_url.]http://digg.com/submit?phase=2&url=[n.url/]&title=[n.subject/][/n.encode_url.]" title="Digg" target="_blank" ignore="y">
+		<img src="/images/social/digg.png" style="[n.social_img_style/]"/>
+	</a>
+</macro>
+
+<macro name="social_stumbleupon" requires="node">
+	<a href="[n.encode_url.]http://www.stumbleupon.com/submit?url=[n.url/]&title=[n.subject/][/n.encode_url.]" title="Stumble Upon" target="_blank" ignore="y">
+		<img src="/images/social/stumbleupon.png" style="[n.social_img_style/]"/>
+	</a>
+</macro>
+
+<macro name="social_google" requires="node">
+	<a href="[n.encode_url.]http://www.google.com/bookmarks/mark?op=add&bkmk=[n.url/]&title=[n.subject/][/n.encode_url.]" title="Google Bookmarks" target="_blank" ignore="y">
+		<img src="/images/social/google.png" style="[n.social_img_style/]"/>
+	</a>
+</macro>
+
+<macro name="social_linkedin" requires="node">
+	<a href="[n.encode_url.]http://www.linkedin.com/shareArticle?mini=true&url=[n.url/]&title=[n.url/]&source=[n.root_node.subject/][/n.encode_url.]" title="LinkedIn" target="_blank" ignore="y">
+		<img src="/images/social/linkedin.png" style="[n.social_img_style/]"/>
+	</a>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/social_facebook_like.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,22 @@
+<macro name="facebook_like_button" requires="node">
+	<span style="vertical-align:-5px">
+		<iframe
+			src="[n.encode_url.]http://www.facebook.com/plugins/like.php?href=[n.url/]&layout=button_count&show_faces=false&width=90&action=like&colorscheme=light&height=20[/n.encode_url.]"
+			scrolling="no"
+			frameborder="0"
+			allowTransparency="true"
+			style="border:none; overflow:hidden; width:90px; height:20px">
+		</iframe>
+	</span>
+</macro>
+
+<override_macro name="topic_right_controls" requires="blog_topic_namespace">
+	<n.page_node.facebook_like_button/>
+	&nbsp;
+	<n.overridden/>
+</override_macro>
+	
+<override_macro name="topic_controls_right" requires="forum_topic_namespace">
+	<n.page_node.facebook_like_button/>
+	<n.overridden/>
+</override_macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/social_google_plus_one.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,23 @@
+<macro name="google_plus_one_button" requires="node">
+	<n.put_in_head.>
+		<script type="text/javascript" src="https://apis.google.com/js/plusone.js"></script>
+	</n.put_in_head.>
+	<span class="plus-one-button" style="vertical-align:-5px">
+		<g:plusone href="[n.url/]" size="medium"></g:plusone>
+	</span>
+</macro>
+
+<override_macro name="topic_right_controls" requires="blog_topic_namespace">
+	<n.page_node.google_plus_one_button/>
+	&nbsp;
+	<n.overridden/>
+</override_macro>
+
+<override_macro name="topic_controls_right" requires="forum_topic_namespace">
+	<n.page_node.google_plus_one_button/>
+	<n.overridden/>
+</override_macro>
+
+<static>
+	g:plusone
+</static>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/social_tweet.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,21 @@
+<macro name="tweet_button" requires="node">
+	<span style="vertical-align:-5px">
+		<iframe
+			src="[n.encode_url.]http://platform.twitter.com/widgets/tweet_button.html?url=[n.url/]&text=[n.subject/]&count=horizontal[/n.encode_url.]"
+			scrolling="no"
+			frameborder="0"
+			allowtransparency="true"
+			style="width:100px;height:20px"></iframe>
+	</span>
+</macro>
+
+<override_macro name="topic_right_controls" requires="blog_topic_namespace">
+	<n.page_node.tweet_button/>
+	&nbsp;
+	<n.overridden/>
+</override_macro>
+
+<override_macro name="topic_controls_right" requires="forum_topic_namespace">
+	<n.page_node.tweet_button/>
+	<n.overridden/>
+</override_macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/spam_searcher.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,161 @@
+<namespace name="searcher_tool"/>
+
+<macro name="searcher" requires="servlet">
+	<n.searcher_tool.>
+		<n.if.not.visitor.is_sysadmin>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.is_sysadmin>
+
+		<n.if.both condition1="[n.is_submitted_form/]" condition2="[n.not.is_null.get_parameter name='results'/]">
+			<then>
+				<n.get_parameter_values. name="results">
+					<n.if.has_more_strings>
+						<then>
+							<n.loop.>
+								<n.if.is_valid_node node_id="[n.current_parameter_value/]">
+									<then>
+										<n.get_node_from_id. node_id="[n.current_parameter_value/]">
+											<n.log.>Deleted <n.id/></n.log.>
+											<n.delete_recursively/>
+										</n.get_node_from_id.>
+									</then>
+								</n.if.is_valid_node>
+							</n.loop.>
+							<n.redirect_to.get_parameter name='url'/>
+						</then>
+					</n.if.has_more_strings>
+				</n.get_parameter_values.>
+			</then>
+		</n.if.both>
+
+		<n.define_search_query_field/>
+		<n.node_page.search_namespace.>
+			<n.set_var name='didSearch' value="false" />
+			<n.catch_exception. id="search-block">
+				<n.do_search_using_params />
+				<n.set_var name='didSearch' value="true" />
+			</n.catch_exception.>
+			<n.html>
+				<head>
+					<meta name="robots" content="noindex,nofollow"/>
+					<n.search_page_title/>
+					<n.set_cookies did_search="[n.var name='didSearch'/]" />
+					<n.search_page_style/>
+
+					<script type="text/javascript">
+						$(document).ready(function() {
+						$('div.clickable').each(function() {
+						var $this = $(this);
+						var $checkbox = $this.prev().children().eq(0);
+						$this.click(function() {
+						var checked = $checkbox.attr('checked');
+						if (checked) {
+						$this.removeClass('info-message');
+						$checkbox.removeAttr('checked');
+						} else {
+						$this.addClass('info-message');
+						$checkbox.attr('checked', true);
+						}
+						});
+						});
+						});
+						function selectAll() {
+						$('input[type=checkbox]').attr('checked', true).parent().next().addClass('info-message');
+						};
+					</script>
+				</head>
+				<body>
+					<n.show_search_form/>
+					<n.show_search_error/>
+
+					<div class="search-results-header">
+						<n.if.has_resort>
+							<then.sort_controls/>
+						</n.if.has_resort>
+
+						<img src="/images/search.png" class="image16"/>
+						Found <n.total_posts/>
+						<n.search_description/>
+					</div>
+
+					<button onclick="selectAll()">Select all</button>
+					<n.form.>
+						<n.results.loop.current_node.>
+							<div style="margin-bottom:1.5em">
+								<div style="margin-bottom:.2em">
+									<input type="checkbox" name="results" value="[n.id/]"/>
+									<n.search_result_topic_subject/>
+									<n.search_result_post_subject/>
+								</div>
+								<div class="clickable">
+									<div style="margin-bottom:.2em">
+										<n.search_result_message_fragment/>
+									</div>
+
+									<div class="weak-color" style="font-size:80%">
+										<n.search_result_in_app/>
+										<n.search_result_on_date/>
+										<n.if.not.is_author_search>
+											<then>
+												by
+												<n.owner.user_link/>
+												User's Posts: <n.owner.node_count/>
+											</then>
+										</n.if.not.is_author_search>
+										&mdash;
+										<n.topic_node.replies/> replies in thread
+									</div>
+								</div>
+							</div>
+						</n.results.loop.current_node.>
+
+						<input type="submit" value="Delete Posts Recursively"/>
+						<input type="hidden" name="url" value="[n.current_url/]"/>
+					</n.form.>
+
+					<n.search_pagination/>
+
+					<n.if.not.lucene_is_ready>
+						<then.index_rebuilt_notice/>
+					</n.if.not.lucene_is_ready>
+				</body>
+			</n.html>
+		</n.node_page.search_namespace.>
+	</n.searcher_tool.>
+</macro>
+
+<macro name="search_path" parameters="query,author,starrer,days,index_record,sort" requires="node, searcher_tool">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=searcher&node=<n.id/>
+		<n.add_to_path name="query" value="[n.query/]" />
+		<n.add_to_path name="author" value="[n.author/]" />
+		<n.add_to_path name="starrer" value="[n.starrer/]" />
+		<n.add_to_path name="days" value="[n.days/]" />
+		<n.add_to_path name="i" value="[n.index_record/]" default_value="0" />
+		<n.add_to_path name="sort" value="[n.sort/]" default_value="relevance" />
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="search_form" dot_parameter="do" parameters="style,query,author,starrer,days" requires="node,searcher_tool">
+	<form style="[n.style/]" action="/template/NamlServlet.jtp">
+		<input type="hidden" name="macro" value="searcher" />
+		<input type="hidden" name="node" value="[n.id/]" />
+		<n.hidden_field name="query" value="[n.query/]" />
+		<n.hidden_field name="author" value="[n.author/]" />
+		<n.hidden_field name="starrer" value="[n.starrer/]" />
+		<n.hidden_field name="days" value="[n.days/]" />
+	<n.do/>
+</form>
+</macro>
+
+<macro name="search_page_length" requires="searcher_tool">
+	100
+</macro>
+
+<macro name="search_result_message_fragment" requires="node,search,searcher_tool">
+	<n.highlight.hide_emails.fragment. size="400">
+		<n.message.as_text/>
+	</n.highlight.hide_emails.fragment.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/thread_navigation.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,29 @@
+<static>sub</static>
+
+<override_macro name="topic_header">
+	<n.overridden/>
+	<n.show_topic_navigation_links/>
+</override_macro>
+
+<macro name="show_topic_navigation_links">
+	<div style="margin:-1em 0 2em">
+		<n.if.page_node.topic_node.has_prev_topic>
+			<then>
+				<span style="float:left;white-space:nowrap">
+					<sub class="weak-color" style="font-size:180%">&lsaquo;</sub>
+					<a href="[n.page_node.topic_node.prev_topic.url/]"><t>Previous Topic</t></a>
+				</span>
+			</then>
+		</n.if.page_node.topic_node.has_prev_topic>
+
+		<n.if.page_node.topic_node.has_next_topic>
+			<then>
+				<span style="float:right;white-space:nowrap">
+					<a href="[n.page_node.topic_node.next_topic.url/]"><t>Next Topic</t></a>
+					<sub class="weak-color" style="font-size:180%">&rsaquo;</sub>
+				</span>
+			</then>
+		</n.if.page_node.topic_node.has_next_topic>
+		<div style="clear:both;height:0">&nbsp;</div>
+	</div>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/naml/zimbaroo.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,2 @@
+<override_macro name="nabble_footer">
+</override_macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/poll/NodeNamespaceExt.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,376 @@
+package nabble.modules.poll;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.DateUtils;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.NamespaceExtension;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.StringList;
+import nabble.view.web.template.DateNamespace;
+import nabble.view.web.template.NodeNamespace;
+import nabble.view.web.template.RequestNamespace;
+import nabble.view.web.template.ServletNamespace;
+import nabble.view.web.template.ServletNamespaceUtils;
+
+import javax.servlet.ServletException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+
+@NamespaceExtension(
+	name = "node_poll",
+	target = NodeNamespace.class
+)
+public class NodeNamespaceExt {
+
+	private final Node node;
+	private final ServletNamespaceUtils servletNsUtils;
+
+	public NodeNamespaceExt(NodeNamespace ns) {
+		this.node = ns.node();
+		this.servletNsUtils = ns.servletNsUtils;
+	}
+
+	private Poll getPoll() {
+		return Poll.of(node);
+	}
+
+	public static final CommandSpec set_poll = CommandSpec.DO()
+		.parameters("poll_question", "poll_options")
+		.optionalParameters("poll_max_choices","poll_days_left",
+				"poll_allow_vote_change","poll_show_results_before_vote",
+				"poll_show_results_before_end")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void set_poll(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp)
+		throws ServletException, Poll.PollEditException, Poll.PollFormatException
+	{
+		User user = servletNsUtils.visitorUser(interp);
+		if (!node.getOwner().equals(user))
+			throw new Poll.PollEditException();
+		String pollQuestion = interp.getArgString("poll_question");
+		RequestNamespace.ParameterValueList pollOptionsList = interp.getArgAsNamespace(RequestNamespace.ParameterValueList.class, "poll_options");
+		String[] pollOptions = pollOptionsList.values().toArray(new String[0]);
+		int maxChoices = interp.getArgAsInt("poll_max_choices",1);
+		int daysLeft = interp.getArgAsInt("poll_days_left", 0);
+		boolean allowVoteChange = interp.getArgAsBoolean("poll_allow_vote_change",false);
+		boolean showResultsBeforeVote = interp.getArgAsBoolean("poll_show_results_before_vote",false);
+		boolean showResultsBeforeEnd = interp.getArgAsBoolean("poll_show_results_before_end",false);
+		setPoll(
+			pollQuestion,
+			pollOptions,
+			maxChoices,
+			daysLeft,
+			allowVoteChange,
+			showResultsBeforeVote,
+			showResultsBeforeEnd
+		);
+	}
+
+	private void setPoll(String pollQuestion, String[] pollOptions, int maxChoices, int daysLeft, boolean allowVoteChange, boolean showResultsBeforeVote, boolean showResultsBeforeEnd)
+		throws Poll.PollFormatException
+	{
+		DbDatabase db = node.getSite().getDb();
+		if( !db.isInTransaction() ) {
+			db.beginTransaction();
+			try {
+				setPoll(
+					pollQuestion,
+					pollOptions,
+					maxChoices,
+					daysLeft,
+					allowVoteChange,
+					showResultsBeforeVote,
+					showResultsBeforeEnd
+				);
+				db.commitTransaction();
+			} finally {
+				db.endTransaction();
+			}
+			return;
+		}
+		Poll poll = new Poll( node.getGoodCopy(), pollOptions.length );
+		poll.set(pollQuestion, pollOptions);
+		poll.setMaxChoices(maxChoices);
+		if (daysLeft > 0) poll.setEndDate(DateUtils.addDays(new Date(), daysLeft));
+		poll.setAllowVoteChange(allowVoteChange);
+		poll.setShowResultsBeforeVote(showResultsBeforeVote);
+		poll.setShowResultsBeforeEnd(showResultsBeforeEnd);
+	}
+
+	public static final CommandSpec edit_poll = CommandSpec.DO()
+			.optionalParameters("poll_max_choices","poll_days_left",
+					"poll_allow_vote_change","poll_show_results_before_vote",
+					"poll_show_results_before_end")
+			.requiredInStack(ServletNamespace.class)
+			.build()
+	;
+
+	@Command public void edit_poll(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp)
+		throws ServletException, Poll.PollEditException, Poll.PollFormatException
+	{
+		User user = servletNsUtils.visitorUser(interp);
+		if (!node.getOwner().equals(user))
+			throw new Poll.PollEditException();
+		Poll poll = getPoll();
+		poll.setMaxChoices(interp.getArgAsInt("poll_max_choices",1));
+		String daysLeftS = interp.getArgString("poll_days_left");
+		if (daysLeftS != null) {
+			int daysLeft;
+			try {
+				daysLeft = Integer.parseInt(daysLeftS);
+			} catch (NumberFormatException e) {
+				throw new Poll.PollFormatException();
+			}
+			Date endDate = poll.endDate();
+			Date now = new Date();
+			if (endDate==null) {
+				poll.setEndDate(DateUtils.addDays(now, daysLeft));
+			} else {
+				int daysLeftOrig = DateUtils.datesBetween(now, endDate);
+				if (daysLeftOrig != daysLeft) {
+					poll.setEndDate(DateUtils.addDays(endDate, daysLeft-daysLeftOrig));
+				}
+			}
+		} else {
+			poll.setEndDate(null);
+		}
+		poll.setAllowVoteChange(interp.getArgAsBoolean("poll_allow_vote_change",false));
+		poll.setShowResultsBeforeVote(interp.getArgAsBoolean("poll_show_results_before_vote",false));
+		poll.setShowResultsBeforeEnd(interp.getArgAsBoolean("poll_show_results_before_end",false));
+	}
+
+	public static final CommandSpec delete_poll = CommandSpec.DO()
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void delete_poll(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp)
+		throws ServletException, Poll.PollEditException
+	{
+		User user = servletNsUtils.visitorUser(interp);
+		if (!node.getOwner().equals(user))
+			throw new Poll.PollEditException();
+		DbDatabase db = node.getSite().getDb();
+		db.beginTransaction();
+		try {
+			Poll poll = Poll.of( node.getGoodCopy() );
+			if( poll != null )
+				poll.delete();
+			db.commitTransaction();
+		} finally {
+			db.endTransaction();
+		}
+	}
+
+	public static final CommandSpec has_poll = CommandSpec.DO;
+
+	@Command public void has_poll(IPrintWriter out, Interpreter interp) {
+		out.print(getPoll()!=null);
+	}
+
+	public static final CommandSpec poll_question = CommandSpec.DO;
+
+	@Command public void poll_question(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		out.print(getPoll().getQuestion());
+	}
+
+	public static final CommandSpec poll_option_list = CommandSpec.DO;
+
+	@Command public void poll_option_list(IPrintWriter out,ScopedInterpreter<PollOptionList> interp) {
+		List<String> options = Arrays.asList(getPoll().getOptions());
+		Object block = interp.getArg(new PollOptionList(options, getPoll().getNode().getId()),"do");
+		out.print(block);
+	}
+
+	public static final CommandSpec poll_max_choices = CommandSpec.DO;
+
+	@Command public void poll_max_choices(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		out.print(getPoll().maxChoices());
+	}
+
+	public static final CommandSpec poll_has_end_date = CommandSpec.DO;
+
+	@Command public void poll_has_end_date(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		out.print(getPoll().endDate()!=null);
+	}
+
+	public static final CommandSpec poll_end_date = CommandSpec.DO;
+
+	@Command public void poll_end_date(IPrintWriter out,ScopedInterpreter<DateNamespace> interp) {
+		out.print(interp.getArg( new DateNamespace(getPoll().endDate()), "do" ));
+	}
+
+	public static final CommandSpec poll_days_left = CommandSpec.DO;
+
+	@Command public void poll_days_left(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		Date endDate = getPoll().endDate();
+		if (endDate!=null) {
+			out.print(DateUtils.datesBetween(new Date(), endDate));
+		}
+	}
+
+	public static final CommandSpec poll_allow_vote_change = CommandSpec.DO;
+
+	@Command public void poll_allow_vote_change(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		out.print(getPoll().allowVoteChange());
+	}
+
+	public static final CommandSpec poll_show_results_before_vote = CommandSpec.DO;
+
+	@Command public void poll_show_results_before_vote(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		out.print(getPoll().showResultsBeforeVote());
+	}
+
+	public static final CommandSpec poll_show_results_before_end = CommandSpec.DO;
+
+	@Command public void poll_show_results_before_end(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		out.print(getPoll().showResultsBeforeEnd());
+	}
+
+	public static final CommandSpec poll_vote_counts = CommandSpec.DO()
+			.build()
+	;
+
+	@Command public void poll_vote_counts(IPrintWriter out,ScopedInterpreter<PollOptionList> interp) {
+		int[] v = getPoll().getVoteCounts();
+		List<String> voteCounts = new ArrayList<String>();
+		for (int n : v) {
+			voteCounts.add(String.valueOf(n));
+		}
+		Object block = interp.getArg(new PollOptionList(voteCounts, getPoll().getNode().getId()),"do");
+		out.print(block);
+	}
+
+	public static final CommandSpec poll_visitor_has_voted = CommandSpec.DO()
+			.dotParameter("index")
+			.requiredInStack(ServletNamespace.class)
+			.build()
+	;
+
+	@Command public void poll_visitor_has_voted(IPrintWriter out,ScopedInterpreter<StringList> interp)
+		throws ServletException
+	{
+		int index = interp.getArgAsInt("index");
+		User viewer = servletNsUtils.visitorUser(interp);
+		int[] v = getPoll().getVotes(viewer);
+		for (int i : v) {
+			if (i==index) {
+				out.print(true);
+				return;
+			}
+		}
+		out.print(false);
+	}
+
+	public static final CommandSpec poll_visitor_can_see_votes = CommandSpec.DO()
+			.requiredInStack(ServletNamespace.class)
+			.build()
+	;
+
+	@Command public void poll_visitor_can_see_votes(IPrintWriter out,Interpreter interp)
+			throws ServletException
+	{
+		Poll poll = getPoll();
+		User viewer = servletNsUtils.visitorUser(interp);
+		Person owner = node.getOwner();
+		if (viewer!=null && viewer.equals(owner)) {
+			out.print(true);
+			return;
+		}
+		if (poll.endDate()!=null) {
+			Date now = new Date();
+			if (!poll.showResultsBeforeEnd() && poll.endDate().after(now)) {
+				out.print(false);
+				return;
+			}
+			if (poll.endDate().before(now)) {
+				out.print(true);
+				return;
+			}
+		}
+		if (!poll.showResultsBeforeVote() && (viewer==null || poll.getVotes(viewer).length==0)) {
+			out.print(false);
+			return;
+		}
+		out.print(true);
+	}
+
+	public static final CommandSpec poll_has_ended = CommandSpec.DO;
+
+	@Command public void poll_has_ended(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		Poll poll = getPoll();
+		boolean hasEnded = poll.endDate() != null && poll.endDate().before(new Date());
+		out.print(hasEnded);
+	}
+
+	public static final CommandSpec poll_visitor_can_vote = CommandSpec.DO()
+			.requiredInStack(ServletNamespace.class)
+			.build()
+	;
+
+	@Command public void poll_visitor_can_vote(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		Poll poll = getPoll();
+		User viewer = servletNsUtils.visitorUser(interp);
+		if (poll.endDate()!=null && poll.endDate().before(new Date())) {
+			out.print(false);
+			return;
+		}
+		if (!poll.allowVoteChange() && (viewer==null || poll.getVotes(viewer).length>0)) {
+			out.print(false);
+			return;
+		}
+		out.print(true);
+	}
+
+	public static final CommandSpec poll_vote = CommandSpec.DO()
+			.parameters("votes")
+			.requiredInStack(ServletNamespace.class)
+			.build()
+	;
+
+	@Command public void poll_vote(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp)
+		throws ServletException, Poll.PollVoteException {
+		Poll poll = getPoll();
+		User voter = servletNsUtils.visitorUser(interp);
+		RequestNamespace.ParameterValueList vlist = interp.getArgAsNamespace(RequestNamespace.ParameterValueList.class, "votes");
+		if (vlist==null) return;
+		List<String> votes = vlist.values();
+		int[] v = new int[votes.size()];
+		for (int i=0; i<v.length; i++)
+			v[i] = Integer.parseInt(votes.get(i));
+		poll.vote(voter, v);
+	}
+
+	@Namespace(
+		name = "poll_option_list",
+		global = true
+	)
+	public static final class PollOptionList extends StringList {
+
+		private final long nodeId;
+
+		PollOptionList(List<String> list, long nodeId) {
+			super(list);
+			this.nodeId = nodeId;
+		}
+
+		@Command public void option_id(IPrintWriter out,Interpreter interp) {
+			out.print(nodeId + "-" + index);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/poll/Poll.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,262 @@
+package nabble.modules.poll;
+
+import java.io.Serializable;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import fschmidt.db.DbNull;
+import fschmidt.db.DbRecord;
+import fschmidt.db.LongKey;
+import nabble.model.ModelException;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.model.ExtensionFactory;
+import nabble.model.ModelHome;
+
+
+final class Poll {
+
+
+	private static final ExtensionFactory<Node,Poll> FACTORY = new ExtensionFactory<Node,Poll>() {
+
+		public String getName() {
+			return PollModule.INSTANCE.getName();
+		}
+
+		public Class<Poll> extensionClass() {
+			return Poll.class;
+		}
+
+		public Poll construct(Node node) {
+			return null;
+		}
+
+		public Poll construct(Node node,ResultSet rs)
+			throws SQLException
+		{
+			int pollOptionCount = rs.getInt("poll_option_count");
+			if( pollOptionCount==0 )
+				return null;
+			return new Poll(node,pollOptionCount);
+		}
+
+		public Serializable getExportData(Node node) {
+			return null; //throw new UnsupportedOperationException();
+		}
+
+		public void saveExportData(Node node,Serializable s) {
+			//throw new UnsupportedOperationException();
+		}
+	};
+
+	static {
+		ModelHome.addNodeExtensionFactory(FACTORY);
+	}
+
+	static void init() {}
+
+	public static Poll of(Node node) {
+		return node.getExtension(FACTORY);
+	}
+
+
+
+
+
+	private final Node node;
+	private int pollOptionCount;
+	private String question;
+	private String[] options;
+	private int maxChoices = 1;
+	private Date endDate = null;
+	private boolean allowVoteChange = true;
+	private boolean showResultsBeforeVote = true;
+	private boolean showResultsBeforeEnd = true;
+
+	Poll(Node node,int pollOptionCount) {
+		this.node = node;
+		this.pollOptionCount = pollOptionCount;
+		this.question = node.getProperty("poll_question");
+		this.options = new String[pollOptionCount];
+		for (int i=0; i<options.length; i++)
+			this.options[i] = node.getProperty("poll_option_"+i);
+		String maxChoicesS = node.getProperty("poll_max_choices");
+		maxChoices = maxChoicesS!=null ? Integer.parseInt(maxChoicesS) : 1;
+		String endtime = node.getProperty("poll_end_date");
+		endDate = endtime == null ? null : new Date(Long.parseLong(endtime));
+		allowVoteChange = !"false".equals(node.getProperty("poll_allow_vote_change"));
+		showResultsBeforeVote = !"false".equals(node.getProperty("poll_show_results_before_vote"));
+		showResultsBeforeEnd = !"false".equals(node.getProperty("poll_show_results_before_end"));
+	}
+
+	void set(String question, String[] options) throws PollFormatException {
+		if( !node.getSite().getDb().isInTransaction() )
+			throw new RuntimeException("not in transaction");
+		if (question.trim().length()==0 || options.length < 2)
+			throw new PollFormatException();
+		this.pollOptionCount = options.length;
+		DbRecord<LongKey,?> record = node.getDbRecord();
+		record.fields().put("poll_option_count", pollOptionCount);
+		record.update();
+		this.question = question.trim();
+		node.setProperty("poll_question", question.trim());
+		this.options = new String[options.length];
+		for (int i=0; i<options.length; i++) {
+			String option = options[i].trim();
+			if (option.length()==0)
+				throw new PollFormatException();
+			this.options[i] = option;
+			node.setProperty("poll_option_"+i, option);
+		}
+		clearVotes();
+	}
+
+	Node getNode() {
+		return node;
+	}
+
+	String getQuestion() {
+		return question;
+	}
+
+	String[] getOptions() {
+		return options;
+	}
+
+	int[] getVoteCounts() {
+		int[] votes = new int[options.length];
+		for (int i=0; i<options.length; i++) {
+			votes[i] = node.getSite().countTags("node_id="+node.getId()+"and label='"+VOTE_LABEL_PREFIX+i+"'");
+		}
+		return votes;
+	}
+
+	private static final String VOTE_LABEL_PREFIX="poll_vote:";
+
+	int[] getVotes(User user) {
+		List<Integer> v = new ArrayList<Integer>();
+		for (int i=0; i<options.length; i++) {
+			boolean vote = node.getSite().hasTags(node, user, "label='"+VOTE_LABEL_PREFIX+i+"'");
+			if (vote) v.add(i);  // duplicates aren't possible because the index is unique
+		}
+		int[] votes = new int[v.size()];
+		for (int i=0; i<votes.length; i++)
+			votes[i] = v.get(i);
+		return votes;
+		/*
+		List<String> labels = node.getSite().findTagLabels(
+				"node_id="+node.getId()+" and user_id="+user.getId()+" and label like '"+VOTE_LABEL_PREFIX+"%'"
+				);
+		int[] votes = new int[labels.size()];
+		for (int i=0; i<votes.length; i++) {
+			votes[i] = Integer.parseInt(labels.get(i).substring(VOTE_LABEL_PREFIX.length()));
+		}
+		return votes;
+		*/
+	}
+
+	void vote(User user, int[] votes) throws PollVoteException {
+		if (votes.length > maxChoices)
+			throw new PollVoteException();
+		if (!allowVoteChange && getVotes(user).length>0)
+			throw new PollVoteException();
+		if (endDate!=null && endDate.before(new Date()))
+			throw new PollVoteException();
+		node.getSite().deleteTags(node, user, "label like '"+VOTE_LABEL_PREFIX+"%'");
+		for (int vote : votes) {
+			node.getSite().addTag(node, user, VOTE_LABEL_PREFIX+vote);
+		}
+	}
+
+	private void clearVotes() {
+		node.getSite().deleteTags(
+				"node_id="+node.getId()+" and label like '"+VOTE_LABEL_PREFIX+"%'"
+				);
+	}
+
+	void delete() {
+		if( !node.getSite().getDb().isInTransaction() )
+			throw new RuntimeException("not in transaction");
+		node.setProperty("poll_question", null);
+		for (int i=0; i<pollOptionCount; i++) {
+			node.setProperty("poll_option_"+i, null);
+		}
+		node.setProperty("poll_max_choices", null);
+		node.setProperty("poll_end_date", null);
+		node.setProperty("poll_allow_vote_change", null);
+		node.setProperty("poll_show_results_before_vote", null);
+		node.setProperty("poll_show_results_before_end", null);
+		clearVotes();
+		DbRecord<LongKey,?> record = node.getDbRecord();
+		record.fields().put("poll_option_count", DbNull.INTEGER);
+		record.update();
+	}
+
+
+	int maxChoices() {
+		return maxChoices;
+	}
+
+	Date endDate() {
+		return endDate;
+	}
+
+	boolean allowVoteChange() {
+		return allowVoteChange;
+	}
+
+	boolean showResultsBeforeVote() {
+		return showResultsBeforeVote;
+	}
+
+	boolean showResultsBeforeEnd() {
+		return showResultsBeforeEnd;
+	}
+
+	void setMaxChoices(int maxChoices) throws PollFormatException {
+		if (maxChoices < 1 || maxChoices > options.length) throw new PollFormatException();
+		this.maxChoices = maxChoices;
+		node.setProperty("poll_max_choices", maxChoices > 1 ? String.valueOf(maxChoices) : null);
+	}
+
+	void setEndDate(Date endDate) {
+		this.endDate = endDate;
+		node.setProperty("poll_end_date", endDate!=null ? String.valueOf(endDate.getTime()) : null);
+	}
+
+	void setAllowVoteChange(boolean allowVoteChange) {
+		node.setProperty("poll_allow_vote_change", allowVoteChange ? null : "false");
+		this.allowVoteChange = allowVoteChange;
+	}
+
+	void setShowResultsBeforeVote(boolean showResultsBeforeVote) {
+		node.setProperty("poll_show_results_before_vote", showResultsBeforeVote ? null : "false");
+		this.showResultsBeforeVote = showResultsBeforeVote;
+	}
+
+	void setShowResultsBeforeEnd(boolean showResultsBeforeEnd) {
+		node.setProperty("poll_show_results_before_end", showResultsBeforeEnd ? null : "false");
+		this.showResultsBeforeEnd = showResultsBeforeEnd;
+	}
+
+	public static class PollFormatException extends ModelException {
+		public PollFormatException() {
+			super(name("invalid_poll_format"), "Invalid poll parameters.");
+		}
+	}
+	
+	public static class PollEditException extends ModelException {
+		public PollEditException() {
+			super(name("poll_edit_disallowed"), "You cannot modify this poll.");
+		}
+	}
+
+	public static class PollVoteException extends ModelException {
+		public PollVoteException() {
+			super(name("invalid_vote_attempt"), "Invalid voting attempt.");
+		}
+	}
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/poll/PollModule.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,46 @@
+package nabble.modules.poll;
+
+import fschmidt.util.java.FutureValue;
+import nabble.modules.ModuleManager;
+import nabble.naml.compiler.Module;
+import nabble.naml.compiler.Source;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+
+public enum PollModule implements Module {
+	INSTANCE;
+
+	private static final Iterable<Class> extensions = Arrays.asList(new Class[]{
+			NodeNamespaceExt.class,
+	});
+
+	private final FutureValue<Collection<Source>> sources = new FutureValue<Collection<Source>>() {
+		protected Collection<Source> compute() {
+			return ModuleManager.loadSource(PollModule.this);
+		}
+	};
+
+	public String getName() {
+		return "poll";
+	}
+
+	public Iterable<Class> getExtensions() {
+		return extensions;
+	}
+
+	public Collection<Source> getSources() {
+		return sources.get();
+	}
+
+	public Set<String> getDependencies() {
+		return Collections.emptySet();
+	}
+
+	static {
+		Poll.init();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/poll/poll.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,572 @@
+=== Permission ===
+
+<macro name="create_poll_permission">
+	Create_poll
+</macro>
+
+<macro name="can_create_poll_in" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.both>
+		<condition1.local_user.is_registered/>
+		<condition2.either>
+			<condition1.local_user.is_site_admin/>
+			<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.create_poll_permission/]" />
+		</condition2.either>
+	</n.both>
+</macro>
+
+<override_macro name="all_permissions_list">
+	<n.overridden />
+	<n.create_poll_permission/>,
+</override_macro>
+
+<override_macro name="permission_rows">
+	<n.overridden />
+	<n.permission_row
+		permission = "[n.create_poll_permission/]"
+		description="[t]Who can create polls.[/t]"
+		anyone_cell = ""
+		administrators_cell = "true"
+	/>
+</override_macro>
+
+=== Node Message ===
+
+<override_macro name="node_message_as_html" requires="node">
+	<n.overridden />
+
+	<n.comment.>
+		If this is a topic page (which calls the "message_text" command), then show the poll.
+		Otherwise (email messages), print a link to the poll.
+	</n.comment.>
+	<n.if.is_in_command name="email_message">
+		<then.poll_link/>
+		<else.poll/>
+	</n.if.is_in_command>
+</override_macro>
+
+<macro name="poll_link" requires="node">
+	<n.if.has_poll>
+		<then>
+			<div style="background-color:#FFFADB;border:#EDDD79 solid 1px;margin-top:1em;padding:.5em">
+				<b><t>This message has a poll</t></b> --  <t>Visit the link below if you want to participate:</t><br/>
+				<a href="[n.url/]"><n.url/></a>
+			</div>
+		</then>
+	</n.if.has_poll>
+</macro>
+
+<override_macro name="message_as_text" requires="message" unindent="true">
+	<n.overridden/>
+
+	<n.if.is_in_command name="node_message_block">
+		<then.if.message_block_node.has_poll>
+			<then>
+				<n.crlf/>[<t>Poll</t>] <t>This message has a poll</t> / <t>Visit the link below if you want to participate:</t>
+				<n.message_block_node.url/>
+			</then>
+		</then.if.message_block_node.has_poll>
+	</n.if.is_in_command >
+</override_macro>
+
+=== New post page ===
+
+<override_macro name="new_post_extra_fields" requires="node_page">
+	<n.overridden/>
+
+	<n.if.both condition1="[n.page_node.is_app/]" condition2="[n.visitor.can_create_poll_in.page_node/]">
+		<then>
+			<div class="extra-fields">
+				<img src="/images/add.png" width="12" height="12" style="margin-top:.2em"/>
+				<n.add_poll_form/>
+			</div>
+		</then>
+	</n.if.both>
+</override_macro>
+
+<override_macro name="init_new_post_custom_fields">
+	<n.overridden/>
+	<n.if.visitor.can_create_poll_in.page_node>
+		<then>
+			<n.poll_max_choices_field.set_value value="1" />
+			<n.poll_allow_vote_change_field.set_value value="true" />
+			<n.poll_show_results_before_vote_field.set_value value="true" />
+			<n.poll_show_results_before_end_field.set_value value="true" />
+		</then>
+	</n.if.visitor.can_create_poll_in.page_node>
+</override_macro>
+
+<override_macro name="save_new_post_custom_fields">
+	<n.overridden/>
+	<n.if.visitor.can_create_poll_in.page_node>
+		<then.new_node.save_new_poll/>
+	</n.if.visitor.can_create_poll_in.page_node>
+</override_macro>
+
+=== Edit post page ===
+
+<override_macro name="edit_post_extra_fields" requires="node_page">
+	<n.overridden/>
+
+	<n.if.both condition1="[n.page_node.parent_node.is_app/]" condition2="[n.visitor.can_create_poll_in.page_node/]">
+		<then>
+			<div class="extra-fields">
+				<img src="/images/add.png" width="12" height="12" style="margin-top:.2em"/>
+				<n.if.page_node.has_poll>
+					<then.edit_poll_form/>
+					<else.add_poll_form/>
+				</n.if.page_node.has_poll>
+			</div>
+		</then>
+	</n.if.both>
+</override_macro>
+
+<override_macro name="init_edit_post_custom_fields" requires="node_page">
+	<n.overridden/>
+	<n.if.both condition1="[n.page_node.has_poll/]" condition2="[n.visitor.can_create_poll_in.page_node/]">
+		<then>
+			<n.poll_max_choices_field.set_value value="[n.page_node.poll_max_choices/]" />
+			<n.poll_days_left_field.set_value value="[n.page_node.poll_days_left/]" />
+			<n.poll_allow_vote_change_field.set_value value="[n.page_node.poll_allow_vote_change/]" />
+			<n.poll_show_results_before_vote_field.set_value value="[n.page_node.poll_show_results_before_vote/]" />
+			<n.poll_show_results_before_end_field.set_value value="[n.page_node.poll_show_results_before_end/]" />
+		</then>
+	</n.if.both>
+</override_macro>
+
+<override_macro name="save_edit_post_custom_fields" requires="node_page">
+	<n.overridden/>
+	<n.if.both condition1="[n.page_node.has_poll/]" condition2="[n.visitor.can_create_poll_in.page_node/]">
+		<then>
+			<n.edit_poll>
+				<poll_max_choices>
+					<n.get_int. default="1" exception="max_choices_field_not_integer"><n.poll_max_choices_field.value/></n.get_int.>
+				</poll_max_choices>
+				<poll_days_left><n.to_null_if. equals=""><n.poll_days_left_field.value/></n.to_null_if.></poll_days_left>
+				<poll_allow_vote_change><n.poll_allow_vote_change_field.value/></poll_allow_vote_change>
+				<poll_show_results_before_vote><n.poll_show_results_before_vote_field.value/></poll_show_results_before_vote>
+				<poll_show_results_before_end><n.poll_show_results_before_end_field.value/></poll_show_results_before_end>
+			</n.edit_poll>
+		</then>
+		<else.page_node.save_new_poll/>
+	</n.if.both>
+</override_macro>
+
+=== Error Handling ===
+
+<override_macro name="custom_new_node_errors">
+	<n.overridden/>
+	<n.poll_errors/>
+</override_macro>
+
+<override_macro name="custom_edit_post_errors">
+	<n.overridden/>
+	<n.poll_errors/>
+</override_macro>
+
+<macro name="poll_errors" requires="error">
+	<n.exception. name="invalid_poll_format">
+		<t>Invalid poll parameters.</t>
+	</n.exception.>
+	<n.exception. name="days_field_not_integer">
+		<t>Poll duration must be a non-negative integer or blank.</t>
+	</n.exception.>
+	<n.exception. name="max_choices_field_not_integer">
+		<t>Poll maximum allowed choices must be a non-negative integer or blank.</t>
+	</n.exception.>
+</macro>
+
+=== Javascript Handler ===
+
+<override_macro name="extra_call_later_handlers">
+	<n.overridden/>
+	<n.poll_js/>
+</override_macro>
+
+=== Other macros ===
+
+<macro name="add_poll_form" requires="node_page">
+	<script type="text/javascript">
+		function addPoll() {
+			$('#poll').show();
+			if ( $('#poll-options div').size() == 0 ) {
+				addOption(false);
+				addOption(false);
+			} else
+				updateOptions();
+			Nabble.resizeFrames();
+		};
+		function removeOption(e) {
+			$(e).parent().remove();
+			updateOptions();
+			Nabble.resizeFrames();
+		};
+		function addOption(focus) {
+			$('#poll-options').append('<n.javascript_string_encode.poll_option_input_row/>');
+			updateOptions();
+			if (focus)
+				$('#poll-options div input').last().focus();
+			Nabble.resizeFrames();
+		};
+		function updateOptions() {
+			var c = 1;
+			$('#poll-options div').each(function() {
+				$('a,span', this).remove();
+				$(this).prepend('<span>'+c+'.</span>');
+				if (c++ > 2) {
+					$(this).append('<a class="removeOption" href="javascript:void(0)" onclick="removeOption(this)"><t>remove</t></a>');
+				}
+			});
+		};
+		function removePoll() {
+			$('input[name=poll_question]').val('');
+			$('#poll').hide();
+		};
+		<n.if.not.is_empty.poll_question_field.value>
+			<then>
+				$(document).ready(function() {
+					updateOptions();
+				});
+			</then>
+		</n.if.not.is_empty.poll_question_field.value>
+	</script>
+	<a href="javascript: void(0)" onclick="addPoll()"><t>Add new poll</t></a>
+
+	<div id="poll" class="extra-field-details medium-border-color" style="[n.if.is_empty.poll_question_field.value][then]display:none[/then][/n.if.is_empty.poll_question_field.value]">
+		<table>
+			<tr valign="top">
+				<td style="text-align:right;padding-top:.4em"><t>Question:</t></td>
+				<td style="padding-left:1.8em"><n.poll_question_field.input type="text" size="40"/></td>
+			</tr>
+			<tr valign="top">
+				<td style="text-align:right;padding-top:.4em"><t>Answers:</t></td>
+				<td style="padding-left:.7em">
+					<div id="poll-options" style="padding-bottom:.3em">
+						<n.get_parameter_values. name="poll_option">
+							<n.loop.poll_option_input_row.current_parameter_value/>
+						</n.get_parameter_values.>
+					</div>
+					<a href="javascript: void addOption(true)"><t>Add new answer</t></a>
+				</td>
+			</tr>
+		</table>
+		<n.poll_settings/>
+		<a href="javascript: void(0)" onclick="removePoll()"><t>Remove Poll</t></a>
+	</div>
+</macro>
+
+<macro name="edit_poll_form" requires="node_page">
+	<script type="text/javascript">
+		Nabble.deletePoll = function(nodeId) {
+			var call = '/template/NamlServlet.jtp?macro=delete_poll_js&node='+nodeId;
+			if (confirm("<t>Delete this poll, including all votes?</t>")) {
+				$.getScript(call, function() {
+					notice('<t>Poll has been deleted.</t>', 2000, 2000);
+					$('#poll').remove();
+				});
+			}
+			return false;
+		};
+	</script>
+	<n.page_node.>
+		<t>Poll</t>
+
+		<div id="poll" class="extra-field-details medium-border-color">
+			<div class="weak-color" style="padding:.3em">
+				<div class="bold"><n.encode.poll_question/></div>
+				<ul style="margin:.3em 0">
+					<n.poll_option_list.loop.>
+						<li><n.current_string/></li>
+					</n.poll_option_list.loop.>
+				</ul>
+				<n.poll_settings />
+				<a href="javascript: void(0)" onclick="Nabble.deletePoll([n.id/])"><t>Remove Poll</t></a>
+			</div>
+		</div>
+	</n.page_node.>
+</macro>
+
+<macro name="poll_settings">
+	<div style="margin: .5em 0 .5em 1em">
+		<t>Multiple selections allowed:</t> <n.poll_max_choices_field.input type="text" size="2" maxlength="2"/><br />
+		<t>Poll ends after <t.number.poll_days_left_field.input type="text" size="2" maxlength="3"/> days (leave blank for unlimited).</t><br />
+		<n.poll_allow_vote_change_field.checkbox/> <label for="poll_allow_vote_change"><t>Allow vote changes</t></label><br />
+		<n.poll_show_results_before_vote_field.checkbox/> <label for="poll_show_results_before_vote"><t>Allow viewing results before vote</t></label><br />
+		<n.poll_show_results_before_end_field.checkbox/> <label for="poll_show_results_before_end"><t>Allow viewing results before end date (poll creators can always view the results)</t></label>
+	</div>
+</macro>
+
+<macro name="save_new_poll" requires="node">
+	<n.if.not.is_empty.poll_question_field.value>
+		<then>
+			<n.set_poll>
+				<poll_question><n.poll_question_field.value/></poll_question>
+				<poll_options><n.get_parameter_values name="poll_option"/></poll_options>
+				<poll_max_choices>
+					<n.get_int. default="1" exception="max_choices_field_not_integer"><n.poll_max_choices_field.value/></n.get_int.>
+				</poll_max_choices>
+				<poll_days_left>
+					<n.get_int. default="0" exception="days_field_not_integer"><n.poll_days_left_field.value/></n.get_int.>
+				</poll_days_left>
+				<poll_allow_vote_change><n.poll_allow_vote_change_field.value/></poll_allow_vote_change>
+				<poll_show_results_before_vote><n.poll_show_results_before_vote_field.value/></poll_show_results_before_vote>
+				<poll_show_results_before_end><n.poll_show_results_before_end_field.value/></poll_show_results_before_end>
+			</n.set_poll>
+		</then>
+	</n.if.not.is_empty.poll_question_field.value>
+</macro>
+
+<macro name="poll_question_field" dot_parameter="do">
+	<n.field. name="poll_question"><n.do/></n.field.>
+</macro>
+
+<macro name="poll_max_choices_field" dot_parameter="do">
+	<n.field. name="poll_max_choices"><n.do/></n.field.>
+</macro>
+
+<macro name="poll_days_left_field" dot_parameter="do">
+	<n.field. name="poll_days_left"><n.do/></n.field.>
+</macro>
+
+<macro name="poll_allow_vote_change_field" dot_parameter="do">
+	<n.field. name="poll_allow_vote_change"><n.do/></n.field.>
+</macro>
+
+<macro name="poll_show_results_before_vote_field" dot_parameter="do">
+	<n.field. name="poll_show_results_before_vote"><n.do/></n.field.>
+</macro>
+
+<macro name="poll_show_results_before_end_field" dot_parameter="do">
+	<n.field. name="poll_show_results_before_end"><n.do/></n.field.>
+</macro>
+
+<macro name="poll_option_input_row" dot_parameter="poll_option">
+	<div class="nowrap">
+		<input type="text" class="poll_option" name="poll_option" size="30" value="[n.hide_null.poll_option/]" />
+	</div>
+</macro>
+
+<macro name="delete_poll_js">
+	<n.get_node_from_id. node_id="[n.get_parameter name='node'/]">
+		<n.delete_poll />
+	</n.get_node_from_id.>
+</macro>
+
+<macro name="poll" requires="node">
+	<n.comment.>
+		Builds the poll interface. Since some options depend on the current visitor, we use javascript
+		to set up some fields. Please look at the "poll_js" macro in order to understand how this poll UI is updated.
+	</n.comment.>
+	<n.if.has_poll>
+		<then>
+			<form method="POST" id="poll_form[n.id/]" onsubmit="return Nabble.vote([n.id/],[n.poll_max_choices/]);" accept-charset="UTF-8" >
+				<input type="hidden" name="node" value="[n.id/]" />
+
+				<div class="medium-border-color border2 rounded" style="margin-top:1em;padding:1em">
+					<div class="big-title second-font">
+						<n.encode.poll_question/>
+					</div>
+					<n.explain_poll_max_choices/>
+
+					<n.poll_option_list.loop.>
+						<div style="margin:.4em 0 0 1em">
+							<span id="poll-option-input[n.option_id/]"></span>
+							<label for="option[n.option_id/]"><n.encode.current_string/></label>
+							<span id="poll-vote-count[n.option_id/]" class="weak-color" style="font-size:80%"></span>
+						</div>
+					</n.poll_option_list.loop.>
+
+					<div style="margin-top:.7em">
+						<n.poll_submit_button/>
+						&nbsp;&nbsp;
+						<n.poll_total_votes/>
+					</div>
+					<n.poll_description_lines/>
+				</div>
+			</form>
+			<n.call_later value="[n.id/]" param="poll_node_id"/>
+		</then>
+	</n.if.has_poll>
+</macro>
+
+<macro name="poll_js" requires="servlet">
+	<n.comment.>
+		This is the javascript code that will update the poll controls and make them available to the users.
+	</n.comment.>
+	<n.param_loop. param="poll_node_id">
+		<n.get_node_from_id. node_id="[n.current_parameter_value/]">
+			<n.set_local_node.this_node />
+			<n.if.visitor.is_registered>
+				<then>
+					<n.poll_option_list.loop.>
+						var input = '<n.local_node.poll_vote_input_field field_id="option[n.option_id/]" index="[n.current_index/]"/>';
+						$('#poll-option-input<n.option_id/>').html(input);
+					</n.poll_option_list.loop.>
+
+					<n.if.poll_has_ended>
+						<then>
+							var input = '<t>This poll is closed.</t>';
+						</then>
+						<else>
+							var txt_vote = "<t>Vote</t>";
+							var input = '<input type="submit" value="' + txt_vote + '"/>';
+							if (!<n.poll_visitor_can_vote/>)
+								input = '<input type="submit" value="' + txt_vote + '" disabled="true"/>';
+						</else>
+					</n.if.poll_has_ended>
+					$('#poll-submit-button<n.id/>').html(input);
+				</then>
+			</n.if.visitor.is_registered>
+
+			<n.if.poll_visitor_can_see_votes>
+				<then>
+					var total_votes = 0;
+					<n.poll_vote_counts.loop.>
+						var text = <n.current_string/> == 1? '(<t>1 vote</t>)' : '(<t><t.number.current_string/> votes</t>)';
+						$('#poll-vote-count<n.option_id/>').html(text);
+						total_votes += <n.current_string/>;
+					</n.poll_vote_counts.loop.>
+					var text = '<t>Total votes:</t> ' + total_votes;
+					$('#poll-total-votes<n.local_node.id/>').html(text).show();
+				</then>
+			</n.if.poll_visitor_can_see_votes>
+		</n.get_node_from_id.>
+	</n.param_loop.>
+</macro>
+
+<macro name="poll_submit_button" requires="node">
+	<n.comment.>
+		Creates the submit button. Initially, it is just a span with some default text.
+		The content of the span is updated by javascript code (see "poll_js" macro for more info)
+	</n.comment.>
+	<span id="poll-submit-button[n.id/]" class="bold">
+		<n.if.poll_has_ended>
+			<then><t>This poll is closed.</t></then>
+			<else>
+				<n.login_link.><t>Login to vote</t></n.login_link.>
+			</else>
+		</n.if.poll_has_ended>
+	</span>
+</macro>
+
+<macro name="poll_total_votes" requires="node">
+	<n.comment.>
+		The span below shows the total number of votes.
+		This element is updated by javascript code (see "poll_js" macro for more info)
+	</n.comment.>
+	<span id="poll-total-votes[n.id/]" class="shaded-bg-color rounded" style="padding:.2em .4em;display:none"></span>
+</macro>
+
+<macro name="poll_description_lines">
+	<div class="weak-color" style="font-size:80%;margin-top:.6em">
+		<n.if.not.poll_allow_vote_change>
+			<then>
+				<t>You cannot change your vote after voting.</t><br/>
+			</then>
+		</n.if.not.poll_allow_vote_change>
+		<n.if.poll_has_end_date>
+			<then>
+				<n.if.poll_has_ended>
+					<then>
+						<t>This poll ended on <t.date.poll_end_date.long_format/>.</t><br/>
+					</then>
+					<else>
+						<t>This poll ends on <t.date.poll_end_date.long_format/>.</t><br/>
+						<n.if.not.poll_show_results_before_end>
+							<then>
+								<t>Results will be shown only after poll has ended.</t><br/>
+							</then>
+						</n.if.not.poll_show_results_before_end>
+					</else>
+				</n.if.poll_has_ended>
+			</then>
+		</n.if.poll_has_end_date>
+		<n.if.not.poll_show_results_before_vote>
+			<then>
+				<t>You have to vote before you can see the results.</t><br/>
+			</then>
+		</n.if.not.poll_show_results_before_vote>
+	</div>
+</macro>
+
+<macro name="explain_poll_max_choices">
+	<n.if.not.equal value1="[n.poll_max_choices/]" value2="1">
+		<then>
+			<div class="weak-color" style="font-size:80%">
+				<t>You can select up to <t.number.poll_max_choices/> options.</t>
+			</div>
+		</then>
+	</n.if.not.equal>
+</macro>
+
+<macro name="vote_input_type" requires="node">
+	<n.if.equal value1="[n.poll_max_choices/]" value2="1">
+		<then>radio</then>
+		<else>checkbox</else>
+	</n.if.equal>
+</macro>
+
+<macro name="poll_vote_input_field" parameters="field_id, index" requires="node">
+	<n.comment.>
+		Creates an input field for a poll option. There are some possibilities
+		depending on the poll configuration and who is the visitor.
+	</n.comment.>
+	<n.if.poll_visitor_has_voted.index>
+		<then>
+			<n.if.poll_visitor_can_vote>
+				<then>
+					<input id="[n.field_id/]" type="[n.vote_input_type/]" name="vote" value="[n.index/]" checked="true" />
+				</then>
+				<else>
+					<input id="[n.field_id/]" type="[n.vote_input_type/]" name="vote" value="[n.index/]" checked="true" disabled="true" />
+				</else>
+			</n.if.poll_visitor_can_vote>
+		</then>
+		<else>
+			<n.if.poll_visitor_can_vote>
+				<then>
+					<input id="[n.field_id/]" type="[n.vote_input_type/]" name="vote" value="[n.index/]"/>
+				</then>
+				<else>
+					<input id="[n.field_id/]" type="[n.vote_input_type/]" name="vote" value="[n.index/]" disabled="true" />
+				</else>
+			</n.if.poll_visitor_can_vote>
+		</else>
+	</n.if.poll_visitor_has_voted.index>
+</macro>
+
+<override_macro name="javascript_library" requires="servlet">
+	<n.overridden/>
+	<n.comment.>
+		Javascript to validate and submit a poll vote.
+	</n.comment.>
+	<n.compress.>
+		Nabble.vote = function(nodeId,maxChoices) {
+			var vote_count = $('#poll_form'+nodeId+' input:checked').length;
+			<![CDATA[
+			if (vote_count > maxChoices) {
+			]]>
+				alert('<t>Please select no more than <t.number>'+maxChoices+'</t.number> options.</t>');
+				return false;
+			} else if (vote_count == 0) {
+				alert('<t>Please select at least one option.</t>');
+				return false;
+			}
+			var params = $('#poll_form'+nodeId).serialize();
+			var call = '/template/NamlServlet.jtp?macro=vote&'+params;
+			var call2 = '/template/NamlServlet.jtp?macro=poll_js&poll_node_id='+nodeId;
+			$.getScript(call, function() {
+				notice('<t>Your vote has been submitted.</t>', 5000, 2000);
+				$.getScript(call2);
+			});
+			return false;
+		};
+	</n.compress.>
+</override_macro>
+
+<macro name="vote">
+	<n.get_node_from_id. node_id="[n.get_parameter name='node'/]">
+		<n.poll_vote votes="[n.get_parameter_values name='vote'/]" />
+	</n.get_node_from_id.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/workgroup/Assignment.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,123 @@
+package nabble.modules.workgroup;
+
+import java.io.Serializable;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import fschmidt.db.DbDatabase;
+import fschmidt.db.DbNull;
+import fschmidt.db.DbRecord;
+import fschmidt.db.LongKey;
+import nabble.model.Db;
+import nabble.model.ExtensionFactory;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+
+
+final class Assignment {
+
+	/** Must be public because XML backups use reflection to create extension objects. */
+	public static final class ExportData implements Serializable {
+		String assigneeEmail;
+		String assigneeName;
+		Integer priority;
+	}
+
+	private static final ExtensionFactory<Node,Assignment> FACTORY = new ExtensionFactory<Node,Assignment>() {
+
+		public String getName() {
+			return WorkgroupModule.INSTANCE.getName();
+		}
+
+		public Class<Assignment> extensionClass() {
+			return Assignment.class;
+		}
+
+		public Assignment construct(Node node) {
+			return null;
+		}
+
+		public Assignment construct(Node node,ResultSet rs)
+			throws SQLException
+		{
+			long assignedUserId = rs.getLong("assigned_user");
+			if( rs.wasNull() )
+				return null;
+			User user = node.getSite().getUser(assignedUserId);
+			int priority = rs.getInt("assigned_priority");
+			return new Assignment(node,user,priority);
+		}
+
+		public Serializable getExportData(Node node) {
+			Assignment assignment = node.getExtension(this);
+			if( assignment == null )
+				return null;
+			ExportData data = new ExportData();
+			data.assigneeEmail = assignment.assignee.getEmail();
+			data.assigneeName = assignment.assignee.getName();
+			data.priority = assignment.priority;
+			return data;
+		}
+
+		public void saveExportData(Node node,Serializable s) {
+			ExportData data = (ExportData)s; 
+			User user = node.getSite().getOrCreateUser(data.assigneeEmail,data.assigneeName);
+			Assignment assignment = new Assignment(node,user,data.priority);
+			assignment.save();
+		}
+	};
+
+	static {
+		ModelHome.addNodeExtensionFactory(FACTORY);
+	}
+
+	static void init() {}
+
+	public static Assignment of(Node node) {
+		return node.getExtension(FACTORY);
+	}
+
+
+
+	private Node node;
+	final User assignee;
+	final int priority;
+
+	Assignment(Node node,User assignee,int priority) {
+		if( priority < 1 || priority > 5 || assignee==null )
+			throw new IllegalArgumentException();
+		this.node = node;
+		this.assignee = assignee;
+		this.priority = priority;
+	}
+
+	void save() {
+		DbDatabase db = node.getSite().getDb();
+		if( !db.isInTransaction() ) {
+			db.beginTransaction();
+			try {
+				node = node.getGoodCopy();
+				save();
+				db.commitTransaction();
+			} finally {
+				db.endTransaction();
+			}
+			return;
+		}
+		if( !assignee.getSite().equals(node.getSite()) )
+			throw new RuntimeException();
+		DbRecord<LongKey,?> record = node.getDbRecord();
+		record.fields().put("assigned_user", assignee.getId());
+		record.fields().put("assigned_priority", priority);
+		node.update();
+	}
+
+	static void unassign(Node node) {
+		if( Assignment.of(node) != null ) {
+			DbRecord<LongKey,?> record = node.getDbRecord();
+			record.fields().put("assigned_user",DbNull.INTEGER);
+			record.fields().put("assigned_priority",DbNull.INTEGER);
+			node.update();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/workgroup/NodeNamespaceExt.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,244 @@
+package nabble.modules.workgroup;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.NoSuchElementException;
+import javax.servlet.ServletException;
+import fschmidt.util.java.Filter;
+import fschmidt.util.java.Stack;
+import fschmidt.util.java.ArrayStack;
+import nabble.model.Db;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.NodeIterator;
+import nabble.model.Site;
+import nabble.model.DbParamSetter;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.NamespaceExtension;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.view.web.template.NodeList;
+import nabble.view.web.template.NodeNamespace;
+import nabble.view.web.template.ServletNamespace;
+import nabble.view.web.template.UserNamespace;
+
+
+@NamespaceExtension (
+	name = "workgroup_node",
+	target = NodeNamespace.class
+)
+public final class NodeNamespaceExt {
+	private final NodeNamespace ns;
+	private final Node node;
+	private final Site site;
+
+	public NodeNamespaceExt(NodeNamespace ns) {
+		this.ns = ns;
+		this.node = ns.node();
+		this.site = node.getSite();
+	}
+
+	private static Integer priority(Interpreter interp) {
+		String priorityS = interp.getArgString("priority");
+		return priorityS == null ? null : Integer.valueOf(priorityS);
+	}
+
+	private User assignee(Interpreter interp) {
+		String assigneeS = interp.getArgString("assignee");
+		return assigneeS == null || "0".equals(assigneeS) ? null : node.getSite().getUser(Long.valueOf(assigneeS));
+	}
+
+	public static final CommandSpec assignment_filter = new CommandSpec.Builder()
+		.parameters("assignee","priority")
+		.build()
+	;
+
+	@Command public void assignment_filter(IPrintWriter out,Interpreter interp) {
+		Integer priority = priority(interp);
+		out.print( ModelHome.assignmentCondition(assignee(interp), priority == null? null : priority == 0? 5 : priority) );
+	}
+
+	@Command public void is_assigned(IPrintWriter out,Interpreter interp) {
+		out.print( Assignment.of(node) != null );
+	}
+
+	@Command public void priority(IPrintWriter out,Interpreter interp) {
+		out.print( Assignment.of(node).priority );
+	}
+
+	public static final CommandSpec assignee = CommandSpec.DO;
+
+	@Command public void assignee(IPrintWriter out,ScopedInterpreter<UserNamespace> interp) {
+		out.print( interp.getArg(new UserNamespace(Assignment.of(node).assignee),"do") );
+	}
+
+	public static final CommandSpec unassign = CommandSpec.NO_OUTPUT;
+
+	@Command public void unassign(IPrintWriter out,Interpreter interp) {
+		Assignment.unassign(node);
+	}
+
+	public static final CommandSpec assign = CommandSpec.NO_OUTPUT()
+		.parameters("assignee","priority")
+		.build()
+	;
+
+	@Command public void assign(IPrintWriter out,Interpreter interp) {
+		UserNamespace userNs = interp.getArgAsNamespace(UserNamespace.class,"assignee");
+		User assignee = userNs.user();
+		int priority = interp.getArgAsInt("priority");
+		new Assignment(node,assignee,priority).save();
+	}
+
+	public static final CommandSpec children_list_by_priority = CommandSpec.DO()
+		.parameters("length")
+		.optionalParameters("start", "filter")
+		.build()
+	;
+
+	@Command public void children_list_by_priority(IPrintWriter out,ScopedInterpreter<NodeList> interp) {
+		int start = NodeNamespace.getLoopStart(interp);
+		int length = NodeNamespace.getLoopLength(interp);
+		String filter = interp.getArgString("filter");
+		NodeIterator<? extends Node> nodeIter = getChildrenByPriority(filter);
+		NodeList.children(out,interp,node,nodeIter,start,length);
+	}
+
+	public static final CommandSpec topics_list_by_priority = CommandSpec.DO()
+		.parameters("length")
+		.optionalParameters("start","filter")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void topics_list_by_priority(IPrintWriter out,ScopedInterpreter<NodeList> interp)
+		throws ServletException
+	{
+		int start = NodeNamespace.getLoopStart(interp);
+		int length = NodeNamespace.getLoopLength(interp);
+		String filter = interp.getArgString("filter");
+		NodeIterator<Node> nodeIter = getTopicsByPriority(filter,ns.filter(interp));
+		NodeList.topics(out,interp,node,nodeIter,start,length);
+	}
+
+
+	private static final class PriorityNode implements Comparable<PriorityNode> {
+		final long nodeId;
+		final int priority;
+		final long lastNodeDate;
+
+		PriorityNode(ResultSet rs) throws SQLException {
+			nodeId = rs.getLong("node_id");
+			priority = rs.getInt("assigned_priority");
+			lastNodeDate = rs.getTimestamp("last_node_date").getTime();
+		}
+
+		public int compareTo(PriorityNode pn) {
+			int diff = priority - pn.priority;
+			return diff != 0 ? diff
+				: lastNodeDate < pn.lastNodeDate ? -1
+				: lastNodeDate > pn.lastNodeDate ? 1
+				: 0
+			;
+		}
+	}
+
+	private static class NodePriorityIterator extends NodeIterator<Node> {
+		private final Site site;
+		private final List<PriorityNode> list;
+		private int next = 0;
+
+		NodePriorityIterator(Site site,List<PriorityNode> list) {
+			this.site = site;
+			this.list = list;
+		}
+
+		public void skip(int n) {
+			next += n;
+		}
+
+		public boolean hasNext() {
+			return next < list.size();
+		}
+
+		public Node next() throws NoSuchElementException {
+			return site.getNode(list.get(next++).nodeId);
+		}
+
+		public void close() {}
+	}
+
+	private NodeIterator<Node> getTopicsByPriority(String cnd,Filter<Node> filter) {
+		if( node.getKind() != Node.Kind.APP )
+			return NodeIterator.empty();
+		List<PriorityNode> list = new ArrayList<PriorityNode>();
+		try {
+			Connection con = site.getDb().getConnection();
+			PreparedStatement pstmtTopics = con.prepareStatement(
+				"select node_id, assigned_priority, last_node_date from node where parent_id = ? and is_app is null" + (cnd==null ? "" : " and " + cnd)
+			);
+			PreparedStatement pstmtApps = con.prepareStatement(
+				"select * from node where parent_id = ? and is_app"
+			);
+			Stack<Long> stack = new ArrayStack<Long>();
+			stack.push(node.getId());
+			do {
+				long nodeId = stack.pop();
+				pstmtTopics.setLong(1,nodeId);
+				ResultSet rs = pstmtTopics.executeQuery();
+				while( rs.next() ) {
+					list.add( new PriorityNode(rs) );
+				}
+				rs.close();
+				pstmtApps.setLong(1,nodeId);
+				rs = pstmtApps.executeQuery();
+				while( rs.next() ) {
+					Node node = site.getNode(rs);
+					if( filter.ok(node) )
+						stack.push( node.getId() );
+				}
+				rs.close();
+			} while( !stack.isEmpty() );
+			con.close();
+		} catch(SQLException e) {
+			throw new RuntimeException(e);
+		}
+		Collections.sort(list);
+		return new NodePriorityIterator(site,list);
+	}
+
+
+	public NodeIterator<? extends Node> getChildrenByPriority(String cnd) {
+		if( !node.isInDb() ) {
+/*
+			List<Node> children = dummyChildMap.get(this);
+			if( children == null )
+				return NodeIterator.empty();
+			return NodeIterator.nodeIterator( children.iterator() );
+*/
+			throw new RuntimeException();
+		}
+		return site.getNodeIterator(
+				"select *"
+				+" from node"
+				+" where parent_id = ?"
+				+(cnd==null?"":"and " + cnd)
+				+" order by assigned_priority, last_node_date, node_id"
+			,
+				new DbParamSetter() {
+					public void setParams(PreparedStatement stmt) throws SQLException {
+						stmt.setLong(1, node.getId());
+					}
+				}
+		);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/workgroup/WorkgroupModule.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,47 @@
+package nabble.modules.workgroup;
+
+import fschmidt.util.java.FutureValue;
+import nabble.naml.compiler.Module;
+import nabble.naml.compiler.Source;
+import nabble.modules.ModuleManager;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
+
+public enum WorkgroupModule implements Module {
+	INSTANCE;
+
+	private static final Iterable<Class> extensions = Arrays.<Class>asList(
+		NodeNamespaceExt.class
+	);
+
+	private final FutureValue<Collection<Source>> sources = new FutureValue<Collection<Source>>() {
+		protected Collection<Source> compute() {
+			return ModuleManager.loadSource(WorkgroupModule.this);
+		}
+	};
+
+	public String getName() {
+		return "workgroup";
+	}
+
+	public Iterable<Class> getExtensions() {
+		return extensions;
+	}
+
+	public Collection<Source> getSources() {
+		return sources.get();
+	}
+
+	public Set<String> getDependencies() {
+		return Collections.emptySet();
+	}
+
+	static {
+		Assignment.init();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/modules/workgroup/workgroup.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,704 @@
+<override_macro name="view_app_canonical_path" requires="http_request">
+	<n.get_node_from_parameter.path
+		template="[n.get_parameter name='macro'/]"
+		priority="[n.get_parameter name='priority'/]"
+		assignee="[n.get_parameter name='assignee'/]"
+		index_record="[n.get_parameter name='index_record'/]"
+		date="[n.get_parameter name='date'/]"
+	/>
+</override_macro>
+
+<override_macro name="url" parameters="template,priority,assignee,index_record,date" requires="node">
+	<n.remove_spaces.>
+		<n.base_url/>
+		<n.path
+			template="[n.template/]"
+			priority="[n.priority/]"
+			assignee="[n.assignee/]"
+			index_record="[n.index_record/]"
+			date="[n.date/]"
+		/>
+	</n.remove_spaces.>
+</override_macro>
+
+<override_macro name="app_url" parameters="template,priority,assignee,index_record,date" requires="node">
+	<n.base_url/><n.app_path
+		template="[n.template/]"
+		priority="[n.priority/]"
+		assignee="[n.assignee/]"
+		index_record="[n.index_record/]"
+		date="[n.date/]"
+	/>
+</override_macro>
+
+<override_macro name="path" parameters="template,priority,assignee,index_record,date" requires="node">
+	<n.if.is_app>
+		<then>
+			<n.app_path
+				template="[n.template/]"
+				priority="[n.priority/]"
+				assignee="[n.assignee/]"
+				index_record="[n.index_record/]"
+				date="[n.date/]"
+			/>
+		</then>
+		<else>
+			<n.post_path />
+		</else>
+	</n.if.is_app>
+</override_macro>
+
+<override_macro name="app_paged_url" requires="paging_page,node_page,servlet">
+	<n.page_node.path
+		template="[n.app_template/]"
+		priority="[n.app_priority/]"
+		assignee="[n.app_assignee/]"
+		date="[n.app_date/]"
+		index_record="[n.page_row/]"
+	/>
+</override_macro>
+
+<override_macro name="app_path" parameters="template,priority,assignee,index_record,date" requires="node">
+	<n.encode_url.remove_spaces.>
+		<n.set_var. name="index_record">
+			<n.to_null_if. equals="0">
+				<n.index_record/>
+			</n.to_null_if.>
+		</n.set_var.>
+		<n.set_var. name="template">
+			<n.to_null_if. equals="[n.app_default_template/]">
+				<n.template/>
+			</n.to_null_if.>
+		</n.set_var.>
+		<n.if.not.is_null.var name="template">
+			<then>
+				<n.if.not.starts_with prefix="view_" text="[n.var name='template'/]">
+					<then>
+						<n.throw_runtime_exception.>
+							template = <n.var name='template'/>
+						</n.throw_runtime_exception.>
+					</then>
+				</n.if.not.starts_with>
+				<n.set_var. name="template">
+					<n.substring text="[n.var name='template'/]" begin="5" />
+				</n.set_var.>
+			</then>
+		</n.if.not.is_null.var>
+		/
+		<n.if>
+			<condition>
+				<n.not.all_true.>
+					<n.is_root/>
+					<n.is_null.var name='template'/>
+					<n.is_null.date/>
+					<n.is_null.priority/>
+					<n.is_null.assignee/>
+					<n.is_null.var name='index_record'/>
+				</n.not.all_true.>
+			</condition>
+			<then>
+				<n.url_encoded_subject/>
+				-f<n.id/>
+				<n.hide_null.prepend. prefix="p"><n.priority/></n.hide_null.prepend.>
+				<n.hide_null.prepend. prefix="a"><n.assignee/></n.hide_null.prepend.>
+				<n.hide_null.prepend. prefix="i"><n.var name="index_record"/></n.hide_null.prepend.>
+				<n.hide_null.prepend. prefix="d"><n.date/></n.hide_null.prepend.>
+				<n.hide_null.prepend. prefix="."><n.var name="template"/></n.hide_null.prepend.>
+				.html
+			</then>
+		</n.if>
+	</n.encode_url.remove_spaces.>
+</override_macro>
+
+<override_macro name="app_path_by_date" parameters="date" requires="node">
+	<n.app_path
+		template="[n.app_template/]"
+		index_record="[n.app_index_record/]"
+		priority="[n.app_priority/]"
+		assignee="[n.app_assignee/]"
+		date="[n.date/]"
+	/>
+</override_macro>
+
+<macro name="app_priority" requires="servlet">
+	<n.get_parameter name="priority"/>
+</macro>
+
+<macro name="app_assignee" requires="servlet">
+	<n.get_parameter name="assignee"/>
+</macro>
+
+<override_macro name="app_title_ending" requires="node">
+	<n.overridden/>
+	<n.if.not.is_null.app_priority>
+		<then>| <t>Filter: priority <t.priority.app_priority/></t></then>
+	</n.if.not.is_null.app_priority>
+	<n.if.not.is_null.app_assignee>
+		<then>| <t>Filter: assignee <t.author.app_assignee/></t></then>
+	</n.if.not.is_null.app_assignee>
+</override_macro>
+
+<override_macro name="compile_all">
+	<n.overridden />
+	<n.compile macro="can_be_assigned_to" namespaces="[n.standard_classes/],[n.user_namespace_class/]" />
+</override_macro>
+
+<override_macro name="save_post" requires="node_editor,servlet">
+	<n.overridden />
+	<n.save_assignment />
+</override_macro>
+
+<override_macro name="edit_post_form">
+	<n.overridden />
+	<n.page_node.assignment_controls/>
+</override_macro>
+
+<override_macro name="reply_form">
+	<n.overridden />
+	<n.page_node.>
+		<n.assignment_controls>
+			<guessed_assignee>
+				<n.if>
+					<condition.both>
+						<condition1.not.page_node.is_app/>
+						<condition2.visitor.equals.page_node.topic_node.assignee/>
+					</condition.both>
+					<then.page_node.owner.id/>
+					<else.null/>
+				</n.if>
+			</guessed_assignee>
+		</n.assignment_controls>
+	</n.page_node.>
+</override_macro>
+
+<override_macro name="instant_emails" requires="instant_mail,node_page">
+	<n.if.page_node.has_topic_node>
+		<then.if.page_node.topic_node.is_assigned>
+			<then>
+				<n.remove_from_instant_mail.page_node.topic_assignee/>
+				<n.if.page_node.topic_assignee.should_get_instant_mail>
+					<then.send_assign_email/>
+				</n.if.page_node.topic_assignee.should_get_instant_mail>
+			</then>
+		</then.if.page_node.topic_node.is_assigned>
+	</n.if.page_node.has_topic_node>
+	<n.overridden />
+</override_macro>
+
+<override_macro name="all_permissions_list">
+	<n.overridden />
+	<n.assignment_permission/>,
+</override_macro>
+
+<override_macro name="permission_rows">
+	<n.overridden />
+	<n.permission_row
+		permission = "[n.assignment_permission/]"
+		description="[t]Who can be assigned topics (in workgroups only)[/t]"
+		anyone_cell = ""
+		authors_cell = ""
+	/>
+</override_macro>
+
+<override_macro name="children_list" parameters="start,length,filter,sort" dot_parameter="do" requires="node">
+	<n.if.equal value1="[n.sort/]" value2="priority">
+		<then.children_list_by_priority start="[n.start/]" length="[n.length/]" filter="[n.filter/]" do="[n.do/]" />
+		<else.overridden start="[n.start/]" length="[n.length/]" filter="[n.filter/]" sort="[n.sort/]" do="[n.do/]" />
+	</n.if.equal>
+</override_macro>
+
+<override_macro name="topics_list" parameters="start,length,filter,sort" dot_parameter="do" requires="node">
+	<n.if.equal value1="[n.sort/]" value2="priority">
+		<then.topics_list_by_priority start="[n.start/]" length="[n.length/]" filter="[n.filter/]" do="[n.do/]" />
+		<else.overridden start="[n.start/]" length="[n.length/]" filter="[n.filter/]" sort="[n.sort/]" do="[n.do/]" />
+	</n.if.equal>
+</override_macro>
+
+<override_macro name="calc_app_topic_filter" requires="node_page,servlet">
+	<n.if.app_is_by_priority>
+		<then>
+			<n.compress.>
+				<n.separate>
+					<text1>
+						<n.page_node.assignment_filter assignee="[n.app_assignee/]" priority="[n.app_priority/]" />
+					</text1>
+					<separator>
+						and
+					</separator>
+					<text2>
+						<n.hide_null.overridden />
+					</text2>
+				</n.separate>
+			</n.compress.>
+		</then>
+		<else.overridden />
+	</n.if.app_is_by_priority>
+</override_macro>
+
+
+<override_macro name="call_view_mixed">
+	<n.if.get_node_from_parameter.is_workgroup>
+		<then.view_mixed_workgroup />
+		<else.overridden />
+	</n.if.get_node_from_parameter.is_workgroup>
+</override_macro>
+
+<subroutine name="view_mixed_workgroup" requires="basic,nabble,servlet">
+	<n.apply_workgroup_app_namespace.view_mixed_page />
+</subroutine>
+
+<macro name="mixed_table_columns" requires="workgroup_app_namespace">
+	<n.pin_column/>
+	<n.mixed_topics_column/>
+	<n.replies_column/>
+	<n.last_post_column white_space="nowrap"/>
+	<n.priority_column clickable="false"/>
+	<n.assignee_column clickable="false"/>
+</macro>
+
+<macro name="mixed_table_columns" requires="workgroup_narrow_app_namespace">
+	<n.pin_column/>
+	<n.mixed_topics_column/>
+	<n.replies_column/>
+	<n.last_post_column white_space="nowrap"/>
+	<n.priority_column clickable="false"/>
+	<n.assignee_column clickable="false"/>
+</macro>
+
+
+<override_macro name="call_view_standard">
+	<n.if.get_node_from_parameter.is_workgroup>
+		<then.view_standard_workgroup />
+		<else.overridden />
+	</n.if.get_node_from_parameter.is_workgroup>
+</override_macro>
+
+<subroutine name="view_standard_workgroup" requires="basic,nabble,servlet">
+	<n.apply_workgroup_app_namespace.view_standard_page />
+</subroutine>
+
+<macro name="standard_table_columns" requires="workgroup_app_namespace">
+	<n.pin_column/>
+	<n.topics_column title="[n.standard_topics_column_title/]" count="[n.page_node.child_count/]"/>
+	<n.replies_column/>
+	<n.last_post_column white_space="nowrap"/>
+	<n.priority_column/>
+	<n.assignee_column/>
+</macro>
+
+<macro name="standard_table_columns" requires="workgroup_narrow_app_namespace">
+	<n.pin_column/>
+	<n.topics_summary_column title="[n.standard_topics_column_title/]" count="[n.page_node.child_count/]" width="50%"/>
+	<n.last_post_column white_space="nowrap" width="50%"/>
+	<n.priority_column/>
+	<n.assignee_column/>
+</macro>
+
+
+<override_subroutine name="view_topics" requires="basic,nabble,servlet">
+	<n.if.get_node_from_parameter.is_workgroup>
+		<then.view_topics_workgroup />
+		<else.overridden />
+	</n.if.get_node_from_parameter.is_workgroup>
+</override_subroutine>
+
+<subroutine name="view_topics_workgroup" requires="basic,nabble,servlet">
+	<n.apply_workgroup_app_namespace.view_topics_page />
+</subroutine>
+
+<macro name="topics_table_columns" requires="workgroup_app_namespace">
+	<n.pin_column/>
+	<n.topics_column count="[n.app_topic_count/]"/>
+	<n.replies_column/>
+	<n.last_post_column white_space="nowrap"/>
+	<n.priority_column/>
+	<n.assignee_column/>
+	<n.subapp_column/>
+</macro>
+
+<macro name="topics_table_columns" requires="workgroup_narrow_app_namespace">
+	<n.pin_column/>
+	<n.topics_summary_column count="[n.app_topic_count/]" width="40%"/>
+	<n.last_post_column white_space="nowrap" width="40%"/>
+	<n.priority_column/>
+	<n.assignee_column/>
+</macro>
+
+<override_macro name="topics_table_sort">
+	<n.if.app_is_by_priority>
+		<then>priority</then>
+		<else.overridden />
+	</n.if.app_is_by_priority>
+</override_macro>
+
+
+
+
+<macro name="app_is_by_priority" requires="node_page,servlet">
+	<n.cache. var="app_is_by_priority">
+		<n.either>
+			<condition1>
+				<n.not.is_null.app_priority/>
+			</condition1>
+			<condition2>
+				<n.both>
+					<condition1>
+						<n.not.is_null.app_assignee/>
+					</condition1>
+					<condition2>
+						<n.if.not.equal value1="[n.app_assignee/]" value2="0" >
+							<then>
+								<n.check_user.app_assignee/>
+								<n.true/>
+							</then>
+							<else>
+								<n.false/>
+							</else>
+						</n.if.not.equal>
+					</condition2>
+				</n.both>
+			</condition2>
+		</n.either>
+	</n.cache.>
+</macro>
+
+<override_macro name="topic_controls_left" requires="forum_topic_namespace">
+	<n.overridden/>
+	<n.workgroup_assignment_status />
+</override_macro>
+
+<macro name="workgroup_assignment_status">
+	<n.if.both condition1="[n.page_node.app_or_root.is_workgroup/]" condition2="[n.page_node.is_assigned/]">
+		<then>
+			<div class="weak-color light-bg-color rounded" style="padding:.5em;margin:.5em 0">
+				Assigned to <n.page_node.assignee.user_link/> at priority
+				<div class="priority-[n.page_node.priority/] priority">&nbsp;<n.page_node.priority/>&nbsp;</div> (<n.page_node.priority_name/>)
+			</div>
+		</then>
+	</n.if.both>
+</macro>
+
+<override_macro name="save_post_by_email" requires="post_by_email" unindent="true">
+	<n.overridden />
+	<n.if.posted_node.topic_or_app.is_assigned>
+		<then.if.posted_node.topic_node.assignee.equals.posted_node.owner>
+			<then.if.replied_to_node.owner.can_be_assigned_to.posted_node.topic_node>
+				<then.posted_node.topic_node.assign assignee="[n.replied_to_node.owner/]" priority="[n.posted_node.topic_node.priority/]" />
+			</then.if.replied_to_node.owner.can_be_assigned_to.posted_node.topic_node>
+		</then.if.posted_node.topic_node.assignee.equals.posted_node.owner>
+	</n.if.posted_node.topic_or_app.is_assigned>
+</override_macro>
+
+
+// from permissions
+
+<macro name="assignment_permission">
+	Assignment
+</macro>
+
+<macro name="can_be_assigned_to" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.both>
+		<condition1.not.local_user.is_anonymous/>
+		<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.assignment_permission/]" />
+	</n.both>
+</macro>
+
+<macro name="assignee_list" requires="node" dot_parameter="do">
+	<n.app_or_root.users_with_permission. permission="[n.assignment_permission/]">
+		<n.sort_by_name/>
+		<n.do/>
+	</n.app_or_root.users_with_permission.>
+</macro>
+
+<macro name="is_workgroup" requires="node">
+	<n.has_groups_with_permission.assignment_permission/>
+</macro>
+
+
+<macro name="priority_column" parameters="clickable">
+	<n.table_column>
+		<head>
+			<td class="medium-border-color priority-column nowrap">
+				<n.if.equal value1="[n.default. to='true'][n.clickable/][/n.default.]" value2="true">
+					<then><n.priority_dropdown/></then>
+					<else><t>Priority</t></else>
+				</n.if.equal>
+			</td>
+		</head>
+		<body>
+			<td class="medium-border-color" align="center"><n.current_node.show_priority/></td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="assignee_column" parameters="width,title,clickable">
+	<n.table_column>
+		<head>
+			<td class="medium-border-color assignee-column nowrap" style="[n.width_style.width/]">
+				<n.if.equal value1="[n.default. to='true'][n.clickable/][/n.default.]" value2="true">
+					<then><n.assignee_dropdown/></then>
+					<else><t>Assignee</t></else>
+				</n.if.equal>
+			</td>
+		</head>
+		<body>
+			<td class="medium-border-color nowrap"><n.current_node.show_assignee/></td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="priority_dropdown">
+	<n.if.not.is_null.app_priority>
+		<then><img src="/images/check.png" width="11" height="11"/></then>
+	</n.if.not.is_null.app_priority>
+	<n.dropdown. id="prioritydropdown" text="[t]Priority[/t]" title="[t]Click to filter[/t]">
+		<n.no_priority_filter_option/>
+		<n.menu_separator/>
+		<n.priority_option priority="1" label="[t]Highest[/t]"/>
+		<n.priority_option priority="2" label="[t]High[/t]"/>
+		<n.priority_option priority="3" label="[t]Normal[/t]"/>
+		<n.priority_option priority="4" label="[t]Low[/t]"/>
+		<n.priority_option priority="5" label="[t]Lowest[/t]"/>
+	</n.dropdown.>
+</macro>
+
+<macro name="assignee_dropdown">
+	<n.if.not.is_null.app_assignee>
+		<then><img src="/images/check.png" width="11" height="11"/></then>
+	</n.if.not.is_null.app_assignee>
+	<n.dropdown. id="assigneedropdown" text="[t]Assignee[/t]" title="[t]Click to filter[/t]">
+		<n.no_assignee_filter_option/>
+		<n.menu_separator/>
+		<n.page_node.assignee_list.loop.>
+			<n.assignee_option assignee="[n.current_user.id/]" label="[n.current_user.name/]"/>
+		</n.page_node.assignee_list.loop.>
+	</n.dropdown.>
+</macro>
+
+<macro name="priority_option" parameters="priority,label">
+	<n.set_var. name="p-url"><n.app_url_by_priority priority="[n.priority/]"/></n.set_var.>
+	<n.set_var. name="style">
+		<n.compress.>
+			padding-left:14px;
+			padding-right:11px;
+			<n.if.equal value1="[n.priority/]" value2="[n.app_priority/]">
+				<then>
+					font-weight:bold;
+					background-image:url(/images/check.png);
+					background-repeat:no-repeat;
+					background-position:left center;
+				</then>
+				<else>font-weight:normal</else>
+			</n.if.equal>
+		</n.compress.>
+	</n.set_var.>
+	var text = '<a href="[n.var name='p-url'/]" style="[n.hide_null.var name='style'/]"><div class="priority-[n.priority/] priority">&nbsp;<n.priority/>&nbsp;</div> <n.javascript_string_encode.label/></a>';
+	dropdown.add('priority-<n.priority/>', text);
+</macro>
+
+<macro name="assignee_option" parameters="assignee,label">
+	<n.set_var. name="a-url"><n.app_url_by_assignee assignee="[n.assignee/]"/></n.set_var.>
+	<n.set_var. name="style">
+		<n.compress.>
+			padding-left:14px;
+			padding-right:11px;
+			<n.if.equal value1="[n.assignee/]" value2="[n.app_assignee/]">
+				<then>
+					background-image:url(/images/check.png);
+					background-repeat:no-repeat;
+					background-position:left center;
+				</then>
+				<else>font-weight:normal</else>
+			</n.if.equal>
+		</n.compress.>
+	</n.set_var.>
+	var text = '<a href="[n.var name='a-url'/]" style="[n.hide_null.var name='style'/]"><n.javascript_string_encode.label/></a>';
+	dropdown.add('assignee-<n.assignee/>', text);
+</macro>
+
+<macro name="no_priority_filter_option">
+	<n.set_var. name="p-url"><n.app_url_by_priority/></n.set_var.>
+	var text = '<a href="[n.var name='p-url'/]" style="padding:.3em 14px;font-weight:normal"><t>No Filter</t></a>';
+	dropdown.add('no-priority-filter', text);
+</macro>
+
+<macro name="no_assignee_filter_option">
+	<n.set_var. name="a-url"><n.app_url_by_assignee/></n.set_var.>
+	var text = '<a href="[n.var name='a-url'/]" style="padding:.3em 14px;font-weight:normal"><t>No Filter</t></a>';
+	dropdown.add('no-assignee-filter', text);
+</macro>
+
+<macro name="app_url_by_priority" parameters="priority">
+	<n.page_node.url
+		template="[n.app_template/]"
+		priority="[n.priority/]"
+		assignee="[n.app_assignee/]"
+		date="[n.app_date/]"
+		index_record="0"
+	/>
+</macro>
+
+<macro name="app_url_by_assignee" parameters="assignee">
+	<n.page_node.url
+		template="[n.app_template/]"
+		priority="[n.app_priority/]"
+		assignee="[n.assignee/]"
+		date="[n.app_date/]"
+		index_record="0"
+	/>
+</macro>
+
+<macro name="show_priority" requires="node">
+	<n.if.is_assigned>
+		<then>
+			<div class="priority-[n.priority/] priority">&nbsp;<n.priority/>&nbsp;</div>
+		</then>
+	</n.if.is_assigned>
+</macro>
+
+<macro name="show_assignee" requires="node">
+	<n.if.is_assigned>
+		<then.assignee.name />
+	</n.if.is_assigned>
+</macro>
+
+<macro name="assignment_controls" parameters="guessed_assignee" requires="node,servlet">
+	<n.set_local_node.topic_or_app/>
+	<n.block.>
+		<n.if.visitor.can_be_assigned_to.local_node>
+			<then.local_node.show_assignment_controls guessed_assignee="[n.guessed_assignee/]" />
+		</n.if.visitor.can_be_assigned_to.local_node>
+	</n.block.>
+</macro>
+
+<macro name="show_assignment_controls" parameters="guessed_assignee" requires="node,servlet">
+	<n.set_local_node.this_node/>
+	<n.block.>
+		<n.if.not.is_submitted_form>
+			<then.if.local_node.is_assigned>
+				<then>
+					<n.assignment_field.set_value value="true" />
+					<n.assignment_priority_field.set_value.local_node.priority />
+					<n.set_var name="currentAssignee" value="[n.local_node.assignee.id/]" />
+					<n.assignment_assignee_field.set_value.>
+						<n.if.not.is_null.guessed_assignee>
+							<then.guessed_assignee/>
+							<else.local_node.assignee.id/>
+						</n.if.not.is_null.guessed_assignee>
+					</n.assignment_assignee_field.set_value.>
+				</then>
+				<else>
+					<n.assignment_field.set_value value="false" />
+					<n.assignment_priority_field.set_value value="3" />
+				</else>
+			</then.if.local_node.is_assigned>
+		</n.if.not.is_submitted_form>
+		<script type="text/javascript">
+			function updateFields() {
+				var $controls = $('#assignee-1,#assignee-2, #assignee-3');
+				if ($('#assignment').val() == 'true')
+					$controls.show();
+				else
+					$controls.hide();
+			};
+
+			$(document).ready(updateFields);
+		</script>
+		<div class="field-box light-border-color">
+			<div class="second-font field-title"><t>Assignment</t></div>
+			<table style="border-collapse:collapse">
+				<tr>
+					<td class="nowrap">
+						<n.assignment_field.select. onchange="updateFields()">
+							<n.select_option. value="false" selectedValue="[n.assignment_field.value/]"><t>Unassigned</t></n.select_option.>
+							<n.select_option. value="true" selectedValue="[n.assignment_field.value/]"><t>Assign</t></n.select_option.>
+						</n.assignment_field.select.>
+					</td>
+					<td id="assignee-1">
+						<n.assignment_assignee_field.select.>
+							<n.local_node.assignee_list.>
+								<n.sort_by_name />
+								<n.loop.>
+									<n.assignment_option
+										value = "[n.current_user.id/]"
+										selectedValue = "[n.assignment_assignee_field.value/]"
+										highlightValue = "[n.var name='currentAssignee'/]"
+										text = "[n.current_user.name/]"
+									/>
+								</n.loop.>
+							</n.local_node.assignee_list.>
+						</n.assignment_assignee_field.select.>
+					</td>
+					<td class="nowrap">
+						<span id="assignee-3" style="display:none">
+							<t>at priority</t>
+							<n.assignment_priority_field.select.>
+								<n.assignment_priority_option. value="1" selectedValue="[n.assignment_priority_field.value/]">1. <t>Highest</t></n.assignment_priority_option.>
+								<n.assignment_priority_option. value="2" selectedValue="[n.assignment_priority_field.value/]">2. <t>High</t></n.assignment_priority_option.>
+								<n.assignment_priority_option. value="3" selectedValue="[n.assignment_priority_field.value/]">3. <t>Normal</t></n.assignment_priority_option.>
+								<n.assignment_priority_option. value="4" selectedValue="[n.assignment_priority_field.value/]">4. <t>Low</t></n.assignment_priority_option.>
+								<n.assignment_priority_option. value="5" selectedValue="[n.assignment_priority_field.value/]">5. <t>Lowest</t></n.assignment_priority_option.>
+							</n.assignment_priority_field.select.>
+						</span>
+					</td>
+				</tr>
+			</table>
+		</div>
+	</n.block.>
+</macro>
+
+<macro name="assignment_priority_option" parameters="value,selectedValue" dot_parameter="text">
+	<n.assignment_option value="[n.value/]" selectedValue="[n.selectedValue/]" highlightValue="[n.selectedValue/]" text="[n.text/]" />
+</macro>
+
+<macro name="assignment_option" parameters="value,selectedValue,highlightValue" dot_parameter="text">
+	<n.select_option value="[n.value/]" selectedValue="[n.selectedValue/]" text="[n.text/]">
+		<class>
+			<n.if.equal value1="[n.value/]" value2="[n.highlightValue/]">
+				<then>highlight</then>
+				<else.null/>
+			</n.if.equal>
+		</class>
+	</n.select_option>
+</macro>
+
+<macro name="assignment_field" dot_parameter="do">
+	<n.field. name="assignment"><n.do/></n.field.>
+</macro>
+
+<macro name="assignment_priority_field" dot_parameter="do">
+	<n.field. name="priority"><n.do/></n.field.>
+</macro>
+
+<macro name="assignment_assignee_field" dot_parameter="do">
+	<n.field. name="assignee"><n.do/></n.field.>
+</macro>
+
+<macro name="save_assignment" requires="node_editor,servlet">
+	<n.set_local_node.edited_node.topic_or_app/>
+	<n.if.visitor.can_be_assigned_to.local_node>
+		<then.if.assignment_field.value>
+			<then.local_node.>
+				<n.assign>
+					<assignee><n.get_user_from_id user_id="[n.assignment_assignee_field.value/]" /></assignee>
+					<priority><n.assignment_priority_field.value/></priority>
+				</n.assign>
+			</then.local_node.>
+			<else.local_node.unassign/>
+		</then.if.assignment_field.value>
+	</n.if.visitor.can_be_assigned_to.local_node>
+</macro>
+
+<macro name="priority_name" requires="node">
+	<n.if.is_assigned>
+		<then.switch. value="[n.priority/]">
+			<n.case value="1" do="[t]Highest[/t]" />
+			<n.case value="2" do="[t]High[/t]" />
+			<n.case value="3" do="[t]Normal[/t]" />
+			<n.case value="4" do="[t]Low[/t]" />
+			<n.case value="5" do="[t]Lowest[/t]" />
+		</then.switch.>
+	</n.if.is_assigned>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/BlockWrapper.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,70 @@
+package nabble.naml.compiler;
+
+import java.io.StringWriter;
+import nabble.model.Init;
+
+
+class BlockWrapper {
+	private static final int blockWriterLimit = Init.get("blockWriterLimit",10 * 1024 * 1024);
+
+	private final Chunk block;
+	private final RunState runState;
+
+	BlockWrapper(Chunk block,RunState runState) {
+		this.block = block;
+		this.runState = runState;
+	}
+
+	private static class BlockWriter extends TemplatePrintWriter {
+		private final StringWriter sw;
+		private boolean isNull = false;
+
+		BlockWriter(StringWriter sw) {
+			super(sw);
+			this.sw = sw;
+		}
+
+		public void print(Object obj) {
+			if( obj == null && sw.getBuffer().length() == 0 && !isNull ) {
+				isNull = true;
+				return;
+			}
+			super.print(obj);
+			checkSize();
+		}
+
+		public void print(String s) {
+			if( s == null && sw.getBuffer().length() == 0 && !isNull ) {
+				isNull = true;
+				return;
+			}
+			super.print(s);
+			checkSize();
+		}
+
+		private void checkSize() {
+			if( sw.getBuffer().length() > blockWriterLimit )
+				throw new RuntimeException("BlockWriter too big");
+		}
+
+		String value() {
+			if( isNull ) {
+				if( sw.getBuffer().length() > 0 )
+					throw new NullPointerException("null written to stream");
+				return null;
+			}
+			return sw.toString();
+		}
+	}
+
+	public final String toString() {
+		BlockWriter out = new BlockWriter(new StringWriter());
+		printTo(out);
+		out.close();
+		return out.value();
+	}
+
+	void printTo(IPrintWriter out) {
+		block.run(out,runState);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/BooleanFormatException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,9 @@
+package nabble.naml.compiler;
+
+
+public final class BooleanFormatException extends IllegalArgumentException {
+
+	public BooleanFormatException(String msg) {
+		super(msg);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Call.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,8 @@
+package nabble.naml.compiler;
+
+import java.util.Collection;
+
+
+interface Call {
+	public Collection<String> getRequiredNamespaces();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Chunk.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,31 @@
+package nabble.naml.compiler;
+
+import fschmidt.util.java.Interner;
+
+
+interface Chunk {
+	public void run(IPrintWriter out,RunState runState);
+	public boolean hasOutput();
+	public Chunk intern();
+
+	static final Interner<Chunk> interner = new Interner<Chunk>();
+
+	static final Chunk NULL = new Chunk() {
+	
+		public void run(IPrintWriter out,RunState runState) {
+			out.print((String)null);
+		}
+	
+		public boolean hasOutput() {
+			return true;
+		}
+	
+		public String toString() {
+			return "nullChunk";
+		}
+
+		@Override public Chunk intern() {
+			return this;
+		}
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Command.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,10 @@
+package nabble.naml.compiler;
+
+import java.lang.annotation.*;
+
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Command {
+	String value() default "";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/CommandSpec.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,192 @@
+package nabble.naml.compiler;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+public final class CommandSpec {
+
+	public static CommandSpec.Builder DO() {
+		return new CommandSpec.Builder()
+			.scopedParameters("do")
+			.dotParameter("do")
+			.outputtedParameters("do")
+			.optionalParameters("do")
+		;
+	}
+
+	public static final CommandSpec DO = DO().build();
+
+	public static CommandSpec.Builder TEXT() {
+		return new CommandSpec.Builder()
+			.dotParameter("text")
+		;
+	}
+
+	public static final CommandSpec TEXT = TEXT().build();
+
+	public static final CommandSpec OPTIONAL_TEXT = TEXT()
+		.optionalParameters("text")
+		.build()
+	;
+
+	public final static CommandSpec EMPTY = new Builder().build();
+
+	public static CommandSpec.Builder NO_OUTPUT() {
+		return new CommandSpec.Builder()
+			.outputtedParameters()
+		;
+	}
+
+	public static final CommandSpec NO_OUTPUT = NO_OUTPUT().build();
+
+	final Set<String> parameters;
+	final Set<String> scopedParameters;
+	final String dotParameter;
+	final Set<String> requiredParameters;
+	private final Map<String,Set<String>> restrictedParams;
+	final boolean removeNulls;
+	final Set<String> outputtedParameters;
+	final Set<Class> requiredInStack;
+
+	public static class Builder {
+		private final Set<String> parameters = new HashSet<String>();
+		private Set<String> scopedParameters = null;
+		private String dotParameter = null;
+		private Set<String> optionalParameters = null;
+		private Map<String,Set<String>> restrictedParams = null;
+		private boolean removeNulls = true;
+		private Set<String> outputtedParameters = null;
+		private final Set<Class> requiredInStack = new HashSet<Class>();
+
+		public Builder parameters(String... parameters) {
+			this.parameters.addAll( Arrays.asList(parameters) );
+			return this;
+		}
+
+		public Builder scopedParameters(String... params) {
+			this.scopedParameters = new HashSet<String>( Arrays.asList(params) );
+			return this;
+		}
+
+		public Builder dotParameter(String param) {
+			this.dotParameter = param;
+			return this;
+		}
+
+		public Builder optionalParameters(String... params) {
+			if( optionalParameters == null )
+				optionalParameters = new HashSet<String>();
+			optionalParameters.addAll( Arrays.asList(params) );
+			return this;
+		}
+
+		public Builder restrictedParameter(String param,String... options) {
+			if( restrictedParams==null )
+				restrictedParams = new HashMap<String,Set<String>>();
+			restrictedParams.put( param, new HashSet<String>(Arrays.asList(options)) );
+			return this;
+		}
+
+		public Builder dontRemoveNulls() {
+			removeNulls = false;
+			return this;
+		}
+
+		public Builder outputtedParameters(String... params) {
+			if( outputtedParameters == null )
+				outputtedParameters = new HashSet<String>();
+			outputtedParameters.addAll( Arrays.asList(params) );
+			return this;
+		}
+
+		public Builder requiredInStack(Class... requiredInStack) {
+			this.requiredInStack.addAll( Arrays.asList(requiredInStack) );
+			return this;
+		}
+
+		public CommandSpec build() {
+			return new CommandSpec(this);
+		}
+	}
+
+	private CommandSpec(Builder builder) {
+		parameters = builder.parameters;
+		scopedParameters = builder.scopedParameters;
+		dotParameter = builder.dotParameter;
+		restrictedParams = builder.restrictedParams;
+		outputtedParameters = builder.outputtedParameters;
+		requiredInStack = builder.requiredInStack.isEmpty() ? Collections.<Class>emptySet() : builder.requiredInStack;
+		if( scopedParameters != null )
+			parameters.addAll(scopedParameters);
+		if( dotParameter != null )
+			parameters.add(dotParameter);
+		if( restrictedParams != null )
+			parameters.addAll(restrictedParams.keySet());
+		if( outputtedParameters != null )
+			parameters.addAll(outputtedParameters);
+		requiredParameters = new HashSet<String>(parameters);
+		if( builder.optionalParameters != null ) {
+			parameters.addAll(builder.optionalParameters);
+			requiredParameters.removeAll(builder.optionalParameters);
+		}
+		removeNulls = builder.removeNulls;
+		for( String s : parameters ) {
+			if( s.indexOf('-') != -1 )
+				throw new RuntimeException("macro attibute '"+s+"' may not contain '-'");
+		}
+	}
+
+	boolean hasScopedParam(String param) {
+		return scopedParameters!=null && scopedParameters.contains(param);
+	}
+
+	Set<String> getParameters() {
+		return Collections.unmodifiableSet(parameters);
+	}
+
+	boolean hasParameter(String param) {
+		return parameters.contains(param);
+	}
+
+	static final String dotWarning = "you may have forgotten the dot at the end of the enclosing tag name";
+
+	void check(Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs,StackTrace stackTrace)
+		throws CompileException
+	{
+		Set<String> params = new HashSet<String>();
+		params.addAll(staticArgs.keySet());
+		params.addAll(dynamicArgs.keySet());
+		if( !parameters.containsAll(params) ) {
+			params.removeAll(parameters);
+			String msg = "parameter(s) "+params+" not allowed, only use "+parameters;
+			if( dotParameter != null )
+				msg += " or "+dotWarning;
+			throw new CompileException(stackTrace,msg);
+		}
+		if( !params.containsAll(requiredParameters) ) {
+			Set<String> required = new HashSet<String>(requiredParameters);
+			required.removeAll(params);
+			if( required.size()==1 && required.iterator().next().equals(dotParameter) )
+				throw new CompileMethodException(stackTrace,"dot_parameter "+required+" was not found but is required, "+dotWarning);
+			throw new CompileMethodException(stackTrace,"parameter(s) "+required+" were not found but are required");
+		}
+		if( restrictedParams!=null ) {
+			for( Map.Entry<String,Set<String>> entry : restrictedParams.entrySet() ) {
+				String name = entry.getKey();
+				Set<String> options = entry.getValue();
+				Chunk chunk = dynamicArgs.get(name);
+				if( chunk!=null && !(chunk==Chunk.NULL && removeNulls) )
+					throw new CompileException(stackTrace,"parameter '"+name+"' cannot be dynamic");
+				String value = staticArgs.get(name);
+				if( value != null && !options.contains(value) )
+					throw new CompileException(stackTrace,"parameter '"+name+"' cannot be '"+value+"', must be one of "+options);
+			}
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/CompileException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,16 @@
+package nabble.naml.compiler;
+
+
+public class CompileException extends Exception {
+	public final StackTrace stackTrace;
+
+	public CompileException(StackTrace stackTrace,String msg) {
+		super(msg+stackTrace);
+		this.stackTrace = new StackTrace(stackTrace);
+	}
+
+	public CompileException(StackTrace stackTrace,String msg,Exception e) {
+		super(msg+stackTrace,e);
+		this.stackTrace = new StackTrace(stackTrace);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/CompileMethodException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,10 @@
+package nabble.naml.compiler;
+
+
+final class CompileMethodException extends CompileException {
+
+	CompileMethodException(StackTrace stackTrace,String msg) {
+		super(stackTrace,msg);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Compiler.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,2010 @@
+package nabble.naml.compiler;
+
+import fschmidt.util.java.ArrayStack;
+import fschmidt.util.java.CollectionUtils;
+import fschmidt.util.java.ObjectUtils;
+import fschmidt.util.java.Stack;
+import nabble.naml.dom.Attribute;
+import nabble.naml.dom.Cdata;
+import nabble.naml.dom.Container;
+import nabble.naml.dom.Element;
+import nabble.naml.dom.ElementName;
+import nabble.naml.dom.EmptyElement;
+import nabble.naml.dom.Naml;
+import nabble.naml.dom.ParseException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.CharArrayWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+
+final class Compiler {
+	private static final Logger logger = LoggerFactory.getLogger(Compiler.class);
+
+	private Traceable traceable = null;
+	private final List<Traceable> traceables = new ArrayList<Traceable>();
+	private final StackTrace stackTrace = new StackTrace() {
+
+		public StackTraceElement pop() {
+			StackTraceElement ste = super.pop();
+			if( traceable != null )
+				traceable.stackTrace.push(ste.intern());
+			return ste;
+		}
+
+	};
+	private final Stack<GenericNamespace> stack = new ArrayStack<GenericNamespace>();
+	private final Program program;
+	private final Map<String,List<Macro>> macros;
+	private final Map<Macro,Macro> overrides;
+	private final Set<String> staticNames;
+	private final GenericNamespace[] base;
+
+	static Template compile( Program program, String templateName, GenericNamespace[] base )
+		throws CompileException
+	{
+		return new Compiler(program,base).compile(templateName);
+	}
+
+	private Compiler( Program program, GenericNamespace[] base ) {
+		this.program = program;
+		this.macros = program.macros();
+		this.overrides = program.overrides();
+		this.staticNames = program.staticNames();
+		this.base = base;
+	}
+
+	private Template compile( String templateName )
+		throws CompileException
+	{
+		for( GenericNamespace ns : base ) {
+			pushWithExtensions(ns);
+		}
+		Macro macro = getTemplate(templateName);
+		if( macro==null )
+			return null;
+		addMeaning(null,macro,null);
+		StackTraceElement stackElem = new StackTraceElement(macro);
+		stackTrace.push( stackElem );
+		try {
+			MacroScope macroScope = new MacroScope(macro);
+			for( String s : macro.parameters ) {
+				macroScope.addParamChunk( new ParamChunk(s) );
+			}
+			Chunk chunk = compileMacro( macroScope );
+			if( chunk instanceof ErrorChunk )
+				throw ((ErrorChunk)chunk).e;
+			chunk = chunk.intern();
+			Template template = new Template(this,program,chunk,classes(base),macro);
+			return template;
+		} finally {
+			stackTrace.pop();
+			cleanUpTraceables();
+		}
+	}
+
+	private static Class<?>[] classes(GenericNamespace[] nss) {
+		List<Class<?>> classes = new ArrayList<Class<?>>();
+		for( GenericNamespace ns : nss ) {
+			if( ns instanceof JavaNamespace ) {
+				JavaNamespace jns = (JavaNamespace)ns;
+				classes.add(jns.cls);
+			}
+		}
+		return classes.toArray(new Class<?>[0]);
+	}
+
+	private void cleanUpTraceables() {
+		for( Traceable t : traceables ) {
+			t.cleanUp();
+		}
+	}
+
+	private void addMeaning(ElementName.Part part,Meaning meaning,MacroScope macroScope) {
+		program.addMeaning(part,meaning,macroScope,base);
+	}
+
+	private int pushWithExtensions(GenericNamespace gns) {
+		stack.push(gns);
+		if( !(gns instanceof JavaNamespace) )
+			return 1;
+		JavaNamespace ns = (JavaNamespace)gns;
+		List<JavaNamespace> extensions = program.extensionMap().get(ns.cls);
+		if( extensions == null ) {
+			return 1;
+		}
+		for( JavaNamespace extension : extensions ) {
+			stack.push(extension);
+		}
+		return 1 + extensions.size();
+	}
+
+	private int push(JavaCall javaCall,String param) {
+		if( javaCall==null || !javaCall.isScoped(param) )
+			return 0;
+		return pushWithExtensions(JavaNamespace.getNamespace(javaCall.scopedType()));
+	}
+
+	private int push(Macro macro) throws CompileException {
+		if( macro.type != Macro.Type.NAMESPACE )
+			return 0;
+		MacroNamespace ns = new MacroNamespace(macro,stackTrace,macros);
+		stack.push(ns);
+		return 1;
+	}
+
+	private void pop(int n) {
+		for( ; n > 0; n-- ) {
+			stack.pop();
+		}
+	}
+
+	private int whereInStack(String namespace) {
+		if( namespace == null )
+			throw new NullPointerException("namespace is null");
+		boolean isTop = true;
+		for( int i = stack.size() - 1; i>=0; i-- ) {
+			GenericNamespace ns = stack.get(i);
+			if( (isTop || ns.isGlobal()) && ns.names().contains(namespace) )
+				return i;
+			if( isTop && !ns.isTransparent() )
+				isTop = false;
+		}
+		return -1;
+	}
+
+	private boolean hasNamespace(String namespace) {
+		return whereInStack(namespace) != -1;
+	}
+
+	private String accessibleStack() {
+		final int top = stack.size() - 1;
+		boolean isTop = true;
+		List<GenericNamespace> list = new ArrayList<GenericNamespace>();
+		for( int i=top; i>=0; i-- ) {
+			GenericNamespace ns = stack.get(i);
+			if( ns.isGlobal() || isTop ) {
+				list.add(ns);
+				if( isTop && !ns.isTransparent() )
+					isTop = false;
+			}
+		}
+		Collections.reverse(list);
+		return list.toString();
+	}
+
+	int whereInStack(Class<?> cls)
+		throws CompileException
+	{
+		boolean isTop = true;
+		for( int i = stack.size() - 1; i>=0; i-- ) {
+			GenericNamespace gns = stack.get(i);
+			if( !(gns instanceof JavaNamespace) )
+				continue;
+			JavaNamespace ns = (JavaNamespace)gns;
+			if( !isTop && !ns.isGlobal() )
+				continue;
+			if( cls.isAssignableFrom(ns.cls) )
+				return i;
+			if( isTop && !ns.isTransparent() )
+				isTop = false;
+		}
+		throw new CompileException(stackTrace,"required namespace '"+JavaNamespace.getNamespace(cls)+"' not found in "+accessibleStack()+"  stack = "+stack);
+	}
+
+	private Macro getTemplate(String templateName)
+		throws CompileException
+	{
+		Set<String> namespacesOnStack = new HashSet<String>();
+		for( GenericNamespace ns : stack ) {
+			namespacesOnStack.addAll( ns.names() );
+		}
+		List<Macro> candidates = macros.get(templateName);
+		if( candidates == null )
+			return null;
+		Macro rtn = null;
+		for( Macro candidate : candidates ) {
+			if( namespacesOnStack.containsAll( candidate.requiredNamespaces ) ) {
+				if( rtn != null )
+					throw conflict(candidate,rtn);
+				rtn = candidate;
+			}
+		}
+		return rtn;
+	}
+
+	private Chunk compileMacro(MacroScope macroScope)
+		throws CompileException
+	{
+		Macro macro = macroScope.macro;
+		for( String ns : macro.requiredNamespaces ) {
+			if( !hasNamespace(ns) )
+				throw new CompileException(stackTrace,"required namespace '"+ns+"' for "+new StackTraceElement(macro)+" not found in "+accessibleStack()+"  stack = "+stack);
+		}
+		if( macroScope.parentScope != null && macro.type==Macro.Type.SUBROUTINE && program.getMacroWhichOverrides(macro)==null )
+			return compileSubroutine(macro,macroScope);
+		int pushed = push(macro);
+		try {
+			Chunk chunk = consolidate( compile( macro.naml, macroScope ) );
+			chunk = trimChunk(chunk);
+			if( !(chunk instanceof ErrorChunk) && macroScope.hasVars )
+				chunk = new VarScope(chunk,macroScope);
+			return chunk;
+		} catch(StackOverflowError e) {
+			throw new CompileException(stackTrace,"stack overflow, probably recursive call");
+		} finally {
+			pop(pushed);
+		}
+	}
+
+	private Chunk compileSubroutine(Macro macro,MacroScope macroScope)
+		throws CompileException
+	{
+		Map<String,Stringable> argMap = new HashMap<String,Stringable>();
+		for( String argName : macroScope.getArgNames() ) {
+			Naml argNaml = macroScope.getArg(argName);
+			Chunk argValue = consolidate( compile(argNaml,macroScope.parentScope) );
+			if( argValue == Chunk.NULL )
+				continue;
+			if( argValue instanceof ErrorChunk )
+				return argValue;
+			Stringable s = argValue==emptyChunk ? new StringableString("")
+				: argValue instanceof StringChunk ? new StringableString(((StringChunk)argValue).s)
+				: new StringableChunk(argValue)
+			;
+			argMap.put(argName,s);
+		}
+		GenericNamespace[] baseSub = new GenericNamespace[macro.requiredNamespaces.size()];
+		List<Integer> iStackList = new ArrayList<Integer>();
+		for( int i=0; i<baseSub.length; i++ ) {
+			String namespace = macro.requiredNamespaces.get(i);
+			int iStack = whereInStack(namespace);
+			if( iStack == -1 )
+				throw new CompileException(stackTrace,"namespace needed for subroutine '"+namespace+"' not found in "+accessibleStack()+"  stack = "+stack);
+			GenericNamespace ns = stack.get(iStack);
+			baseSub[i] = ns;
+			if( ns instanceof JavaNamespace )
+				iStackList.add( iStack(iStack) );
+		}
+		int[] aiStack = new int[iStackList.size()];
+		for( int i=0; i<aiStack.length; i++ ) {
+			aiStack[i] = iStackList.get(i);
+		}
+		return new Subroutine(this,argMap,aiStack,baseSub,stackTrace.peek().commandName());
+	}
+
+
+	private static final ElementName.Part nPart = new ElementName.Part("n").intern();
+	private static final StringChunk closingTag = new StringChunk(">");
+	private static final StringChunk closingEmptyTag = new StringChunk("/>");
+
+	private List<Chunk> compile(Naml naml,MacroScope macroScope)
+		throws CompileException
+	{
+		final List<Chunk> chunks = new ArrayList<Chunk>();
+		for( Object obj : naml ) {
+			if( obj instanceof Element ) {
+				Element element = (Element)obj;
+				final ElementName name = element.name();
+				List<ElementName.Part> parts = name.parts();
+				if( parts.size() > 1 && parts.get(0) == nPart ) {
+					Chunk chunk = compileElement(element,macroScope);
+					chunks.add(chunk);
+					continue;
+				} else if( parts.size() == 1 && parts.get(0).text() == "t" ) {
+					Chunk chunk = compileTranslation(element,macroScope);
+					chunks.add(chunk);
+					continue;
+				}
+				if( !staticNames.contains(name.toLowerCaseString()) ) {
+					stackTrace.push( new StackTraceElement(macroScope.source(),element) );
+					try {
+						throw new CompileException(stackTrace,"invalid static tag: "+name);
+					} finally {
+						stackTrace.pop();
+					}
+				}
+				chunks.add( new StringChunk( "<" + element.name() ) );
+				for( Attribute attr : element.attributes() ) {
+					Chunk valueChunk = consolidate( compile( attr.value(), macroScope ) );
+					if( valueChunk instanceof ErrorChunk ) {
+						chunks.add( valueChunk );
+					} else if( valueChunk == emptyChunk ) {
+						chunks.add( new StringChunk( attr.toString("") ) );
+					} else if( valueChunk instanceof StringChunk ) {
+						StringChunk stringChunk = (StringChunk)valueChunk;
+						chunks.add( new StringChunk( attr.toString(stringChunk.s) ) );
+					} else {
+						StackTraceElement stackTraceElement = new StackTraceElement(macroScope.source(),element,("[attr - "+attr.name()+"]"));
+						stackTrace.push(stackTraceElement);
+						try {
+							chunks.add( new DynamicAttr( attr, valueChunk ) );
+						} finally {
+							stackTrace.pop();
+						}
+					}
+				}
+				String s = element.spaceAtEndOfOpeningTag();
+				if( s.length() > 0 )
+					chunks.add( new StringChunk(s) );
+				if( element instanceof Container ) {
+					chunks.add( closingTag );
+					Container container = (Container)element;
+					chunks.addAll( compile(container.contents(),macroScope) );
+					chunks.add( new StringChunk(container.closingTag()) );
+				} else {
+					chunks.add( closingEmptyTag );
+				}
+				continue;
+			} else if( obj instanceof Cdata ) {
+				Cdata cdata = (Cdata)obj;
+				chunks.add( new StringChunk(cdata.text()) );
+				continue;
+			}
+			String s = obj.toString();
+			if( s.length() > 0 )
+				chunks.add( new StringChunk(s) );
+		}
+		return chunks;
+	}
+
+	private Chunk compileTranslation(Element element,MacroScope macroScope)
+		throws CompileException
+	{
+		stackTrace.push( new StackTraceElement(macroScope.source(),element) );
+		try {
+			if( !(element instanceof Container) )
+				throw new CompileException(stackTrace,"'t' tag may not be empty");
+			if( element.name().endsWithDot() )
+				throw new CompileException(stackTrace,"'t' tag may not end with dot");
+		} finally {
+			stackTrace.pop();
+		}
+		Container container = (Container)element;
+		Naml contents = container.contents();
+		String macroName = Macro.translationMacroName(contents,null);
+		stackTrace.push( new StackTraceElement(macroScope.source(),element,macroName) );
+		try {
+			List<Macro> macroList = macros.get(macroName);
+			if( macroList == null ) {
+				contents = removeTranslationArgs(macroScope.source(),contents);
+				return consolidate( compile(contents,macroScope) );
+			}
+			if( macroList.size() != 1 )
+				throw new RuntimeException("invalid - "+macroList);
+			Macro macro = macroList.get(0);
+			MacroScope newScope = macroScope.newScope(macro);
+			getTranslationArgs(macroScope.source(),contents,newScope);
+			addMeaning(container.name().parts().get(0),macro,macroScope);
+			return getMacroChunk2(newScope);
+		} finally {
+			stackTrace.pop();
+		}
+	}
+
+	private Naml removeTranslationArgs(Source source,Naml oldContents) throws CompileException {
+		boolean changed = false;
+		Naml newContents = new Naml();
+		for( Object obj : oldContents ) {
+			if( obj instanceof Element ) {
+				Element element = (Element)obj;
+				ElementName name = element.name();
+				List<ElementName.Part> parts = name.parts();
+				if( parts.size() >= 2 && parts.get(0).text() == "t" ) {
+					changed = true;
+					stackTrace.push( new StackTraceElement(source,element) );
+					try {
+						if( parts.size() == 2 ) {
+							if( name.endsWithDot() )
+								throw new CompileException(stackTrace,"translation argument cannot end with dot");
+							if( !(element instanceof Container) )
+								throw new CompileException(stackTrace,"translation argument cannot be empty");
+							Container container = (Container)element;
+							newContents.addAll( container.contents() );
+						} else {
+							List<ElementName.Part> newParts = new ArrayList<ElementName.Part>();
+							newParts.add(nPart);
+							newParts.addAll( parts.subList(2,parts.size()) );
+							ElementName newName = new ElementName(name.endsWithDot(),newParts);
+							if( element instanceof Container ) {
+								Container container = (Container)element;
+								Naml newContainerContents = removeTranslationArgs(source,container.contents());
+								Element newElement = new Container(newName,element.attributes(),"",element.lineNumber(),newContainerContents,"");
+								newContents.add(newElement);
+							} else {
+								Element newElement = new EmptyElement(newName,element.attributes(),"",element.lineNumber());
+								newContents.add(newElement);
+							}
+						}
+					} finally {
+						stackTrace.pop();
+					}
+					continue;
+				}
+				if( element instanceof Container ) {
+					Container oldContainer = (Container)element;
+					Naml oldContainerContents = oldContainer.contents();
+					Naml newContainerContents = removeTranslationArgs(source,oldContainerContents);
+					if( newContainerContents != oldContainerContents ) {
+						changed = true;
+						Container newContainer = new Container(oldContainer.name(),oldContainer.attributes(),"",oldContainer.lineNumber(),newContainerContents,"");
+						newContents.add(newContainer);
+						continue;
+					}
+				}
+			}
+			newContents.add(obj);
+		}
+		return changed ? newContents : oldContents;
+	}
+
+	private void getTranslationArgs(Source source,Naml oldContents,MacroScope newScope) throws CompileException {
+		for( Object obj : oldContents ) {
+			if( obj instanceof Element ) {
+				Element element = (Element)obj;
+				ElementName name = element.name();
+				List<ElementName.Part> parts = name.parts();
+				if( parts.size() >= 2 && parts.get(0).text() == "t" ) {
+					String argName = parts.get(1).text();
+					stackTrace.push( new StackTraceElement(source,element) );
+					try {
+						if( parts.size() == 2 ) {
+							if( name.endsWithDot() )
+								throw new CompileException(stackTrace,"translation argument cannot end with dot");
+							if( !(element instanceof Container) )
+								throw new CompileException(stackTrace,"translation argument cannot be empty");
+							Container container = (Container)element;
+							newScope.addArg( argName, container.contents() );
+						} else {
+							newScope.addArg( argName, getMacroMultiDotArg(element,2,true) );
+						}
+					} finally {
+						stackTrace.pop();
+					}
+					continue;
+				}
+				if( element instanceof Container ) {
+					Container container = (Container)element;
+					getTranslationArgs(source,container.contents(),newScope);
+					continue;
+				}
+			}
+		}
+	}
+
+	private Chunk compileElement(Element element,MacroScope macroScope)
+		throws CompileException
+	{
+		ElementName name = element.name();
+		try {
+			if( name.parts().size() == 2 ) {
+				return singleMethodTag(element,macroScope);
+			} else {
+				return multiMethodTag(element,1,macroScope);
+			}
+		} catch(CompileMethodException e) {
+			return new ErrorChunk(e);
+		}
+	}
+
+	private Chunk singleMethodTag(Element element,MacroScope macroScope)
+		throws CompileException
+	{
+		ElementName.Part part = element.name().parts().get(1);
+		String cmdName = part.text();
+		StackTraceElement stackElem = new StackTraceElement(macroScope.source(),element,cmdName);
+		stackTrace.push(stackElem);
+		try {
+			Chunk chunk = getMacroArgChunk(cmdName,macroScope);
+			if( chunk != null ) {
+				if( element.name().endsWithDot() )
+					throw new CompileException(stackTrace,"macro parameter cannot take a dot_parameter");
+				if( element instanceof Container )
+					throw new CompileException(stackTrace,"parameter must be empty element, tag must end with '/'");
+				if( !element.attributes().isEmpty() )
+					throw new CompileException(stackTrace,"no arguments are allowed in a parameter");
+				addMeaning(part,ParamMeaning.INSTANCE,macroScope);
+				return chunk;
+			}
+			Call call = getCommandCall(cmdName,macroScope);
+			if( call instanceof Macro ) {
+				Macro macro = (Macro)call;
+				addMeaning(part,macro,macroScope);
+				return getMacroChunk(macro,element,macroScope,false);
+			}
+			JavaCall javaCall = (JavaCall)call;
+			stackElem.setMethod(javaCall.getMethod());
+			Map<String,Chunk> dynamicArgs = attributes(element,javaCall,macroScope);
+			if( element.name().endsWithDot() ) {
+				getDotArg( (Container)element, macroScope, javaCall, dynamicArgs );
+			} else if( element instanceof Container ) {
+				getTagArgs( (Container)element, macroScope, javaCall, dynamicArgs );
+			}
+			addMeaning(part,javaCall.javaCommand,macroScope);
+			return methodChunk(element,macroScope,javaCall,dynamicArgs);
+		} finally {
+			stackTrace.pop();
+		}
+	}
+
+	private Chunk multiMethodTag(Element element,int iCmd,MacroScope macroScope)
+		throws CompileException
+	{
+		ElementName.Part part = element.name().parts().get(iCmd);
+		String methodName = part.text();
+		StackTraceElement stackElem = new StackTraceElement(macroScope.source(),element,methodName);
+		stackTrace.push(stackElem);
+		try {
+			Chunk chunk = getMacroArgChunk(methodName,macroScope);
+			if( chunk != null ) {
+				if( iCmd != element.name().parts().size() - 1 || element.name().endsWithDot() )
+					throw new CompileException(stackTrace,"macro parameter cannot take a dot_parameter");
+				if( !element.attributes().isEmpty() )
+					throw new CompileException(stackTrace,"no arguments are allowed in an parameter");
+				addMeaning(part,ParamMeaning.INSTANCE,macroScope);
+				return chunk;
+			}
+			Call call = getCommandCall(methodName,macroScope);
+			if( iCmd == element.name().parts().size() - 1 ) {
+				if( call instanceof Macro ) {
+					Macro macro = (Macro)call;
+					addMeaning(part,macro,macroScope);
+					return getMacroChunk(macro,element,macroScope,true);
+				}
+				JavaCall javaCall = (JavaCall)call;
+				stackElem.setMethod(javaCall.getMethod());
+				Map<String,Chunk> dynamicArgs = attributes(element,javaCall,macroScope);
+				if( element.name().endsWithDot() )
+					getDotArg( (Container)element, macroScope, javaCall, dynamicArgs );
+				addMeaning(part,javaCall.javaCommand,macroScope);
+				return methodChunk(element,macroScope,javaCall,dynamicArgs);
+			}
+			if( call instanceof Macro ) {
+				Macro macro = (Macro)call;
+				if( macro.dotParam==null )
+					throw new CompileException(stackTrace,"macro '"+macro.name+"' doesn't support multi-method tags");
+				MacroScope newScope = macroScope.newScope(macro);
+				newScope.addArg(macro.dotParam,getMacroMultiDotArg(element,iCmd+1,false));
+				if( !element.name().endsWithDot() && iCmd==1 && element instanceof Container ) {
+					getMacroTagArgs( macro, (Container)element, newScope );
+				}
+				addMeaning(part,macro,macroScope);
+				return getMacroChunk2(newScope);
+			}
+			JavaCall javaCall = (JavaCall)call;
+			stackElem.setMethod(javaCall.getMethod());
+			String dotParam = javaCall.cmdSpec().dotParameter;
+			if( dotParam==null )
+				throw new CompileException(stackTrace,"method '"+methodName+"' in "+javaCall.method().getDeclaringClass()+" doesn't support multi-method tags");
+			int pushed = push(javaCall,dotParam);
+			try {
+				chunk = multiMethodTag(element,iCmd+1,macroScope);
+				chunk = trimChunk(chunk);
+			} finally {
+				pop(pushed);
+			}
+			Map<String,Chunk> dynamicArgs = new HashMap<String,Chunk>();
+			if( iCmd==1 && element instanceof Container && !element.name().endsWithDot() ) {
+				getTagArgs( (Container)element, macroScope, javaCall, dynamicArgs );
+			}
+			if( dynamicArgs.put( javaCall.cmdSpec().dotParameter, chunk ) != null )
+				throw new CompileException(stackTrace,"dot_parameter duplicated as regular argument");
+			addMeaning(part,javaCall.javaCommand,macroScope);
+			return methodChunk(element,macroScope,javaCall,dynamicArgs);
+		} finally {
+			stackTrace.pop();
+		}
+	}
+
+	private Map<String,Chunk> attributes(Element element,JavaCall javaCall,MacroScope macroScope) throws CompileException {
+		Map<String,Chunk> dynamicArgs = new HashMap<String,Chunk>();
+		for( Attribute attr : element.attributes() ) {
+			String name = attr.name();
+			int pushed = push(javaCall,name);
+			try {
+				dynamicArgs.put( name, consolidate( compile(attr.value(),macroScope) ) );
+			} finally {
+				pop(pushed);
+			}
+		}
+		return dynamicArgs;
+	}
+
+	private Chunk getMacroArgChunk(String cmdName,MacroScope macroScope)
+		throws CompileException
+	{
+		ParamChunk paramChunk = macroScope.getParamChunk(cmdName);
+		if( paramChunk != null )
+			return paramChunk;
+		Naml macroArg = macroScope.getArg(cmdName);
+		if( macroArg == null )
+			return null;
+		return consolidate( compile(macroArg,macroScope.parentScope) );
+	}
+
+	private Chunk getMacroChunk(Macro macro,Element element,MacroScope macroScope,boolean isMulti)
+		throws CompileException
+	{
+		MacroScope newScope = macroScope.newScope(macro);
+		for( Attribute attr : element.attributes() ) {
+			String name = attr.name();
+			if( !macro.parameters.contains(name) )
+				throw new CompileException(stackTrace,"parameter '"+name+"' not allowed, only use "+macro.parameters);
+			newScope.addArg(name,attr.value());
+		}
+		if( element instanceof Container ) {
+			Container container = (Container)element;
+			ElementName name = container.name();
+			if( name.endsWithDot() ) {
+				if( macro.dotParam==null )
+					throw new CompileException(stackTrace,"macro '"+macro.name+"' doesn't have a dot_parameter");
+				Naml naml = container.contents();
+				naml = Macro.trim(naml);
+				newScope.addArg( macro.dotParam, naml );
+			} else if(!isMulti) {
+				getMacroTagArgs( macro, container, newScope );
+			}
+		}
+		return getMacroChunk2(newScope);
+	}
+
+	private void getMacroTagArgs(Macro macro,Container container,MacroScope newScope)
+		throws CompileException
+	{
+		for( Object obj : container.contents() ) {
+			if( obj instanceof Element ) {
+				Element element = (Element)obj;
+				ElementName name = element.name();
+				boolean isSingle = name.parts().size() == 1;
+				String argName = name.parts().get(0).text();
+				if( !macro.parameters.contains(argName) )
+					throw new CompileException(stackTrace,"tag '"+argName+"' is not an allowed here, only these are allowed: "+macro.parameters);
+				if( isSingle ) {
+					if( !(element instanceof Container) )
+						throw new CompileException(stackTrace,"empty tag '"+argName+"' not allowed here, argument expected");
+					if( !element.attributes().isEmpty() )
+						throw new CompileException(stackTrace,"tag '"+argName+"' may not have arguments");
+					Container argContainer = (Container)element;
+					Naml naml = argContainer.contents();
+					naml = Macro.trim(naml);
+					newScope.addArg(argName,naml);
+				} else {
+					newScope.addArg(argName,getMacroMultiDotArg(element,1,true));
+				}
+			} else if( !(obj instanceof String) ) {
+				throw new CompileException(stackTrace,obj.getClass().getName()+" not allowed here");
+			}
+		}
+	}
+
+	private static Naml getMacroMultiDotArg(Element element,int i,boolean allowArgContents) {
+		ElementName name = element.name();
+		List<ElementName.Part> parts = new ArrayList<ElementName.Part>();
+		parts.add(nPart);
+		parts.addAll( name.parts().subList(i,name.parts().size()) );
+		ElementName newName = new ElementName(name.endsWithDot(),parts);
+		Element newElement;
+		if( name.endsWithDot() || allowArgContents && element instanceof Container ) {
+			Container argContainer = (Container)element;
+			newElement = new Container(newName,element.attributes(),"",element.lineNumber(),argContainer.contents(),"");
+		} else {
+			newElement = new EmptyElement(newName,element.attributes(),"",element.lineNumber());
+		}
+		Naml naml = new Naml();
+		naml.add(newElement);
+		return naml;
+	}
+
+	private static final Naml nullNaml;
+	static {
+		try {
+			nullNaml = Naml.parser().parse("<n.null/>");
+		} catch(ParseException e) {
+			logger.error("",e);
+			System.exit(-1);
+			throw new RuntimeException(e);
+		}
+	}
+
+	private Chunk getMacroChunk2(MacroScope newScope)
+		throws CompileException
+	{
+		Set<String> parameters = new HashSet<String>(newScope.macro.parameters);
+		parameters.removeAll(newScope.getArgNames());
+		for( String param : parameters ) {
+			newScope.addArg( param, nullNaml );
+		}
+		return compileMacro(newScope);
+	}
+
+	private void getDotArg(Container container,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs)
+		throws CompileException
+	{
+		String dotParam = javaCall.cmdSpec().dotParameter;
+		if( dotParam==null )
+			throw new CompileException(stackTrace,"command '"+javaCall.name()+"' doesn't support multi-method tags");
+		int pushed = push(javaCall,dotParam);
+		try {
+			Chunk block = consolidate( compile( container.contents(), macroScope ) );
+			block = trimChunk(block);
+			if( dynamicArgs.put( dotParam, block ) != null )
+				throw new CompileException(stackTrace,"dot_parameter duplicated as regular argument");
+		} finally {
+			pop(pushed);
+		}
+	}
+
+	private void getTagArgs(Container container,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs)
+		throws CompileException
+	{
+		for( Object obj : container.contents() ) {
+			if( obj instanceof Element ) {
+				Element element = (Element)obj;
+				ElementName name = element.name();
+				boolean isSingle = name.parts().size() == 1;
+				if( isSingle && !(element instanceof Container) )
+					throw new CompileException(stackTrace,"empty " + notAllowed(macroScope,element,javaCall) );
+				String argName = name.parts().get(0).text();
+				if( !javaCall.cmdSpec().parameters.contains(argName) )
+					throw new CompileException(stackTrace,notAllowed(macroScope,element,javaCall));
+				if( isSingle && !element.attributes().isEmpty() )
+					throw new CompileException(stackTrace,"tag "+argName+" may not have arguments");
+				int pushed = push(javaCall,argName);
+				try {
+					Chunk block;
+					if( isSingle ) {
+						Container argContainer = (Container)element;
+						block = consolidate( compile( argContainer.contents(), macroScope ) );
+					} else {
+						block = compileElement( element, macroScope );
+					}
+					block = trimChunk(block);
+					if( dynamicArgs.put( argName, block ) != null )
+						throw new CompileException(stackTrace,"duplicate argument: "+argName);
+				} catch(TemplateRuntimeException e) {
+					throw e;
+				} catch(RuntimeException e) {
+					throw new TemplateRuntimeException("in "+name,e);
+				} finally {
+					pop(pushed);
+				}
+			} else if( !(obj instanceof String) ) {
+				throw new CompileException(stackTrace,obj.getClass().getName()+" not allowed here");
+			}
+		}
+	}
+
+	private static String notAllowed(MacroScope macroScope,Element element,JavaCall javaCall) {
+		Set<String> parameters = javaCall.cmdSpec().parameters;
+		StringBuilder buf = new StringBuilder();
+		buf
+			.append( "tag " )
+			.append( new StackTraceElement(macroScope.source(),element) )
+			.append( " is not allowed here, " )
+		;
+		if( parameters.isEmpty() ) {
+			buf.append( "no tags allowed" );
+		} else {
+			buf.append("only tags ");
+			Iterator<String> iter = parameters.iterator();
+			buf
+				.append( "<" )
+				.append( iter.next() )
+				.append( ">, " )
+			;
+			while( iter.hasNext() ) {
+				buf
+					.append( ", <" )
+					.append( iter.next() )
+					.append( ">" )
+				;
+			}
+			buf.append( " are allowed here" );
+		}
+		if( javaCall.cmdSpec().dotParameter != null )
+			buf.append( ", or you may have forgotten the dot at the end of the tag name" );
+		buf
+			.append( " in tag element '" )
+			.append( javaCall.name() )
+			.append( "'" )
+		;
+		return buf.toString();
+	}
+
+	private Chunk methodChunk(Element element,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs)
+		throws CompileException
+	{
+		Chunk methodChunk = methodChunk2(element,macroScope,javaCall,dynamicArgs);
+		if( methodChunk instanceof Block ) {
+			Block block = (Block)methodChunk;
+			Chunk chunk = block.prerun(new CompileTimeRunState());
+			if( chunk != null ) {
+				return chunk;
+			}
+		}
+		for( Chunk chunk : dynamicArgs.values() ) {
+			if( chunk instanceof ErrorChunk )
+				return chunk;
+		}
+		return methodChunk;
+	}
+
+	private Chunk methodChunk2(Element element,MacroScope macroScope,JavaCall javaCall,Map<String,Chunk> dynamicArgs)
+		throws CompileException
+	{
+		Map<String,String> staticArgs = new HashMap<String,String>();
+		for( Iterator<Map.Entry<String,Chunk>> iter = dynamicArgs.entrySet().iterator(); iter.hasNext(); ) {
+			Map.Entry<String,Chunk> entry = iter.next();
+			Chunk chunk = entry.getValue();
+			if( chunk == emptyChunk ) {
+				staticArgs.put( entry.getKey(), "" );
+				iter.remove();
+			} else if( chunk instanceof StringChunk ) {
+				StringChunk sc = (StringChunk)entry.getValue();
+				staticArgs.put( entry.getKey(), sc.s );
+				iter.remove();
+			}
+		}
+		if( javaCall.cmdSpec().removeNulls )
+			removeNulls(dynamicArgs);
+		javaCall.cmdSpec().check(staticArgs,dynamicArgs,stackTrace);
+		if( javaCall.name() == "var" ) {
+			return new GetVar(macroScope,staticArgs,dynamicArgs);
+		}
+		if( javaCall.name() == "set_var" ) {
+			return new SetVar(macroScope,staticArgs,dynamicArgs);
+		}
+		if( javaCall.name() == "uplevel_var" ) {
+			return new GetVar(macroScope.parentScope,staticArgs,dynamicArgs);
+		}
+		if( javaCall.name() == "uplevel_set_var" ) {
+			return new SetVar(macroScope.parentScope,staticArgs,dynamicArgs);
+		}
+		Map<Class,Integer> inStack = new HashMap<Class,Integer>();
+		for( Class cls : javaCall.cmdSpec().requiredInStack ) {
+			inStack.put( cls, iStack(whereInStack(cls)) );
+		}
+		if( javaCall.isScoped() ) {
+			if( javaCall.cmdSpec().scopedParameters == null )
+				throw new CompileException(stackTrace,"a scoped method must specify scoped arguments");
+			Map<String,Chunk> dynamicArgs2 = new HashMap<String,Chunk>(dynamicArgs);
+			Map<String,Chunk> scopedArgs = new HashMap<String,Chunk>();
+			for( String scopedParam : javaCall.cmdSpec().scopedParameters ) {
+				Chunk chunk = dynamicArgs2.remove(scopedParam);
+				if( chunk != null )
+					scopedArgs.put(scopedParam,chunk);
+			}
+			return new Scoped(this,javaCall,inStack,staticArgs,dynamicArgs2,scopedArgs);
+		}
+		return new Block(this,javaCall,inStack,staticArgs,dynamicArgs);
+	}
+
+	private static void removeNulls(Map<String,Chunk> dynamicArgs) {
+		for( Iterator<Chunk> iter = dynamicArgs.values().iterator(); iter.hasNext(); ) {
+			if( iter.next() == Chunk.NULL )
+				iter.remove();
+		}
+	}
+
+	private int iStack(int i) {
+		if( !(stack.get(i) instanceof JavaNamespace) )
+			throw new RuntimeException();
+		int n = 0;
+		for( int j=i; j>=0; j-- ) {
+			if( !stack.get(j).isGlobal() ) {
+				final int size = stack.size();
+				for( int k=i; k<size; k++ ) {
+					if( stack.get(k) instanceof JavaNamespace )
+						n++;
+				}
+				return -n;
+			}
+		}
+		for( int k=0; k<i; k++ ) {
+			if( stack.get(k) instanceof JavaNamespace )
+				n++;
+		}
+		return n;
+	}
+
+	private Call getCommandCall(String cmdName,MacroScope macroScope)
+		throws CompileException
+	{
+		Call call = null;
+		boolean isTop = true;
+		Set<Class> nsSet = new HashSet<Class>();
+		Set<String> namespacesOnStack = new HashSet<String>();
+		for( int i = stack.size() - 1; i>=0; i-- ) {
+			GenericNamespace ns = stack.get(i);
+			if( !isTop && !ns.isGlobal() )
+				continue;
+			namespacesOnStack.addAll( ns.names() );
+			if( ns instanceof JavaNamespace ) {
+				JavaNamespace jns = (JavaNamespace)ns;
+				Class cls = jns.cls;
+				JavaCommand javaCmd = JavaCommand.getJavaCommand(cls,cmdName);
+				if( javaCmd != null ) {
+					if( !(call instanceof JavaCall && ((JavaCall)call).javaCommand == javaCmd) ) {
+						i = iStack(i);
+						JavaCall javaCall = new JavaCall(javaCmd,i);
+						if( call != null )
+							throw conflict(javaCall,call);
+						call = javaCall;
+						program.addMeaning(javaCmd);
+					}
+				}
+			}
+			if( !ns.isTransparent() && isTop )
+				isTop = false;
+		}
+		List<Macro> candidates = macros.get(cmdName);
+		if( candidates != null ) {
+			for( Macro candidate : candidates ) {
+				if( namespacesOnStack.containsAll( candidate.requiredNamespaces ) ) {
+					if( call == null ) {
+						call = candidate;
+					} else {
+						if( call.getRequiredNamespaces().containsAll(candidate.getRequiredNamespaces()) ) {
+							if( candidate.getRequiredNamespaces().containsAll(call.getRequiredNamespaces()) )
+								throw conflict(candidate,call);
+							// keep call
+						} else if( candidate.getRequiredNamespaces().containsAll(call.getRequiredNamespaces()) ) {
+							call = candidate;
+						} else {
+							throw conflict(candidate,call);
+						}
+					}
+				}
+			}
+		}
+		if( cmdName.equals("overridden") ) {
+			if( call != null )
+				throw new CompileException(stackTrace,"conflict between ("+call+") and 'overridden' command");
+			call = overrides.get(macroScope.macro);
+		}
+		if( call == null )
+			throw new CompileMethodException(stackTrace,"macro or method for '"+cmdName+"' not found in "+accessibleStack()+"  stack = "+stack);
+		return call;
+	}
+
+	private CompileException conflict(Call call1,Call call2) {
+		return new CompileException(stackTrace,"conflict between ("+call1+") and ("+call2+")");
+	}
+
+	private static Chunk consolidate(List<Chunk> chunks) {
+		List<Chunk> flat = new ArrayList<Chunk>();
+		for( Chunk chunk : chunks ) {
+			if( chunk instanceof Chunks ) {
+				flat.addAll( Arrays.asList(((Chunks)chunk).chunks) );
+			} else {
+				flat.add(chunk);
+			}
+		}
+		if( flat.size() == 1 )
+			return flat.get(0);
+		List<Chunk> merged = new ArrayList<Chunk>();
+		StringChunk stringChunk = null;
+		StringBuilder buf = new StringBuilder();
+		for( Chunk chunk : flat ) {
+			if( chunk == emptyChunk )
+				continue;
+			if( chunk instanceof ErrorChunk )
+				return chunk;
+			if( chunk instanceof StringChunk ) {
+				StringChunk sc = (StringChunk)chunk;
+				if( stringChunk==null ) {
+					stringChunk = sc;
+				} else {
+					if( buf.length()==0 )
+						buf.append( stringChunk.s );
+					buf.append( sc.s );
+				}
+			} else {
+				if( buf.length() > 0 ) {
+					merged.add( new StringChunk(buf.toString()) );
+					buf.setLength(0);
+					stringChunk = null;
+				} else if( stringChunk != null ) {
+					merged.add( stringChunk );
+					stringChunk = null;
+				}
+				merged.add(chunk);
+			}
+		}
+		if( buf.length() > 0 ) {
+			merged.add( new StringChunk(buf.toString()) );
+		} else if( stringChunk != null ) {
+			merged.add( stringChunk );
+		}
+		return newChunks(merged);
+	}
+
+	private static Chunk trimChunk(Chunk chunk) {
+		if( chunk instanceof StringChunk ) {
+			StringChunk sc = (StringChunk)chunk;
+			String s = sc.s.trim();
+			if( s.length() == 0 )
+				return emptyChunk;
+			if( s.length() == sc.s.length() )
+				return sc;
+			return new StringChunk(s);
+		}
+		if( chunk instanceof Chunks ) {
+			boolean changed = false;
+			List<Chunk> chunks = new ArrayList<Chunk>(Arrays.asList(((Chunks)chunk).chunks));
+			if( chunks.size() >= 2 ) {
+				for( int i=0; i<chunks.size(); ) {
+					Chunk firstChunk = chunks.get(i);
+					if( firstChunk instanceof StringChunk ) {
+						StringChunk sc = (StringChunk)firstChunk;
+						String s = sc.s.replaceAll("^\\s+","");
+						if( s.length() < sc.s.length() ) {
+							changed = true;
+							if( s.length() == 0 ) {
+								chunks.remove(i);
+								continue;
+							} else {
+								chunks.set(i,new StringChunk(s));
+							}
+						}
+					} else if( !firstChunk.hasOutput() ) {
+						i++;
+						continue;
+					}
+					break;
+				}
+				for( int i = chunks.size() - 1; i>=0; i-- ) {
+					Chunk lastChunk = chunks.get(i);
+					if( lastChunk instanceof StringChunk ) {
+						StringChunk sc = (StringChunk)lastChunk;
+						String s = sc.s.replaceAll("\\s+$","");
+						if( s.length() < sc.s.length() ) {
+							changed = true;
+							if( s.length() == 0 ) {
+								chunks.remove(i);
+								continue;
+							} else {
+								chunks.set(i,new StringChunk(s));
+							}
+						}
+					} else if( !lastChunk.hasOutput() ) {
+						continue;
+					}
+					break;
+				}
+				if( changed )
+					return newChunks(chunks);
+			}
+		}
+		return chunk;
+	}
+
+	private static final Chunk emptyChunk = new Chunk() {
+
+		public void run(IPrintWriter out,RunState runState) {}
+
+		public boolean hasOutput() {
+			return false;
+		}
+
+		public String toString() {
+			return "emptyChunk";
+		}
+
+		@Override public Chunk intern() {
+			return this;
+		}
+	};
+
+	private static final class Chunks implements Chunk {
+		private Chunk[] chunks;
+		private int hash = 0;
+
+		Chunks(List<Chunk> chunks) {
+			this.chunks = chunks.toArray(new Chunk[chunks.size()]);
+		}
+
+		public void run(IPrintWriter out,RunState runState) {
+			for( Chunk chunk : chunks ) {
+				chunk.run(out,runState);
+			}
+		}
+
+		public boolean hasOutput() {
+			for( Chunk chunk : chunks ) {
+				if( chunk.hasOutput() )
+					return true;
+			}
+			return false;
+		}
+
+		public String toString() {
+			return "{Chunks: "+chunks+"}";
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( this==obj )
+				return true;
+			if( !(obj instanceof Chunks) )
+				return false;
+			Chunks c = (Chunks)obj;
+			final int n = chunks.length;
+			if( n != c.chunks.length )
+				return false;
+			for( int i=0; i<n; i++ ) {
+				if( chunks[i] != c.chunks[i] )
+					return false;
+			}
+			return true;
+		}
+
+		@Override public int hashCode() {
+			if( hash == 0 )
+				hash = Arrays.hashCode(chunks);
+			return hash;
+		}
+
+		@Override public Chunk intern() {
+			Chunk[] a = new Chunk[chunks.length];
+			for( int i=0; i<a.length; i++ ) {
+				a[i] = chunks[i].intern();
+			}
+			chunks = a;
+			return interner.intern(this);
+		}
+	}
+
+	private static Chunk newChunks(List<Chunk> chunks) {
+		switch( chunks.size() ) {
+		case 0:
+			return emptyChunk;
+		case 1:
+			return chunks.get(0);
+		default:
+			return new Chunks(chunks);
+		}
+	}
+
+	private static class ErrorChunk implements Chunk {
+		final CompileException e;
+
+		ErrorChunk(CompileException e) {
+			this.e = e;
+		}
+
+		public void run(IPrintWriter out,RunState runState) {
+			throw new RuntimeException(e);
+		}
+
+		public boolean hasOutput() {
+			return false;
+		}
+/*
+		@Override public boolean equals(Object obj) {
+			throw new RuntimeException("never");
+		}
+
+		@Override public int hashCode() {
+			throw new RuntimeException("never");
+		}
+*/
+		@Override public Chunk intern() {
+			throw new RuntimeException("never");
+		}
+	}
+
+	private static class DynamicAttr implements Chunk {
+		private final Attribute attr;
+		private Chunk chunk;
+
+		DynamicAttr(Attribute attr,Chunk chunk) {
+			this.attr = attr;
+			this.chunk = chunk;
+		}
+
+		public void run(IPrintWriter out,RunState runState) {
+			String attrValue = new BlockWrapper(chunk,runState).toString();
+			if( attrValue != null )
+				out.print( attr.toString(attrValue) );
+		}
+
+		public boolean hasOutput() {
+			return true;
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( this==obj )
+				return true;
+			if( !(obj instanceof DynamicAttr) )
+				return false;
+			DynamicAttr da = (DynamicAttr)obj;
+			return da.attr == attr && da.chunk.equals(chunk);
+		}
+
+		@Override public int hashCode() {
+			return attr.hashCode() * 31 + chunk.hashCode();
+		}
+
+		@Override public Chunk intern() {
+			chunk = chunk.intern();
+			return interner.intern(this);
+		}
+	}
+
+
+	private static class VarScope implements Chunk {
+		private final Macro macro;
+		private Chunk chunk;
+
+		VarScope(Chunk chunk,MacroScope macroScope) {
+			this.macro = macroScope.macro;
+			this.chunk = chunk;
+		}
+
+		public void run(IPrintWriter out,RunState runState) {
+			runState.pushVars(macro);
+			try {
+				chunk.run(out,runState);
+			} finally {
+				runState.popVars(macro);
+			}
+		}
+
+		public boolean hasOutput() {
+			return chunk.hasOutput();
+		}
+
+		public String toString() {
+			return "{VarScope}";
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( this==obj )
+				return true;
+			if( !(obj instanceof VarScope) )
+				return false;
+			VarScope v = (VarScope)obj;
+			return macro == v.macro && chunk.equals(v.chunk);
+		}
+
+		@Override public int hashCode() {
+			return chunk.hashCode()*31 + macro.hashCode();
+		}
+
+		@Override public Chunk intern() {
+			chunk = chunk.intern();
+			return interner.intern(this);
+		}
+	}
+
+	private interface Stringable {
+		public String toString(RunState runState);
+		public void internChunk();
+	}
+
+	private final static class StringableString implements Stringable {
+		private final String s;
+
+		StringableString(String s) {
+			this.s = s;
+		}
+
+		public String toString(RunState runState) {
+			return s;
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( !(obj instanceof StringableString) )
+				return false;
+			StringableString ss = (StringableString)obj;
+			return ObjectUtils.equals(ss.s,s);
+		}
+
+		@Override public int hashCode() {
+			int h = getClass().hashCode();
+			if( s != null )
+				h += s.hashCode();
+			return h;
+		}
+
+		public void internChunk() {}
+	}
+
+	private final static class StringableChunk implements Stringable {
+		private Chunk chunk;
+
+		StringableChunk(Chunk chunk) {
+			this.chunk = chunk;
+		}
+
+		public String toString(RunState runState) {
+			return new BlockWrapper(chunk,runState).toString();
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( !(obj instanceof StringableChunk) )
+				return false;
+			StringableChunk sc = (StringableChunk)obj;
+			return sc.chunk == chunk;
+		}
+
+		@Override public int hashCode() {
+			return chunk.hashCode() + getClass().hashCode();
+		}
+
+		public void internChunk() {
+			chunk = chunk.intern();
+		}
+	}
+
+	private static Stringable getStringable(String name,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) {
+		Chunk chunk = dynamicArgs.get(name);
+		if( chunk != null )
+			return new StringableChunk(chunk);
+		String s = staticArgs.get(name);
+		return new StringableString(s);
+	}
+
+	private static final class GetVar implements Chunk {
+		private final Macro macro;
+		private final Stringable name;
+
+		GetVar(MacroScope macroScope,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) {
+			macroScope.hasVars = true;
+			this.macro = macroScope.macro;
+			this.name = getStringable("name",staticArgs,dynamicArgs);
+		}
+
+		public void run(IPrintWriter out,RunState runState) {
+			String nameS = name.toString(runState);
+			String valueS = runState.getVars(macro).get(nameS);
+			out.print(valueS);
+		}
+
+		public boolean hasOutput() {
+			return true;
+		}
+
+		public String toString() {
+			return "{GetVar}";
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( !(obj instanceof GetVar) )
+				return false;
+			GetVar v = (GetVar)obj;
+			return v.macro == macro && v.name.equals(name);
+		}
+
+		@Override public int hashCode() {
+			int h = macro.hashCode();
+			h = h * 31 + name.hashCode();
+			return h;
+		}
+
+		@Override public Chunk intern() {
+			name.internChunk();
+			return interner.intern(this);
+		}
+	}
+
+	private static final class SetVar implements Chunk {
+		private final Macro macro;
+		private final Stringable name;
+		private final Stringable value;
+
+		SetVar(MacroScope macroScope,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) {
+			macroScope.hasVars = true;
+			this.macro = macroScope.macro;
+			this.name = getStringable("name",staticArgs,dynamicArgs);
+			this.value = getStringable("value",staticArgs,dynamicArgs);
+		}
+
+		public void run(IPrintWriter out,RunState runState) {
+			String nameS = name.toString(runState);
+			String valueS = value.toString(runState);
+			runState.getVars(macro).put(nameS,valueS);
+		}
+
+		public boolean hasOutput() {
+			return false;
+		}
+
+		public String toString() {
+			return "{SetVar}";
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( !(obj instanceof SetVar) )
+				return false;
+			SetVar v = (SetVar)obj;
+			return v.macro == macro && v.name.equals(name) && v.value.equals(value);
+		}
+
+		@Override public int hashCode() {
+			int h = macro.hashCode();
+			h = h * 31 + name.hashCode();
+			h = h * 31 + value.hashCode();
+			return h;
+		}
+
+		@Override public Chunk intern() {
+			name.internChunk();
+			value.internChunk();
+			return interner.intern(this);
+		}
+	}
+
+
+	private static final Set<JavaCommand> badMethods = new CopyOnWriteArraySet<JavaCommand>();
+	private static final Map<JavaCommand,Set<String>> badParams = new ConcurrentHashMap<JavaCommand,Set<String>>();
+
+	private static class CompileTimeIntepreterException extends RuntimeException {}
+
+	private static class CompileTimeArgException extends RuntimeException {
+		final String argName;
+
+		CompileTimeArgException(String argName) {
+			this.argName = argName;
+		}
+	}
+
+	private static class CompileTimeScopedException extends RuntimeException {
+
+		CompileTimeScopedException(CompileTimeIntepreterException e) {
+			super(e);
+		}
+	}
+
+	private static class ChunkWrapper {
+		final String argName;
+		final Chunk chunk;
+
+		ChunkWrapper(String argName,Chunk chunk) {
+			this.argName = argName;
+			this.chunk = chunk;
+		}
+
+		public String toString() {
+			throw new CompileTimeArgException(argName);
+		}
+	}
+
+	static abstract class Traceable {
+		StackTrace stackTrace = new StackTrace();
+
+		Traceable(Compiler compiler) {
+			compiler.traceable = this;
+			compiler.traceables.add(this);
+		}
+
+		private void cleanUp() {
+			Collections.reverse(stackTrace);
+			stackTrace = stackTrace.intern();
+		}
+	}
+
+	static boolean nestedEquals(Map<String,Chunk> map1,Map<String,Chunk> map2) {
+		if( map1.size() != map2.size() )
+			return false;
+		for( Map.Entry<String,Chunk> entry : map1.entrySet() ) {
+			if( entry.getValue() != map2.get(entry.getKey()) )
+				return false;
+		}
+		return true;
+	}
+
+	private static class Block extends Traceable implements Chunk {
+		final JavaCall method;
+		final Map<Class,Integer> inStack;
+		private final Map<String,String> staticArgs;
+		private Map<String,Chunk> dynamicArgs;
+		private final boolean hasOutput;
+		private int hash = 0;
+
+		Block(Compiler compiler,JavaCall method,Map<Class,Integer> inStack,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs) {
+			this(compiler,method,inStack,staticArgs,dynamicArgs,Collections.<String>emptySet());
+		}
+
+		Block(Compiler compiler,JavaCall method,Map<Class,Integer> inStack,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs,Set<String> scopedArgs) {
+			super(compiler);
+			this.method = method;
+			this.inStack = CollectionUtils.optimizeMap(inStack);
+			this.staticArgs = CollectionUtils.optimizeMap(staticArgs);
+			this.dynamicArgs = CollectionUtils.optimizeMap(dynamicArgs);
+			Set<String> outputtedParameters = method.cmdSpec().outputtedParameters;
+			if( outputtedParameters == null ) {
+				this.hasOutput = true;
+			} else {
+				boolean hasOutput = false;
+				Set<String> staticArgNames = staticArgs.keySet();
+				for( String s : outputtedParameters ) {
+					if( staticArgNames.contains(s) ) {
+						hasOutput = true;
+						break;
+					}
+					Chunk chunk = dynamicArgs.get(s);
+					if( chunk == null ) {
+						if( scopedArgs.contains(s) ) {
+							hasOutput = true;
+							break;
+						}
+					} else if( chunk.hasOutput() ) {
+						hasOutput = true;
+						break;
+					}
+				}
+				this.hasOutput = hasOutput;
+			}
+		}
+
+		private void invoke(RunState runState,IPrintWriter out,InterpreterImpl interp)
+			throws IllegalAccessException, InvocationTargetException
+		{
+			try {
+				method.invoke(runState,out,interp);
+			} finally {
+				interp.close();
+			}
+		}
+
+		private Chunk prerun(RunState runState)
+			throws CompileException
+		{
+			try {
+				JavaCommand javaCommand = method.javaCommand;
+				if( !javaCommand.isStatic || badMethods.contains(javaCommand) )
+					return null;
+				{
+					Set<String> params = badParams.get(javaCommand);
+					if( params != null ) {
+						Set<String> chunkSet = dynamicArgs.keySet();
+						for( String param : params ) {
+							if( chunkSet.contains(param) )
+								return null;
+						}
+					}
+				}
+				CompilerPrintWriter out = new CompilerPrintWriter(new CharArrayWriter());
+//				RunState runState = new CompileTimeRunState();
+				try {
+					Map<String,Object> args = new HashMap<String,Object>(staticArgs);
+					for( Map.Entry<String,Chunk> entry : dynamicArgs.entrySet() ) {
+						String argName = entry.getKey();
+						Chunk argValue = entry.getValue();
+						args.put( argName, new ChunkWrapper(argName,argValue) );
+					}
+					invoke(runState,out,newInterpreter(runState,args));
+					out.close();
+					return Compiler.consolidate(out.chunks);
+				} catch(InvocationTargetException e) {
+					Throwable cause = e.getCause();
+					if( cause instanceof CompileTimeIntepreterException ) {
+						badMethods.add(javaCommand);
+						logger.debug("bad method: "+javaCommand.method,cause);
+					} else if( cause instanceof CompileTimeArgException ) {
+						CompileTimeArgException ex = (CompileTimeArgException)cause;
+						Set<String> params = badParams.get(javaCommand);
+						if( params == null ) {
+							params = new CopyOnWriteArraySet<String>();
+							badParams.put(javaCommand,params);
+						}
+						params.add(ex.argName);
+//						logger.debug("bad argument '"+ex.argName+"' in "+javaCommand.method);
+					} else if( cause instanceof CompileTimeScopedException ) {
+						logger.debug("ignoring: "+javaCommand.method,cause);
+					} else {
+						throw e;
+					}
+				}
+				return null;
+			} catch(InvocationTargetException e) {
+				throw compileFix(e);
+			} catch(IllegalAccessException e) {
+				throw new TemplateRuntimeException(e);
+			}
+		}
+
+		public void run(IPrintWriter out,RunState runState) {
+			Stack<StackTrace> stack = StackTrace.stack();
+			stack.push(stackTrace);
+			try {
+				if( dynamicArgs.isEmpty() ) {
+					invoke(runState,out,newInterpreter(runState,staticArgs));
+				} else {
+					Map<String,Object> args = new HashMap<String,Object>(staticArgs);
+					for( Map.Entry<String,Chunk> entry : dynamicArgs.entrySet() ) {
+						args.put( entry.getKey(), new BlockWrapper(entry.getValue(),runState) );
+					}
+					invoke(runState,out,newInterpreter(runState,args));
+				}
+			} catch(IllegalAccessException e) {
+				throw new TemplateRuntimeException(e);
+			} catch(InvocationTargetException e) {
+				throw interpFix(e);
+			} finally {
+				stack.pop();
+			}
+		}
+
+		public boolean hasOutput() {
+			return hasOutput;
+		}
+
+		InterpreterImpl newInterpreter(RunState runState,Map<String,?> args) {
+			return new InterpreterImpl(method.cmdSpec(),runState,inStack,args);
+		}
+
+		public String toString() {
+			return "{Block: "+stackTrace.peek()+"}";
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( this==obj )
+				return true;
+			if( !(obj instanceof Block) )
+				return false;
+			return equals((Block)obj);
+		}
+
+		boolean equals(Block block) {
+			return block.method.equals(method)
+				&& block.inStack.equals(inStack)
+				&& block.staticArgs.equals(staticArgs)
+				&& nestedEquals(block.dynamicArgs,dynamicArgs)
+				&& block.getClass().equals(getClass())
+			;
+		}
+
+		@Override public int hashCode() {
+			if( hash == 0 )
+				hash = calcHash();
+			return hash;
+		}
+
+		int calcHash() {
+			int h = method.hashCode();
+			h = h * 31 + inStack.hashCode();
+			h = h * 31 + staticArgs.hashCode();
+			h = h * 31 + dynamicArgs.hashCode();
+			return h;
+		}
+
+		void internMembers() {
+			Map<String,Chunk> m = new HashMap<String,Chunk>();
+			for( Map.Entry<String,Chunk> entry : dynamicArgs.entrySet() ) {
+				m.put(entry.getKey(),entry.getValue().intern());
+			}
+			dynamicArgs = CollectionUtils.optimizeMap(m);
+		}
+
+		@Override public final Chunk intern() {
+			internMembers();
+			return interner.intern(this);
+		}
+	}
+
+	private static final class Scoped extends Block {
+		private Map<String,Chunk> scopedArgs;
+
+		Scoped(Compiler compiler,JavaCall method,Map<Class,Integer> inStack,Map<String,String> staticArgs,Map<String,Chunk> dynamicArgs,Map<String,Chunk> scopedArgs) {
+			super(compiler,method,inStack,staticArgs,dynamicArgs,scopedArgs.keySet());
+			this.scopedArgs = CollectionUtils.optimizeMap(scopedArgs);
+		}
+
+		InterpreterImpl newInterpreter(RunState runState,Map<String,?> args) {
+			return new ScopedInterpreterImpl<Object>(method.cmdSpec(),runState,inStack,args,scopedArgs);
+		}
+
+		boolean equals(Block block) {
+			if( !super.equals(block) )
+				return false;
+			Scoped scoped = (Scoped)block;
+			return nestedEquals(scoped.scopedArgs,scopedArgs);
+		}
+
+		int calcHash() {
+			return super.calcHash() * 31 + scopedArgs.hashCode();
+		}
+
+		void internMembers() {
+			super.internMembers();
+			Map<String,Chunk> m = new HashMap<String,Chunk>();
+			for( Map.Entry<String,Chunk> entry : scopedArgs.entrySet() ) {
+				m.put(entry.getKey(),entry.getValue().intern());
+			}
+			scopedArgs = CollectionUtils.optimizeMap(m);
+		}
+	}
+
+	private static final class Subroutine extends Traceable implements Chunk {
+		private final Map<String,Stringable> argMap;
+		private final int[] aiStack;
+		private final GenericNamespace[] base;
+		private final String commandName;
+		private Template template = null;
+		private int hash = 0;
+
+		Subroutine(Compiler compiler,Map<String,Stringable> argMap,int[] aiStack,GenericNamespace[] base,String commandName) {
+			super(compiler);
+			this.argMap = argMap;
+			this.aiStack = aiStack;
+			this.base = base;
+			this.commandName = commandName;
+		}
+
+		public void run(IPrintWriter out,RunState runState) {
+			Stack<StackTrace> stackTraces = StackTrace.stack();
+			stackTraces.push(stackTrace);
+			try {
+				synchronized(this) {
+					if( template == null ) {
+						try {
+							template = runState.template().program().getTemplate(commandName,base);
+						} catch(CompileException e) {
+							throw new TemplateRuntimeException(e);
+						}
+						if( template == null )
+							throw new TemplateRuntimeException("couldn't find: "+commandName);
+					}
+				}
+				Map<String,Object> args = new HashMap<String,Object>();
+				for( Map.Entry<String,Stringable> entry : argMap.entrySet() ) {
+					String value = entry.getValue().toString(runState);
+					if( value != null )
+						args.put( entry.getKey(), value );
+				}
+				Object[] stack = new Object[aiStack.length];
+				for( int i=0; i<aiStack.length; i++ ) {
+					stack[i] = runState.getFromStack(aiStack[i]);
+				}
+				template.run( runState.callDepth()+1, runState.getEncoder(), out, args, stack );
+			} finally {
+				stackTraces.pop();
+			}
+		}
+
+		public boolean hasOutput() {
+			return true;
+		}
+
+		public String toString() {
+			return "{Subroutine: "+commandName+"}";
+		}
+/*
+		@Override public boolean equals(Object obj) {
+			if( this==obj )
+				return true;
+			if( !(obj instanceof Subroutine) )
+				return false;
+			Subroutine sub = (Subroutine)obj;
+			return sub.commandName.equals(commandName)
+				&& sub.argMap.equals(argMap)
+				&& Arrays.equals(sub.aiStack,aiStack)
+				&& Arrays.equals(sub.base,base)
+			;
+		}
+
+		@Override public int hashCode() {
+			if( hash == 0 ) {
+				int h = commandName.hashCode();
+				h = h * 31 + argMap.hashCode();
+				h = h * 31 + Arrays.hashCode(aiStack);
+				h = h * 31 + Arrays.hashCode(base);
+				hash = h;
+			}
+			return hash;
+		}
+*/
+		@Override public final Chunk intern() {
+			for( Stringable s : argMap.values() ) {
+				s.internChunk();
+			}
+			return interner.intern(this);
+		}
+	}
+
+	private static CompileException compileFix(InvocationTargetException e) {
+		Throwable t = e.getCause();
+		if( t instanceof Error )
+			throw (Error)t;
+		if( t instanceof TemplateRuntimeException )
+			throw (TemplateRuntimeException)t;
+		if( t instanceof CompileException )
+			return (CompileException)t;
+		throw new TemplateRuntimeException((Exception)t);
+	}
+
+
+	// from old CompilerDataImpl
+/*
+	private Chunk runHandler(Block block)
+		throws CompileException
+	{
+		JavaCommand javaCommand = block.method.javaCommand;
+		if( !javaCommand.isStatic || badMethods.contains(javaCommand) )
+			return null;
+		CompilerPrintWriter writer = new CompilerPrintWriter(new CharArrayWriter());
+		RunState runState = new CompileTimeRunState();
+		try {
+			block.run(writer,runState);
+			writer.close();
+			return Compiler.consolidate(writer.chunks);
+		} catch(CompileTimeIntepreterException e) {
+			badMethods.add(javaCommand);
+			logger.info("bad method: "+javaCommand.method);
+		} catch(CompileTimeArgException e) {
+//e.printStackTrace();
+			// nothing
+		}
+		return null;
+	}
+*/
+	private static class CompilerPrintWriter extends TemplatePrintWriter {
+		private final CharArrayWriter sw;
+		private boolean isNull = false;
+		final List<Chunk> chunks = new ArrayList<Chunk>();
+
+		CompilerPrintWriter(CharArrayWriter sw) {
+			super(sw);
+			this.sw = sw;
+		}
+
+		public void print(Object obj) {
+			if( obj == null )
+//				return;
+				throw new RuntimeException("why");
+			if( obj instanceof ChunkWrapper ) {
+				addStringChunk();
+				chunks.add( ((ChunkWrapper)obj).chunk );
+			} else if( obj instanceof BlockWrapper ) {  // should only happen for scoped args
+				try {
+					super.print(obj);
+				} catch(CompileTimeIntepreterException e) {
+					throw new CompileTimeScopedException(e);
+				}
+			} else {
+				super.print(obj);
+			}
+		}
+
+		public void print(String s) {
+			if( s == null && sw.size() == 0 && chunks.isEmpty() && !isNull ) {
+				isNull = true;
+				return;
+			}
+			super.print(s);
+		}
+
+		public void close() {
+			super.close();
+			addStringChunk();
+			if( isNull ) {
+				if( !chunks.isEmpty() )
+					throw new NullPointerException("null written to stream");
+				chunks.add(Chunk.NULL);
+			}
+		}
+
+		private void addStringChunk() {
+			if( sw.size() > 0 ) {
+				chunks.add( new StringChunk(sw.toString()) );
+				sw.reset();
+			}
+		}
+	}
+
+	private final class CompileTimeRunState implements RunState {
+		private final Stack<Object> stack = new ArrayStack<Object>();
+
+		CompileTimeRunState() {
+			int n = Compiler.this.stack.size();
+			while( n-- > 0 ) {
+				stack.push(null);
+			}
+		}
+
+		public Template template() {
+			throw new CompileTimeIntepreterException();
+		}
+
+		public Program program() {
+			return program;
+		}
+
+		public int callDepth() {
+			throw new CompileTimeIntepreterException();
+		}
+
+		public void putArg(String name,String value) {
+			throw new CompileTimeIntepreterException();
+		}
+
+		public String getArg(String name) {
+			throw new CompileTimeIntepreterException();
+		}
+
+		public Object getNamespace(String key) {
+			throw new CompileTimeIntepreterException();
+		}
+
+		public String saveNamespace(Object namespace) {
+			throw new CompileTimeIntepreterException();
+		}
+
+		public Object getFromStack(int i) {
+			if( i < 0 )
+				i = stack.size() + i;
+			Object obj = stack.get(i);
+			if( obj == null )
+				throw new CompileTimeIntepreterException();
+			return obj;
+		}
+
+		public int push(Object scope) {
+			return RunStateImpl.push(stack,program.extensionMap(),scope);
+		}
+
+		public void pop(int n) {
+			RunStateImpl.pop(stack,n);
+		}
+
+		public boolean hasNamespace(String namespace) {
+			return Compiler.this.hasNamespace(namespace);
+		}
+
+		public boolean isInCommandStack(String commandName) {
+			for( StackTraceElement stackElem : stackTrace ) {
+				if( commandName.equals(stackElem.commandName()) )
+					return true;
+			}
+			return false;
+		}
+
+		public Encoder getEncoder() {
+			return Encoder.TEXT;
+		}
+
+		public void setEncoder(Encoder encoder) {
+			if( encoder != Encoder.TEXT )
+				throw new CompileTimeIntepreterException();
+		}
+
+		public Map<String,String> getVars(Macro macro) {
+			throw new CompileTimeIntepreterException();
+		}
+
+		public void pushVars(Macro macro) {
+			throw new CompileTimeIntepreterException();
+		}
+
+		public void popVars(Macro macro) {
+			throw new CompileTimeIntepreterException();
+		}
+
+	}
+
+	static RuntimeException interpFix(InvocationTargetException e) {
+		Throwable t = e.getCause();
+		if( t instanceof Error )
+			throw (Error)t;
+		if( t instanceof TemplateRuntimeException )
+			return (TemplateRuntimeException)t;
+		if( t instanceof ExitException )
+			return (ExitException)t;
+		return new TemplateRuntimeException((Exception)t);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Encoder.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,27 @@
+package nabble.naml.compiler;
+
+import fschmidt.util.java.HtmlUtils;
+
+
+public interface Encoder {
+	public String encode(String s);
+
+	public static final Encoder TEXT = new Encoder() {
+		public String encode(String s) {
+			return s;
+		}
+	};
+
+	public static final Encoder HTML = new Encoder() {
+		public String encode(String s) {
+			return HtmlUtils.htmlEncode(s);
+		}
+	};
+
+	public static final Encoder URL = new Encoder() {
+		public String encode(String s) {
+			return HtmlUtils.urlEncode(s);
+		}
+	};
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/ExitException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,4 @@
+package nabble.naml.compiler;
+
+
+public final class ExitException extends RuntimeException {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/GenericNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,11 @@
+package nabble.naml.compiler;
+
+import java.util.List;
+
+
+interface GenericNamespace {
+	public boolean isGlobal();
+	public boolean isTransparent();
+	public List<String> names();
+	public String getId();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/IPrintWriter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,18 @@
+package nabble.naml.compiler;
+
+
+/*
+PrintWriter should have been an interface.  Because the java.io library is poorly designed, I have to hack this interface.
+*/
+
+public interface IPrintWriter {
+	public PrintWriter getPrintWriter();
+	public void close();
+	public void print(Object obj);
+	public void print(String s);
+	public void print(boolean b);
+	public void print(char c);
+	public void print(Boolean b);
+    public void flush();
+	// add more as needed
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Interpreter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,24 @@
+package nabble.naml.compiler;
+
+
+public interface Interpreter extends Encoder {
+	public Object getArg(String param);
+	public String getArgString(String param);
+	public Template template();
+	public <T> T getFromStack(Class<T> cls);
+	public boolean hasModule(String moduleName);
+	public int callDepth();
+
+	public boolean getArgAsBoolean(String param) throws BooleanFormatException;
+	public boolean getArgAsBoolean(String param,boolean defaultValue) throws BooleanFormatException;
+	public int getArgAsInt(String param) throws NumberFormatException;
+	public int getArgAsInt(String param, int defaultValue) throws NumberFormatException;
+	public long getArgAsLong(String param) throws NumberFormatException;
+	public <T> T getArgAsNamespace(Class<T> cls,String param);
+	public Encoder getEncoder();
+	public void setEncoder(Encoder encoder);
+
+	// compile-time methods
+	public boolean hasNamespace(String namespace);
+	public boolean isInCommandStack(String commandName);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/InterpreterImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,141 @@
+package nabble.naml.compiler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+
+class InterpreterImpl implements Interpreter {
+	private static final Logger logger = LoggerFactory.getLogger(Compiler.class);
+
+	final CommandSpec cmdSpec;
+	final RunState runState;
+	private final Map<Class,Integer> inStack;
+	private final Map<String,?> args;
+	private final Encoder oldEncoder;
+
+	InterpreterImpl(CommandSpec cmdSpec,RunState runState,Map<Class,Integer> inStack,Map<String,?> args) {
+		this.cmdSpec = cmdSpec;
+		this.runState = runState;
+		this.inStack = inStack;
+		this.args = args;
+		this.oldEncoder = runState.getEncoder();
+	}
+
+	public Object getArg(String param) {
+		return args.get(param);
+	}
+
+	public String getArgString(String param) {
+		return Template.stringValue( getArg(param) );
+	}
+
+	public Template template() {
+		return runState.template();
+	}
+
+	public <T> T getFromStack(Class<T> cls) {
+		Integer i = inStack.get(cls);
+		if( i == null )
+			throw new RuntimeException(""+cls+" not found in stack");
+		return cls.cast(runState.getFromStack(i));
+	}
+
+	public int callDepth() {
+		return runState.callDepth();
+	}
+
+	public final <T> T getArgAsNamespace(Class<T> cls,String param) {
+		String s = getArgString(param);
+		if( s == null )
+			return null;
+		return cls.cast(runState.getNamespace(s));
+	}
+
+	public boolean hasNamespace(String namespace) {
+		return runState.hasNamespace(namespace);
+	}
+
+	public boolean isInCommandStack(String commandName) {
+		return runState.isInCommandStack(commandName);
+	}
+
+
+	// from AbstractInterpreter
+
+	public final boolean hasModule(String moduleName) {
+		return runState.program().moduleNames().contains(moduleName);
+	}
+
+	public final boolean getArgAsBoolean(String param)
+		throws BooleanFormatException
+	{
+		try {
+			return Template.booleanValue( getArg(param) );
+		} catch(BooleanFormatException e) {
+			throw new BooleanFormatException( newMsg(param,e) );
+		}
+	}
+
+	public final boolean getArgAsBoolean(String param,boolean defaultValue)
+		throws BooleanFormatException
+	{
+		try {
+			return Template.booleanValue( getArg(param), defaultValue );
+		} catch(BooleanFormatException e) {
+			throw new BooleanFormatException( newMsg(param,e) );
+		}
+	}
+
+	public final int getArgAsInt(String param)
+		throws NumberFormatException
+	{
+		try {
+			return Template.intValue( getArg(param) );
+		} catch(NumberFormatException e) {
+			throw new NumberFormatException( newMsg(param,e) );
+		}
+	}
+
+	public int getArgAsInt(String param, int defaultValue)
+		throws NumberFormatException
+	{
+		try {
+			return Template.intValue( getArg(param), defaultValue );
+		} catch(NumberFormatException e) {
+			throw new NumberFormatException( newMsg(param,e) );
+		}
+	}
+
+	public final long getArgAsLong(String param)
+		throws NumberFormatException
+	{
+		try {
+			return Template.longValue( getArg(param) );
+		} catch(NumberFormatException e) {
+			throw new NumberFormatException( newMsg(param,e) );
+		}
+	}
+
+	private static String newMsg(String param,Exception e) {
+		return "For argument: \"" + param + "\" - " + e.getMessage();
+	}
+
+	public String encode(String s) {
+		return s==null ? null : getEncoder().encode(s);
+	}
+
+	public Encoder getEncoder() {
+		return runState.getEncoder();
+	}
+
+	public void setEncoder(Encoder encoder) {
+		runState.setEncoder(encoder);
+	}
+
+	void close() {
+		runState.setEncoder(oldEncoder);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/JavaCall.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,76 @@
+package nabble.naml.compiler;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+import java.util.Collection;
+
+
+final class JavaCall implements Call {
+	final JavaCommand javaCommand;
+	final int iStack;
+
+	JavaCall(JavaCommand javaCommand,int iStack) {
+		this.javaCommand = javaCommand;
+		this.iStack = iStack;
+	}
+
+	@Override public Collection<String> getRequiredNamespaces() {
+		return javaCommand.getRequiredNamespaces();
+	}
+
+	Object invoke(RunState runState, Object... args)
+		throws IllegalAccessException, InvocationTargetException
+	{
+		Object obj = javaCommand.isStatic ? null : runState.getFromStack(iStack);
+		return javaCommand.method.invoke(obj,args);
+	}
+
+	Method getMethod() {
+		return javaCommand.method;
+	}
+
+	boolean isScoped() {
+		return javaCommand.scopedType != null;
+	}
+
+	boolean isScoped(String param) {
+		return isScoped() && javaCommand.cmdSpec.hasScopedParam(param);
+	}
+
+	Class scopedType() {
+		return javaCommand.scopedType;
+	}
+
+	String getJavaName() {
+		return javaCommand.method.getName();
+	}
+
+	public String toString() {
+		return javaCommand.method.toString() + " - stack index "+iStack;
+	}
+
+	String name() {
+		return javaCommand.name;
+	}
+
+	CommandSpec cmdSpec() {
+		return javaCommand.cmdSpec;
+	}
+
+	Method method() {
+		return javaCommand.method;
+	}
+
+	@Override public boolean equals(Object obj) {
+		if( !(obj instanceof JavaCall) )
+			return false;
+		JavaCall jc = (JavaCall)obj;
+		return jc.javaCommand==javaCommand && jc.iStack==iStack;
+	}
+
+	@Override public int hashCode() {
+		return iStack*31 + javaCommand.hashCode();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/JavaCommand.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,155 @@
+package nabble.naml.compiler;
+
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.Interner;
+import fschmidt.util.java.Memoizer;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+
+public final class JavaCommand implements Meaning {
+	final String name;
+	final Method method;
+	final Class scopedType;
+	final CommandSpec cmdSpec;
+	final boolean isStatic;
+	private final String id;
+
+	private JavaCommand(String name,Method method) {
+		this.name = name;
+		this.method = method;
+		this.scopedType = getScopedType();
+		this.cmdSpec = getCommandSpec();
+		this.isStatic = Modifier.isStatic(method.getModifiers());
+		this.id = method.getDeclaringClass().getName() + '.' + name;
+	}
+
+	@Override public String toString() {
+		return "{JavaCommand id=" + id + " method=[" + method + "]}";
+	}
+
+	@Override public String getName() {
+		return name;
+	}
+
+	@Override public String getId() {
+		return id;
+	}
+
+	@Override public Collection<String> getRequiredNamespaces() {
+		return Collections.singleton(JavaNamespace.getNamespace(method.getDeclaringClass()).name);
+	}
+
+	public String addsNamespace() {
+		if( scopedType == null )
+			return null;
+		return JavaNamespace.getNamespace(scopedType).name;
+	}
+
+	public Method getMethod() {
+		return method;
+	}
+
+	private Class getScopedType() {
+		if( !ScopedInterpreter.class.isAssignableFrom(method.getParameterTypes()[1]) )
+			return null;
+		ParameterizedType pt = (ParameterizedType)method.getGenericParameterTypes()[1];
+		return getClass(pt.getActualTypeArguments()[0]);
+	}
+
+	private static Class getClass(Type type) {
+		if( type instanceof ParameterizedType ) {
+			ParameterizedType pt = (ParameterizedType)type;
+			return (Class)pt.getRawType();
+		}
+		return (Class)type;
+	}
+
+	private CommandSpec getCommandSpec() {
+		try {
+			Field f = method.getDeclaringClass().getDeclaredField(method.getName());
+//			f.setAccessible(true);
+			if( !f.getType().equals(CommandSpec.class) )
+				throw new RuntimeException("field "+f+" which matches "+method+" must be a CommandSpec");
+			if( !Modifier.isStatic(f.getModifiers()) )
+				throw new RuntimeException("field "+f+" which matches "+method+" must be static");
+			return (CommandSpec)f.get(null);
+		} catch(IllegalAccessException e) {
+			throw new TemplateRuntimeException(e);
+		} catch(NoSuchFieldException e) {
+			return CommandSpec.EMPTY;
+		}
+	}
+
+	@Override public boolean equals(Object obj) {
+		if( !(obj instanceof JavaCommand) )
+			return false;
+		JavaCommand jc = (JavaCommand)obj;
+		return jc.method.equals(method);
+	}
+
+	@Override public int hashCode() {
+		return method.hashCode();
+	}
+
+	private static final Interner<JavaCommand> interner = new Interner<JavaCommand>();
+
+	private static final Memoizer<Class,Map<String,JavaCommand>> cache = new Memoizer<Class,Map<String,JavaCommand>>(new Computable<Class,Map<String,JavaCommand>>() {
+		public Map<String,JavaCommand> get(Class cls) {
+			Map<String,JavaCommand> map = new HashMap<String,JavaCommand>();
+			for( Method m : cls.getMethods() ) {
+				Command command = m.getAnnotation(Command.class);
+				if( command == null )
+					continue;
+				String name = command.value();
+				if( name.length()==0 )
+					name = m.getName();
+				if( m.getReturnType() != Void.TYPE )
+					throw new RuntimeException("command "+m+" doesn't return void");
+				Class<?>[] params = m.getParameterTypes();
+				if( params.length != 2 )
+					throw new RuntimeException("command "+m+" should have 2 params");
+				if( !params[0].equals(IPrintWriter.class) )
+					throw new RuntimeException("first param of command "+m+" should be of type IPrintWriter");
+				if( !Interpreter.class.isAssignableFrom(params[1]) )
+					throw new RuntimeException("first param of command "+m+" should be of type Interpreter");
+				JavaCommand jc = interner.intern(new JavaCommand(name,m));
+				JavaCommand old = map.put(name,jc);
+				if( old != null )
+					throw new RuntimeException("duplicate commands "+old.method+" and "+m);
+			}
+			return map;
+		}
+	});
+
+	static JavaCommand getJavaCommand(Class cls,String name) {
+		return cache.get(cls).get(name);
+	}
+
+	public String[] getParameterNames() {
+		return getCommandSpec().getParameters().toArray(new String[0]);
+	}
+
+	public String getDotParameterName() {
+		return getCommandSpec().dotParameter;
+	}
+
+	public String[] getRequiredParameterNames() {
+		return getCommandSpec().requiredParameters.toArray(new String[0]);
+	}
+
+	private static final Pattern ID_PTN = Pattern.compile("([^.!]+\\.)+[^.!]+");
+
+	public static boolean isJavaCommandId(String id) {
+		return ID_PTN.matcher(id).matches();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/JavaNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,105 @@
+package nabble.naml.compiler;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.List;
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.Memoizer;
+
+
+final class JavaNamespace implements GenericNamespace {
+	final Class cls;
+	private final boolean isGlobal;
+	private final boolean isTransparent;
+	final String name;
+	final Class extensionTarget;
+	final Constructor extensionConstructor;
+	private final List<String> names = new ArrayList<String>();
+
+	private JavaNamespace(Class<?> cls,Namespace namespace) {
+		this.cls = cls;
+		this.name = namespace.name();
+		this.isGlobal = namespace.global();
+		this.isTransparent = namespace.transparent();
+		this.extensionTarget = null;
+		this.extensionConstructor = null;
+		addNames(cls);
+	}
+
+	private JavaNamespace(Class<?> cls,NamespaceExtension namespaceExt) {
+		this.cls = cls;
+		this.name = namespaceExt.name();
+		this.extensionTarget = namespaceExt.target();
+		this.isGlobal = getNamespace(extensionTarget).isGlobal;
+		this.isTransparent = true;
+		try {
+			this.extensionConstructor = cls.getConstructor(extensionTarget);
+		} catch(NoSuchMethodException e) {
+			throw new RuntimeException(e);
+		}
+		addNames(cls);
+	}
+
+	@Override public boolean isGlobal() {
+		return isGlobal;
+	}
+
+	@Override public boolean isTransparent() {
+		return isTransparent;
+	}
+
+	@Override public List<String> names() {
+		return names;
+	}
+
+	@Override public String getId() {
+		return cls.getName();
+	}
+
+	private void addNames(Class<?> c) {
+		if( c==null )
+			return;
+		Namespace namespace = c.getAnnotation(Namespace.class);
+		if( namespace != null ) {
+			names.add( namespace.name() );
+		}
+		NamespaceExtension namespaceExt = c.getAnnotation(NamespaceExtension.class);
+		if( namespaceExt != null )
+			names.add(namespaceExt.name());
+		addNames(c.getSuperclass());
+/*
+		for( Class ifc : c.getInterfaces() ) {
+			addNames(ifc);
+		}
+*/
+	}
+
+	public String toString() {
+		return name;
+	}
+
+
+	private static Memoizer<Class<?>,JavaNamespace> cache = new Memoizer<Class<?>,JavaNamespace>(new Computable<Class<?>,JavaNamespace>() {
+		public JavaNamespace get(Class<?> cls) {
+			Namespace namespace = cls.getAnnotation(Namespace.class);
+			if( namespace != null )
+				return new JavaNamespace(cls,namespace);
+			NamespaceExtension namespaceExt = cls.getAnnotation(NamespaceExtension.class);
+			if( namespaceExt != null )
+				return new JavaNamespace(cls,namespaceExt);
+			throw new TemplateRuntimeException(""+cls+" isn't annotated as a Namespace or NamespaceExtension");
+		}
+	});
+
+	static JavaNamespace getNamespace(Class cls) {
+		return cache.get(cls);
+	}
+
+	static JavaNamespace getNamespaceExt(Class cls) {
+		JavaNamespace ns = getNamespace(cls);
+		if( ns.extensionTarget == null )
+			throw new RuntimeException(""+cls+" isn't NamespaceExtension");
+		return ns;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Macro.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,357 @@
+package nabble.naml.compiler;
+
+import fschmidt.util.java.CollectionUtils;
+import nabble.naml.dom.Attribute;
+import nabble.naml.dom.Cdata;
+import nabble.naml.dom.Container;
+import nabble.naml.dom.Element;
+import nabble.naml.dom.ElementName;
+import nabble.naml.dom.EmptyElement;
+import nabble.naml.dom.Naml;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+
+public final class Macro implements Call, Meaning {
+	private static final Logger logger = LoggerFactory.getLogger(Macro.class);
+
+	public enum Type {
+		MACRO, SUBROUTINE, TRANSLATION, NAMESPACE;
+
+		final String tagName;
+
+		Type() {
+			this.tagName = name().toLowerCase();
+		}
+	};
+
+	static final String OVERRIDE_PREFIX = "override_";
+
+	public final Source source;
+	public final Element element;
+	final String name;
+	final Set<String> parameters = new HashSet<String>();
+	final String dotParam;
+	final List<String> requiredNamespaces;
+	final boolean isOverride;
+	final Naml naml;
+	final Type type;
+	final String extendsNs;
+	private final String id;
+
+	private static final ElementName fromName = new ElementName("from");
+	private static final ElementName toName = new ElementName("to");
+	private static final ElementName doName = new ElementName("n.do");
+
+	Macro(Source source,Element element,StackTrace stackTrace,Map<String,Integer> macroCount) throws CompileException {
+		this.source = source;
+		this.element = element;
+		Map<String,Attribute> attrs = element.attributeMap();
+		String tagName = element.name().parts().get(0).text();
+		if( tagName.startsWith(OVERRIDE_PREFIX) ) {
+			this.isOverride = true;
+			tagName = tagName.substring(OVERRIDE_PREFIX.length());
+		} else {
+			this.isOverride = false;
+		}
+		if( tagName.equals(Type.NAMESPACE.tagName) ) {
+			this.type = Type.NAMESPACE;
+			if( !(element instanceof EmptyElement) )
+				throw new CompileException(stackTrace,element.name()+" must be empty");
+			this.name = asString(attrs.remove("name"),stackTrace);
+			if( name==null )
+				throw new CompileException(stackTrace,"'name' attribute required");
+			this.dotParam = "do";
+			this.parameters.add(dotParam);
+			this.requiredNamespaces = Collections.emptyList();
+			this.naml = new Naml();
+			this.naml.add( new EmptyElement(doName,Collections.<Attribute>emptyList(),"",element.lineNumber()) );
+			this.extendsNs = asString(attrs.remove("extends"),stackTrace);
+		} else if( tagName.equals(Type.TRANSLATION.tagName) ) {
+			this.type = Type.TRANSLATION;
+			if( !attrs.isEmpty() )
+				throw new CompileException(stackTrace,"parameters "+attrs.keySet()+" not allowed in macros/templates");
+			if( !(element instanceof Container) )
+				throw new CompileException(stackTrace,"empty "+element.name()+" not allowed");
+			Container container = (Container)element;
+			this.dotParam = null;
+			this.requiredNamespaces = Collections.emptyList();
+			Iterator<Object> iter = container.contents().iterator();
+			Object obj = iter.next();
+			if( obj instanceof String )
+				obj = iter.next();
+			Container from = (Container)obj;
+			if( !from.name().equals(fromName) )
+				throw new CompileException(stackTrace,"'from' tag expected");
+			obj = iter.next();
+			if( obj instanceof String )
+				obj = iter.next();
+			Container to = (Container)obj;
+			if( !to.name().equals(toName) )
+				throw new CompileException(stackTrace,"'to' tag expected");
+			this.name = translationMacroName(from.contents(),parameters);
+			this.naml = to.contents();
+			this.extendsNs = null;
+		} else {
+			this.type = tagName.equals(Type.SUBROUTINE.tagName) ? Type.SUBROUTINE : Type.MACRO;
+			if( !(element instanceof Container) )
+				throw new CompileException(stackTrace,"empty "+element.name()+" not allowed");
+			Container container = (Container)element;
+			this.name = asString(attrs.remove("name"),stackTrace);
+			if( name==null )
+				throw new CompileException(stackTrace,"'name' attribute required");
+			if( name.indexOf('-') != -1 )
+				throw new CompileException(stackTrace,"macro name may not contain '-'");
+			parseAttrSet(parameters,asString(attrs.remove("parameters"),stackTrace));
+			String dotParam = null;
+			{
+				List<String> dotParamNames = new ArrayList<String>();
+				parseAttrSet(dotParamNames,asString(attrs.remove("dot_parameter"),stackTrace));
+				if( dotParamNames.size() > 1 )
+					throw new CompileException(stackTrace,"only one dot_parameter allowed");
+				if( dotParamNames.size() == 1 ) {
+					dotParam = dotParamNames.get(0);
+					parameters.add(dotParam);
+				}
+			}
+			this.dotParam = dotParam;
+			for( String s : parameters ) {
+				if( s.indexOf('-') != -1 )
+					throw new CompileException(stackTrace,"macro attibutes may not contain '-'");
+			}
+			List<String> requiredNamespaces = new ArrayList<String>();
+			parseAttrSet(requiredNamespaces,asString(attrs.remove("requires"),stackTrace));
+			this.requiredNamespaces = CollectionUtils.optimizeList(requiredNamespaces);
+			boolean unindent = "true".equals(asString(attrs.remove("unindent"),stackTrace));
+			if( type==Type.SUBROUTINE && this.requiredNamespaces.isEmpty() )
+				throw new CompileException(stackTrace,"subroutine must specify required stack");
+			if( !attrs.isEmpty() )
+				throw new CompileException(stackTrace,"parameters "+attrs.keySet()+" not allowed in macros/templates");
+			Naml naml = trim(container.contents());
+			if( unindent )
+				naml = unindent(naml);
+			this.naml = naml;
+			this.extendsNs = null;
+		}
+		{
+			StringBuilder buf = new StringBuilder();
+			buf.append( name );
+			buf.append( '!' ).append( source );
+			Integer count = macroCount.get(this.name);
+			if( count == null ) {
+				macroCount.put(this.name,1);
+			} else {
+				count += 1;
+				macroCount.put(this.name,count);
+				buf.append( '!' ).append( count );
+			}
+			id = buf.toString().intern();
+		}
+	}
+
+	public static String getNameFromId(String id) {
+		return id.substring(0, id.indexOf('!'));
+	}
+
+	public static String getSourceFromId(String id) {
+		int posColon = id.indexOf(':');
+		int posCount = id.indexOf('!', posColon);
+		return id.substring(posColon+1, posCount >= 0? posCount : id.length());
+	}
+
+	private static final Pattern spacePtn = Pattern.compile("\\s+");
+
+	static String translationMacroName(Naml contents,Set<String> parameters) {
+		String text = gutTranslationArgs(contents,parameters).toString();
+		return "translation: " + spacePtn.matcher(text).replaceAll(" ");
+	}
+
+	private static Naml gutTranslationArgs(Naml oldContents,Set<String> parameters) {
+		boolean changed = false;
+		Naml newContents = new Naml();
+		for( Object obj : oldContents ) {
+			if( obj instanceof Element ) {
+				Element element = (Element)obj;
+				ElementName name = element.name();
+				List<ElementName.Part> parts = name.parts();
+				if( parts.size() >= 2 && "t".equals(parts.get(0).text()) ) {
+					changed = true;
+					if( parts.size() > 2 )
+						name = new ElementName( false, parts.subList(0,2) );
+					Element newElement = new EmptyElement(name,Collections.<Attribute>emptyList(),"",element.lineNumber());
+					newContents.add(newElement);
+					if( parameters != null )
+						parameters.add(parts.get(1).text());
+					continue;
+				}
+				if( element instanceof Container ) {
+					Container oldContainer = (Container)element;
+					Naml oldContainerContents = oldContainer.contents();
+					Naml newContainerContents = gutTranslationArgs(oldContainerContents,parameters);
+					if( newContainerContents != oldContainerContents ) {
+						changed = true;
+						Container newContainer = new Container(oldContainer.name(),oldContainer.attributes(),"",oldContainer.lineNumber(),newContainerContents,"");
+						newContents.add(newContainer);
+						continue;
+					}
+				}
+			}
+			newContents.add(obj);
+		}
+		return changed ? newContents : oldContents;
+	}
+
+	private static String asString(Attribute attr,StackTrace stackTrace) throws CompileException {
+		if( attr == null )
+			return null;
+		Naml value = attr.value();
+		if( value.isEmpty() )
+			throw new CompileException(stackTrace,"attribute '"+attr.name()+"' must have a value");
+		Object obj = value.get(0);
+		if( value.size() > 1 || !(obj instanceof String) )
+			throw new CompileException(stackTrace,"attribute '"+attr.name()+"' must have string value");
+		String s = (String)obj;
+		if( s.length()==0 )
+			throw new CompileException(stackTrace,"attribute '"+attr.name()+"' may not be empty");
+		return s;
+	}
+
+	private static void parseAttrSet(Collection<String> col,String attrS) {
+		if( attrS != null ) {
+			for( String s : attrS.split(",") ) {
+				s = s.trim();
+				if( !s.equals("") )
+					col.add(s);
+			}
+		}
+	}
+
+	static Naml trim(Naml naml) {
+		if( naml.size()==0 )
+			return naml;
+		boolean isChanged = false;
+		Object firstObj = naml.get(0);
+		if( firstObj instanceof String ) {
+			String s = (String)firstObj;
+			int i = 0;
+			if( Character.isWhitespace(s.charAt(i)) ) {
+				isChanged = true;
+				naml = new Naml(naml);
+				while( ++i < s.length() && Character.isWhitespace(s.charAt(i)) );
+				if( i == s.length() ) {
+					naml.remove(0);
+					if( naml.size()==0 )
+						return naml;
+				} else {
+					naml.set(0,s.substring(i));
+				}
+			}
+		}
+		int last = naml.size() - 1;
+		Object lastObj = naml.get(last);
+		if( lastObj instanceof String ) {
+			String s = (String)lastObj;
+			int i = s.length() - 1;
+			if( Character.isWhitespace(s.charAt(i)) ) {
+				if( !isChanged )
+					naml = new Naml(naml);
+				while( --i >= 0 && Character.isWhitespace(s.charAt(i)) );
+				if( i == -1 ) {
+					naml.remove(last);
+				} else {
+					naml.set(last,s.substring(0,i+1));
+				}
+			}
+		}
+		return naml;
+	}
+
+	private static final Pattern indentPtn = Pattern.compile("([\\r\\n])[ \\t]+");
+
+	private static Naml unindent(Naml naml) {
+		boolean isChanged = false;
+		Naml newNaml = new Naml(naml);
+		for( ListIterator<Object> iter = newNaml.listIterator(); iter.hasNext(); ) {
+			Object obj = iter.next();
+			if( obj instanceof String ) {
+				String s = (String)obj;
+				String s2 = indentPtn.matcher(s).replaceAll("$1");
+				if( !s.equals(s2) ) {
+					isChanged = true;
+					iter.set(s2);
+				}
+			} else if( obj instanceof Cdata ) {
+				Cdata cdata = (Cdata)obj;
+				String s = cdata.text();
+				String s2 = indentPtn.matcher(s).replaceAll("$1");
+				if( !s.equals(s2) ) {
+					isChanged = true;
+					iter.set(new Cdata(s2));
+				}
+			} else if( obj instanceof Container ) {
+				Container c = (Container)obj;
+				Naml contents = c.contents();
+				Naml contents2 = unindent(contents);
+				if( contents != contents2 ) {
+					isChanged = true;
+					Container c2 = new Container(c.name(),c.attributes(),c.spaceAtEndOfClosingTag(),c.lineNumber(),contents2,c.spaceAtEndOfClosingTag());
+					iter.set(c2);
+				}
+			}
+		}
+		return isChanged ? newNaml : naml;
+	}
+
+	@Override public String getName() {
+		return name;
+	}
+
+	@Override public Collection<String> getRequiredNamespaces() {
+		return requiredNamespaces;
+	}
+
+	@Override public String getId() {
+		return id;
+	}
+
+	@Override public String toString() {
+		return name + " - " + source;
+	}
+
+	public Type getType() {
+		return type;
+	}
+
+	public boolean isOverride() {
+		return isOverride;
+	}
+
+	public String[] getParameterNames() {
+		return parameters.toArray(new String[0]);
+	}
+/*
+	@Override public boolean equals(Object obj) {
+		if( obj == this )
+			return true;
+		if( !(obj instanceof Macro) )
+			return false;
+		Macro m = (Macro)obj;
+		return m.key.equals(key) && m.source==source;
+	}
+
+	@Override public int hashCode() {
+		return key.hashCode() + 31*source.id.hashCode();
+	}
+*/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/MacroNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,73 @@
+package nabble.naml.compiler;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+
+
+final class MacroNamespace implements GenericNamespace {
+	private final Macro macro;
+	private final List<String> names = new ArrayList<String>();
+
+	MacroNamespace(Macro macro,StackTrace stackTrace,Map<String,List<Macro>> macros)
+		throws CompileException
+	{
+		this.macro = macro;
+		while(true) {
+			names.add(macro.name);
+			if( macro.extendsNs == null )
+				break;
+			stackTrace.push( new StackTraceElement(macro) );
+			try {
+				List<Macro> candidates = macros.get(macro.extendsNs);
+				if( candidates==null )
+					throw new CompileException(stackTrace,"'extend' namespace not found: "+macro.extendsNs);
+				for( Iterator<Macro> iter = candidates.iterator(); iter.hasNext(); ) {
+					Macro m = iter.next();
+					if( m.type != Macro.Type.NAMESPACE )
+						iter.remove();
+				}
+				if( candidates.size() == 0 )
+					throw new CompileException(stackTrace,"'extend' namespace not found: "+macro.extendsNs);
+				if( candidates.size() > 1 )
+					throw new CompileException(stackTrace,"duplicate 'extend' namespaces: "+candidates);
+				macro = candidates.get(0);
+			} finally {
+				stackTrace.pop();
+			}
+		}
+	}
+
+	@Override public boolean isGlobal() {
+		return true;
+	}
+
+	@Override public boolean isTransparent() {
+		return false;
+	}
+
+	@Override public List<String> names() {
+		return names;
+	}
+
+	@Override public String getId() {
+		return macro.getId();
+	}
+
+	@Override public boolean equals(Object obj) {
+		if( !(obj instanceof MacroNamespace) )
+			return false;
+		MacroNamespace ns = (MacroNamespace)obj;
+		return names.equals(ns.names);
+	}
+
+	@Override public int hashCode() {
+		return names.hashCode();
+	}
+
+	@Override public String toString() {
+		return names.get(0);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/MacroScope.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,52 @@
+package nabble.naml.compiler;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import nabble.naml.dom.Naml;
+
+
+final class MacroScope {
+	final Macro macro;
+	final MacroScope parentScope;
+	private final Map<String,Naml> args = new HashMap<String,Naml>();
+	private final Map<String,ParamChunk> paramChunks = new HashMap<String,ParamChunk>();
+	boolean hasVars = false;
+
+	private MacroScope(Macro macro,MacroScope parentScope) {
+		this.macro = macro;
+		this.parentScope = parentScope;
+	}
+
+	MacroScope(Macro macro) {
+		this(macro,null);
+	}
+
+	Source source() {
+		return macro.source;
+	}
+
+	void addArg(String name,Naml naml) {
+		args.put(name,naml);
+	}
+
+	void addParamChunk(ParamChunk paramChunk) {
+		paramChunks.put(paramChunk.name,paramChunk);
+	}
+
+	Naml getArg(String name) {
+		return args.get(name);
+	}
+
+	ParamChunk getParamChunk(String name) {
+		return paramChunks.get(name);
+	}
+
+	Set<String> getArgNames() {
+		return args.keySet();
+	}
+
+	MacroScope newScope(Macro macro) {
+		return new MacroScope(macro,this);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Meaning.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,10 @@
+package nabble.naml.compiler;
+
+import java.util.Collection;
+
+
+public interface Meaning {
+	public String getName();
+	public Collection<String> getRequiredNamespaces();
+	public String getId();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Module.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,12 @@
+package nabble.naml.compiler;
+
+import java.util.Collection;
+import java.util.Set;
+
+
+public interface Module {
+	public String getName();
+	public Iterable<Class> getExtensions();
+	public Collection<Source> getSources();
+	public Set<String> getDependencies();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Namespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,12 @@
+package nabble.naml.compiler;
+
+import java.lang.annotation.*;
+
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Namespace {
+	String name();
+	boolean global();
+	boolean transparent() default false;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/NamespaceExtension.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,11 @@
+package nabble.naml.compiler;
+
+import java.lang.annotation.*;
+
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NamespaceExtension {
+	String name();
+	Class target();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/NamlNullPointerException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,10 @@
+package nabble.naml.compiler;
+
+
+public class NamlNullPointerException extends TemplateRuntimeException {
+
+	public NamlNullPointerException(String msg) {
+		super(msg);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/ParamChunk.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,42 @@
+package nabble.naml.compiler;
+
+
+final class ParamChunk implements Chunk {
+	final String name;
+
+	ParamChunk(String name) {
+		this.name = name;
+	}
+	
+	public void run(IPrintWriter out,RunState runState) {
+		String s = runState.getArg(name);
+		if( s != null )
+			s = runState.getEncoder().encode(s);
+		out.print(s);
+	}
+
+	public boolean hasOutput() {
+		return true;
+	}
+
+	@Override public String toString() {
+		return "{ParamChunk: "+name+"}";
+	}
+
+	@Override public boolean equals(Object obj) {
+		if( this==obj )
+			return true;
+		if( !(obj instanceof ParamChunk) )
+			return false;
+		ParamChunk pc = (ParamChunk)obj;
+		return pc.name.equals(name);
+	}
+
+	@Override public int hashCode() {
+		return name.hashCode() + getClass().hashCode();
+	}
+
+	@Override public Chunk intern() {
+		return interner.intern(this);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/ParamMeaning.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,25 @@
+package nabble.naml.compiler;
+
+import java.util.Collections;
+import java.util.Collection;
+
+
+public enum ParamMeaning implements Meaning {
+	INSTANCE;
+
+	@Override public String toString() {
+		return "PARAMETER";
+	}
+
+	@Override public String getName() {
+		return "MACRO PARAMETER";
+	}
+
+	@Override public Collection<String> getRequiredNamespaces() {
+		return Collections.emptySet();
+	}
+
+	@Override public String getId() {
+		return "PARAM";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Primitive.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,4 @@
+package nabble.naml.compiler;
+
+
+public interface Primitive {}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/PrintWriter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1042 @@
+/**
+ * %W% %E%
+ *
+ * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ */
+
+package nabble.naml.compiler;
+
+import java.io.Writer;
+import java.io.PrintStream;
+import java.io.OutputStream;
+import java.io.FileNotFoundException;
+import java.io.UnsupportedEncodingException;
+import java.io.File;
+import java.io.IOException;
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
+import java.io.FileOutputStream;
+import java.io.InterruptedIOException;
+import java.util.Formatter;
+import java.util.Locale;
+
+/**
+ * Prints formatted representations of objects to a text-output stream.  This
+ * class implements all of the <tt>print</tt> methods found in {@link
+ * PrintStream}.  It does not contain methods for writing raw bytes, for which
+ * a program should use unencoded byte streams.
+ *
+ * <p> Unlike the {@link PrintStream} class, if automatic flushing is enabled
+ * it will be done only when one of the <tt>println</tt>, <tt>printf</tt>, or
+ * <tt>format</tt> methods is invoked, rather than whenever a newline character
+ * happens to be output.  These methods use the platform's own notion of line
+ * separator rather than the newline character.
+ *
+ * <p> Methods in this class never throw I/O exceptions, although some of its
+ * constructors may.  The client may inquire as to whether any errors have
+ * occurred by invoking {@link #checkError checkError()}.
+ *
+ * @version 	%I%, %G%
+ * @author	Frank Yellin
+ * @author	Mark Reinhold
+ * @since	JDK1.1
+ */
+
+public class PrintWriter extends Writer implements IPrintWriter {
+
+	// added -fschmidt
+
+	public PrintWriter getPrintWriter() {
+		return this;
+	}
+
+	public void print(Boolean b) {
+		print((Object)b);
+	}
+
+
+    /**
+     * The underlying character-output stream of this
+     * <code>PrintWriter</code>.
+     *
+     * @since 1.2
+     */
+    protected Writer out;
+
+    private boolean autoFlush = false;
+    private boolean trouble = false;
+    private Formatter formatter;
+    private PrintStream psOut = null;
+
+    /**
+     * Line separator string.  This is the value of the line.separator
+     * property at the moment that the stream was created.
+     */
+    private static final String lineSeparator = System.getProperty("line.separator");
+
+    /**
+     * Creates a new PrintWriter, without automatic line flushing.
+     *
+     * @param  out        A character-output stream
+     */
+    public PrintWriter (Writer out) {
+	this(out, false);
+    }
+
+    /**
+     * Creates a new PrintWriter.
+     *
+     * @param  out        A character-output stream
+     * @param  autoFlush  A boolean; if true, the <tt>println</tt>,
+     *                    <tt>printf</tt>, or <tt>format</tt> methods will
+     *                    flush the output buffer
+     */
+    public PrintWriter(Writer out,
+		       boolean autoFlush) {
+	super(out);
+	this.out = out;
+	this.autoFlush = autoFlush;
+    }
+
+    /**
+     * Creates a new PrintWriter, without automatic line flushing, from an
+     * existing OutputStream.  This convenience constructor creates the
+     * necessary intermediate OutputStreamWriter, which will convert characters
+     * into bytes using the default character encoding.
+     *
+     * @param  out        An output stream
+     *
+     * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+     */
+    public PrintWriter(OutputStream out) {
+	this(out, false);
+    }
+
+    /**
+     * Creates a new PrintWriter from an existing OutputStream.  This
+     * convenience constructor creates the necessary intermediate
+     * OutputStreamWriter, which will convert characters into bytes using the
+     * default character encoding.
+     *
+     * @param  out        An output stream
+     * @param  autoFlush  A boolean; if true, the <tt>println</tt>,
+     *                    <tt>printf</tt>, or <tt>format</tt> methods will
+     *                    flush the output buffer
+     *
+     * @see java.io.OutputStreamWriter#OutputStreamWriter(java.io.OutputStream)
+     */
+    public PrintWriter(OutputStream out, boolean autoFlush) {
+	this(new BufferedWriter(new OutputStreamWriter(out)), autoFlush);
+
+	// save print stream for error propagation
+	if (out instanceof java.io.PrintStream) { 
+	    psOut = (PrintStream) out;
+	}
+    }
+
+    /**
+     * Creates a new PrintWriter, without automatic line flushing, with the
+     * specified file name.  This convenience constructor creates the necessary
+     * intermediate {@link java.io.OutputStreamWriter OutputStreamWriter},
+     * which will encode characters using the {@linkplain
+     * java.nio.charset.Charset#defaultCharset() default charset} for this
+     * instance of the Java virtual machine.
+     *
+     * @param  fileName
+     *         The name of the file to use as the destination of this writer.
+     *         If the file exists then it will be truncated to zero size;
+     *         otherwise, a new file will be created.  The output will be
+     *         written to the file and is buffered.
+     *
+     * @throws  FileNotFoundException
+     *          If the given string does not denote an existing, writable
+     *          regular file and a new regular file of that name cannot be
+     *          created, or if some other error occurs while opening or
+     *          creating the file
+     *
+     * @throws  SecurityException
+     *          If a security manager is present and {@link
+     *          SecurityManager#checkWrite checkWrite(fileName)} denies write
+     *          access to the file
+     *
+     * @since  1.5
+     */
+    public PrintWriter(String fileName) throws FileNotFoundException {
+	this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName))),
+	     false);
+    }
+
+    /**
+     * Creates a new PrintWriter, without automatic line flushing, with the
+     * specified file name and charset.  This convenience constructor creates
+     * the necessary intermediate {@link java.io.OutputStreamWriter
+     * OutputStreamWriter}, which will encode characters using the provided
+     * charset.
+     *
+     * @param  fileName
+     *         The name of the file to use as the destination of this writer.
+     *         If the file exists then it will be truncated to zero size;
+     *         otherwise, a new file will be created.  The output will be
+     *         written to the file and is buffered.
+     *
+     * @param  csn
+     *         The name of a supported {@linkplain java.nio.charset.Charset
+     *         charset}
+     *
+     * @throws  FileNotFoundException
+     *          If the given string does not denote an existing, writable
+     *          regular file and a new regular file of that name cannot be
+     *          created, or if some other error occurs while opening or
+     *          creating the file
+     *
+     * @throws  SecurityException
+     *          If a security manager is present and {@link
+     *          SecurityManager#checkWrite checkWrite(fileName)} denies write
+     *          access to the file
+     *
+     * @throws  UnsupportedEncodingException
+     *          If the named charset is not supported
+     *
+     * @since  1.5
+     */
+    public PrintWriter(String fileName, String csn)
+	throws FileNotFoundException, UnsupportedEncodingException
+    {
+	this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName), csn)),
+	     false);
+    }
+
+    /**
+     * Creates a new PrintWriter, without automatic line flushing, with the
+     * specified file.  This convenience constructor creates the necessary
+     * intermediate {@link java.io.OutputStreamWriter OutputStreamWriter},
+     * which will encode characters using the {@linkplain
+     * java.nio.charset.Charset#defaultCharset() default charset} for this
+     * instance of the Java virtual machine.
+     *
+     * @param  file
+     *         The file to use as the destination of this writer.  If the file
+     *         exists then it will be truncated to zero size; otherwise, a new
+     *         file will be created.  The output will be written to the file
+     *         and is buffered.
+     *
+     * @throws  FileNotFoundException
+     *          If the given file object does not denote an existing, writable
+     *          regular file and a new regular file of that name cannot be
+     *          created, or if some other error occurs while opening or
+     *          creating the file
+     *
+     * @throws  SecurityException
+     *          If a security manager is present and {@link
+     *          SecurityManager#checkWrite checkWrite(file.getPath())}
+     *          denies write access to the file
+     *
+     * @since  1.5
+     */
+    public PrintWriter(File file) throws FileNotFoundException {
+	this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))),
+	     false);
+    }
+
+    /**
+     * Creates a new PrintWriter, without automatic line flushing, with the
+     * specified file and charset.  This convenience constructor creates the
+     * necessary intermediate {@link java.io.OutputStreamWriter
+     * OutputStreamWriter}, which will encode characters using the provided
+     * charset.
+     *
+     * @param  file
+     *         The file to use as the destination of this writer.  If the file
+     *         exists then it will be truncated to zero size; otherwise, a new
+     *         file will be created.  The output will be written to the file
+     *         and is buffered.
+     *
+     * @param  csn
+     *         The name of a supported {@linkplain java.nio.charset.Charset
+     *         charset}
+     *
+     * @throws  FileNotFoundException
+     *          If the given file object does not denote an existing, writable
+     *          regular file and a new regular file of that name cannot be
+     *          created, or if some other error occurs while opening or
+     *          creating the file
+     *
+     * @throws  SecurityException
+     *          If a security manager is present and {@link
+     *          SecurityManager#checkWrite checkWrite(file.getPath())}
+     *          denies write access to the file
+     *
+     * @throws  UnsupportedEncodingException
+     *          If the named charset is not supported
+     *
+     * @since  1.5
+     */
+    public PrintWriter(File file, String csn)
+	throws FileNotFoundException, UnsupportedEncodingException
+    {
+	this(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), csn)),
+	     false);
+    }
+
+    /** Checks to make sure that the stream has not been closed */
+    private void ensureOpen() throws IOException {
+	if (out == null)
+	    throw new IOException("Stream closed");
+    }
+
+    /**
+     * Flushes the stream.
+     * @see #checkError()
+     */
+    public void flush() {
+	try {
+	    synchronized (lock) {
+		ensureOpen();
+		out.flush();
+	    }
+	}
+	catch (IOException x) {
+	    trouble = true;
+	}
+    }
+
+    /**
+     * Closes the stream and releases any system resources associated
+     * with it. Closing a previously closed stream has no effect.
+     *
+     * @see #checkError()
+     */
+    public void close() {
+	try {
+	    synchronized (lock) {
+		if (out == null)
+		    return;
+		out.close();
+		out = null;
+	    }
+	}
+	catch (IOException x) {
+	    trouble = true;
+	}
+    }
+
+    /**
+     * Flushes the stream if it's not closed and checks its error state.
+     *
+     * @return <code>true</code> if the print stream has encountered an error,
+     * 		either on the underlying output stream or during a format
+     *		conversion.
+     */
+    public boolean checkError() {
+	if (out != null) {
+	    flush();
+	}
+	if (out instanceof java.io.PrintWriter) {
+	    PrintWriter pw = (PrintWriter) out; 
+	    return pw.checkError();
+	} else if (psOut != null) {
+	    return psOut.checkError();
+	}
+	return trouble;
+    }
+
+    /**
+     * Indicates that an error has occurred.
+     *
+     * <p> This method will cause subsequent invocations of {@link
+     * #checkError()} to return <tt>true</tt> until {@link
+     * #clearError()} is invoked.
+     */
+    protected void setError() {
+	trouble = true;
+    }
+
+    /**
+     * Clears the error state of this stream.
+     *
+     * <p> This method will cause subsequent invocations of {@link
+     * #checkError()} to return <tt>false</tt> until another write
+     * operation fails and invokes {@link #setError()}.
+     *
+     * @since 1.6
+     */
+    protected void clearError() {
+        trouble = false;
+    }
+ 
+    /*
+     * Exception-catching, synchronized output operations,
+     * which also implement the write() methods of Writer
+     */
+
+    /**
+     * Writes a single character.
+     * @param c int specifying a character to be written.
+     */
+    public void write(int c) {
+	try {
+	    synchronized (lock) {
+		ensureOpen();
+		out.write(c);
+	    }
+	}
+	catch (InterruptedIOException x) {
+	    Thread.currentThread().interrupt();
+	}
+	catch (IOException x) {
+	    trouble = true;
+	}
+    }
+
+    /**
+     * Writes A Portion of an array of characters.
+     * @param buf Array of characters
+     * @param off Offset from which to start writing characters
+     * @param len Number of characters to write
+     */
+    public void write(char buf[], int off, int len) {
+	try {
+	    synchronized (lock) {
+		ensureOpen();
+		out.write(buf, off, len);
+	    }
+	}
+	catch (InterruptedIOException x) {
+	    Thread.currentThread().interrupt();
+	}
+	catch (IOException x) {
+	    trouble = true;
+	}
+    }
+
+    /**
+     * Writes an array of characters.  This method cannot be inherited from the
+     * Writer class because it must suppress I/O exceptions.
+     * @param buf Array of characters to be written
+     */
+    public void write(char buf[]) {
+	write(buf, 0, buf.length);
+    }
+
+    /**
+     * Writes a portion of a string.
+     * @param s A String
+     * @param off Offset from which to start writing characters
+     * @param len Number of characters to write
+     */
+    public void write(String s, int off, int len) {
+	try {
+	    synchronized (lock) {
+		ensureOpen();
+		out.write(s, off, len);
+	    }
+	}
+	catch (InterruptedIOException x) {
+	    Thread.currentThread().interrupt();
+	}
+	catch (IOException x) {
+	    trouble = true;
+	}
+    }
+
+    /**
+     * Writes a string.  This method cannot be inherited from the Writer class
+     * because it must suppress I/O exceptions.
+     * @param s String to be written
+     */
+    public void write(String s) {
+	write(s, 0, s.length());
+    }
+
+    private void newLine() {
+	try {
+	    synchronized (lock) {
+		ensureOpen();
+		out.write(lineSeparator);
+		if (autoFlush)
+		    out.flush();
+	    }
+	}
+	catch (InterruptedIOException x) {
+	    Thread.currentThread().interrupt();
+	}
+	catch (IOException x) {
+	    trouble = true;
+	}
+    }
+
+    /* Methods that do not terminate lines */
+
+    /**
+     * Prints a boolean value.  The string produced by <code>{@link
+     * java.lang.String#valueOf(boolean)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     *
+     * @param      b   The <code>boolean</code> to be printed
+     */
+    public void print(boolean b) {
+	write(b ? "true" : "false");
+    }
+
+    /**
+     * Prints a character.  The character is translated into one or more bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     *
+     * @param      c   The <code>char</code> to be printed
+     */
+    public void print(char c) {
+	write(c);
+    }
+
+    /**
+     * Prints an integer.  The string produced by <code>{@link
+     * java.lang.String#valueOf(int)}</code> is translated into bytes according
+     * to the platform's default character encoding, and these bytes are
+     * written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param      i   The <code>int</code> to be printed
+     * @see        java.lang.Integer#toString(int)
+     */
+    public void print(int i) {
+	write(String.valueOf(i));
+    }
+
+    /**
+     * Prints a long integer.  The string produced by <code>{@link
+     * java.lang.String#valueOf(long)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param      l   The <code>long</code> to be printed
+     * @see        java.lang.Long#toString(long)
+     */
+    public void print(long l) {
+	write(String.valueOf(l));
+    }
+
+    /**
+     * Prints a floating-point number.  The string produced by <code>{@link
+     * java.lang.String#valueOf(float)}</code> is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param      f   The <code>float</code> to be printed
+     * @see        java.lang.Float#toString(float)
+     */
+    public void print(float f) {
+	write(String.valueOf(f));
+    }
+
+    /**
+     * Prints a double-precision floating-point number.  The string produced by
+     * <code>{@link java.lang.String#valueOf(double)}</code> is translated into
+     * bytes according to the platform's default character encoding, and these
+     * bytes are written in exactly the manner of the <code>{@link
+     * #write(int)}</code> method.
+     *
+     * @param      d   The <code>double</code> to be printed
+     * @see        java.lang.Double#toString(double)
+     */
+    public void print(double d) {
+	write(String.valueOf(d));
+    }
+
+    /**
+     * Prints an array of characters.  The characters are converted into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param      s   The array of chars to be printed
+     *
+     * @throws  NullPointerException  If <code>s</code> is <code>null</code>
+     */
+    public void print(char s[]) {
+	write(s);
+    }
+
+    /**
+     * Prints a string.  If the argument is <code>null</code> then the string
+     * <code>"null"</code> is printed.  Otherwise, the string's characters are
+     * converted into bytes according to the platform's default character
+     * encoding, and these bytes are written in exactly the manner of the
+     * <code>{@link #write(int)}</code> method.
+     *
+     * @param      s   The <code>String</code> to be printed
+     */
+    public void print(String s) {
+	if (s == null) {
+	    s = "null";
+	}
+	write(s);
+    }
+
+    /**
+     * Prints an object.  The string produced by the <code>{@link
+     * java.lang.String#valueOf(Object)}</code> method is translated into bytes
+     * according to the platform's default character encoding, and these bytes
+     * are written in exactly the manner of the <code>{@link #write(int)}</code>
+     * method.
+     *
+     * @param      obj   The <code>Object</code> to be printed
+     * @see        java.lang.Object#toString()
+     */
+    public void print(Object obj) {
+	write(String.valueOf(obj));
+    }
+
+    /* Methods that do terminate lines */
+
+    /**
+     * Terminates the current line by writing the line separator string.  The
+     * line separator string is defined by the system property
+     * <code>line.separator</code>, and is not necessarily a single newline
+     * character (<code>'\n'</code>).
+     */
+    public void println() {
+	newLine();
+    }
+
+    /**
+     * Prints a boolean value and then terminates the line.  This method behaves
+     * as though it invokes <code>{@link #print(boolean)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @param x the <code>boolean</code> value to be printed
+     */
+    public void println(boolean x) {
+	synchronized (lock) {
+	    print(x);
+	    println();
+	}
+    }
+
+    /**
+     * Prints a character and then terminates the line.  This method behaves as
+     * though it invokes <code>{@link #print(char)}</code> and then <code>{@link
+     * #println()}</code>.
+     *
+     * @param x the <code>char</code> value to be printed
+     */
+    public void println(char x) {
+	synchronized (lock) {
+	    print(x);
+	    println();
+	}
+    }
+
+    /**
+     * Prints an integer and then terminates the line.  This method behaves as
+     * though it invokes <code>{@link #print(int)}</code> and then <code>{@link
+     * #println()}</code>.
+     *
+     * @param x the <code>int</code> value to be printed
+     */
+    public void println(int x) {
+	synchronized (lock) {
+	    print(x);
+	    println();
+	}
+    }
+
+    /**
+     * Prints a long integer and then terminates the line.  This method behaves
+     * as though it invokes <code>{@link #print(long)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @param x the <code>long</code> value to be printed
+     */
+    public void println(long x) {
+	synchronized (lock) {
+	    print(x);
+	    println();
+	}
+    }
+
+    /**
+     * Prints a floating-point number and then terminates the line.  This method
+     * behaves as though it invokes <code>{@link #print(float)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @param x the <code>float</code> value to be printed
+     */
+    public void println(float x) {
+	synchronized (lock) {
+	    print(x);
+	    println();
+	}
+    }
+
+    /**
+     * Prints a double-precision floating-point number and then terminates the
+     * line.  This method behaves as though it invokes <code>{@link
+     * #print(double)}</code> and then <code>{@link #println()}</code>.
+     *
+     * @param x the <code>double</code> value to be printed
+     */
+    public void println(double x) {
+	synchronized (lock) {
+	    print(x);
+	    println();
+	}
+    }
+
+    /**
+     * Prints an array of characters and then terminates the line.  This method
+     * behaves as though it invokes <code>{@link #print(char[])}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @param x the array of <code>char</code> values to be printed
+     */
+    public void println(char x[]) {
+	synchronized (lock) {
+	    print(x);
+	    println();
+	}
+    }
+
+    /**
+     * Prints a String and then terminates the line.  This method behaves as
+     * though it invokes <code>{@link #print(String)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @param x the <code>String</code> value to be printed
+     */
+    public void println(String x) {
+	synchronized (lock) {
+	    print(x);
+	    println();
+	}
+    }
+
+    /**
+     * Prints an Object and then terminates the line.  This method calls
+     * at first String.valueOf(x) to get the printed object's string value,
+     * then behaves as
+     * though it invokes <code>{@link #print(String)}</code> and then
+     * <code>{@link #println()}</code>.
+     *
+     * @param x  The <code>Object</code> to be printed.
+     */
+    public void println(Object x) {
+	String s = String.valueOf(x);
+	synchronized (lock) {
+	    print(s);
+	    println();
+	}
+    }
+
+    /**
+     * A convenience method to write a formatted string to this writer using
+     * the specified format string and arguments.  If automatic flushing is
+     * enabled, calls to this method will flush the output buffer.
+     *
+     * <p> An invocation of this method of the form <tt>out.printf(format,
+     * args)</tt> behaves in exactly the same way as the invocation
+     *
+     * <pre>
+     *     out.format(format, args) </pre>
+     *
+     * @param  format
+     *         A format string as described in <a
+     *         href="../util/Formatter.html#syntax">Format string syntax</a>.
+     *
+     * @param  args
+     *         Arguments referenced by the format specifiers in the format
+     *         string.  If there are more arguments than format specifiers, the
+     *         extra arguments are ignored.  The number of arguments is
+     *         variable and may be zero.  The maximum number of arguments is
+     *         limited by the maximum dimension of a Java array as defined by
+     *         the <a href="http://java.sun.com/docs/books/vmspec/">Java
+     *         Virtual Machine Specification</a>.  The behaviour on a
+     *         <tt>null</tt> argument depends on the <a
+     *         href="../util/Formatter.html#syntax">conversion</a>.
+     *
+     * @throws  IllegalFormatException
+     *          If a format string contains an illegal syntax, a format
+     *          specifier that is incompatible with the given arguments,
+     *          insufficient arguments given the format string, or other
+     *          illegal conditions.  For specification of all possible
+     *          formatting errors, see the <a
+     *          href="../util/Formatter.html#detail">Details</a> section of the
+     *          formatter class specification.
+     *
+     * @throws  NullPointerException
+     *          If the <tt>format</tt> is <tt>null</tt>
+     *
+     * @return  This writer
+     *
+     * @since  1.5
+     */
+    public PrintWriter printf(String format, Object ... args) {
+	return format(format, args);
+    }
+
+    /**
+     * A convenience method to write a formatted string to this writer using
+     * the specified format string and arguments.  If automatic flushing is
+     * enabled, calls to this method will flush the output buffer.
+     *
+     * <p> An invocation of this method of the form <tt>out.printf(l, format,
+     * args)</tt> behaves in exactly the same way as the invocation
+     *
+     * <pre>
+     *     out.format(l, format, args) </pre>
+     *
+     * @param  l
+     *         The {@linkplain java.util.Locale locale} to apply during
+     *         formatting.  If <tt>l</tt> is <tt>null</tt> then no localization
+     *         is applied.
+     *
+     * @param  format
+     *         A format string as described in <a
+     *         href="../util/Formatter.html#syntax">Format string syntax</a>.
+     *
+     * @param  args
+     *         Arguments referenced by the format specifiers in the format
+     *         string.  If there are more arguments than format specifiers, the
+     *         extra arguments are ignored.  The number of arguments is
+     *         variable and may be zero.  The maximum number of arguments is
+     *         limited by the maximum dimension of a Java array as defined by
+     *         the <a href="http://java.sun.com/docs/books/vmspec/">Java
+     *         Virtual Machine Specification</a>.  The behaviour on a
+     *         <tt>null</tt> argument depends on the <a
+     *         href="../util/Formatter.html#syntax">conversion</a>.
+     *
+     * @throws  IllegalFormatException
+     *          If a format string contains an illegal syntax, a format
+     *          specifier that is incompatible with the given arguments,
+     *          insufficient arguments given the format string, or other
+     *          illegal conditions.  For specification of all possible
+     *          formatting errors, see the <a
+     *          href="../util/Formatter.html#detail">Details</a> section of the
+     *          formatter class specification.
+     *
+     * @throws  NullPointerException
+     *          If the <tt>format</tt> is <tt>null</tt>
+     *
+     * @return  This writer
+     *
+     * @since  1.5
+     */
+    public PrintWriter printf(Locale l, String format, Object ... args) {
+	return format(l, format, args);
+    }
+
+    /**
+     * Writes a formatted string to this writer using the specified format
+     * string and arguments.  If automatic flushing is enabled, calls to this
+     * method will flush the output buffer.
+     *
+     * <p> The locale always used is the one returned by {@link
+     * java.util.Locale#getDefault() Locale.getDefault()}, regardless of any
+     * previous invocations of other formatting methods on this object.
+     *
+     * @param  format
+     *         A format string as described in <a
+     *         href="../util/Formatter.html#syntax">Format string syntax</a>.
+     *
+     * @param  args
+     *         Arguments referenced by the format specifiers in the format
+     *         string.  If there are more arguments than format specifiers, the
+     *         extra arguments are ignored.  The number of arguments is
+     *         variable and may be zero.  The maximum number of arguments is
+     *         limited by the maximum dimension of a Java array as defined by
+     *         the <a href="http://java.sun.com/docs/books/vmspec/">Java
+     *         Virtual Machine Specification</a>.  The behaviour on a
+     *         <tt>null</tt> argument depends on the <a
+     *         href="../util/Formatter.html#syntax">conversion</a>.
+     *
+     * @throws  IllegalFormatException
+     *          If a format string contains an illegal syntax, a format
+     *          specifier that is incompatible with the given arguments,
+     *          insufficient arguments given the format string, or other
+     *          illegal conditions.  For specification of all possible
+     *          formatting errors, see the <a
+     *          href="../util/Formatter.html#detail">Details</a> section of the
+     *          Formatter class specification.
+     *
+     * @throws  NullPointerException
+     *          If the <tt>format</tt> is <tt>null</tt>
+     *
+     * @return  This writer
+     *
+     * @since  1.5
+     */
+    public PrintWriter format(String format, Object ... args) {
+	try {
+	    synchronized (lock) {
+		ensureOpen();
+		if ((formatter == null)
+		    || (formatter.locale() != Locale.getDefault()))
+		    formatter = new Formatter(this);
+		formatter.format(Locale.getDefault(), format, args);
+		if (autoFlush)
+		    out.flush();
+	    }
+	} catch (InterruptedIOException x) {
+	    Thread.currentThread().interrupt();
+	} catch (IOException x) {
+	    trouble = true;
+	}
+	return this;
+    }
+
+    /**
+     * Writes a formatted string to this writer using the specified format
+     * string and arguments.  If automatic flushing is enabled, calls to this
+     * method will flush the output buffer.
+     *
+     * @param  l
+     *         The {@linkplain java.util.Locale locale} to apply during
+     *         formatting.  If <tt>l</tt> is <tt>null</tt> then no localization
+     *         is applied.
+     *
+     * @param  format
+     *         A format string as described in <a
+     *         href="../util/Formatter.html#syntax">Format string syntax</a>.
+     *
+     * @param  args
+     *         Arguments referenced by the format specifiers in the format
+     *         string.  If there are more arguments than format specifiers, the
+     *         extra arguments are ignored.  The number of arguments is
+     *         variable and may be zero.  The maximum number of arguments is
+     *         limited by the maximum dimension of a Java array as defined by
+     *         the <a href="http://java.sun.com/docs/books/vmspec/">Java
+     *         Virtual Machine Specification</a>.  The behaviour on a
+     *         <tt>null</tt> argument depends on the <a
+     *         href="../util/Formatter.html#syntax">conversion</a>.
+     *
+     * @throws  IllegalFormatException
+     *          If a format string contains an illegal syntax, a format
+     *          specifier that is incompatible with the given arguments,
+     *          insufficient arguments given the format string, or other
+     *          illegal conditions.  For specification of all possible
+     *          formatting errors, see the <a
+     *          href="../util/Formatter.html#detail">Details</a> section of the
+     *          formatter class specification.
+     *
+     * @throws  NullPointerException
+     *          If the <tt>format</tt> is <tt>null</tt>
+     *
+     * @return  This writer
+     *
+     * @since  1.5
+     */
+    public PrintWriter format(Locale l, String format, Object ... args) {
+	try {
+	    synchronized (lock) {
+		ensureOpen();
+		if ((formatter == null) || (formatter.locale() != l))
+		    formatter = new Formatter(this, l);
+		formatter.format(l, format, args);
+		if (autoFlush)
+		    out.flush();
+	    }
+	} catch (InterruptedIOException x) {
+	    Thread.currentThread().interrupt();
+	} catch (IOException x) {
+	    trouble = true;
+	}
+	return this;
+    }
+
+    /**
+     * Appends the specified character sequence to this writer.
+     *
+     * <p> An invocation of this method of the form <tt>out.append(csq)</tt>
+     * behaves in exactly the same way as the invocation
+     *
+     * <pre>
+     *     out.write(csq.toString()) </pre>
+     *
+     * <p> Depending on the specification of <tt>toString</tt> for the
+     * character sequence <tt>csq</tt>, the entire sequence may not be
+     * appended. For instance, invoking the <tt>toString</tt> method of a
+     * character buffer will return a subsequence whose content depends upon
+     * the buffer's position and limit.
+     *
+     * @param  csq
+     *         The character sequence to append.  If <tt>csq</tt> is
+     *         <tt>null</tt>, then the four characters <tt>"null"</tt> are
+     *         appended to this writer.
+     *
+     * @return  This writer
+     *
+     * @since  1.5
+     */
+    public PrintWriter append(CharSequence csq) {
+	if (csq == null)
+	    write("null");
+	else
+	    write(csq.toString());
+    	return this;
+    }
+
+    /**
+     * Appends a subsequence of the specified character sequence to this writer.
+     *
+     * <p> An invocation of this method of the form <tt>out.append(csq, start,
+     * end)</tt> when <tt>csq</tt> is not <tt>null</tt>, behaves in
+     * exactly the same way as the invocation
+     *
+     * <pre>
+     *     out.write(csq.subSequence(start, end).toString()) </pre>
+     *
+     * @param  csq
+     *         The character sequence from which a subsequence will be
+     *         appended.  If <tt>csq</tt> is <tt>null</tt>, then characters
+     *         will be appended as if <tt>csq</tt> contained the four
+     *         characters <tt>"null"</tt>.
+     *
+     * @param  start
+     *         The index of the first character in the subsequence
+     *
+     * @param  end
+     *         The index of the character following the last character in the
+     *         subsequence
+     *
+     * @return  This writer
+     *
+     * @throws  IndexOutOfBoundsException
+     *          If <tt>start</tt> or <tt>end</tt> are negative, <tt>start</tt>
+     *          is greater than <tt>end</tt>, or <tt>end</tt> is greater than
+     *          <tt>csq.length()</tt>
+     *
+     * @since  1.5
+     */
+    public PrintWriter append(CharSequence csq, int start, int end) {
+	CharSequence cs = (csq == null ? "null" : csq);
+	write(cs.subSequence(start, end).toString());
+    	return this;
+    }
+    
+    /**
+     * Appends the specified character to this writer.
+     *
+     * <p> An invocation of this method of the form <tt>out.append(c)</tt>
+     * behaves in exactly the same way as the invocation
+     *
+     * <pre>
+     *     out.write(c) </pre>
+     *
+     * @param  c
+     *         The 16-bit character to append
+     *
+     * @return  This writer
+     *
+     * @since 1.5
+     */
+    public PrintWriter append(char c) {
+	write(c);
+	return this;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Program.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,403 @@
+package nabble.naml.compiler;
+
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.ComputationException;
+import fschmidt.util.java.FutureValue;
+import fschmidt.util.java.Identity;
+import fschmidt.util.java.Interner;
+import fschmidt.util.java.Memoizer;
+import fschmidt.util.java.ObjectUtils;
+import nabble.naml.dom.ElementName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+
+public final class Program {
+	private static final Logger logger = LoggerFactory.getLogger(Program.class);
+
+	private static final class Key {
+		final String name;
+		final GenericNamespace[] base;
+
+		Key(String name,GenericNamespace[] base) {
+			if( name==null )
+				throw new NullPointerException("name is null");
+			this.name = name;
+			this.base = base;
+		}
+
+		public boolean equals(Object obj) {
+			if( !(obj instanceof Key) )
+				return false;
+			Key key = (Key)obj;
+			return key.name.equals(name) && Arrays.equals(key.base,base);
+		}
+
+		public int hashCode() {
+			return name.hashCode();
+		}
+	}
+
+	private static final class PartUsage {
+		private final Identity<ElementName.Part> part;
+		private final Usage usage;
+
+		PartUsage(ElementName.Part part,Usage usage) {
+			this.part = part.identity();
+			this.usage = usage;
+		}
+
+		public boolean equals(Object obj) {
+			if( !(obj instanceof PartUsage) )
+				return false;
+			PartUsage p = (PartUsage)obj;
+			return p.part==part && ObjectUtils.equals(p.usage,usage);
+		}
+
+		public int hashCode() {
+			int hash = part.hashCode();
+			if( usage != null )
+				hash += usage.hashCode();
+			return hash;
+		}
+	}
+
+	private final List<Source> sources;
+	private final Collection<Module> modules;
+	private final ConcurrentMap<PartUsage,Meaning> partMeaning = new ConcurrentHashMap<PartUsage,Meaning>();
+	private final ConcurrentMap<String,Meaning> meaningMap = new ConcurrentHashMap<String,Meaning>();
+	private final ConcurrentMap<Meaning,Set<Usage>> usageMap = new ConcurrentHashMap<Meaning,Set<Usage>>();
+
+	private final Memoizer<Key,Template> macroTemplates = new Memoizer<Key,Template>(new Computable<Key,Template>() {
+		public Template get(Key key) throws ComputationException {
+			try {
+				long start = System.currentTimeMillis();
+				Template t = Compiler.compile(Program.this,key.name,key.base);
+				logger.info("Compiling '"+key.name+"' took " + (System.currentTimeMillis()-start) + " ms");
+				return t;
+			} catch(CompileException e) {
+				throw new ComputationException(e);
+			}
+		}
+	});
+
+	private final FutureValue<Set<String>> moduleNames = new FutureValue<Set<String>>() {
+		protected Set<String> compute() {
+			Set<String> moduleNames = new HashSet<String>();
+			for( Module m : modules ) {
+				if( !moduleNames.add(m.getName()) )
+					throw new RuntimeException("duplicate module: "+m.getName());
+			}
+			return moduleNames;
+		}
+	};
+
+	private final FutureValue<Map<Class,List<JavaNamespace>>> extensionMap = new FutureValue<Map<Class,List<JavaNamespace>>>() {
+		protected Map<Class,List<JavaNamespace>> compute() {
+			Map<Class,List<JavaNamespace>> extensionMap = new HashMap<Class,List<JavaNamespace>>();
+			for( Module m : modules ) {
+				for( Class extensionClass : m.getExtensions() ) {
+					JavaNamespace ns = JavaNamespace.getNamespaceExt(extensionClass);
+					List<JavaNamespace> extensions = extensionMap.get(ns.extensionTarget);
+					if( extensions == null ) {
+						extensions = new ArrayList<JavaNamespace>();
+						extensionMap.put(ns.extensionTarget,extensions);
+					}
+					extensions.add(ns);
+				}
+			}
+			return extensionMap;
+		}
+	};
+
+	private final FutureValue<Set<String>> staticNames = new FutureValue<Set<String>>() {
+		protected Set<String> compute() throws CompileException {
+			Set<String> staticNames = new HashSet<String>();
+			for( Source source : getSources() ) {
+				staticNames.addAll(source.staticNames());
+			}
+			return staticNames;
+		}
+	};
+
+	private static class Macros {
+		final Map<String,List<Macro>> nameMap = new HashMap<String,List<Macro>>();
+		final Map<Macro,Macro> overrideMap = new HashMap<Macro,Macro>();
+		final Map<Macro,Macro> overriddenMap = new HashMap<Macro,Macro>();
+	}
+
+	private static final class MacroKey {
+		private final Macro macro;
+
+		MacroKey(Macro macro) {
+			this.macro = macro;
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( !(obj instanceof MacroKey) )
+				return false;
+			MacroKey key = (MacroKey)obj;
+			return key.macro.name.equals(macro.name) && key.macro.requiredNamespaces.equals(macro.requiredNamespaces);
+		}
+
+		@Override public int hashCode() {
+			return macro.name.hashCode() + 31 * macro.requiredNamespaces.hashCode();
+		}
+	}
+
+	private final FutureValue<Macros> macros = new FutureValue<Macros>() {
+		protected Macros compute() throws CompileException {
+			Macros macros = new Macros();
+			Map<MacroKey,Macro> map = new HashMap<MacroKey,Macro>();
+			StackTrace stackTrace = new StackTrace();
+			for( Source source : getSources() ) {
+				for( Macro macro : source.getMacros() ) {
+					stackTrace.push( new StackTraceElement(macro) );
+					try {
+						Macro dup = map.put(new MacroKey(macro),macro);
+						if( !macro.isOverride ) {
+							if( dup != null )
+								throw new CompileException(stackTrace,"macro '"+macro+"' conflicts with '"+dup+"'");
+						} else {
+							if( dup == null )
+								throw new CompileException(stackTrace,"no macro found to override");
+							macros.overrideMap.put(macro,dup);
+							macros.overriddenMap.put(dup,macro);
+						}
+						addMeaning(macro);
+					} finally {
+						stackTrace.pop();
+					}
+				}
+			}
+			for( Macro macro : map.values() ) {
+				List<Macro> list = macros.nameMap.get(macro.name);
+				if( list == null ) {
+					list = Collections.singletonList(macro);
+					macros.nameMap.put(macro.name,list);
+				} else if( list.size() == 1 ) {
+					list = new ArrayList<Macro>(list);
+					list.add(macro);
+					macros.nameMap.put(macro.name,list);
+				} else {
+					list.add(macro);
+				}
+			}
+			return macros;
+		}
+	};
+
+	private Program(List<Module> modules) {
+		this.modules = modules;
+		this.sources = new ArrayList<Source>();
+		Set<String> names = new HashSet<String>();
+		for( Module module : modules ) {
+			for( String dependency : module.getDependencies() ) {
+				if( !names.contains(dependency) )
+					throw new IllegalArgumentException("module '"+module.getName()+"' dependency '"+dependency+"' not found");
+			}
+			if( !names.add(module.getName()) )
+				throw new IllegalArgumentException("duplicate module name: "+module.getName());
+			this.sources.addAll( module.getSources() );
+		}
+
+		// check sources
+		Set<String> ids = new HashSet<String>();
+		for( Source s : this.sources ) {
+			if( !ids.add(s.id) )
+				throw new IllegalArgumentException("duplicate source: "+s);
+		}
+	}
+
+	Set<String> moduleNames() {
+		return moduleNames.get();
+	}
+
+	Map<Class,List<JavaNamespace>> extensionMap() {
+		return extensionMap.get();
+	}
+
+	Set<String> staticNames() {
+		return staticNames.get();
+	}
+
+	Map<String,List<Macro>> macros() {
+		return macros.get().nameMap;
+	}
+
+	Map<Macro,Macro> overrides() {
+		return macros.get().overrideMap;
+	}
+
+	Map<Macro,Macro> overridden() {
+		return macros.get().overriddenMap;
+	}
+
+	public List<Source> getSources() {
+		return sources;
+	}
+
+	public Template getTemplate(String templateName)
+		throws CompileException
+	{
+		return getTemplate(templateName,new GenericNamespace[0]);
+	}
+
+	public Template getTemplate(String templateName,String... base)
+		throws CompileException
+	{
+		return getTemplate(templateName,getNamespaces(base));
+	}
+
+	public Template getTemplate(String templateName,Class... base)
+		throws CompileException
+	{
+		GenericNamespace[] nsBase = new GenericNamespace[base.length];
+		for( int i=0; i<base.length; i++ ) {
+			nsBase[i] = JavaNamespace.getNamespace(base[i]);
+		}
+		return getTemplate(templateName,nsBase);
+	}
+
+	Template getTemplate(String templateName,GenericNamespace... base)
+		throws CompileException
+	{
+		try {
+			Key key = new Key(templateName,base);
+			return macroTemplates.get(key);
+		} catch(ComputationException e) {
+			Throwable cause = e.getCause();
+			if( cause instanceof CompileException )
+				throw (CompileException)cause;
+			throw e;
+		}
+	}
+
+	public boolean equals(Object obj) {
+		if( !(obj instanceof Program) )
+			return false;
+		Program t = (Program)obj;
+		return t.sources.equals(sources) && t.modules.equals(modules);
+	}
+
+	public int hashCode() {
+		return sources.hashCode() + 31*modules.hashCode();
+	}
+
+	void addMeaning(Meaning meaning) {
+		Meaning old = meaningMap.putIfAbsent( meaning.getId(), meaning );
+		if( old != null && old != meaning ) {
+			throw new RuntimeException("meaning="+meaning+" old="+old);
+		}
+	}
+
+	void addMeaning(ElementName.Part part,Meaning meaning,MacroScope macroScope,GenericNamespace[] base) {
+		Set<Usage> usages = usageMap.get(meaning);
+		if( usages == null ) {
+			usages = Collections.newSetFromMap( new ConcurrentHashMap<Usage,Boolean>() );
+			Set<Usage> old = usageMap.putIfAbsent(meaning,usages);
+			if( old != null )
+				usages = old;
+		}
+		if( part != null ) {
+			List<Macro> macroPath = new ArrayList<Macro>();
+			addMacros(macroScope,macroPath);
+			Usage usage = new Usage(base,macroPath).intern();
+			usages.add(usage);
+			PartUsage p = new PartUsage(part,null);
+			Meaning m = partMeaning.putIfAbsent(p,meaning);
+			if( m!=null && !m.equals(meaning) ) {
+				p = new PartUsage(part,usage);
+				m = partMeaning.putIfAbsent(p,meaning);
+				if( m!=null && !m.equals(meaning) )
+					throw new RuntimeException();
+			}
+		}
+	}
+
+	private void addMacros(MacroScope macroScope,List<Macro> macroPath) {
+		if( macroScope.parentScope != null )
+			addMacros(macroScope.parentScope,macroPath);
+		macroPath.add(macroScope.macro);
+	}
+
+	public final Meaning getMeaning(ElementName.Part namePart,Usage usage) {
+		Meaning m = partMeaning.get(new PartUsage(namePart,usage));
+		if( m == null )
+			m = partMeaning.get(new PartUsage(namePart,null));
+		return m;
+	}
+
+	public final Meaning getMeaning(String id) {
+		return meaningMap.get(id);
+	}
+
+	public Macro getMacroOverriddenBy(Macro macro) {
+		return overrides().get(macro);
+	}
+
+	public Macro getMacroWhichOverrides(Macro macro) {
+		return overridden().get(macro);
+	}
+
+	public boolean isCompiled(Meaning meaning) {
+		return usageMap.containsKey(meaning);
+	}
+
+	public Set<Usage> getUsages(Meaning meaning) {
+		return usageMap.get(meaning);
+	}
+
+	private static final Interner<Program> interner = new Interner<Program>();
+
+	public static Program getInstance(List<Module> modules) {
+		return interner.intern(new Program(modules));
+	}
+
+	public Usage getUsage(String[] baseIds,List<Macro> macroPath) {
+		return new Usage(getNamespaces(baseIds),macroPath);
+	}
+
+	private GenericNamespace[] getNamespaces(String[] ids) {
+		GenericNamespace[] a = new GenericNamespace[ids.length];
+		for( int i=0; i<ids.length; i++ ) {
+			a[i] = getNamespace(ids[i]);
+		}
+		return a;
+	}
+
+	private GenericNamespace getNamespace(String id) {
+		if( id.indexOf('!') == -1 ) {
+			try {
+				return JavaNamespace.getNamespace(Class.forName(id));
+			} catch(ClassNotFoundException e) {
+				throw new RuntimeException(e);
+			}
+		} else {
+			Macro macro = (Macro)getMeaning(id);
+			try {
+				return new MacroNamespace(macro,new StackTrace(),macros());
+			} catch(CompileException e) {
+				throw new RuntimeException(e);
+			}
+		}
+	}
+
+	public Collection<Macro> getMacrosByName(String name) {
+		return macros().get(name);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/RunState.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,24 @@
+package nabble.naml.compiler;
+
+import java.util.Map;
+
+
+interface RunState {
+	public Template template();
+	public Program program();
+	public int callDepth();
+	public void putArg(String name,String value);
+	public String getArg(String name);
+	public Object getNamespace(String key);
+	public String saveNamespace(Object namespace);
+	public Object getFromStack(int i);
+	public int push(Object scope);
+	public void pop(int n);
+	public boolean hasNamespace(String namespace);
+	public boolean isInCommandStack(String commandName);
+	public Encoder getEncoder();
+	public void setEncoder(Encoder encoder);
+	public Map<String,String> getVars(Macro macro);
+	public void pushVars(Macro macro);
+	public void popVars(Macro macro);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/RunStateImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,132 @@
+package nabble.naml.compiler;
+
+import fschmidt.util.java.ArrayStack;
+import fschmidt.util.java.Stack;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+final class RunStateImpl implements RunState {
+	private final Stack<Object> stack = new ArrayStack<Object>();
+	private final Template template;
+	private final int callDepth;
+	private final Map<String,String> args = new HashMap<String,String>();
+	private final Map<String,Object> namespaceMap = new HashMap<String,Object>();
+	private final Map<Class,List<JavaNamespace>> extensionMap;
+	private Encoder encoder;
+	private final Map<Macro,Stack<Map<String,String>>> varsMap = new HashMap<Macro,Stack<Map<String,String>>>();
+
+	RunStateImpl(Template template,int callDepth,Encoder encoder) {
+		this.template = template;
+		this.callDepth = callDepth;
+		this.encoder = encoder;
+		this.extensionMap = template.program().extensionMap();
+	}
+
+	public Template template() {
+		return template;
+	}
+
+	public Program program() {
+		return template.program();
+	}
+
+	public int callDepth() {
+		return callDepth;
+	}
+
+	public void putArg(String name,String value) {
+		args.put(name,value);
+	}
+
+	public String getArg(String name) {
+		return args.get(name);
+	}
+
+	public Object getNamespace(String key) {
+		return namespaceMap.get(key);
+	}
+
+	public String saveNamespace(Object namespace) {
+		String key = namespace.toString();
+		namespaceMap.put(key,namespace);
+		return key;
+	}
+
+	public Object getFromStack(int i) {
+		if( i < 0 )
+			i = stack.size() + i;
+		return stack.get(i);
+	}
+
+	public int push(Object scope) {
+		return push(stack,extensionMap,scope);
+	}
+
+	static int push(Stack<Object> stack,Map<Class,List<JavaNamespace>> extensionMap,Object scope) {
+		stack.push(scope);
+		int pushed = 1;
+		List<JavaNamespace> extensions = extensionMap.get(scope.getClass());
+		if( extensions == null )
+			return 1;
+		for( JavaNamespace extension : extensions ) {
+			try {
+				stack.push(extension.extensionConstructor.newInstance(scope));
+			} catch(InstantiationException e) {
+				throw new TemplateRuntimeException(e);
+			} catch(IllegalAccessException e) {
+				throw new TemplateRuntimeException(e);
+			} catch(InvocationTargetException e) {
+				throw Compiler.interpFix(e);
+			}
+		}
+		return 1 + extensions.size();
+	}
+
+	public void pop(int n) {
+		pop(stack,n);
+	}
+
+	static void pop(Stack<Object> stack,int n) {
+		for( ; n > 0; n-- ) {
+			stack.pop();
+		}
+	}
+
+	public boolean hasNamespace(String namespace) {
+		throw new RuntimeException("hasNamespace only works at compile-time");
+	}
+
+	public boolean isInCommandStack(String commandName) {
+		throw new RuntimeException("isInStack only works at compile-time");
+	}
+
+	public Encoder getEncoder() {
+		return encoder;
+	}
+
+	public void setEncoder(Encoder encoder) {
+		this.encoder = encoder;
+	}
+
+	public Map<String,String> getVars(Macro macro) {
+		return varsMap.get(macro).peek();
+	}
+
+	public void pushVars(Macro macro) {
+		Stack<Map<String,String>> vars = varsMap.get(macro);
+		if( vars == null ) {
+			vars = new ArrayStack<Map<String,String>>();
+			varsMap.put(macro,vars);
+		}
+		vars.push(new HashMap<String,String>());
+	}
+
+	public void popVars(Macro macro) {
+		varsMap.get(macro).pop();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/ScopedInterpreter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,7 @@
+package nabble.naml.compiler;
+
+
+public interface ScopedInterpreter<T> extends Interpreter {
+	public Object getArg(T scope,String param);
+	public String getArgString(T scope,String param);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/ScopedInterpreterImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,45 @@
+package nabble.naml.compiler;
+
+import java.util.Map;
+
+
+final class ScopedInterpreterImpl<T> extends InterpreterImpl implements ScopedInterpreter<T> {
+	private final Map<String,Chunk> dynamicArgs;
+
+	ScopedInterpreterImpl(CommandSpec cmdSpec,RunState runState,Map<Class,Integer> inStack,Map<String,?> args,Map<String,Chunk> dynamicArgs) {
+		super(cmdSpec,runState,inStack,args);
+		this.dynamicArgs = dynamicArgs;
+	}
+
+	public Object getArg(final T scope,String param) {
+		if( scope == null )
+			throw new TemplateRuntimeException("scope is null");
+		if( !cmdSpec.scopedParameters.contains(param) )
+			throw new RuntimeException("not scoped parameter");
+		Chunk chunk = dynamicArgs.get(param);
+		if( chunk != null ) {
+			return new BlockWrapper(chunk,runState) {
+				void printTo(IPrintWriter out) {
+					int pushed = runState.push(scope);
+					try {
+						super.printTo(out);
+					} finally {
+						runState.pop(pushed);
+					}
+				}
+			};
+		}
+		Object obj = getArg(param);
+		if( obj != null )
+			return obj;
+		if( scope instanceof Primitive )
+			return scope;
+		return runState.saveNamespace(scope);
+	}
+
+	public String getArgString(T scope,String param) {
+		Object val = getArg(scope,param);
+		return val==null ? null : val.toString();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Source.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,154 @@
+package nabble.naml.compiler;
+
+import fschmidt.util.java.Interner;
+import nabble.naml.dom.Container;
+import nabble.naml.dom.Element;
+import nabble.naml.dom.ElementName;
+import nabble.naml.dom.Naml;
+import nabble.naml.dom.ParseException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Arrays;
+
+
+public final class Source implements Comparable<Source> {
+	public final String id;
+	public final String content;
+	private final Set<ElementName> ignoreTags;
+	private List<Macro> macros = null;
+	private Set<String> staticNames = null;
+
+	private Source(String id,String content,Set<ElementName> ignoreTags) {
+		this.id = id;
+		this.content = content;
+		this.ignoreTags = ignoreTags;
+	}
+
+	@Override public boolean equals(Object obj) {
+		if( !(obj instanceof Source) )
+			return false;
+		Source source = (Source)obj;
+		return source.id.equals(id) && source.content.equals(content);
+	}
+
+	@Override public int hashCode() {
+		return content.hashCode();
+	}
+
+	@Override public int compareTo(Source source) {
+		if( this == source )
+			return 0;
+		int rtn = id.compareTo(source.id);
+		if( rtn != 0 )
+			return rtn;
+		return content.compareTo(source.content);
+	}
+
+	@Override public String toString() {
+		return id;
+	}
+
+	public Collection<Macro> getMacros() throws CompileException {
+		compile();
+		return macros;
+	}
+
+	Collection<String> staticNames() throws CompileException {
+		compile();
+		return staticNames;
+	}
+
+	private static final Set<ElementName> macroNames = new LinkedHashSet<ElementName>(Arrays.asList(
+		new ElementName( Macro.Type.MACRO.tagName ),
+		new ElementName( Macro.OVERRIDE_PREFIX + Macro.Type.MACRO.tagName ),
+		new ElementName( Macro.Type.SUBROUTINE.tagName ),
+		new ElementName( Macro.OVERRIDE_PREFIX + Macro.Type.SUBROUTINE.tagName ),
+		new ElementName( Macro.Type.TRANSLATION.tagName ),
+		new ElementName( Macro.OVERRIDE_PREFIX + Macro.Type.TRANSLATION.tagName ),
+		new ElementName( Macro.Type.NAMESPACE.tagName )
+	) );
+	private static final ElementName staticName = new ElementName("static");
+
+	public Naml parse() throws CompileException {
+		StackTrace stackTrace = new StackTrace();
+		try {
+			return Naml.parser().parse(content);
+		} catch(ParseException e) {
+			stackTrace.push( new StackTraceElement(this,e.lineNumber) );
+			try {
+				throw new CompileException(stackTrace,"parse exception in "+id+" - "+e.getMessage(),e);
+			} finally {
+				stackTrace.pop();
+			}
+		}
+	}
+
+	private synchronized void compile() throws CompileException {
+		if( macros != null )
+			return;
+		List<Macro> macros = new ArrayList<Macro>();
+		staticNames = new HashSet<String>();
+		StackTrace stackTrace = new StackTrace();
+		Map<String,Integer> macroCount = new HashMap<String,Integer>();
+		for( Object obj : parse() ) {
+			if( obj instanceof Element ) {
+				Element element = (Element)obj;
+				stackTrace.push( new StackTraceElement(this,element) );
+				try {
+					ElementName tagName = element.name();
+					if( tagName.equals(staticName) ) {
+						if( !(element instanceof Container) )
+							throw new CompileException(stackTrace,"empty '"+tagName+"' not allowed");
+						Container container = (Container)element;
+						Naml staticNaml = container.contents();
+						if( staticNaml.isEmpty() )
+							throw new CompileException(stackTrace,"static tag may not be empty");
+						if( staticNaml.size() > 1 )
+							throw new CompileException(stackTrace,"static tag may only contain a list of names");
+						Object objS = staticNaml.get(0);
+						if( !(objS instanceof String) )
+							throw new CompileException(stackTrace,"static tag may only contain a list of names");
+						String names = (String)objS;
+						for( String name : names.split("\\s+") ) {
+							name = name.toLowerCase();
+							staticNames.add(name);
+						}
+						continue;
+					}
+					if( macroNames.contains(tagName) ) {
+						Macro macro = new Macro(this,element,stackTrace,macroCount);
+						macros.add(macro);
+					} else if( !ignoreTags.contains(tagName) ) {
+						Set<ElementName> allowed = new LinkedHashSet<ElementName>();
+						allowed.addAll(macroNames);
+						allowed.add(staticName);
+						allowed.addAll(ignoreTags);
+						throw new CompileException(stackTrace,"'"+tagName+"' not valid, only "+allowed+" tags allowed at root");
+					}
+				} finally {
+					stackTrace.pop();
+				}
+			}
+		}
+		this.macros = macros;
+	}
+
+
+	private static final Interner<Source> interner = new Interner<Source>();
+
+	public static Source getInstance(String id,String content,Set<ElementName> ignoreTags) {
+		return interner.intern(new Source(id,content,ignoreTags));
+	}
+
+	public static Source getInstance(String id,String content) {
+		return getInstance(id,content,Collections.<ElementName>emptySet());
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/StackTrace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,96 @@
+package nabble.naml.compiler;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.WeakHashMap;
+import java.util.EmptyStackException;
+import fschmidt.util.java.ArrayUtils;
+import fschmidt.util.java.ArrayStack;
+import fschmidt.util.java.Stack;
+import fschmidt.util.java.Interner;
+
+
+public class StackTrace extends ArrayStack<StackTraceElement> {
+
+	private static final ThreadLocal<Stack<StackTrace>> stack = new ThreadLocal<Stack<StackTrace>>() {
+		protected Stack<StackTrace> initialValue() {
+			Stack<StackTrace> stack = new ArrayStack<StackTrace>();
+			allStacks.put( Thread.currentThread(), stack );
+			return stack;
+		}
+	};
+
+	private static final Map<Thread,Stack<StackTrace>> allStacks = Collections.synchronizedMap(new WeakHashMap<Thread,Stack<StackTrace>>());
+
+	private static final Interner<StackTrace> interner = new Interner<StackTrace>();
+
+	public StackTrace() {}
+
+	StackTrace(StackTrace st) {
+		super(st);
+	}
+
+	StackTrace intern() {
+		trimToSize();
+		return interner.intern(this);
+	}
+
+	public String toString() {
+		StringBuilder buf = new StringBuilder();
+		for( int i=size()-1; i>=0; i-- ) {
+			StackTraceElement stackTraceElement = get(i);
+			buf.append( "\n\tin " ).append( stackTraceElement );
+		}
+		return buf.toString();
+	}
+/*
+	public boolean containsSourceStartingWith(String s) {
+		for( StackTraceElement ste : this ) {
+			if( ste.source.id.startsWith(s) )
+				return true;
+		}
+		return false;
+	}
+*/
+	static Stack<StackTrace> stack() {
+		return stack.get();
+	}
+
+	private static StackTrace stackTrace(Stack<StackTrace> stack) {
+		StackTrace stackTrace = new StackTrace();
+		for( StackTrace st : stack ) {
+			stackTrace.addAll(st);
+		}
+		return stackTrace;
+	}
+
+	public static StackTrace current() {
+		return stackTrace(stack());
+	}
+
+	public static Map<Thread,String[]> getAllStackTraces() {
+		Map<Thread,Stack<StackTrace>> mapCopy;
+		synchronized(allStacks) {
+			mapCopy = new HashMap<Thread,Stack<StackTrace>>(allStacks);
+		}
+		Map<Thread,String[]> rtn = new HashMap<Thread,String[]>();
+		for( Map.Entry<Thread,Stack<StackTrace>> entry : mapCopy.entrySet() ) {
+			Thread thread = entry.getKey();
+			try {
+				Stack<StackTrace> stack = new ArrayStack<StackTrace>(entry.getValue());  // not thread-safe but the best I can do
+				StackTrace stackTrace = stackTrace(stack);
+				if( stackTrace.isEmpty() )
+					continue;
+				String[] aStr = new String[stackTrace.size()];
+				for( int i=0; i<aStr.length; i++ ) {
+					aStr[i] = stackTrace.get(i).toString();
+				}
+				ArrayUtils.reverse(aStr);
+				rtn.put(thread,aStr);
+			} catch(EmptyStackException e) {}
+		}
+		return rtn;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/StackTraceElement.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,96 @@
+package nabble.naml.compiler;
+
+import java.lang.reflect.Method;
+import fschmidt.util.java.Interner;
+import nabble.naml.dom.Element;
+
+
+public final class StackTraceElement {
+
+	private static final Interner<StackTraceElement> interner = new Interner<StackTraceElement>();
+
+	public final Source source;
+	private final int lineNumber;
+	private final Element element;
+	private final String commandName;
+	private Method method;
+
+	StackTraceElement(Source source,int lineNumber) {
+		this.source = source;
+		this.lineNumber = lineNumber;
+		this.element = null;
+		this.commandName = null;
+	}
+
+	public StackTraceElement(Source source,Element element) {
+		this(source,element,(String)null);
+	}
+
+	StackTraceElement(Macro macro) {
+		this(macro.source,macro.element);
+	}
+
+	StackTraceElement(Source source,Element element,String commandName) {
+		this.source = source;
+		this.lineNumber = element.lineNumber();
+		this.element = element;
+		this.commandName = commandName==null ? null : commandName.intern();
+	}
+
+	StackTraceElement(Source source,Element element,JavaCommand javaCommand) {
+		this(source,element,javaCommand.name);
+		setMethod(javaCommand.method);
+	}
+
+	void setMethod(Method method) {
+		this.method = method;
+	}
+
+	@Override public boolean equals(Object obj) {
+		if( !(obj instanceof StackTraceElement) )
+			return false;
+		StackTraceElement ste = (StackTraceElement)obj;
+		return ste.source == source
+			&& ste.lineNumber == lineNumber
+			&& ste.element == element
+			&& ste.commandName == commandName
+		;
+	}
+
+	@Override public int hashCode() {
+		int hash = source.id.hashCode();
+		hash = 31*hash + lineNumber;
+		if( element != null )
+			hash = 31*hash + element.hashCode();
+		if( commandName != null )
+			hash = 31*hash + commandName.hashCode();
+		return hash;
+	}
+
+	StackTraceElement intern() {
+		return interner.intern(this);
+	}
+
+	public String toString() {
+		if( element == null )
+			return source + ":" + (lineNumber+1);
+		StringBuilder sb = new StringBuilder();
+		if( commandName != null )
+			sb.append( commandName );
+		sb
+			.append( "(" )
+			.append( source )
+			.append( ":" )
+			.append( lineNumber+1 )
+			.append( ") - " )
+			.append( element.openingTag() )
+		;
+		if( method != null )
+			sb.append( " - " ).append( method );
+		return sb.toString();
+	}
+
+	String commandName() {
+		return commandName;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/StringChunk.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,41 @@
+package nabble.naml.compiler;
+
+
+final class StringChunk implements Chunk {
+	final String s;
+
+	StringChunk(String s) {
+		if( s.length() == 0 )
+			throw new RuntimeException("empty StringChunk");
+		this.s = s;
+	}
+	
+	public void run(IPrintWriter out,RunState runState) {
+		out.print(s);
+	}
+
+	public boolean hasOutput() {
+		return true;
+	}
+
+	public String toString() {
+		return "{String: \""+s+"\"}";
+	}
+
+	@Override public boolean equals(Object obj) {
+		if( this==obj )
+			return true;
+		if( !(obj instanceof StringChunk) )
+			return false;
+		StringChunk sc = (StringChunk)obj;
+		return sc.s.equals(s);
+	}
+
+	@Override public int hashCode() {
+		return s.hashCode() + getClass().hashCode();
+	}
+
+	@Override public Chunk intern() {
+		return interner.intern(this);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Template.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,141 @@
+package nabble.naml.compiler;
+
+import fschmidt.util.java.Stack;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+public final class Template extends Compiler.Traceable {
+	private final Program program;
+	private final Chunk chunk;
+	private final Class[] baseClasses;
+	public final Macro macro;
+
+	Template(Compiler compiler,Program program,Chunk chunk,Class[] baseClasses,Macro macro) {
+		super(compiler);
+		this.program = program;
+		this.chunk = chunk;
+		this.baseClasses = baseClasses;
+		this.macro = macro;
+	}
+
+	public String name() {
+		return macro.name;
+	}
+
+	public final Program program() {
+		return program;
+	}
+
+	public final void run(IPrintWriter out,Map<String,Object> args,Object... base) {
+		run(0,Encoder.TEXT,out,args,base);
+		out.flush();
+	}
+
+	final void run(int callDepth,Encoder encoder,IPrintWriter out,Map<String,Object> args,Object... base) {
+		Set<String> argNames = macro.parameters;
+		if( !argNames.containsAll(args.keySet()) ) {
+			Set<String> badArgs = new HashSet<String>(args.keySet());
+			badArgs.removeAll(argNames);
+			throw new RuntimeException("invalid args: "+badArgs);
+		}
+		Stack<StackTrace> stack = StackTrace.stack();
+		stack.push(stackTrace);
+		try {
+			check(base);
+			RunState runState = new RunStateImpl(this,callDepth,encoder);
+			for( Object obj : base ) {
+				runState.push(obj);
+			}
+			for( Map.Entry<String,Object> entry : args.entrySet() ) {
+				String key = entry.getKey();
+				Object value = entry.getValue();
+				if( value instanceof String ) {
+					runState.putArg( key, (String)value );
+				} else {
+					runState.putArg( key, runState.saveNamespace(value) );
+				}
+			}
+			chunk.run(out,runState);
+		} catch (ExitException e) {
+			// do nothing.
+		} finally {
+			stack.pop();
+		}
+	}
+
+	private void check(Object[] base) {
+		if( base.length != baseClasses.length )
+			throw new TemplateRuntimeException("base mismatch base.length="+base.length+" baseClasses.length="+baseClasses.length);
+		for( int i=0; i<baseClasses.length; i++ ) {
+			if( !baseClasses[i].isInstance(base[i]) )
+				throw new TemplateRuntimeException("base mismatch");
+		}
+	}
+
+//	private static void handle(Exception e) {
+//		if( e instanceof RuntimeException )
+//			throw (RuntimeException)e;
+//		throw new TemplateRuntimeException(e);
+//	}
+
+	public static String stringValue(Object obj) {
+		if( obj==null )
+			return null;
+		return obj.toString();
+	}
+
+	public static boolean booleanValue(String s)
+		throws BooleanFormatException
+	{
+		if( s.equalsIgnoreCase("true") )
+			return true;
+		if( s.equalsIgnoreCase("false") )
+			return false;
+		throw new BooleanFormatException("For input string: \""+s+"\"");
+	}
+
+	public static boolean booleanValue(Object obj)
+		throws BooleanFormatException
+	{
+		String s = stringValue(obj);
+		if( s==null )
+			throw new BooleanFormatException("For input string: null");
+		return booleanValue(s);
+	}
+
+	public static boolean booleanValue(Object obj,boolean defaultValue)
+		throws BooleanFormatException
+	{
+		String s = stringValue(obj);
+		if( s==null )
+			return defaultValue;
+		return booleanValue(s);
+	}
+
+	public static int intValue(Object obj)
+		throws NumberFormatException
+	{
+		String s = stringValue(obj);
+		return Integer.parseInt(s);
+	}
+
+	public static int intValue(Object obj, int defaultValue)
+		throws NumberFormatException
+	{
+		String s = stringValue(obj);
+		if( s==null )
+			return defaultValue;
+		return Integer.parseInt(s);
+	}
+
+	public static long longValue(Object obj)
+		throws NumberFormatException
+	{
+		String s = stringValue(obj);
+		return Long.parseLong(s);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/TemplatePrintWriter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,38 @@
+package nabble.naml.compiler;
+
+import java.io.Writer;
+
+
+public class TemplatePrintWriter extends PrintWriter {
+
+	public static final TemplatePrintWriter NULL = new TemplatePrintWriter( new Writer() {
+		public void write(char[] cbuf,int off,int len) {}
+		public void flush() {}
+		public void close() {}
+	} );
+
+	public TemplatePrintWriter(Writer out) {
+		super(out);
+	}
+
+	public void print(Object obj) {
+		if( obj == null )
+			throw new NamlNullPointerException("null written to stream");
+		if( obj instanceof BlockWrapper ) {
+			((BlockWrapper)obj).printTo(this);
+		} else {
+			super.print(obj);
+		}
+	}
+
+	public void print(String s) {
+		if( s == null )
+			throw new NamlNullPointerException("null written to stream");
+		super.print(s);
+	}
+
+	public void print(Boolean b) {
+		throw new RuntimeException("no Booleans allowed, use boolean instead");
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/TemplateRuntimeException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,23 @@
+package nabble.naml.compiler;
+
+
+public class TemplateRuntimeException extends RuntimeException {
+
+	public TemplateRuntimeException(Exception cause) {
+		super(hideNull(cause.getMessage())+StackTrace.current(),cause);
+	}
+
+	public TemplateRuntimeException(String msg,Exception cause) {
+		super(hideNull(msg)+StackTrace.current(),cause);
+	}
+
+	public TemplateRuntimeException(String msg) {
+		super(hideNull(msg)+StackTrace.current());
+	}
+
+	protected TemplateRuntimeException() {}
+
+	private static String hideNull(String s) {
+		return s==null ? "" : s;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/Usage.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,48 @@
+package nabble.naml.compiler;
+
+import java.util.Arrays;
+import java.util.List;
+import fschmidt.util.java.Interner;
+
+
+public final class Usage {
+	private final GenericNamespace[] base;
+	private final List<Macro> macroPath;
+
+	Usage(GenericNamespace[] base,List<Macro> macroPath) {
+		this.base = base;
+		this.macroPath = macroPath;
+	}
+
+	public String[] baseIds() {
+		String[] a = new String[base.length];
+		for( int i=0; i<base.length; i++ ) {
+			a[i] = base[i].getId();
+		}
+		return a;
+	}
+
+	public List<Macro> macroPath() {
+		return macroPath;
+	}
+
+	@Override public boolean equals(Object obj) {
+		if( !(obj instanceof Usage) )
+			return false;
+		Usage t = (Usage)obj;
+		return Arrays.equals(t.base,base) && t.macroPath.equals(macroPath);
+	}
+
+	@Override public int hashCode() {
+		int hash = Arrays.hashCode(base);
+		hash = 31*hash + macroPath.hashCode();
+		return hash;
+	}
+
+	private static final Interner<Usage> interner = new Interner<Usage>();
+
+	Usage intern() {
+		return interner.intern(this);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/Attribute.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,68 @@
+package nabble.naml.dom;
+
+
+public final class Attribute {
+	final String spaceBeforeName;
+	final String name;
+	final String spaceAfterName;
+	final String spaceBeforeValue;
+	final Naml value;
+	final char quote;
+
+	Attribute(String spaceBeforeName,String name,String spaceAfterName,String spaceBeforeValue,Naml value,char quote) {
+		this.spaceBeforeName = spaceBeforeName.intern();
+		this.name = name.intern();
+		this.spaceAfterName = spaceAfterName.intern();
+		this.spaceBeforeValue = spaceBeforeValue.intern();
+		this.value = value;
+		this.quote = quote;
+	}
+
+	public String spaceBeforeName() {
+		return spaceBeforeName;
+	}
+
+	public String name() {
+		return name;
+	}
+
+	public String spaceAfterName() {
+		return spaceAfterName;
+	}
+
+	public String spaceBeforeValue() {
+		return spaceBeforeValue;
+	}
+
+	public Naml value() {
+		return value;
+	}
+
+	public char quote() {
+		return quote;
+	}
+
+	public String toString(String value) {
+		return spaceBeforeName + name + spaceAfterName + '=' + spaceBeforeValue + quote + value + quote;
+	}
+
+	public String toString() {
+		String s = value.toString();
+		if( quote == '"' )
+			s = s.replace('<','[').replace('>',']');
+		return toString(s);
+	}
+/*
+	@Override public boolean equals(Object obj) {
+		if( !(obj instanceof Attribute) )
+			return false;
+		Attribute attr = (Attribute)obj;
+		return attr.name.equals(name)
+			&& (value==null ? attr.value==null : value.equals(attr.value));
+	}
+
+	@Override public int hashCode() {
+		return name.hashCode() * 31 + value.hashCode();
+	}
+*/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/Cdata.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,14 @@
+package nabble.naml.dom;
+
+
+public final class Cdata extends Text {
+
+	public Cdata(String text) {
+		super(text);
+	}
+
+	public String toString() {
+		return "<![CDATA[" + text + "]]>";
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/Comment.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,14 @@
+package nabble.naml.dom;
+
+
+public final class Comment extends Text {
+
+	public Comment(String text) {
+		super(text);
+	}
+
+	public String toString() {
+		return "<!--" + text + "-->";
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/Container.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,58 @@
+package nabble.naml.dom;
+
+import java.util.Collection;
+
+
+public final class Container extends Element {
+	private final Naml contents;
+	private final String spaceAtEndOfClosingTag;
+	private final String closingTag;
+
+	public Container(ElementName name,Collection<Attribute> attributes,String spaceAtEndOfOpeningTag,int lineNumber,Naml contents,String spaceAtEndOfClosingTag) {
+		super(name,attributes,spaceAtEndOfOpeningTag,lineNumber);
+		this.contents = contents;
+		this.spaceAtEndOfClosingTag = spaceAtEndOfClosingTag.intern();
+		this.closingTag = ( "</" + name + spaceAtEndOfClosingTag + ">" ).intern();
+	}
+
+	public Naml contents() {
+		return contents;
+	}
+
+	public String spaceAtEndOfClosingTag() {
+		return spaceAtEndOfClosingTag;
+	}
+
+	public String openingTag() {
+		StringBuilder buf = new StringBuilder();
+		addStartTag(buf);
+		buf.append( ">" );
+		return buf.toString();
+	}
+
+	public String closingTag() {
+		return closingTag;
+	}
+
+	public String toString() {
+		StringBuilder buf = new StringBuilder();
+		addStartTag(buf);
+		buf
+			.append( ">" )
+			.append( contents )
+			.append( closingTag )
+		;
+		return buf.toString();
+	}
+/*
+	public boolean equals(Object obj) {
+		if( !(obj instanceof Container) )
+			return false;
+		Container element = (Container)obj;
+		return element.name.equals(name)
+			&& element.attrMap.equals(attrMap)
+			&& element.contents.equals(contents)
+		;
+	}
+*/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/Element.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,59 @@
+package nabble.naml.dom;
+
+import java.util.Map;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+
+
+public abstract class Element {
+	final ElementName name;
+	final Map<String,Attribute> attrMap = new LinkedHashMap<String,Attribute>();
+	final String spaceAtEndOfOpeningTag;
+	final int lineNumber;
+
+	public Element(ElementName name,Iterable<Attribute> attributes,String spaceAtEndOfOpeningTag,int lineNumber) {
+		this.name = name;
+		for( Attribute attr : attributes ) {
+			attrMap.put(attr.name,attr);
+		}
+		this.spaceAtEndOfOpeningTag = spaceAtEndOfOpeningTag.intern();
+		this.lineNumber = lineNumber;
+	}
+
+	public final ElementName name() {
+		return name;
+	}
+
+	public final Collection<Attribute> attributes() {
+		return attrMap.values();
+	}
+
+	public final Attribute getAttribute(String name) {
+		return attrMap.get(name);
+	}
+
+	public final Map<String,Attribute> attributeMap() {
+		return new HashMap<String,Attribute>(attrMap);
+	}
+
+	public final String spaceAtEndOfOpeningTag() {
+		return spaceAtEndOfOpeningTag;
+	}
+
+	public final int lineNumber() {
+		return lineNumber;
+	}
+
+	final void addStartTag(StringBuilder buf) {
+		buf.append( '<' );
+		buf.append( name );
+		for( Attribute attr : attributes() ) {
+			buf.append( attr );
+		}
+		buf.append( spaceAtEndOfOpeningTag );
+	}
+
+	public abstract String openingTag();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/ElementName.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,109 @@
+package nabble.naml.dom;
+
+import fschmidt.util.java.Identity;
+import fschmidt.util.java.Interner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public final class ElementName {
+
+	public static final class Part extends Text {
+		private final Identity<Part> identity = new Identity<Part>();
+
+		public Part(String text) {
+			super(text);
+		}
+
+		@Override public boolean equals(Object obj) {
+			if( !(obj instanceof Part) )
+				return false;
+			Part part = (Part)obj;
+			return part.text==text;
+		}
+
+		@Override public int hashCode() {
+			return text.hashCode();
+		}
+
+		private static final Interner<Part> interner = new Interner<Part>();
+
+		public Part intern() {
+			return interner.intern(this);
+		}
+
+		public Identity<Part> identity() {
+			return identity;
+		}
+	}
+
+	private final List<Part> parts;
+	private final boolean endsWithDot;
+	private final String stringVal;
+	private final String lowerCaseStringVal;
+
+	public ElementName(boolean endsWithDot,List<Part> parts) {
+		this.parts = parts;
+		this.endsWithDot = endsWithDot;
+		this.stringVal = stringVal().intern();
+		this.lowerCaseStringVal = stringVal.toLowerCase().intern();
+	}
+
+	public ElementName(String s) {
+		this.stringVal = s.intern();
+		this.lowerCaseStringVal = stringVal.toLowerCase().intern();
+		this.parts = new ArrayList<Part>();
+		int i = 0;
+		int i2;
+		while( (i2 = s.indexOf('.',i)) != -1 ) {
+			parts.add( new Part( s.substring(i,i2) ) );
+			i = i2 + 1;
+		}
+		this.endsWithDot = i == s.length();
+		if( !endsWithDot )
+			parts.add( new Part( s.substring(i) ) );
+		// yuck
+		Part firstPart = parts.get(0);
+		if( !firstPart.text.equals("t") )
+			parts.set( 0, firstPart.intern() );
+	}
+
+	public List<Part> parts() {
+		return parts;
+	}
+
+	public boolean endsWithDot() {
+		return endsWithDot;
+	}
+
+	private String stringVal() {
+		StringBuilder buf = new StringBuilder();
+		for( Part part : parts ) {
+			buf.append( part ).append( '.' );
+		}
+		if( !endsWithDot )
+			buf.setLength( buf.length() - 1 );
+		return buf.toString();
+	}
+
+	public String toString() {
+		return stringVal;
+	}
+
+	public String toLowerCaseString() {
+		return lowerCaseStringVal;
+	}
+
+	@Override public boolean equals(Object obj) {
+		if( !(obj instanceof ElementName) )
+			return false;
+		ElementName name = (ElementName)obj;
+		return name.parts.equals(parts) && name.endsWithDot == endsWithDot;
+	}
+
+	@Override public int hashCode() {
+		return parts.hashCode();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/EmptyElement.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,33 @@
+package nabble.naml.dom;
+
+import java.util.Collection;
+
+
+public final class EmptyElement extends Element {
+
+	public EmptyElement(ElementName name,Collection<Attribute> attributes,String spaceAtEndOfOpeningTag,int lineNumber) {
+		super(name,attributes,spaceAtEndOfOpeningTag,lineNumber);
+	}
+
+	public String toString() {
+		StringBuilder buf = new StringBuilder();
+		addStartTag(buf);
+		buf.append( "/>" );
+		return buf.toString();
+	}
+
+	public String openingTag() {
+		return toString();
+	}
+
+/*
+	public boolean equals(Object obj) {
+		if( !(obj instanceof EmptyElement) )
+			return false;
+		EmptyElement element = (EmptyElement)obj;
+		return element.name.equals(name)
+			&& element.attrMap.equals(attrMap)
+		;
+	}
+*/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/Naml.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,26 @@
+package nabble.naml.dom;
+
+import java.util.ArrayList;
+
+
+public final class Naml extends ArrayList<Object> {
+
+	public Naml() {}
+
+	public Naml(Naml naml) {
+		super(naml);
+	}
+
+	public String toString() {
+		StringBuilder buf = new StringBuilder();
+		for( Object o : this ) {
+			buf.append( o );
+		}
+		return buf.toString();
+	}
+
+	public static Parser parser() {
+		return new ParserImpl();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/ParseException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,12 @@
+package nabble.naml.dom;
+
+
+public final class ParseException extends Exception {
+	public final int lineNumber;
+
+	ParseException(String msg,int lineNumber) {
+		super(msg+" at line "+(1+lineNumber));
+		this.lineNumber = lineNumber;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/Parser.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,6 @@
+package nabble.naml.dom;
+
+
+public interface Parser {
+	public Naml parse(String source) throws ParseException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/ParserImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,208 @@
+package nabble.naml.dom;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.List;
+import java.util.ArrayList;
+
+
+final class ParserImpl implements Parser {
+	private static final Logger logger = LoggerFactory.getLogger(ParserImpl.class);
+
+	private int startingLine = 0;
+	private int i;
+	private int iLine;
+	private int line;
+	private int len;
+	private String text;
+	private ParserImpl subParser = null;
+
+	public Naml parse(String source) throws ParseException {
+		i = 0;
+		iLine = 0;
+		line = startingLine;
+		len = source.length();
+		text = source;
+		return doParse(null).naml;
+	}
+
+	private static class Return {
+		final Naml naml;
+		final String spaceAtEndOfClosingTag;
+
+		Return(Naml naml,String spaceAtEndOfClosingTag) {
+			this.naml = naml;
+			this.spaceAtEndOfClosingTag = spaceAtEndOfClosingTag;
+		}
+	}
+
+	private Return doParse(String tagName) throws ParseException {
+		int start = i;
+		Naml naml = new Naml();
+		int i2;
+		while( i < len ) {
+			i2 = text.indexOf('<',i);
+			if( i2 == -1 ) {
+				if( tagName != null )
+					throw parseException("unclosed tag: "+tagName,start);
+				naml.add( text.substring(i).intern() );
+				return new Return(naml,null);
+			}
+			if( i < i2 )
+				naml.add( text.substring(i,i2).intern() );
+			if( text.startsWith("<!--",i2) ) {
+				i = text.indexOf("-->",i2);
+				if( i < i2+4 )
+					throw parseException("unclosed comment",i2);
+				naml.add( new Comment( text.substring(i2+4,i) ) );
+				i += 3;
+			} else if( text.startsWith("<![CDATA[",i2) ) {
+				i = text.indexOf("]]>",i2);
+				if( i == -1 )
+					throw parseException("unclosed CDATA",i2);
+				naml.add( new Cdata( text.substring(i2+9,i) ) );
+				i += 3;
+			} else {
+				i = text.indexOf('>',i2);
+				if( i == -1 )
+					throw parseException("unclosed tag",i2);
+				setLine(i2);
+				if( text.charAt(i2+1) == '/' ) {
+					int nameStart = i2 + 2;
+					int nameEnd = nameEnd(nameStart);
+					int spaceEnd = skipSpace(nameEnd);
+					if( spaceEnd != i )
+						throw parseException("illegal char",spaceEnd);
+					if( !text.substring(nameStart,nameEnd).equals(tagName) ) {
+						if( tagName == null )
+							throw parseException("unmatched closing tag: "+text.substring(i2,i+1),i2);
+						else
+							throw parseException("closing tag "+text.substring(i2,i+1)+" doesn't match expected </"+tagName+">",i2);
+					}
+					i++;
+					return new Return(naml,text.substring(nameEnd,spaceEnd));
+				}
+				int startLine = line;
+				if( text.charAt(i-1) == '/' ) {
+					TagInfo tagInfo = new TagInfo(i2+1,i-1);
+					if( tagInfo.name.endsWithDot() )
+						throw parseException("empty tag can't end with dot: "+tagInfo.tagName,i);
+					naml.add( new EmptyElement(tagInfo.name,tagInfo.attributes,tagInfo.spaceAtEnd,startLine) );
+					i++;
+				} else {
+					TagInfo tagInfo = new TagInfo(i2+1,i);
+					i++;
+					String s = tagInfo.tagName.toLowerCase();
+					Return r = doParse(tagInfo.tagName);
+					naml.add( new Container(tagInfo.name,tagInfo.attributes,tagInfo.spaceAtEnd,startLine,r.naml,r.spaceAtEndOfClosingTag) );
+				}
+			}
+		}
+		if( tagName != null )
+			throw parseException("unclosed tag: "+tagName,start);
+		return new Return(naml,null);
+	}
+
+	private class TagInfo {
+		final String tagName;
+		final ElementName name;
+		final List<Attribute> attributes = new ArrayList<Attribute>();
+		final String spaceAtEnd;
+
+		TagInfo(int start,int end) throws ParseException {
+			int i = nameEnd(start);
+			tagName = text.substring(start,i);
+			name = new ElementName(tagName);
+			int afterSpace;
+			while( (afterSpace = skipSpace(i)) < end ) {
+				int afterName = nameEnd(afterSpace);
+				int afterSpaceAfterName = skipSpace(afterName);
+				if( text.charAt(afterSpaceAfterName) != '=' )
+					throw parseException("expected '=' not found for attribute '"+text.substring(afterSpace,afterName)+"'",afterSpaceAfterName);
+				int beforeSpaceBeforeValue = afterSpaceAfterName + 1;
+				int afterSpaceBeforeValue = skipSpace(beforeSpaceBeforeValue);
+				char quote = text.charAt(afterSpaceBeforeValue);
+				if( quote != '"' && quote != '\'' )
+					throw parseException("missing quote",afterSpaceBeforeValue);
+				int startValue = afterSpaceBeforeValue + 1; 
+				int endValue = startValue; 
+				while(true) {
+					if( endValue == end )
+						throw parseException("missing closing quote",endValue);
+					char c = text.charAt(endValue);
+					if( c == quote )
+						break;
+					if( c == '<' || c == '>' )
+						throw parseException("invalid quoted char '"+c+"'",endValue);
+					endValue++;
+				}
+				String spaceBeforeName = text.substring(i,afterSpace);
+				String name = text.substring(afterSpace,afterName);
+				String spaceAfterName = text.substring(afterName,afterSpaceAfterName);
+				String spaceBeforeValue = text.substring(beforeSpaceBeforeValue,afterSpaceBeforeValue);
+				String valueStr = text.substring(startValue,endValue);
+				Naml value;
+				if( quote == '"' ) {
+					setLine(startValue);
+					ParserImpl parser = parser();
+					String source = valueStr.replace('[','<').replace(']','>');
+					value = parser.parse(source);
+				} else {
+					value = new Naml();
+					value.add(valueStr);
+				}
+				attributes.add( new Attribute(spaceBeforeName,name,spaceAfterName,spaceBeforeValue,value,quote) );
+				i = endValue + 1;
+			}
+			spaceAtEnd = text.substring(i,end);
+		}
+	}
+
+	private ParserImpl parser() {
+		if( subParser == null )
+			subParser = new ParserImpl();
+		subParser.startingLine = line;
+		return subParser;
+	}
+
+	private int skipSpace(int i) {
+		while( i<len && Character.isWhitespace(text.charAt(i)) )
+			i++;
+		return i;
+	}
+
+	private int nameEnd(int i) throws ParseException {
+		if( i >= len )
+			throw parseException("name not found",i);
+		char c = text.charAt(i);
+		if( !( Character.isLetter(c) || c=='_' ) )
+			throw parseException("invalid char: '"+c+"'",i);
+		while( ++i < len ) {
+			c = text.charAt(i);
+			if( !( Character.isLetterOrDigit(c) || c=='_' || c=='.' || c=='-' || c==':' ) )
+				break;
+		}
+		return i;
+	}
+
+	private void setLine(int i) {
+		line += lines(iLine,i);
+		iLine = i;
+	}
+
+	private int lines(int start,int end) {
+		int n = 0;
+		int i = start - 1;
+		while(true) {
+			i = text.indexOf('\n',i+1);
+			if( i == -1 || i >= end )
+				return n;
+			n++;
+		}
+	}
+
+	private ParseException parseException(String msg,int i) {
+		return new ParseException(msg,startingLine+lines(0,i));
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/dom/Text.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,19 @@
+package nabble.naml.dom;
+
+
+abstract class Text {
+	final String text;
+
+	Text(String text) {
+		this.text = text.intern();
+	}
+
+	public final String text() {
+		return text;
+	}
+
+	@Override public String toString() {
+		return text;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/namespaces/BasicNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1064 @@
+package nabble.naml.namespaces;
+
+import fschmidt.util.java.ArrayStack;
+import fschmidt.util.java.ObjectUtils;
+import fschmidt.util.java.Stack;
+import fschmidt.util.mail.MailAddress;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Encoder;
+import nabble.naml.compiler.ExitException;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.JavaCommand;
+import nabble.naml.compiler.Macro;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.compiler.StackTrace;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplateRuntimeException;
+import nabble.naml.compiler.NamlNullPointerException;
+import nabble.view.web.template.DateNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+@Namespace (
+	name = "basic",
+	global = true
+)
+public final class BasicNamespace {
+	private static final Logger logger = LoggerFactory.getLogger(BasicNamespace.class);
+
+	private final Template template;
+
+	public BasicNamespace(Template template) {
+		this.template = template;
+	}
+
+	public static final CommandSpec exit = CommandSpec.NO_OUTPUT;
+
+	@CommandDoc(
+		"Exits the page generation and returns the control back to the browser."
+	)
+	@Command public void exit(IPrintWriter out,Interpreter interp) {
+		throw new ExitException();
+	}
+
+	public static final CommandSpec throw_runtime_exception = new CommandSpec.Builder()
+		.dotParameter("text")
+		.outputtedParameters()
+		.build()
+	;
+
+	@CommandDoc(
+		value= "Throws a runtime exception to show that something is wrong or broken. "+
+		"If this command is called, the page will display the full stack trace of the error, which "+
+		"should be investigated and fixed.",
+		params = {"text=The message displayed by the exception"},
+		seeAlso = {"throw_template_exception"}
+	)
+	@Command public void throw_runtime_exception(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		throw new RuntimeException(text);
+	}
+
+	public static final CommandSpec throw_template_exception = new CommandSpec.Builder()
+		.dotParameter("name")
+		.build()
+	;
+
+	@CommandDoc(
+		value= "Throws an exception that can be caught by the @link{catch_exception} command.",
+		params = {"name=The name of the exception, which should be used later for catching and handling."},
+		seeAlso = {"ExceptionNamespace.exception"}
+	)
+	@Command public void throw_template_exception(IPrintWriter out,Interpreter interp)
+		throws TemplateException
+	{
+		String name = interp.getArgString("name");
+		throw TemplateException.newInstance(name);
+	}
+
+	@Command("true") public static void _true(IPrintWriter out,Interpreter interp) {
+		out.print(true);
+	}
+
+	@Command("false") public static void _false(IPrintWriter out,Interpreter interp) {
+		out.print(false);
+	}
+
+	public static final CommandSpec either = new CommandSpec.Builder()
+		.parameters("condition1","condition2")
+		.build()
+	;
+
+	@Command public static void either(IPrintWriter out,Interpreter interp) {
+		out.print( interp.getArgAsBoolean("condition1") || interp.getArgAsBoolean("condition2") );
+	}
+
+	public static final CommandSpec both = new CommandSpec.Builder()
+		.parameters("condition1","condition2")
+		.build()
+	;
+
+	@Command public static void both(IPrintWriter out,Interpreter interp) {
+		out.print( interp.getArgAsBoolean("condition1") && interp.getArgAsBoolean("condition2") );
+	}
+
+	public static final CommandSpec _if = new CommandSpec.Builder()
+		.dotParameter("condition")
+		.optionalParameters("then","else")
+		.outputtedParameters("then","else")
+		.dontRemoveNulls()
+		.build()
+	;
+
+	@CommandDoc(
+		value= "Calls the \"then\" block if the 'condition' (dot_parameter) is true. " +
+			"Otherwise, calls the \"else\" block (if available).",
+		params = {
+			"condition=Condition to be tested",
+			"then=Block for the true case",
+			"else=Block for the false case"
+		}
+	)
+	@Command("if") public static void _if(IPrintWriter out,Interpreter interp) {
+		Object block = interp.getArg( interp.getArgAsBoolean("condition") ? "then" : "else" );
+		if( block != null )
+			out.print(block);
+	}
+
+	public static final CommandSpec not = new CommandSpec.Builder()
+		.dotParameter("condition")
+		.build()
+	;
+
+	@Command public static void not(IPrintWriter out,Interpreter interp) {
+		out.print( !interp.getArgAsBoolean("condition") );
+	}
+
+	private static final CommandSpec optionalValue = new CommandSpec.Builder()
+		.dotParameter("value")
+		.optionalParameters("value")
+		.build()
+	;
+
+	public static final CommandSpec hide_null = optionalValue;
+
+	@Command public static void hide_null(IPrintWriter out,Interpreter interp) {
+		String s = interp.getArgString("value");
+		if( s != null )
+			out.print(s);
+	}
+
+
+	public static final CommandSpec is_null = optionalValue;
+
+	@Command public static void is_null(IPrintWriter out,Interpreter interp) {
+		String s = interp.getArgString("value");
+		out.print( s==null );
+	}
+
+	@Command("null") public static void _null(IPrintWriter out,Interpreter interp) {
+		out.print((String)null);
+	}
+
+	public static final CommandSpec null_exception_to_null = optionalValue;
+
+	@Command public static void null_exception_to_null(IPrintWriter out,Interpreter interp) {
+		try {
+			out.print( interp.getArgString("value") );
+		} catch(NamlNullPointerException e) {
+			out.print( (String)null );
+		}
+	}
+
+	private static final Random RANDOM = new Random();
+
+	public static final CommandSpec random = new CommandSpec.Builder()
+		.parameters("max")
+		.build()
+	;
+
+	@Command public void random(IPrintWriter out,Interpreter interp) {
+		int max = interp.getArgAsInt("max");
+		int r = RANDOM.nextInt(max);
+		out.print(r);
+	}
+
+	public static final CommandSpec equal = new CommandSpec.Builder()
+		.optionalParameters("value1","value2")
+		.build()
+	;
+
+	@Command public static void equal(IPrintWriter out,Interpreter interp) {
+		out.print( ObjectUtils.equals(interp.getArgString("value1"),interp.getArgString("value2")) );
+	}
+
+	public static final CommandSpec starts_with = new CommandSpec.Builder()
+		.dotParameter("text")
+		.parameters("prefix")
+		.build()
+	;
+
+	@Command public void starts_with(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		String prefix = interp.getArgString("prefix");
+		out.print( text.startsWith(prefix) );
+	}
+
+	public static final CommandSpec ends_with = new CommandSpec.Builder()
+		.dotParameter("text")
+		.parameters("suffix")
+		.build()
+	;
+
+	@Command public void ends_with(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		String suffix = interp.getArgString("suffix");
+		out.print( text.endsWith(suffix) );
+	}
+
+	public static final CommandSpec substring = new CommandSpec.Builder()
+		.dotParameter("text")
+		.parameters("begin")
+		.optionalParameters("end")
+		.build()
+	;
+
+	@Command public void substring(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		int begin = Integer.parseInt(interp.getArgString("begin"));
+		String end = interp.getArgString("end");
+		out.print( end==null ? text.substring(begin) : text.substring(begin,Integer.parseInt(end)) );
+	}
+
+	public static final CommandSpec contains_substring = new CommandSpec.Builder()
+		.dotParameter("string")
+		.parameters("substring")
+		.build()
+	;
+
+	@Command public void contains_substring(IPrintWriter out,Interpreter interp) {
+		out.print( interp.getArgString("string").indexOf(interp.getArgString("substring")) != -1 );
+	}
+
+	public static final CommandSpec regex_quote = new CommandSpec.Builder()
+		.dotParameter("string")
+		.build()
+	;
+
+	@Command public void regex_quote(IPrintWriter out,Interpreter interp) {
+		out.print( Pattern.quote(interp.getArgString("string")) );
+	}
+
+	public static final CommandSpec append = new CommandSpec.Builder()
+		.dotParameter("text")
+		.parameters("suffix")
+		.optionalParameters("text","except_if")
+		.restrictedParameter("except_if","already-included")
+		.build()
+	;
+
+	@Command public void append(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		out.print(text);
+		if( text!=null && text.length() > 0 ) {
+			String suffix = interp.getArgString("suffix");
+			if( !( "already-included".equals(interp.getArgString("except_if"))
+				&& text.toLowerCase().indexOf(suffix.toLowerCase()) != -1
+			) )
+				out.print(suffix);
+		}
+	}
+
+	public static final CommandSpec prepend = new CommandSpec.Builder()
+		.dotParameter("text")
+		.parameters("prefix")
+		.optionalParameters("text","except_if")
+		.restrictedParameter("except_if","already-included")
+		.build()
+	;
+
+	@Command public void prepend(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		if( text!=null && text.length() > 0 ) {
+			String prefix = interp.getArgString("prefix");
+			if( !( "already-included".equals(interp.getArgString("except_if"))
+				&& text.toLowerCase().indexOf(prefix.toLowerCase()) != -1
+			) )
+				out.print(prefix);
+		}
+		out.print(text);
+	}
+
+	public static final CommandSpec separate = new CommandSpec.Builder()
+		.parameters("text1","separator","text2")
+		.build()
+	;
+
+	@Command public void separate(IPrintWriter out,Interpreter interp) {
+		String text1 = interp.getArgString("text1");
+		if( text1 == null )
+			throw new NullPointerException("text1 is null");
+		String text2 = interp.getArgString("text2");
+		if( text2 == null )
+			throw new NullPointerException("text2 is null");
+		out.print( text1 );
+		if( text1.trim().length() > 0 && text2.trim().length() > 0 )
+			out.print( interp.getArg("separator") );
+		out.print( text2 );
+	}
+
+/*
+	public static final CommandSpec is_in_namespace = new CommandSpec.Builder()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public static void is_in_namespace(IPrintWriter out,Interpreter interp) {
+		out.print( interp.hasNamespace(interp.getArgString("name")) );
+	}
+*/
+
+	public static final CommandSpec is_in_command = new CommandSpec.Builder()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public static void is_in_command(IPrintWriter out,Interpreter interp) {
+		out.print( interp.isInCommandStack(interp.getArgString("name")) );
+	}
+
+
+	public static final CommandSpec to_lower_case = CommandSpec.TEXT;
+
+	@Command public void to_lower_case(IPrintWriter out,Interpreter interp) {
+		out.print( interp.getArgString("text").toLowerCase() );
+	}
+
+	public static final CommandSpec to_upper_case = CommandSpec.TEXT;
+
+	@Command public void to_upper_case(IPrintWriter out,Interpreter interp) {
+		out.print( interp.getArgString("text").toUpperCase() );
+	}
+
+	public static final CommandSpec capitalize = CommandSpec.TEXT;
+
+	@Command public void capitalize(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		String[] words = text.split("[ ]");
+		StringBuilder b = new StringBuilder();
+		for (String w : words) {
+			if (b.length() > 0)
+				b.append(' ');
+			b.append(w.substring(0,1).toUpperCase());
+			b.append(w.substring(1,w.length()));
+		}
+		out.print(b.toString());
+	}
+
+	public static final CommandSpec regex = new CommandSpec.Builder()
+		.parameters("pattern","text")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.build()
+	;
+
+	@Command public static void regex(IPrintWriter out,ScopedInterpreter<RegexNamespace> interp) {
+		Pattern p = Pattern.compile( interp.getArgString("pattern") );
+		String s = interp.getArgString("text");
+		Matcher m = p.matcher(s);
+		RegexNamespace ns = new RegexNamespace(m,s);
+		out.print( interp.getArg(ns,"do") );
+	}
+
+	public static final CommandSpec regex_replace_all = new CommandSpec.Builder()
+		.dotParameter("text")
+		.parameters("pattern","replacement")
+		.build()
+	;
+
+	@Command public static void regex_replace_all(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		String pattern = interp.getArgString("pattern");
+		String replacement = interp.getArgString("replacement");
+		out.print( text.replaceAll(pattern, replacement) );
+	}
+
+	public static final CommandSpec regex_replace_first = new CommandSpec.Builder()
+		.dotParameter("text")
+		.parameters("pattern","replacement")
+		.build()
+	;
+
+	@Command public static void regex_replace_first(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		String pattern = interp.getArgString("pattern");
+		String replacement = interp.getArgString("replacement");
+		out.print( text.replaceFirst(pattern, replacement) );
+	}
+
+	public static final CommandSpec string_replace_all = new CommandSpec.Builder()
+		.dotParameter("text")
+		.parameters("target","replacement")
+		.build()
+	;
+
+	@Command public static void string_replace_all(IPrintWriter out,Interpreter interp) {
+		out.print( interp.getArgString("text").replace(interp.getArgString("target"),interp.getArgString("replacement")) );
+	}
+
+	public static final CommandSpec trim = CommandSpec.TEXT;
+
+	@Command public static void trim(IPrintWriter out,Interpreter interp) {
+		String s = interp.getArgString("text");
+		out.print( s==null ? null : s.trim() );
+	}
+
+	public static final CommandSpec encode = CommandSpec.OPTIONAL_TEXT;
+
+	@Command public static void encode(IPrintWriter out,Interpreter interp) {
+		String s = interp.getArgString("text");
+		out.print( interp.encode(s) );
+	}
+
+	public static final CommandSpec use_text_encoder = CommandSpec.OPTIONAL_TEXT;
+
+	@Command public void use_text_encoder(IPrintWriter out,Interpreter interp) {
+		interp.setEncoder(Encoder.TEXT);
+		String s = interp.getArgString("text");
+		out.print(s);
+	}
+
+	public static final CommandSpec use_html_encoder = CommandSpec.OPTIONAL_TEXT;
+
+	@Command public void use_html_encoder(IPrintWriter out,Interpreter interp) {
+		interp.setEncoder(Encoder.HTML);
+		String s = interp.getArgString("text");
+		out.print(s);
+	}
+
+	public static final CommandSpec use_url_encoder = CommandSpec.OPTIONAL_TEXT;
+
+	@Command public void use_url_encoder(IPrintWriter out,Interpreter interp) {
+		interp.setEncoder(Encoder.URL);
+		String s = interp.getArgString("text");
+		out.print(s);
+	}
+
+	public static final CommandSpec is_empty = optionalValue;
+
+	@Command public static void is_empty(IPrintWriter out,Interpreter interp) {
+		String s = interp.getArgString("value");
+		out.print( s==null || s.length() == 0 );
+	}
+
+
+	public static final CommandSpec var = new CommandSpec.Builder()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void var(IPrintWriter out,Interpreter interp) {
+		throw new RuntimeException("never");
+	}
+
+	public static final CommandSpec set_var = new CommandSpec.Builder()
+		.parameters("name")
+		.dotParameter("value")
+		.optionalParameters("value")
+		.outputtedParameters()
+		.build()
+	;
+
+	@Command public void set_var(IPrintWriter out,Interpreter interp) {
+		throw new RuntimeException("never");
+	}
+
+	public static final CommandSpec uplevel_var = var;
+
+	@Command public void uplevel_var(IPrintWriter out,Interpreter interp) {
+		throw new RuntimeException("never");
+	}
+
+	public static final CommandSpec uplevel_set_var = set_var;
+
+	@Command public void uplevel_set_var(IPrintWriter out,Interpreter interp) {
+		throw new RuntimeException("never");
+	}
+
+	private final Map<String,String> globalVars = new HashMap<String,String>();
+
+	public static final CommandSpec global_var = var;
+
+	@Command public void global_var(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print( globalVars.get(name) );
+	}
+
+	public static final CommandSpec global_set_var = set_var;
+
+	@Command public void global_set_var(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		String value = interp.getArgString("value");
+		globalVars.put(name,value);
+	}
+
+	public static final CommandSpec global_is_var_set = var;
+
+	@Command public void global_is_var_set(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print( globalVars.containsKey(name) );
+	}
+
+
+	public static final CommandSpec _default = new CommandSpec.Builder()
+		.dotParameter("text")
+		.optionalParameters("text")
+		.parameters("to")
+		.build()
+	;
+
+	@Command("default") public static void _default(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		out.print( text!=null ? text : interp.getArgString("to") );
+	}
+
+	private static final int whileLimit = 1000000;
+
+	private static class BreakException extends TemplateRuntimeException {
+		BreakException() {
+			super("break called outside of while");
+		}
+	}
+
+	public static final CommandSpec _while = new CommandSpec.Builder()
+		.dotParameter("condition")
+		.parameters("loop")
+		.outputtedParameters("loop")
+		.build()
+	;
+
+	@Command("while") public void _while(IPrintWriter out,Interpreter interp) {
+		Object condition = interp.getArg("condition");
+		Object block = interp.getArg("loop");
+		int count = 0;
+		try {
+			while( Template.booleanValue(condition) ) {
+				if( ++count > whileLimit )
+					throw new RuntimeException("while loop limit of "+whileLimit+" iterations exceeded");
+				out.print(block);
+			}
+		} catch(BreakException e) {}
+	}
+
+	@Command("break") public void _break(IPrintWriter out,Interpreter interp) {
+		throw new BreakException();
+	}
+
+
+	@Command public void nop(IPrintWriter out,Interpreter interp) {}
+
+
+	private static final class ExceptionInfo {
+		private final String forStr;
+		private final TemplateException ex;
+
+		private ExceptionInfo(String forStr,TemplateException ex) {
+			this.forStr = forStr;
+			this.ex = ex;
+		}
+	}
+
+	private Stack<ExceptionInfo> exceptionStack = new ArrayStack<ExceptionInfo>();
+
+	public static final CommandSpec catch_exception = new CommandSpec.Builder()
+		.parameters("id")
+		.dotParameter("do")
+		.outputtedParameters("do")
+		.build()
+	;
+
+	@Command public void catch_exception(IPrintWriter out,Interpreter interp) {
+		String idStr = interp.getArgString("id");
+		TemplateException ex = null;
+		try {
+			out.print( interp.getArg("do") );
+		} catch(TemplateRuntimeException e) {
+			Throwable cause = e.getCause();
+			if( cause instanceof TemplateException ) {
+				ex = (TemplateException)cause;
+			} else {
+				throw e;
+			}
+		}
+		exceptionStack.push( new ExceptionInfo(idStr,ex) );
+	}
+
+	@Namespace (
+		name = "error",
+		global = false
+	)
+	public static class ExceptionNamespace {
+		protected final TemplateException ex;
+		protected boolean isDone = false;
+
+		public ExceptionNamespace(TemplateException ex) {
+			this.ex = ex;
+		}
+
+		public static final CommandSpec exception = new CommandSpec.Builder()
+			.parameters("name")
+			.dotParameter("do")
+			.build()
+		;
+
+		@Command public void exception(IPrintWriter out,Interpreter interp) {
+			if( isDone )
+				return;
+			String name = interp.getArgString("name");
+			if( ex.isNamed(name) ) {
+				out.print( interp.getArg("do") );
+				isDone = true;
+			}
+		}
+
+		public static final CommandSpec unknown_exception = CommandSpec.DO;
+
+		@Command public void unknown_exception(IPrintWriter out,ScopedInterpreter<UnknownException> interp) {
+			if( isDone )
+				return;
+			out.print( interp.getArg(new UnknownException(ex), "do") );
+			isDone = true;
+		}
+
+		protected static final CommandSpec scopedCommandSpec = new CommandSpec.Builder()
+			.parameters("name")
+			.dotParameter("do")
+			.scopedParameters("do")
+			.build()
+		;
+
+		protected final <N> void scopedException(IPrintWriter out,ScopedInterpreter<N> interp,N ns) {
+			if( isDone )
+				return;
+			String name = interp.getArgString("name");
+			if( ex.isNamed(name) ) {
+				out.print(interp.getArg(ns,"do"));
+				isDone = true;
+			}
+		}
+
+		protected final <N> void unnamedScopedException(IPrintWriter out,ScopedInterpreter<N> interp,N ns) {
+			if( isDone )
+				return;
+			out.print(interp.getArg(ns,"do"));
+			isDone = true;
+		}
+
+	}
+
+	@Namespace (
+		name = "unknown_exception",
+		global = false
+	)
+	public static class UnknownException extends ExceptionNamespace {
+
+		public UnknownException(TemplateException ex) {
+			super(ex);
+		}
+
+		@Command public void message(IPrintWriter out,Interpreter interp) {
+			out.print(ex.getMessage());
+		}
+	}
+
+	public static interface ExceptionNamespaceFactory<E extends ExceptionNamespace> {
+		public E newExceptionNamespace(TemplateException ex);
+	}
+
+/*	private static final ExceptionNamespaceFactory<ExceptionNamespace> myExceptionNamespaceFactory =
+		new ExceptionNamespaceFactory<ExceptionNamespace>() {
+			public ExceptionNamespace newExceptionNamespace(TemplateException ex) {
+				return new ExceptionNamespace(ex);
+			}
+		}
+	;*/
+
+	public static final CommandSpec handle_exception = CommandSpec.DO()
+		.parameters("for")
+		.requiredInStack(BasicNamespace.class)
+		.build()
+	;
+/*
+	@Command public void handle_exception(IPrintWriter out,ScopedInterpreter<ExceptionNamespace> interp) {
+		handleException(out,interp,myExceptionNamespaceFactory);
+	}
+*/
+	public <E extends ExceptionNamespace> void handleException(IPrintWriter out,ScopedInterpreter<E> interp,ExceptionNamespaceFactory<E> factory) {
+		if( exceptionStack.isEmpty() )
+			throw new RuntimeException("there are no exceptions available to be handled");
+		ExceptionInfo ei = exceptionStack.pop();
+		String forStr = interp.getArgString("for");
+		if( !forStr.equals(ei.forStr) )
+			throw new RuntimeException("found exception for: "+ei.forStr);
+		if( ei.ex == null )
+			return;
+		E en = factory.newExceptionNamespace(ei.ex);
+		out.print( interp.getArg(en,"do") );
+		if( !en.isDone ) {
+			logger.error("unexpected exception"+StackTrace.current(),en.ex);
+			out.print( "Unexpected exception: " + en.ex );
+		}
+	}
+
+	public static final CommandSpec has_exception = new CommandSpec.Builder()
+		.parameters("for")
+		.build()
+	;
+
+	@Command public void has_exception(IPrintWriter out,Interpreter interp) {
+		if( exceptionStack.isEmpty() )
+			throw new RuntimeException("there are no exceptions available to be handled");
+		ExceptionInfo ei = exceptionStack.peek();
+		String forStr = interp.getArgString("for");
+		if( !forStr.equals(ei.forStr) )
+			throw new RuntimeException("found exception for: "+ei.forStr);
+		out.print( ei.ex != null );
+	}
+
+	public static final CommandSpec string_list = CommandSpec.DO()
+		.optionalParameters("values","separator","trim")
+		.build()
+	;
+
+	@Command public void string_list(IPrintWriter out,ScopedInterpreter<StringList> interp) {
+		List<String> list = new ArrayList<String>();
+		String csv = interp.getArgString("values");
+		if( csv != null ) {
+			String separator = interp.getArgString("separator");
+			if( separator == null )
+				separator = ",";
+			boolean trim = interp.getArgAsBoolean("trim", true);
+			for( String s : csv.split(separator) ) {
+				list.add( trim? s.trim() : s );
+			}
+		}
+		Object block = interp.getArg(new StringList(list),"do");
+		out.print(block);
+	}
+
+	public static final CommandSpec string_map = new CommandSpec.Builder()
+		.parameters("key")
+		.dotParameter("entries")
+		.build()
+	;
+
+	@Command public void string_map(IPrintWriter out, Interpreter interp) {
+		String key = interp.getArgString("key");
+		String[] entries = interp.getArgString("entries").split("\n");
+		for (String entry : entries) {
+			String[] keyValue = entry.split(":");
+			if (key.equals(keyValue[0].trim())) {
+				out.print(keyValue[1].trim());
+				return;
+			}
+		}
+		out.print((String) null);
+	}
+
+	public static final CommandSpec _int = CommandSpec.DO()
+		.parameters("i")
+		.build()
+	;
+
+	@Command("int") public void _int(IPrintWriter out,ScopedInterpreter<IntegerNamespace> interp) {
+		out.print( interp.getArg(new IntegerNamespace(interp.getArgAsInt("i")),"do") );
+	}
+
+	public static final CommandSpec extract_email_address_from = new CommandSpec.Builder()
+		.dotParameter("text")
+		.build()
+	;
+
+	@Command public static void extract_email_address_from(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text").trim();
+		String email = null;
+		if (new MailAddress(text).isValid()) {
+			email = text;
+		} else {
+			int posOpen = text.lastIndexOf('<');
+			int posClose = text.lastIndexOf('>');
+			if (posOpen >= 0 && posClose > posOpen) {
+				String middle = text.substring(posOpen+1, posClose);
+				if (new MailAddress(middle).isValid())
+					email = middle;
+			}
+		}
+		out.print(email);
+	}
+
+	@Command public void crlf(IPrintWriter out,Interpreter interp) {
+		out.print("\r\n");
+	}
+
+	@Command public void space(IPrintWriter out,Interpreter interp) {
+		out.print(' ');
+	}
+
+   	@Command public void double_quote(IPrintWriter out,Interpreter interp) {
+		out.print("\"");
+	}
+
+	public static final CommandSpec has_module = new CommandSpec.Builder()
+		.dotParameter("module")
+		.build()
+	;
+
+	@Command public static void has_module(IPrintWriter out,Interpreter interp) {
+		out.print( interp.hasModule(interp.getArgString("module")) );
+	}
+
+	public static final CommandSpec no_output = CommandSpec.NO_OUTPUT()
+		.dotParameter("text")
+		.build()
+	;
+
+	@Command public static void no_output(IPrintWriter out,Interpreter interp) {
+		interp.getArgString("text");
+	}
+
+
+	@Command public static void lt(IPrintWriter out,Interpreter interp) {
+		out.print('<');
+	}
+
+	@Command public static void gt(IPrintWriter out,Interpreter interp) {
+		out.print('>');
+	}
+
+	public static final CommandSpec now = CommandSpec.DO;
+
+	@Command public void now(IPrintWriter out,ScopedInterpreter<DateNamespace> interp) {
+		out.print( interp.getArg(new DateNamespace(new Date()), "do" ));
+	}
+
+	public static final CommandSpec call_depth = CommandSpec.DO;
+
+	@Command public void call_depth(IPrintWriter out,ScopedInterpreter<IntegerNamespace> interp) {
+		out.print( interp.getArg(new IntegerNamespace(interp.callDepth()),"do") );
+	}
+
+
+	public static final CommandSpec _switch = CommandSpec.DO()
+		.optionalParameters("value")
+		.build()
+	;
+
+	@Command("switch") public void _switch(IPrintWriter out,ScopedInterpreter<SwitchNamespace> interp) {
+		out.print( interp.getArg(new SwitchNamespace(interp.getArgString("value")),"do") );
+	}
+
+	@Namespace (
+		name = "switch",
+		global = false,
+		transparent = true
+	)
+	public static final class SwitchNamespace {
+		private final String value;
+		private boolean isDone = false;
+
+		private SwitchNamespace(String value) {
+			this.value = value;
+		}
+
+		@Command public void switch_value(IPrintWriter out,Interpreter interp) {
+			out.print( value );
+		}
+
+		public static final CommandSpec _case = new CommandSpec.Builder()
+			.parameters("value")
+			.dotParameter("do")
+			.outputtedParameters()  // to trim surrounding white-space
+			.build()
+		;
+
+		@Command("case") public void _case(IPrintWriter out,Interpreter interp) {
+			if( !isDone && interp.getArgString("value").equals(value) ) {
+				out.print( interp.getArg("do") );
+				isDone = true;
+			}
+		}
+
+		public static final CommandSpec default_case = new CommandSpec.Builder()
+			.dotParameter("do")
+			.outputtedParameters("do")
+			.build()
+		;
+
+		@Command public void default_case(IPrintWriter out,Interpreter interp) {
+			if( !isDone ) {
+				out.print( interp.getArg("do") );
+				isDone = true;
+			}
+		}
+
+		public static final CommandSpec regex_case = new CommandSpec.Builder()
+			.parameters("regex")
+			.scopedParameters("do")
+			.dotParameter("do")
+			.outputtedParameters("do")
+			.build()
+		;
+
+		@Command public void regex_case(IPrintWriter out,ScopedInterpreter<RegexNamespace> interp) {
+			if( !isDone ) {
+				Matcher m = Pattern.compile(interp.getArgString("regex")).matcher(value);
+				if( m.matches() ) {
+					out.print( interp.getArg(new RegexNamespace(m,value),"do") );
+					isDone = true;
+				}
+			}
+		}
+
+	}
+
+
+
+	@Namespace (
+		name = "block",
+		global = false
+	)
+	public static final class BlockNamespace {}
+
+	private static final BlockNamespace BLOCK = new BlockNamespace();
+
+	public static final CommandSpec block = CommandSpec.DO;
+
+	@Command public void block(IPrintWriter out,ScopedInterpreter<BlockNamespace> interp) {
+		out.print( interp.getArg(BLOCK,"do") );
+	}
+
+
+
+	@Namespace (
+		name = "counter",
+		global = false
+	)
+	public static final class CounterNamespace {
+		private int counter = 0;
+
+		public static final CommandSpec increment = CommandSpec.NO_OUTPUT;
+
+		@Command public void increment(IPrintWriter out,Interpreter interp) {
+			counter++;
+		}
+
+		@Command public void value(IPrintWriter out,Interpreter interp) {
+			out.print(counter);
+		}
+
+	}
+
+	private Map<String,CounterNamespace> counterMap = null;
+
+	public static final CommandSpec counter = CommandSpec.DO()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void counter(IPrintWriter out,ScopedInterpreter<CounterNamespace> interp) {
+		if( counterMap == null )
+			counterMap = new HashMap<String,CounterNamespace>();
+		String name = interp.getArgString("name");
+		if( name==null )
+			throw new NullPointerException("name is required");
+		CounterNamespace ns = counterMap.get(name);
+		if( ns == null ) {
+			ns = new CounterNamespace();
+			counterMap.put(name,ns);
+		}
+		out.print( interp.getArg(ns,"do") );
+	}
+
+
+	public static final CommandSpec compile_template = CommandSpec.NO_OUTPUT()
+		.parameters("macro","namespaces")
+		.build()
+	;
+
+	@Command public void compile_template(IPrintWriter out,Interpreter interp)
+		throws ClassNotFoundException, CompileException
+	{
+		String macroName = interp.getArgString("macro");
+		String namespacesStr = interp.getArgString("namespaces").trim();
+		String[] namespaces = namespacesStr.length()==0 ? new String[0] : namespacesStr.split("\\s*,\\s*");
+		if( interp.template().program().getTemplate( macroName, namespaces ) == null )
+			throw new RuntimeException("macro '"+macroName+"' not found");
+	}
+
+
+
+
+	private static final CommandSpec ID = new CommandSpec.Builder()
+		.parameters("id")
+		.build();
+
+	public static final CommandSpec command_exists = ID;
+
+	@Command public void command_exists(IPrintWriter out, Interpreter interp) {
+		out.print(template.program().getMeaning(interp.getArgString("id")) != null);
+	}
+
+	public static final CommandSpec command_is_binary = ID;
+
+	@Command public void command_is_binary(IPrintWriter out, Interpreter interp) {
+		out.print(JavaCommand.isJavaCommandId(interp.getArgString("id")));
+	}
+
+	public static final CommandSpec command_name = ID;
+
+	@Command public void command_name(IPrintWriter out, Interpreter interp) {
+		String id = interp.getArgString("id");
+		out.print(Macro.getNameFromId(id));
+	}
+
+	public static final CommandSpec command_source_name = ID;
+
+	@Command public void command_source_name(IPrintWriter out, Interpreter interp) {
+		String id = interp.getArgString("id");
+		out.print(Macro.getSourceFromId(id));
+	}
+
+
+	public static final CommandSpec get_macro_id = new CommandSpec.Builder()
+		.parameters("macro_name")
+		.build()
+	;
+
+	@Command public void get_macro_id(IPrintWriter out, Interpreter interp) {
+		String name = interp.getArgString("macro_name");
+		Collection<Macro> macros = template.program().getMacrosByName(name);
+		if( macros.isEmpty() )
+			throw new RuntimeException("macro not found");
+		if( macros.size() != 1 )
+			throw new RuntimeException("macro not unique");
+		Macro macro = macros.iterator().next();
+		out.print(macro.getId());
+	}
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/namespaces/CommandDoc.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,14 @@
+package nabble.naml.namespaces;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface CommandDoc {
+	String value();
+	String[] params() default {};
+	String[] seeAlso() default {};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/namespaces/IntegerNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,76 @@
+package nabble.naml.namespaces;
+
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.Primitive;
+
+
+@Namespace (
+	name = "integer",
+	global = false,
+	transparent = true
+)
+public final class IntegerNamespace implements Primitive {
+	private int i;
+
+	public IntegerNamespace(int i) {
+		this.i = i;
+	}
+
+	public String toString() {
+		return Integer.toString(i);
+	}
+
+	private static final CommandSpec I = new CommandSpec.Builder()
+		.dotParameter("i")
+		.build()
+	;
+
+	public static final CommandSpec plus = I;
+
+	@Command public void plus(IPrintWriter out,Interpreter interp) {
+		out.print( i = i + interp.getArgAsInt("i") );
+	}
+
+	@Command public void value(IPrintWriter out,Interpreter interp) {
+		out.print(i);
+	}
+
+	public static final CommandSpec minus = I;
+
+	@Command public void minus(IPrintWriter out,Interpreter interp) {
+		out.print( i = i - interp.getArgAsInt("i") );
+	}
+
+	public static final CommandSpec is_greater_than = I;
+
+	@Command public void is_greater_than(IPrintWriter out,Interpreter interp) {
+		out.print( i > interp.getArgAsInt("i") );
+	}
+
+	public static final CommandSpec is_greater_than_or_equal_to = I;
+
+	@Command public void is_greater_than_or_equal_to(IPrintWriter out,Interpreter interp) {
+		out.print( i >= interp.getArgAsInt("i") );
+	}
+
+	public static final CommandSpec is_less_than = I;
+
+	@Command public void is_less_than(IPrintWriter out,Interpreter interp) {
+		out.print( i < interp.getArgAsInt("i") );
+	}
+
+	public static final CommandSpec is_less_than_or_equal_to = I;
+
+	@Command public void is_less_than_or_equal_to(IPrintWriter out,Interpreter interp) {
+		out.print( i <= interp.getArgAsInt("i") );
+	}
+
+	@Command public void one(IPrintWriter out,Interpreter interp) {
+		out.print( 1 );
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/namespaces/ListSequence.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,117 @@
+package nabble.naml.namespaces;
+
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+
+import java.util.List;
+
+
+@Namespace (
+	name = "list",
+	global = true
+)
+public class ListSequence<T> extends Sequence {
+	protected final List<T> elements;
+	protected int index = -1;
+
+	public ListSequence(List<T> elements) {
+		this.elements = elements;
+	}
+
+	protected final T get() {
+		return elements.get(index);
+	}
+
+	protected final T getPrevious() {
+		return index == 0? null : elements.get(index-1);
+	}
+
+	public static final CommandSpec has_more_elements = new CommandSpec.Builder()
+		.dotParameter("n")
+		.optionalParameters("n")
+		.build()
+	;
+
+	@Command public final void has_more_elements(IPrintWriter out,Interpreter interp) {
+		int n = interp.getArgAsInt("n", 1);
+		out.print( index+n < elements.size() );
+	}
+
+	public static final CommandSpec next_element = new CommandSpec.Builder()
+		.optionalParameters("inc")
+		.build()
+	;
+
+	@Command public void next_element(IPrintWriter out,Interpreter interp) {
+		int inc = interp.getArgAsInt("inc", 1);
+		index += inc;
+		out.print( index < elements.size() );
+	}
+
+	@Command public void current_element(IPrintWriter out,Interpreter interp) {
+		out.print( get() );
+	}
+
+	@Command public void element_count(IPrintWriter out,Interpreter interp) {
+		out.print( elements.size() );
+	}
+
+	@Command public void is_last_element(IPrintWriter out,Interpreter interp) {
+		out.print( index == elements.size()-1);
+	}
+
+	@Command public void is_first_element(IPrintWriter out,Interpreter interp) {
+		out.print( index == 0);
+	}
+
+	@Command public void list_is_empty(IPrintWriter out,Interpreter interp) {
+		out.print( elements.isEmpty() );
+	}
+
+	public static final CommandSpec remove_current_element = CommandSpec.NO_OUTPUT;
+
+	@Command public void remove_current_element(IPrintWriter out,Interpreter interp) {
+		elements.remove(index--);
+	}
+
+	public static final CommandSpec reset_list_index = CommandSpec.NO_OUTPUT;
+
+	@Command public void reset_list_index(IPrintWriter out,Interpreter interp) {
+		index = -1;
+	}
+
+	@Command public void current_index(IPrintWriter out,Interpreter interp) {
+		out.print(index);
+	}
+
+	public static final CommandSpec has_element_at = new CommandSpec.Builder()
+		.parameters("index")
+		.build()
+	;
+
+	@Command public void has_element_at(IPrintWriter out,Interpreter interp) {
+		int index = interp.getArgAsInt("index");
+		out.print( index < elements.size() );
+	}
+
+	public static final CommandSpec join = new CommandSpec.Builder()
+		.dotParameter("do")
+		.parameters("separator")
+		.build()
+	;
+
+	@Command public void join(IPrintWriter out,Interpreter interp) {
+		Object todo = interp.getArg("do");
+		String separator = interp.getArgString("separator");
+		StringBuilder buf = new StringBuilder();
+		for( index = 0; index < elements.size(); index++ ) {
+			if( index > 0 )
+				buf.append( separator );
+			buf.append( todo );
+		}
+		out.print( buf );
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/namespaces/RegexNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,72 @@
+package nabble.naml.namespaces;
+
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+
+import java.util.regex.Matcher;
+
+
+@Namespace (
+	name = "regex",
+	global = true
+)
+public class RegexNamespace extends Sequence {
+	private final Matcher matcher;
+	private final String source;  // only for debugging
+	private boolean started = false;
+	private boolean isFound = false;
+
+	public RegexNamespace(Matcher matcher,String source) {
+		this.matcher = matcher;
+		this.source = source;
+	}
+
+	private boolean find() {
+		started = true;
+		return isFound = matcher.find();
+	}
+
+	@Command public void find(IPrintWriter out,Interpreter interp) {
+		out.print( find() );
+	}
+
+	@Command public void matches(IPrintWriter out,Interpreter interp) {
+		out.print( matcher.matches() );
+	}
+
+	public static final CommandSpec found = new CommandSpec.Builder()
+		.optionalParameters("group")
+		.build()
+	;
+
+	@Command public void found(IPrintWriter out,Interpreter interp) {
+		String group = interp.getArgString("group");
+		out.print( group==null ? matcher.group() : matcher.group(Integer.parseInt(group)) );
+	}
+
+	@Command public void has_more_elements(IPrintWriter out,Interpreter interp) {
+		if( !started ) {
+			out.print( matcher.find() );
+			matcher.reset();
+		} else if( isFound ) {
+			int start = matcher.start();
+			out.print( matcher.find() );
+			if( !matcher.find(start) )
+				throw new RuntimeException();
+		} else {
+			out.print( false );
+		}
+	}
+
+	@Command public void next_element(IPrintWriter out,Interpreter interp) {
+		out.print( find() );
+	}
+
+	@Command public void current_element(IPrintWriter out,Interpreter interp) {
+		out.print( matcher.group() );
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/namespaces/Sequence.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,17 @@
+package nabble.naml.namespaces;
+
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Command;
+
+
+@Namespace (
+	name = "sequence",
+	global = true
+)
+public abstract class Sequence {
+	@Command public abstract void has_more_elements(IPrintWriter out,Interpreter interp);
+	@Command public abstract void next_element(IPrintWriter out,Interpreter interp);
+	@Command public abstract void current_element(IPrintWriter out,Interpreter interp);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/namespaces/StringList.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,85 @@
+package nabble.naml.namespaces;
+
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+
+import java.util.Collections;
+import java.util.List;
+
+
+@Namespace (
+	name = "string_list",
+	global = true
+)
+public class StringList extends ListSequence<String> {
+
+	public StringList(List<String> strings) {
+		super(strings);
+	}
+
+	@Command public void current_string(IPrintWriter out,Interpreter interp) {
+		current_element(out,interp);
+	}
+
+	@Command public void string_count(IPrintWriter out,Interpreter interp) {
+		element_count(out,interp);
+	}
+
+	public static final CommandSpec add = CommandSpec.NO_OUTPUT()
+		.dotParameter("s")
+		.build()
+	;
+
+	@Command public void add(IPrintWriter out,Interpreter interp) {
+		String s = interp.getArgString("s");
+		if( s != null && !elements.contains(s))
+			elements.add(s);
+	}
+
+	public static final CommandSpec remove = add;
+
+	@Command public void remove(IPrintWriter out,Interpreter interp) {
+		String s = interp.getArgString("s");
+		if( s != null )
+			elements.remove(s);
+	}
+
+	public static final CommandSpec sort = CommandSpec.NO_OUTPUT;
+
+	@Command public void sort(IPrintWriter out,Interpreter interp) {
+		Collections.sort(elements);
+	}
+
+	public static final CommandSpec contains = new CommandSpec.Builder()
+		.dotParameter("s")
+		.build()
+	;
+
+	@Command public void contains(IPrintWriter out,Interpreter interp) {
+		String s = interp.getArgString("s");
+		out.print( elements.contains(s) );
+	}
+
+	@Command public void next_string(IPrintWriter out,Interpreter interp) {
+		next_element(out,interp);
+	}
+
+	public static final CommandSpec has_more_strings = has_more_elements;
+
+	@Command public final void has_more_strings(IPrintWriter out,Interpreter interp) {
+		has_more_elements(out,interp);
+	}
+
+	public static final CommandSpec at_index = new CommandSpec.Builder()
+		.dotParameter("index")
+		.build()
+	;
+
+	@Command public void at_index(IPrintWriter out,Interpreter interp) {
+		int i = interp.getArgAsInt("index");
+		out.print(elements.get(i));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/namespaces/TemplateException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,63 @@
+package nabble.naml.namespaces;
+
+
+public class TemplateException extends Exception {
+
+	// this class is just to prevent constructor confusion with message
+	protected static final class Name {
+		private final String s;
+
+		private Name(String s) {
+			this.s = s;
+		}
+	}
+
+	protected static Name name(String s) {
+		return new Name(s);
+	}
+
+
+	private final String name;
+
+	protected TemplateException(Name name) {
+		this.name = name.s;
+	}
+
+	protected TemplateException(Name name,Exception cause) {
+		super(cause.getMessage(),cause);
+		this.name = name.s;
+	}
+
+	protected TemplateException(Name name,String msg) {
+		super(msg);
+		this.name = name.s;
+	}
+
+	protected TemplateException(Name name,String msg,Exception cause) {
+		super(msg,cause);
+		this.name = name.s;
+	}
+
+	public static TemplateException newInstance(String name) {
+		return new TemplateException(name(name));
+	}
+
+	public static TemplateException newInstance(String name,Exception cause) {
+		return new TemplateException(name(name),cause);
+	}
+
+	protected String getName() {
+		return name;
+	}
+
+	public boolean isNamed(String name) {
+		return this.name.equals(name);
+	}
+
+	public String toString() {
+		String s = getClass().getName() + ": " + getName();
+		String message = getLocalizedMessage();
+		return (message != null) ? (s + ": " + message) : s;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/utils/Jetty.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,252 @@
+package nabble.utils;
+
+import cachingfilter.CachingFilter;
+import fschmidt.util.executor.JettyThreadPool;
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.servlet.AuthenticatingFilter;
+import fschmidt.util.servlet.BadBotFilter;
+import fschmidt.util.servlet.JarDefaultServlet;
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.JtpContextServlet;
+import fschmidt.util.servlet.NoCacheDefaultServlet;
+import fschmidt.util.servlet.RedirectFilter;
+import nabble.model.Init;
+import nabble.view.lib.MyJtpServlet;
+import nabble.view.lib.NabbleConnectionLimitFilter;
+import nabble.view.lib.NabbleErrorFilter;
+import nabble.view.lib.NabbleErrorHandler;
+import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.NCSARequestLog;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.ContextHandlerCollection;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.Holder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlets.GzipFilter;
+import org.eclipse.jetty.servlets.WelcomeFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServlet;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+
+
+public class Jetty {
+
+	private static final Logger logger = LoggerFactory.getLogger(Jetty.class);
+
+	private ContextHandlerCollection contexts = new ContextHandlerCollection();
+	public static volatile boolean isJetty = false;
+
+	public Jetty() {
+		isJetty = true;
+	}
+
+	public ServletContextHandler newContext(String path) throws MalformedURLException {
+		return new ServletContextHandler(contexts, path, ServletContextHandler.SESSIONS);
+	}
+
+	public ServletContextHandler newContext(String path, String resourceBase) throws MalformedURLException {
+		ServletContextHandler context = new ServletContextHandler(contexts, path, ServletContextHandler.SESSIONS);
+		setResourceBase(context, resourceBase);
+		return context;
+	}
+
+	public ServletHolder newServletHolder(HttpServlet servlet, ServletContextHandler context, String[] paths) {
+		ServletHolder sh = new ServletHolder(servlet);
+		for (String path : paths) {
+			context.addServlet(sh, path);
+		}
+		return sh;
+	}
+
+	public ServletContextHandler newFolderContext(String path, String folderPath, String[] paths, boolean dirAllowed) throws MalformedURLException {
+		ServletContextHandler context = new ServletContextHandler(contexts, path, ServletContextHandler.SESSIONS);
+		context.setResourceBase(folderPath);
+		ServletHolder sh = new ServletHolder(new NoCacheDefaultServlet());
+		sh.setInitParameter("maxCacheSize", "0");
+		sh.setInitParameter("dirAllowed", String.valueOf(dirAllowed));
+		for (String p : paths) {
+			context.addServlet(sh, p);
+		}
+		return context;
+	}
+
+	public ServletContextHandler newTools2Context() throws MalformedURLException {
+		ServletContextHandler context = newContext("/tools2", "fschmidt/tools/web/Index.class");
+		// context.setMaxFormContentSize(0);  ???
+		context.addFilter(WelcomeFilter.class,"/*",0).setInitParameter("welcome","Index.jtp");
+		ServletHolder sh = newServletHolder(new JtpContextServlet(), context, new String[] { "/", "*.jtp", "*.class", "*.txt" });
+		sh.setInitParameter("base", "fschmidt.tools.web");
+		addDefaultServlet(context, new String[]{"*.html"} );
+		return context;
+	}
+
+	public void setResourceBase(ServletContextHandler context,String name) throws MalformedURLException {
+		URL url = IoUtils.getUrlFromResource(name);
+		url = new URL(url,".");
+		context.setResourceBase(url.toString());
+	}
+
+	public ServletContextHandler newWebContext(Map<String, String> initParams) throws MalformedURLException {
+		ServletContextHandler context = newContext("/", "nabble/view/web/Index.class");
+
+		newServletHolder( new LuanServlet("classpath:nabble/view/web"), context, new String[]{"*.luan"} );
+
+		JtpContext mainServlet = MyJtpServlet.getJtpContext();
+		String[] paths = new String[] { "/", "*.jtp", "*.class", "*.html", "*.xml", "*.xml.gz", "*.txt", "/file/*", "/attachment/*" };
+		ServletHolder sh = newServletHolder((HttpServlet) mainServlet, context, paths);
+		sh.setInitParameter("urlMapping", "true");
+		sh.setInitParameter("characterEncoding", "utf-8");
+		setInitParameters(sh, initParams);
+		context.setErrorHandler(new NabbleErrorHandler());
+		addDefaultServlet(context);
+		return context;
+	}
+
+	public ServletHolder addDefaultServlet(ServletContextHandler context) {
+		return addDefaultServlet(context, new String[0]);
+	}
+
+	public NCSARequestLog newNCSARequestLog() {
+		return newNCSARequestLog(null);
+	}
+
+	public NCSARequestLog newNCSARequestLog(String filename) {
+		NCSARequestLog log = filename == null? new NCSARequestLog() : new NCSARequestLog(filename);
+		log.setPreferProxiedForAddress(true);
+		log.setExtended(false);
+		return log;
+	}
+
+	public Server newServer(int port, NCSARequestLog log) {
+		Server server = new Server(port);
+		RequestLogHandler rlh = new RequestLogHandler();
+		rlh.setRequestLog(log);
+		HandlerCollection handlers = new HandlerCollection();
+		handlers.setHandlers(new Handler[] {contexts, rlh});
+		server.setHandler(handlers);
+		return server;
+	}
+
+	public Server newServer(int port, String host, NCSARequestLog log) {
+		Server server = new Server();
+		SelectChannelConnector con = new SelectChannelConnector();
+		con.setPort(port);
+		con.setHost(host);
+		server.setConnectors( new Connector[] {con});
+
+		RequestLogHandler rlh = new RequestLogHandler();
+		rlh.setRequestLog(log);
+		HandlerCollection handlers = new HandlerCollection();
+		handlers.setHandlers(new Handler[] {contexts, rlh});
+		server.setHandler(handlers);
+		return server;
+	}
+
+	public void setThreadPool(Server server) {
+		server.setThreadPool(new JettyThreadPool(nabble.model.Executors.foregroundExecutor));
+	}
+
+	public ServletHolder addDefaultServlet(ServletContextHandler context, String[] paths) {
+		ServletHolder sh = new ServletHolder(new JarDefaultServlet());
+		context.addServlet(sh, "*.gif");
+		context.addServlet(sh, "*.js");
+		context.addServlet(sh, "*.css");
+		context.addServlet(sh, "*.xls");
+		context.addServlet(sh, "*.ico");
+		context.addServlet(sh, "*.png");
+		context.addServlet(sh, "*.htm");
+		context.addServlet(sh, "*.woff");
+		context.addServlet(sh, "*.woff2");
+		context.addServlet(sh, "*.svg");
+		for (String path : paths) {
+			context.addServlet(sh, path);
+		}
+		sh.setInitParameter("dirAllowed","false");
+		sh.setInitParameter("gzip","false");
+		sh.setInitParameter("maxCacheSize","0");
+		return sh;
+	}
+
+	public void authenticate(ServletContextHandler context, String path, String username, String password) {
+		FilterHolder fh = context.addFilter(AuthenticatingFilter.class, path, 0);
+		fh.setInitParameter("realm", "Nabble");
+		fh.setInitParameter("username", username);
+		fh.setInitParameter("password", password);
+	}
+
+	public void addShutdownHook(final Server server) {
+		Runnable runnable = new Runnable() {
+			public void run() {
+				try {
+					server.stop();
+					logger.info("Jetty shutdown done");
+				} catch (Exception e) {
+					logger.error("jetty shutdown failed", e);
+				}
+			}
+		};
+		Init.addShutdownHook(runnable);
+	}
+
+	public void addNabbleErrorFilter(ServletContextHandler context) {
+		context.addFilter(NabbleErrorFilter.class, "/*", 0);
+	}
+
+	public void addGzipFilter(ServletContextHandler context) {
+		context.addFilter(GzipFilter.class, "/*", 0);
+	}
+
+	public void addNabbleConnectionLimitFilter(ServletContextHandler context, Map<String, String> initParams) {
+		FilterHolder fh = context.addFilter(NabbleConnectionLimitFilter.class,"/*",0);
+		setInitParameters(fh, initParams);
+	}
+
+	public void addBadBotFilter(ServletContextHandler context, Map<String, String> initParams) {
+		FilterHolder fh = context.addFilter(BadBotFilter.class,"/*",0);
+		setInitParameters(fh, initParams);
+	}
+
+	public void addCachingFilter(ServletContextHandler context, Map<String, String> initParams) {
+		FilterHolder fh = context.addFilter(CachingFilter.class,"/*",0);
+		setInitParameters(fh, initParams);
+	}
+
+	public void addRedirectFilter(ServletContextHandler context, String domain) {
+		FilterHolder fh = context.addFilter(RedirectFilter.class, "/*", 0);
+		fh.setInitParameter("host", domain);
+	}
+
+	private void setInitParameters(Holder fh, Map<String, String> initParams) {
+		for (Map.Entry<String, String> entry : initParams.entrySet())
+			fh.setInitParameter(entry.getKey(), entry.getValue());
+	}
+
+	public void addCustomMimeTypes(MimeTypes mimeTypes) {
+		mimeTypes.addMimeMapping("docm", "application/vnd.ms-word.document.macroEnabled.12");
+		mimeTypes.addMimeMapping("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
+		mimeTypes.addMimeMapping("dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template");
+		mimeTypes.addMimeMapping("potm", "application/vnd.ms-powerpoint.template.macroEnabled.12");
+		mimeTypes.addMimeMapping("potx", "application/vnd.openxmlformats-officedocument.presentationml.template");
+		mimeTypes.addMimeMapping("ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12");
+		mimeTypes.addMimeMapping("ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12");
+		mimeTypes.addMimeMapping("ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow");
+		mimeTypes.addMimeMapping("pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12");
+		mimeTypes.addMimeMapping("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
+		mimeTypes.addMimeMapping("xlam", "application/vnd.ms-excel.addin.macroEnabled.12");
+		mimeTypes.addMimeMapping("xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12");
+		mimeTypes.addMimeMapping("xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12");
+		mimeTypes.addMimeMapping("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+		mimeTypes.addMimeMapping("xltm", "application/vnd.ms-excel.template.macroEnabled.12");
+		mimeTypes.addMimeMapping("xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template");
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/utils/Log4j.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,67 @@
+package nabble.utils;
+
+import java.io.IOException;
+
+import fschmidt.util.log4j.MultiAppender;
+import org.apache.log4j.Appender;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.FileAppender;
+import org.apache.log4j.Level;
+import org.apache.log4j.RollingFileAppender;
+
+
+public final class Log4j {
+
+	private static final PatternLayout LAYOUT = new PatternLayout("%d | %-5p | %c - %m%n");
+
+	public static void initForConsole() {
+		Logger.getRootLogger().setLevel(Level.INFO);
+		PatternLayout layout = new PatternLayout("%d{HH:mm:ss} %-5p %c - %m%n");
+		ConsoleAppender appender = new ConsoleAppender(layout,"System.err");
+		Logger.getRootLogger().addAppender(appender);
+	}
+
+	public static void initForFile(Level level, String fileName)
+			throws IOException
+	{
+		Logger.getRootLogger().setLevel(level);
+		PatternLayout layout = new PatternLayout("%d | %-5p | %c - %m%n");
+		FileAppender appender = new FileAppender(layout,fileName);
+		Logger.getRootLogger().addAppender(appender);
+	}
+
+	public static void initBasicFiles(String infoFile, String warnFile, String errorFile, String maxFileSize)
+			throws IOException
+	{
+		LogManager.getLoggerRepository().setThreshold(Level.INFO);
+
+		RollingFileAppender infoAppender = new RollingFileAppender(LAYOUT, infoFile);
+		infoAppender.setMaxFileSize(maxFileSize);
+		infoAppender.setThreshold(Level.INFO);
+
+		RollingFileAppender warnAppender = new RollingFileAppender(LAYOUT, warnFile);
+		warnAppender.setMaxFileSize(maxFileSize);
+		warnAppender.setThreshold(Level.WARN);
+
+		RollingFileAppender errorAppender = new RollingFileAppender(LAYOUT, errorFile);
+		errorAppender.setMaxFileSize(maxFileSize);
+		errorAppender.setThreshold(Level.ERROR);
+
+		MultiAppender appender = new MultiAppender(new Appender[] { infoAppender, warnAppender, errorAppender });
+		Logger.getRootLogger().addAppender(appender);
+	}
+
+	public static void initForClass(String className, String fileName, String maxFileSize)
+			throws IOException
+	{
+		Logger logger = Logger.getLogger(className);
+		RollingFileAppender appender = new RollingFileAppender(LAYOUT, fileName);
+		appender.setMaxFileSize(maxFileSize);
+		logger.addAppender(appender);
+	}
+
+	private Log4j() {}  // never
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/utils/LuanServlet.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,58 @@
+package nabble.utils;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import luan.Luan;
+import luan.LuanException;
+import nabble.utils.luan.HttpServicer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class LuanServlet extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(LuanServlet.class);
+	protected final String uriPrefix;
+	protected final HttpServicer httpServicer;
+
+	private LuanServlet(String uriPrefix,Luan luan) {
+		this.uriPrefix = uriPrefix;
+		try {
+			luan.eval("require 'classpath:nabble/utils/init.luan'");
+		} catch(LuanException e) {
+			throw new RuntimeException(e);
+		}
+		this.httpServicer = new HttpServicer(luan);
+	}
+
+	public LuanServlet(String uriPrefix) {
+		this(uriPrefix,new Luan());
+	}
+
+	@Override protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		String path = request.getRequestURI();
+		service(request,response,path);
+	}
+
+	public void service(HttpServletRequest request,HttpServletResponse response,String path)
+		throws IOException
+	{
+		if( !path.endsWith(".luan") )
+			throw new RuntimeException("'"+path+"' doesn't end with '.luan'");
+		String uri = uriPrefix + path;
+		try {
+			if( !httpServicer.service(request,response,uri) )
+				response.sendError(HttpServletResponse.SC_NOT_FOUND);
+		} catch(LuanException e) {
+//			throw new RuntimeException(e);
+			String err = e.getLuanStackTraceString();
+			logger.error(err);
+			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,"<pre>"+err+"</pre>");
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/utils/Utils.luan	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,67 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local ipairs = Luan.ipairs or error()
+local try = Luan.try or error()
+local Io = require "luan:Io.luan"
+local uri = Io.uri or error()
+local String = require "luan:String.luan"
+local trim = String.trim or error()
+local Table = require "luan:Table.luan"
+local Mail = require "luan:mail/Mail.luan"
+local Init = require "file:conf/Init.luan"
+local Logging = require "luan:logging/Logging.luan"
+local logger = Logging.logger "Utils"
+
+
+local Utils = {}
+
+function Utils.ssh(domain,cmd)
+	local cmd = "ssh -oConnectTimeout=10 -oServerAliveInterval=10 -oBatchMode=yes -oStrictHostKeyChecking=no -p 14299 administrator@"..domain.." '"..cmd.."'"
+	local con = uri("bash:"..cmd)
+	return con.read_text()
+end
+
+function Utils.ssh_is_ok(domain)
+	return try {
+		function()
+			local s = Utils.ssh(domain,"echo ssh-ok")
+			if trim(s) ~= "ssh-ok" then
+				error("check_ssh failed with: "..s)
+			end
+			return true
+		end
+		catch = function(e)
+			logger.error(e.get_message())
+			Utils.send_mail {
+				subject = domain.." ssh_is_ok failed"
+				body = ""
+			}
+			return false
+		end
+	}
+end
+
+
+java()
+local MailHome = require "java:fschmidt.util.mail.MailHome"
+local MailAddress = require "java:fschmidt.util.mail.MailAddress"
+local PlainTextContent = require "java:fschmidt.util.mail.PlainTextContent"
+
+function Utils.send_mail(mail)
+	mail.from = mail.from or Init.domain.."<monitor@nabble.com>"
+	mail.to = mail.to or Init.monitor_emails or error()
+
+	local jmail = MailHome.newMail()
+	jmail.setFrom( MailAddress.new(mail.from) )
+	local mail_to = {}
+	for _,to in ipairs(mail.to) do
+		mail_to[#mail_to+1] = MailAddress.new(to)
+	end
+	jmail.setTo( Table.unpack(mail_to) )
+	jmail.setSubject( mail.subject )
+	jmail.setContent( PlainTextContent.new(mail.body) )
+	MailHome.getDefaultSmtpServer().send(jmail)
+end
+
+
+return Utils
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/utils/init.luan	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,3 @@
+local Package = require "luan:Package.luan"
+Package.loaded["luan:http/Http.luan"] = require "classpath:nabble/utils/luan/Http.luan"
+return true
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/utils/luan/Http.luan	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,160 @@
+java()
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local ipairs = Luan.ipairs or error()
+local pairs = Luan.pairs or error()
+local set_metatable = Luan.set_metatable or error()
+local type = Luan.type or error()
+local Io = require "luan:Io.luan"
+local Html = require "luan:Html.luan"
+local url_encode = Html.url_encode or error()
+local Table = require "luan:Table.luan"
+local clear = Table.clear or error()
+local Package = require "luan:Package.luan"
+local String = require "luan:String.luan"
+local matches = String.matches or error()
+local Boot = require "luan:Boot.luan"
+local HttpServicer = require "java:nabble.utils.luan.HttpServicer"
+local IoLuan = require "java:luan.modules.IoLuan"
+
+
+local Http = {}
+
+Http.version = "jetty"
+
+local function sent_error(_,_,_)
+	error "headers are not accessible after you start writing content"
+end
+
+local sent_error_metatable = { __index=sent_error, __new_index=sent_error }
+
+function Http.sent_headers(headers)
+	clear(headers)
+	set_metatable(headers,sent_error_metatable)
+end
+
+
+local function new_common(this)
+	this = this or {}
+	this.headers = {}
+	return this
+end
+
+local function to_list(input)
+	return type(input) == "table" and input or {input}
+end
+
+
+function Http.new_request(this)
+	this = new_common(this)
+	this.method = "GET"  -- default
+	-- this.path
+	-- this.protocol
+	this.scheme = "http"  -- default
+	this.port = 80  -- default
+	this.parameters = {}
+	this.cookies = {}
+
+	function this.query_string()
+		local string_uri = Io.uri "string:"
+		local out = string_uri.text_writer()
+		local and_char = ""
+		for name, values in pairs(this.parameters) do
+			for _, value in ipairs(to_list(values)) do
+				out.write( and_char, url_encode(name), "=", url_encode(value) )
+				and_char = "&"
+			end
+		end
+		out.close()
+		local s = string_uri.read_text()
+		return s ~= "" and s or nil
+	end
+
+	function this.full_path()  -- compatible with impl
+		local path = this.path
+		if this.method ~= "POST" then
+			local query = this.query_string()
+			if query ~= nil then
+				path = path.."?"..query
+			end
+		end
+		return path
+	end
+
+	function this.url()
+		return this.scheme.."://"..this.headers["host"]..this.full_path()
+	end
+
+	return this
+end
+
+local STATUS = {
+	OK = 200
+	MOVED_PERMANENTLY = 301
+	-- add more as needed
+}
+Http.STATUS = STATUS
+
+function Http.new_response(this)
+	this = new_common(this)
+	this.status = STATUS.OK
+	if this.java ~= nil then
+		this.send_redirect = this.java.sendRedirect
+		this.send_error = this.java.sendError
+
+		function this.set_cookie(name,value,attributes)
+			attributes = attributes or {}
+			attributes["Path"] = attributes["Path"] or "/"
+			HttpServicer.setCookie(Http.request.java,this.java,name,value,attributes)
+		end
+
+		function this.set_persistent_cookie(name,value,attributes)
+			attributes = attributes or {}
+			attributes["Max-Age"] = "10000000"
+			this.set_cookie(name,value,attributes)
+		end
+
+		function this.remove_cookie(name,attributes)
+			attributes = attributes or {}
+			attributes["Max-Age"] = "0"
+			this.set_cookie(name,"delete",attributes)
+		end
+
+		function this.set()
+			HttpServicer.setResponse(this,this.java)
+			Http.sent_headers(this.headers)
+		end
+
+		function this.text_writer()
+			this.java.setCharacterEncoding "UTF-8"
+			this.java.setContentType "text/html; charset=UTF-8"
+			this.set()
+			return Boot.text_writer(this.java.getWriter())
+		end
+
+		function this.binary_writer()
+			this.set()
+			return IoLuan.binaryWriter(this.java.getOutputStream())
+		end
+
+		function this.reset()
+			this.java.reset()
+			set_metatable(this.headers,nil)
+		end
+	end
+	return this
+end
+
+-- request = new_request{}  -- filled in by HttpServicer
+-- response = new_response{}  -- filled in by HttpServicer
+
+
+function Http.uncache_site()
+	for k in pairs(Table.copy(Package.loaded)) do
+		if matches(k,"^site:") then
+			Package.loaded[k] = nil
+		end
+	end
+end
+
+return Http
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/utils/luan/HttpServicer.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,197 @@
+package nabble.utils.luan;
+
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+import java.io.PrintWriter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Enumeration;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import luan.Luan;
+import luan.LuanFunction;
+import luan.LuanException;
+import luan.LuanTable;
+import luan.LuanCloner;
+import luan.modules.PackageLuan;
+import luan.modules.IoLuan;
+import luan.modules.TableLuan;
+import luan.modules.Utils;
+import luan.modules.url.LuanUrl;
+
+
+public final class HttpServicer {
+	private static final Logger logger = LoggerFactory.getLogger(HttpServicer.class);
+
+	private Luan currentLuan;
+
+	public HttpServicer(Luan luan) {
+		this.currentLuan = luan;
+	}
+
+	public boolean service(HttpServletRequest request,HttpServletResponse response,String modName)
+		throws LuanException
+	{
+		LuanFunction fn;
+		Luan luan;
+		synchronized(this) {
+			enableLoad("luan:http/Http.luan",modName);
+			LuanTable module = (LuanTable)PackageLuan.require(currentLuan,"luan:http/Http.luan");
+			Object mod = PackageLuan.load(currentLuan,modName);
+			if( mod.equals(Boolean.FALSE) )
+				return false;
+			if( !(mod instanceof LuanFunction) )
+				throw new LuanException( "module '"+modName+"' must return a function" );
+			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
+			luan = (Luan)cloner.clone(currentLuan);
+			fn = (LuanFunction)cloner.get(mod);
+		}
+
+		LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:http/Http.luan");
+
+		// request
+		LuanFunction newRequestFn = (LuanFunction)module.rawGet("new_request");
+		LuanTable requestTbl = (LuanTable)newRequestFn.call();
+		module.rawPut("request",requestTbl);
+		requestTbl.rawPut("java",request);
+		requestTbl.rawPut("method",request.getMethod());
+		requestTbl.rawPut("path",request.getRequestURI());
+		requestTbl.rawPut("protocol",request.getProtocol());
+		requestTbl.rawPut("scheme",request.getScheme());
+		requestTbl.rawPut("port",request.getServerPort());
+
+		LuanTable headersTbl = (LuanTable)requestTbl.rawGet("headers");
+		for( Enumeration<String> enKeys = request.getHeaderNames(); enKeys.hasMoreElements(); ) {
+			String key = enKeys.nextElement();
+			List<String> values = new ArrayList<String>();
+			for( Enumeration<String> en = request.getHeaders(key); en.hasMoreElements(); ) {
+				values.add(en.nextElement());
+			}
+			int size = values.size();
+			if(size==0) throw new RuntimeException();
+			key = key.toLowerCase();
+			Object value = size==1 ? values.get(0) : new LuanTable(luan,values);
+			headersTbl.rawPut(key,value);
+		}
+
+		LuanTable parametersTbl = (LuanTable)requestTbl.rawGet("parameters");
+		for( Object obj : request.getParameterMap().entrySet() ) {
+			Map.Entry entry = (Map.Entry)obj;
+			String[] a = (String[])entry.getValue();
+			Object value = a.length==1 ? a[0] : new LuanTable(luan,Arrays.asList(a));
+			parametersTbl.rawPut(entry.getKey(),value);
+		}
+		LuanTable cookieTbl = (LuanTable)requestTbl.rawGet("cookies");
+		for( Cookie cookie : request.getCookies() ) {
+			cookieTbl.rawPut( cookie.getName(), unescape(cookie.getValue()) );
+		}
+
+
+		// response
+		LuanTable responseTbl = new LuanTable(luan);
+		responseTbl.rawPut("java",response);
+		LuanFunction newResponseFn = (LuanFunction)module.rawGet("new_response");
+		newResponseFn.call( responseTbl );
+		module.rawPut("response",responseTbl);
+
+		fn.call();
+		return true;
+	}
+
+	public static void setResponse(LuanTable responseTbl,HttpServletResponse response) throws LuanException {
+		int status = Luan.asInteger(responseTbl.rawGet("status"));
+		response.setStatus(status);
+		LuanTable responseHeaders = (LuanTable)responseTbl.rawGet("headers");
+		for( Map.Entry<Object,Object> entry : responseHeaders.rawIterable() ) {
+			String name = (String)entry.getKey();
+			Object val = entry.getValue();
+			if( val instanceof LuanTable ) {
+				LuanTable values = (LuanTable)val;
+				for( Object value : values.asList() ) {
+					setResponse(response,name,value);
+				}
+			} else {
+				setResponse(response,name,val);
+			}
+		}
+	}
+
+	private static void setResponse(HttpServletResponse response,String name,Object value) throws LuanException {
+		if( value instanceof String ) {
+			response.setHeader(name,(String)value);
+			return;
+		}
+		Integer i = Luan.asInteger(value);
+		if( i != null ) {
+			response.setIntHeader(name,i);
+			return;
+		}
+		throw new IllegalArgumentException("value must be string or integer for headers table");
+	}
+
+	private void enableLoad(String... mods) throws LuanException {
+		LuanTable loaded = PackageLuan.loaded(currentLuan);
+		for( String mod : mods ) {
+			if( loaded.rawGet(mod) == null ) {
+				LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
+				currentLuan = (Luan)cloner.clone(currentLuan);
+				break;
+			}
+		}
+	}
+
+
+
+	// static utils
+
+	private static String escape(String value) {
+		return value.replaceAll(";", "%3B");
+	}
+
+	private static String unescape(String value) {
+		return value.replaceAll("%3B", ";");
+	}
+
+	private static Cookie getCookie(HttpServletRequest request,String name) {
+		Cookie[] cookies = request.getCookies();
+		if( cookies == null )
+			return null;
+		for (Cookie cookie : cookies) {
+			if (cookie.getName().equals(name))
+				return cookie;
+		}
+		return null;
+	}
+
+	public static void setCookie(HttpServletRequest request,HttpServletResponse response,String name,String value,LuanTable attributes) {
+		Cookie cookie = getCookie(request,name);
+		if( cookie==null || !cookie.getValue().equals(value) ) {
+			cookie = new Cookie(name, escape(value));
+			if( attributes != null ) {
+				String path = (String)attributes.rawGet("Path");
+				if( path != null )
+					cookie.setPath(path);
+				String domain = (String)attributes.rawGet("Domain");
+				if (domain != null && domain.length() > 0)
+					cookie.setDomain(domain);
+				String maxAge = (String)attributes.rawGet("Max-Age");
+				if( maxAge != null )
+					cookie.setMaxAge(Integer.parseInt(maxAge));
+			}
+			response.addCookie(cookie);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/Cache.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,172 @@
+/*
+
+Copyright (C) 2001  Franklin Schmidt <frank@gustos.com>
+
+*/
+
+package nabble.view.lib;
+
+import fschmidt.db.Listener;
+import fschmidt.util.servlet.HttpCache;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.FileUpload;
+import nabble.model.Message;
+import nabble.model.ModelHome;
+import nabble.model.Executors;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+
+public final class Cache {
+	private static final Logger logger = LoggerFactory.getLogger(Cache.class);
+
+	private static final int nodeEvent = 1;
+	private static final int descendantChangeEvent = 2;
+//	private static final int topicEvent = 3;
+//	private static final int postEvent = 4;
+//	private static final int skinEvent = 5;
+//	private static final int activityEvent = 6;
+	private static final int regularEvent = 7;
+	private static final int nodeFileEvent = 9;
+//	private static final int forumFileEvent = 10;
+//	private static final int topicRootEvent = 11;
+	private static final int userFileEvent = 12;
+	private static final int siteChangeEvent = 13;
+	private static final int newUserEvent = 14;
+	private static final int groupChangeEvent = 15;
+
+	private static String event(int type,long id) {
+		return Integer.toString(type) + ':' + Long.toString(id);
+	}
+
+	private static String event(int type,long id1,long id2) {
+		return Integer.toString(type) + ':' + Long.toString(id1) + ':' + Long.toString(id2);
+	}
+
+	public static String descendantChangeEvent(Node node) {
+		return event(descendantChangeEvent,node.getSite().getId(),node.getId());
+	}
+
+	public static String nodeChangeEvent(Node node) {
+		return event(nodeEvent,node.getSite().getId(),node.getId());
+	}
+
+	public static String nodeFileChangeEvent(Node node) {
+		return event(nodeFileEvent,node.getSite().getId(),node.getId());
+	}
+
+	public static String userFileChangeEvent(User user) {
+		return event(userFileEvent,user.getSite().getId(), user.getId());
+	}
+
+	public static String siteChangeEvent(Site site) {
+		return event(siteChangeEvent, site.getId());
+	}
+
+	public static String newUserEvent(Site site) {
+		return event(newUserEvent, site.getId());
+	}
+
+	public static String groupChangeEvent(Site site) {
+		return event(groupChangeEvent, site.getId());
+	}
+
+
+	static {
+
+		ModelHome.addDescendantChangeListener(new Listener<Node>() {
+			public void event(Node node) {
+				uncache( Cache.descendantChangeEvent(node) );
+			}
+		} );
+
+		ModelHome.addNodeChangeListener(new Listener<Node>() {
+			public void event(Node node) {
+				uncache( Cache.nodeChangeEvent(node) );
+			}
+		} );
+
+		FileUpload.addFileUpdateListener(new Listener<Message.Source>() {
+			public void event(Message.Source src) {
+				char type = src.getMessageSourceType().getCode();
+				switch(type) {
+				case 't':
+					return;
+				case 'a':  // for now -fschmidt
+					uncache( Cache.userFileChangeEvent(((Message.AvatarSource)src).getUser()) );
+					return;
+				case 'n':
+					uncache( Cache.nodeFileChangeEvent((Node)src) );
+					return;
+				case 's':
+					uncache( Cache.siteChangeEvent((Site)src) );
+					return;
+				default:
+					throw new RuntimeException("type = '"+type+"'");
+				}
+			}
+		} );
+
+		ModelHome.addSiteChangeListener(new Listener<Site>() {
+			public void event(Site site) {
+				uncache( Cache.siteChangeEvent(site) );
+			}
+		} );
+
+		ModelHome.addUserInsertListener(new Listener<User>() {
+			public void event(User user) {
+				uncache( Cache.newUserEvent(user.getSite()) );
+			}
+		} );
+
+		Permissions.addGroupChangeListener(new Listener<Site>() {
+			public void event(Site site) {
+				uncache( Cache.newUserEvent(site) );
+			}
+		} );
+
+		Permissions.addPermissionChangeListener(new Listener<Site>() {
+			public void event(Site site) {
+				uncache( Cache.siteChangeEvent(site) );
+			}
+		} );
+
+	}
+
+	public static void uncache(String event) {
+		HttpCache cache = MyJtpServlet.getJtpContextNotNull().getHttpCache();
+		if( cache != null )
+			cache.modified(event);
+	}
+
+
+	private static Set<Long> secondSet = new HashSet<Long>();
+
+	public static synchronized String regularEvent(long seconds) {
+		final String event = event(regularEvent,seconds);
+		if( secondSet.add(seconds) ) {
+			Executors.scheduleWithFixedDelay(new Runnable(){public void run(){
+				JtpContext context = MyJtpServlet.getJtpContext();
+				if( context != null ) {
+					HttpCache cache = context.getHttpCache();
+					if( cache != null ) {
+						cache.modified(event);
+					}
+				}
+			}},seconds,seconds,TimeUnit.SECONDS);
+		}
+		return event;
+	}
+
+	private static final long secondsPerDay = 60*60*24;
+	public static final String dailyEvent = regularEvent(secondsPerDay);
+
+	static void init() {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/ChangeEmailMail.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,82 @@
+
+package nabble.view.lib;
+
+import fschmidt.util.mail.AlternativeMultipartContent;
+import fschmidt.util.mail.Content;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import fschmidt.util.mail.TextContent;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+
+public class ChangeEmailMail {
+
+	public static void send(Site site, String username, String oldEmail, String newEmail, String url) {
+		StringWriter content = new StringWriter();
+		PrintWriter out = new PrintWriter(content);
+		// text part
+		
+		out.print( "\r\nDear " );
+		out.print( (username) );
+		out.print( ",\r\n\r\nYou or the administrator of \"" );
+		out.print( (site.getRootNode().getSubject()) );
+		out.print( "\" wants to change the email address of your user account.\r\nUsername: " );
+		out.print( (username) );
+		out.print( "\r\nOld email address: " );
+		out.print( (oldEmail) );
+		out.print( "\r\nNew email address: " );
+		out.print( (newEmail) );
+		out.print( "\r\n\r\nPlease click on the link below if you want to confirm this change:\r\n" );
+		out.print( (url) );
+		out.print( "\r\n\r\nIf you didn't request this email or have no idea why you received it, please ignore it.\r\n\r\nRegards,\r\nThe Nabble Team\r\n" );
+
+		out.flush();
+		String text = content.toString();
+
+		content.getBuffer().setLength(0);
+		// aol part
+		
+		out.print( "\r\nDear " );
+		out.print( (username) );
+		out.print( ",\r\n\r\nYou or the administrator of \"" );
+		out.print( (site.getRootNode().getSubject()) );
+		out.print( "\" wants to change the email address of your user account.\r\nUsername: " );
+		out.print( (username) );
+		out.print( "\r\nOld email address: " );
+		out.print( (oldEmail) );
+		out.print( "\r\nNew email address: " );
+		out.print( (newEmail) );
+		out.print( "\r\n\r\nPlease click on the link below if you want to confirm this change:\r\n<a href=\"" );
+		out.print( (url) );
+		out.print( "\">" );
+		out.print( (url) );
+		out.print( "</a>\r\n\r\nIf you didn't request this email or have no idea why you received it, please ignore it.\r\n\r\nRegards,\r\nThe Nabble Team\r\n" );
+
+		out.flush();
+		String aol = content.toString();
+
+		out.close();
+		try {
+			Mail mail = MailHome.newMail();
+			mail.setFrom( new MailAddress(ModelHome.noReply) );
+			mail.setTo( new MailAddress(newEmail) );
+			mail.setSubject( "Email Change" );
+			mail.setContent( new AlternativeMultipartContent(new Content[]{
+				new PlainTextContent(text),
+				new TextContent("x-aol",aol),
+			}) );
+			ModelHome.send(mail);
+		} catch(MailException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/ChangeEmailMail.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,82 @@
+<%
+package nabble.view.lib;
+
+import fschmidt.util.mail.AlternativeMultipartContent;
+import fschmidt.util.mail.Content;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import fschmidt.util.mail.TextContent;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+
+public class ChangeEmailMail {
+
+	public static void send(Site site, String username, String oldEmail, String newEmail, String url) {
+		StringWriter content = new StringWriter();
+		PrintWriter out = new PrintWriter(content);
+		// text part
+		%>
+		Dear <%=username%>,
+
+		You or the administrator of "<%=site.getRootNode().getSubject()%>" wants to change the email address of your user account.
+		Username: <%=username%>
+		Old email address: <%=oldEmail%>
+		New email address: <%=newEmail%>
+
+		Please click on the link below if you want to confirm this change:
+		<%=url%>
+
+		If you didn't request this email or have no idea why you received it, please ignore it.
+
+		Regards,
+		The Nabble Team
+		<%
+		out.flush();
+		String text = content.toString();
+
+		content.getBuffer().setLength(0);
+		// aol part
+		%>
+		Dear <%=username%>,
+
+		You or the administrator of "<%=site.getRootNode().getSubject()%>" wants to change the email address of your user account.
+		Username: <%=username%>
+		Old email address: <%=oldEmail%>
+		New email address: <%=newEmail%>
+
+		Please click on the link below if you want to confirm this change:
+		<a href="<%=url%>"><%=url%></a>
+
+		If you didn't request this email or have no idea why you received it, please ignore it.
+
+		Regards,
+		The Nabble Team
+		<%
+		out.flush();
+		String aol = content.toString();
+
+		out.close();
+		try {
+			Mail mail = MailHome.newMail();
+			mail.setFrom( new MailAddress(ModelHome.noReply) );
+			mail.setTo( new MailAddress(newEmail) );
+			mail.setSubject( "Email Change" );
+			mail.setContent( new AlternativeMultipartContent(new Content[]{
+				new PlainTextContent(text),
+				new TextContent("x-aol",aol),
+			}) );
+			ModelHome.send(mail);
+		} catch(MailException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/ClearCache.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,19 @@
+package nabble.view.lib;
+
+import fschmidt.util.servlet.DbCache;
+import nabble.model.Db;
+import nabble.utils.Log4j;
+
+
+public final class ClearCache {
+
+	public static void run() {
+		new DbCache(Db.dbGlobal()).clear();
+	}
+/*
+	public static void main(String[] args) {
+		Log4j.initForConsole();
+		run();
+	}
+*/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/EmbedUtils.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,50 @@
+
+package nabble.view.lib;
+
+import nabble.model.Node;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+public final class EmbedUtils {
+	private EmbedUtils() {}  // never
+
+	private static String url(HttpServletRequest request, Node node) {
+		return Jtp.getBaseUrl(request) + Jtp.path(node);
+	}
+
+	private static String getRawForumSnippet(HttpServletRequest request, Node app) {
+		return 
+		"<a id=\"nabblelink\" href=\""
+		+(url(request,app))
+		+"\">"
+		+(app.getSubject())
+		+"</a>\r\n<script src=\""
+		+(Jtp.getBaseUrl(request))
+		+"/embed/f"
+		+(app.getId())
+		+"\"></script>"
+;
+	}
+
+	public static String getForumSnippet(HttpServletRequest request, Node app) {
+		return getRawForumSnippet(request,app).replace("<", "&lt;").replace(">", "&gt;");
+	}
+	
+	public static String getTopicSnippet(HttpServletRequest request, Node rootPost) {
+		return 
+		"<a id=\"nabblelink\" href=\""
+		+(url(request,rootPost))
+		+"\">"
+		+(rootPost.getSubject())
+		+"</a>\r\n<script src=\""
+		+(rootPost.getSite().getBaseUrl())
+		+"/embed/p"
+		+(rootPost.getId())
+		+"\"></script>"
+
+		.replace("<", "&lt;").replace(">", "&gt;");
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/EmbedUtils.jmp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,32 @@
+<%
+package nabble.view.lib;
+
+import nabble.model.Node;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+public final class EmbedUtils {
+	private EmbedUtils() {}  // never
+
+	private static String url(HttpServletRequest request, Node node) {
+		return Jtp.getBaseUrl(request) + Jtp.path(node);
+	}
+
+	private static String getRawForumSnippet(HttpServletRequest request, Node app) {
+		return %><a id="nabblelink" href="<%=url(request,app)%>"><%=app.getSubject()%></a>
+<script src="<%=Jtp.getBaseUrl(request)%>/embed/f<%=app.getId()%>"></script><%;
+	}
+
+	public static String getForumSnippet(HttpServletRequest request, Node app) {
+		return getRawForumSnippet(request,app).replace("<", "&lt;").replace(">", "&gt;");
+	}
+	
+	public static String getTopicSnippet(HttpServletRequest request, Node rootPost) {
+		return %><a id="nabblelink" href="<%=url(request,rootPost)%>"><%=rootPost.getSubject()%></a>
+<script src="<%=rootPost.getSite().getBaseUrl()%>/embed/p<%=rootPost.getId()%>"></script><%
+		.replace("<", "&lt;").replace(">", "&gt;");
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/HtmlViewUtils.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,200 @@
+
+package nabble.view.lib;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+// used outside nabble, so do not use Init
+public final class HtmlViewUtils {
+
+	private HtmlViewUtils() {}  // never
+
+
+	/** Number of pages to be displayed before/after the current page*/
+	public static final int NEIGHBOR_PAGES = 3;
+
+	public static interface PagingPath {
+		public String path(int row);
+	}
+
+	public static final class GenericPagingPath implements PagingPath {
+		private final String url;
+
+		public GenericPagingPath(String url) {
+			this.url = url;
+		}
+
+		public String path(int row) {
+			return url + (row==0 ? "" : "&i=" + row);
+		}
+	}
+
+	public static void genericPaging(HttpServletRequest request,HttpServletResponse response, int count, int currentRecord, int maxRows, PagingPath pagingPath, String margins)
+		throws IOException
+	{
+		genericPaging(request, response, count, currentRecord, maxRows, pagingPath, margins, false, NEIGHBOR_PAGES);
+	}
+
+	public static void genericPaging(HttpServletRequest request,HttpServletResponse response, int count, int currentRecord, int maxRows, PagingPath pagingPath, String margins, boolean hideLastPage,int NEIGHBOR_PAGES)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		if (count > maxRows) {
+			
+		out.print( "\r\n<style type=\"text/css\">\r\n	span.page { padding: .1em .3em }\r\n	span.current-page { border-width:1px; border-style:solid; }\r\n</style>\r\n<span style=\"float:right;white-space:nowrap;padding:" );
+		out.print( (margins) );
+		out.print( ";font-weight:normal\">\r\n	" );
+
+					int currentPage = (currentRecord / maxRows) + 1;
+					int lastPage = count / maxRows;
+					// If there are more items on the next page...
+					if (count % maxRows > 0)
+						lastPage++; // ... I increment the last page.
+
+					int before = currentPage - NEIGHBOR_PAGES;
+					int after = currentPage + NEIGHBOR_PAGES;
+					if (before <= 1) {
+						// Among the first pages
+						int limit = Math.min(currentPage + NEIGHBOR_PAGES, lastPage);
+
+						for (int i = 1; i <= limit; i++) {
+							if (i == currentPage) {
+								
+		out.print( "\r\n<span class=\"current-page page medium-border-color\">" );
+		out.print( (i) );
+		out.print( "</span>\r\n" );
+
+							} else {
+								int page = (i-1) * maxRows;
+								
+		out.print( "\r\n	<span class=\"page\">\r\n		<a href=\"" );
+		out.print( (pagingPath.path(page)) );
+		out.print( "\" title=\"page " );
+		out.print( (i) );
+		out.print( "\">" );
+		out.print( (i) );
+		out.print( "</a>\r\n	</span>\r\n" );
+
+							}
+						}
+
+						if (limit < lastPage) {
+							int page = (lastPage-1) * maxRows;
+							boolean atEnd = after == lastPage-1;
+							
+		out.print( "\r\n" );
+		out.print( (atEnd? "" : "...") );
+		out.print( "\r\n" );
+ if (!hideLastPage || atEnd) { 
+		out.print( "\r\n<span class=\"page\">\r\n	<a href=\"" );
+		out.print( (pagingPath.path(page)) );
+		out.print( "\" title=\"page " );
+		out.print( (lastPage) );
+		out.print( "\"" );
+		out.print( (atEnd? "" : " rel=\"nofollow\"") );
+		out.print( ">" );
+		out.print( (lastPage) );
+		out.print( "</a>\r\n</span>\r\n" );
+ } 
+		out.print( "\r\n" );
+
+						}
+					} else if (before > 1 && after < lastPage) {
+						// In the middle pages
+						
+		out.print( "\r\n<span class=\"page\">\r\n	<a href=\"" );
+		out.print( (pagingPath.path(0)) );
+		out.print( "\" title=\"page 1\">1</a>\r\n</span>\r\n" );
+		out.print( (before == 2? "" : "...") );
+		out.print( "\r\n" );
+
+						int limit = before + 2 * NEIGHBOR_PAGES;
+						for (int i = before; i <= limit; i++) {
+							if (i == currentPage) {
+								
+		out.print( "<span class=\"current-page page medium-border-color\">" );
+		out.print( (i) );
+		out.print( "</span>" );
+
+							} else {
+								int page = (i-1) * maxRows;
+								
+		out.print( "\r\n	<span class=\"page\">\r\n		<a href=\"" );
+		out.print( (pagingPath.path(page)) );
+		out.print( "\" title=\"page " );
+		out.print( (i) );
+		out.print( "\">" );
+		out.print( (i) );
+		out.print( "</a>\r\n	</span>\r\n" );
+
+							}
+						}
+
+						if (limit < lastPage) {
+							int page = (lastPage-1) * maxRows;
+							boolean atEnd = after == lastPage-1;
+							
+		out.print( "\r\n" );
+		out.print( (atEnd? "" : "...") );
+		out.print( "\r\n" );
+ if (!hideLastPage || atEnd) { 
+		out.print( "\r\n<span class=\"page\">\r\n	<a href=\"" );
+		out.print( (pagingPath.path(page)) );
+		out.print( "\" title=\"page " );
+		out.print( (lastPage) );
+		out.print( "\"" );
+		out.print( (atEnd? "" : " rel=\"nofollow\"") );
+		out.print( ">" );
+		out.print( (lastPage) );
+		out.print( "</a>\r\n</span>\r\n" );
+ } 
+		out.print( "\r\n" );
+
+						}
+					} else {
+						// Among the last pages
+						
+		out.print( "\r\n<span class=\"page\">\r\n	<a href=\"" );
+		out.print( (pagingPath.path(0)) );
+		out.print( "\" title=\"page 1\">1</a>\r\n</span>\r\n" );
+		out.print( (before == 2? "" : "...") );
+		out.print( "\r\n" );
+
+						for (int i = before; i <= lastPage; i++) {
+							if (i == currentPage) {
+								
+		out.print( "<span class=\"current-page page medium-border-color\">" );
+		out.print( (i) );
+		out.print( "</span>" );
+
+							} else {
+								int page = (i-1) * maxRows;
+								
+		out.print( "\r\n	<span class=\"page\">\r\n		<a href=\"" );
+		out.print( (pagingPath.path(page)) );
+		out.print( "\" title=\"page " );
+		out.print( (i) );
+		out.print( "\">" );
+		out.print( (i) );
+		out.print( "</a>\r\n	</span>\r\n" );
+
+							}
+						}
+					}
+				
+		out.print( "\r\n</span>\r\n" );
+
+		}
+	}
+
+	public static void googleAnalytics(PrintWriter out) {
+		
+		out.print( "\r\n<script>\r\n	(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){\r\n	(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),\r\n	m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)\r\n	})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');\r\n	\r\n	ga('create', 'UA-91855-9', 'auto', 'nabble');\r\n	ga('nabble.send', 'pageview');\r\n</script>\r\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/HtmlViewUtils.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,168 @@
+<%
+package nabble.view.lib;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+// used outside nabble, so do not use Init
+public final class HtmlViewUtils {
+
+	private HtmlViewUtils() {}  // never
+
+
+	/** Number of pages to be displayed before/after the current page*/
+	public static final int NEIGHBOR_PAGES = 3;
+
+	public static interface PagingPath {
+		public String path(int row);
+	}
+
+	public static final class GenericPagingPath implements PagingPath {
+		private final String url;
+
+		public GenericPagingPath(String url) {
+			this.url = url;
+		}
+
+		public String path(int row) {
+			return url + (row==0 ? "" : "&i=" + row);
+		}
+	}
+
+	public static void genericPaging(HttpServletRequest request,HttpServletResponse response, int count, int currentRecord, int maxRows, PagingPath pagingPath, String margins)
+		throws IOException
+	{
+		genericPaging(request, response, count, currentRecord, maxRows, pagingPath, margins, false, NEIGHBOR_PAGES);
+	}
+
+	public static void genericPaging(HttpServletRequest request,HttpServletResponse response, int count, int currentRecord, int maxRows, PagingPath pagingPath, String margins, boolean hideLastPage,int NEIGHBOR_PAGES)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		if (count > maxRows) {
+			%>
+			<style type="text/css">
+				span.page { padding: .1em .3em }
+				span.current-page { border-width:1px; border-style:solid; }
+			</style>
+			<span style="float:right;white-space:nowrap;padding:<%=margins%>;font-weight:normal">
+				<%
+					int currentPage = (currentRecord / maxRows) + 1;
+					int lastPage = count / maxRows;
+					// If there are more items on the next page...
+					if (count % maxRows > 0)
+						lastPage++; // ... I increment the last page.
+
+					int before = currentPage - NEIGHBOR_PAGES;
+					int after = currentPage + NEIGHBOR_PAGES;
+					if (before <= 1) {
+						// Among the first pages
+						int limit = Math.min(currentPage + NEIGHBOR_PAGES, lastPage);
+
+						for (int i = 1; i <= limit; i++) {
+							if (i == currentPage) {
+								%>
+								<span class="current-page page medium-border-color"><%=i%></span>
+								<%
+							} else {
+								int page = (i-1) * maxRows;
+								%>
+									<span class="page">
+										<a href="<%=pagingPath.path(page)%>" title="page <%=i%>"><%=i%></a>
+									</span>
+								<%
+							}
+						}
+
+						if (limit < lastPage) {
+							int page = (lastPage-1) * maxRows;
+							boolean atEnd = after == lastPage-1;
+							%>
+							<%=atEnd? "" : "..."%>
+							<% if (!hideLastPage || atEnd) { %>
+							<span class="page">
+								<a href="<%=pagingPath.path(page)%>" title="page <%=lastPage%>"<%=atEnd? "" : " rel=\"nofollow\""%>><%=lastPage%></a>
+							</span>
+							<% } %>
+							<%
+						}
+					} else if (before > 1 && after < lastPage) {
+						// In the middle pages
+						%>
+						<span class="page">
+							<a href="<%=pagingPath.path(0)%>" title="page 1">1</a>
+						</span>
+						<%=before == 2? "" : "..."%>
+						<%
+						int limit = before + 2 * NEIGHBOR_PAGES;
+						for (int i = before; i <= limit; i++) {
+							if (i == currentPage) {
+								%><span class="current-page page medium-border-color"><%=i%></span><%
+							} else {
+								int page = (i-1) * maxRows;
+								%>
+									<span class="page">
+										<a href="<%=pagingPath.path(page)%>" title="page <%=i%>"><%=i%></a>
+									</span>
+								<%
+							}
+						}
+
+						if (limit < lastPage) {
+							int page = (lastPage-1) * maxRows;
+							boolean atEnd = after == lastPage-1;
+							%>
+							<%=atEnd? "" : "..."%>
+							<% if (!hideLastPage || atEnd) { %>
+							<span class="page">
+								<a href="<%=pagingPath.path(page)%>" title="page <%=lastPage%>"<%=atEnd? "" : " rel=\"nofollow\""%>><%=lastPage%></a>
+							</span>
+							<% } %>
+						<%
+						}
+					} else {
+						// Among the last pages
+						%>
+						<span class="page">
+							<a href="<%=pagingPath.path(0)%>" title="page 1">1</a>
+						</span>
+						<%=before == 2? "" : "..."%>
+						<%
+						for (int i = before; i <= lastPage; i++) {
+							if (i == currentPage) {
+								%><span class="current-page page medium-border-color"><%=i%></span><%
+							} else {
+								int page = (i-1) * maxRows;
+								%>
+									<span class="page">
+										<a href="<%=pagingPath.path(page)%>" title="page <%=i%>"><%=i%></a>
+									</span>
+								<%
+							}
+						}
+					}
+				%>
+			</span>
+			<%
+		}
+	}
+
+	public static void googleAnalytics(PrintWriter out) {
+		%>
+		<script>
+			(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+			(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+			m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+			})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+			
+			ga('create', 'UA-91855-9', 'auto', 'nabble');
+			ga('nabble.send', 'pageview');
+		</script>
+		<%
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/Jtp.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,966 @@
+package nabble.view.lib;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.DailyNumber;
+import nabble.model.Init;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.NodeIterator;
+import nabble.model.NodeSearcher;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.web.forum.Permalink;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.NodeNamespace;
+import nabble.view.web.template.UserNamespace;
+import org.apache.commons.fileupload.DiskFileUpload;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class Jtp {
+
+	private static final Logger logger = LoggerFactory.getLogger(Jtp.class);
+
+	private static final String ANONYMOUS_COOKIE_ID = "anonymousId";
+	private static final String ANONYMOUS_COOKIE_NAME = "anonymousName";
+
+	private Jtp() {}  // never
+
+	public static Person getVisitor(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException
+	{
+		Person visitor = getUser(request,response);
+		if( visitor != null )
+			return visitor;
+		return getOrCreateAnonymous(request,response);
+	}
+
+	private static Person getAnonymous(HttpServletRequest request) {
+		String anonymousId = ServletUtils.getCookieValue(request, ANONYMOUS_COOKIE_ID);
+		if (anonymousId == null)
+			return null;
+		String anonymousName = ServletUtils.getCookieValue(request, ANONYMOUS_COOKIE_NAME);
+		return getSite(request).getAnonymous(anonymousId, anonymousName == null? null : HtmlUtils.urlDecode(anonymousName));
+	}
+
+	private static Person getOrCreateAnonymous(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException
+	{
+		Person anonymous = getAnonymous(request);
+		if (anonymous == null) {
+			Site site = getSiteNotNull(request);
+			String cookie = site.newAnonymousCookie();
+			anonymous = site.getAnonymous(cookie,null);
+			ServletUtils.setCookie(request, response, ANONYMOUS_COOKIE_ID, cookie, true, null);
+		}
+		return anonymous;
+	}
+
+	public static User getUser(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException
+	{
+		return getUser(request);
+	}
+
+	public static User getUser(HttpServletRequest request)
+		throws ServletException
+	{
+		User user = null;
+		String userId = ServletUtils.getCookieValue(request,"userId");
+		if (userId != null && userId.length() > 0) {
+			user = getSiteNotNull(request).getUser(Long.valueOf(userId));
+		}
+		if( user==null )
+			return null;
+		if( ServletUtils.getCookieValue(request,"username")==null)
+			return null;
+		String pwd = ServletUtils.getCookieValue(request,"password");
+		if ( pwd==null )
+			return null;
+		String passcookie = HtmlUtils.urlDecode(pwd);
+		if( ! (user.isRegistered() && user.checkPasscookie(passcookie)) )
+			return null;
+		trackUser(request, user);
+		return user;
+	}
+
+	public static void doLogin(HttpServletRequest request, HttpServletResponse response, User user, boolean save)
+		throws IOException
+	{
+		if( !user.isRegistered() )
+			throw new RuntimeException("user must be registered to login");
+
+		ServletUtils.setCookie(request,response,"userId", String.valueOf(user.getId()), save, null);
+		ServletUtils.setCookie(request,response,"password", HtmlUtils.urlEncode(user.getPasscookie()), save, null);
+		ServletUtils.setCookie(request,response,"username", HtmlUtils.urlEncode(user.getName()), save, null);
+		dontCache(response);
+
+		DailyNumber.logins.inc();
+		trackUser(request, user);
+
+		// fix anonymous nodes from this user
+		String anonymousId = ServletUtils.getCookieValue(request, ANONYMOUS_COOKIE_ID);
+		if (anonymousId != null) {
+			user.moveToRegisteredAccount(anonymousId);
+			ServletUtils.removeCookie(request, response, ANONYMOUS_COOKIE_ID, null);
+			ServletUtils.removeCookie(request, response, ANONYMOUS_COOKIE_NAME, null);
+		}
+	}
+
+	private static Set trackUsers = Init.get("trackUsers", Collections.EMPTY_SET);
+
+	private static void trackUser(HttpServletRequest request, User user) {
+		long siteId = user.getSite().getId();
+		String siteAndUser = siteId + "|" + user.getId();
+		if (trackUsers.contains(siteAndUser))
+			logger.error("Track User [site=" + siteId + " | user = " + user.getId() + "] = " + getClientIpAddr(request));
+	}
+
+	public static String getClientIpAddr(HttpServletRequest request) {
+		return JtpContextServlet.getClientIpAddr(request);
+	}
+
+	public static void logout(HttpServletRequest request,HttpServletResponse response) {
+		request.getSession().removeAttribute("nextUrl");
+		ServletUtils.removeCookie(request,response,"userId", null);
+		ServletUtils.removeCookie(request,response,"password", null);
+		ServletUtils.removeCookie(request,response,"username", null);
+	}
+
+	public static void login(String msg, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		String nextUrl = getCurrentPath(request);
+//if(nextUrl.endsWith("NamlServlet.jtp")) logger.error("nextUrl = "+nextUrl);
+		response.sendRedirect(loginPath(getSiteNotNull(request),msg,nextUrl));
+	}
+
+	public static String getCurrentPath(HttpServletRequest request) {
+		String s = request.getServletPath();
+		String q = request.getQueryString();
+		if( q != null )
+			s += "?" + q;
+		return s;
+	}
+
+	public static String loginPath(Site site,String message,String nextUrl) {
+		Map<String,Object> args = new HashMap<String,Object>();
+		if( message != null )
+			args.put("message",message);
+		if( nextUrl != null )
+			args.put("nextUrl",nextUrl);
+		Template template = site.getTemplate( "login_path",
+			BasicNamespace.class, NabbleNamespace.class
+		);
+		StringWriter sw = new StringWriter();
+		template.run( new TemplatePrintWriter(sw), args,
+			new BasicNamespace(template), new NabbleNamespace(site)
+		);
+		return sw.toString();
+	}
+
+
+	public static String hideNull(Object obj) {
+		return obj==null ? "" : obj.toString();
+	}
+
+	public static String userUrl(Person user) {
+		String base = user.getSite().getBaseUrl();
+		return base + "/template/NamlServlet.jtp?macro=user_nodes&user=" + user.getIdString();
+	}
+
+	public static String userLink(Person user) {
+		return "<a href=\"" + userUrl(user) + "\" rel=\"nofollow\" target=\"_top\">"
+				+user.getNameHtml()+"</a>";
+	}
+
+	public static String userLinkJs(HttpServletRequest request,User user) {
+		StringBuilder buf = new StringBuilder();
+		buf.append("<script>document.write('<a href=\\\"");
+		buf.append(request.getContextPath());
+		buf.append("' +'/user/UserNodes' + '.jtp?'+'user=");
+		buf.append(user.getId());
+		buf.append("\\\" rel=\\\"nofollow\\\" ");
+		buf.append("' + Nabble.embeddedTarget('_top') + '");
+		buf.append(">');</script>");
+		buf.append(user.getNameHtml());
+		buf.append("<script>document.write('</a>');</script>");
+		return buf.toString();
+	}
+
+
+	public static String subjectEncode(String s) {
+		return ViewUtils.subjectEncode(s);
+	}
+
+	public static String link(Node node) {
+		if (node == null)
+			return "";
+		return "<a href=\"" + url(node) + "\">" + node.getSubjectHtml() + "</a>";
+	}
+
+	public static String url(Node node) {
+		return node.getSite().getBaseUrl() + path(node);
+	}
+
+	public static String path(Node node) {
+		switch( node.getKind() ) {
+		case POST:
+			return Permalink.path(null,node);
+		case APP:
+			Site site = node.getSite();
+			Template template = site.getTemplate( "app_path",
+				BasicNamespace.class, NabbleNamespace.class, NodeNamespace.class
+			);
+			StringWriter sw = new StringWriter();
+			template.run( new TemplatePrintWriter(sw), Collections.<String,Object>emptyMap(),
+				new BasicNamespace(template), new NabbleNamespace(site), new NodeNamespace(node)
+			);
+			return sw.toString();
+		}
+		throw new RuntimeException("never");
+	}
+
+	public static String topicViewPath(Node node, long selectedId, TopicView topicView) {
+		Site site = node.getSite();
+		Template template = site.getTemplate( "topic_path",
+			BasicNamespace.class, NabbleNamespace.class, NodeNamespace.class
+		);
+		StringWriter sw = new StringWriter();
+		Map<String,Object> args = new HashMap<String,Object>();
+		args.put( "view", topicView == TopicView.CLASSIC? "classic" : topicView == TopicView.THREADED? "threaded" : "list");
+		args.put( "selected_id", selectedId);
+		template.run( new TemplatePrintWriter(sw), args,
+			new BasicNamespace(template), new NabbleNamespace(site), new NodeNamespace(node)
+		);
+		return sw.toString();
+	}
+
+	public static String link(User user) {
+		return "<a href=\"" + url(user) + "\">" + user.getNameHtml() + "</a>";
+	}
+
+	public static String url(User user) {
+		return user.getSite().getBaseUrl() + path(user);
+	}
+
+	public static String path(User user) {
+		Site site = user.getSite();
+		Template template = site.getTemplate( "path",
+			BasicNamespace.class, NabbleNamespace.class, UserNamespace.class
+		);
+		StringWriter sw = new StringWriter();
+		template.run( new TemplatePrintWriter(sw), Collections.<String,Object>emptyMap(),
+			new BasicNamespace(template), new NabbleNamespace(site), new UserNamespace(user)
+		);
+		return sw.toString();
+	}
+
+	public static void javascriptRedirect(HttpServletRequest request,HttpServletResponse response,Node node)
+		throws IOException, ServletException
+	{
+		Shared.javascriptRedirect(request,response,url(node));
+	}
+
+	public static String getStartFragment(String text,int minSize,int maxSize) {
+		if( text.length() <= maxSize )
+			return text;
+		int i = maxSize;
+		while( !Character.isWhitespace(text.charAt(i)) ) {
+			i--;
+			if( i <= minSize )
+				return text.substring(0,maxSize);
+		}
+		while( Character.isWhitespace(text.charAt(i)) ) {
+			if( i < minSize )
+				return text.substring(0,maxSize);
+			i--;
+		}
+		return text.substring(0,i+1);
+	}
+
+	public static String truncate(String text,int len,String dotdotdot) {
+		return text.length() <= len ? text : text.substring(0,len) + dotdotdot;
+	}
+
+	public static String breakUp(final String text) {
+		return HtmlUtils.breakUp(text,30,true);
+	}
+
+	private static final String PUNCTUATION = "[\\!\"\\&\\'\\(\\)\\*\\+\\,\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\]\\^\\_\\`\\{\\|\\}\\~]";
+
+	private static void metaKeywords(final String text,Set<String> words) {
+		StringTokenizer str = new StringTokenizer(text.toLowerCase().replaceAll(PUNCTUATION," "));
+		while( str.hasMoreTokens() ) {
+			String s = str.nextToken();
+			if( "re".equals(s) )
+				continue;
+			words.add(s);
+		}
+	}
+
+	private static final Set<String> invalidKeywords = new TreeSet<String>();
+	static {
+		String[] words = {
+			"hi", "a", "an", "in", "i", "hello", "please",
+			"to", "on", "by", "t", "m", "but", "the", "of",
+			"it", "so", "at", "am", "dear", "me", "and", "are",
+			"wonder", "from", "be", "been", "is",
+			"was", "if", "this", "that", "there"
+		};
+		for (String word : words) {
+			invalidKeywords.add(word);
+		}
+	}
+
+	private static String metaKeywords(Set<String> words) {
+		StringBuilder buf = new StringBuilder();
+		for( String s : words ) {
+			if (invalidKeywords.contains(s.toLowerCase()))
+				continue;
+			if (buf.length() > 0) buf.append(", ");
+			buf.append(s);
+		}
+		return buf.toString();
+	}
+
+	public static String metaKeywords(Node node) {
+		return metaKeywords(node, true);
+	}
+
+	public static String metaKeywords(Node node, boolean includeMessage) {
+		Set<String> words = new LinkedHashSet<String>();
+		for( Node n = node; n!=null; n = n.getParent() ) {
+			metaKeywords(n.getSubjectHtml(),words);
+			if (includeMessage) {
+				metaKeywords(getFragment(n.getMessage().getText(), 200), words);
+				includeMessage = false;
+			}
+		}
+		if( node.getKind() == Node.Kind.APP ) {
+			String type = node.getType();
+			if (type.equals(Node.Type.GALLERY)) {
+				words.add("photo");
+				words.add("pictures");
+				words.add("gallery");
+				words.add("images");
+			} else if (type.equals(Node.Type.BLOG)) {
+				words.add("blog");
+			} else if (type.equals(Node.Type.NEWS)) {
+				words.add("news");
+				words.add("newspaper");
+				words.add("magazine");
+			} else {
+				words.add("forum");
+				words.add("board");
+				words.add("community");
+			}
+			if (node.getAssociatedMailingList() != null)
+				words.add("mailing list archive");
+		}
+		return metaKeywords(words);
+	}
+
+	public static String metaDescription(Node node) {
+		StringBuilder buf = new StringBuilder();
+		String name = node.getSubjectHtml();
+		buf.append(name);
+		if( node.getKind() == Node.Kind.APP ) {
+			String viewName = viewName(node).toLowerCase();
+			if (! (name.toLowerCase().indexOf(viewName) >= 0)) {
+				buf.append(' ').append(viewName);
+			}
+			if (node.getAssociatedMailingList()!=null)
+				buf.append(" and mailing list archive");
+			buf.append(".");
+		}
+		appendSnippet(node.getMessage().getText(), buf);
+		return buf.toString();
+	}
+
+	private static void appendSnippet(String text, StringBuilder buf) {
+		if (buf.length() >= 200 || text.length() == 0) return;
+		String fragment = getFragment(text, 200 - buf.length());
+		if (buf.charAt(buf.length() - 1) != '.') buf.append('.');
+		buf.append(' ');
+		buf.append(ModelHome.hideAllEmails(HtmlUtils.htmlEncode(fragment).replaceAll("\\s+"," ")));
+		if (fragment.length() < text.length()) buf.append("...");
+	}
+
+	private static String getFragment(String text, int size) {
+		if (text.length() <= size) return text;
+		int end = text.lastIndexOf(' ', size);
+		if (end < 0) end = size;
+		return text.substring(0, end);
+	}
+
+
+	public static String formatDateOnly(String label, Date date) {
+		return "<script>document.write('" + label + "' + Nabble.formatDateOnly(new Date("+date.getTime()+")));</script>";
+	}
+
+	public static String formatDateOnly(Date date) {
+		return "<script>document.write(Nabble.formatDateOnly(new Date("+date.getTime()+")));</script>";
+	}
+
+	public static String formatDateLong(String label, Date date) {
+		return "<script>document.write('" + label + "' + Nabble.formatDateLong(new Date("+date.getTime()+")));</script>";
+	}
+
+	public static String formatDateLong(Date date) {
+		return "<script>document.write(Nabble.formatDateLong(new Date("+date.getTime()+")));</script>";
+	}
+
+	public static String formatDateShort(Date date) {
+		return "<script>document.write(Nabble.formatDateShort(new Date("+date.getTime()+")));</script>";
+	}
+
+
+	private static final String jsWriteStart = "<script>document.write(";
+	private static final String jsWriteEnd = ");</script>";
+
+	public static String javascriptQuote(String s) {
+		StringBuilder buf = new StringBuilder();
+		buf.append( "'" );
+		int i = 0;
+		while(true) {
+			int i2 = s.indexOf(jsWriteStart,i);
+			if( i2 == -1 )
+				break;
+			int i3 = s.indexOf(jsWriteEnd,i2);
+			if( i3 == -1 )
+				throw new RuntimeException();
+			buf.append( HtmlUtils.javascriptStringEncode(s.substring(i,i2)) );
+			buf.append( "'+(" );
+			buf.append( s.substring(i2+jsWriteStart.length(),i3) );
+			buf.append( ")+'" );
+			i = i3 + jsWriteEnd.length();
+		}
+		buf.append( HtmlUtils.javascriptStringEncode(s.substring(i)) );
+		buf.append( "'" );
+		return buf.toString();
+	}
+
+	public static int getYear(Date date) {
+		return date.getYear() + 1900;
+	}
+
+	public static int thisYear() {
+		return getYear(new Date());
+	}
+
+	public static String capitalize(String s) {
+		return s.substring(0,1).toUpperCase() + s.substring(1);
+	}
+
+	public static void dontCache(HttpServletResponse response) {
+		response.setHeader("Cache-Control","no-cache, max-age=0");
+	}
+
+
+	public static Map<String,FileItem> getFileItems(HttpServletRequest request)
+		throws FileUploadException
+	{
+		DiskFileUpload fu = new DiskFileUpload();
+		Map<String,FileItem> map = new HashMap<String,FileItem>();
+		@SuppressWarnings("unchecked")
+		List<FileItem> fileItems = fu.parseRequest(request);
+		for( FileItem fi : fileItems ) {
+			map.put(fi.getFieldName(),fi);
+		}
+		return map;
+	}
+
+	public static String helpIndexUrl(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		StringBuilder url = new StringBuilder();
+		url.append( request.getContextPath() );
+		url.append( "/help/Index.jtp" );
+		return url.toString();
+	}
+
+	public static long getLong(HttpServletRequest request,String param)
+		throws ServletException
+	{
+		return parseLong( request, request.getParameter(param) );
+	}
+
+	public static long parseLong(HttpServletRequest request,String s)
+		throws ServletException
+	{
+		try {
+			return Long.parseLong(s);
+		} catch(NumberFormatException e) {
+			if (invalidReferer(request) || s.contains(" Result: ")) {
+				logger.warn("Bad URL", e);
+				throw new MinorServletException(e);
+			} else
+				throw e;
+		}
+	}
+
+	public static int getInt(HttpServletRequest request,String param)
+		throws ServletException
+	{
+		return parseInt( request, request.getParameter(param) );
+	}
+
+	public static int parseInt(HttpServletRequest request,String s)
+		throws ServletException
+	{
+		try {
+			return Integer.parseInt(s);
+		} catch(NumberFormatException e) {
+			if (invalidReferer(request))
+				throw new ServletException(e);
+			else
+				throw e;
+		}
+	}
+
+	/*
+	The User-Agent should not be considered.  If a browser doesn't send the referer, then we have no way
+	to know if the referer is valid or not.  So we should assume it isn't to avoid having cases
+	where it really isn't valid go into error.log .  If the referer really was valid, then the same problem
+	should show up again in a good browser.  We shouldn't worry so much about taking care of broken browsers.
+	-fschmidt
+	*/
+	public static boolean invalidReferer(HttpServletRequest request) {
+		String referer = request.getHeader("referer");
+		if( referer == null
+			|| !referer.startsWith("http://" + request.getHeader("host"))
+		)
+			return true;
+		if( request.getMethod().equals("GET") ) {
+			StringBuffer url = request.getRequestURL();
+			String query = request.getQueryString();
+			if( query != null )
+				url.append( '?' ).append( query );
+			if( url.toString().equals(referer) )
+				return true;
+		}
+		return false;
+	}
+
+	public static ServletException servletException(HttpServletRequest request,String msg) {
+		if( invalidReferer(request) )
+			return new ServletException(msg);
+		throw new RuntimeException(msg);
+	}
+
+	public static String getString(HttpServletRequest request, String param)
+		throws ServletException
+	{
+		String value = request.getParameter(param);
+		if (value == null && invalidReferer(request)) {
+			throw new ServletException("Parameter is null: " + param + " [referer is null]");
+		}
+		return value;
+	}
+
+	// should be removed
+	public static String getReadAuthorizationKey(Node node) {
+		if( node==null )
+			return null;
+		node = Permissions.getPrivateNode(node);
+		return node!=null ? Long.toString(node.getId()) : null;
+	}
+
+	// should be removed
+	public static boolean authorizeForRead(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		Node node = getSiteNotNull(request).getNode( Long.parseLong(key) );
+		User user = getUser(request,response);
+		if( user==null ) {
+			login("You must login to view " + node.getSubject(), request, response);
+			return false;
+		}
+
+		if( !Jtp.canBeViewedBy(node,user) ) {
+			response.sendRedirect(getUnauthorizedPath(node));
+			return false;
+		}
+		return true;
+	}
+
+	private static String getUnauthorizedPath(Node node) {
+		Template template = node.getSite().getTemplate( "unauthorized_path",
+			BasicNamespace.class, NabbleNamespace.class, NodeNamespace.class
+		);
+		StringWriter sw = new StringWriter();
+		template.run( new TemplatePrintWriter(sw), Collections.<String, Object>emptyMap(),
+			new BasicNamespace(template), new NabbleNamespace(node.getSite()), new NodeNamespace(Permissions.getPrivateNode(node))
+		);
+		return sw.toString();
+	}
+
+	private static final long startTime = System.currentTimeMillis()/1000*1000;
+
+	public static boolean cacheMe(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		if( startTime <= request.getDateHeader("If-Modified-Since") ) {
+			response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
+			return true;
+		}
+		response.setDateHeader("Last-Modified",startTime);
+		return false;
+	}
+
+
+	public static void addBreadCrumbEvents(Collection<String> events,Node node) {
+		if( node==null ) return;
+		for( Node f = node.getApp(); f != null; f = f.getParent() ) {
+			events.add( Cache.nodeChangeEvent(f) );
+		}
+	}
+
+
+	public static List<Node> getPinnedChildren(Node parent) {
+		List<Node> pinned = new ArrayList<Node>();
+		NodeIterator<? extends Node> iter = parent.getChildren();
+		try {
+			while( iter.hasNext() ) {
+				Node node = iter.next();
+				if( !node.isPinned() )
+					break;
+				pinned.add(node);
+			}
+		} finally {
+			iter.close();
+		}
+		return pinned;
+	}
+
+	public static void addPinnedChild(Node parent, Node child) {
+		List<Node> pinned = getPinnedChildren(parent);
+		if (child.getKind() == Node.Kind.APP) {
+			// Insert after the last pinned forum (before pinned threads)
+			boolean added = false;
+			for (int i = 0; i < pinned.size(); i++) {
+				Node node = pinned.get(i);
+				if (node.getKind() == Node.Kind.POST) {
+					pinned.add(i, child);
+					added = true;
+					break;
+				}
+			}
+			if (!added)
+				pinned.add(child);
+		}
+		else {
+			pinned.add(child);
+		}
+		parent.pin(pinned.toArray(new Node[0]));
+	}
+
+	public static void unpinChild(Node parent, Node child) {
+		List<Node> pinned = getPinnedChildren(parent);
+		pinned.remove(child);
+		parent.pin(pinned.toArray(new Node[0]));
+	}
+
+
+	public static String snippet(Node node,int len) {
+		String text = node.getMessage().getTextWithoutQuotes();
+		return HtmlUtils.htmlEncode(ModelHome.hideAllEmails(NodeSearcher.getStartingFragment(text, len, "...")));
+	}
+
+
+	public static String noCid(String url) {
+		return url.replaceAll("(;|&)cid=(\\d|\\-)+", "");
+	}
+
+	// should go away  -fschmidt
+	public static String viewName(Node node) {
+		Node forum = node.getApp();
+		if (forum == null)
+			return "Forum";
+		String type = forum.getType();
+		if (type.equals(Node.Type.GALLERY))
+			return "Gallery";
+		else if (type.equals(Node.Type.NEWS))
+			return "Newspaper";
+		else if (type.equals(Node.Type.BLOG))
+			return "Blog";
+		else
+			return "Forum";
+	}
+
+	// should go away  -fschmidt
+	public static String childName(Node node, boolean plural) {
+		Node forum = node.getApp();
+		if (forum == null)
+			return plural? "Sub-Forums" : "Sub-Forum";
+		String type = forum.getType();
+		if (type.equals(Node.Type.GALLERY) ||
+			type.equals(Node.Type.NEWS) ||
+			type.equals(Node.Type.BLOG) ||
+			type.equals(Node.Type.BOARD))
+			return plural? "Subcategories" : "Subcategory";
+		else
+			return plural? "Sub-Forums" : "Sub-Forum";
+	}
+
+	public static String parentName(Node node) {
+		if (node.getParent() == null)
+			return "Current Parent";
+		return "Parent " + viewName(node.getParent());
+	}
+
+	// what do you plan for this?   -fschmidt
+	public static String getSmallLogo(String type) {
+		if (type.equals(Node.Type.GALLERY))
+			return "<img src=\"/images/homepage/gallery_sm.png\" width=20 height=15 alt=\"Free photo gallery\">";
+		else if (type.equals(Node.Type.BLOG))
+			return "<img src=\"/images/homepage/blog_sm.png\" width=20 height=17 alt=\"Free blog\">";
+		else if (type.equals(Node.Type.NEWS))
+			return "<img src=\"/images/homepage/news_sm.png\" width=20 height=15 alt=\"Free newspaper\">";
+		else
+			return "";
+	}
+
+	static String getCanonicalUrl(HttpServletRequest request) {
+		String current = ServletUtils.getCurrentURL(request);
+		if (current.startsWith(Lazy.homeContextUrl))
+			return null;
+		String host = request.getHeader("host");
+		return current.replace("http://"+host, Lazy.homeContextUrl);
+	}
+
+	private static final Pattern NUMBER_PATTERN = Pattern.compile("^[0-9]+$");
+	public static boolean isInteger(String s) {
+		return s != null && NUMBER_PATTERN.matcher(s).find();
+	}
+
+	/**
+	 * Maximum number of rows an app page can have.
+	 * This limit must exist because there are physical restrictions:
+	 *  - the $Js URL can't support too many nodes at the same time.
+	 *  - div tags have a limit of 32,768 pixels of height.
+	 */
+	public static int getMaxRowsPerPage(String type) {
+		int n = 100;
+		if (type.equals(Node.Type.BLOG))
+			n = 20;
+		return n;
+	}
+
+	/** Default number of rows an app page has. */
+	public static int getDefaultRowsPerPage(String type) {
+		int n = 35;
+		if (type.equals(Node.Type.BLOG))
+			n = 10;
+		else if (type.equals(Node.Type.GALLERY))
+			n = 12;
+		else if (type.equals(Node.Type.GALLERY))
+			n = 25;
+		return n;
+	}
+
+	public static int getDefaultMixedLength() { return 6;}
+	public static int getMaxMixedLength() { return 20;}
+
+	private static final String defaultHost = (String)Init.get("defaultHost");
+	static {
+		if( defaultHost==null ) {
+			logger.error("no defaultHost");
+			System.exit(-1);
+		}
+	}
+
+	public static String getDefaultHost() {
+		return defaultHost;
+	}
+
+	public static String getBaseUrl(HttpServletRequest request) {
+		String host = request.getHeader("host");
+		String scheme = request.getHeader("X-Forwarded-Proto");
+		if( scheme == null )
+			scheme = request.getScheme();
+		return scheme + "://" + host;
+	}
+
+	// fix jetty bug
+	public static void sendRedirect(HttpServletRequest request,HttpServletResponse response,String url)
+		throws IOException
+	{
+		if( url.startsWith("/") )
+			url = Jtp.getBaseUrl(request) + url;
+		response.sendRedirect(url);
+	}
+
+	private static class Lazy {
+		static final Pattern ROOT_URL_PATTERN;
+		static final String defaultContextUrl;
+		static final String homeContextUrl;
+		static {
+			defaultContextUrl = "http://" + defaultHost;
+			homeContextUrl = Init.get("homeContextUrl",defaultContextUrl);
+			ROOT_URL_PATTERN = Pattern.compile( ".*\\.(\\d+)\\."+Pattern.quote(defaultHost) );
+		}
+	}
+
+	public static String homePage() {
+		return nabble.view.web.Index.url();
+	}
+
+	public static String supportUrl() {
+		return "http://support.nabble.com/";
+	}
+
+	public static String supportLink() {
+		String supportUrl = supportUrl();
+		return supportUrl==null ? null : "<a href='"+supportUrl+"' target='_top'>Nabble Support</a>";
+	}
+
+	public static String defaultContextUrl() {
+		return Lazy.defaultContextUrl;
+	}
+
+	public static String homeContextUrl() {
+		return Lazy.homeContextUrl;
+	}
+
+	// doesn't return null
+	public static Site getSiteNotNull(HttpServletRequest request)
+		throws ServletException
+	{
+		Site site = getSite(request);
+		if( site == null )
+			throw servletException(request,"site not found");
+		return site;
+	}
+
+	private static final Object noSite = new Object();
+
+	public static Site getSite(HttpServletRequest request) {
+		Object obj = request.getAttribute("site");
+		if( obj == null ) {
+			Long siteId = getSiteIdFromDomain( ServletUtils.getHost(request) );
+			if( siteId != null )
+				obj = ModelHome.getSite(siteId);
+			if( obj == null )
+				obj = noSite;
+			request.setAttribute("root",obj);
+		}
+		return obj==noSite ? null : (Site)obj;
+	}
+
+	public static Site getSiteFromUrl(String url) {
+		String domain = extractDomain(url);
+		if( domain == null )
+			return null;
+		Long siteId = getSiteIdFromDomain(domain);
+		if( siteId == null )
+			return null;
+		return ModelHome.getSite(siteId);
+	}
+
+	public static Long getSiteIdFromDomain(String domain) {
+		if( domain.equals(defaultHost) )
+			return null;
+		Matcher m = Lazy.ROOT_URL_PATTERN.matcher(domain);
+		if( m.matches() ) {
+			return Long.parseLong(m.group(1));
+		} else {
+			return ModelHome.getSiteIdFromDomain(domain);
+		}
+	}
+
+	public static String getDefaultBaseUrl(Site site) {
+		return ViewUtils.getDefaultBaseUrl( site.getId(), site.getRootNode().getSubject(), defaultHost );
+	}
+
+	public static String extractDomain(String url) {
+		int posDoubleSlash = url.indexOf("//");
+		int posDomainEnd = url.indexOf('/', posDoubleSlash+2);
+		// if the last slash was not found, we can assume the domain ends at the end of the string.
+		posDomainEnd = posDomainEnd == -1? url.length() : posDomainEnd;
+		return posDoubleSlash > 0 && posDoubleSlash < 8 && posDomainEnd > posDoubleSlash? url.substring(posDoubleSlash+2, posDomainEnd) : null;
+	}
+
+	// permissions hacks
+
+	private static boolean can(Person person,String macro,Node node) {
+		Template template = node.getSite().getTemplate( macro,
+			BasicNamespace.class, NabbleNamespace.class, UserNamespace.class
+		);
+		StringWriter sw = new StringWriter();
+		Map<String,Object> args = new HashMap<String,Object>();
+		args.put( "node_attr", new NodeNamespace(node) );
+		template.run( new TemplatePrintWriter(sw), args,
+			new BasicNamespace(template), new NabbleNamespace(node.getSite()), new UserNamespace(person)
+		);
+		return sw.toString().equals("true");
+	}
+
+	public static boolean canBeEditedBy(Node node,Person person) {
+		return can(person,"can_edit",node);
+	}
+
+	public static boolean canBeRemovedBy(Node node,Person person) {
+		return can(person,"can_move",node);
+	}
+
+	public static boolean canBeDeletedBy(Node node,Person person) {
+		return can(person,"can_delete",node);
+	}
+
+	public static boolean canBeViewedBy(Node node,Person person) {
+		return can(person,"can_view",node);
+	}
+
+	public static boolean canMove(Node node,Person person) {
+		return can(person,"can_move",node);
+	}
+
+	public static boolean canAssign(Node node,Person person) {
+		return can(person,"can_be_assigned_to",node);
+	}
+
+	public static boolean canChangePostDateOf(Node node,Person person) {
+		return can(person,"can_change_post_date_of",node);
+	}
+
+	public static boolean isSiteAdmin(Site site,Person person) {
+		return person instanceof User && site.getRootNode().getOwner().equals(person);  // for now
+	}
+
+	public static final String CACHED = "cached";
+
+	public static boolean isCached(HttpServletRequest request,HttpServletResponse response) {
+		return response.containsHeader("Etag") || request.getAttribute(CACHED) != null;
+	}
+
+	public static String termsUrl(boolean back) {
+		return homeContextUrl() + "/Terms.jtp";
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/JtpContextServlet.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,796 @@
+package nabble.view.lib;
+
+import fschmidt.util.servlet.*;
+import fschmidt.util.java.ProcUtils;
+import fschmidt.util.java.SimpleClassLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+
+public final class JtpContextServlet extends HttpServlet implements JtpContext {
+	private static final Logger logger = LoggerFactory.getLogger(JtpContextServlet.class);
+
+	private static final Set<String> allowedMethods = new HashSet<String>(Arrays.asList(
+		"GET", "POST", "HEAD"
+	));
+	private String base;
+	private boolean reload = false;
+	private boolean recompile = false;
+	private SimpleClassLoader.Filter filter = null;
+	private ClassLoader cl = null;
+	private Map<String,HttpServlet> map = new HashMap<String,HttpServlet>();
+	private long clTime;
+	private Object lock = new Object();
+	private HttpCache httpCache;
+	private boolean isCaching;
+	private String characterEncoding;
+	private Map<String, String> customHeaders = new HashMap<String, String>();
+	private UrlMapper urlMapper = new UrlMapper() {
+		public UrlMapping getUrlMapping(HttpServletRequest request) {
+			return null;
+		}
+	};
+	private Set<String> errorCache = null;
+	private Collection<String> ipList = null;
+	private static final String authKeyAttr = "authKey";
+	private static final String[] noModifyingEvents = new String[]{"_"};
+
+	public void setUrlMapper(UrlMapper urlMapper) {
+		this.urlMapper = urlMapper;
+	}
+
+	public HttpCache getHttpCache() {
+		return httpCache;
+	}
+
+	public void setHttpCache(HttpCache httpCache) {
+		this.httpCache = httpCache;
+	}
+
+	public void addCustomHeader(String key, String value) {
+		this.customHeaders.put(key, value);
+	}
+
+	public void unloadServlets() {
+		if( !reload )
+			throw new UnsupportedOperationException("'reload' must be set");
+		synchronized(lock) {
+			cl = new SimpleClassLoader(filter);
+			map = new HashMap<String,HttpServlet>();
+			clTime = System.currentTimeMillis();
+		}
+	}
+
+	public void setBase(String base) {
+		if( base==null )
+			throw new NullPointerException();
+		this.base = base;
+	}
+
+	public void init()
+		throws ServletException
+	{
+		ServletContext context = getServletContext();
+		String newBase = getInitParameter("base");
+		if( newBase != null )
+			setBase(newBase);
+		recompile = Boolean.valueOf(getInitParameter("recompile"));
+		reload = recompile || Boolean.valueOf(getInitParameter("reload"));
+		if( reload ) {
+			filter = new SimpleClassLoader.Filter(){
+				final String s = base + ".";
+				public boolean load(String className)  {
+					return className.startsWith(s);
+				}
+			};
+			unloadServlets();
+		}
+		context.setAttribute(JtpContext.attrName,this);
+		String servletS = getInitParameter("servlet");
+		if( servletS != null ) {
+			throw new RuntimeException("the 'servlet' init parameter is no longer supported");
+		}
+		isCaching = "true".equalsIgnoreCase(getInitParameter("cache"));
+		if( isCaching ) {
+			if( httpCache==null ) {
+				logger.error("can't set init parameter 'cache' to true without httpCache");
+				System.exit(-1);
+			}
+			logger.info("cache");
+		}
+		characterEncoding = getInitParameter("characterEncoding");
+		{
+			String s = getInitParameter("timeLimit");
+			if( s != null )
+				timeLimit = Long.parseLong(s);
+		}
+		{
+			String s = getInitParameter("errorCacheSize");
+			if( s != null ) {
+				final int errorCacheSize = Integer.parseInt(s);
+				errorCache = Collections.synchronizedSet(Collections.newSetFromMap(new LinkedHashMap<String,Boolean>(){
+					protected boolean removeEldestEntry(Map.Entry eldest) {
+						return size() > errorCacheSize;
+					}
+				}));
+			}
+		}
+		{
+			String s = getInitParameter("ipListSize");
+			if( s != null ) {
+				final int ipListSize = Integer.parseInt(s);
+				ipList = Collections.synchronizedList(new LinkedList<String>() {
+					public boolean add(String s) {
+						if( contains(s) )
+							return false;
+						super.add(s);
+						if( size() > ipListSize )
+							removeFirst();
+						return true;
+					}
+				});
+			}
+		}
+	}
+
+	private boolean isInErrorCache(String s) {
+		return errorCache==null || !errorCache.add(s);
+	}
+
+	private boolean isInIpList(String ip) {
+		return ipList!=null && !ipList.add(ip);
+	}
+
+	public static interface DestroyListener {
+		public void destroyed();
+	}
+
+	private DestroyListener destroyListener = null;
+
+	public void addDestroyListener(DestroyListener dl) {
+		synchronized(lock) {
+			if( destroyListener!=null )
+				throw new RuntimeException("only one DestroyListener allowed");
+			destroyListener = dl;
+		}
+	}
+
+	public void destroy() {
+		synchronized(lock) {
+			if( destroyListener != null )
+				destroyListener.destroyed();
+		}
+	}
+
+	public static final class RequestAndResponse {
+		public final HttpServletRequest request;
+		public final HttpServletResponse response;
+
+		public RequestAndResponse(HttpServletRequest request,HttpServletResponse response) {
+			this.request = request;
+			this.response = response;
+		}
+	}
+
+	public static interface CustomWrappers {
+		public RequestAndResponse wrap(HttpServletRequest request, HttpServletResponse response);
+	}
+
+	private CustomWrappers customWrappers;
+
+	public void setCustomWrappers(CustomWrappers customWrappers) {
+		this.customWrappers = customWrappers;
+	}
+
+	private static String hideNull(String s) {
+		return s==null ? "" : s;
+	}
+
+	private String getServletPath(HttpServletRequest request) {
+		return request.getServletPath() + hideNull(request.getPathInfo());
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		final TimeLimit tl = startTimeLimit(request);
+		response = new HttpServletResponseWrapper(response) {
+			PrintWriter writer = null;
+			ServletOutputStream out = null;
+
+			public PrintWriter getWriter()
+				throws java.io.IOException
+			{
+				if( writer==null ) {
+					writer = new PrintWriter(super.getWriter()) {
+						public void write(String s,int off,int len) {
+							long t = System.currentTimeMillis();
+							super.write(s,off,len);
+							tl.ioTime += System.currentTimeMillis() - t;
+						}
+						public void write(char[] buf,int off,int len) {
+							long t = System.currentTimeMillis();
+							super.write(buf,off,len);
+							tl.ioTime += System.currentTimeMillis() - t;
+						}
+						public void write(int c) {
+							long t = System.currentTimeMillis();
+							super.write(c);
+							tl.ioTime += System.currentTimeMillis() - t;
+						}
+						public void flush() {
+							long t = System.currentTimeMillis();
+							super.flush();
+							tl.ioTime += System.currentTimeMillis() - t;
+						}
+						public void println() {
+							long t = System.currentTimeMillis();
+							super.println();
+							tl.ioTime += System.currentTimeMillis() - t;
+						}
+					};
+				}
+				return writer;
+			}
+
+			public ServletOutputStream getOutputStream()
+				throws java.io.IOException
+			{
+				if( out==null ) {
+					final ServletOutputStream sos = super.getOutputStream();
+					out = new ServletOutputStream() {
+						public void write(byte[] b,int off,int len) throws IOException {
+							long t = System.currentTimeMillis();
+							sos.write(b,off,len);
+							tl.ioTime += System.currentTimeMillis() - t;
+						}
+						public void write(byte[] b) throws IOException {
+							long t = System.currentTimeMillis();
+							sos.write(b);
+							tl.ioTime += System.currentTimeMillis() - t;
+						}
+						public void write(int c) throws IOException {
+							long t = System.currentTimeMillis();
+							sos.write(c);
+							tl.ioTime += System.currentTimeMillis() - t;
+						}
+						public void flush() throws IOException {
+							long t = System.currentTimeMillis();
+							sos.flush();
+							tl.ioTime += System.currentTimeMillis() - t;
+						}
+					};
+				}
+				return out;
+			}
+
+			public void sendError(int sc) throws IOException {
+				long t = System.currentTimeMillis();
+				super.sendError(sc);
+				tl.ioTime += System.currentTimeMillis() - t;
+			}
+
+			public void sendRedirect(String location) throws IOException {
+				if( containsHeader("Expires") )
+					setHeader("Expires",null);
+				if( containsHeader("Last-Modified") )
+					setHeader("Last-Modified",null);
+				if( containsHeader("Etag") )
+					setHeader("Etag",null);
+				if( containsHeader("Cache-Control") )
+					setHeader("Cache-Control",null);
+				if( containsHeader("Content-Type") )
+					setHeader("Content-Type",null);
+				if( containsHeader("Content-Length") )
+//					setHeader("Content-Length",null);
+					setContentLength(0);
+				super.sendRedirect(location);
+			}
+		};
+		service2(request,response);
+		checkTimeLimit(request);
+	}
+
+	private void service2(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		if( !allowedMethods.contains(request.getMethod()) ) {
+			response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+			return;
+		}
+//		String contextPath = request.getContextPath();
+//		String contextUrl = ServletUtils.getContextURL(request);
+
+		// First we set the character encoding because any manipulation
+		// of request parameters will break without this.
+		response.setHeader("Content-Type","text/html; charset=utf-8");  // default, servlet can override
+		if( characterEncoding != null ) {
+			response.setCharacterEncoding(characterEncoding);
+			request.setCharacterEncoding(characterEncoding);
+		}
+
+		HttpServlet servlet;
+		String path = getServletPath(request);
+
+		UrlMapping urlMapping = urlMapper.getUrlMapping(request);
+		if( urlMapping != null ) {
+			try {
+				servlet = getServletFromClass(urlMapping.servletClass.getName());
+			} catch(ClassNotFoundException e) {
+				throw new RuntimeException(e);
+			}
+			final Map params = urlMapping.parameterMap;
+			request = new BetterRequestWrapper(request) {
+				public Map getParameterMap() {
+					return params;
+				}
+			};
+		} else {
+			try {
+				servlet = getServlet(path);
+			} catch(ClassNotFoundException e) {
+				response.sendError(HttpServletResponse.SC_NOT_FOUND);
+				String agent = request.getHeader("user-agent");
+				String referer = request.getHeader("referer");
+				String remote = getClientIpAddr(request);
+				String msg = request.getRequestURL()+" referer="+referer+" user-agent="+agent+" remote="+remote;
+				if( referer==null ) {
+					logger.info(msg,e);
+				} else {
+					logger.warn(msg,e);
+				}
+				return;
+			}
+		}
+
+		// Custom headers
+		addCustomHeaders(response);
+
+		AuthorizingServlet auth = servlet instanceof AuthorizingServlet ? (AuthorizingServlet)servlet : null;
+		if( isCaching ) {
+			String etagS = request.getHeader("If-None-Match");
+			if( etagS != null ) {
+				String prevEtag = null;
+				for( String etag : etagS.split("\\s*,\\s*") ) {
+					if( etag.equals(prevEtag) )
+						continue;
+					prevEtag = etag;
+					if( etag.length()>=2 && etag.charAt(0)=='"' && etag.charAt(etag.length()-1)=='"' )
+						etag = etag.substring(1,etag.length()-1);
+					String authKey = null;
+					if( etag.length()>=2 && etag.charAt(0)=='[' ) {
+						int i = etag.indexOf(']');
+						if( i > 0 ) {
+							if( auth != null )
+								authKey = etag.substring(1,i);
+							etag = etag.substring(i+1);
+						}
+					}
+					String[] events = etag.split("~");
+					long lastModified = getLastModified(events);
+					try {
+						if( lastModified <= request.getDateHeader("If-Modified-Since") ) {
+							if( authKey==null || authorize(auth,authKey,request,response) )
+								response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
+							return;
+						}
+					} catch(RuntimeException e) {
+						handleException(request,e);
+					}
+				}
+			}
+		}
+		String authKey = auth==null ? null : getAuthorizationKey(auth,request);
+		if( authKey != null ) {
+			if( !authorize(auth,authKey,request,response) )
+				return;
+			request.setAttribute(authKeyAttr,authKey);
+		}
+
+		if( servlet instanceof CanonicalUrl ) {
+			CanonicalUrl srv = (CanonicalUrl)servlet;
+			StringBuffer currentUrl = request.getRequestURL();
+			int i = currentUrl.indexOf(";");
+			if( i != -1 )
+				currentUrl.setLength(i);
+			String query = request.getQueryString();
+			if( query != null )
+				currentUrl.append( '?' ).append( query );
+			try {
+				String canonicalUrl = srv.getCanonicalUrl(request);
+				if( canonicalUrl != null && !stripScheme(currentUrl.toString()).equals(stripScheme(canonicalUrl)) ) {
+					response.setHeader("Location",canonicalUrl);
+					response.sendError( HttpServletResponse.SC_MOVED_PERMANENTLY );
+					return;
+				}
+			} catch(RuntimeException e) {
+				logger.warn("couldn't get canonical url",e);
+			}
+		}
+
+		request.setAttribute("servlet",servlet);
+
+		try {
+			if (customWrappers != null) {
+				RequestAndResponse rr = customWrappers.wrap(request, response);
+				request = rr.request;
+				response = rr.response;
+			}
+			servlet.service(request,response);
+		} catch(RuntimeException e) {
+			handleException(request,e);
+		} catch(ServletException e) {
+			handleException(request,e);
+		}
+	}
+
+	private static String stripScheme(String url) {
+		return url.substring(url.indexOf(':'));
+	}
+
+	public void setEtag( HttpServletRequest request, HttpServletResponse response, String... modifyingEvents ) {
+		if( modifyingEvents.length == 0 )
+			modifyingEvents = noModifyingEvents;
+		StringBuilder buf = new StringBuilder();
+		String authKey = (String)request.getAttribute(authKeyAttr);
+		if( authKey != null )
+			buf.append( '[' ).append( authKey).append( ']' );
+		buf.append( modifyingEvents[0] );
+		for( int i=1; i<modifyingEvents.length; i++ ) {
+			buf.append( '~' ).append( modifyingEvents[i] );
+		}
+		response.setHeader("Etag",buf.toString());
+		long lastModified = getLastModified(modifyingEvents);
+		response.setDateHeader("Last-Modified",lastModified);
+		response.setHeader("Cache-Control","max-age=0");
+	}
+
+	private boolean authorize(AuthorizingServlet auth,String authKey,HttpServletRequest request,HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		try {
+			if (customWrappers != null) {
+				RequestAndResponse rr = customWrappers.wrap(request, response);
+				request = rr.request;
+				response = rr.response;
+			}
+			return auth.authorize(authKey,request,response);
+		} catch(RuntimeException e) {
+			handleException(request,e);
+		} catch(ServletException e) {
+			handleException(request,e);
+		}
+		throw new RuntimeException("never");
+	}
+
+	private String getAuthorizationKey(AuthorizingServlet auth,HttpServletRequest request)
+		throws ServletException
+	{
+		try {
+			return auth.getAuthorizationKey(request);
+		} catch(RuntimeException e) {
+			handleException(request,e);
+		} catch(ServletException e) {
+			handleException(request,e);
+		}
+		return null;  // never gets here
+	}
+
+	private long getLastModified(String[] modifyingEvents) {
+		long[] lastModifieds = httpCache.lastModifieds(modifyingEvents);
+		long lastModified = lastModifieds[0];
+		for( int i=1; i<lastModifieds.length; i++ ) {
+			long lm = lastModifieds[i];
+			if( lastModified < lm )
+				lastModified = lm;
+		}
+		return lastModified;
+	}
+
+	/** Adds all custom headers to the response object. */
+	private void addCustomHeaders(HttpServletResponse response) {
+		Set<Map.Entry<String, String>> entries = this.customHeaders.entrySet();
+		for (Map.Entry<String, String> entry : entries) {
+			response.setHeader(entry.getKey(), entry.getValue());
+		}
+	}
+
+	private void handleException(HttpServletRequest request,RuntimeException e)
+		throws ServletException
+	{
+		JtpRuntimeException rte;
+		try {
+			String agent = request.getHeader("user-agent");
+			if( agent == null )
+				throw new JtpServletException(request,"null agent",e);
+			if (!isValidAgent(agent))
+				throw new JtpServletException(request, "bad agent " + agent, e);
+			String remote = getClientIpAddr(request);
+			String referer = request.getHeader("referer");
+			StringBuilder buf = new StringBuilder()
+				.append( "method=" ).append( request.getMethod() )
+				.append( " user-agent=" ).append( agent )
+				.append( " referer=" ).append( referer )
+				.append( " remote=" ).append( remote )
+			;
+			String etag = request.getHeader("If-None-Match");
+			if( etag != null )
+				buf.append( " etag=[" ).append( etag ).append( "]" );
+			if( referer==null || isInIpList(remote) )
+				throw new JtpServletException(request,buf.toString(),e);
+			rte = new JtpRuntimeException(request,buf.toString(),e);
+		} catch(RuntimeException e2) {
+			logger.error("failed to handle",e);
+			throw e2;
+		}
+		throw rte;
+	}
+
+	private static void handleException(HttpServletRequest request,ServletException e)
+		throws ServletException
+	{
+		String agent = request.getHeader("user-agent");
+		throw new JtpServletException(request,"user-agent="+agent+" method="+request.getMethod()+" referer="+request.getHeader("referer"),e);
+	}
+
+	private static class JtpRuntimeException extends RuntimeException {
+		private JtpRuntimeException(HttpServletRequest request,String msg,RuntimeException e) {
+			super("url="+getCurrentURL(request)+"  "+msg,e);
+		}
+	}
+
+	private static class JtpServletException extends ServletException {
+		private JtpServletException(HttpServletRequest request,String msg,Exception e) {
+			super("url="+getCurrentURL(request)+"  "+msg,e);
+		}
+	}
+
+	// work-around jetty bug
+	private static String getCurrentURL(HttpServletRequest request) {
+		try {
+			return ServletUtils.getCurrentURL(request);
+		} catch(RuntimeException e) {
+			logger.warn("jetty screwed up",e);
+			return "[failed]";
+		}
+	}
+
+	private static boolean isValidAgent(String agent) {
+		if (agent == null)
+			return false;
+		for (String badAgent : badAgents) {
+			if (agent.indexOf(badAgent) >= 0)
+				return false;
+		}
+		return true;
+	}
+
+	private static final String[] badAgents = new String[]{
+		"MJ12bot",
+		"WISEnutbot",
+		"Win98",  // not worth handling these
+		"Windows 98",
+		"Windows 95",
+		"RixBot",
+		"User-Agent",  // from corrupt header
+		"Firefox/0",  // ancient version of Firefox
+		"Firefox/2.",  // ancient version of Firefox
+		"Firefox/3.",  // ancient version of Firefox
+		"Opera 7.",  // ancient version of Opera
+		"Opera/7.",
+		"Opera 8.",
+		"Opera/8.",
+		"Opera/9.",
+		"TwitterFeed 3",
+		"NAVER Blog Rssbot",
+		"AOL 9.0",
+		"rssreader@newstin.com",
+		"PHPCrawl",
+		"MSIE 2.",
+		"MSIE 4.",
+		"MSIE 5.",
+		"MSIE 6.",
+		"MSIE 7.0",
+		"Mozilla/0.",
+		"Mozilla/2.0",
+		"Mozilla/3.0",
+		"Mozilla/4.6",
+		"Mozilla/4.7",
+		"RSSIncludeBot/1.0", // cause exceptions in xml feeds
+		"Powermarks",
+		"GenwiFeeder",
+		"Akregator",
+		"ia_archiver",
+		"Atomic_Email_Hunter",
+		"Yahoo! Slurp",
+		"Python-urllib",
+		"BlackBerry",
+		"SimplePie", // Feeds parser
+		"www.webintegration.at", // crazy bot
+		"www.run4dom.com", // crazy bot
+		"zia-httpmirror",
+		"POE-Component-Client-HTTP",
+		"anonymous",
+		"Sosospider",
+		"Java/1.6",
+		"Shareaza",
+		"Jakarta Commons-HttpClient",
+		"Apache-HttpClient",
+		"Baiduspider",
+		"bingbot",
+		"MLBot", // www.metadatalabs.com/mlbot
+		"www.vbseo.com",
+		"yacybot", // yacy.net/bot.html
+		"SearchBot"
+	};
+
+	private static boolean isBot(String agent) {
+		if (agent == null)
+			return false;
+		for (String bot : bots) {
+			if (agent.indexOf(bot) >= 0)
+				return true;
+		}
+		return false;
+	}
+
+	private static final String[] bots = new String[]{
+		"Googlebot"
+	};
+
+	private HttpServlet getServlet(String path)
+		throws ServletException, ClassNotFoundException, IOException
+	{
+		int i = path.lastIndexOf('.');
+		if( i == -1 )
+			throw new ClassNotFoundException(path);
+		return getServletFromClass(
+			base + path.substring(0,i).replace('/','.')
+		);
+	}
+
+	private HttpServlet getServletFromClass(String cls)
+		throws ClassNotFoundException
+	{
+		synchronized(lock) {
+			if( reload && hasChanged(cls) ) {
+				unloadServlets();
+			}
+			HttpServlet srv = map.get(cls);
+			if( srv==null ) {
+				try {
+					Class clas = reload ? cl.loadClass(cls) : Class.forName(cls);
+					srv = (HttpServlet)clas.newInstance();
+				} catch(IllegalAccessException e) {
+					throw new RuntimeException(e);
+				} catch(InstantiationException e) {
+					throw new RuntimeException(e);
+				}
+				try {
+					srv.init(this);
+				} catch(ServletException e) {
+					throw new RuntimeException(e);
+				}
+				map.put(cls,srv);
+			}
+			return srv;
+		}
+	}
+
+	private boolean hasChanged(String cls) {
+		try {
+			URL url = cl.getResource( SimpleClassLoader.classToResource(cls) );
+			if( url==null )
+				return true;
+			File file = new File(url.getPath());
+			if( recompile ) {
+				String path = file.toString();
+				if( !path.endsWith(".class") )
+					throw new RuntimeException();
+				File dir = file.getParentFile();
+				String base = path.substring(0,path.length()-6);
+				File source = new File( base + ".jtp" );
+				if( source.lastModified() > clTime ) {
+					Process proc = Runtime.getRuntime().exec(new String[]{
+						"java", "fschmidt.tools.Jtp", source.getName()
+					},null,dir);
+					ProcUtils.checkProc(proc);
+				}
+				source = new File( base + ".java" );
+				if( source.lastModified() > clTime ) {
+					Process proc = Runtime.getRuntime().exec(new String[]{
+						"javac", "-g", source.getName()
+					},null,dir);
+					ProcUtils.checkProc(proc);
+				}
+			}
+			return file.lastModified() > clTime;
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+
+	private long timeLimit = 0;
+	private static final String timeLimitAttr = "time-limit";
+
+	private static class TimeLimit {
+		long timeLimit;
+		final long startTime = System.currentTimeMillis();
+		long ioTime = 0L;
+
+		TimeLimit(long timeLimit) {
+			this.timeLimit = timeLimit;
+		}
+	}
+
+	public long getTimeLimit() {
+		return timeLimit;
+	}
+
+	public void setTimeLimit(long timeLimit) {
+		this.timeLimit = timeLimit;
+	}
+
+	private TimeLimit startTimeLimit(HttpServletRequest request) {
+		TimeLimit tl = new TimeLimit(timeLimit);
+		request.setAttribute( timeLimitAttr, tl );
+		return tl;
+	}
+
+	public void setTimeLimit(HttpServletRequest request,long timeLimit) {
+		TimeLimit tl = (TimeLimit)request.getAttribute(timeLimitAttr);
+		tl.timeLimit = timeLimit;
+	}
+
+	private void checkTimeLimit(HttpServletRequest request) {
+		TimeLimit tl = (TimeLimit)request.getAttribute(timeLimitAttr);
+		if( tl.timeLimit == 0L )
+			return;
+        long time = System.currentTimeMillis() - tl.startTime - tl.ioTime;
+        if( time > tl.timeLimit ) {
+			float free = Runtime.getRuntime().freeMemory();
+			float total = Runtime.getRuntime().totalMemory();
+			float used = (total - free) * 100f;
+			logger.error(ServletUtils.getCurrentURL(request,100) + " took " + time + " ms | " + String.format("%.1f",used/total) + '%');
+/*
+			Scheduler scheduler = TheScheduler.get();
+			if( scheduler instanceof ProfilingScheduler ) {
+				ProfilingScheduler profilingScheduler = (ProfilingScheduler)scheduler;
+				if( profilingScheduler.getMode()==ProfilingScheduler.Mode.FOREGROUND ) {
+					profilingScheduler.captureCPUSnapshot();
+				}
+			}
+*/
+		}
+	}
+
+	public static String getClientIpAddr(HttpServletRequest request) {
+		String ip = request.getHeader("X-Real-IP");
+		if( ip == null )
+			ip = request.getRemoteAddr();
+		return ip;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/MinorServletException.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,21 @@
+package nabble.view.lib;
+
+import javax.servlet.ServletException;
+
+
+public final class MinorServletException extends ServletException {
+
+	MinorServletException(Throwable cause) {
+		super(cause);
+	}
+
+	public static boolean isIn(Throwable th) {
+		while( th != null ) {
+			if( th instanceof MinorServletException )
+				return true;
+			th = th.getCause();
+		}
+		return false;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/MyJtpServlet.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,94 @@
+package nabble.view.lib;
+
+import fschmidt.util.servlet.DbCache;
+import fschmidt.util.servlet.HttpCache;
+import fschmidt.util.servlet.JtpContext;
+import fschmidt.util.servlet.MemCache;
+import nabble.model.Db;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+// misnamed for historical reasons
+public final class MyJtpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(MyJtpServlet.class);
+
+	public static final HttpCache cache;
+	private static volatile JtpContextServlet jtpContext = null;
+	private static final Object jtpContextLock = new Object();
+
+	static {
+//	    SitemapGen.runSitemap(new SitemapTemplate());
+		cache =
+			new MemCache(
+				new DbCache(Db.dbGlobal())
+			)
+		;
+	    Cache.init();
+
+		synchronized(jtpContextLock) {
+			jtpContext = new JtpContextServlet();
+			jtpContext.addDestroyListener( new JtpContextServlet.DestroyListener() {
+				public void destroyed() {
+					jtpContext = null;
+				}
+			} );
+			jtpContext.setBase("nabble.view.web");
+			jtpContextLock.notifyAll();
+		}
+		jtpContext.setUrlMapper(new UrlMapperImpl(new Class[]{
+			nabble.view.web.Index.class,
+			nabble.view.web.embed.NabbleEmbed.class,
+			nabble.view.web.forum.Permalink.class,
+			nabble.view.web.forum.FileDownload.class,
+		    nabble.view.web.forum.Thumbnail.class,
+			nabble.view.web.forum.AttachmentDownload.class,
+			nabble.view.web.template.NamlDownload.class,
+			nabble.view.web.more.Forum.class,
+			nabble.view.web.more.MailingListRequest.class,
+			nabble.view.web.more.ForumStart.class,
+			nabble.view.web.seo.WidgetRedir.class,
+			nabble.view.web.util.GradientImage.class,
+			nabble.view.web.util.RoundedCorner.class,
+			nabble.view.web.w3c.PolicyXML.class,
+			nabble.view.web.w3c.PolicyHTML.class,
+			nabble.view.web.w3c.P3PXML.class,
+			nabble.view.web.tools.Index.class,
+		}));
+		if( cache != null ) {
+			jtpContext.setHttpCache(cache);
+		}
+
+		/**
+		 * Adds a P3P compact header information to a HttpServletResponse object.
+		 * This is required for the embedding project because Internet Explorer (IE) blocks
+		 * cookies from a third-party iframe (different domain). This piece of code
+		 * solves the problem and allows IE to save the cookies without problems.
+		 */
+		jtpContext.addCustomHeader("p3p", "CP=\"IDC DSP TAIi PSAi PSDi OTPi OUR IND PHY ONL UNI NAV DEM PRE LOC\"");
+	}
+
+	public static JtpContext getJtpContext() {
+		return jtpContext;
+	}
+
+	public static JtpContext getJtpContextNotNull() {
+		synchronized(jtpContextLock) {
+			if( jtpContext==null ) {
+				try {
+					jtpContextLock.wait(10000L);
+				} catch(InterruptedException e) {
+					throw new RuntimeException(e);
+				}
+				if( jtpContext==null )
+					throw new NullPointerException("jtpContext is null");
+			}
+			return jtpContext;
+		}
+	}
+
+	public static void nop() {}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/NabbleConnectionLimitFilter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,29 @@
+package nabble.view.lib;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import fschmidt.util.servlet.ConnectionLimitFilter;
+
+
+public final class NabbleConnectionLimitFilter extends ConnectionLimitFilter {
+
+	public void doFilter(
+		ServletRequest req,
+		ServletResponse res,
+		FilterChain chain
+	)
+		throws IOException, ServletException
+	{
+		HttpServletRequest request = (HttpServletRequest)req;
+		if( request.getServletPath().contains("/tools/") ) {
+			chain.doFilter(req,res);
+			return;
+		}
+		super.doFilter(req,res,chain);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/NabbleErrorFilter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,43 @@
+package nabble.view.lib;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+public class NabbleErrorFilter implements Filter {
+	public static final String WORK_AROUND_ERROR_EXCEPTION = "work-around.error.exception";
+
+	public void destroy() { }
+
+	public void doFilter(ServletRequest req, ServletResponse res,
+			FilterChain chain) throws IOException, ServletException {
+		
+		try {
+			chain.doFilter(req, res);
+		} catch(Exception e) {
+			req.setAttribute(WORK_AROUND_ERROR_EXCEPTION, e);
+			
+			if(e instanceof RuntimeException) {
+				RuntimeException th = (RuntimeException)e;
+				throw th;
+			} 
+			else if(e instanceof IOException) {
+				IOException th = (IOException)e;
+				throw th;
+			} else {
+				ServletException th = (ServletException)e;
+				if( MinorServletException.isIn(th) )
+					throw new IOException(th);
+				throw th;
+			}
+		} 
+	}
+	
+	public void init(FilterConfig arg0) throws ServletException { }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/NabbleErrorHandler.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,202 @@
+
+package nabble.view.lib;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Init;
+import nabble.model.Site;
+import nabble.model.UpdatingException;
+import nabble.model.User;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.server.Request;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+
+public final class NabbleErrorHandler extends org.eclipse.jetty.server.handler.ErrorHandler {
+
+	private static final Logger logger = LoggerFactory.getLogger(NabbleErrorHandler.class);
+	private HttpServletResponse response;
+
+	public void handle(String string, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
+		this.response = httpServletResponse;
+		super.handle(string, request, httpServletRequest, httpServletResponse);
+	}
+
+	protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+        throws IOException
+    {
+		if (code == HttpServletResponse.SC_SERVICE_UNAVAILABLE) {
+			serviceUnavailablePage(writer);
+			return;
+		}
+		if (message == null)
+			message = HttpGenerator.getReasonBuffer(code).toString();
+
+		Throwable throwable = (Throwable)request.getAttribute(NabbleErrorFilter.WORK_AROUND_ERROR_EXCEPTION);
+		if (UpdatingException.isIn(throwable)) {
+			logger.error("Updating database: "+ServletUtils.getCurrentURL(request));
+			databaseUpdatePage(writer);
+			return;
+		}
+		try {
+			writeMyErrorPage(request,writer,code,message,showStacks, throwable);
+		} catch (ServletException e) {
+			logger.error(ServletUtils.getCurrentURL(request), e);
+			throw new RuntimeException(e);
+		} catch (RuntimeException e) {
+			logger.error(ServletUtils.getCurrentURL(request), e);
+			throw e;
+		}
+	}
+
+    private void writeMyErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks, Throwable throwable)
+        throws IOException, ServletException
+    {
+		PrintWriter out = new PrintWriter(writer);
+		boolean hasTweaks = false;
+		boolean isAdmin = false;
+		String homeLink;
+		Site site = null;
+		try {
+			site = Jtp.getSite(request);
+		} catch(RuntimeException e) {}
+		if( site == null ) {
+			homeLink = "<a href='" + Jtp.homePage() + "'>Nabble</a>";
+		} else {
+			homeLink = Jtp.link(site.getRootNode());
+			hasTweaks = site.getCustomTweaks().size() > 0;
+			User user = Jtp.getUser(request, response);
+			isAdmin = user != null && (site.getRootNode().getOwner().equals(user) || Permissions.isInGroup(user, "Administrators"));
+		}
+		
+		out.print( "\n<html>\n	<head>\n		" );
+ writeErrorPageHead(request,writer,code,message); 
+		out.print( "\n		<META NAME=\"robots\" CONTENT=\"noindex,nofollow\">\n		<link rel=\"stylesheet\" href=\"" );
+		out.print( (Shared.getCssPath()) );
+		out.print( "\" type=\"text/css\" />\n		<style type=\"text/css\">\n			p { margin: .3em 0 0; }\n			.error-details {\n				border: 1px solid gray;\n				padding: .5em;\n				width:90%;\n				margin-left:2.5%;\n				position: relative;\n				overflow-x:scroll;\n			}\n		</style>\n	</head>\n	<body>\n		" );
+
+				String uri = request.getRequestURI();
+		        if (uri!=null) {
+					uri = HtmlUtils.htmlEncode(uri);
+		        }
+				
+		out.print( "\n<div class=\"nabble\" id=\"nabble\">\n	<div class=\"top-bar\">\n		<div class=\"breadcrumbs\" style=\"float:left;\">\n			" );
+		out.print( (homeLink) );
+		out.print( "\n		</div>\n	</div>\n	" );
+
+					if (throwable != null) {
+						if( throwable instanceof ServletException ) {
+							if( MinorServletException.isIn(throwable) ) {
+								logger.info(ServletUtils.getCurrentURL(request), throwable);
+							} else {
+								logger.warn(ServletUtils.getCurrentURL(request), throwable);
+							}
+						} else {
+							logger.error(ServletUtils.getCurrentURL(request), throwable);
+						}
+						
+		out.print( "\n<h1>Oops... An error has occurred</h1>\n\nPlease contact <a href=\"" );
+		out.print( (Jtp.supportUrl()) );
+		out.print( "\" target=\"_new\">Nabble Support</a> and explain what you did to cause this error.\nYour feedback is very important to us.\n\n" );
+ if (hasTweaks) { 
+		out.print( "\n	" );
+ if (isAdmin) { 
+		out.print( "\n		<div class=\"info-message rounded\" style=\"margin-top:1em;padding:.5em .3em\">\n			<h3 style=\"margin-top:0;padding-top:0\">Modified NAML Code</h3>\n			" );
+		out.print( (site.getRootNode().getSubjectHtml()) );
+		out.print( " has modified NAML code and this could be the cause of the error below.\n			Please take a careful look at your changes and try to make sure your code is not broken.<br/>\n		</div>\n	" );
+ } 
+		out.print( "\n	<div style=\"margin:1.2em 0;font-weight:bold\">\n		<img src=\"/images/tool.png\" width=\"16\" height=\"17\" style=\"vertical-align:-25%\"/>\n		<a href=\"/template/NamlEditor.jtp\">Go to NAML Editor</a>\n	</div>\n" );
+ } 
+		out.print( "\n\n<h2 style=\"margin-top:1em\">More Details</h2>\n<div class=\"error-details light-bg-color\">\n	<h3>Error " );
+		out.print( (code) );
+		out.print( "</h3>\n	<pre>" );
+		out.print( (HtmlUtils.htmlEncode(message)) );
+		out.print( "</pre>\n	" );
+ if (throwable.getCause() != null) {
+								String msg = throwable.getCause().getMessage();
+								if( msg != null ) {
+									int posBreak = msg.indexOf("\n\t");
+									msg = posBreak > 0? msg.substring(0, posBreak) : msg;
+									msg = HtmlUtils.htmlEncode(msg);
+									msg = msg.replaceAll("\n", "<br/>").replaceAll("\t","&nbsp;&nbsp;&nbsp;&nbsp;");
+									
+		out.print( "\n<p><b>Message</b>: " );
+		out.print( (msg) );
+		out.print( "</p>\n" );
+
+								}
+							}
+		out.print( "\n<p><b>RequestURI</b>: " );
+		out.print( (uri) );
+		out.print( "</p>\n<p><b>Server</b>: " );
+		out.print( (Jtp.getDefaultHost()) );
+		out.print( "</p>\n" );
+
+
+							if (showStacks) {
+								StringWriter sw = new StringWriter();
+								PrintWriter pw = new PrintWriter(sw);
+								throwable.printStackTrace(pw);
+								pw.flush();
+								
+		out.print( "<h3 style=\"margin-top:1.2em\">Caused by:</h3><pre>" );
+		out.print( (HtmlUtils.htmlEncode(sw.toString())) );
+		out.print( "</pre>" );
+
+							}
+							
+		out.print( "\n</div>\n" );
+
+					} else {
+						
+		out.print( "\n<h2 style=\"padding:0;margin:1em 0\">" );
+		out.print( (message) );
+		out.print( "</h2>\n\nPlease contact <a href=\"" );
+		out.print( (Jtp.supportUrl()) );
+		out.print( "\" target=\"_new\">Nabble Support</a> if you need help.\n" );
+
+					}
+					{
+						StringBuffer url = request.getRequestURL();
+						String query = request.getQueryString();
+						if( query != null )
+							url.append( '?' ).append( query );
+						
+		out.print( "\n<p>URL = <a href=\"" );
+		out.print( (url) );
+		out.print( "\">" );
+		out.print( (url) );
+		out.print( "</a></p>\n" );
+
+					}
+					
+		out.print( "\n<table class=\"footer-table shaded-bg-color\">\n	<tr>\n		<td class=\"footer-left\">\n			Powered by <a href=\"" );
+		out.print( (Jtp.homePage()) );
+		out.print( "\" target=\"_top\" title=\"Free forum and other embeddable web apps\">Nabble</a>\n		</td>\n		<td class=\"footer-right\"></td>\n	</tr>\n</table>\n</div>\n</body>\n</html>\n" );
+
+	}
+
+	private void serviceUnavailablePage(Writer writer) {
+		PrintWriter out = new PrintWriter(writer);
+		
+		out.print( "\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n	<head>\n		<title>Service Unavailable</title>\n		<META NAME=\"robots\" CONTENT=\"noindex,nofollow\">\n	</head>\n	<body style=\"text-align:center;font-family: Verdana,sans-serif;font-size:.84em;padding-top:2em\">\n		<img src=\"https://www.nabble.com/assets/images/logo_nabble_home.png\"/>\n		<h1 style=\"margin-top:1.5em\">This Nabble server is over capacity.</h1>\n		<div style=\"margin:0 15% 2em;text-align:center;background:#eee;padding:1em 0\">\n			Too many requests... Please wait a moment and try again.\n		</div>\n\n		Powered by <a href=\"https://www.nabble.com/\">Nabble</a>\n		</div>\n	</body>\n</html>\n" );
+
+	}
+
+	private void databaseUpdatePage(Writer writer) {
+		PrintWriter out = new PrintWriter(writer);
+		
+		out.print( "\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n	<head>\n		<title>Updating Database</title>\n		<META NAME=\"robots\" CONTENT=\"noindex,nofollow\">\n	</head>\n	<body style=\"text-align:center;font-family: Verdana,sans-serif;font-size:.84em;padding-top:2em\">\n		<h1 style=\"margin-top:3em\">Updating Database</h1>\n		<div style=\"margin:0 25% 2em;text-align:center;background:#eee;padding:1em 0\">\n			This database is being updated. Please try again in a few seconds.\n		</div>\n		<h2>Reloading in <span id=\"sec\">60</span> seconds</h2>\n		<script type=\"text/javascript\">\n			var seconds = 60;\n			function countDown() {\n				if (seconds == 0) {\n					window.location.reload();\n					return;\n				}\n				document.getElementById('sec').innerHTML = seconds;\n				seconds--;\n				setTimeout(countDown, 1000);\n			};\n			countDown();\n		</script>\n\n		Powered by <a href=\"https://www.nabble.com/\">Nabble</a>\n		</div>\n	</body>\n</html>\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/NabbleErrorHandler.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,262 @@
+<%
+package nabble.view.lib;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Init;
+import nabble.model.Site;
+import nabble.model.UpdatingException;
+import nabble.model.User;
+import org.eclipse.jetty.http.HttpGenerator;
+import org.eclipse.jetty.server.Request;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+
+public final class NabbleErrorHandler extends org.eclipse.jetty.server.handler.ErrorHandler {
+
+	private static final Logger logger = LoggerFactory.getLogger(NabbleErrorHandler.class);
+	private HttpServletResponse response;
+
+	public void handle(String string, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
+		this.response = httpServletResponse;
+		super.handle(string, request, httpServletRequest, httpServletResponse);
+	}
+
+	protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks)
+        throws IOException
+    {
+		if (code == HttpServletResponse.SC_SERVICE_UNAVAILABLE) {
+			serviceUnavailablePage(writer);
+			return;
+		}
+		if (message == null)
+			message = HttpGenerator.getReasonBuffer(code).toString();
+
+		Throwable throwable = (Throwable)request.getAttribute(NabbleErrorFilter.WORK_AROUND_ERROR_EXCEPTION);
+		if (UpdatingException.isIn(throwable)) {
+			logger.error("Updating database: "+ServletUtils.getCurrentURL(request));
+			databaseUpdatePage(writer);
+			return;
+		}
+		try {
+			writeMyErrorPage(request,writer,code,message,showStacks, throwable);
+		} catch (ServletException e) {
+			logger.error(ServletUtils.getCurrentURL(request), e);
+			throw new RuntimeException(e);
+		} catch (RuntimeException e) {
+			logger.error(ServletUtils.getCurrentURL(request), e);
+			throw e;
+		}
+	}
+
+    private void writeMyErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks, Throwable throwable)
+        throws IOException, ServletException
+    {
+		PrintWriter out = new PrintWriter(writer);
+		boolean hasTweaks = false;
+		boolean isAdmin = false;
+		String homeLink;
+		Site site = null;
+		try {
+			site = Jtp.getSite(request);
+		} catch(RuntimeException e) {}
+		if( site == null ) {
+			homeLink = "<a href='" + Jtp.homePage() + "'>Nabble</a>";
+		} else {
+			homeLink = Jtp.link(site.getRootNode());
+			hasTweaks = site.getCustomTweaks().size() > 0;
+			User user = Jtp.getUser(request, response);
+			isAdmin = user != null && (site.getRootNode().getOwner().equals(user) || Permissions.isInGroup(user, "Administrators"));
+		}
+		%>
+		<html>
+			<head>
+				<% writeErrorPageHead(request,writer,code,message); %>
+				<META NAME="robots" CONTENT="noindex,nofollow">
+				<link rel="stylesheet" href="<%=Shared.getCssPath()%>" type="text/css" />
+				<style type="text/css">
+					p { margin: .3em 0 0; }
+					.error-details {
+						border: 1px solid gray;
+						padding: .5em;
+						width:90%;
+						margin-left:2.5%;
+						position: relative;
+						overflow-x:scroll;
+					}
+				</style>
+			</head>
+			<body>
+				<%
+				String uri = request.getRequestURI();
+		        if (uri!=null) {
+					uri = HtmlUtils.htmlEncode(uri);
+		        }
+				%>
+				<div class="nabble" id="nabble">
+					<div class="top-bar">
+						<div class="breadcrumbs" style="float:left;">
+							<%=homeLink%>
+						</div>
+					</div>
+					<%
+					if (throwable != null) {
+						if( throwable instanceof ServletException ) {
+							if( MinorServletException.isIn(throwable) ) {
+								logger.info(ServletUtils.getCurrentURL(request), throwable);
+							} else {
+								logger.warn(ServletUtils.getCurrentURL(request), throwable);
+							}
+						} else {
+							logger.error(ServletUtils.getCurrentURL(request), throwable);
+						}
+						%>
+						<h1>Oops... An error has occurred</h1>
+
+						Please contact <a href="<%=Jtp.supportUrl()%>" target="_new">Nabble Support</a> and explain what you did to cause this error.
+						Your feedback is very important to us.
+
+						<% if (hasTweaks) { %>
+							<% if (isAdmin) { %>
+								<div class="info-message rounded" style="margin-top:1em;padding:.5em .3em">
+									<h3 style="margin-top:0;padding-top:0">Modified NAML Code</h3>
+									<%=site.getRootNode().getSubjectHtml()%> has modified NAML code and this could be the cause of the error below.
+									Please take a careful look at your changes and try to make sure your code is not broken.<br/>
+								</div>
+							<% } %>
+							<div style="margin:1.2em 0;font-weight:bold">
+								<img src="/images/tool.png" width="16" height="17" style="vertical-align:-25%"/>
+								<a href="/template/NamlEditor.jtp">Go to NAML Editor</a>
+							</div>
+						<% } %>
+
+						<h2 style="margin-top:1em">More Details</h2>
+						<div class="error-details light-bg-color">
+							<h3>Error <%=code%></h3>
+							<pre><%=HtmlUtils.htmlEncode(message)%></pre>
+							<% if (throwable.getCause() != null) {
+								String msg = throwable.getCause().getMessage();
+								if( msg != null ) {
+									int posBreak = msg.indexOf("\n\t");
+									msg = posBreak > 0? msg.substring(0, posBreak) : msg;
+									msg = HtmlUtils.htmlEncode(msg);
+									msg = msg.replaceAll("\n", "<br/>").replaceAll("\t","&nbsp;&nbsp;&nbsp;&nbsp;");
+									%>
+									<p><b>Message</b>: <%=msg%></p>
+									<%
+								}
+							}%>
+							<p><b>RequestURI</b>: <%=uri%></p>
+							<p><b>Server</b>: <%=Jtp.getDefaultHost()%></p>
+							<%
+
+							if (showStacks) {
+								StringWriter sw = new StringWriter();
+								PrintWriter pw = new PrintWriter(sw);
+								throwable.printStackTrace(pw);
+								pw.flush();
+								%><h3 style="margin-top:1.2em">Caused by:</h3><pre><%=HtmlUtils.htmlEncode(sw.toString())%></pre><%
+							}
+							%>
+						</div>
+						<%
+					} else {
+						%>
+						<h2 style="padding:0;margin:1em 0"><%=message%></h2>
+
+						Please contact <a href="<%=Jtp.supportUrl()%>" target="_new">Nabble Support</a> if you need help.
+						<%
+					}
+					{
+						StringBuffer url = request.getRequestURL();
+						String query = request.getQueryString();
+						if( query != null )
+							url.append( '?' ).append( query );
+						%>
+						<p>URL = <a href="<%=url%>"><%=url%></a></p>
+						<%
+					}
+					%>
+					<table class="footer-table shaded-bg-color">
+						<tr>
+							<td class="footer-left">
+								Powered by <a href="<%=Jtp.homePage()%>" target="_top" title="Free forum and other embeddable web apps">Nabble</a>
+							</td>
+							<td class="footer-right"></td>
+						</tr>
+					</table>
+				</div>
+			</body>
+		</html>
+		<%
+	}
+
+	private void serviceUnavailablePage(Writer writer) {
+		PrintWriter out = new PrintWriter(writer);
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<title>Service Unavailable</title>
+				<META NAME="robots" CONTENT="noindex,nofollow">
+			</head>
+			<body style="text-align:center;font-family: Verdana,sans-serif;font-size:.84em;padding-top:2em">
+				<img src="https://www.nabble.com/assets/images/logo_nabble_home.png"/>
+				<h1 style="margin-top:1.5em">This Nabble server is over capacity.</h1>
+				<div style="margin:0 15% 2em;text-align:center;background:#eee;padding:1em 0">
+					Too many requests... Please wait a moment and try again.
+				</div>
+
+				Powered by <a href="https://www.nabble.com/">Nabble</a>
+				</div>
+			</body>
+		</html>
+		<%
+	}
+
+	private void databaseUpdatePage(Writer writer) {
+		PrintWriter out = new PrintWriter(writer);
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<title>Updating Database</title>
+				<META NAME="robots" CONTENT="noindex,nofollow">
+			</head>
+			<body style="text-align:center;font-family: Verdana,sans-serif;font-size:.84em;padding-top:2em">
+				<h1 style="margin-top:3em">Updating Database</h1>
+				<div style="margin:0 25% 2em;text-align:center;background:#eee;padding:1em 0">
+					This database is being updated. Please try again in a few seconds.
+				</div>
+				<h2>Reloading in <span id="sec">60</span> seconds</h2>
+				<script type="text/javascript">
+					var seconds = 60;
+					function countDown() {
+						if (seconds == 0) {
+							window.location.reload();
+							return;
+						}
+						document.getElementById('sec').innerHTML = seconds;
+						seconds--;
+						setTimeout(countDown, 1000);
+					};
+					countDown();
+				</script>
+
+				Powered by <a href="https://www.nabble.com/">Nabble</a>
+				</div>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/NewSiteMail.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,29 @@
+
+package nabble.view.lib;
+
+import nabble.model.Site;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.ServletNamespace;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+
+
+public final class NewSiteMail {
+
+	public static void send(Site site, HttpServletRequest request, HttpServletResponse response) {
+		Template template = site.getTemplate( "send bookmark email",
+			BasicNamespace.class, NabbleNamespace.class, ServletNamespace.class
+		);
+		template.run( TemplatePrintWriter.NULL, Collections.<String, Object>emptyMap(),
+			new BasicNamespace(template), new NabbleNamespace(site), new ServletNamespace(request, response)
+		);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/NewSiteMail.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,29 @@
+<%
+package nabble.view.lib;
+
+import nabble.model.Site;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.ServletNamespace;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+
+
+public final class NewSiteMail {
+
+	public static void send(Site site, HttpServletRequest request, HttpServletResponse response) {
+		Template template = site.getTemplate( "send bookmark email",
+			BasicNamespace.class, NabbleNamespace.class, ServletNamespace.class
+		);
+		template.run( TemplatePrintWriter.NULL, Collections.<String, Object>emptyMap(),
+			new BasicNamespace(template), new NabbleNamespace(site), new ServletNamespace(request, response)
+		);
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/Permissions.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,542 @@
+package nabble.view.lib;
+
+import fschmidt.db.Listener;
+import fschmidt.db.ListenerList;
+import fschmidt.util.java.Filter;
+import nabble.model.Db;
+import nabble.model.Init;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.model.User;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+
+public final class Permissions {
+	private Permissions() {}  // never
+
+	private static String encode(String s) {
+		StringBuilder buf = new StringBuilder();
+		int n = s.length();
+		for( int i=0; i<n; i++ ) {
+			char c = s.charAt(i);
+			if( c == '\'' )
+				buf.append('\'');
+			buf.append(c);
+		}
+		return buf.toString();
+	}
+
+	private static void dbCheck(Site site) {
+		if( !site.getDb().isInTransaction() )
+			throw new RuntimeException("not in transaction");
+	}
+
+	public static boolean isInGroup(User user,String group) {
+		return user.getSite().hasTags(null,user,
+			"label='group:" + encode(group) + "'"
+		);
+	}
+
+	public static void addToGroup(User user,String group) {
+		Site site = user.getSite();
+		dbCheck(site);
+		site.addTag( null, user, "group:" + group );
+		groupChangeListeners.event(site);
+	}
+
+	public static void removeFromGroup(User user,String group) {
+		Site site = user.getSite();
+		dbCheck(site);
+		user.getSite().deleteTags(null,user,
+			"label='group:" + encode(group) + "'"
+		);
+		groupChangeListeners.event(site);
+	}
+
+	public static void removeGroup(Site site,String group) {
+		site.deleteTags(
+			"node_id is null and user_id is not null and label='group:" + encode(group) + "'"
+		);
+		groupChangeListeners.event(site);
+	}
+
+	public static void removeGroups(User user) {
+		user.getSite().deleteTags(null,user,
+			"label like 'group:%'"
+		);
+		groupChangeListeners.event(user.getSite());
+	}
+
+	private static List<String> getGroups(Site site,String sqlCondition) {
+		List<String> list = new ArrayList<String>();
+		for( String label : site.findTagLabels(sqlCondition) ) {
+			list.add( label.substring(6) );
+		}
+		return list;
+	}
+
+	public static List<String> getGroups(User user) {
+		return getGroups( user.getSite(),
+			"node_id is null and user_id=" + user.getId() + " and label like 'group:%'"
+		);
+	}
+
+	public static List<String> getGroups(Site site) {
+		return getGroups( site,
+			"node_id is null and user_id is not null and label like 'group:%'"
+		);
+	}
+
+	public static List<User> getUsersInGroup(Site site,String group) {
+		return site.findTagUsers(
+			"node_id is null and user_id is not null and label='group:" + encode(group) + "'"
+		);
+	}
+
+	public static void addPermission(Node node,String permission) {
+		Site site = node.getSite();
+		dbCheck(site);
+		site.addTag( node, null, "permission:" + permission );
+		permissionChangeListeners.event(site);
+	}
+
+	public static void addPermission(Node node,String permission,String group) {
+		Site site = node.getSite();
+		dbCheck(site);
+		if( !nodeHasPermission(node,permission) )
+			addPermission(node,permission);
+		site.addTag( node, null, "permission:" + permission + ":" + group );
+		permissionChangeListeners.event(site);
+	}
+
+	public static void addPermission(Site site,String permission,String group) {
+		dbCheck(site);
+		if( !siteHasPermission(site,permission) )
+			site.addTag( null, null, "permission:" + permission );
+		site.addTag( null, null, "permission:" + permission + ":" + group );
+		permissionChangeListeners.event(site);
+	}
+
+	public static void removePermission(Node node,String permission,String group) {
+		Site site = node.getSite();
+		dbCheck(site);
+		site.deleteTags(node,null,
+			"label='permission:" + encode(permission) + ":" + encode(group) + "'"
+		);
+		permissionChangeListeners.event(site);
+	}
+
+	public static void removePermission(Node node,String permission) {
+		Site site = node.getSite();
+		site.deleteTags(node,null,
+			"(label = 'permission:" + encode(permission) + "'"
+				+ " or label like 'permission:" + encode(permission) + ":%')"
+		);
+		permissionChangeListeners.event(site);
+	}
+
+	public static void removePermissions(Node node) {
+		Site site = node.getSite();
+		dbCheck(site);
+		site.deleteTags(node,null,
+			"label like 'permission:%'"
+		);
+		permissionChangeListeners.event(site);
+	}
+
+	public static boolean isPermissionVersion(Site site,String version) {
+		return site.hasTags(null,null,
+			"label='permission-version:" + version + "'"
+		);
+	}
+
+	public static void setPermissionVersion(Site site,String version) {
+		dbCheck(site);
+		deletePermissionVersion(site);
+		site.addTag( null, null, "permission-version:" + version );
+	}
+
+	public static void deletePermissionVersion(Site site) {
+		site.deleteTags(null,null,
+			"(label like 'permission:%' or label like 'site_default_permission:%' or label like 'permission-version:%')"
+		);
+	}
+
+	private static String query(Node node) {
+		return node==null ? "node_id is null" : "node_id=" + node.getId();
+	}
+
+	private static boolean siteHasPermission(Site site,String permission) {
+		return site.hasTags(null,null,
+			"label='permission:" + encode(permission) + "'"
+		);
+	}
+
+	public static boolean nodeHasPermission(Node node,String permission) {
+		return node.getSite().hasTags(node,null,
+			"label='permission:" + encode(permission) + "'"
+		);
+	}
+
+	public static Node getPermissionNode(Node node,String permission) {
+		for( Node n : node.getAncestors() ) {
+			if( nodeHasPermission(n,permission) )
+				return n;
+		}
+		return null;
+	}
+
+	public static List<String> getGroupsWithPermission(Node node,String permission) {
+		Site site = node.getSite();
+		node = getPermissionNode(node,permission);
+		if( node == null && !siteHasPermission(site,permission) )
+			return Collections.emptyList();
+		List<String> list = new ArrayList<String>();
+		String labelStart = "permission:" + encode(permission) + ":";
+		int i = labelStart.length();
+		for( String label : site.findTagLabels(
+			query(node) + " and user_id is null and label like '" + labelStart + "%'"
+		) ) {
+			list.add( label.substring(i) );
+		}
+		return list;
+	}
+
+	public static boolean hasGroupsWithPermission(Node node,String permission) {
+		Site site = node.getSite();
+		node = getPermissionNode(node,permission);
+		if( node == null && !siteHasPermission(site,permission) )
+			return false;
+		return site.hasTags(node,null,
+			"label like 'permission:" + encode(permission) + ":%'"
+		);
+	}
+
+	public static final String ANYONE_GROUP = "Anyone";
+	public static final String REGISTERED_GROUP = "Registered";
+	public static final String AUTHOR_GROUP = "Authors";
+	public static final String ADMINISTRATORS_GROUP = "Administrators";
+
+	public static List<String> getPersonGroups(Person person) {
+		List<String> groups;
+		if (person instanceof User) {
+			User user = (User) person;
+			groups = getGroups((User) person);
+			if (user.isRegistered())
+				groups.add(REGISTERED_GROUP);
+		} else {
+			groups = new ArrayList<String>();
+		}
+		groups.add(ANYONE_GROUP);
+		return groups;
+	}
+
+	public static boolean hasPermission(Node permissionNode,Node targetNode,Person person,String permission) {
+		Person owner = targetNode.getOwner();
+		Site site = permissionNode.getSite();
+		permissionNode = getPermissionNode(permissionNode,permission);
+		if( permissionNode == null && !siteHasPermission(site,permission) )
+			return false;
+		String s = "label='permission:" + encode(permission) + ":";
+		List<String> groups = getPersonGroups(person);
+		if( owner.equals(person) )
+			groups.add(AUTHOR_GROUP);
+		for( String group : groups ) {
+			if( site.hasTags(permissionNode,null, s + encode(group) + "'" ) )
+				return true;
+		}
+		return false;
+	}
+
+	public static boolean hasPermission(Node node,String group,String permission) {
+		Site site = node.getSite();
+		node = getPermissionNode(node,permission);
+		if( node == null && !siteHasPermission(site,permission) )
+			return false;
+		return hasPermission(site,node,group,permission);
+	}
+
+	private static boolean hasPermission(Site site,Node node,String group,String permission) {
+		return site.hasTags(node,null,
+			"label='permission:" + encode(permission) + ":" + encode(group) + "'"
+		);
+	}
+
+	public static boolean hasPermission(Site site,String group,String permission) {
+		return siteHasPermission(site,permission) && site.hasTags(null,null,
+			"label='permission:" + encode(permission) + ":" + encode(group) + "'"
+		);
+	}
+
+	public static List<User> getUsersWithPermission(Node node,String permission) {
+		Site site = node.getSite();
+		node = getPermissionNode(node,permission);
+		if( node == null && !siteHasPermission(site,permission) )
+			return Collections.emptyList();
+		if (hasPermission(site, node, ANYONE_GROUP,permission))
+			return site.getUsers(null);
+		else if (hasPermission(site, node, REGISTERED_GROUP,permission))
+			return site.getUsers("registered is not null");
+		String labelStart = "permission:" + encode(permission) + ":";
+		int i = labelStart.length();
+		return site.findTagUsers(
+			"node_id is null and user_id is not null and label in ("
+			+	"select 'group:' || substring(label," + (i+1) + ") from tag where " + query(node) + " and user_id is null and label like '" + labelStart + "%'"
+			+")"
+		);
+	}
+
+
+	public static final String VIEW_PERMISSION = "View";
+
+	public static boolean isPrivate(Node node) {
+		return getPrivateNode(node) != null;
+	}
+
+	public static boolean canBeViewedByParentViewers(Node node) {
+		if( node.getKind() != Node.Kind.APP )
+			return true;
+		if( !nodeHasPermission(node,VIEW_PERMISSION) )
+			return true;
+		Node parent = node.getParent();
+		if( parent != null )
+			parent = getPrivateNode(parent);
+		if( parent == null && !siteHasPermission(node.getSite(),VIEW_PERMISSION) )
+			return !isPrivate(node);
+		return !node.getSite().hasTags(parent,null,
+			"label like 'permission:View:%'"
+			+" and label not in (select label from tag where node_id=" + node.getId() + " and user_id is null and label like 'permission:View:%')"
+		);
+	}
+
+	public static final Filter<Node> canBeViewedByParentViewersFilter = new Filter<Node>() {
+		public boolean ok(Node node) {
+			return canBeViewedByParentViewers(node);
+		}
+	};
+
+	public static boolean canBeViewedByPerson(Node node,Person person) {
+		if( person instanceof User ) {
+			User user = (User)person;
+			if( isSysAdmin(user) )
+				return true;
+			if( isInGroup(user,ADMINISTRATORS_GROUP) )
+				return true;
+		}
+		if( node.getSite().getRootNode().getOwner().equals(person) )
+			return true;
+		Node permissionNode = node.getApp();
+		if( permissionNode==null )
+			permissionNode = node.getSite().getRootNode();
+		return hasPermission(permissionNode,node,person,VIEW_PERMISSION);
+	}
+
+	public static final Filter<Node> canBeViewedByPersonFilter(final Person person) {
+		return new Filter<Node>() {
+			public boolean ok(Node node) {
+				return canBeViewedByPerson(node,person);
+			}
+		};
+	}
+
+	public static Node getPrivateNode(Node node) {
+		node = getPermissionNode(node,VIEW_PERMISSION);
+		return node==null || hasPermission(node.getSite(),node,ANYONE_GROUP,VIEW_PERMISSION) ? null : node;
+	}
+
+	public static Node getPrivateNodeForSearch(Node node) {
+		node = getPrivateNode(node);
+		while( node != null && canBeViewedByParentViewers(node) ) {
+			node = getPrivateNode(node.getParent());
+		}
+		return node;
+	}
+
+	// Banning ------------------------------------------------------
+
+	public static boolean isBanned(User user) {
+		return user.getSite().hasTags(null,user,"label='banned'");
+	}
+
+	public static void ban(User user) {
+		user.getSite().addTag(null, user, "banned");
+	}
+
+	public static void unban(User user) {
+		user.getSite().deleteTags(null,user,"label='banned'");
+	}
+
+	public static List<User> getBannedUsers(Site site) {
+		return site.findTagUsers(
+			"node_id is null and user_id is not null and label='banned'"
+		);
+	}
+
+
+
+	private static final Set<String> sysadmins = Init.get("sysadmins",Collections.<String>emptySet());
+
+	public static boolean isSysAdmin(User user) {
+		return sysadmins.contains(user.getEmail());
+	}
+
+
+	// site permissions
+
+	public static void addSitePermission(Site site,String permission) {
+		dbCheck(site);
+		site.addTag( null, null, "site_permission:" + permission );
+	}
+
+	public static void addSiteDefaultPermission(Site site,String permission) {
+		dbCheck(site);
+		site.addTag( null, null, "site_default_permission:" + permission );
+	}
+
+	private static boolean hasSitePermission(Site site,String permission) {
+		return site.hasTags(null,null,
+			"label='site_permission:" + encode(permission) + "'"
+		);
+	}
+
+	public static boolean siteHasSitePermission(Site site,String permission) {
+		return hasSitePermission(site,permission);
+	}
+
+	private static boolean hasSiteDefaultPermission(Site site,String permission) {
+		return site.hasTags(null,null,
+			"label='site_default_permission:" + encode(permission) + "'"
+		);
+	}
+
+	public static void addSitePermission(Site site,String permission,String group) {
+		dbCheck(site);
+		if( !hasSitePermission(site,permission) )
+			site.addTag( null, null, "site_permission:" + permission );
+		site.addTag( null, null, "site_permission:" + permission + ":" + group );
+	}
+
+	public static void addSiteDefaultPermission(Site site,String permission,String group) {
+		dbCheck(site);
+		if( !hasSiteDefaultPermission(site,permission) )
+			site.addTag( null, null, "site_default_permission:" + permission );
+		site.addTag( null, null, "site_default_permission:" + permission + ":" + group );
+	}
+
+	public static void removeSitePermission(Site site,String permission,String group) {
+		dbCheck(site);
+		site.deleteTags(null,null,
+			"label='site_permission:" + encode(permission) + ":" + encode(group) + "'"
+		);
+	}
+
+	public static void removeSitePermission(Site site,String permission) {
+		site.deleteTags(null,null,
+			"(label = 'site_permission:" + encode(permission) + "'"
+				+ " or label like 'site_permission:" + encode(permission) + ":%')"
+		);
+	}
+
+	public static void removeSitePermissions(Site site) {
+		dbCheck(site);
+		site.deleteTags(null,null,
+			"label like 'site_permission:%'"
+		);
+	}
+
+	private static String sitePermissionLabel(Site site,String permission) {
+		if( hasSitePermission(site,permission) )
+			return "site_permission:" + encode(permission);
+		else if( hasSiteDefaultPermission(site,permission) )
+			return "site_default_permission:" + encode(permission);
+		else
+			return null;
+	}
+
+	public static List<String> getGroupsWithSitePermission(Site site,String permission) {
+		String labelStart = sitePermissionLabel(site,permission);
+		if( labelStart == null )
+			return Collections.emptyList();
+		List<String> list = new ArrayList<String>();
+		int i = labelStart.length();
+		for( String label : site.findTagLabels(
+			"node_id is null and user_id is null and label like '" + labelStart + ":%'"
+		) ) {
+			list.add( label.substring(i) );
+		}
+		return list;
+	}
+
+	public static boolean hasGroupsWithSitePermission(Site site,String permission) {
+		String labelStart = sitePermissionLabel(site,permission);
+		if( labelStart == null )
+			return false;
+		return site.hasTags(null,null,
+			"label like '" + labelStart + ":%'"
+		);
+	}
+
+	public static boolean hasSitePermission(Site site,Person person,String permission) {
+		String labelStart = sitePermissionLabel(site,permission);
+		if( labelStart == null )
+			return false;
+		String s = "label='" + labelStart + ":";
+		List<String> groups = getPersonGroups(person);
+		for( String group : groups ) {
+			if( site.hasTags(null,null, s + encode(group) + "'" ) )
+				return true;
+		}
+		return false;
+	}
+
+	public static boolean hasSitePermission(Site site,String group,String permission) {
+		String labelStart = sitePermissionLabel(site,permission);
+		if( labelStart == null )
+			return false;
+		return site.hasTags(null,null,
+			"label='" + labelStart + ":" + encode(group) + "'"
+		);
+	}
+
+	public static boolean hasSiteDefaultPermission(Site site,String group,String permission) {
+		return hasSiteDefaultPermission(site,permission) && site.hasTags(null,null,
+			"label='site_default_permission:" + encode(permission) + ":" + encode(group) + "'"
+		);
+	}
+
+	public static List<User> getUsersWithSitePermission(Site site,String permission) {
+		String labelStart = sitePermissionLabel(site,permission);
+		if (labelStart == null)
+			return Collections.emptyList();
+		if (hasSitePermission(site,ANYONE_GROUP,permission))
+			return site.getUsers(null);
+		else if (hasSitePermission(site,REGISTERED_GROUP,permission))
+			return site.getUsers("registered is not null");
+		labelStart += ":";
+		int i = labelStart.length();
+		return site.findTagUsers(
+			"node_id is null and user_id is not null and label in ("
+			+	"select 'group:' || substring(label," + (i+1) + ") from tag where node is null and user_id is null and label like '" + labelStart + "%'"
+			+")"
+		);
+	}
+
+
+	private static final ListenerList<Site> groupChangeListeners = new ListenerList<Site>();
+
+	public static void addGroupChangeListener(final Listener<Site> listener) {
+		groupChangeListeners.add(listener);
+	}
+
+	private static final ListenerList<Site> permissionChangeListeners = new ListenerList<Site>();
+
+	public static void addPermissionChangeListener(final Listener<Site> listener) {
+		permissionChangeListeners.add(listener);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/Recaptcha.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,57 @@
+package nabble.view.lib;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import luan.lib.json.JsonParser;
+import luan.lib.parser.ParseException;
+import fschmidt.util.java.IoUtils;
+import nabble.model.ModelException;
+import nabble.view.lib.Jtp;
+
+
+public final class Recaptcha {
+
+	public static final String JS = "<script src='https://www.google.com/recaptcha/api.js'></script>";
+
+	public static final String DIV = "<div class='g-recaptcha' data-sitekey='6Lf12z4UAAAAAKFG8EczAb6BjVvaFM2rqHswrCP7'></div>";
+
+	private static final String SECRET = "6Lf12z4UAAAAAChAhLaE4-pxoY7z9LAWkSJnif4s";
+	private static final String URL = "https://www.google.com/recaptcha/api/siteverify";
+	private static final String PARAMS = "secret="+SECRET+"&response=";
+
+	private static String getDomainIP(String domain)
+		throws UnknownHostException
+	{
+		int i = domain.indexOf(":");
+		if( i > 0 )
+			domain = domain.substring(0,i);
+		return InetAddress.getByName(domain).getHostAddress();
+	}
+
+	public static void check(HttpServletRequest request) 
+		throws ModelException.InvalidRecaptcha, IOException
+	{
+		String response = request.getParameter("g-recaptcha-response");
+		String json = IoUtils.post( URL, PARAMS+response );
+//System.out.println(json);
+		Map map;
+		try {
+			map = (Map)JsonParser.parse(json);
+		} catch(ParseException e) {
+			throw new RuntimeException(e);
+		}
+		boolean success = (Boolean)map.get("success");
+		if( !success )
+			throw new ModelException.InvalidRecaptcha();
+		String hostname = (String)map.get("hostname");
+		String hostIP = getDomainIP(hostname);
+		String nabbleIP = getDomainIP(Jtp.getDefaultHost());
+		if( !nabbleIP.equals(hostIP) )
+			throw new RuntimeException("invalid host");
+	}
+
+	private Recaptcha() {}  // never
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/Shared.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,616 @@
+
+package nabble.view.lib;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.java.MD5Util;
+import nabble.model.Init;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.model.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public final class Shared {
+    private static final Logger logger = LoggerFactory.getLogger(Shared.class);
+
+	private Shared() {}  // never
+
+	public static volatile int javascriptVersion = 102;
+
+	public static final int cssVersion = 29;
+
+	public static String getCssPath() {
+		return "/nabble.css?v="+cssVersion;
+	}
+
+	public static String getJavascriptPath(HttpServletRequest request) {
+		return Jtp.getSite(request) == null? "/Javascript.jtp" : "/template/NamlServlet.jtp?macro=javascript_library";
+	}
+
+	public static String getTabsPath() {
+		return "/util/nabbletabs.js";
+	}
+
+	public static String getJQueryPath() {
+		return "/util/jquery-1.7.2.pack.js";
+	}
+
+	public static String getDropdownJsPath() {
+		return "/util/nabbledropdown-2.4.1.js";
+	}
+
+	public static void head(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		head(request, response, true);
+	}
+
+	public static void head(HttpServletRequest request,HttpServletResponse response, boolean useSiteStyle)
+		throws IOException
+	{
+		if( request.getAttribute("head") != null )
+			return;
+		request.setAttribute("head","x");
+		PrintWriter out = response.getWriter();
+		Site site = Jtp.getSite(request);
+		boolean isApp = site != null && site.getRootNode().getKind() == Node.Kind.APP;
+		
+		out.print( "\r\n<link rel=\"stylesheet\" href=\"" );
+		out.print( (Shared.getCssPath()) );
+		out.print( "\" type=\"text/css\" />\r\n" );
+ if (isApp && useSiteStyle) { 
+		out.print( "\r\n<link rel=\"stylesheet\" href=\"/template/NamlServlet.jtp?macro=site_style\" type=\"text/css\" />\r\n" );
+ } 
+		out.print( "\r\n" );
+ loadJavascript(request, out); 
+		out.print( "\r\n" );
+ if (isApp) { 
+		out.print( "\r\n<script type=\"text/javascript\">\r\n	Nabble.setFontSize();\r\n</script>\r\n" );
+ } 
+		out.print( "\r\n" );
+
+		loadAnalytics(request,response);
+	}
+
+	public static void loadJavascript(HttpServletRequest request, PrintWriter out) {
+		
+		out.print( "\r\n<script src=\"" );
+		out.print( (getJQueryPath()) );
+		out.print( "\" type=\"text/javascript\"></script>\r\n<script src=\"" );
+		out.print( (getDropdownJsPath()) );
+		out.print( "\" type=\"text/javascript\"></script>\r\n<script src=\"" );
+		out.print( (getJavascriptPath(request)) );
+		out.print( "\" type=\"text/javascript\"></script>\r\n" );
+
+	}
+
+	private static void checkHead(HttpServletRequest request) {
+		if( request.getAttribute("head") == null )
+			throw new RuntimeException("Shared.head not called");
+	}
+
+	public static void minHeader(HttpServletRequest request,HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		minHeader(request,response,null);
+	}
+
+	public static void minHeader(HttpServletRequest request,HttpServletResponse response,Node node)
+		throws IOException, ServletException
+	{
+		minHeader(request, response, node, null);
+	}
+
+	public static void minHeaderGlobal(HttpServletRequest request,HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		Site site = Jtp.getSite(request);
+		if (site == null) {
+			String homepageLink = "<a id=\"homelink\" href=\"" + Jtp.homePage() + "\">Nabble</a>";
+			minHeader(request, response, null, new String[] { homepageLink });
+			PrintWriter out = response.getWriter();
+			
+		out.print( "\r\n<script type=\"text/javascript\">\r\n	if (Nabble.getParent().nabbleinfo && Nabble.getParent().nabbleinfo.what) {\r\n		var home = Nabble.get('homelink');\r\n		home.setAttribute('href', '/');\r\n		home.innerHTML='Back to ' + Nabble.getParent().nabbleinfo.what;\r\n	}\r\n</script>\r\n" );
+
+		} else {
+			Node node = site.getRootNode();
+			String homepageLink = "<a href=\"" + Jtp.url(node) + "\">" + node.getSubjectHtml() + "</a>";
+			minHeader(request, response, null, new String[] { homepageLink });
+		}
+	}
+
+	public static void minHeader(HttpServletRequest request, HttpServletResponse response, Node node, String[] breadcrumbLinks)
+		throws IOException, ServletException
+	{
+		minHeader(request, response, node, breadcrumbLinks, true);
+	}
+
+	public static void minHeader(HttpServletRequest request, HttpServletResponse response, Node node, String[] breadcrumbLinks, boolean embeddedRedirect)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		checkHead(request);
+		Node app = node==null ? null : node.getApp();
+		if (embeddedRedirect)
+			embeddedRedirect(request, out, app);
+		
+		out.print( "\r\n<div id=\"notice\" class=\"notice rounded-bottom\"></div>\r\n<div class=\"nabble\" id=\"nabble\">\r\n	<div class=\"top-bar\">\r\n		<div class=\"breadcrumbs\" style=\"float:left;\">\r\n			" );
+
+					if (app != null)
+						breadcrumb(request,out,app);
+					else if (breadcrumbLinks != null)
+						breadcrumb(out,breadcrumbLinks);
+					
+		out.print( "\r\n</div>\r\n<div style=\"text-align:right;\">\r\n<span style=\"white-space:nowrap;\" id=\"nabble-user-header\"></span>\r\n<script type=\"text/javascript\">Nabble.userHeader();</script>\r\n</div>\r\n</div>\r\n<div style=\"clear:both;\"></div>\r\n" );
+
+			if (request.getAttribute("search") == null) {
+				
+		out.print( "\r\n<script type=\"text/javascript\">\r\n	Nabble.deleteCookie(\"query\");\r\n	Nabble.deleteCookie(\"searchuser\");\r\n	Nabble.deleteCookie(\"searchterms\");\r\n</script>\r\n" );
+
+			}
+	}
+
+	public static void noHeader(HttpServletRequest request,HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<div class=\"nabble\" id=\"nabble\">\r\n" );
+
+	}
+
+	public static void editHeader(String firstText, String secondText, PrintWriter out) {
+		
+		out.print( "\r\n<div class=\"second-font shaded-bg-color\" style=\"font-size: 120%;padding: .4em;font-weight:bold\">\r\n	" );
+		out.print( (firstText) );
+		out.print( " <span class=\"weak-color\">" );
+		out.print( (secondText == null? "" : "&ndash; " + secondText) );
+		out.print( "</span>\r\n</div>\r\n" );
+
+	}
+
+	public static void helpHeader(HttpServletRequest request,HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		Site site = Jtp.getSite(request);
+		String homepageLink = "<a href=\"/\">" + (site == null? "Nabble":site.getRootNode().getSubjectHtml()) + "</a>";
+		String helpLink = "<a href=\"/help/Index.jtp\">Help</a>";
+		minHeader(request, response, null, new String[] { homepageLink, helpLink });
+	}
+
+	public static void footer(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		Site site = Jtp.getSite(request);
+		if( site == null ) {
+			
+		out.print( "\r\n<table class=\"footer-table shaded-bg-color\">\r\n	<tr>\r\n		<td class=\"footer-left\">\r\n			<a href=\"" );
+		out.print( (Jtp.homePage()) );
+		out.print( "\" target=\"_top\">Free Forum</a> by Nabble\r\n		</td>\r\n	</tr>\r\n</table>\r\n" );
+
+		} else {
+			
+		out.print( "\r\n<table class=\"footer-table shaded-bg-color\">\r\n	<tr>\r\n		<td class=\"footer-left\">\r\n			<a href=\"" );
+		out.print( (Jtp.homePage()) );
+		out.print( "\" target=\"_top\">Free Forum</a>\r\n			by Nabble\r\n		</td>\r\n		<td class=\"footer-right\">\r\n			<a href=\"" );
+		out.print( (Jtp.helpIndexUrl(request,response)) );
+		out.print( "\">Help</a>\r\n		</td>\r\n	</tr>\r\n</table>\r\n" );
+
+		}
+		
+		out.print( "\r\n</div>\r\n" );
+
+	}
+
+	public static void noFooter(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n</div>\r\n" );
+
+	}
+
+	public static void breadcrumb(HttpServletRequest request,PrintWriter out,Node node)
+		throws ServletException, IOException
+	{
+		List<String> links = new ArrayList<String>();
+		node = node.getApp();
+		while (node != null) {
+			links.add(0, "<a href=\"" + Jtp.path(node) + "\">" + node.getSubjectHtml() + "</a>");
+			node = node.getParent();
+		}
+		String[] array = new String[links.size()];
+		links.toArray(array);
+		breadcrumb(out, array);
+	}
+
+	private static void breadcrumb(PrintWriter out, String[] links)
+		throws ServletException, IOException
+	{
+		
+		out.print( "<span class=\"weak-color\" style=\"white-space:nowrap\">" );
+
+		for (String s : links) {
+			
+		out.print( (s) );
+
+			boolean isLastString = s.equals(links[links.length-1]);
+			if (!isLastString) {
+				
+		out.print( "&nbsp;&rsaquo;&nbsp;" );
+
+			}
+		}
+		
+		out.print( "</span>" );
+
+	}
+
+	public static void javascriptRedirect(HttpServletRequest request,HttpServletResponse response,String url)
+		throws IOException, ServletException
+	{
+		javascriptRedirect(request,response,url,null);
+	}
+
+	public static void javascriptRedirect(HttpServletRequest request,HttpServletResponse response,String url,String script)
+		throws IOException, ServletException
+	{
+		javascriptRedirect(request, response, url, script, false);
+	}
+
+	public static void javascriptRedirect(HttpServletRequest request,HttpServletResponse response,String url,String script, boolean leaveEmbedding)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n<head>\r\n	<script src=\"" );
+		out.print( (getJQueryPath()) );
+		out.print( "\" type=\"text/javascript\"></script>\r\n	<script src=\"" );
+		out.print( (getJavascriptPath(request)) );
+		out.print( "\" type=\"text/javascript\"></script>\r\n	" );
+ loadAnalytics(request,response); 
+		out.print( "\r\n	<script type=\"text/javascript\">\r\n		" );
+		out.print( (Jtp.hideNull(script)) );
+		out.print( "\r\n		var url = \"" );
+		out.print( (HtmlUtils.javascriptStringEncode(url)) );
+		out.print( "\";\r\n\r\n		" );
+		out.print( (leaveEmbedding? "top." : "") );
+		out.print( "location.replace(url);\r\n	</script>\r\n</head>\r\n</html>\r\n" );
+
+	}
+
+	/* Rebuilds the embedding iframe and reload the contents.*/
+	public static void restartEmbedding(Node node, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<script src=\"" );
+		out.print( (getJQueryPath()) );
+		out.print( "\" type=\"text/javascript\"></script>\r\n		<script src=\"" );
+		out.print( (getJavascriptPath(request)) );
+		out.print( "\" type=\"text/javascript\"></script>\r\n		<script type=\"text/javascript\">\r\n			Nabble.restartEmbedding(" );
+		out.print( (node.getId()) );
+		out.print( ",'" );
+		out.print( (node.getSite().getBaseUrl()) );
+		out.print( "');\r\n		</script>\r\n	</head>\r\n</html>\r\n" );
+
+	}
+
+	public static void showPending(PrintWriter out, Node post) {
+		Node.MailToList mail = post.getMailToList();
+		if (mail == null || !(post.getOwner() instanceof User))
+			return;
+		showPending(out, mail.isPending());
+	}
+
+    public static void showPending(PrintWriter out, boolean isPending) {
+		if (isPending) {
+			
+		out.print( "\r\n<img src=\"/images/icon_pending.png\" class=\"image16\" title=\"Pending - this message has not been accepted by the mailing list yet\"/>\r\n" );
+
+		}
+	}
+
+	public static void errorMessage(HttpServletRequest request,HttpServletResponse response,String errorMsg,String prompt)
+		throws ServletException, IOException
+	{
+		if( errorMsg==null )
+			return;
+		PrintWriter out = response.getWriter();
+		errorMsg = HtmlUtils.htmlEncode(errorMsg);
+		int posLinkOpen = errorMsg.indexOf("[link]");
+		if (posLinkOpen >= 0) {
+			int posLinkClose = errorMsg.indexOf("[/link]", posLinkOpen);
+			String url = errorMsg.substring(posLinkOpen+6, posLinkClose);
+			errorMsg = errorMsg.substring(0, posLinkOpen) + "<a href=\"" + url + "\">" + url + "</a>" + errorMsg.substring(posLinkClose+7);
+		}
+		
+		out.print( "\r\n<table class=\"error-message\" style=\"width:100%\">\r\n	<tr>\r\n		<td style=\"padding: .4em; width: 40px;\">\r\n			<img src=\"/images/icon_alert.png\" class=\"image32\"/>\r\n		</td>\r\n		<td style=\"padding: .4em;\">\r\n			<strong>" );
+		out.print( (errorMsg) );
+		out.print( "</strong>\r\n			" );
+		out.print( (prompt == null? "" : "<div style=\"padding-top:.3em\">" + prompt + "</div>") );
+		out.print( "\r\n		</td>\r\n	</tr>\r\n</table>\r\n" );
+
+	}
+
+	public static void title(HttpServletRequest request,HttpServletResponse response,String s)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		Site site = Jtp.getSite(request);
+		
+		out.print( "\r\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />\r\n<title>" );
+		out.print( (site == null?"Nabble":site.getRootNode().getSubjectHtml()) );
+		out.print( " - " );
+		out.print( (s) );
+		out.print( "</title>\r\n" );
+
+		head(request,response);
+	}
+
+	private static void loadAnalytics(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		HtmlViewUtils.googleAnalytics(out);
+	}
+
+	public static void analytics(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<script type=\"text/javascript\">\r\n	if (Nabble.analytics) Nabble.analytics();\r\n</script>\r\n" );
+
+	}
+
+	public static void postInThreadLink(HttpServletRequest request, PrintWriter out, Node post)
+		throws ServletException, IOException
+	{
+		String topicSubject = Jtp.breakUp(post.getTopic().getSubjectHtml());
+		String postSubject = Jtp.breakUp(post.getSubjectHtml());
+		if( postSubject.endsWith(topicSubject) ) {
+			
+		out.print( "<a href=\"" );
+		out.print( (Jtp.path(post)) );
+		out.print( "\">" );
+		out.print( (postSubject) );
+		out.print( "</a>" );
+
+		} else {
+			
+		out.print( (topicSubject) );
+		out.print( "</a> &nbsp; (<a href=\"" );
+		out.print( (Jtp.path(post)) );
+		out.print( "\">" );
+		out.print( (postSubject) );
+		out.print( "</a>)" );
+
+		}
+	}
+
+	public static void profileHeading(HttpServletRequest request,PrintWriter out,User user,String heading)
+	{
+		
+		out.print( "\r\n<h1>" );
+		out.print( (heading) );
+		out.print( "</h1>\r\n" );
+
+	}
+
+	public static void returnToJs(HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		returnToJs(null, request, response);
+	}
+
+	public static void returnToJs(Node node, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		if (node == null) {
+			Site site = Jtp.getSite(request);
+			if (site != null && site.getRootNode().getKind() == Node.Kind.APP)
+				node = site.getRootNode();
+		}
+		
+		out.print( "\r\n<div id=\"return-link\" style=\"margin-top:1em\">\r\n	" );
+ if (node != null) { 
+		out.print( "\r\n	&laquo;\r\n	<a href=\"" );
+		out.print( (Jtp.path(node)) );
+		out.print( "\">Return to " );
+		out.print( (node.getSubject()) );
+		out.print( "</a>\r\n	" );
+ } 
+		out.print( "\r\n</div>\r\n" );
+
+	}
+
+	public static void framedCss(PrintWriter out)
+	{
+		
+		out.print( "\r\n<style>\r\nbody {\r\n	margin: .8em;\r\n}\r\n</style>\r\n" );
+
+	}
+
+	public static void embeddedRedirect(HttpServletRequest request, PrintWriter out, Node forum)
+		throws IOException
+	{
+		// The embedding URL is stored in the node.embedding_url field.
+		String embeddingUrl = null;
+		for(
+			Node node = forum;
+			embeddingUrl == null && node != null;
+			node = node.getParent()
+		) {
+			embeddingUrl = node.getEmbeddingUrl();
+		}
+
+		if (embeddingUrl == null)
+			return;
+		
+		out.print( "\r\n<script type=\"text/javascript\">\r\n	if (!Nabble.isEmbedded) {\r\n		var url = top.location.href;\r\n		var cidPos = url.indexOf(';cid=');\r\n		if (cidPos > 0) {\r\n			url = url.substring(0, cidPos);\r\n		}\r\n\r\n		var posHtml = url.lastIndexOf('.html');\r\n		var hash;\r\n		" );
+
+				/*
+					If the URL is mapped, we extract the last
+					part, which gives information about the item.
+					For example:
+					https://www.nabble.com/text-p100.html
+					We extract "p100", because we know how to
+					rebuild this URL with this information.
+
+					See JsEmbed.jtp for more details on how to
+					rebuild the URL.
+				*/
+				
+		out.print( "\r\nif (url.indexOf('?') == -1 && posHtml > 0) {\r\n	var posStart = url.lastIndexOf('-');\r\n	hash = (posStart > 0)? url.substring(posStart, posHtml):'';\r\n} else {\r\n	" );
+
+					/*
+						If the URL is NOT mapped, we have to extract
+						the information after the domain.
+						For example:
+						  - http://localhost/forum/NewTopic.jtp?forum=2
+						We extract "forum/NewTopic.jtp?forum=2".
+					*/
+					
+		out.print( "\r\nhash = url.replace('http://', '');\r\nhash = hash.substring(hash.indexOf('/')+1);\r\nhash = '+' + hash;\r\n}\r\n" );
+
+				/*
+					In order to restore the current hash, I append it to the
+					same string after a pipe separator. This will be restored
+					in the Nabble.getCurrentUrl() function in JsEmbed.jtp.
+				*/
+				
+		out.print( "\r\nif (top.location.hash) {\r\n	hash += '|' + top.location.hash.substring(1);\r\n}\r\nif (Nabble.analytics) Nabble.analytics();\r\ntop.location.replace('" );
+		out.print( (embeddingUrl) );
+		out.print( "' + (hash.length==1?'':'#nabble' + encodeURIComponent(hash)));\r\n}\r\n</script>\r\n" );
+
+	}
+
+	public static String getAvatarImageURL(Person visitor, boolean smallImage) {
+		String imageName = smallImage? ModelHome.AVATAR_SMALL : ModelHome.AVATAR_BIG;
+		String defaultImage = "/images/" + imageName;
+		if( visitor instanceof User ) {
+			User user = (User)visitor;
+			if( user.hasAvatar() )
+				return "/file/a" + user.getId() + '/' + imageName;
+		}
+		return defaultImage;
+	}
+
+	public static String getDefaultAvatarImageURL(boolean smallImage) {
+		String imageName = smallImage? ModelHome.AVATAR_SMALL : ModelHome.AVATAR_BIG;
+		return "/images/" + imageName;
+	}
+
+	public static String simpleBannedMessage(Node app) {
+		StringBuilder builder = new StringBuilder();
+		builder
+			.append("Sorry, but the administrators of this ")
+			.append(Jtp.viewName(app).toLowerCase())
+			.append(" have banned you.\n ");
+		return builder.toString();
+	}
+
+	public static String bannedMessage(Node app, boolean isPost) {
+		StringBuilder builder = new StringBuilder();
+		builder.append(simpleBannedMessage(app));
+		if (isPost)
+			builder.append("You can't post a message here, but you can post in other places.");
+		else
+			builder.append("You can't create a forum here, but you can create in other places.");
+		return builder.toString();
+	}
+
+
+	public static void simplePage(String pageTitle, Node node, String messageTitle, String message, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<META NAME=\"robots\" CONTENT=\"noindex,nofollow\">\r\n		" );
+ title(request, response, pageTitle); 
+		out.print( "\r\n		<style type=\"text/css\">\r\n			div.main-title {\r\n				font-size:120%;\r\n				font-weight:bold;\r\n				margin:1em 0;\r\n				padding: .2em;\r\n			}\r\n		</style>\r\n		<script type=\"text/javascript\">\r\n			" );
+/* here I set the next url for the login UI */
+		out.print( "\r\n			var loginNextUrl = '" );
+		out.print( (node==null?"/":Jtp.url(node)) );
+		out.print( "';\r\n		</script>\r\n	</head>\r\n	<body>\r\n		" );
+
+					if (node == null)
+						minHeaderGlobal(request, response);
+					else
+						minHeader(request, response, node);
+				
+		out.print( "\r\n<div class=\"shaded-bg-color rounded second-font main-title\">\r\n	" );
+		out.print( (messageTitle) );
+		out.print( "\r\n</div>\r\n" );
+		out.print( (message) );
+		out.print( "\r\n" );
+ if (node != null) { 
+		out.print( "\r\n<br><br>&laquo; <a href=\"" );
+		out.print( (Jtp.url(node)) );
+		out.print( "\">Back to " );
+		out.print( (node.getSubjectHtml()) );
+		out.print( "</a>\r\n" );
+ } else { 
+		out.print( "\r\n<br>\r\n" );
+ } 
+		out.print( "\r\n<br/><br/>\r\n" );
+ footer(request,response); 
+		out.print( "\r\n" );
+ analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+
+	public static void canonical(HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		String canonical = Jtp.getCanonicalUrl(request);
+		if (canonical != null) {
+			PrintWriter out = response.getWriter();
+			
+		out.print( "\r\n<link rel=\"canonical\" href=\"" );
+		out.print( (canonical) );
+		out.print( "\" />\r\n" );
+
+		}
+	}
+
+	/** (To be used by scripts)
+	 * http://tablesorter.com/docs/
+	 */
+	public static void tableSorter(PrintWriter out, String sortList)
+		throws IOException, ServletException
+	{
+		
+		out.print( "\r\n<link rel=\"stylesheet\" href=\"/util/tablesorter/style.css\" type=\"text/css\" />\r\n<script src=\"/util/tablesorter/jquery.tablesorter.min.js\" type=\"text/javascript\"></script>\r\n<script type=\"text/javascript\">\r\n	$(document).ready(function() {\r\n		$(\"table.tablesorter\").tablesorter(" );
+		out.print( (sortList) );
+		out.print( ");\r\n	});\r\n</script>\r\n" );
+
+	}
+
+	/** (To be used by scripts) */
+	public static void countTableRows(PrintWriter out)
+		throws IOException, ServletException
+	{
+		
+		out.print( "\r\n<div id=\"rows\"></div>\r\n<script type=\"text/javascript\">\r\n	$(document).ready(function() {\r\n		var count = $('tbody tr').size();\r\n		$(\"#rows\").html(count + \" rows\");\r\n	});\r\n</script>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/Shared.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,652 @@
+<%
+package nabble.view.lib;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.java.MD5Util;
+import nabble.model.Init;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.model.User;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public final class Shared {
+    private static final Logger logger = LoggerFactory.getLogger(Shared.class);
+
+	private Shared() {}  // never
+
+	public static volatile int javascriptVersion = 102;
+
+	public static final int cssVersion = 29;
+
+	public static String getCssPath() {
+		return "/nabble.css?v="+cssVersion;
+	}
+
+	public static String getJavascriptPath(HttpServletRequest request) {
+		return Jtp.getSite(request) == null? "/Javascript.jtp" : "/template/NamlServlet.jtp?macro=javascript_library";
+	}
+
+	public static String getTabsPath() {
+		return "/util/nabbletabs.js";
+	}
+
+	public static String getJQueryPath() {
+		return "/util/jquery-1.7.2.pack.js";
+	}
+
+	public static String getDropdownJsPath() {
+		return "/util/nabbledropdown-2.4.1.js";
+	}
+
+	public static void head(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		head(request, response, true);
+	}
+
+	public static void head(HttpServletRequest request,HttpServletResponse response, boolean useSiteStyle)
+		throws IOException
+	{
+		if( request.getAttribute("head") != null )
+			return;
+		request.setAttribute("head","x");
+		PrintWriter out = response.getWriter();
+		Site site = Jtp.getSite(request);
+		boolean isApp = site != null && site.getRootNode().getKind() == Node.Kind.APP;
+		%>
+		<link rel="stylesheet" href="<%=Shared.getCssPath()%>" type="text/css" />
+		<% if (isApp && useSiteStyle) { %>
+		<link rel="stylesheet" href="/template/NamlServlet.jtp?macro=site_style" type="text/css" />
+		<% } %>
+		<% loadJavascript(request, out); %>
+		<% if (isApp) { %>
+		<script type="text/javascript">
+			Nabble.setFontSize();
+		</script>
+		<% } %>
+		<%
+		loadAnalytics(request,response);
+	}
+
+	public static void loadJavascript(HttpServletRequest request, PrintWriter out) {
+		%>
+		<script src="<%=getJQueryPath()%>" type="text/javascript"></script>
+		<script src="<%=getDropdownJsPath()%>" type="text/javascript"></script>
+		<script src="<%=getJavascriptPath(request)%>" type="text/javascript"></script>
+		<%
+	}
+
+	private static void checkHead(HttpServletRequest request) {
+		if( request.getAttribute("head") == null )
+			throw new RuntimeException("Shared.head not called");
+	}
+
+	public static void minHeader(HttpServletRequest request,HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		minHeader(request,response,null);
+	}
+
+	public static void minHeader(HttpServletRequest request,HttpServletResponse response,Node node)
+		throws IOException, ServletException
+	{
+		minHeader(request, response, node, null);
+	}
+
+	public static void minHeaderGlobal(HttpServletRequest request,HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		Site site = Jtp.getSite(request);
+		if (site == null) {
+			String homepageLink = "<a id=\"homelink\" href=\"" + Jtp.homePage() + "\">Nabble</a>";
+			minHeader(request, response, null, new String[] { homepageLink });
+			PrintWriter out = response.getWriter();
+			%>
+			<script type="text/javascript">
+				if (Nabble.getParent().nabbleinfo && Nabble.getParent().nabbleinfo.what) {
+					var home = Nabble.get('homelink');
+					home.setAttribute('href', '/');
+					home.innerHTML='Back to ' + Nabble.getParent().nabbleinfo.what;
+				}
+			</script>
+			<%
+		} else {
+			Node node = site.getRootNode();
+			String homepageLink = "<a href=\"" + Jtp.url(node) + "\">" + node.getSubjectHtml() + "</a>";
+			minHeader(request, response, null, new String[] { homepageLink });
+		}
+	}
+
+	public static void minHeader(HttpServletRequest request, HttpServletResponse response, Node node, String[] breadcrumbLinks)
+		throws IOException, ServletException
+	{
+		minHeader(request, response, node, breadcrumbLinks, true);
+	}
+
+	public static void minHeader(HttpServletRequest request, HttpServletResponse response, Node node, String[] breadcrumbLinks, boolean embeddedRedirect)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		checkHead(request);
+		Node app = node==null ? null : node.getApp();
+		if (embeddedRedirect)
+			embeddedRedirect(request, out, app);
+		%>
+		<div id="notice" class="notice rounded-bottom"></div>
+		<div class="nabble" id="nabble">
+			<div class="top-bar">
+				<div class="breadcrumbs" style="float:left;">
+					<%
+					if (app != null)
+						breadcrumb(request,out,app);
+					else if (breadcrumbLinks != null)
+						breadcrumb(out,breadcrumbLinks);
+					%>
+				</div>
+				<div style="text-align:right;">
+					<span style="white-space:nowrap;" id="nabble-user-header"></span>
+					<script type="text/javascript">Nabble.userHeader();</script>
+				</div>
+			</div>
+			<div style="clear:both;"></div>
+			<%
+			if (request.getAttribute("search") == null) {
+				%>
+				<script type="text/javascript">
+					Nabble.deleteCookie("query");
+					Nabble.deleteCookie("searchuser");
+					Nabble.deleteCookie("searchterms");
+				</script>
+				<%
+			}
+	}
+
+	public static void noHeader(HttpServletRequest request,HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<div class="nabble" id="nabble">
+		<%
+	}
+
+	public static void editHeader(String firstText, String secondText, PrintWriter out) {
+		%>
+		<div class="second-font shaded-bg-color" style="font-size: 120%;padding: .4em;font-weight:bold">
+			<%=firstText%> <span class="weak-color"><%=secondText == null? "" : "&ndash; " + secondText%></span>
+		</div>
+		<%
+	}
+
+	public static void helpHeader(HttpServletRequest request,HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		Site site = Jtp.getSite(request);
+		String homepageLink = "<a href=\"/\">" + (site == null? "Nabble":site.getRootNode().getSubjectHtml()) + "</a>";
+		String helpLink = "<a href=\"/help/Index.jtp\">Help</a>";
+		minHeader(request, response, null, new String[] { homepageLink, helpLink });
+	}
+
+	public static void footer(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		Site site = Jtp.getSite(request);
+		if( site == null ) {
+			%>
+			<table class="footer-table shaded-bg-color">
+				<tr>
+					<td class="footer-left">
+						<a href="<%=Jtp.homePage()%>" target="_top">Free Forum</a> by Nabble
+					</td>
+				</tr>
+			</table>
+			<%
+		} else {
+			%>
+			<table class="footer-table shaded-bg-color">
+				<tr>
+					<td class="footer-left">
+						<a href="<%=Jtp.homePage()%>" target="_top">Free Forum</a>
+						by Nabble
+					</td>
+					<td class="footer-right">
+						<a href="<%=Jtp.helpIndexUrl(request,response)%>">Help</a>
+					</td>
+				</tr>
+			</table>
+			<%
+		}
+		%>
+		</div>
+		<%
+	}
+
+	public static void noFooter(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		</div>
+		<%
+	}
+
+	public static void breadcrumb(HttpServletRequest request,PrintWriter out,Node node)
+		throws ServletException, IOException
+	{
+		List<String> links = new ArrayList<String>();
+		node = node.getApp();
+		while (node != null) {
+			links.add(0, "<a href=\"" + Jtp.path(node) + "\">" + node.getSubjectHtml() + "</a>");
+			node = node.getParent();
+		}
+		String[] array = new String[links.size()];
+		links.toArray(array);
+		breadcrumb(out, array);
+	}
+
+	private static void breadcrumb(PrintWriter out, String[] links)
+		throws ServletException, IOException
+	{
+		%><span class="weak-color" style="white-space:nowrap"><%
+		for (String s : links) {
+			%><%=s%><%
+			boolean isLastString = s.equals(links[links.length-1]);
+			if (!isLastString) {
+				%>&nbsp;&rsaquo;&nbsp;<%
+			}
+		}
+		%></span><%
+	}
+
+	public static void javascriptRedirect(HttpServletRequest request,HttpServletResponse response,String url)
+		throws IOException, ServletException
+	{
+		javascriptRedirect(request,response,url,null);
+	}
+
+	public static void javascriptRedirect(HttpServletRequest request,HttpServletResponse response,String url,String script)
+		throws IOException, ServletException
+	{
+		javascriptRedirect(request, response, url, script, false);
+	}
+
+	public static void javascriptRedirect(HttpServletRequest request,HttpServletResponse response,String url,String script, boolean leaveEmbedding)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+		<head>
+			<script src="<%=getJQueryPath()%>" type="text/javascript"></script>
+			<script src="<%=getJavascriptPath(request)%>" type="text/javascript"></script>
+			<% loadAnalytics(request,response); %>
+			<script type="text/javascript">
+				<%=Jtp.hideNull(script)%>
+				var url = "<%=HtmlUtils.javascriptStringEncode(url)%>";
+
+				<%=leaveEmbedding? "top." : ""%>location.replace(url);
+			</script>
+		</head>
+		</html>
+		<%
+	}
+
+	/* Rebuilds the embedding iframe and reload the contents.*/
+	public static void restartEmbedding(Node node, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<script src="<%=getJQueryPath()%>" type="text/javascript"></script>
+				<script src="<%=getJavascriptPath(request)%>" type="text/javascript"></script>
+				<script type="text/javascript">
+					Nabble.restartEmbedding(<%=node.getId()%>,'<%=node.getSite().getBaseUrl()%>');
+				</script>
+			</head>
+		</html>
+		<%
+	}
+
+	public static void showPending(PrintWriter out, Node post) {
+		Node.MailToList mail = post.getMailToList();
+		if (mail == null || !(post.getOwner() instanceof User))
+			return;
+		showPending(out, mail.isPending());
+	}
+
+    public static void showPending(PrintWriter out, boolean isPending) {
+		if (isPending) {
+			%>
+			<img src="/images/icon_pending.png" class="image16" title="Pending - this message has not been accepted by the mailing list yet"/>
+			<%
+		}
+	}
+
+	public static void errorMessage(HttpServletRequest request,HttpServletResponse response,String errorMsg,String prompt)
+		throws ServletException, IOException
+	{
+		if( errorMsg==null )
+			return;
+		PrintWriter out = response.getWriter();
+		errorMsg = HtmlUtils.htmlEncode(errorMsg);
+		int posLinkOpen = errorMsg.indexOf("[link]");
+		if (posLinkOpen >= 0) {
+			int posLinkClose = errorMsg.indexOf("[/link]", posLinkOpen);
+			String url = errorMsg.substring(posLinkOpen+6, posLinkClose);
+			errorMsg = errorMsg.substring(0, posLinkOpen) + "<a href=\"" + url + "\">" + url + "</a>" + errorMsg.substring(posLinkClose+7);
+		}
+		%>
+		<table class="error-message" style="width:100%">
+			<tr>
+				<td style="padding: .4em; width: 40px;">
+					<img src="/images/icon_alert.png" class="image32"/>
+				</td>
+				<td style="padding: .4em;">
+					<strong><%=errorMsg%></strong>
+					<%=prompt == null? "" : "<div style=\"padding-top:.3em\">" + prompt + "</div>"%>
+				</td>
+			</tr>
+		</table>
+		<%
+	}
+
+	public static void title(HttpServletRequest request,HttpServletResponse response,String s)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		Site site = Jtp.getSite(request);
+		%>
+		<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+		<title><%=site == null?"Nabble":site.getRootNode().getSubjectHtml()%> - <%=s%></title>
+		<%
+		head(request,response);
+	}
+
+	private static void loadAnalytics(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		HtmlViewUtils.googleAnalytics(out);
+	}
+
+	public static void analytics(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<script type="text/javascript">
+			if (Nabble.analytics) Nabble.analytics();
+		</script>
+		<%
+	}
+
+	public static void postInThreadLink(HttpServletRequest request, PrintWriter out, Node post)
+		throws ServletException, IOException
+	{
+		String topicSubject = Jtp.breakUp(post.getTopic().getSubjectHtml());
+		String postSubject = Jtp.breakUp(post.getSubjectHtml());
+		if( postSubject.endsWith(topicSubject) ) {
+			%><a href="<%=Jtp.path(post)%>"><%=postSubject%></a><%
+		} else {
+			%><%=topicSubject%></a> &nbsp; (<a href="<%=Jtp.path(post)%>"><%=postSubject%></a>)<%
+		}
+	}
+
+	public static void profileHeading(HttpServletRequest request,PrintWriter out,User user,String heading)
+	{
+		%>
+		<h1><%=heading%></h1>
+		<%
+	}
+
+	public static void returnToJs(HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		returnToJs(null, request, response);
+	}
+
+	public static void returnToJs(Node node, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		if (node == null) {
+			Site site = Jtp.getSite(request);
+			if (site != null && site.getRootNode().getKind() == Node.Kind.APP)
+				node = site.getRootNode();
+		}
+		%>
+		<div id="return-link" style="margin-top:1em">
+			<% if (node != null) { %>
+			&laquo;
+			<a href="<%=Jtp.path(node)%>">Return to <%=node.getSubject()%></a>
+			<% } %>
+		</div>
+		<%
+	}
+
+	public static void framedCss(PrintWriter out)
+	{
+		%>
+		<style>
+		body {
+			margin: .8em;
+		}
+		</style>
+		<%
+	}
+
+	public static void embeddedRedirect(HttpServletRequest request, PrintWriter out, Node forum)
+		throws IOException
+	{
+		// The embedding URL is stored in the node.embedding_url field.
+		String embeddingUrl = null;
+		for(
+			Node node = forum;
+			embeddingUrl == null && node != null;
+			node = node.getParent()
+		) {
+			embeddingUrl = node.getEmbeddingUrl();
+		}
+
+		if (embeddingUrl == null)
+			return;
+		%>
+		<script type="text/javascript">
+			if (!Nabble.isEmbedded) {
+				var url = top.location.href;
+				var cidPos = url.indexOf(';cid=');
+				if (cidPos > 0) {
+					url = url.substring(0, cidPos);
+				}
+
+				var posHtml = url.lastIndexOf('.html');
+				var hash;
+				<%
+				/*
+					If the URL is mapped, we extract the last
+					part, which gives information about the item.
+					For example:
+					https://www.nabble.com/text-p100.html
+					We extract "p100", because we know how to
+					rebuild this URL with this information.
+
+					See JsEmbed.jtp for more details on how to
+					rebuild the URL.
+				*/
+				%>
+				if (url.indexOf('?') == -1 && posHtml > 0) {
+					var posStart = url.lastIndexOf('-');
+					hash = (posStart > 0)? url.substring(posStart, posHtml):'';
+				} else {
+					<%
+					/*
+						If the URL is NOT mapped, we have to extract
+						the information after the domain.
+						For example:
+						  - http://localhost/forum/NewTopic.jtp?forum=2
+						We extract "forum/NewTopic.jtp?forum=2".
+					*/
+					%>
+					hash = url.replace('http://', '');
+					hash = hash.substring(hash.indexOf('/')+1);
+					hash = '+' + hash;
+				}
+				<%
+				/*
+					In order to restore the current hash, I append it to the
+					same string after a pipe separator. This will be restored
+					in the Nabble.getCurrentUrl() function in JsEmbed.jtp.
+				*/
+				%>
+				if (top.location.hash) {
+					hash += '|' + top.location.hash.substring(1);
+				}
+				if (Nabble.analytics) Nabble.analytics();
+				top.location.replace('<%=embeddingUrl%>' + (hash.length==1?'':'#nabble' + encodeURIComponent(hash)));
+			}
+		</script>
+		<%
+	}
+
+	public static String getAvatarImageURL(Person visitor, boolean smallImage) {
+		String imageName = smallImage? ModelHome.AVATAR_SMALL : ModelHome.AVATAR_BIG;
+		String defaultImage = "/images/" + imageName;
+		if( visitor instanceof User ) {
+			User user = (User)visitor;
+			if( user.hasAvatar() )
+				return "/file/a" + user.getId() + '/' + imageName;
+		}
+		return defaultImage;
+	}
+
+	public static String getDefaultAvatarImageURL(boolean smallImage) {
+		String imageName = smallImage? ModelHome.AVATAR_SMALL : ModelHome.AVATAR_BIG;
+		return "/images/" + imageName;
+	}
+
+	public static String simpleBannedMessage(Node app) {
+		StringBuilder builder = new StringBuilder();
+		builder
+			.append("Sorry, but the administrators of this ")
+			.append(Jtp.viewName(app).toLowerCase())
+			.append(" have banned you.\n ");
+		return builder.toString();
+	}
+
+	public static String bannedMessage(Node app, boolean isPost) {
+		StringBuilder builder = new StringBuilder();
+		builder.append(simpleBannedMessage(app));
+		if (isPost)
+			builder.append("You can't post a message here, but you can post in other places.");
+		else
+			builder.append("You can't create a forum here, but you can create in other places.");
+		return builder.toString();
+	}
+
+
+	public static void simplePage(String pageTitle, Node node, String messageTitle, String message, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow">
+				<% title(request, response, pageTitle); %>
+				<style type="text/css">
+					div.main-title {
+						font-size:120%;
+						font-weight:bold;
+						margin:1em 0;
+						padding: .2em;
+					}
+				</style>
+				<script type="text/javascript">
+					<%/* here I set the next url for the login UI */%>
+					var loginNextUrl = '<%=node==null?"/":Jtp.url(node)%>';
+				</script>
+			</head>
+			<body>
+				<%
+					if (node == null)
+						minHeaderGlobal(request, response);
+					else
+						minHeader(request, response, node);
+				%>
+				<div class="shaded-bg-color rounded second-font main-title">
+					<%=messageTitle%>
+				</div>
+				<%=message%>
+				<% if (node != null) { %>
+				<br><br>&laquo; <a href="<%=Jtp.url(node)%>">Back to <%=node.getSubjectHtml()%></a>
+				<% } else { %>
+				<br>
+				<% } %>
+				<br/><br/>
+				<% footer(request,response); %>
+				<% analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+
+	public static void canonical(HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		String canonical = Jtp.getCanonicalUrl(request);
+		if (canonical != null) {
+			PrintWriter out = response.getWriter();
+			%>
+			<link rel="canonical" href="<%=canonical%>" />
+			<%
+		}
+	}
+
+	/** (To be used by scripts)
+	 * http://tablesorter.com/docs/
+	 */
+	public static void tableSorter(PrintWriter out, String sortList)
+		throws IOException, ServletException
+	{
+		%>
+		<link rel="stylesheet" href="/util/tablesorter/style.css" type="text/css" />
+		<script src="/util/tablesorter/jquery.tablesorter.min.js" type="text/javascript"></script>
+		<script type="text/javascript">
+			$(document).ready(function() {
+				$("table.tablesorter").tablesorter(<%=sortList%>);
+			});
+		</script>
+		<%
+	}
+
+	/** (To be used by scripts) */
+	public static void countTableRows(PrintWriter out)
+		throws IOException, ServletException
+	{
+		%>
+		<div id="rows"></div>
+		<script type="text/javascript">
+			$(document).ready(function() {
+				var count = $('tbody tr').size();
+				$("#rows").html(count + " rows");
+			});
+		</script>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/SiteDeleteMail.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,65 @@
+
+package nabble.view.lib;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.model.ModelHome;
+import nabble.view.lib.help.Help;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+
+public final class SiteDeleteMail {
+
+    public static void send(User user,Site site,int days) {
+		StringWriter buf = new StringWriter();
+		PrintWriter out = new PrintWriter(buf);
+		Node rootNode = site.getRootNode();
+		String what = rootNode.getKind()==Node.Kind.APP ? "forum" : "thread";
+		
+		out.print( "\r\nDear Nabble user,\r\n\r\nThe " );
+		out.print( (what) );
+		out.print( " listed below is inactive and has been scheduled for deletion in " );
+		out.print( (days) );
+		out.print( " days.\r\nTo prevent this " );
+		out.print( (what) );
+		out.print( " from being deleted, visit the " );
+		out.print( (what) );
+		out.print( " and follow the instructions.\r\n\r\n" );
+		out.print( (Jtp.url(rootNode)) );
+		out.print( "\r\n\r\nIf this " );
+		out.print( (what) );
+		out.print( " is deleted, your posts in this " );
+		out.print( (what) );
+		out.print( " will be deleted.\r\nYou can see your posts here:\r\n\r\n" );
+		out.print( (site.getBaseUrl()) );
+		out.print( "/template/NamlServlet.jtp?macro=user_nodes&user=" );
+		out.print( (user.getId()) );
+		out.print( "\r\n\r\n** For more information, see:\r\n" );
+		out.print( (Help.inactivity_deletion.url()) );
+		out.print( "\r\n\r\nRegards,\r\nThe Nabble team\r\n" );
+
+		out.flush();
+		String text = buf.toString();
+
+		Mail mail = MailHome.newMail();
+		mail.setFrom( new MailAddress(ModelHome.noReply,"Nabble") );
+		mail.setTo( new MailAddress(user.getEmail()) );
+		mail.setSubject("Nabble "+what+" scheduled for deletion");
+		mail.setSentDate(new Date());
+		mail.setContent(new PlainTextContent(text));
+		MailHome.getDefaultSmtpServer().sendFrom(mail, "bounces+deletion@n2.nabble.com");
+	}
+
+	private SiteDeleteMail() {}  // never
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/SiteDeleteMail.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,61 @@
+<%
+package nabble.view.lib;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.model.ModelHome;
+import nabble.view.lib.help.Help;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+
+public final class SiteDeleteMail {
+
+    public static void send(User user,Site site,int days) {
+		StringWriter buf = new StringWriter();
+		PrintWriter out = new PrintWriter(buf);
+		Node rootNode = site.getRootNode();
+		String what = rootNode.getKind()==Node.Kind.APP ? "forum" : "thread";
+		%>
+		Dear Nabble user,
+
+		The <%=what%> listed below is inactive and has been scheduled for deletion in <%=days%> days.
+		To prevent this <%=what%> from being deleted, visit the <%=what%> and follow the instructions.
+
+		<%=Jtp.url(rootNode)%>
+
+		If this <%=what%> is deleted, your posts in this <%=what%> will be deleted.
+		You can see your posts here:
+
+		<%=site.getBaseUrl()%>/template/NamlServlet.jtp?macro=user_nodes&user=<%=user.getId()%>
+
+		** For more information, see:
+		<%=Help.inactivity_deletion.url()%>
+
+		Regards,
+		The Nabble team
+		<%
+		out.flush();
+		String text = buf.toString();
+
+		Mail mail = MailHome.newMail();
+		mail.setFrom( new MailAddress(ModelHome.noReply,"Nabble") );
+		mail.setTo( new MailAddress(user.getEmail()) );
+		mail.setSubject("Nabble "+what+" scheduled for deletion");
+		mail.setSentDate(new Date());
+		mail.setContent(new PlainTextContent(text));
+		MailHome.getDefaultSmtpServer().sendFrom(mail, "bounces+deletion@n2.nabble.com");
+	}
+
+	private SiteDeleteMail() {}  // never
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/SubscribeDefaultsMail.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,43 @@
+
+package nabble.view.lib;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Date;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+
+
+public final class SubscribeDefaultsMail {
+
+	public static Mail newMail(String email, MailingList mailingList, String url) {
+		StringWriter content = new StringWriter();
+		PrintWriter out = new PrintWriter(content);
+		
+		out.print( "\nDear Nabble user:\n\nAfter your subscription request to " );
+		out.print( (mailingList.getListAddress()) );
+		out.print( " has been accepted,\nplease follow the link below to turn off list mail delivery to your email address:\n\n" );
+		out.print( (url) );
+		out.print( "\n\nRegards,\nThe Nabble Team\n" );
+
+		out.close();
+		try {
+			Mail mail = MailHome.newMail();
+			mail.setFrom( new MailAddress(ModelHome.noReply) );
+			mail.setTo( new MailAddress(email) );
+			mail.setSubject( "Mailing list subscription settings" );
+			mail.setContent( new PlainTextContent(content.toString()) );
+			mail.setSentDate( new Date() );
+			return mail;
+		} catch(MailException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/SubscribeDefaultsMail.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,47 @@
+<%
+package nabble.view.lib;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Date;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+
+
+public final class SubscribeDefaultsMail {
+
+	public static Mail newMail(String email, MailingList mailingList, String url) {
+		StringWriter content = new StringWriter();
+		PrintWriter out = new PrintWriter(content);
+		%>
+		Dear Nabble user:
+		
+		After your subscription request to <%=mailingList.getListAddress()%> has been accepted,
+		please follow the link below to turn off list mail delivery to your email address:
+		
+		<%=url%>
+		
+		Regards,
+		The Nabble Team
+		<%
+		out.close();
+		try {
+			Mail mail = MailHome.newMail();
+			mail.setFrom( new MailAddress(ModelHome.noReply) );
+			mail.setTo( new MailAddress(email) );
+			mail.setSubject( "Mailing list subscription settings" );
+			mail.setContent( new PlainTextContent(content.toString()) );
+			mail.setSentDate( new Date() );
+			return mail;
+		} catch(MailException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/Test.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,163 @@
+package nabble.view.lib;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.Statement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.ListIterator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.IoUtils;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.model.Db;
+import nabble.model.User;
+import nabble.model.ModelException;
+import nabble.model.Message;
+import nabble.model.Node;
+
+
+public final class Test {
+	private Test() {}  // never
+
+	private static final Logger logger = LoggerFactory.getLogger(Test.class);
+
+	public static interface Task {
+		public void run(Site site) throws Exception;
+	}
+
+	private static final String PREFEX = "test-";
+	private static final List<Site> sites = new ArrayList<Site>();
+	private static final List<Thread> threads = new ArrayList<Thread>();
+/*
+	static {
+		try {
+			Connection con = Db.dbGlobal().getConnection();
+			Statement stmt = con.createStatement();
+			ResultSet rs = stmt.executeQuery(
+				"select site_id from site_global"
+			);
+			while( rs.next() ) {
+				long siteId = rs.getLong("site_id");
+				Site site = ModelHome.getSite(siteId);
+				String name = name(site);
+				if( name.startsWith(PREFEX) )
+					sites.add(site);
+			}
+			rs.close();
+			stmt.close();
+			con.close();
+		} catch(SQLException e) {
+			logger.error("init",e);
+			System.exit(-1);
+		}
+	}
+*/
+	public static void delete() {
+		checkThreads();
+		for( Site site : sites ) {
+			site.getRootNode().deleteRecursively();
+		}
+		sites.clear();
+		logger.info("sites deleted");
+	}
+
+	public static void populate(int n,String email) {
+		if( !sites.isEmpty() )
+			throw new RuntimeException("sites not empty");
+		DbDatabase db = Db.dbGlobal();
+		for( int i=1; i<=n; i++ ) {
+			logger.info("creating site "+i);
+			db.beginTransaction();
+			try {
+				Site site = ModelHome.newSite( "forum", PREFEX+i, "", Message.Format.TEXT, email, "tester" );
+				Permissions.addToGroup( (User)site.getRootNode().getOwner(), Permissions.ADMINISTRATORS_GROUP );
+				String key = site.newRegistration(email,"tester","tester","/");
+				User user = (User)site.getRootNode().getOwner();
+				user.setPassword("tester");
+				user.register();
+				user.update();
+				db.commitTransaction();
+				site = site.getGoodCopy();
+				sites.add(site);
+			} catch (ModelException e) {
+				throw new RuntimeException(e);
+			} finally {
+				db.endTransaction();
+			}
+		}
+		logger.info(""+n+" sites populated");
+	}
+
+	public static void getGoodCopies() {
+		for( ListIterator<Site> iter = sites.listIterator(); iter.hasNext(); ) {
+			iter.set( iter.next().getGoodCopy() );
+		}
+	}
+
+	private static void checkThreads() {
+		if( !threads.isEmpty() )
+			throw new RuntimeException("tasks running");
+	}
+
+	public static void run(final Task task) {
+		for( Site site : sites ) {
+			final Site taskSite = site;
+			Thread thread = new Thread(new Runnable(){public void run(){
+				try {
+					task.run(taskSite);
+				} catch(Exception e) {
+					logger.error("task failed for "+taskSite,e);
+				}
+			}});
+			thread.start();
+			threads.add(thread);
+		}
+	}
+
+	public static void waitForThreads() {
+		for( Thread thread : threads ) {
+			try {
+				thread.join();
+			} catch(InterruptedException e) {
+				throw new RuntimeException(e);
+			}
+		}
+		threads.clear();
+		logger.info("threads done");
+	}
+
+
+	// utils
+
+	public static String name(Site site) {
+		return site.getRootNode().getSubject();
+	}
+
+	public static Node newPost(Node parent,String subject) throws ModelException {
+		Node node;
+		DbDatabase db = parent.getSite().getDb();
+		db.beginTransaction();
+		try {
+			node = parent.getGoodCopy().getOwner().newChildNode(Node.Kind.POST,subject,"some message",Message.Format.TEXT,parent);
+			node.insert(false);
+			db.commitTransaction();
+		} finally {
+			db.endTransaction();
+		}
+		return node.getGoodCopy();
+	}
+
+	public static String readPage(Node node) throws IOException {
+		return IoUtils.readPage( Jtp.url(node) );
+	}
+
+	public static void clearPageCache() {
+		MyJtpServlet.getJtpContext().getHttpCache().clear();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/TopicView.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,7 @@
+package nabble.view.lib;
+
+public enum TopicView {
+	THREADED,
+	LIST,
+	CLASSIC
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/UrlMappable.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,11 @@
+package nabble.view.lib; 
+
+import java.util.Map;
+import java.util.regex.Pattern;
+import javax.servlet.http.HttpServletRequest;
+
+
+public interface UrlMappable {
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl);
+	public Pattern getUrlPattern();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/UrlMapperImpl.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,84 @@
+package nabble.view.lib;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Pattern;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import fschmidt.util.servlet.JtpContext;
+import fschmidt.util.servlet.UrlMapper;
+import fschmidt.util.servlet.UrlMapping;
+import nabble.view.web.template.UrlMapperNamespace;
+
+
+public final class UrlMapperImpl implements UrlMapper {
+
+	private static class RegexToClass {
+		private final Pattern ptn;
+		private final Class cls;
+
+		public RegexToClass(Class cls) {
+			if( !UrlMappable.class.isAssignableFrom(cls) )
+				throw new RuntimeException(cls.getName()+" doesn't implement UrlMappable");
+			this.cls = cls;
+			try {
+				this.ptn = ((UrlMappable)cls.newInstance()).getUrlPattern();
+			} catch(InstantiationException e) {
+				throw new RuntimeException(e);
+			} catch(IllegalAccessException e) {
+				throw new RuntimeException(e);
+			}
+		}
+	}
+
+	private final RegexToClass[] regexToClass;
+
+	UrlMapperImpl(final Class[] classes) {
+		RegexToClass[] regexToClass = new RegexToClass[classes.length];
+		for( int i=0; i<classes.length; i++ ) {
+			regexToClass[i] = new RegexToClass(classes[i]);
+		}
+		this.regexToClass = regexToClass;
+	}
+
+	public UrlMapping getUrlMapping(HttpServletRequest request) {
+		String url = request.getRequestURL().toString();
+/*
+		if( Jtp.getSite(request) == null && !url.startsWith(Jtp.defaultContextUrl()) )
+			return redir;
+*/
+		UrlMapping urlMapping = UrlMapperNamespace.getUrlMapping(request);
+		if( urlMapping != null )
+			return urlMapping;
+		int i = url.indexOf(';');
+		if( i != -1 )
+			url = url.substring(0,i);
+		for (RegexToClass rc : regexToClass) {
+			if (rc.ptn.matcher(url).find()) {
+				try {
+					UrlMappable ubm = (UrlMappable)rc.cls.newInstance();
+					Map<String,String[]> params = ubm.getParameterMapFromUrl(request,url);
+					return new UrlMapping(rc.cls,params);
+				} catch(IllegalAccessException e) {
+					throw new RuntimeException(e);
+				} catch(InstantiationException e) {
+					throw new RuntimeException(e);
+				}
+			}
+		}
+		return null;
+	}
+/*
+	public static class Redir extends HttpServlet {
+		protected void service(HttpServletRequest request,HttpServletResponse response)
+			throws IOException
+		{
+			response.setHeader("Location", "http://www.jmeter-archive.org/missing.html");
+			response.sendError(HttpServletResponse.SC_MOVED_PERMANENTLY);
+		}
+	}
+	private static final UrlMapping redir = new UrlMapping(Redir.class,Collections.emptyMap());
+*/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/ViewUtils.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,70 @@
+package nabble.view.lib;
+
+
+// used outside nabble, so do not use Init
+public final class ViewUtils {
+
+	private ViewUtils() {}  // never
+
+	private static char removeAccent(char c) {
+		int code = (int) c;
+		if (code >= 192 && code <= 196) return 'A';
+		else if (code >= 200 && code <= 203) return 'E';
+		else if (code >= 204 && code <= 207) return 'I';
+		else if (code >= 210 && code <= 214) return 'O';
+		else if (code >= 217 && code <= 220) return 'U';
+		else if (code >= 224 && code <= 228) return 'a';
+		else if (code >= 232 && code <= 235) return 'e';
+		else if (code >= 236 && code <= 239) return 'i';
+		else if (code >= 242 && code <= 246) return 'o';
+		else if (code >= 249 && code <= 252) return 'u';
+		else if (code == 231) return 'c';
+		else if (code == 199) return 'C';
+		else return c;
+	}
+
+	public static String subjectEncode(String s) {
+		StringBuilder builder = new StringBuilder();
+		final char[] chars = s.toCharArray();
+		char lastChar = '-';
+		for (char c : chars) {
+			c = removeAccent(c);
+			boolean isDigit = c >= '0' && c <= '9';
+			boolean isLowerChar = c >= 'a' && c <= 'z';
+			boolean isUpperChar = c >= 'A' && c <= 'Z';
+			if (isDigit || isLowerChar || isUpperChar) {
+				builder.append(c);
+				lastChar = c;
+			} else {
+				if (lastChar != '-' && builder.length() > 0)
+					builder.append('-');
+				lastChar = '-';
+			}
+		}
+		// Truncate if bigger than 100 chars
+		if (builder.length() > 100) {
+			builder.delete(100, builder.length()-1);
+		}
+
+		int lastPos = builder.length()-1;
+		if (builder.lastIndexOf("-") == lastPos && lastPos >= 0)
+			builder.deleteCharAt(lastPos);
+		return builder.toString();
+	}
+
+	public static String getDefaultBaseUrl(long siteId,String subject,String host) {
+		String subjectPart = subjectEncode(subject).toLowerCase();
+		if (subjectPart.length() == 0)
+			subjectPart = "x";
+
+		// The max length of a domain is 255 chars, but each part (aka octet) can only
+		// have 63 chars (RFC 2181). So we truncate the subject if it goes beyond that size.
+		if (subjectPart.length() > 63) {
+			subjectPart = subjectPart.substring(0, 62);
+		}
+		if (subjectPart.endsWith("-"))
+			subjectPart = subjectPart.substring(0, subjectPart.length()-1);
+		return subjectPart + '.' + siteId + '.' + host;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/help/Help.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,414 @@
+
+package nabble.view.lib.help;
+
+import fschmidt.html.Html;
+import fschmidt.util.java.HtmlUtils;
+import nabble.model.Lucene;
+import nabble.model.Message;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.web.template.HtmlListNamespace;
+import nabble.modules.ModuleManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public abstract class Help {
+	private static final Logger logger = LoggerFactory.getLogger(Help.class);
+
+	private static final Map<Integer,Help> map = new HashMap<Integer,Help>();
+	public final int id;
+	public final String question;
+
+	Help(int id,String question) {
+		this.id = id;
+		this.question = question;
+		if( map.put(id,this) != null )
+			throw new RuntimeException("duplicate id");
+	}
+
+	public abstract String answer();
+	public String getMetaDescription() { return null; }
+	public String getMetaKeywords() { return null; }
+
+	public String path() {
+		return "/help/Answer.jtp?id=" + id;
+	}
+
+	public String url() {
+		return Jtp.defaultContextUrl()+path();
+	}
+
+	public String link() {
+		return 
+		"<a href=\""
+		+(url())
+		+"\">"
+		+(question)
+		+"</a>"
+;
+	}
+
+	public String url(HttpServletRequest request) throws IOException {
+		return url();
+	}
+
+	public String link(HttpServletRequest request) throws IOException {
+		return link();
+	}
+
+	public static Help getHelp(int id) {
+		return map.get(id);
+	}
+
+	public static final Help what = new Help(
+		1, 
+		"What is Nabble?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Nabble wants to improve public discussions on the web and provide useful embeddable applications to end users.\r\n		This includes forums, user groups, message boards, mailing lists, photo galleries, newspapers, blogs, etc.  There are many vibrant discussions in these places, so are problems such as cluttered UI, broken search, moderation, and cataloging. Nabble wants to be a place where your discussion can grow and be free of these problems.\r\n		</p>\r\n	"
+;}};
+	public static final Help free = new Help(
+		2, 
+		"Is Nabble really free?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Yes. Nabble is absolutely free and will remain free to the users. This includes the end users,\r\n		mailing list owners, and webmasters. If your website uses Nabble as a hosted forum or a list archive,\r\n		be assured that there is no limit on traffic or disk space, no hosting fees, no pay-for versions or label schemes.\r\n		Nabble is just free.\r\n		</p>\r\n	"
+;}};
+	public static final Help add = new Help(
+		4, 
+		"How do I add my mailing list to Nabble archive?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Go the <a href=\"/\">Nabble home page</a>, and click on the <a href=\"/more/MailingListRequest.jtp\">Archive Mailing list</a> link.\r\n		Fill out the form and follow the instructions there.\r\n		</p>\r\n	"
+;}};
+	public static final Help find = new Help(
+		5, 
+		"How do I find my mailing list on Nabble?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Search your mailing list address from <a href=\"/\">Nabble home page</a>. If there is a match, you will find a forum link in the upper part of the result page.\r\n		</p>\r\n	"
+;}};
+	public static final Help owner = new Help(
+		6, 
+		"Do I have to be the list owner to archive a list at Nabble?"
+){public String answer(){return 
+		"\r\n		<p>\r\n			No. Anybody can add a list to Nabble archive.\r\n			If you subscribe to a list, and find yourself receiving too many emails, or having a hard time searching through the messages, you can consider getting a Nabble archive for the list.\r\n			This way you can view, search, post, entirely from the web without cluttering your inbox.\r\n		</p>\r\n	"
+;}};
+	public static final Help why = new Help(
+		7, 
+		"Why is my mailing list archived without my knowledge?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Mailing list archive is not something new. Besides Nabble, a public mailing list is very likely archived somewhere else.\r\n		For example, try searching on <a href=\"http://gmane.org/find.php\" target=\"_top\" rel=\"nofollow\">Gmane</a>, <a href=\"http://www.mail-archive.com/\" target=\"_top\" rel=\"nofollow\">Mail-archive</a>, <a href=\"http://marc.theaimsgroup.com/\" target=\"_top\" rel=\"nofollow\">MARC</a>, and <a href=\"http://opensubscriber.com/\" target=\"_top\" rel=\"nofollow\">OpenSubscriber</a>.\r\n		They have all existed long before Nabble. Nabble may have found your public mailing list from these places.\r\n		Or, your list's members may have added your list to Nabble's archive.\r\n		</p>\r\n		<p>\r\n		If you feel upset because nobody asked you for permission, take it easy.\r\n		An archive works like Google. Google will use a crawler to crawl your site to collect the web pages and index them,\r\n		much the same way as an archive using an email address to subscribe to your list to receive the messages and index them.\r\n		Were Google to ask permission from every webmaster, it would take too long, and there\r\n		would be no Google or search engines. But, if you don't like it, we will delete it.\r\n		</p>\r\n	"
+;}};
+	public static final Help sell = new Help(
+		10, 
+		"Can Nabble sell the data (archive)?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		It's like asking can Google sell the web pages in its index?\r\n		Absolutely not. A message belongs to its author. Besides, an email address is sacred.\r\n		</p>\r\n	"
+;}};
+	public static final Help post = new Help(
+		11, 
+		"Did Nabble break my subscription policy?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Nabble supports many mailing list software such as mailman, ezmlm, and listserv. For all supported list software, Nabble conforms to the mailing list subscription policy. A non-subscriber will be prompted to subscribe to the mailing list before he/she can post to the mailing list. Without subscription, the post will be rejected as a non-subscriber post. Note that only registered users can post from Nabble. The Nabble registration process is the same as the mailing list subscription process. This means extra protection and validation.\r\n		</p>\r\n		<p>\r\n		You can think of Nabble as a web interface to your list. Without it, a user will have to view and post from an email box that tends to clutter. With Nabble, users who prefer the web view can post from the web, and interact with the same email users seamlessly.\r\n		</p>\r\n		<p>\r\n		The feature of bridging a forum and a list is not new. It is called mail-to-news gateway - Gmane.org probably has already archived your list this way. If you still have concerns, please contact us.\r\n		</p>\r\n	"
+;}};
+	public static final Help userid = new Help(
+		12, 
+		"Why do I see a user account with my name when I haven't registered?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Nabble archives public mailing lists and will create user accounts for users on these lists.  If you have posted to the  mailing lists before, Nabble may have created an unregistered account for you.  Since this account is unregistered, you cannot use it until you register with Nabble.  When you register with Nabble using the same email address as you used on the mailing list, you will be able to take ownership of this account and will be able to post messages.\r\n		</p>\r\n		<p>\r\n		It is important that you use the same email address (that you used for the mailing list) to register because Nabble identifies users by their email addresses.  If you use an email address different from the one that you used for the mailing list, you are just creating a new account.  Nabble currently does not support merging of multiple user accounts.\r\n		</p>\r\n	"
+;}};
+	public static final Help delpost = new Help(
+		13, 
+		"How do I delete my posts?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Log in to (or register with) Nabble and then you can find and delete your posts on Nabble.\r\n		</p>\r\n		<p>\r\n		Please note that Nabble archives public mailing lists. If you post to a public mailing list, then your message will become public records and anybody can archive it. There are several public mailing list archives like Nabble, for example, Gmane and mail-archive.com; in addition, a mailing list usually has its own native archive. Once you make a public post, it's hard to remove it from the web.\r\n		</p>\r\n		<p>\r\n		Most archives do not allow you to delete your message. With Nabble, you can. But you will need to register with Nabble using the same email address that you used for your post. Please note that we have no particular interest in knowing your email address because your email address is already available to public through the mailing list archives. We require you to register only because we need to verify that you are the owner of the post. After you register, log in, go to your post, and you will find a \"delete\" link to delete your post.\r\n		</p>\r\n	"
+;}};
+	public static final Help pending = new Help(
+		15, 
+		"Why is my post \"pending\"? How to fix it?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Your post is pending because you posted it to a special Nabble forum which is both a forum and a web\r\n		gateway to a mailing list. Your message is already posted on Nabble, but it has not been accepted by\r\n		the mailing list yet.\r\n		</p>\r\n		<p>\r\n		A mailing list is a subscriber-only email forwarding service. A subscriber sends email to the\r\n		mailing list, and the mailing list fowards his email to all subscribers. This allows subscribers to discuss\r\n		topics by reading and replying via email. Nabble works as a bridge to forward your post to the mailing list.\r\n		If you are a subscriber, the mailing list will take your post and forward it to all subscribers.\r\n		But if you are not, then your post will be rejected and thus it will be in a \"pending\" status.\r\n		</p>\r\n		<p>\r\n		<strong>Are you a registered user of Nabble?</strong> Please note that being registered with Nabble does not mean that you are a subscriber to mailing lists. In fact, each mailing list requires a separate subscription.\r\n		</p>\r\n		<p>\r\n		Have you confirmed your subscription yet? You may have just clicked a \"Subscribe\" button, but may not have followed\r\n		through with the two additional steps:\r\n		</p>\r\n		<ol>\r\n			<li>Check your email for a confirmation email from the mailing list.\r\n			<li>Follow the instruction in the email to confirm subscription.\r\n		</ol>\r\n		<p>\r\n		Sounds complicated? You bet. But the good part of this mechanism is that it helps to filter out careless posters.\r\n		Because most users on a mailing list are dedicated and careful users, you are likely to get quality responses\r\n		once your post goes through.\r\n		</p>\r\n		<h3>How to fix pending messages?</h3>\r\n		<p>\r\n			Nabble doesn't allow you to resend your message to the mailing list.\r\n			The right way of doing this is to compose a new message, which will be sent as a new email.\r\n			You might want to delete your old pending messages in order to keep the forum clean and organized.\r\n			To find your pending messages, go to <b>Your Account > Account Settings > Pending Posts</b>.\r\n		</p>\r\n		<p>\r\n		The most important thing is to make sure you are a subscriber.\r\n		If you haven't subscribed yet, do it and wait for confirmation.\r\n		Then follow the instruction to confirm.\r\n		If you don't remember whether you have subscribed before, subscribe again.\r\n		After you have confirmed a subscription, go back to your pending message and repost the message.\r\n		</p>\r\n\r\n		<h3>I am sure I have subscribed, but why is my message still pending?</h3>\r\n		<p>\r\n		A post to a mailing list will appear as pending right after being posted.\r\n		This is normal because it takes a few minutes for your post to get forwarded and processed by a mailing list server.\r\n		Usually, if you are a subscriber, after a few minutes, your pending message will get posted and the pending messages page will remove that post from the list.\r\n		</p>\r\n		<p>\r\n		When you are a subscriber and you see a post pending for several hours after your have posted, it could be that the mailing list server is slow or unavailable.\r\n		Your post may be pending simply because the mailing list server has not processed it yet.\r\n		Try going to the mailing list website to see if it is slow. Don't post repeatedly. Try reposting in a day or two.\r\n		</p>\r\n		<p>\r\n		Sometimes mailing lists may reject your message for format or editorial reasons. For example, some mailing lists\r\n		do not accept HTML formats, and some forbids certain language or content. Usually, the mailing list will reject\r\n		your post and email you a notice with an explanation. In such cases, go to the pending post to \"Edit\" it so that\r\n		it conforms with the mailing list requirement, then post again. Or, you can simply delete your post.\r\n		</p>\r\n	"
+;}};
+	public static final Help moderation = new Help(
+		16, 
+		"How do I remove SPAM or bad users from my forum?"
+){public String answer(){return 
+		"\r\n		<p>\r\n			SPAM and bad posts are common problems for forums.\r\n			With Nabble, forum owners can easily remove unwanted messages and ban bad users.\r\n			Both actions are available in the dropdown menu close to the message.\r\n		</p>\r\n		<p>\r\n			We should remember that anonymous users can't be banned.\r\n			However, you can disable anonymous posts for your forum by accessing the <b>Options > Users > Control anonymous users</b> action in the forum options menu.\r\n		</p>\r\n	"
+;}};
+	public static final Help mailingListIntro = new Help(
+		17, 
+		"What is a \"mailing list\"?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		A mailing list is an email forwarding service for subscribers. People set up a mailing list to distribute information about a certain topic. If you are interested in the topic, you can subscribe and then start receiving messages via email. You can also email your questions or relevant information to the mailing list which will then forward them to all other subscribers. Some people call \"mailing list\" the email discussion group.\r\n		</p>\r\n		<h2>Nabble provides a web archive/gateway to mailing lists.</h2>\r\n		<p>\r\n		Nabble works as a web archive to mailing lists to allow search and browsing. Also it allows web users to post messages through Nabble which then forwards it as an email to the mailing list. This means web users can join an email discussion through the web and all users are in sync.\r\n		</p>\r\n		<p>\r\n		Nabble supports many mailing list software. For all the supported listserv software, Nabble conforms with the mailing list's subscription policy. This means that a user will need to become a subscriber to a mailing list before he can post through the Nabble web archive.\r\n		</p>\r\n		<p>\r\n		<b>Please note:</b> being a registered Nabble user does not mean you are a subscriber to a mailing list. Each mailing list requires separate subscription.\r\n		</p>\r\n	"
+;}};
+	public static final Help stopEmails = new Help(
+		18, 
+		"Why do I receive emails after I made a post on Nabble? How to stop it?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		If you find yourself receiving many emails (not spam, just regular emails) after making a post on Nabble, don't worry, it can be easily fixed.\r\n		</p>\r\n		<p>\r\n		What happened is that your were probably posting through Nabble to a \"mailing list\", also known as the email discussion group,\r\n		and it requires that you become a subscriber before you can post. You probably remember seeing the prompt to subscribe.\r\n		You have to do that in order for your post to go out, but as a result, you subscribed and that is why you are receiving\r\n		the emails. You should also have received an automatic email from Nabble with a link that you can click to stop\r\n		receiving emails. If you missed that email or don't remember, continue reading.\r\n		</p>\r\n		<h3>How to stop it?</h3>\r\n		<ol>\r\n		<li>Go to the forum where you posted a message.\r\n		<br/>If you remember where you posted, go there directly. If you don't, try to find your post first, then go up one level\r\n		to the forum level. Or, you can search Nabble by the email address of the mailing list. You can find the email address by\r\n		looking for the \"to:\" field in those unwanted emails that you received from the mailing list.\r\n		<br/><br/>\r\n		<li>Click the \"mailing list options\" link.\r\n		<br/>In the subsequent page you will find options to turn off email delievery or simply unsubscribe. Follow through\r\n		the instructions there.\r\n		</ol>\r\n	"
+;}};
+	public static final Help stopAlert = new Help(
+		19, 
+		"How do I stop an email alert?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		At the end of an email alert, there is always a link to stop it. Click that link and you will no longer be alerted.\r\n	"
+;}};
+	public static final Help restrictions = new Help(
+		20, 
+		"How do I control who can view and post in my forum? "
+){public String answer(){return 
+		"\r\n		<p>\r\n			Under the \"Options\" menu, under \"Users\", is the option \"Who can view & post\".\r\n			This page controls who can view and post in this forum.  But if this forum has a parent forum\r\n			and if the parent forum has restrictions, then those restriction will also apply.\r\n			This means that restrictions are only added as you go down your forum hierarchy.\r\n			This makes sense because when you set restrictions, you assume that they will apply to\r\n			all sub-forums as well.\r\n		</p>\r\n		<p>\r\n			Let's look at a few examples.  Suppose you have a forum hierarchy and want to ban anonymous users\r\n			throughout. You would go to the top forum and select that only registered users can create new topics and replies:\r\n		</p>\r\n		<p><img src=\"/images/help/registered_only.png\" width=505 height=108 style=\"padding-left:2em\"/></p>\r\n		<p>\r\n			Now suppose that under the top forum, you have an \"Official Business\" forum where only members of\r\n			a group should be allowed to post.  You would select that only members can create new topics and replies:\r\n		</p>\r\n		<p><img src=\"/images/help/members_only.png\" width=505 height=108 style=\"padding-left:2em\"/></p>\r\n		<p>\r\n			You would then authorize the right members for that \"Official Business\" forum only.\r\n		</p>\r\n		<p>\r\n			Now suppose that under that forum, you have an \"Announcements\" forum where only the organizer should\r\n			post.  The organizer should create that forum and select again that only members can post and reply in that forum,\r\n			but this time not authorize anyone else.  Now only the owner of the \"Announcements\" forum is authorized\r\n			to post there.  But suppose you want to allow members to comment on announcements but not to\r\n			make announcements.  Then instead of members can create new topics, but let everyone reply.\r\n			Note that \"Everyone\" means everyone who would otherwise be allowed to post.  So now within the \"Announcements\" forum,\r\n			only the owner can create threads, but all members can reply to those threads.\r\n		</p>\r\n	"
+;}};
+
+	public static final Help copy = new Help(
+		9, 
+		"Isn't Nabble trying to copy and replace me?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Nabble wants to be useful and transparent.  If you feel shocked on seeing the Nabble mirror of your content,\r\n		please relax and be assured that the content is still yours, and you can delete it anytime. Then, before cursing\r\n		Nabble, try asking yourself:\r\n		</p>\r\n\r\n		<ul>\r\n		<li><p>Is Nabble useful?\r\n		<br>You probably will find the archive search useful. Nabble search is fast, and you can cross search all your\r\n		lists instead of searching each list or relying on Google's \"site:archive url\" search (which is a hack because\r\n		Google does not index all your posts, especially the new ones). What's more, some of your users may want a web\r\n		interface to read and post instead of using a cluttered email box. With Nabble, these users can talk from the\r\n		web and interact with the traditional email users seamlessly.\r\n		</p>\r\n		<li><p>Is Nabble taking users away from my site?\r\n		<br>We understand this concern. You put in effort to grow your users, and we have no right to get in between.\r\n		What we offer is an <a href=\""
+		+(Help.embed_what_how.url())
+		+"\" target=\"_top\" rel=\"nofollow\">\"embeddable\" forum</a>.\r\n		It means that you can customize the Nabble mirror to \"embed\" it into your website. The Nabble mirror will still be hosted at\r\n		Nabble, but you can control the look and feel so that it looks like a part of\r\n		your site, complete with your logo, style sheet, javascript, and even your ads.\r\n		</p>\r\n		<li><p>Is Nabble bypassing me?\r\n		<br>No. <a href=\""
+		+(Help.post.url())
+		+"\" target=\"_top\" rel=\"nofollow\">Nabble conforms with your subscription policy</a>.\r\n		This means if users want to post to your list via Nabble, they will have to join your list to become subscribers.\r\n		</p>\r\n		<li><p>What's in it for Nabble?\r\n		<br>Nabble wants to become the best place for online discussions. That's it for now. We don't know\r\n		yet how to make money.\r\n		</p>\r\n		</ul>\r\n		<p>\r\n		Keep in mind that by mirroring your discussion on Nabble, you could also expand your subscribers through\r\n		Nabble users. They may join your discussions and thus expand your membership.\r\n		They could also go from the discussion page to visit your website.\r\n		</p>\r\n	"
+;}};
+	public static final Help listSpam = new Help(
+		22, 
+		"My list received spam from Nabble. What do I do to prevent it from happening again?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		We hate spam. We sincerely apologize if a spam comes to your list through Nabble.\r\n		</p>\r\n		<p>\r\n		Please let us know through the <a href=\""
+		+(Jtp.supportUrl())
+		+"\">support forum</a> if you see or suspect spam. We will take actions immediately.\r\n		</p>\r\n		<p>\r\n		Forum owners can also <a href=\""
+		+(Help.moderation.url())
+		+"\" target=\"_top\" rel=\"nofollow\">delete spam and ban bad users</a>.\r\n		</p>\r\n	"
+;}};
+	public static final Help cataloging = new Help(
+		24, 
+		"What's the right way to organize forums and messages?"
+){public String answer(){return 
+		"\r\n		<p>\r\n			Nabble offers a flexible way to organize forums and messages.\r\n		</p>\r\n		<p>\r\n			An active forum is great, but its many users and diverse interests often drive the discussion off topic. Nabble tries to address this problem by allowing users to easily create child forums. A child forum structure helps to keep messages focused and properly categorized. The hierarchy is displayed at the parent level for easy drill down. Users who want to ignore off-topic discussions can drill down to the relevant child; and the users who don't mind reading all the messages can still do it at the parent forum level.\r\n		</p>\r\n		<h3>Create a child forum only when it is truly needed</h3>\r\n		<p>\r\n			Creating child forums is a powerful way to add a structure to your discussion, but you should be careful to avoid overdoing it. For example, if you are in a new forum with few messages and users, why bother creating a fancy structure? No matter how well structured, a forum without users is still a dead forum. What's more, a structure that goes too wide or too deep could get people confused as to where to visit or post, thus making it difficult for them to join the discussion. We recommend that you create child forums only when it is truly needed.\r\n		</p>\r\n		<h3>Force users to select a child forum to post</h3>\r\n		<p>\r\n			After you have created child forums, you may find users ignore it when they post, and most of the messages continue to get posted at the parent level. In such cases, don't just blame the users. Take a good look at your design and ask, does it make sense? Is it clear and easy to follow? Is the hierarchy too wide or too deep? If you feel certain that the structure is sound, and that you and a few other forum members support it, then you can use the \"Who Can View & Post\" feature to <a href=\""
+		+(Help.restrictions.url())
+		+"\">make the parent forum read-only</a>. This means that a user cannot post a new message in the parent forum. They will be prompted to drill down to a child forum to post there.\r\n		</p>\r\n		<h3>Keep the hierarchy efficient</h3>\r\n		<p>\r\n			You can build an elaborate structure by using a combination of parent and child categories and forums, but don't overdo it. The goal of a hierarchy is to help users navigate. Does yours help people get to the right forum quickly? For example, if a category has only one child forum under it, then it is a waste, because it just means a useless extra click for navigation.\r\n		</p>\r\n	"
+;}};
+	public static final Help password = new Help(
+		25, 
+		"How does Nabble store passwords?"
+){public String answer(){return 
+		"\r\n		<p>\r\n			Nabble does not store your password in plain text. Only a cryptographic digest of you password is kept in our database. This is why if you forget your password we cannot email it back to you. But if you do forget it, we can always email you a link which allows you to reset your password and pick a new one.\r\n		</p>\r\n		<p>\r\n			It is a good security practice, however, to use different passwords for different websites, so please do not choose a password that you already use elsewhere.\r\n		</p>\r\n	"
+;}};
+	public static final Help seo = new Help(
+		27, 
+		"Is my application optimized for search engines?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Yes. Nabble's content pages are designed with SEO (Search Engine Optimization) in mind.\r\n		Our SEO effort goes further beyond the usual tags and headers to advanced techniques such as URL encoding and PageRank distribution.\r\n		</p>\r\n	"
+;}};
+	public static final Help mirror = new Help(
+		30, 
+		"Can I mirror my groups on Nabble?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		If you own a discussion group such as Yahoo or Google group, you can mirror it on Nabble by using Nabble's mailing list archive feature. Nabble complements your existing group by providing a nice threaded view and a <a href=\""
+		+(Help.search.url())
+		+"\">powerful search</a>. What's more, if you have a quality group you may expand it by getting new users from Nabble.\r\n		</p>\r\n		<p>\r\n		Most importantly, Nabble adds an extra layer of redundancy. With Nabble, you can download all your content for backup or moving purposes anytime you want. This means you will never get stuck with any forum provider including Nabble.\r\n		</p>\r\n	"
+;}};
+	public static final Help search = new Help(
+		31, 
+		"What search features does Nabble provide?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Nabble allows many flavors of specific search.\r\n		</p>\r\n		<ul>\r\n		<li>You can search entire Nabble (with the help of Google Search).\r\n		<li>You can search within an application (e.g., forum, photo gallery, newspaper, blog) to include all subcategories below.\r\n		<li>You can narrow your search result by date.\r\n		<li>You can search messages by user. Click a user name link to go to the user's profile page, search from there. Then you can narrow your search result by date, by category, or by both.\r\n		<li>Nabble search supports keyword stemming, i.e. search, searching, searched, searches...\r\n		</ul>\r\n\r\n		<h2>Examples</h2>\r\n\r\n		<p>\r\n		<table class=\"search-tips-table\" style=\"\r\n			width: 100%;\r\n			border-collapse: collapse;\r\n		\">\r\n		<style type=\"text/css\">\r\n		.nabble .search-tips-table th {\r\n			text-align: left;\r\n			font-weight: bold;\r\n			padding: .5em;\r\n			vertical-align: top;\r\n		}\r\n		.nabble .search-tips-table td {\r\n			padding: .3em;\r\n			border-bottom-width: 1px;\r\n			border-bottom-style: solid;\r\n			vertical-align: top;\r\n		}\r\n		</style>\r\n		<tr>\r\n			<th>Your search</th>\r\n			<th>What will search results show</th>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">hello world</code></td>\r\n\r\n			<td class=\"info-message\">Messages with both <code class=\"important\">hello</code> and <code class=\"important\">world</code></td>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">\"hello world\"</code></td>\r\n			<td class=\"info-message\">Messages with the phrase <code class=\"important\">hello world</code></td>\r\n\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">hello OR world</code></td>\r\n			<td class=\"info-message\">Messages with either <code class=\"important\">hello</code> or <code class=\"important\">world</code></td>\r\n		</tr>\r\n		<tr>\r\n\r\n			<td class=\"info-message\"><code class=\"important\">hello AND world</code></td>\r\n			<td class=\"info-message\">Messages with <code class=\"important\">hello</code> and <code class=\"important\">world</code>; same as entering <code class=\"important\">hello world</code></td>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">\"hello world\" lucene</code></td>\r\n\r\n			<td class=\"info-message\">Messages with both the phrase <code class=\"important\">hello world</code> and <code class=\"important\">lucene</code></td>\r\n		</tr>\r\n\r\n\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">hello NOT world</code></td>\r\n			<td class=\"info-message\">Messages that have <code class=\"important\">hello</code>, but do not have <code class=\"important\">world</code></td>\r\n\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">(\"hello world\" lucene) OR apache</code></td>\r\n			<td class=\"info-message\">Messages with either both <code class=\"important\">hello world</code> and <code class=\"important\">lucene</code>, or <code class=\"important\">apache</code></td>\r\n		</tr>\r\n\r\n\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">lucene NOT \"hello world\"</code></td>\r\n			<td class=\"info-message\">Messages with <code class=\"important\">lucene</code>, but not the phrase <code class=\"important\">hello world</code></td>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">hello*</code></td>\r\n\r\n			<td class=\"info-message\">Messages that have words that begin with <code class=\"important\">hello</code>. For example, <code class=\"important\">hello</code>, <code class=\"important\">helloworld</code>, and <code class=\"important\">hellooooooo</code>. * is a wildcard and matches 0 or more characters.</td>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">par?</code></td>\r\n\r\n			<td class=\"info-message\">Messages that have words such as <code class=\"important\">park</code>, <code class=\"important\">part</code>, <code class=\"important\">para</code>, and so forth. ? is a 1-character wildcard and matches 1 and only 1 character.</td>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">subject:\"hello world\"</code></td>\r\n			<td class=\"info-message\">Messages with the phrase <code class=\"important\">hello world</code> in its <code class=\"important\">subject</code> only.</td>\r\n\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">subject:(hello world)</code></td>\r\n			<td class=\"info-message\">Messages with <code class=\"important\">hello</code> and <code class=\"important\">world</code> in its <code class=\"important\">subject</code> only. Note the use of () to groups words together, rather than \"\" which denote a phrase.</td>\r\n	    </tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">message:\"hello world\"</code></td>\r\n			<td class=\"info-message\">Messages with the phrase <code class=\"important\">hello world</code> in its <code class=\"important\">message body</code> only.</td>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">author:\"Erik Hatcher\"</code></td>\r\n			<td class=\"info-message\">Messages of <code class=\"important\">Erik Hatcher</code> only.</td>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"info-message\"><code class=\"important\">roam~</code></td>\r\n			<td class=\"info-message\">Messages with <code class=\"important\">foam</code>, <code class=\"important\">roam</code>, and so forth. In essence, messages that match or sound like <code class=\"important\">roam</code>.</td>\r\n		</tr>\r\n		</table>\r\n		</p>\r\n\r\n		<a name=\"search1\"><h2>Search Using Words or Phrases </h2></a>\r\n		<p>You may search Nabble using a single-word (e.g., \"test\" or \"hello\") or use a group of words or a phrase surrounded by double quotes (e.g., \"hello dolly\"). When searching by a group of words or a phrase, Nabble will return only those items that have both the words present. </p>\r\n\r\n		<a name=\"search2\"><h2>Boolean Searches</h2></a>\r\n		<p>Boolean operators allow combining of search terms using logical operators such as AND, OR, and NOT. Please note that the Boolean operators must be all caps as in the examples below:</p>\r\n\r\n		<ul>\r\n			<li>To search for documents that contain \"debian linux\" and \"java\" use: \"debian linux\" AND java </li>\r\n			<li>To search for documents that contain either \"jetty\" or \"resin\" use: jetty OR resin </li>\r\n			<li>To search for documents that contain \"linux\" but not \"kernel\" use: linux NOT kernel </li>\r\n			<li>To search for documents that contain \"lucene\" or \"nutch\" but not \"apache\" use: (lucene OR nutch) NOT apache </li>\r\n		</ul>\r\n\r\n		<a name=\"search3\"><h2>Search Messages, Subjects, Authors</h2></a>\r\n		<p>You can limit your search to either the message text, the subject of the message, the author name, the name of the forum, and/or the topic of the discussion by using the syntax shown in examples below: </p>\r\n\r\n		<ul>\r\n			<li>subject:\"lord of the rings\" message:film </li>\r\n			<li>author:\"doug cutting\" message:lucene </li>\r\n		</ul>\r\n\r\n		<p>If you don't specify the type of search, it will search all -- message, subject, author. </p>\r\n\r\n		<a name=\"search4\"><h2>Wildcard Searches </h2></a>\r\n		<p>Nabble allows you to do both single and multiple character wildcard searches. </p>\r\n\r\n		<ul>\r\n			<li>To perform a single character wildcard search use the \"?\" symbol. The single character wildcard search looks for terms that match that with the single character replaced. For example, to search for \"text\" or \"test\" you can use: te?t </li>\r\n			<li>To perform a multiple character wildcard search use the \"*\" symbol. Multiple character wildcard searches looks for 0 or more characters. For example, to search for photo, photography, photographer, you can use: photo* </li>\r\n			<li>You can also use the wildcard searches in the middle of a term.</li>\r\n		</ul>\r\n\r\n		<p><strong>Note:</strong> You cannot use a * or ? symbol as the first character of a search. Also if your wildcard search will result in too many matches, it cannot be processed.</p>\r\n\r\n		<a name=\"search5\"><h2>Fuzzy Searches</h2> </a>\r\n		<p>To do a fuzzy search use the tilde, \"~\", symbol at the end of a single word search. For example to search for a term similar in spelling to \"roam\" use the fuzzy search: roam~ </p>\r\n\r\n		<p>This search will find terms like foam and roams. </p>\r\n\r\n		<a name=\"search6\"><h2>Proximity Searches </h2></a>\r\n		<p>Nabble supports proximity search -- that is, finding words that are a within a specific distance away from each other. To do a proximity search use the tilde, \"~\", symbol at the end of a Phrase. For example to search for a \"apache\" and \"jakarta\" within 10 words of each other in a document use: \"jakarta apache\"~10 </p>\r\n\r\n	"
+;}};
+    public static final Help orphan = new Help(
+		34, 
+		"What does \"parent message unknown\" mean?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Some messages that we receive from mailing lists don't have enough information for us to determine which message the message we received was in reply to.  In other words, we don't know the parent of that message.  So we mark the message with the image <img alt=\"Parent Message unknown\" title=\"Parent Message unknown\" width=\"13\" height=\"14\" src=\"/images/icon_orphan.png\" style=\"vertical-align:middle; vertical-align: absmiddle;\" border=\"0\" /> and usually assign the first post in the thread as the parent.  This only matters in the \"Threaded\" view where you can see the structure of messages and their replies.\r\n		</p>\r\n	"
+;}};
+	public static final Help security = new Help(
+		35, 
+		"Does Nabble have a security policy for mailing lists?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Yes.\r\n		</p>\r\n		<p>\r\n		Javascript code is not allowed in Nabble's archive.\r\n		Thus, any code that may arrive with an email is blocked for your security.\r\n		Only the safe part of the message is displayed in the archive, and\r\n		we also show a warning that the JavaScript was blocked.\r\n		</p>\r\n		<p>Nabble also removes <b>&lt;style&gt;</b> tags because they might prevent messages from being displayed properly.</p>\r\n	"
+;}};
+	public static final Help embed_what_how = new Help(
+		36, 
+		"Embeddable Forum, Photo Gallery, News, Blog, Mailing List & Other Apps!"
+){
+		public String getMetaDescription() { return "Nabble has different embeddable applications, including free forum, photo gallery, news, blog and much more! All applications have no installation, no HTML hassle, just copy and paste of a small javascript code."; }
+		public String getMetaKeywords() { return "free, embeddable, forum, photo gallery, newspaper, blog, mailing list archive, easy, embedding, embedded, embed my forum, hosted, website, site, HTML"; }
+		public String answer(){return 
+		"\r\n\r\n		<h2>All Nabble apps are naturally embeddable!</h2>\r\n\r\n		<p>Yes, with Nabble, you get an embeddable forum, embeddable photo gallery, embeddable news, embeddable blog, embeddable mailing list & archive... and there are more to come!</p>\r\n		<p>All these embeddable apps are fully featured web applications. They all have full text search, user profile and access control, email subscription and integration, XML feed, moderation, CSS customization, unlimited uploading of pictures and files, threading, cataloging ... What's more, embedding a Nabble app is as easy as embedding a YouTube video - you just copy and paste a line of javascript code into your HTML page.</p>\r\n		<p>You may have seen some embeddable widgets, but Nabble's \"embeddability\" is far more advanced. A widget runs in a fixed frame, making the content either underflows or overflows (frame scrolling is your only option). In contrast, a Nabble embeddable app adjusts its height automatically and always fit seamlessly into your site.</p>\r\n\r\n		<p>All you have to do is go to your application, click on \"Options > Embedding options\" and you will see a text area with a code snippet.</p>\r\n		<p>Copy and paste that snippet into your HTML web page. Now open the page and you will see that your forum loads up seamlessly. No installation, no HTML hassle, just copy and paste.</p>\r\n		<p><b>Embedding may have issues with third-party cookies.</b>  This can be a problem if the user has disabled third-party cookies or if they are using Safari.  To avoid problems, we suggest that you use a <a href=\""
+		+(Help.domain_names.url())
+		+"\">custom domain</a> for Nabble that matches your domain.  For example, if your website is www.your-domain.com, you could use forum.your-domain.com for Nabble.  This ensures that Nabble's cookies aren't considered third-party by the browser.</p>\r\n		<p>Users will browse, search, post, and navigate without ever leaving your page. Your embedded application works as if it were custom built, installed, and visually integrated with your website.</p>\r\n		<p>Try this feature by creating a new application (e.g., forum, photo gallery, newspaper, blog, etc.) or using any Nabble forum that allows embedding. It is dead easy and seamless.</p>\r\n		"
+;}};
+
+		public static final Help embed_permalinks = new Help(
+		38, 
+		"How do I link to a page in an embedded application?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		You will notice that when you navigate through an embedded application (e.g., forum, photo gallery, blog, etc), the URL in the browser doesn't change.\r\n		To get the URL of a specific page, such as a thread or a post, in an embedded application, look on the top-right corner of each page for <b>Permalinks</b>.</p>\r\n		<p><img src=\"/images/help/help_embed_permalink.png\" style=\"padding-left:2em\"/></p>\r\n		<p>When you click on the \"Permalink\" link, you will see the page URL in a text field. All you have to do is click on the URL to\r\n		select it, and then copy it (e.g., ctrl+c).</p>\r\n		<p><img src=\"/images/help/help_embed_permalink2.png\" style=\"padding-left:2em\"/></p>\r\n		Even though the URL points to the <i>nabble.com</i> domain, the browser will redirect to the embedding page so that it will look\r\n		exactly like it was when you first got the permalink (you should <a href=\""
+		+(Help.embed_redirect.url())
+		+"\">enable this option</a> to work).\r\n		</p>\r\n	"
+;}};
+	public static final Help embed_skin = new Help(
+		39, 
+		"How can I customize the appearance of my embeddable application?"
+){
+		public String getMetaDescription() { return "All Nabble applications are fully customizable. You can easily change the CSS stylesheet of your embeddable forum, photo gallery, newspaper, blog and mailing list archives."; }
+		public String getMetaKeywords() { return "customizable, embeddable, forum, photo gallery, newspaper, blog, mailing list archive, css, skins, examples, simple, easy"; }
+		public String answer(){return 
+		"\r\n		<p>\r\n			Go to \"Options > Editor > Change appearance\" link in your application.\r\n			This screen has easy options to customize the basic look of your application:\r\n		</p>\r\n		<p><img src=\"/images/help/help_style_easy.png\" style=\"padding-left:2em\" alt=\"Here you can customize details about your application\"/></p>\r\n		<p>\r\n			If you want a more detailed customization, you can go to the \"Look and Feel\" section in order to provide a custom CSS (Cascading Style Sheets) text for your application.\r\n		</p>\r\n		<p><img src=\"/images/help/help_style_tab.png\" style=\"padding-left:2em\"/></p>\r\n		<p>As you can see in the image above, Nabble has some predefined styles ready for you. You can simply click on them to get their CSS into the text area.\r\n			After that, you can modify the text the way you want. This is a valuable source of examples that you can also use to learn more about Nabble's style.</p>\r\n		<h2>How to write your custom CSS</h2>\r\n		<p>\r\n			If you want to change the appearance of your application, you have to override <a href=\""
+		+(Shared.getCssPath())
+		+"\">Nabble's style</a>.\r\n			The <b>cheat table</b> below can easily guide you through this process. Learn how each style is defined and change them as you want.\r\n			Since colors may require hexadecimal codes, you can use a tool like <a href=\"http://www.colorpicker.com/\" target=\"_new\" rel=\"nofollow\">Color Picker</a> to help you in this sense.\r\n		</p>\r\n		<p class=\"medium-border-color info-message\" style=\"padding:.7em;border-width:1px;border-style:solid;\">If you want to import an external CSS file, you should use the following command:<br/><font face=monospace size=3>@import url(\"http://www.example.com/file.css\");</font></p>\r\n		<style>\r\n			.nabble .item-small { font-size:90%; }\r\n			.nabble .color-box { border:1px solid #000000; }\r\n		</style>\r\n		<table class=\"editor-table\" cellpadding=\"5\" style=\"font-size:90%;\">\r\n			<tr class=\"shaded-bg-color\">\r\n				<td>Item to Change</td>\r\n				<td>Default Value</td>\r\n				<td>How to Change (Example)</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Font</b><div class=\"item-small\">Font family and its size.</div></td>\r\n				<td>Verdana 0.84em</td>\r\n				<td>\r\n					body, input, button, textarea, select {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;font-family: <b>Tahoma</b>;<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;font-size: <b>0.8em</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Background Color</b><div class=\"item-small\">Background of the pages.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#FFFFFF\">&nbsp;&nbsp;&nbsp;</span> FFFFFF (white)</td>\r\n				<td>\r\n					body, .nabble, .nabble .no-bg-color {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Text Color</b><div class=\"item-small\">Color of texts (paragraphs, etc.).</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#000000\">&nbsp;&nbsp;&nbsp;</span> 000000 (black)</td>\r\n				<td>\r\n					.nabble,<br/>\r\n					.nabble table,<br/>\r\n					.nabble .info-message th,<br/>\r\n					.nabble .message-text, .small,<br/>\r\n					.nabble .editor-table td, p, form,<br/>\r\n					small, ul, table td, .breadcrumbs {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;<br/>\r\n					}<br/>\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Link Color</b><div class=\"item-small\">Color of unvisited links.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#0000EE\">&nbsp;&nbsp;&nbsp;</span> 0000EE</td>\r\n				<td>\r\n					.nabble a:link,<br/>\r\n					.nabble a.not-visited-link {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Link Color (Visited)</b><div class=\"item-small\">Color of visited links.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#551A8B\">&nbsp;&nbsp;&nbsp;</span> 551A8B</td>\r\n				<td>\r\n					.nabble a:visited,<br/>\r\n					.nabble a.visited-link {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Header Text Color (H1)</b><div class=\"item-small\">Color of header texts (big titles, etc.).</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#333333\">&nbsp;&nbsp;&nbsp;</span> 333333</td>\r\n				<td>\r\n					.nabble h1 {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;</br>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Header Text Color (H2)</b><div class=\"item-small\">Color of small header texts.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#000000\">&nbsp;&nbsp;&nbsp;</span> 000000</td>\r\n				<td>\r\n					.nabble h2 {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;</br>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Important Text</b><div class=\"item-small\">Color of important texts.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#cc0000\">&nbsp;&nbsp;&nbsp;</span> CC0000</td>\r\n				<td>\r\n					.nabble .important {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;</br>\r\n					}\r\n				</td>\r\n			</tr>\r\n            <tr>\r\n				<td><b>Form Label Text</b><div class=\"item-small\">Color of labels on forms and other special places.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#666666\">&nbsp;&nbsp;&nbsp;</span> 666666</td>\r\n				<td>\r\n					.nabble th,<br/>\r\n					.nabble .form-label,<br/>\r\n					.nabble .weak-color {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n				<tr>\r\n				<td><b>Light Background</b><div class=\"item-small\">Color of light backgrounds, which are used in some areas of the screen.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#F5F5F5\">&nbsp;&nbsp;&nbsp;</span> F5F5F5</td>\r\n				<td>\r\n					.nabble .light-bg-color {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Shaded Background</b><div class=\"item-small\">Color of shaded backgrounds, which are used in some areas of the screen.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#EEEEEE\">&nbsp;&nbsp;&nbsp;</span> EEEEEE</td>\r\n				<td>\r\n					.nabble .shaded-bg-color {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Dark Background</b><div class=\"item-small\">Color of dark backgrounds, which are used in some areas of the screen.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#DDDDDD\">&nbsp;&nbsp;&nbsp;</span> DDDDDD</td>\r\n				<td>\r\n					.nabble .dark-bg-color {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n            <tr>\r\n				<td><b>Error Message</b><div class=\"item-small\">Color of error messages.</div></td>\r\n				<td><span class=\"color-box\" style=\"border-color:#cc3300;background-color:#ffffcc\">&nbsp;&nbsp;&nbsp;</span> FFFFCC</td>\r\n				<td>\r\n					.nabble .error-message {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;border-color:<b>#&lt;color&gt;</b>;<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Info Message</b><div class=\"item-small\">Color of info messages.</div></td>\r\n				<td><span class=\"color-box\" style=\"border-color:#ffcc33;background-color:#ffffcc\">&nbsp;&nbsp;&nbsp;</span> FFFFCC</td>\r\n				<td>\r\n					.nabble .info-message {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;border-color:<b>#&lt;color&gt;</b>;<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Highlight</b><div class=\"item-small\">Color of highlights, which are used to catch user's attention.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#ffff99\">&nbsp;&nbsp;&nbsp;</span> FFFF66</td>\r\n				<td>\r\n					.nabble .highlight {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n            <tr>\r\n				<td><b>Light Border Color</b><div class=\"item-small\">Color of the light border, which is used to separate areas on the screen.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#eeeeee\">&nbsp;&nbsp;&nbsp;</span> EEEEEE</td>\r\n				<td>\r\n					.nabble .light-border-color {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;border-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n            <tr>\r\n				<td><b>Medium Border Color</b><div class=\"item-small\">Color of the medium border, which is used to separate areas on the screen.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#cccccc\">&nbsp;&nbsp;&nbsp;</span> CCCCCC</td>\r\n				<td>\r\n					.nabble .medium-border-color {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;border-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n            <tr>\r\n				<td><b>Dark Border Color</b><div class=\"item-small\">Color of the dark border, which is used to separate areas on the screen.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#666666\">&nbsp;&nbsp;&nbsp;</span> 666666</td>\r\n				<td>\r\n					.nabble .dark-border-color {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;border-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n            <tr>\r\n				<td><b>Dropdown Colors</b><div class=\"item-small\">Colors of the dropdown box.</div></td>\r\n				<td><span class=\"color-box\" style=\"border-color:#cccccc;background-color:#eeeeee\">&nbsp;&nbsp;&nbsp;</span> CCCCCC</td>\r\n				<td>\r\n					span.dropdown table {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-color:<b>#&lt;color&gt;</b>;<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;border-color:<b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n            <tr>\r\n				<td><b>Dropdown Item Colors</b><div class=\"item-small\">Colors of each dropdown item.</div></td>\r\n				<td>\r\n					<span class=\"color-box\" style=\"background-color:#0000EE\">&nbsp;&nbsp;&nbsp;</span> 0000EE (foreground / normal and hover)<br/><br/>\r\n					<span class=\"color-box\" style=\"background-color:transparent\">&nbsp;&nbsp;&nbsp;</span> Transparent (background normal)<br/><br/>\r\n					<span class=\"color-box\" style=\"background-color:#dddddd\">&nbsp;&nbsp;&nbsp;</span> DDDDDD (background hover)\r\n				</td>\r\n				<td>\r\n					<i>/* Normal */</i><br/>\r\n					span.dropdown-item {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color:<b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n            <tr>\r\n				<td><b>Date Color</b><div class=\"item-small\">Color used to display dates of messages.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#6a6a6a\">&nbsp;&nbsp;&nbsp;</span> 6A6A6A</td>\r\n				<td>\r\n					span.post-date {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color:<b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Author Color</b><div class=\"item-small\">Color used to display the author name on messages.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#116611\">&nbsp;&nbsp;&nbsp;</span> 116611</td>\r\n				<td>\r\n					span.post-author {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color:<b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Subject Color</b><div class=\"item-small\">Color used to display subjects of messages.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#111166\">&nbsp;&nbsp;&nbsp;</span> 111166</td>\r\n				<td>\r\n					.post-subject {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color:<b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n            <tr>\r\n				<td><b>Message Preview Color</b><div class=\"item-small\">Color used to display the message preview line (snippets).</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#909090\">&nbsp;&nbsp;&nbsp;</span> 909090</td>\r\n				<td>\r\n					span.post-snippet {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;color:<b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Post Hover Color</b><div class=\"item-small\">Color used to highlight a post when the user moves the mouse over it.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#eeeeee\">&nbsp;&nbsp;&nbsp;</span> EEEEEE</td>\r\n				<td>\r\n					.post-hover {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-color:<b>#&lt;color&gt;</b>;<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Post Box</b><div class=\"item-small\">Box where the post is displayed.</div></td>\r\n				<td><span class=\"color-box\" style=\"background-color:#D9D9D9\">&nbsp;&nbsp;&nbsp;</span> D9D9D9</td>\r\n				<td>\r\n					div.post-border {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;border-color: <b>#&lt;color&gt;</b>;<br/>\r\n					}<br/>\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td><b>Relationship Lines</b><div style=\"font-size:80%\">Lines that connect parents and children posts. This item requires images for the lines.</div></td>\r\n				<td>N/A</td>\r\n				<td>\r\n					span.connect-line {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-image:url(\"<b>&lt;image-URL&gt;</b>\");<br/>\r\n					}<br/>\r\n					<br/>\r\n					span.connect-end {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-image:url(\"<b>&lt;image-URL&gt;</b>\");<br/>\r\n					}<br/>\r\n					<br/>\r\n					td.connect-end {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-image:url(\"<b>&lt;image-URL&gt;</b>\");<br/>\r\n					}<br/>\r\n					<br/>\r\n					td.connect-node {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-image:url(\"<b>&lt;image-URL&gt;</b>\");<br/>\r\n					}<br/>\r\n					<br/>\r\n					td.connect-node-closed {<br/>\r\n					&nbsp;&nbsp;&nbsp;&nbsp;background-image:url(\"<b>&lt;image-URL&gt;</b>\");<br/>\r\n					}\r\n				</td>\r\n			</tr>\r\n		</table>\r\n	"
+;}};
+	public static final Help embed_redirect = new Help(
+		40, 
+		"Is my embedded application also available on Nabble's website?"
+){public String answer(){return 
+		"\r\n		<p>\r\n		Yes, but you have the option to redirect all visits to your website. In this case,\r\n		when someone tries to access your application or posts, the browser will redirect to\r\n		your website and the desired page will be displayed. This option allows our search\r\n		engine to find messages from your application, but users will be redirected if they try\r\n		to open those messages.\r\n		 </p>\r\n		<p>\r\n		To configure this option, go to the page where your application is embedded in and click\r\n		on <i>\"Options > Embedding options\"</i>:\r\n		</p>\r\n		<p><img src=\"/images/help/help_embed_default.png\" style=\"padding-left:2em\"/></p>\r\n	"
+;}};
+
+	private static class Lazy {
+		private static final String[] examples = new String[]{
+			"<quote>some text</quote>",
+			"<quote author='Bob'>some text</quote>",
+			"<email>someone@nabble.com</email>",
+			"<raw>\nuninterpreted HTML like <a href='#'>link</a>\n</raw>",
+		};
+		private static final String exampleRows;
+		static {
+			StringBuilder buf = new StringBuilder();
+			for( String example : examples ) {
+				buf.append( 
+		"\r\n					<tr>\r\n						<td class=\"medium-border-color\">"
+		+(HtmlUtils.htmlEncode(example))
+		+"</td>\r\n						<td class=\"second medium-border-color\">"
+		+(getHtml(example))
+		+"</td>\r\n					</tr>\r\n				"
+ );
+			}
+			exampleRows = buf.toString();
+		}
+	}
+
+	private static String getHtml(String text) {
+		try {
+			Html html = new Html(text);
+			HtmlListNamespace ns = new HtmlListNamespace(html,null, nabble.model.Message.Format.HTML);
+			Program program = Program.getInstance(ModuleManager.getGenericModules());
+			Template template = program.getTemplate( "process_message_html",
+				BasicNamespace.class, HtmlListNamespace.class
+			);
+			template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(),
+				new BasicNamespace(template), ns
+			);
+			return ns.toString();
+		} catch(CompileException e) {
+			logger.error("",e);
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static final Help formatting = new Help(
+		41, 
+		"How are messages formatted?"
+){public String answer(){return 
+		"\r\n		<p>For formatting, we use standard HTML tags.</p>\r\n\r\n		<p>By default, your message is assumed to be text.  In this case, we will process some HTML tags but we will generally show your text as your entered it.  If you check the \"Message is in HTML Format\" checkbox, then you can enter HTML.</p>\r\n\r\n		<p>We support some special tags which we process. Here is how they are used:</p>\r\n\r\n		<p>\r\n		<style type=\"text/css\">\r\n			table.type-examples {\r\n				border-collapse:collapse;\r\n			}\r\n			table.type-examples td {\r\n				padding: .5em;\r\n				border-bottom-width:1px;\r\n				border-bottom-style: solid;\r\n			}\r\n			table.type-examples td.second {\r\n				border-left-width:1px;\r\n				border-left-style: solid;\r\n			}\r\n		</style>\r\n		<table class=\"type-examples\">\r\n			<tr>\r\n				<td class=\"shaded-bg-color medium-border-color\"><b>You Type</b></td>\r\n				<td class=\"second shaded-bg-color medium-border-color\"><b>We Show</b></td>\r\n			</tr>\r\n			"
+		+(Lazy.exampleRows)
+		+"\r\n		</table>\r\n		</p>\r\n	"
+;}};
+
+	public static final Help anonymous = new Help(
+		42, 
+		"What are anonymous users?"
+){public String answer(){return 
+		"\r\n		<p>\r\n			Anonymous users are users that didn't register with Nabble.\r\n			All Nabble requires from them is a name that is displayed together with the message.\r\n			Forums that allow anonymous users tend to have more participation, but they might generate confusion when users provide false or deceiving names.\r\n			As the forum owner, you can always remove those messages from the forum.\r\n		</p>\r\n		<p>If you don't want anonymous users in your forum, you can disable them by accessing <b>Options > Users > Control anonymous users</b>.</p>\r\n	"
+;}};
+
+	public static final Help mixed = new Help(
+		43, 
+		"How can I fix a mixed bulletin board?"
+){public String answer(){return 
+		"\r\n		<p>\r\n			In traditional forums, a bulleting board only shows subcategories where users are allowed to post messages.\r\n			With Nabble, any forum can be displayed as a bulletin board and this might create a strange board with threads and forums mixed in the same list.\r\n			This situation is not easy to create since Nabble doesn't allow users to post new topics under a board,\r\n			but you could face this problem if you change a regular forum into a bulletin board format or move threads directly to the board.\r\n		</p>\r\n		<p>\r\n			A mixed board generally isn't a good idea and should be fixed in order to avoid confusion.\r\n			Note that Nabble doesn't hide threads in that view because this would increase the confusion among users.\r\n			It is the forum owner's responsibility to keep the forum clean and organized.\r\n		</p>\r\n		<h3>How to fix it?</h3>\r\n		<p>\r\n			If your forum has a mixed bulletin board, do the following:\r\n		</p>\r\n		<ol>\r\n			<li style=\"padding-bottom:.3em\">If you have just a few threads under the board, you can move them one by one to the appropriate sub-forum.</li>\r\n			<li>\r\n				If the number of threads is high and moving one by one is inefficient, you should create a new board and move the current forum as a sub-forum of it.\r\n				You might have to move other sub-forums around in order to rebuild the bulletin board.\r\n			</li>\r\n		</ol>\r\n	"
+;}};
+
+	public static final Help subscriptions = new Help(
+		45, 
+		"Is it possible to post messages through email?"
+){public String answer(){return 
+		"\r\n		<p>\r\n			Yes. Nabble has email subscriptions that allow you to receive emails for each message\r\n			posted under a forum. If you reply to those emails, your response will be appended to\r\n			the discussion in the forum archive.\r\n		</p>\r\n		<p>\r\n			To subscribe to a forum, open the forum page and click on <i>Options > Subscribe via email</i>.\r\n			You can choose if you want to receive an email for each message posted or just new topics.\r\n		</p>\r\n\r\n		<h2>Detailed Explanation</h2>\r\n\r\n		<p>\r\n		All forums at Nabble have a built-in mailing list that people can use to communicate.\r\n		In other words, users can compose, read and reply to messages via email independently of the forum web interface.\r\n		</p>\r\n\r\n		<p>If you want to subscribe to a mailing list, open the forum interface and click on \"Options > Subscribe via email\" as seen below:</p>\r\n\r\n		<p><img src=\"/images/help/subscribe.png\" alt=\"Subscribe via email\"></p>\r\n\r\n		<p>Here you can see the options when you click on that link:</p>\r\n\r\n		<p><img src=\"/images/help/subscribe2.png\" alt=\"Subscription options\"></p>\r\n\r\n		<p><b>(1) Individual emails</b> - You receive one email for each new post under the forum. If you reply to such emails, your message will be archived as a new reply on the forum.</p>\r\n\r\n		<p><b>(2) Digest</b> - You receive one email with a summary of all posts created in the last 24 hours. You can't reply to this type of email.</p>\r\n\r\n		<p>To manage your subscriptions, click on your profile (top right corner), then click on \"Personal Settings > Email Subscriptions\". From that screen you can change and remove subscriptions.</p>\r\n\r\n		<h2>Posting via Email</h2>\r\n\r\n		<p>To know the email of a given forum, go to the forum and click on \"Options > Post by email...\". To prevent spam, forum addresses are unique for each user. So you can't get a forum address and send it to your friends (they must click on that link).</p>\r\n	"
+;}};
+
+	public static final Help embed_js_options = new Help(
+		48, 
+		"Can I change other behaviors of an embedded forum?"
+){public String answer(){return 
+		"\r\n		<p>\r\n			Yes. Besides the <a href=\""
+		+(Help.embed_redirect.url())
+		+"\" rel=\"nofollow\">redirect option</a>, we have a few options that you can use to change other behaviors of your embedded forum.\r\n			These options must be set as javascript variables in your HTML page (place them before the embedding code).\r\n			For example, you can add the following code to your page:\r\n		</p>\r\n		<div style=\"font-family:monospace;margin:1em;\">\r\n			&lt;script type=\"text/javascript\"><br>\r\n			&nbsp;&nbsp;&nbsp;nabble_width = \"500px\";<br>\r\n			&nbsp;&nbsp;&nbsp;nabble_ignore_scroll = true;<br>\r\n			&nbsp;&nbsp;&nbsp;nabble_ignore_title = true;<br>\r\n			&lt;/script><br>\r\n		</div>\r\n		<style type=\"text/css\">\r\n			table.info td {\r\n				padding: .5em;\r\n			}\r\n			tr.header td {\r\n				font-weight:bold;\r\n				padding: .1em .5em;\r\n			}\r\n		</style>\r\n		<table class=\"info\">\r\n			<tr class=\"header\">\r\n				<td class=\"shaded-bg-color\">Variable</td>\r\n				<td class=\"shaded-bg-color\">Description</td>\r\n			</tr>\r\n			<tr>\r\n				<td>nabble_width</td>\r\n				<td>\r\n					Use this variable if you want to have an embedded forum with fixed width.\r\n					The default value is \"100%\" and you can change it according to standard CSS rules\r\n					(e.g., \"50%\", \"10em\", \"500px\").\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td>nabble_ignore_scroll</td>\r\n				<td>\r\n					Set this variable to <b>true</b> if you don't want the embedded forum to\r\n					scroll to specific posts or messages. By default, the embedded forum will\r\n					eventually scroll the page to specific messages depending on the user navigation\r\n					and clicked links.\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td>nabble_scroll_top</td>\r\n				<td>\r\n					Set this variable to <b>true</b> if you want the embedded forum to\r\n					scroll to the top of your HTML page whenever the user navigates through the forum.\r\n					By default, the forum will scroll to its top, which is not necessarily the top of your\r\n					HTML page.\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td>nabble_ignore_title</td>\r\n				<td>\r\n					Set this variable to <b>true</b> if you don't want the embedded forum to\r\n					change the title of your HTML page. By default, the embedded forum will\r\n					set the title of your page with the forum name.\r\n				</td>\r\n			</tr>\r\n		</table>\r\n	"
+;}};
+
+	public static final Help online = new Help(
+		49, 
+		"Is it possible to show which users are online?"
+){
+		public String getMetaDescription() { return "Nabble applications display who is online when you browse forums, photo galleries, newspapers, blogs and mailing list archives."; }
+		public String getMetaKeywords() { return "online users, forum, photo gallery, newspaper, blog, mailing list archive, free forum, free blog, free photo gallery"; }
+		public String answer(){return 
+		"\r\n		<p>\r\n			Yes, all Nabble applications (e.g., forum, photo gallery, newspaper, blog, etc.) have this feature.\r\n			Online users have a green circle at the bottom right corner of their avatars.\r\n		</p>\r\n	"
+;}};
+
+	public static final Help domain_names = new Help(
+		51, 
+		"Can I access my forum with my own domain name?"
+){
+		public String answer(){return 
+		"\r\n		<p>\r\n			Yes. Nabble allows you to change the domain name configuration of your application (e.g., forum, gallery, blog, news, etc.)\r\n			by clicking on \"Options > Editor > Change domain name\". If you decide to use your own domain name, you will have to change\r\n			its DNS configuration and make it resolve to the Nabble server where your forum is located (you can find instructions on the configuration screen).\r\n		</p>\r\n		<p>\r\n			There are some advantages about custom domain names that you should be aware of. One of them is that you can &ndash; for example &ndash;\r\n			use Google Custom Search to let users search your forum and your website at the same time. Another advantage is that Nabble will allow you\r\n			to run custom JavaScript code in your pages <i>(to be implemented / Templates feature)</i>.\r\n		</p>\r\n	"
+;}};
+
+	public static final Help inactivity_deletion = new Help(
+		53, 
+		"Will Nabble delete my forum, posts or anything I have created?"
+){
+		public String answer(){return 
+		"\r\n		<p>\r\n			Nabble has a garbage collector process that searches for <b>inactive</b> forums, topics or messages\r\n			in the database. If you receive an email with forums scheduled for deletion, you have to first understand\r\n			why they are in that list. The most common cases are:\r\n		</p>\r\n		<p>\r\n			<b>(1) Your forum hasn't been viewed by anyone recently</b>:\r\n			If your forum is not dead, you can just save it from deletion by following the\r\n			instructions on its page.\r\n		</p>\r\n		<p>\r\n			<b>(2) Your post was removed from the forum</b>: the forum owner (or some other user\r\n			that you replied to) might have moved your post (and replies) out of the forum. This is different from\r\n			physical deletion. When a post is removed from a forum, it still exists in Nabble's database and can be found in\r\n			the author's profile page. Such messages are not part of any forum and are probably not read by anyone.\r\n			So you shouldn't care much about those messages.\r\n		</p>\r\n\r\n		<p class=\"shaded-bg-color\" style=\"padding:.5em\">\r\n			In summary, only root nodes are checked for deletion.\r\n			This means that only threads that have been removed or inactive root level forums will be caught by the garbage collector process.\r\n		</p>\r\n\r\n		<h2>Each visit counts</h2>\r\n		<p>Threads inside an active forum will NOT be scheduled for deletion, even if they don't get any visits for a long time\r\n		(although other threads in the same forum must get visits in order to keep the forum active). <b>So each visit adds activity\r\n		points to the whole structure</b>. On the other hand, when a post or any node is removed from the structure,\r\n		it will become a separated structure with its own activity level.</p>\r\n\r\n		<h2>How deletion works</h2>\r\n		<p>If your forum gets deleted, the threads under it are not immediately deleted.\r\n		What happens is that after the forum is deleted, these threads become top level independent nodes and\r\n		then they are subject to the same deletion process after some weeks.  So if a forum owner allows his forum\r\n		to be deleted, thread starters can still save their threads.</p>\r\n\r\n		<p class=\"shaded-bg-color\" style=\"padding:.5em;font-weight:bold\">In any case, you can save your forums, topics and messages from deletion by following the\r\n		instructions on their page. You should also know that you can download the archives of your application\r\n		by clicking on \"Options > Download archives\" (if you are the administrator).\r\n		<a href=\""
+		+(Help.export.url())
+		+"\">Click here</a> for more information.</p>\r\n	"
+;}};
+
+	public static final Help export = new Help(
+		54, 
+		"Can I export my forum to a standard format?"
+){
+		public String answer(){return 
+		"\r\n		<p>Yes, Nabble can export your forum data, subcategories and messages to the standard XML format.\r\n			You can download these files by clicking on \"Options > Download archives\". You will realize that Nabble\r\n			doesn't have these files ready for you, so first you will have to build them. This process may take some\r\n			minutes or even hours depending on the size of your application. You will receive an email when this process\r\n			has finished.</p>\r\n		<p>To make downloading easier, Nabble groups XML files into zip archives. Each zip archive contains thousands\r\n			of XML files, where each file represents a node in the forum structure (<a href=\""
+		+(Jtp.homeContextUrl())
+		+"/back-end.html\">learn more</a>).\r\n			Below you can find the description of each field in the XML file, which may help you in creating a parser for this information.</p>\r\n\r\n			<table class=\"editor-table\" cellpadding=\"5\" style=\"font-size:90%;\">\r\n			<tr class=\"shaded-bg-color\">\r\n			<th align=\"left\">Field</th>\r\n			<th align=\"left\">Description</th>\r\n			</tr>\r\n			<tr>\r\n			<td>exportId</td>\r\n			<td>ID of the node represented by the XML file. This ID may be referenced by other XML files as a way to express relationship.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>kind</td>\r\n			<td>Kind of the node (\"APP\" for applications or \"POST\" for messages).</td>\r\n			</tr>\r\n			<tr>\r\n			<td>ownerEmail</td>\r\n			<td>Email of the user who created the node represented by the XML file.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>ownerName</td>\r\n			<td>Name of the user who created the node represented by the XML file.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>ownerAnonymousId</td>\r\n			<td>If the node was created by an anonymous user, this ID will identify this user (anonymous users are not registered and thus have no email information).</td>\r\n			</tr>\r\n			<tr>\r\n			<td>subject</td>\r\n			<td>Title/subject of this node.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>message</td>\r\n			<td>Message contents of this node.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>msgFmt</td>\r\n			<td>Format of the message (m=mail, h=html or t=text).</td>\r\n			</tr>\r\n			<tr>\r\n			<td>parentId</td>\r\n			<td>ID of the parent node.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>whenCreated</td>\r\n			<td>Date/Time when the node was created (number of milliseconds since January 1, 1970, 00:00:00 GMT)</td>\r\n			</tr>\r\n			<tr>\r\n			<td>hasReplyAlert</td>\r\n			<td>true/false if user wants to receive new replies by email.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>whenUpdated</td>\r\n			<td>Date/Time when the node was updated (number of milliseconds since January 1, 1970, 00:00:00 GMT)</td>\r\n			</tr>\r\n			<tr>\r\n			<td>restriction</td>\r\n			<td>Type of restriction for this node (i.e., indicates who can view and post messages under this node): NONE, REGISTERED, PROTECTED_CHILDREN, REGISTERED_PROTECTED_CHILDREN, PROTECTED, PRIVATE, PROTECTED_READ_ONLY, PRIVATE_READ_ONLY.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>type</td>\r\n			<td>Node/Application type (FORUM, BOARD, CATEGORY, NEWS, GALLERY, BLOG, COMMENT)</td>\r\n			</tr>\r\n			<tr>\r\n			<td>customStyle</td>\r\n			<td>Custom CSS stylesheet created for this node.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>pin</td>\r\n			<td>Order of this node in the pinned list of the parent node.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>files</td>\r\n			<td>Files attached to this node. Creates one entry for each file. Contents are byte arrays encrypted using Base64 encoding.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>messageID</td>\r\n			<td>\"Message ID\" header of the email sent for this node (only for mailing list archives).</td>\r\n			</tr>\r\n			<tr>\r\n			<td>isGuessedParent</td>\r\n			<td>true/false if Nabble had to guess the parent of this node (only for mailing list archives).</td>\r\n			</tr>\r\n			<tr>\r\n			<td>mlAddress</td>\r\n			<td>Email address of the mailing list archived by this node (only for mailing list archives).</td>\r\n			</tr>\r\n			<tr>\r\n			<td>mlUrl</td>\r\n			<td>Website URL of the mailing list archived by this node.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>mlPlainTextOnly</td>\r\n			<td>true/false if this mailing list accepts only plain-text emails.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>mlIgnoreNoArchive</td>\r\n			<td>true/false if Nabble should ignore the X-No-Archive header in emails sent to this mailing list archive.</td>\r\n			</tr>\r\n			<tr>\r\n			<td>mlServer</td>\r\n			<td>Mailing list server type (e.g., google groups, yahoo groups, mailman, etc.)</td>\r\n			</tr>\r\n			</table>\r\n	"
+;}};
+
+	public static final Help pinned_subapps = new Help(
+		55, 
+		"What is the difference between pinned and unpinned sub-forums?"
+){
+		public String answer(){return 
+		"\r\n			<p>\r\n				Before addressing this question, you have to understand how things work in the background.\r\n				Essentially, Nabble has a node-architecture that resembles a file system, where forums (and other apps)\r\n				are like folders and posts are like files. Forums and posts can be pinned to their parent in order to always\r\n				be displayed on top. Here is an illustration:\r\n			</p>\r\n\r\n			<p><img src=\"/images/help/help_node_structure.png\" style=\"padding-left:2em\"/></p>\r\n\r\n			<p>\r\n				Only forum owners can pin sub-forums and topics. Actually, sub-forums created by owners are automatically\r\n				pinned, and this makes a big difference to the forum structure. Nabble gives more priority to pinned\r\n				sub-forums when it comes to how the forum front page looks. Unpinned sub-forums (also called\r\n				<b>floating sub-forums</b>) are not part of the real forum structure and they float like normal topics.\r\n				Here is a screenshot:\r\n			</p>\r\n\r\n			<p><img src=\"/images/help/help_sub_forums.png\" style=\"padding-left:2em\"/></p>\r\n\r\n			<p>\r\n				As you can see, floating sub-forums aren't much different than normal topics (except they have a folder\r\n				icon close to them). When they receive new posts, they jump to the top just like other topics that receive\r\n				new replies.\r\n			</p>\r\n	"
+;}};
+
+	public static final Help mixed_lengths = new Help(
+		56, 
+		"How can I customize the \"Mixed\" application type?"
+){
+		public String answer(){return 
+		"\r\n		<p>\r\n			If you have an app with the Mixed type and you want to customize the number of topics in the front page, first click on \"Options > Application > Change appearance\".\r\n			In the \"Preferences\" group, there is a field for the number of topics in the mixed style:<br/><br/>\r\n			<img src=\"/images/help/mixed_lengths.png\" alt=\"topics configuration for mixed app type\"/>\r\n			<br/><br/>\r\n			Enter a comma-separated list of values that represent how many topics each section of the mixed view should display.<br>\r\n			<b>1st value</b> = number of topics to be displayed for the current application<br>\r\n			<b>2nd value</b> = number of topics to be displayed for the first subcategory<br>\r\n			<b>3rd value</b> = number of topics to be displayed for the second subcategory<br>\r\n			and so on...<br>\r\n			The <b>last number</b> is used for all other sections.<br>\r\n			<div class=\"important\">(All values must range from 1 to 20)</div>\r\n			<br>Some examples:<br>\r\n			<b>6</b> = All sections will display 6 topics.<br>\r\n			<b>6,3</b> = 6 topics for the current application, 3 topics for all subcategories.<br>\r\n			<b>6,7,6</b> = 6 topics for the current application, 7 topics for the fist subcategory and 6 topics for all other subcategories.<br>\r\n			<b>6,5,5,5,6</b> = 6 topics for the current application, 5 topics for the first three subcategories and 6 topics for all other subcategories.<br>\r\n		</p>\r\n	"
+;}};
+
+	public static void index() {
+		Lucene.addHelp(map.values());
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/lib/help/Help.jmp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1402 @@
+<%
+package nabble.view.lib.help;
+
+import fschmidt.html.Html;
+import fschmidt.util.java.HtmlUtils;
+import nabble.model.Lucene;
+import nabble.model.Message;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.web.template.HtmlListNamespace;
+import nabble.modules.ModuleManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public abstract class Help {
+	private static final Logger logger = LoggerFactory.getLogger(Help.class);
+
+	private static final Map<Integer,Help> map = new HashMap<Integer,Help>();
+	public final int id;
+	public final String question;
+
+	Help(int id,String question) {
+		this.id = id;
+		this.question = question;
+		if( map.put(id,this) != null )
+			throw new RuntimeException("duplicate id");
+	}
+
+	public abstract String answer();
+	public String getMetaDescription() { return null; }
+	public String getMetaKeywords() { return null; }
+
+	public String path() {
+		return "/help/Answer.jtp?id=" + id;
+	}
+
+	public String url() {
+		return Jtp.defaultContextUrl()+path();
+	}
+
+	public String link() {
+		return %><a href="<%=url()%>"><%=question%></a><%;
+	}
+
+	public String url(HttpServletRequest request) throws IOException {
+		return url();
+	}
+
+	public String link(HttpServletRequest request) throws IOException {
+		return link();
+	}
+
+	public static Help getHelp(int id) {
+		return map.get(id);
+	}
+
+	public static final Help what = new Help(
+		1, %>What is Nabble?<%){public String answer(){return %>
+		<p>
+		Nabble wants to improve public discussions on the web and provide useful embeddable applications to end users.
+		This includes forums, user groups, message boards, mailing lists, photo galleries, newspapers, blogs, etc.  There are many vibrant discussions in these places, so are problems such as cluttered UI, broken search, moderation, and cataloging. Nabble wants to be a place where your discussion can grow and be free of these problems.
+		</p>
+	<%;}};
+	public static final Help free = new Help(
+		2, %>Is Nabble really free?<%){public String answer(){return %>
+		<p>
+		Yes. Nabble is absolutely free and will remain free to the users. This includes the end users,
+		mailing list owners, and webmasters. If your website uses Nabble as a hosted forum or a list archive,
+		be assured that there is no limit on traffic or disk space, no hosting fees, no pay-for versions or label schemes.
+		Nabble is just free.
+		</p>
+	<%;}};
+	public static final Help add = new Help(
+		4, %>How do I add my mailing list to Nabble archive?<%){public String answer(){return %>
+		<p>
+		Go the <a href="/">Nabble home page</a>, and click on the <a href="/more/MailingListRequest.jtp">Archive Mailing list</a> link.
+		Fill out the form and follow the instructions there.
+		</p>
+	<%;}};
+	public static final Help find = new Help(
+		5, %>How do I find my mailing list on Nabble?<%){public String answer(){return %>
+		<p>
+		Search your mailing list address from <a href="/">Nabble home page</a>. If there is a match, you will find a forum link in the upper part of the result page.
+		</p>
+	<%;}};
+	public static final Help owner = new Help(
+		6, %>Do I have to be the list owner to archive a list at Nabble?<%){public String answer(){return %>
+		<p>
+			No. Anybody can add a list to Nabble archive.
+			If you subscribe to a list, and find yourself receiving too many emails, or having a hard time searching through the messages, you can consider getting a Nabble archive for the list.
+			This way you can view, search, post, entirely from the web without cluttering your inbox.
+		</p>
+	<%;}};
+	public static final Help why = new Help(
+		7, %>Why is my mailing list archived without my knowledge?<%){public String answer(){return %>
+		<p>
+		Mailing list archive is not something new. Besides Nabble, a public mailing list is very likely archived somewhere else.
+		For example, try searching on <a href="http://gmane.org/find.php" target="_top" rel="nofollow">Gmane</a>, <a href="http://www.mail-archive.com/" target="_top" rel="nofollow">Mail-archive</a>, <a href="http://marc.theaimsgroup.com/" target="_top" rel="nofollow">MARC</a>, and <a href="http://opensubscriber.com/" target="_top" rel="nofollow">OpenSubscriber</a>.
+		They have all existed long before Nabble. Nabble may have found your public mailing list from these places.
+		Or, your list's members may have added your list to Nabble's archive.
+		</p>
+		<p>
+		If you feel upset because nobody asked you for permission, take it easy.
+		An archive works like Google. Google will use a crawler to crawl your site to collect the web pages and index them,
+		much the same way as an archive using an email address to subscribe to your list to receive the messages and index them.
+		Were Google to ask permission from every webmaster, it would take too long, and there
+		would be no Google or search engines. But, if you don't like it, we will delete it.
+		</p>
+	<%;}};
+	public static final Help sell = new Help(
+		10, %>Can Nabble sell the data (archive)?<%){public String answer(){return %>
+		<p>
+		It's like asking can Google sell the web pages in its index?
+		Absolutely not. A message belongs to its author. Besides, an email address is sacred.
+		</p>
+	<%;}};
+	public static final Help post = new Help(
+		11, %>Did Nabble break my subscription policy?<%){public String answer(){return %>
+		<p>
+		Nabble supports many mailing list software such as mailman, ezmlm, and listserv. For all supported list software, Nabble conforms to the mailing list subscription policy. A non-subscriber will be prompted to subscribe to the mailing list before he/she can post to the mailing list. Without subscription, the post will be rejected as a non-subscriber post. Note that only registered users can post from Nabble. The Nabble registration process is the same as the mailing list subscription process. This means extra protection and validation.
+		</p>
+		<p>
+		You can think of Nabble as a web interface to your list. Without it, a user will have to view and post from an email box that tends to clutter. With Nabble, users who prefer the web view can post from the web, and interact with the same email users seamlessly.
+		</p>
+		<p>
+		The feature of bridging a forum and a list is not new. It is called mail-to-news gateway - Gmane.org probably has already archived your list this way. If you still have concerns, please contact us.
+		</p>
+	<%;}};
+	public static final Help userid = new Help(
+		12, %>Why do I see a user account with my name when I haven't registered?<%){public String answer(){return %>
+		<p>
+		Nabble archives public mailing lists and will create user accounts for users on these lists.  If you have posted to the  mailing lists before, Nabble may have created an unregistered account for you.  Since this account is unregistered, you cannot use it until you register with Nabble.  When you register with Nabble using the same email address as you used on the mailing list, you will be able to take ownership of this account and will be able to post messages.
+		</p>
+		<p>
+		It is important that you use the same email address (that you used for the mailing list) to register because Nabble identifies users by their email addresses.  If you use an email address different from the one that you used for the mailing list, you are just creating a new account.  Nabble currently does not support merging of multiple user accounts.
+		</p>
+	<%;}};
+	public static final Help delpost = new Help(
+		13, %>How do I delete my posts?<%){public String answer(){return %>
+		<p>
+		Log in to (or register with) Nabble and then you can find and delete your posts on Nabble.
+		</p>
+		<p>
+		Please note that Nabble archives public mailing lists. If you post to a public mailing list, then your message will become public records and anybody can archive it. There are several public mailing list archives like Nabble, for example, Gmane and mail-archive.com; in addition, a mailing list usually has its own native archive. Once you make a public post, it's hard to remove it from the web.
+		</p>
+		<p>
+		Most archives do not allow you to delete your message. With Nabble, you can. But you will need to register with Nabble using the same email address that you used for your post. Please note that we have no particular interest in knowing your email address because your email address is already available to public through the mailing list archives. We require you to register only because we need to verify that you are the owner of the post. After you register, log in, go to your post, and you will find a "delete" link to delete your post.
+		</p>
+	<%;}};
+	public static final Help pending = new Help(
+		15, %>Why is my post "pending"? How to fix it?<%){public String answer(){return %>
+		<p>
+		Your post is pending because you posted it to a special Nabble forum which is both a forum and a web
+		gateway to a mailing list. Your message is already posted on Nabble, but it has not been accepted by
+		the mailing list yet.
+		</p>
+		<p>
+		A mailing list is a subscriber-only email forwarding service. A subscriber sends email to the
+		mailing list, and the mailing list fowards his email to all subscribers. This allows subscribers to discuss
+		topics by reading and replying via email. Nabble works as a bridge to forward your post to the mailing list.
+		If you are a subscriber, the mailing list will take your post and forward it to all subscribers.
+		But if you are not, then your post will be rejected and thus it will be in a "pending" status.
+		</p>
+		<p>
+		<strong>Are you a registered user of Nabble?</strong> Please note that being registered with Nabble does not mean that you are a subscriber to mailing lists. In fact, each mailing list requires a separate subscription.
+		</p>
+		<p>
+		Have you confirmed your subscription yet? You may have just clicked a "Subscribe" button, but may not have followed
+		through with the two additional steps:
+		</p>
+		<ol>
+			<li>Check your email for a confirmation email from the mailing list.
+			<li>Follow the instruction in the email to confirm subscription.
+		</ol>
+		<p>
+		Sounds complicated? You bet. But the good part of this mechanism is that it helps to filter out careless posters.
+		Because most users on a mailing list are dedicated and careful users, you are likely to get quality responses
+		once your post goes through.
+		</p>
+		<h3>How to fix pending messages?</h3>
+		<p>
+			Nabble doesn't allow you to resend your message to the mailing list.
+			The right way of doing this is to compose a new message, which will be sent as a new email.
+			You might want to delete your old pending messages in order to keep the forum clean and organized.
+			To find your pending messages, go to <b>Your Account > Account Settings > Pending Posts</b>.
+		</p>
+		<p>
+		The most important thing is to make sure you are a subscriber.
+		If you haven't subscribed yet, do it and wait for confirmation.
+		Then follow the instruction to confirm.
+		If you don't remember whether you have subscribed before, subscribe again.
+		After you have confirmed a subscription, go back to your pending message and repost the message.
+		</p>
+
+		<h3>I am sure I have subscribed, but why is my message still pending?</h3>
+		<p>
+		A post to a mailing list will appear as pending right after being posted.
+		This is normal because it takes a few minutes for your post to get forwarded and processed by a mailing list server.
+		Usually, if you are a subscriber, after a few minutes, your pending message will get posted and the pending messages page will remove that post from the list.
+		</p>
+		<p>
+		When you are a subscriber and you see a post pending for several hours after your have posted, it could be that the mailing list server is slow or unavailable.
+		Your post may be pending simply because the mailing list server has not processed it yet.
+		Try going to the mailing list website to see if it is slow. Don't post repeatedly. Try reposting in a day or two.
+		</p>
+		<p>
+		Sometimes mailing lists may reject your message for format or editorial reasons. For example, some mailing lists
+		do not accept HTML formats, and some forbids certain language or content. Usually, the mailing list will reject
+		your post and email you a notice with an explanation. In such cases, go to the pending post to "Edit" it so that
+		it conforms with the mailing list requirement, then post again. Or, you can simply delete your post.
+		</p>
+	<%;}};
+	public static final Help moderation = new Help(
+		16, %>How do I remove SPAM or bad users from my forum?<%){public String answer(){return %>
+		<p>
+			SPAM and bad posts are common problems for forums.
+			With Nabble, forum owners can easily remove unwanted messages and ban bad users.
+			Both actions are available in the dropdown menu close to the message.
+		</p>
+		<p>
+			We should remember that anonymous users can't be banned.
+			However, you can disable anonymous posts for your forum by accessing the <b>Options > Users > Control anonymous users</b> action in the forum options menu.
+		</p>
+	<%;}};
+	public static final Help mailingListIntro = new Help(
+		17, %>What is a "mailing list"?<%){public String answer(){return %>
+		<p>
+		A mailing list is an email forwarding service for subscribers. People set up a mailing list to distribute information about a certain topic. If you are interested in the topic, you can subscribe and then start receiving messages via email. You can also email your questions or relevant information to the mailing list which will then forward them to all other subscribers. Some people call "mailing list" the email discussion group.
+		</p>
+		<h2>Nabble provides a web archive/gateway to mailing lists.</h2>
+		<p>
+		Nabble works as a web archive to mailing lists to allow search and browsing. Also it allows web users to post messages through Nabble which then forwards it as an email to the mailing list. This means web users can join an email discussion through the web and all users are in sync.
+		</p>
+		<p>
+		Nabble supports many mailing list software. For all the supported listserv software, Nabble conforms with the mailing list's subscription policy. This means that a user will need to become a subscriber to a mailing list before he can post through the Nabble web archive.
+		</p>
+		<p>
+		<b>Please note:</b> being a registered Nabble user does not mean you are a subscriber to a mailing list. Each mailing list requires separate subscription.
+		</p>
+	<%;}};
+	public static final Help stopEmails = new Help(
+		18, %>Why do I receive emails after I made a post on Nabble? How to stop it?<%){public String answer(){return %>
+		<p>
+		If you find yourself receiving many emails (not spam, just regular emails) after making a post on Nabble, don't worry, it can be easily fixed.
+		</p>
+		<p>
+		What happened is that your were probably posting through Nabble to a "mailing list", also known as the email discussion group,
+		and it requires that you become a subscriber before you can post. You probably remember seeing the prompt to subscribe.
+		You have to do that in order for your post to go out, but as a result, you subscribed and that is why you are receiving
+		the emails. You should also have received an automatic email from Nabble with a link that you can click to stop
+		receiving emails. If you missed that email or don't remember, continue reading.
+		</p>
+		<h3>How to stop it?</h3>
+		<ol>
+		<li>Go to the forum where you posted a message.
+		<br/>If you remember where you posted, go there directly. If you don't, try to find your post first, then go up one level
+		to the forum level. Or, you can search Nabble by the email address of the mailing list. You can find the email address by
+		looking for the "to:" field in those unwanted emails that you received from the mailing list.
+		<br/><br/>
+		<li>Click the "mailing list options" link.
+		<br/>In the subsequent page you will find options to turn off email delievery or simply unsubscribe. Follow through
+		the instructions there.
+		</ol>
+	<%;}};
+	public static final Help stopAlert = new Help(
+		19, %>How do I stop an email alert?<%){public String answer(){return %>
+		<p>
+		At the end of an email alert, there is always a link to stop it. Click that link and you will no longer be alerted.
+	<%;}};
+	public static final Help restrictions = new Help(
+		20, %>How do I control who can view and post in my forum? <%){public String answer(){return %>
+		<p>
+			Under the "Options" menu, under "Users", is the option "Who can view & post".
+			This page controls who can view and post in this forum.  But if this forum has a parent forum
+			and if the parent forum has restrictions, then those restriction will also apply.
+			This means that restrictions are only added as you go down your forum hierarchy.
+			This makes sense because when you set restrictions, you assume that they will apply to
+			all sub-forums as well.
+		</p>
+		<p>
+			Let's look at a few examples.  Suppose you have a forum hierarchy and want to ban anonymous users
+			throughout. You would go to the top forum and select that only registered users can create new topics and replies:
+		</p>
+		<p><img src="/images/help/registered_only.png" width=505 height=108 style="padding-left:2em"/></p>
+		<p>
+			Now suppose that under the top forum, you have an "Official Business" forum where only members of
+			a group should be allowed to post.  You would select that only members can create new topics and replies:
+		</p>
+		<p><img src="/images/help/members_only.png" width=505 height=108 style="padding-left:2em"/></p>
+		<p>
+			You would then authorize the right members for that "Official Business" forum only.
+		</p>
+		<p>
+			Now suppose that under that forum, you have an "Announcements" forum where only the organizer should
+			post.  The organizer should create that forum and select again that only members can post and reply in that forum,
+			but this time not authorize anyone else.  Now only the owner of the "Announcements" forum is authorized
+			to post there.  But suppose you want to allow members to comment on announcements but not to
+			make announcements.  Then instead of members can create new topics, but let everyone reply.
+			Note that "Everyone" means everyone who would otherwise be allowed to post.  So now within the "Announcements" forum,
+			only the owner can create threads, but all members can reply to those threads.
+		</p>
+	<%;}};
+
+	public static final Help copy = new Help(
+		9, %>Isn't Nabble trying to copy and replace me?<%){public String answer(){return %>
+		<p>
+		Nabble wants to be useful and transparent.  If you feel shocked on seeing the Nabble mirror of your content,
+		please relax and be assured that the content is still yours, and you can delete it anytime. Then, before cursing
+		Nabble, try asking yourself:
+		</p>
+
+		<ul>
+		<li><p>Is Nabble useful?
+		<br>You probably will find the archive search useful. Nabble search is fast, and you can cross search all your
+		lists instead of searching each list or relying on Google's "site:archive url" search (which is a hack because
+		Google does not index all your posts, especially the new ones). What's more, some of your users may want a web
+		interface to read and post instead of using a cluttered email box. With Nabble, these users can talk from the
+		web and interact with the traditional email users seamlessly.
+		</p>
+		<li><p>Is Nabble taking users away from my site?
+		<br>We understand this concern. You put in effort to grow your users, and we have no right to get in between.
+		What we offer is an <a href="<%=Help.embed_what_how.url()%>" target="_top" rel="nofollow">"embeddable" forum</a>.
+		It means that you can customize the Nabble mirror to "embed" it into your website. The Nabble mirror will still be hosted at
+		Nabble, but you can control the look and feel so that it looks like a part of
+		your site, complete with your logo, style sheet, javascript, and even your ads.
+		</p>
+		<li><p>Is Nabble bypassing me?
+		<br>No. <a href="<%=Help.post.url()%>" target="_top" rel="nofollow">Nabble conforms with your subscription policy</a>.
+		This means if users want to post to your list via Nabble, they will have to join your list to become subscribers.
+		</p>
+		<li><p>What's in it for Nabble?
+		<br>Nabble wants to become the best place for online discussions. That's it for now. We don't know
+		yet how to make money.
+		</p>
+		</ul>
+		<p>
+		Keep in mind that by mirroring your discussion on Nabble, you could also expand your subscribers through
+		Nabble users. They may join your discussions and thus expand your membership.
+		They could also go from the discussion page to visit your website.
+		</p>
+	<%;}};
+	public static final Help listSpam = new Help(
+		22, %>My list received spam from Nabble. What do I do to prevent it from happening again?<%){public String answer(){return %>
+		<p>
+		We hate spam. We sincerely apologize if a spam comes to your list through Nabble.
+		</p>
+		<p>
+		Please let us know through the <a href="<%=Jtp.supportUrl()%>">support forum</a> if you see or suspect spam. We will take actions immediately.
+		</p>
+		<p>
+		Forum owners can also <a href="<%=Help.moderation.url()%>" target="_top" rel="nofollow">delete spam and ban bad users</a>.
+		</p>
+	<%;}};
+	public static final Help cataloging = new Help(
+		24, %>What's the right way to organize forums and messages?<%){public String answer(){return %>
+		<p>
+			Nabble offers a flexible way to organize forums and messages.
+		</p>
+		<p>
+			An active forum is great, but its many users and diverse interests often drive the discussion off topic. Nabble tries to address this problem by allowing users to easily create child forums. A child forum structure helps to keep messages focused and properly categorized. The hierarchy is displayed at the parent level for easy drill down. Users who want to ignore off-topic discussions can drill down to the relevant child; and the users who don't mind reading all the messages can still do it at the parent forum level.
+		</p>
+		<h3>Create a child forum only when it is truly needed</h3>
+		<p>
+			Creating child forums is a powerful way to add a structure to your discussion, but you should be careful to avoid overdoing it. For example, if you are in a new forum with few messages and users, why bother creating a fancy structure? No matter how well structured, a forum without users is still a dead forum. What's more, a structure that goes too wide or too deep could get people confused as to where to visit or post, thus making it difficult for them to join the discussion. We recommend that you create child forums only when it is truly needed.
+		</p>
+		<h3>Force users to select a child forum to post</h3>
+		<p>
+			After you have created child forums, you may find users ignore it when they post, and most of the messages continue to get posted at the parent level. In such cases, don't just blame the users. Take a good look at your design and ask, does it make sense? Is it clear and easy to follow? Is the hierarchy too wide or too deep? If you feel certain that the structure is sound, and that you and a few other forum members support it, then you can use the "Who Can View & Post" feature to <a href="<%=Help.restrictions.url()%>">make the parent forum read-only</a>. This means that a user cannot post a new message in the parent forum. They will be prompted to drill down to a child forum to post there.
+		</p>
+		<h3>Keep the hierarchy efficient</h3>
+		<p>
+			You can build an elaborate structure by using a combination of parent and child categories and forums, but don't overdo it. The goal of a hierarchy is to help users navigate. Does yours help people get to the right forum quickly? For example, if a category has only one child forum under it, then it is a waste, because it just means a useless extra click for navigation.
+		</p>
+	<%;}};
+	public static final Help password = new Help(
+		25, %>How does Nabble store passwords?<%){public String answer(){return %>
+		<p>
+			Nabble does not store your password in plain text. Only a cryptographic digest of you password is kept in our database. This is why if you forget your password we cannot email it back to you. But if you do forget it, we can always email you a link which allows you to reset your password and pick a new one.
+		</p>
+		<p>
+			It is a good security practice, however, to use different passwords for different websites, so please do not choose a password that you already use elsewhere.
+		</p>
+	<%;}};
+	public static final Help seo = new Help(
+		27, %>Is my application optimized for search engines?<%){public String answer(){return %>
+		<p>
+		Yes. Nabble's content pages are designed with SEO (Search Engine Optimization) in mind.
+		Our SEO effort goes further beyond the usual tags and headers to advanced techniques such as URL encoding and PageRank distribution.
+		</p>
+	<%;}};
+	public static final Help mirror = new Help(
+		30, %>Can I mirror my groups on Nabble?<%){public String answer(){return %>
+		<p>
+		If you own a discussion group such as Yahoo or Google group, you can mirror it on Nabble by using Nabble's mailing list archive feature. Nabble complements your existing group by providing a nice threaded view and a <a href="<%=Help.search.url()%>">powerful search</a>. What's more, if you have a quality group you may expand it by getting new users from Nabble.
+		</p>
+		<p>
+		Most importantly, Nabble adds an extra layer of redundancy. With Nabble, you can download all your content for backup or moving purposes anytime you want. This means you will never get stuck with any forum provider including Nabble.
+		</p>
+	<%;}};
+	public static final Help search = new Help(
+		31, %>What search features does Nabble provide?<%){public String answer(){return %>
+		<p>
+		Nabble allows many flavors of specific search.
+		</p>
+		<ul>
+		<li>You can search entire Nabble (with the help of Google Search).
+		<li>You can search within an application (e.g., forum, photo gallery, newspaper, blog) to include all subcategories below.
+		<li>You can narrow your search result by date.
+		<li>You can search messages by user. Click a user name link to go to the user's profile page, search from there. Then you can narrow your search result by date, by category, or by both.
+		<li>Nabble search supports keyword stemming, i.e. search, searching, searched, searches...
+		</ul>
+
+		<h2>Examples</h2>
+
+		<p>
+		<table class="search-tips-table" style="
+			width: 100%;
+			border-collapse: collapse;
+		">
+		<style type="text/css">
+		.nabble .search-tips-table th {
+			text-align: left;
+			font-weight: bold;
+			padding: .5em;
+			vertical-align: top;
+		}
+		.nabble .search-tips-table td {
+			padding: .3em;
+			border-bottom-width: 1px;
+			border-bottom-style: solid;
+			vertical-align: top;
+		}
+		</style>
+		<tr>
+			<th>Your search</th>
+			<th>What will search results show</th>
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">hello world</code></td>
+
+			<td class="info-message">Messages with both <code class="important">hello</code> and <code class="important">world</code></td>
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">"hello world"</code></td>
+			<td class="info-message">Messages with the phrase <code class="important">hello world</code></td>
+
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">hello OR world</code></td>
+			<td class="info-message">Messages with either <code class="important">hello</code> or <code class="important">world</code></td>
+		</tr>
+		<tr>
+
+			<td class="info-message"><code class="important">hello AND world</code></td>
+			<td class="info-message">Messages with <code class="important">hello</code> and <code class="important">world</code>; same as entering <code class="important">hello world</code></td>
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">"hello world" lucene</code></td>
+
+			<td class="info-message">Messages with both the phrase <code class="important">hello world</code> and <code class="important">lucene</code></td>
+		</tr>
+
+
+		<tr>
+			<td class="info-message"><code class="important">hello NOT world</code></td>
+			<td class="info-message">Messages that have <code class="important">hello</code>, but do not have <code class="important">world</code></td>
+
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">("hello world" lucene) OR apache</code></td>
+			<td class="info-message">Messages with either both <code class="important">hello world</code> and <code class="important">lucene</code>, or <code class="important">apache</code></td>
+		</tr>
+
+
+		<tr>
+			<td class="info-message"><code class="important">lucene NOT "hello world"</code></td>
+			<td class="info-message">Messages with <code class="important">lucene</code>, but not the phrase <code class="important">hello world</code></td>
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">hello*</code></td>
+
+			<td class="info-message">Messages that have words that begin with <code class="important">hello</code>. For example, <code class="important">hello</code>, <code class="important">helloworld</code>, and <code class="important">hellooooooo</code>. * is a wildcard and matches 0 or more characters.</td>
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">par?</code></td>
+
+			<td class="info-message">Messages that have words such as <code class="important">park</code>, <code class="important">part</code>, <code class="important">para</code>, and so forth. ? is a 1-character wildcard and matches 1 and only 1 character.</td>
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">subject:"hello world"</code></td>
+			<td class="info-message">Messages with the phrase <code class="important">hello world</code> in its <code class="important">subject</code> only.</td>
+
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">subject:(hello world)</code></td>
+			<td class="info-message">Messages with <code class="important">hello</code> and <code class="important">world</code> in its <code class="important">subject</code> only. Note the use of () to groups words together, rather than "" which denote a phrase.</td>
+	    </tr>
+		<tr>
+			<td class="info-message"><code class="important">message:"hello world"</code></td>
+			<td class="info-message">Messages with the phrase <code class="important">hello world</code> in its <code class="important">message body</code> only.</td>
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">author:"Erik Hatcher"</code></td>
+			<td class="info-message">Messages of <code class="important">Erik Hatcher</code> only.</td>
+		</tr>
+		<tr>
+			<td class="info-message"><code class="important">roam~</code></td>
+			<td class="info-message">Messages with <code class="important">foam</code>, <code class="important">roam</code>, and so forth. In essence, messages that match or sound like <code class="important">roam</code>.</td>
+		</tr>
+		</table>
+		</p>
+
+		<a name="search1"><h2>Search Using Words or Phrases </h2></a>
+		<p>You may search Nabble using a single-word (e.g., "test" or "hello") or use a group of words or a phrase surrounded by double quotes (e.g., "hello dolly"). When searching by a group of words or a phrase, Nabble will return only those items that have both the words present. </p>
+
+		<a name="search2"><h2>Boolean Searches</h2></a>
+		<p>Boolean operators allow combining of search terms using logical operators such as AND, OR, and NOT. Please note that the Boolean operators must be all caps as in the examples below:</p>
+
+		<ul>
+			<li>To search for documents that contain "debian linux" and "java" use: "debian linux" AND java </li>
+			<li>To search for documents that contain either "jetty" or "resin" use: jetty OR resin </li>
+			<li>To search for documents that contain "linux" but not "kernel" use: linux NOT kernel </li>
+			<li>To search for documents that contain "lucene" or "nutch" but not "apache" use: (lucene OR nutch) NOT apache </li>
+		</ul>
+
+		<a name="search3"><h2>Search Messages, Subjects, Authors</h2></a>
+		<p>You can limit your search to either the message text, the subject of the message, the author name, the name of the forum, and/or the topic of the discussion by using the syntax shown in examples below: </p>
+
+		<ul>
+			<li>subject:"lord of the rings" message:film </li>
+			<li>author:"doug cutting" message:lucene </li>
+		</ul>
+
+		<p>If you don't specify the type of search, it will search all -- message, subject, author. </p>
+
+		<a name="search4"><h2>Wildcard Searches </h2></a>
+		<p>Nabble allows you to do both single and multiple character wildcard searches. </p>
+
+		<ul>
+			<li>To perform a single character wildcard search use the "?" symbol. The single character wildcard search looks for terms that match that with the single character replaced. For example, to search for "text" or "test" you can use: te?t </li>
+			<li>To perform a multiple character wildcard search use the "*" symbol. Multiple character wildcard searches looks for 0 or more characters. For example, to search for photo, photography, photographer, you can use: photo* </li>
+			<li>You can also use the wildcard searches in the middle of a term.</li>
+		</ul>
+
+		<p><strong>Note:</strong> You cannot use a * or ? symbol as the first character of a search. Also if your wildcard search will result in too many matches, it cannot be processed.</p>
+
+		<a name="search5"><h2>Fuzzy Searches</h2> </a>
+		<p>To do a fuzzy search use the tilde, "~", symbol at the end of a single word search. For example to search for a term similar in spelling to "roam" use the fuzzy search: roam~ </p>
+
+		<p>This search will find terms like foam and roams. </p>
+
+		<a name="search6"><h2>Proximity Searches </h2></a>
+		<p>Nabble supports proximity search -- that is, finding words that are a within a specific distance away from each other. To do a proximity search use the tilde, "~", symbol at the end of a Phrase. For example to search for a "apache" and "jakarta" within 10 words of each other in a document use: "jakarta apache"~10 </p>
+
+	<%;}};
+    public static final Help orphan = new Help(
+		34, %>What does "parent message unknown" mean?<%){public String answer(){return %>
+		<p>
+		Some messages that we receive from mailing lists don't have enough information for us to determine which message the message we received was in reply to.  In other words, we don't know the parent of that message.  So we mark the message with the image <img alt="Parent Message unknown" title="Parent Message unknown" width="13" height="14" src="/images/icon_orphan.png" style="vertical-align:middle; vertical-align: absmiddle;" border="0" /> and usually assign the first post in the thread as the parent.  This only matters in the "Threaded" view where you can see the structure of messages and their replies.
+		</p>
+	<%;}};
+	public static final Help security = new Help(
+		35, %>Does Nabble have a security policy for mailing lists?<%){public String answer(){return %>
+		<p>
+		Yes.
+		</p>
+		<p>
+		Javascript code is not allowed in Nabble's archive.
+		Thus, any code that may arrive with an email is blocked for your security.
+		Only the safe part of the message is displayed in the archive, and
+		we also show a warning that the JavaScript was blocked.
+		</p>
+		<p>Nabble also removes <b>&lt;style&gt;</b> tags because they might prevent messages from being displayed properly.</p>
+	<%;}};
+	public static final Help embed_what_how = new Help(
+		36, %>Embeddable Forum, Photo Gallery, News, Blog, Mailing List & Other Apps!<%){
+		public String getMetaDescription() { return "Nabble has different embeddable applications, including free forum, photo gallery, news, blog and much more! All applications have no installation, no HTML hassle, just copy and paste of a small javascript code."; }
+		public String getMetaKeywords() { return "free, embeddable, forum, photo gallery, newspaper, blog, mailing list archive, easy, embedding, embedded, embed my forum, hosted, website, site, HTML"; }
+		public String answer(){return %>
+
+		<h2>All Nabble apps are naturally embeddable!</h2>
+
+		<p>Yes, with Nabble, you get an embeddable forum, embeddable photo gallery, embeddable news, embeddable blog, embeddable mailing list & archive... and there are more to come!</p>
+		<p>All these embeddable apps are fully featured web applications. They all have full text search, user profile and access control, email subscription and integration, XML feed, moderation, CSS customization, unlimited uploading of pictures and files, threading, cataloging ... What's more, embedding a Nabble app is as easy as embedding a YouTube video - you just copy and paste a line of javascript code into your HTML page.</p>
+		<p>You may have seen some embeddable widgets, but Nabble's "embeddability" is far more advanced. A widget runs in a fixed frame, making the content either underflows or overflows (frame scrolling is your only option). In contrast, a Nabble embeddable app adjusts its height automatically and always fit seamlessly into your site.</p>
+
+		<p>All you have to do is go to your application, click on "Options > Embedding options" and you will see a text area with a code snippet.</p>
+		<p>Copy and paste that snippet into your HTML web page. Now open the page and you will see that your forum loads up seamlessly. No installation, no HTML hassle, just copy and paste.</p>
+		<p><b>Embedding may have issues with third-party cookies.</b>  This can be a problem if the user has disabled third-party cookies or if they are using Safari.  To avoid problems, we suggest that you use a <a href="<%=Help.domain_names.url()%>">custom domain</a> for Nabble that matches your domain.  For example, if your website is www.your-domain.com, you could use forum.your-domain.com for Nabble.  This ensures that Nabble's cookies aren't considered third-party by the browser.</p>
+		<p>Users will browse, search, post, and navigate without ever leaving your page. Your embedded application works as if it were custom built, installed, and visually integrated with your website.</p>
+		<p>Try this feature by creating a new application (e.g., forum, photo gallery, newspaper, blog, etc.) or using any Nabble forum that allows embedding. It is dead easy and seamless.</p>
+		<%;}};
+
+		public static final Help embed_permalinks = new Help(
+		38, %>How do I link to a page in an embedded application?<%){public String answer(){return %>
+		<p>
+		You will notice that when you navigate through an embedded application (e.g., forum, photo gallery, blog, etc), the URL in the browser doesn't change.
+		To get the URL of a specific page, such as a thread or a post, in an embedded application, look on the top-right corner of each page for <b>Permalinks</b>.</p>
+		<p><img src="/images/help/help_embed_permalink.png" style="padding-left:2em"/></p>
+		<p>When you click on the "Permalink" link, you will see the page URL in a text field. All you have to do is click on the URL to
+		select it, and then copy it (e.g., ctrl+c).</p>
+		<p><img src="/images/help/help_embed_permalink2.png" style="padding-left:2em"/></p>
+		Even though the URL points to the <i>nabble.com</i> domain, the browser will redirect to the embedding page so that it will look
+		exactly like it was when you first got the permalink (you should <a href="<%=Help.embed_redirect.url()%>">enable this option</a> to work).
+		</p>
+	<%;}};
+	public static final Help embed_skin = new Help(
+		39, %>How can I customize the appearance of my embeddable application?<%){
+		public String getMetaDescription() { return "All Nabble applications are fully customizable. You can easily change the CSS stylesheet of your embeddable forum, photo gallery, newspaper, blog and mailing list archives."; }
+		public String getMetaKeywords() { return "customizable, embeddable, forum, photo gallery, newspaper, blog, mailing list archive, css, skins, examples, simple, easy"; }
+		public String answer(){return %>
+		<p>
+			Go to "Options > Editor > Change appearance" link in your application.
+			This screen has easy options to customize the basic look of your application:
+		</p>
+		<p><img src="/images/help/help_style_easy.png" style="padding-left:2em" alt="Here you can customize details about your application"/></p>
+		<p>
+			If you want a more detailed customization, you can go to the "Look and Feel" section in order to provide a custom CSS (Cascading Style Sheets) text for your application.
+		</p>
+		<p><img src="/images/help/help_style_tab.png" style="padding-left:2em"/></p>
+		<p>As you can see in the image above, Nabble has some predefined styles ready for you. You can simply click on them to get their CSS into the text area.
+			After that, you can modify the text the way you want. This is a valuable source of examples that you can also use to learn more about Nabble's style.</p>
+		<h2>How to write your custom CSS</h2>
+		<p>
+			If you want to change the appearance of your application, you have to override <a href="<%=Shared.getCssPath()%>">Nabble's style</a>.
+			The <b>cheat table</b> below can easily guide you through this process. Learn how each style is defined and change them as you want.
+			Since colors may require hexadecimal codes, you can use a tool like <a href="http://www.colorpicker.com/" target="_new" rel="nofollow">Color Picker</a> to help you in this sense.
+		</p>
+		<p class="medium-border-color info-message" style="padding:.7em;border-width:1px;border-style:solid;">If you want to import an external CSS file, you should use the following command:<br/><font face=monospace size=3>@import url("http://www.example.com/file.css");</font></p>
+		<style>
+			.nabble .item-small { font-size:90%; }
+			.nabble .color-box { border:1px solid #000000; }
+		</style>
+		<table class="editor-table" cellpadding="5" style="font-size:90%;">
+			<tr class="shaded-bg-color">
+				<td>Item to Change</td>
+				<td>Default Value</td>
+				<td>How to Change (Example)</td>
+			</tr>
+			<tr>
+				<td><b>Font</b><div class="item-small">Font family and its size.</div></td>
+				<td>Verdana 0.84em</td>
+				<td>
+					body, input, button, textarea, select {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;font-family: <b>Tahoma</b>;<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;font-size: <b>0.8em</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Background Color</b><div class="item-small">Background of the pages.</div></td>
+				<td><span class="color-box" style="background-color:#FFFFFF">&nbsp;&nbsp;&nbsp;</span> FFFFFF (white)</td>
+				<td>
+					body, .nabble, .nabble .no-bg-color {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Text Color</b><div class="item-small">Color of texts (paragraphs, etc.).</div></td>
+				<td><span class="color-box" style="background-color:#000000">&nbsp;&nbsp;&nbsp;</span> 000000 (black)</td>
+				<td>
+					.nabble,<br/>
+					.nabble table,<br/>
+					.nabble .info-message th,<br/>
+					.nabble .message-text, .small,<br/>
+					.nabble .editor-table td, p, form,<br/>
+					small, ul, table td, .breadcrumbs {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;<br/>
+					}<br/>
+				</td>
+			</tr>
+			<tr>
+				<td><b>Link Color</b><div class="item-small">Color of unvisited links.</div></td>
+				<td><span class="color-box" style="background-color:#0000EE">&nbsp;&nbsp;&nbsp;</span> 0000EE</td>
+				<td>
+					.nabble a:link,<br/>
+					.nabble a.not-visited-link {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Link Color (Visited)</b><div class="item-small">Color of visited links.</div></td>
+				<td><span class="color-box" style="background-color:#551A8B">&nbsp;&nbsp;&nbsp;</span> 551A8B</td>
+				<td>
+					.nabble a:visited,<br/>
+					.nabble a.visited-link {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Header Text Color (H1)</b><div class="item-small">Color of header texts (big titles, etc.).</div></td>
+				<td><span class="color-box" style="background-color:#333333">&nbsp;&nbsp;&nbsp;</span> 333333</td>
+				<td>
+					.nabble h1 {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;</br>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Header Text Color (H2)</b><div class="item-small">Color of small header texts.</div></td>
+				<td><span class="color-box" style="background-color:#000000">&nbsp;&nbsp;&nbsp;</span> 000000</td>
+				<td>
+					.nabble h2 {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;</br>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Important Text</b><div class="item-small">Color of important texts.</div></td>
+				<td><span class="color-box" style="background-color:#cc0000">&nbsp;&nbsp;&nbsp;</span> CC0000</td>
+				<td>
+					.nabble .important {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;</br>
+					}
+				</td>
+			</tr>
+            <tr>
+				<td><b>Form Label Text</b><div class="item-small">Color of labels on forms and other special places.</div></td>
+				<td><span class="color-box" style="background-color:#666666">&nbsp;&nbsp;&nbsp;</span> 666666</td>
+				<td>
+					.nabble th,<br/>
+					.nabble .form-label,<br/>
+					.nabble .weak-color {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+				<tr>
+				<td><b>Light Background</b><div class="item-small">Color of light backgrounds, which are used in some areas of the screen.</div></td>
+				<td><span class="color-box" style="background-color:#F5F5F5">&nbsp;&nbsp;&nbsp;</span> F5F5F5</td>
+				<td>
+					.nabble .light-bg-color {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Shaded Background</b><div class="item-small">Color of shaded backgrounds, which are used in some areas of the screen.</div></td>
+				<td><span class="color-box" style="background-color:#EEEEEE">&nbsp;&nbsp;&nbsp;</span> EEEEEE</td>
+				<td>
+					.nabble .shaded-bg-color {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Dark Background</b><div class="item-small">Color of dark backgrounds, which are used in some areas of the screen.</div></td>
+				<td><span class="color-box" style="background-color:#DDDDDD">&nbsp;&nbsp;&nbsp;</span> DDDDDD</td>
+				<td>
+					.nabble .dark-bg-color {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+            <tr>
+				<td><b>Error Message</b><div class="item-small">Color of error messages.</div></td>
+				<td><span class="color-box" style="border-color:#cc3300;background-color:#ffffcc">&nbsp;&nbsp;&nbsp;</span> FFFFCC</td>
+				<td>
+					.nabble .error-message {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;border-color:<b>#&lt;color&gt;</b>;<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Info Message</b><div class="item-small">Color of info messages.</div></td>
+				<td><span class="color-box" style="border-color:#ffcc33;background-color:#ffffcc">&nbsp;&nbsp;&nbsp;</span> FFFFCC</td>
+				<td>
+					.nabble .info-message {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;border-color:<b>#&lt;color&gt;</b>;<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Highlight</b><div class="item-small">Color of highlights, which are used to catch user's attention.</div></td>
+				<td><span class="color-box" style="background-color:#ffff99">&nbsp;&nbsp;&nbsp;</span> FFFF66</td>
+				<td>
+					.nabble .highlight {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+            <tr>
+				<td><b>Light Border Color</b><div class="item-small">Color of the light border, which is used to separate areas on the screen.</div></td>
+				<td><span class="color-box" style="background-color:#eeeeee">&nbsp;&nbsp;&nbsp;</span> EEEEEE</td>
+				<td>
+					.nabble .light-border-color {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;border-color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+            <tr>
+				<td><b>Medium Border Color</b><div class="item-small">Color of the medium border, which is used to separate areas on the screen.</div></td>
+				<td><span class="color-box" style="background-color:#cccccc">&nbsp;&nbsp;&nbsp;</span> CCCCCC</td>
+				<td>
+					.nabble .medium-border-color {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;border-color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+            <tr>
+				<td><b>Dark Border Color</b><div class="item-small">Color of the dark border, which is used to separate areas on the screen.</div></td>
+				<td><span class="color-box" style="background-color:#666666">&nbsp;&nbsp;&nbsp;</span> 666666</td>
+				<td>
+					.nabble .dark-border-color {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;border-color: <b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+            <tr>
+				<td><b>Dropdown Colors</b><div class="item-small">Colors of the dropdown box.</div></td>
+				<td><span class="color-box" style="border-color:#cccccc;background-color:#eeeeee">&nbsp;&nbsp;&nbsp;</span> CCCCCC</td>
+				<td>
+					span.dropdown table {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-color:<b>#&lt;color&gt;</b>;<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;border-color:<b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+            <tr>
+				<td><b>Dropdown Item Colors</b><div class="item-small">Colors of each dropdown item.</div></td>
+				<td>
+					<span class="color-box" style="background-color:#0000EE">&nbsp;&nbsp;&nbsp;</span> 0000EE (foreground / normal and hover)<br/><br/>
+					<span class="color-box" style="background-color:transparent">&nbsp;&nbsp;&nbsp;</span> Transparent (background normal)<br/><br/>
+					<span class="color-box" style="background-color:#dddddd">&nbsp;&nbsp;&nbsp;</span> DDDDDD (background hover)
+				</td>
+				<td>
+					<i>/* Normal */</i><br/>
+					span.dropdown-item {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color:<b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+            <tr>
+				<td><b>Date Color</b><div class="item-small">Color used to display dates of messages.</div></td>
+				<td><span class="color-box" style="background-color:#6a6a6a">&nbsp;&nbsp;&nbsp;</span> 6A6A6A</td>
+				<td>
+					span.post-date {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color:<b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Author Color</b><div class="item-small">Color used to display the author name on messages.</div></td>
+				<td><span class="color-box" style="background-color:#116611">&nbsp;&nbsp;&nbsp;</span> 116611</td>
+				<td>
+					span.post-author {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color:<b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Subject Color</b><div class="item-small">Color used to display subjects of messages.</div></td>
+				<td><span class="color-box" style="background-color:#111166">&nbsp;&nbsp;&nbsp;</span> 111166</td>
+				<td>
+					.post-subject {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color:<b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+            <tr>
+				<td><b>Message Preview Color</b><div class="item-small">Color used to display the message preview line (snippets).</div></td>
+				<td><span class="color-box" style="background-color:#909090">&nbsp;&nbsp;&nbsp;</span> 909090</td>
+				<td>
+					span.post-snippet {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;color:<b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Post Hover Color</b><div class="item-small">Color used to highlight a post when the user moves the mouse over it.</div></td>
+				<td><span class="color-box" style="background-color:#eeeeee">&nbsp;&nbsp;&nbsp;</span> EEEEEE</td>
+				<td>
+					.post-hover {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-color:<b>#&lt;color&gt;</b>;<br/>
+					}
+				</td>
+			</tr>
+			<tr>
+				<td><b>Post Box</b><div class="item-small">Box where the post is displayed.</div></td>
+				<td><span class="color-box" style="background-color:#D9D9D9">&nbsp;&nbsp;&nbsp;</span> D9D9D9</td>
+				<td>
+					div.post-border {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;border-color: <b>#&lt;color&gt;</b>;<br/>
+					}<br/>
+				</td>
+			</tr>
+			<tr>
+				<td><b>Relationship Lines</b><div style="font-size:80%">Lines that connect parents and children posts. This item requires images for the lines.</div></td>
+				<td>N/A</td>
+				<td>
+					span.connect-line {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-image:url("<b>&lt;image-URL&gt;</b>");<br/>
+					}<br/>
+					<br/>
+					span.connect-end {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-image:url("<b>&lt;image-URL&gt;</b>");<br/>
+					}<br/>
+					<br/>
+					td.connect-end {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-image:url("<b>&lt;image-URL&gt;</b>");<br/>
+					}<br/>
+					<br/>
+					td.connect-node {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-image:url("<b>&lt;image-URL&gt;</b>");<br/>
+					}<br/>
+					<br/>
+					td.connect-node-closed {<br/>
+					&nbsp;&nbsp;&nbsp;&nbsp;background-image:url("<b>&lt;image-URL&gt;</b>");<br/>
+					}
+				</td>
+			</tr>
+		</table>
+	<%;}};
+	public static final Help embed_redirect = new Help(
+		40, %>Is my embedded application also available on Nabble's website?<%){public String answer(){return %>
+		<p>
+		Yes, but you have the option to redirect all visits to your website. In this case,
+		when someone tries to access your application or posts, the browser will redirect to
+		your website and the desired page will be displayed. This option allows our search
+		engine to find messages from your application, but users will be redirected if they try
+		to open those messages.
+		 </p>
+		<p>
+		To configure this option, go to the page where your application is embedded in and click
+		on <i>"Options > Embedding options"</i>:
+		</p>
+		<p><img src="/images/help/help_embed_default.png" style="padding-left:2em"/></p>
+	<%;}};
+
+	private static class Lazy {
+		private static final String[] examples = new String[]{
+			"<quote>some text</quote>",
+			"<quote author='Bob'>some text</quote>",
+			"<email>someone@nabble.com</email>",
+			"<raw>\nuninterpreted HTML like <a href='#'>link</a>\n</raw>",
+		};
+		private static final String exampleRows;
+		static {
+			StringBuilder buf = new StringBuilder();
+			for( String example : examples ) {
+				buf.append( %>
+					<tr>
+						<td class="medium-border-color"><%=HtmlUtils.htmlEncode(example)%></td>
+						<td class="second medium-border-color"><%=getHtml(example)%></td>
+					</tr>
+				<% );
+			}
+			exampleRows = buf.toString();
+		}
+	}
+
+	private static String getHtml(String text) {
+		try {
+			Html html = new Html(text);
+			HtmlListNamespace ns = new HtmlListNamespace(html,null, nabble.model.Message.Format.HTML);
+			Program program = Program.getInstance(ModuleManager.getGenericModules());
+			Template template = program.getTemplate( "process_message_html",
+				BasicNamespace.class, HtmlListNamespace.class
+			);
+			template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(),
+				new BasicNamespace(template), ns
+			);
+			return ns.toString();
+		} catch(CompileException e) {
+			logger.error("",e);
+			throw new RuntimeException(e);
+		}
+	}
+
+	public static final Help formatting = new Help(
+		41, %>How are messages formatted?<%){public String answer(){return %>
+		<p>For formatting, we use standard HTML tags.</p>
+
+		<p>By default, your message is assumed to be text.  In this case, we will process some HTML tags but we will generally show your text as your entered it.  If you check the "Message is in HTML Format" checkbox, then you can enter HTML.</p>
+
+		<p>We support some special tags which we process. Here is how they are used:</p>
+
+		<p>
+		<style type="text/css">
+			table.type-examples {
+				border-collapse:collapse;
+			}
+			table.type-examples td {
+				padding: .5em;
+				border-bottom-width:1px;
+				border-bottom-style: solid;
+			}
+			table.type-examples td.second {
+				border-left-width:1px;
+				border-left-style: solid;
+			}
+		</style>
+		<table class="type-examples">
+			<tr>
+				<td class="shaded-bg-color medium-border-color"><b>You Type</b></td>
+				<td class="second shaded-bg-color medium-border-color"><b>We Show</b></td>
+			</tr>
+			<%=Lazy.exampleRows%>
+		</table>
+		</p>
+	<%;}};
+
+	public static final Help anonymous = new Help(
+		42, %>What are anonymous users?<%){public String answer(){return %>
+		<p>
+			Anonymous users are users that didn't register with Nabble.
+			All Nabble requires from them is a name that is displayed together with the message.
+			Forums that allow anonymous users tend to have more participation, but they might generate confusion when users provide false or deceiving names.
+			As the forum owner, you can always remove those messages from the forum.
+		</p>
+		<p>If you don't want anonymous users in your forum, you can disable them by accessing <b>Options > Users > Control anonymous users</b>.</p>
+	<%;}};
+
+	public static final Help mixed = new Help(
+		43, %>How can I fix a mixed bulletin board?<%){public String answer(){return %>
+		<p>
+			In traditional forums, a bulleting board only shows subcategories where users are allowed to post messages.
+			With Nabble, any forum can be displayed as a bulletin board and this might create a strange board with threads and forums mixed in the same list.
+			This situation is not easy to create since Nabble doesn't allow users to post new topics under a board,
+			but you could face this problem if you change a regular forum into a bulletin board format or move threads directly to the board.
+		</p>
+		<p>
+			A mixed board generally isn't a good idea and should be fixed in order to avoid confusion.
+			Note that Nabble doesn't hide threads in that view because this would increase the confusion among users.
+			It is the forum owner's responsibility to keep the forum clean and organized.
+		</p>
+		<h3>How to fix it?</h3>
+		<p>
+			If your forum has a mixed bulletin board, do the following:
+		</p>
+		<ol>
+			<li style="padding-bottom:.3em">If you have just a few threads under the board, you can move them one by one to the appropriate sub-forum.</li>
+			<li>
+				If the number of threads is high and moving one by one is inefficient, you should create a new board and move the current forum as a sub-forum of it.
+				You might have to move other sub-forums around in order to rebuild the bulletin board.
+			</li>
+		</ol>
+	<%;}};
+
+	public static final Help subscriptions = new Help(
+		45, %>Is it possible to post messages through email?<%){public String answer(){return %>
+		<p>
+			Yes. Nabble has email subscriptions that allow you to receive emails for each message
+			posted under a forum. If you reply to those emails, your response will be appended to
+			the discussion in the forum archive.
+		</p>
+		<p>
+			To subscribe to a forum, open the forum page and click on <i>Options > Subscribe via email</i>.
+			You can choose if you want to receive an email for each message posted or just new topics.
+		</p>
+
+		<h2>Detailed Explanation</h2>
+
+		<p>
+		All forums at Nabble have a built-in mailing list that people can use to communicate.
+		In other words, users can compose, read and reply to messages via email independently of the forum web interface.
+		</p>
+
+		<p>If you want to subscribe to a mailing list, open the forum interface and click on "Options > Subscribe via email" as seen below:</p>
+
+		<p><img src="/images/help/subscribe.png" alt="Subscribe via email"></p>
+
+		<p>Here you can see the options when you click on that link:</p>
+
+		<p><img src="/images/help/subscribe2.png" alt="Subscription options"></p>
+
+		<p><b>(1) Individual emails</b> - You receive one email for each new post under the forum. If you reply to such emails, your message will be archived as a new reply on the forum.</p>
+
+		<p><b>(2) Digest</b> - You receive one email with a summary of all posts created in the last 24 hours. You can't reply to this type of email.</p>
+
+		<p>To manage your subscriptions, click on your profile (top right corner), then click on "Personal Settings > Email Subscriptions". From that screen you can change and remove subscriptions.</p>
+
+		<h2>Posting via Email</h2>
+
+		<p>To know the email of a given forum, go to the forum and click on "Options > Post by email...". To prevent spam, forum addresses are unique for each user. So you can't get a forum address and send it to your friends (they must click on that link).</p>
+	<%;}};
+
+	public static final Help embed_js_options = new Help(
+		48, %>Can I change other behaviors of an embedded forum?<%){public String answer(){return %>
+		<p>
+			Yes. Besides the <a href="<%=Help.embed_redirect.url()%>" rel="nofollow">redirect option</a>, we have a few options that you can use to change other behaviors of your embedded forum.
+			These options must be set as javascript variables in your HTML page (place them before the embedding code).
+			For example, you can add the following code to your page:
+		</p>
+		<div style="font-family:monospace;margin:1em;">
+			&lt;script type="text/javascript"><br>
+			&nbsp;&nbsp;&nbsp;nabble_width = "500px";<br>
+			&nbsp;&nbsp;&nbsp;nabble_ignore_scroll = true;<br>
+			&nbsp;&nbsp;&nbsp;nabble_ignore_title = true;<br>
+			&lt;/script><br>
+		</div>
+		<style type="text/css">
+			table.info td {
+				padding: .5em;
+			}
+			tr.header td {
+				font-weight:bold;
+				padding: .1em .5em;
+			}
+		</style>
+		<table class="info">
+			<tr class="header">
+				<td class="shaded-bg-color">Variable</td>
+				<td class="shaded-bg-color">Description</td>
+			</tr>
+			<tr>
+				<td>nabble_width</td>
+				<td>
+					Use this variable if you want to have an embedded forum with fixed width.
+					The default value is "100%" and you can change it according to standard CSS rules
+					(e.g., "50%", "10em", "500px").
+				</td>
+			</tr>
+			<tr>
+				<td>nabble_ignore_scroll</td>
+				<td>
+					Set this variable to <b>true</b> if you don't want the embedded forum to
+					scroll to specific posts or messages. By default, the embedded forum will
+					eventually scroll the page to specific messages depending on the user navigation
+					and clicked links.
+				</td>
+			</tr>
+			<tr>
+				<td>nabble_scroll_top</td>
+				<td>
+					Set this variable to <b>true</b> if you want the embedded forum to
+					scroll to the top of your HTML page whenever the user navigates through the forum.
+					By default, the forum will scroll to its top, which is not necessarily the top of your
+					HTML page.
+				</td>
+			</tr>
+			<tr>
+				<td>nabble_ignore_title</td>
+				<td>
+					Set this variable to <b>true</b> if you don't want the embedded forum to
+					change the title of your HTML page. By default, the embedded forum will
+					set the title of your page with the forum name.
+				</td>
+			</tr>
+		</table>
+	<%;}};
+
+	public static final Help online = new Help(
+		49, %>Is it possible to show which users are online?<%){
+		public String getMetaDescription() { return "Nabble applications display who is online when you browse forums, photo galleries, newspapers, blogs and mailing list archives."; }
+		public String getMetaKeywords() { return "online users, forum, photo gallery, newspaper, blog, mailing list archive, free forum, free blog, free photo gallery"; }
+		public String answer(){return %>
+		<p>
+			Yes, all Nabble applications (e.g., forum, photo gallery, newspaper, blog, etc.) have this feature.
+			Online users have a green circle at the bottom right corner of their avatars.
+		</p>
+	<%;}};
+
+	public static final Help domain_names = new Help(
+		51, %>Can I access my forum with my own domain name?<%){
+		public String answer(){return %>
+		<p>
+			Yes. Nabble allows you to change the domain name configuration of your application (e.g., forum, gallery, blog, news, etc.)
+			by clicking on "Options > Editor > Change domain name". If you decide to use your own domain name, you will have to change
+			its DNS configuration and make it resolve to the Nabble server where your forum is located (you can find instructions on the configuration screen).
+		</p>
+		<p>
+			There are some advantages about custom domain names that you should be aware of. One of them is that you can &ndash; for example &ndash;
+			use Google Custom Search to let users search your forum and your website at the same time. Another advantage is that Nabble will allow you
+			to run custom JavaScript code in your pages <i>(to be implemented / Templates feature)</i>.
+		</p>
+	<%;}};
+
+	public static final Help inactivity_deletion = new Help(
+		53, %>Will Nabble delete my forum, posts or anything I have created?<%){
+		public String answer(){return %>
+		<p>
+			Nabble has a garbage collector process that searches for <b>inactive</b> forums, topics or messages
+			in the database. If you receive an email with forums scheduled for deletion, you have to first understand
+			why they are in that list. The most common cases are:
+		</p>
+		<p>
+			<b>(1) Your forum hasn't been viewed by anyone recently</b>:
+			If your forum is not dead, you can just save it from deletion by following the
+			instructions on its page.
+		</p>
+		<p>
+			<b>(2) Your post was removed from the forum</b>: the forum owner (or some other user
+			that you replied to) might have moved your post (and replies) out of the forum. This is different from
+			physical deletion. When a post is removed from a forum, it still exists in Nabble's database and can be found in
+			the author's profile page. Such messages are not part of any forum and are probably not read by anyone.
+			So you shouldn't care much about those messages.
+		</p>
+
+		<p class="shaded-bg-color" style="padding:.5em">
+			In summary, only root nodes are checked for deletion.
+			This means that only threads that have been removed or inactive root level forums will be caught by the garbage collector process.
+		</p>
+
+		<h2>Each visit counts</h2>
+		<p>Threads inside an active forum will NOT be scheduled for deletion, even if they don't get any visits for a long time
+		(although other threads in the same forum must get visits in order to keep the forum active). <b>So each visit adds activity
+		points to the whole structure</b>. On the other hand, when a post or any node is removed from the structure,
+		it will become a separated structure with its own activity level.</p>
+
+		<h2>How deletion works</h2>
+		<p>If your forum gets deleted, the threads under it are not immediately deleted.
+		What happens is that after the forum is deleted, these threads become top level independent nodes and
+		then they are subject to the same deletion process after some weeks.  So if a forum owner allows his forum
+		to be deleted, thread starters can still save their threads.</p>
+
+		<p class="shaded-bg-color" style="padding:.5em;font-weight:bold">In any case, you can save your forums, topics and messages from deletion by following the
+		instructions on their page. You should also know that you can download the archives of your application
+		by clicking on "Options > Download archives" (if you are the administrator).
+		<a href="<%=Help.export.url()%>">Click here</a> for more information.</p>
+	<%;}};
+
+	public static final Help export = new Help(
+		54, %>Can I export my forum to a standard format?<%){
+		public String answer(){return %>
+		<p>Yes, Nabble can export your forum data, subcategories and messages to the standard XML format.
+			You can download these files by clicking on "Options > Download archives". You will realize that Nabble
+			doesn't have these files ready for you, so first you will have to build them. This process may take some
+			minutes or even hours depending on the size of your application. You will receive an email when this process
+			has finished.</p>
+		<p>To make downloading easier, Nabble groups XML files into zip archives. Each zip archive contains thousands
+			of XML files, where each file represents a node in the forum structure (<a href="<%=Jtp.homeContextUrl()%>/back-end.html">learn more</a>).
+			Below you can find the description of each field in the XML file, which may help you in creating a parser for this information.</p>
+
+			<table class="editor-table" cellpadding="5" style="font-size:90%;">
+			<tr class="shaded-bg-color">
+			<th align="left">Field</th>
+			<th align="left">Description</th>
+			</tr>
+			<tr>
+			<td>exportId</td>
+			<td>ID of the node represented by the XML file. This ID may be referenced by other XML files as a way to express relationship.</td>
+			</tr>
+			<tr>
+			<td>kind</td>
+			<td>Kind of the node ("APP" for applications or "POST" for messages).</td>
+			</tr>
+			<tr>
+			<td>ownerEmail</td>
+			<td>Email of the user who created the node represented by the XML file.</td>
+			</tr>
+			<tr>
+			<td>ownerName</td>
+			<td>Name of the user who created the node represented by the XML file.</td>
+			</tr>
+			<tr>
+			<td>ownerAnonymousId</td>
+			<td>If the node was created by an anonymous user, this ID will identify this user (anonymous users are not registered and thus have no email information).</td>
+			</tr>
+			<tr>
+			<td>subject</td>
+			<td>Title/subject of this node.</td>
+			</tr>
+			<tr>
+			<td>message</td>
+			<td>Message contents of this node.</td>
+			</tr>
+			<tr>
+			<td>msgFmt</td>
+			<td>Format of the message (m=mail, h=html or t=text).</td>
+			</tr>
+			<tr>
+			<td>parentId</td>
+			<td>ID of the parent node.</td>
+			</tr>
+			<tr>
+			<td>whenCreated</td>
+			<td>Date/Time when the node was created (number of milliseconds since January 1, 1970, 00:00:00 GMT)</td>
+			</tr>
+			<tr>
+			<td>hasReplyAlert</td>
+			<td>true/false if user wants to receive new replies by email.</td>
+			</tr>
+			<tr>
+			<td>whenUpdated</td>
+			<td>Date/Time when the node was updated (number of milliseconds since January 1, 1970, 00:00:00 GMT)</td>
+			</tr>
+			<tr>
+			<td>restriction</td>
+			<td>Type of restriction for this node (i.e., indicates who can view and post messages under this node): NONE, REGISTERED, PROTECTED_CHILDREN, REGISTERED_PROTECTED_CHILDREN, PROTECTED, PRIVATE, PROTECTED_READ_ONLY, PRIVATE_READ_ONLY.</td>
+			</tr>
+			<tr>
+			<td>type</td>
+			<td>Node/Application type (FORUM, BOARD, CATEGORY, NEWS, GALLERY, BLOG, COMMENT)</td>
+			</tr>
+			<tr>
+			<td>customStyle</td>
+			<td>Custom CSS stylesheet created for this node.</td>
+			</tr>
+			<tr>
+			<td>pin</td>
+			<td>Order of this node in the pinned list of the parent node.</td>
+			</tr>
+			<tr>
+			<td>files</td>
+			<td>Files attached to this node. Creates one entry for each file. Contents are byte arrays encrypted using Base64 encoding.</td>
+			</tr>
+			<tr>
+			<td>messageID</td>
+			<td>"Message ID" header of the email sent for this node (only for mailing list archives).</td>
+			</tr>
+			<tr>
+			<td>isGuessedParent</td>
+			<td>true/false if Nabble had to guess the parent of this node (only for mailing list archives).</td>
+			</tr>
+			<tr>
+			<td>mlAddress</td>
+			<td>Email address of the mailing list archived by this node (only for mailing list archives).</td>
+			</tr>
+			<tr>
+			<td>mlUrl</td>
+			<td>Website URL of the mailing list archived by this node.</td>
+			</tr>
+			<tr>
+			<td>mlPlainTextOnly</td>
+			<td>true/false if this mailing list accepts only plain-text emails.</td>
+			</tr>
+			<tr>
+			<td>mlIgnoreNoArchive</td>
+			<td>true/false if Nabble should ignore the X-No-Archive header in emails sent to this mailing list archive.</td>
+			</tr>
+			<tr>
+			<td>mlServer</td>
+			<td>Mailing list server type (e.g., google groups, yahoo groups, mailman, etc.)</td>
+			</tr>
+			</table>
+	<%;}};
+
+	public static final Help pinned_subapps = new Help(
+		55, %>What is the difference between pinned and unpinned sub-forums?<%){
+		public String answer(){return %>
+			<p>
+				Before addressing this question, you have to understand how things work in the background.
+				Essentially, Nabble has a node-architecture that resembles a file system, where forums (and other apps)
+				are like folders and posts are like files. Forums and posts can be pinned to their parent in order to always
+				be displayed on top. Here is an illustration:
+			</p>
+
+			<p><img src="/images/help/help_node_structure.png" style="padding-left:2em"/></p>
+
+			<p>
+				Only forum owners can pin sub-forums and topics. Actually, sub-forums created by owners are automatically
+				pinned, and this makes a big difference to the forum structure. Nabble gives more priority to pinned
+				sub-forums when it comes to how the forum front page looks. Unpinned sub-forums (also called
+				<b>floating sub-forums</b>) are not part of the real forum structure and they float like normal topics.
+				Here is a screenshot:
+			</p>
+
+			<p><img src="/images/help/help_sub_forums.png" style="padding-left:2em"/></p>
+
+			<p>
+				As you can see, floating sub-forums aren't much different than normal topics (except they have a folder
+				icon close to them). When they receive new posts, they jump to the top just like other topics that receive
+				new replies.
+			</p>
+	<%;}};
+
+	public static final Help mixed_lengths = new Help(
+		56, %>How can I customize the "Mixed" application type?<%){
+		public String answer(){return %>
+		<p>
+			If you have an app with the Mixed type and you want to customize the number of topics in the front page, first click on "Options > Application > Change appearance".
+			In the "Preferences" group, there is a field for the number of topics in the mixed style:<br/><br/>
+			<img src="/images/help/mixed_lengths.png" alt="topics configuration for mixed app type"/>
+			<br/><br/>
+			Enter a comma-separated list of values that represent how many topics each section of the mixed view should display.<br>
+			<b>1st value</b> = number of topics to be displayed for the current application<br>
+			<b>2nd value</b> = number of topics to be displayed for the first subcategory<br>
+			<b>3rd value</b> = number of topics to be displayed for the second subcategory<br>
+			and so on...<br>
+			The <b>last number</b> is used for all other sections.<br>
+			<div class="important">(All values must range from 1 to 20)</div>
+			<br>Some examples:<br>
+			<b>6</b> = All sections will display 6 topics.<br>
+			<b>6,3</b> = 6 topics for the current application, 3 topics for all subcategories.<br>
+			<b>6,7,6</b> = 6 topics for the current application, 7 topics for the fist subcategory and 6 topics for all other subcategories.<br>
+			<b>6,5,5,5,6</b> = 6 topics for the current application, 5 topics for the first three subcategories and 6 topics for all other subcategories.<br>
+		</p>
+	<%;}};
+
+	public static void index() {
+		Lucene.addHelp(map.values());
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/action_row.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,93 @@
+<macro name="action_link_style">
+	<n.put_in_head.>
+		<style type="text/css">
+			div.action-link {
+				float:left;
+				white-space:nowrap;
+				margin:.7em .3em;
+			}
+		</style>
+	</n.put_in_head.>
+	action-link
+</macro>
+
+<macro name="new_topic_action_link" parameters="text">
+	<div class="[n.action_link_style/]">
+		<img src="/images/icon_post_message.png" class="image16" />
+		<n.page_node.>
+			<n.new_topic_link>
+				<title>
+					<t>Post new message in <t.location.subject/></t>
+				</title>
+				<text>
+					<n.default. to="[t]New Topic[/t]"><n.text/></n.default.>
+				</text>
+			</n.new_topic_link>
+		</n.page_node.>
+	</div>
+</macro>
+
+<macro name="returnable_action_link" dot_parameter="action_link">
+	<n.if.is_front_page>
+		<then>
+			<n.action_link/>
+		</then>
+		<else>
+			<div class="[n.action_link_style/]" style="margin-left:.6em">
+				<img src="/images/forum_sm.png" class="image16"/>
+				<n.page_node.node_link text="[t]Main Page[/t]"/>
+			</div>
+		</else>
+	</n.if.is_front_page>
+</macro>
+
+<macro name="subapps_action_link" parameters="text">
+	<n.returnable_action_link.>
+		<n.page_node.>
+			<n.if.has_subapps>
+				<then>
+					<div class="[n.action_link_style/]" style="margin-left:.6em">
+						<img src="/images/forum_sm.png" class="image16"/>
+						<n.node_link>
+							<href><n.url template="view_subapps"/></href>
+							<text>
+								<n.default. to="[t]Sub-Forums[/t]"><n.text/></n.default.>
+							</text>
+						</n.node_link>
+					</div>
+				</then>
+			</n.if.has_subapps>
+		</n.page_node.>
+	</n.returnable_action_link.>
+</macro>
+
+<macro name="topics_action_link" parameters="only_if_has_subapps" requires="node_page,servlet">
+	<n.if.either condition1="[n.is_empty.only_if_has_subapps/]" condition2="[n.page_node.has_subapps/]">
+		<then>
+			<n.returnable_action_link.>
+				<div class="[n.action_link_style/]" style="margin-left:.6em">
+					<img src="/images/thread_sm.png" class="image16"/>
+					<n.page_node.node_link href="[n.path template='view_topics'/]" text="[t]Topics View[/t]" />
+				</div>
+			</n.returnable_action_link.>
+		</then>
+	</n.if.either>
+</macro>
+
+<macro name="people_action_link" requires="node_page">
+	<n.if.page_node.has_people_page>
+		<then>
+			<div class="[n.action_link_style/]" style="margin-left:.6em">
+				<img src="/images/people_sm.png" class="image16"/>
+				<n.page_node.people_link/>
+			</div>
+		</then>
+	</n.if.page_node.has_people_page>
+</macro>
+
+<macro name="options_action_menu" requires="node_page">
+	<div class="[n.action_link_style/]" style="margin-left:.6em">
+		<img src="/images/gear.png" class="image16"/>
+		<n.page_node.app_dropdown/>
+	</div>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/adv_search.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,274 @@
+<macro name="adv_search_page" requires="servlet">
+	<n.node_page.>
+		<n.catch_exception. id="set-block">
+			<n.if.is_submitted_form>
+				<then>
+					<n.redirect_to.page_node.>
+						<n.search_path>
+							<query>
+								<n.compress.>
+									<n.set_message_text_contains_group/>
+									<n.set_message_subject_contains_group/>
+									<n.set_author_name_group/>
+									<n.set_any_message_part_contains_group/>
+								</n.compress.>
+							</query>
+							<days>
+								<n.get_int. default="0" exception="days_field_not_integer">
+									<n.get_parameter name="days" />
+								</n.get_int.>
+							</days>
+						</n.search_path>
+					</n.redirect_to.page_node.>
+				</then>
+			</n.if.is_submitted_form>
+		</n.catch_exception.>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.title.><t>Advanced Search</t></n.title.>
+				<style type="text/css">
+					div.group-header {
+						padding: .2em;
+						margin-top:1em;
+						font-size: 120%;
+						font-weight: bold;
+					}
+					td.form-label {
+						white-space: nowrap;
+					}
+				</style>
+			</head>
+			<body>
+				<n.adv_search_exceptions/>
+				<h1><t>Advanced Search</t></h1>
+
+				<p><t>Use the options below to precisely specify your search criteria.</t></p>
+
+				<n.form.>
+					<table class="advanced-search-table">
+						<n.message_text_contains_group/>
+						<n.message_subject_contains_group/>
+						<n.author_name_group/>
+						<n.any_message_part_contains_group/>
+						<n.message_date_group/>
+						<n.submit_button/>
+					</table>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="adv_search_exceptions">
+	<n.format_error.handle_exception. for="set-block">
+		<n.exception. name="days_field_not_integer">
+			<t>Invalid number of days, must be integer.</t>
+		</n.exception.>
+	</n.format_error.handle_exception.>
+</macro>
+
+<macro name="set_message_text_contains_group">
+	<n.adv_search_and_query name="messageand" field="message" />
+	<n.adv_search_or_query name="messageor" field="message" />
+	<n.adv_search_phrase_query name="messagephrase" field="message" />
+	<n.adv_search_not_query name="messagenot" field="message" />
+</macro>
+
+<macro name="set_message_subject_contains_group">
+	<n.adv_search_and_query name="subjectand" field="subject" />
+	<n.adv_search_or_query name="subjector" field="subject" />
+	<n.adv_search_phrase_query name="subjectphrase" field="subject" />
+	<n.adv_search_not_query name="subjectnot" field="subject" />
+</macro>
+
+<macro name="set_author_name_group">
+	<n.adv_search_phrase_query name="authorphrase" field="author" />
+	<n.adv_search_not_phrase_query name="authorphrasenot" field="author" />
+</macro>
+
+<macro name="set_any_message_part_contains_group">
+	<n.get_parameter name="alland" />
+	<n.adv_search_or_query name="allor" />
+	<n.adv_search_phrase_query name="allphrase" />
+	<n.adv_search_not_query name="allnot" />
+</macro>
+
+<macro name="message_text_contains_group">
+	<tr>
+		<td colspan="2">
+			<div class="shaded-bg-color rounded second-font group-header">
+				<t>Message text contains</t>
+			</div>
+		</td>
+	</tr>
+	<tr>
+		<td class="form-label"><t>all of the words:</t></td>
+		<td><n.adv_search_input name="messageand" /></td>
+	</tr>
+	<tr>
+		<td class="form-label"><t>at least one of the words:</t></td>
+		<td><n.adv_search_input name="messageor" /></td>
+	</tr>
+	<tr>
+		<td class="form-label"><t>the exact phrase:</t></td>
+		<td><n.adv_search_input name="messagephrase" /></td>
+	</tr>
+	<tr>
+		<td class="form-label end-group"><t>none of the words:</t></td>
+		<td class="end-group"><n.adv_search_input name="messagenot" /></td>
+	</tr>
+</macro>
+
+<macro name="message_subject_contains_group">
+	<tr>
+		<td colspan="2">
+			<div class="shaded-bg-color rounded second-font group-header">
+				<t>Message subject contains</t>
+			</div>
+		</td>
+	</tr>
+	<tr>
+		<td class="form-label"><t>all of the words:</t></td>
+		<td><n.adv_search_input name="subjectand" /></td>
+	</tr>
+	<tr>
+		<td class="form-label"><t>at least one of the words:</t></td>
+		<td><n.adv_search_input name="subjector" /></td>
+	</tr>
+	<tr>
+		<td class="form-label"><t>the exact phrase:</t></td>
+		<td><n.adv_search_input name="subjectphrase" /></td>
+	</tr>
+	<tr>
+		<td class="form-label end-group"><t>none of the words:</t></td>
+		<td class="end-group"><n.adv_search_input name="subjectnot" /></td>
+	</tr>
+</macro>
+
+<macro name="author_name_group">
+	<tr>
+		<td colspan="2">
+			<div class="shaded-bg-color rounded second-font group-header">
+				<t>Author name</t>
+			</div>
+		</td>
+	</tr>
+	<tr>
+		<td class="form-label"><t>is:</t></td>
+		<td><n.adv_search_input name="authorphrase" /></td>
+	</tr>
+	<tr>
+		<td class="form-label end-group"><t>is not:</t></td>
+		<td class="end-group"><n.adv_search_input name="authorphrasenot" /></td>
+	</tr>
+</macro>
+
+<macro name="any_message_part_contains_group">
+	<tr>
+		<td colspan="2">
+			<div class="shaded-bg-color rounded second-font group-header">
+				<t>Any message part contains</t>
+			</div>
+		</td>
+	</tr>
+	<tr>
+		<td class="form-label"><t>all of the words:</t></td>
+		<td><n.adv_search_input name="alland" /></td>
+	</tr>
+	<tr>
+		<td class="form-label"><t>at least one of the words:</t></td>
+		<td><n.adv_search_input name="allor" /></td>
+	</tr>
+	<tr>
+		<td class="form-label"><t>the exact phrase:</t></td>
+		<td><n.adv_search_input name="allphrase" /></td>
+	</tr>
+	<tr>
+		<td class="form-label end-group"><t>none of the words:</t></td>
+		<td class="end-group"><n.adv_search_input name="allnot" /></td>
+	</tr>
+</macro>
+
+<macro name="message_date_group">
+	<tr>
+		<td colspan="2">
+			<div class="shaded-bg-color rounded second-font group-header">
+				<t>Message date</t>
+			</div>
+		</td>
+	</tr>
+	<tr>
+		<td class="form-label end-group"><t>is within the last:</t></td>
+		<td><input name="days" size="5" value="[n.hide_if. equals='0'][n.hide_null.get_parameter name='days' /][/n.hide_if.]" /> <t>days</t></td>
+	</tr>
+</macro>
+
+<macro name="submit_button">
+	<tr>
+		<td></td>
+		<td style="padding-top:.5em">
+			<input type="submit" class="toolbar action-button" value="[t]Search[/t]"/>
+		</td>
+	</tr>
+</macro>
+
+<macro name="adv_search_input" parameters="name">
+	<input name="[n.name/]" type="text" style="width:370px;" value="[n.hide_null.get_parameter.name/]"/>
+</macro>
+
+<macro name="adv_search_and_query" parameters="name,field">
+	<n.regex_replace_all. pattern='(".+?"|[^ "]+)' replacement="[n.field/]:$0"><n.get_parameter name="[n.name/]" /></n.regex_replace_all.>
+</macro>
+
+<macro name="remove_double_quotes" dot_parameter="text">
+	<n.regex_replace_all. pattern='"' replacement=""><n.text/></n.regex_replace_all.>
+</macro>
+
+<macro name="adv_search_phrase_query" parameters="name,field">
+	<n.set_var. name="value"><n.trim.remove_double_quotes.get_parameter name="[n.name/]" /></n.set_var.>
+	<n.if.not.is_empty.var name="value">
+		<then>
+			<n.hide_null.append text="[n.field/]" suffix=":" />"<n.var name="value" />"
+		</then>
+	</n.if.not.is_empty.var>
+</macro>
+
+<macro name="adv_search_or_query" parameters="name,field">
+	<n.set_var. name="value"><n.trim.get_parameter name="[n.name/]" /></n.set_var.>
+	<n.set_var. name="field"><n.hide_null.append text="[n.field/]" suffix=":" /></n.set_var.>
+	<n.if.not.is_empty.var name="value">
+		<then>
+			<n.regex. pattern='(".+?"|[^ "]+)' text="[n.var name='value'/]">
+				<n.no_output.next_element/>
+				<n.if.not.has_more_elements>
+					<then>
+						<n.var name="field"/><n.current_element/>
+					</then>
+					<else>
+						<n.var name="field"/>(<n.current_element/>
+						<n.loop.>
+							<n.nop/> OR <n.current_element/>
+						</n.loop.>)
+					</else>
+				</n.if.not.has_more_elements>
+			</n.regex.>
+		</then>
+	</n.if.not.is_empty.var>
+</macro>
+
+<macro name="adv_search_not_query" parameters="name,field">
+	<n.if.not.is_empty.trim.get_parameter name="[n.name/]">
+		<then>
+			NOT <n.adv_search_or_query name="[n.name/]" field="[n.field/]" />
+		</then>
+	</n.if.not.is_empty.trim.get_parameter>
+</macro>
+
+<macro name="adv_search_not_phrase_query" parameters="name,field">
+	<n.if.not.is_empty.trim.get_parameter name="[n.name/]">
+		<then>
+			NOT <n.adv_search_phrase_query name="[n.name/]" field="[n.field/]" />
+		</then>
+	</n.if.not.is_empty.trim.get_parameter>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/app.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,687 @@
+<macro name="view_app" requires="servlet,nabble,basic">
+	<n.switch. value="[n.get_node_from_parameter.type/]">
+		<n.case. value="forum">
+			<n.call_view_forum/>
+		</n.case.>
+		<n.case. value="mixed">
+			<n.call_view_mixed/>
+		</n.case.>
+		<n.case. value="category">
+			<n.view_category/>
+		</n.case.>
+		<n.case. value="board">
+			<n.view_board/>
+		</n.case.>
+		<n.case. value="gallery">
+			<n.view_gallery/>
+		</n.case.>
+		<n.case. value="blog">
+			<n.view_blog/>
+		</n.case.>
+		<n.case. value="news">
+			<n.view_news/>
+		</n.case.>
+		<n.default_case.throw_runtime_exception text="invalid app type: '[n.switch_value/]'" />
+	</n.switch.>
+</macro>
+
+<macro name="call_view_forum">
+	<n.call_view_standard/>
+</macro>
+
+<namespace name="app_namespace" />
+<namespace name="narrow_app_namespace" />
+
+<macro name="apply_app_namespace" dot_parameter="do">
+	<n.app_namespace.do/>
+</macro>
+
+// workgroup stuff --- will clean up later [Hugo - Aug 2011]
+
+<namespace name="workgroup_app_namespace" />
+<namespace name="workgroup_narrow_app_namespace" />
+
+<macro name="apply_workgroup_app_namespace" dot_parameter="do">
+	<n.workgroup_app_namespace.do />
+</macro>
+
+<macro name="view_app canonical path" requires="http_request">
+	<n.view_app_canonical_path/>
+</macro>
+
+<macro name="view_topics canonical path" requires="http_request">
+	<n.view_app_canonical_path/>
+</macro>
+
+add more as needed
+
+<macro name="view_app_canonical_path" requires="http_request">
+	<n.get_node_from_parameter.path
+		template="[n.get_parameter name='macro'/]"
+		index_record="[n.get_parameter name='index_record'/]"
+		date="[n.get_parameter name='date'/]"
+	/>
+</macro>
+
+<macro name="app_html" parameters="head,body" requires="servlet">
+	<n.app_min_html>
+		<head>
+			<n.head/>
+			<n.app_meta/>
+			<n.increment_view_count/>
+		</head>
+		<body>
+			<n.page_node.app_hardcoded_notices/>
+			<n.newsflash/>
+			<n.show_administrator_notice/>
+			<n.app_body_header/>
+			<n.body/>
+			<n.app_body_footer/>
+		</body>
+	</n.app_min_html>
+</macro>
+
+<macro name="app_min_html" parameters="head,body" requires="servlet">
+	<n.node_page.>
+		<n.app_caching/>
+		<n.check_that_is_app/>
+		<n.html>
+			<head>
+				<n.head/>
+			</head>
+			<body>
+				<n.body/>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="app_caching" requires="node_page">
+	<n.uncache_for.>
+		<n.descendant_changes.page_node.id/>
+		<n.bread_crumb_changes.page_node.id/>
+	</n.uncache_for.>
+</macro>
+
+<macro name="node_parameter_or_root" dot_parameter="do">
+	<n.if.has_parameter name="node">
+		<then.get_node_from_parameter.do/>
+        <else.root_node.do/>
+	</n.if.has_parameter>
+</macro>
+
+<macro name="embedding_redirection_js">
+	<n.node_parameter_or_root.>
+		<n.if.has_embedding_redirection_url>
+			<then>
+				<script type="text/javascript">
+					var embeddedUrl = '<n.embedding_redirection_url/>';
+					var botPattern = "(googlebot\/|Googlebot-Mobile|Googlebot-Image|Google favicon|Mediapartners-Google|bingbot|slurp|java|wget|curl|Commons-HttpClient|Python-urllib|libwww|httpunit|nutch|phpcrawl|msnbot|jyxobot|FAST-WebCrawler|FAST Enterprise Crawler|biglotron|teoma|convera|seekbot|gigablast|exabot|ngbot|ia_archiver|GingerCrawler|webmon |httrack|webcrawler|grub.org|UsineNouvelleCrawler|antibot|netresearchserver|speedy|fluffy|bibnum.bnf|findlink|msrbot|panscient|yacybot|AISearchBot|IOI|ips-agent|tagoobot|MJ12bot|dotbot|woriobot|yanga|buzzbot|mlbot|yandexbot|purebot|Linguee Bot|Voyager|CyberPatrol|voilabot|baiduspider|citeseerxbot|spbot|twengabot|postrank|turnitinbot|scribdbot|page2rss|sitebot|linkdex|Adidxbot|blekkobot|ezooms|dotbot|Mail.RU_Bot|discobot|heritrix|findthatfile|europarchive.org|NerdByNature.Bot|sistrix crawler|ahrefsbot|Aboundex|domaincrawler|wbsearchbot|summify|ccbot|edisterbot|seznambot|ec2linkfinder|gslfbot|aihitbot|intelium_bot|facebookexternalhit|yeti|RetrevoPageAnalyzer|lb-spider|sogou|lssbot|careerbot|wotbox|wocbot|ichiro|DuckDuckBot|lssrocketcrawler|drupact|webcompanycrawler|acoonbot|openindexspider|gnam gnam spider|web-archive-net.com.bot|backlinkcrawler|coccoc|integromedb|content crawler spider|toplistbot|seokicks-robot|it2media-domain-crawler|ip-web-crawler.com|siteexplorer.info|elisabot|proximic|changedetection|blexbot|arabot|WeSEE:Search|niki-bot|CrystalSemanticsBot|rogerbot|360Spider|psbot|InterfaxScanBot|Lipperhey SEO Service|CC Metadata Scaper|g00g1e.net|GrapeshotCrawler|urlappendbot|brainobot|fr-crawler|binlar|SimpleCrawler|Livelapbot|Twitterbot|cXensebot|smtbot|bnf.fr_bot|A6-Indexer|ADmantX|Facebot|Twitterbot|OrangeBot|memorybot|AdvBot|MegaIndex|SemanticScholarBot|ltx71|nerdybot|xovibot|BUbiNG|Qwantify|archive.org_bot|Applebot|TweetmemeBot|crawler4j|findxbot|SemrushBot|yoozBot|lipperhey|y!j-asr|Domain Re-Animator Bot|AddThis)";
+					var re = new RegExp(botPattern, 'i');
+					if( !Nabble.isEmbedded && !re.test(navigator.userAgent) ) { 
+						var url = top.location.href;
+						
+						var posHtml = url.lastIndexOf('.html');
+						var c = '-';
+						var hash;
+						if (url.indexOf('?') == -1 && posHtml > 0) {
+							var posStart = url.lastIndexOf('-');
+							hash = (posStart > 0)? url.substring(posStart+1, posHtml):'';
+						} else {
+							hash = url.replace(new RegExp('https?://'), '');
+							hash = hash.substring(hash.indexOf('/')+1);
+							hash = hash;
+							c = '+';
+						}
+						if (Nabble.analytics) Nabble.analytics();
+						var _hash = hash.length==1?'':'#nabble' + c + encodeURIComponent(hash);
+
+						if (top.location.hash) {
+							var realHash = top.location.hash.substring(1);
+							_hash += '|' + encodeURIComponent(realHash);
+						}
+						_hash = _hash.length == 8 ? '' : _hash;
+						top.location.replace(embeddedUrl + _hash);
+					}
+				</script>
+			</then>
+		</n.if.has_embedding_redirection_url>
+	</n.node_parameter_or_root.>
+</macro>
+
+<macro name="check_that_is_app">
+	<n.if.not.page_node.is_app>
+		<then.redirect_to.page_node.url/>
+	</n.if.not.page_node.is_app>
+</macro>
+
+<macro name="app_meta" requires="node_page,servlet">
+	<n.if>
+		<condition>
+			<n.both>
+				<condition1.is_null.app_index_record/>
+				<condition2.equal value1="[n.app_template/]" value2="[n.app_default_template/]"/>
+			</n.both>
+		</condition>
+		<then>
+			<n.app_meta_description/>
+			<n.app_meta_keywords/>
+		</then>
+		<else>
+			<META NAME="robots" CONTENT="noindex,follow"/>
+		</else>
+	</n.if>
+</macro>
+
+<macro name="app_meta_description" requires="node_page">
+	<META NAME="description" CONTENT="[n.page_node.build_meta_description/]"/>
+</macro>
+
+<macro name="build_meta_description" requires="node">
+	<n.if.has_property name="page_meta_description">
+		<then.get_property name="page_meta_description"/>
+		<else.default_meta_description/>
+	</n.if.has_property>
+</macro>
+
+<macro name="app_meta_keywords" requires="node_page">
+	<META NAME="keywords" CONTENT="[n.page_node.build_meta_keywords/]"/>
+</macro>
+
+<macro name="build_meta_keywords" requires="node">
+	<n.default_meta_keywords/>
+</macro>
+
+<macro name="app_body_footer">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="app_title">
+	<title><n.compress.>
+		<n.page_node.app_title_contents/>
+		<n.page_node.app_title_ending/>
+	</n.compress.></title>
+</macro>
+
+<macro name="app_title_contents" requires="node">
+	<n.if.has_property name="page_title">
+		<then.get_property name="page_title"/>
+		<else.default_app_title_contents/>
+	</n.if.has_property>
+</macro>
+
+<macro name="default_app_title_contents" requires="node">
+	<n.if.not.is_root>
+		<then><n.root_node.subject/> -</then>
+	</n.if.not.is_root>
+	<n.subject/>
+</macro>
+
+<macro name="app_title_ending" requires="node">
+	<n.if.is_associated_with_mailing_list_archive>
+		<then>| <t>Mailing List Archive</t></then>
+	</n.if.is_associated_with_mailing_list_archive>
+	<n.if.not.equal value1="[n.app_template/]" value2="[n.app_default_template/]">
+		<then>| <n.capitalize.substring text="[n.app_template/]" begin="5" /></then>
+	</n.if.not.equal>
+	<n.hide_if_equals. value1="[n.app_page_number/]" value2="1">
+		| <t>Page <t.number.app_page_number/></t>
+	</n.hide_if_equals.>
+</macro>
+
+<macro name="app_body_header" requires="node_page,servlet">
+	<n.page_node.>
+		<div id="forum-header">
+			<h1 id="forum-title" class="app-title-[n.id/] adbayes-content"><n.subject/></h1>
+			<div id="description-box" class="adbayes-content">
+				<n.mailing_list_information/>
+				<n.node_message_as_html/>
+			</div>
+			<div id="search-box" class="search-box">
+				<n.search_box/>
+			</div>
+		</div>
+	</n.page_node.>
+</macro>
+
+
+<macro name="app_topic_pagination" requires="node_page,servlet" parameters="margin">
+	<n.paging.
+		total_rows="[n.app_topic_count/]"
+		current_row="[n.app_index_record/]"
+		rows_per_page="[n.app_rows_per_page/]"
+	>
+		<n.generic_paging>
+			<margin>
+				<n.default. to='.55em .2em'>
+					<n.margin/>
+				</n.default.>
+			</margin>
+			<url>
+				<n.app_paged_url/>
+			</url>
+		</n.generic_paging>
+	</n.paging.>
+</macro>
+
+<macro name="app_child_pagination" requires="node_page,servlet" parameters="margin">
+	<n.paging.
+		total_rows="[n.page_node.child_count/]"
+		current_row="[n.app_index_record/]"
+		rows_per_page="[n.app_rows_per_page/]"
+	>
+		<n.generic_paging>
+			<margin>
+				<n.default. to='.55em .2em'>
+					<n.margin/>
+				</n.default.>
+			</margin>
+			<url>
+				<n.app_paged_url/>
+			</url>
+		</n.generic_paging>
+	</n.paging.>
+</macro>
+
+<macro name="app_paged_url" requires="paging_page,node_page,servlet">
+	<n.page_node.path
+		template="[n.app_template/]"
+		date="[n.app_date/]"
+		index_record="[n.page_row/]"
+	/>
+</macro>
+
+<macro name="app_topic_count" requires="node_page,servlet">
+	<n.page_node.topic_count filter="[n.app_topic_filter/]" />
+</macro>
+
+<macro name="app_topic_filter" requires="node_page,servlet">
+	<n.cache var="app_topic_filter" value="[n.calc_app_topic_filter/]" />
+</macro>
+
+<macro name="calc_app_topic_filter" requires="node_page,servlet">
+	<n.if.not.is_null.app_date>
+		<then.page_node.date_filter date="[n.app_date/]" />
+		<else.null />
+	</n.if.not.is_null.app_date>
+</macro>
+
+
+<macro name="is_front_page" requires="servlet">
+	<n.equal value1="[n.app_template/]" value2="view_app" />
+</macro>
+
+<macro name="app_index_record" requires="servlet">
+	<n.get_parameter name="index_record"/>
+</macro>
+
+<macro name="app_template" requires="servlet">
+	<n.get_parameter name="macro"/>
+</macro>
+
+<macro name="app_date" requires="servlet">
+	<n.get_parameter name="date"/>
+</macro>
+
+
+
+<macro name="generic_paging" requires="paging" parameters="margin,url">
+	<n.if.has_paging>
+		<then>
+			<n.put_in_head.>
+				<style type="text/css">
+					span.current-page { padding: .1em .4em; }
+					span.page { padding: .1em; }
+					span.pages a { padding: .1em .4em; }
+					span.current-page { border-width:1px; border-style:solid; }
+					span.pages {
+						float:right;
+						white-space:nowrap;
+						font-weight:normal;
+					}
+				</style>
+			</n.put_in_head.>
+			<n.remove_spaces_between_tags.>
+				<span class="pages" style="padding:[n.margin/]">
+					<n.if.not.is_at_beginning>
+						<then>
+							<n.first_page.link url="[n.url/]" /> ...
+						</then>
+					</n.if.not.is_at_beginning>
+					<n.neighboring_pages.show url="[n.url/]" />
+					<n.if.not.is_at_end>
+						<then>
+							... <n.last_page.link url="[n.url/]" />
+						</then>
+					</n.if.not.is_at_end>
+				</span>
+			</n.remove_spaces_between_tags.>
+		</then>
+	</n.if.has_paging>
+</macro>
+
+<macro name="show" requires="paging_page" parameters="url">
+	<n.if.is_current_page>
+		<then>
+			<span class="current-page medium-border-color"><n.page_number/></span>
+		</then>
+		<else>
+			<n.link url="[n.url/]" />
+		</else>
+	</n.if.is_current_page>
+</macro>
+
+<macro name="link" requires="paging_page" parameters="url">
+	<span class="page">
+		<a href="[n.url/]" title="[t]Page [t.number.page_number/][/t]"><n.page_number/></a>
+	</span>
+</macro>
+
+
+
+<macro name="set_app_rows_per_page" parameters="rows_per_page">
+	<n.global_set_var name="app_rows_per_page" value="[n.rows_per_page/]" />
+</macro>
+
+<macro name="app_rows_per_page" requires="node_page">
+	<n.cache var="app_rows_per_page" value="[n.page_node.default_rows_per_page/]" />
+</macro>
+
+<macro name="app_page_number" requires="node_page,servlet">
+	<n.cache. var="app_page_number">
+		<n.paging.
+			total_rows="0"
+			current_row="[n.app_index_record/]"
+			rows_per_page="[n.app_rows_per_page/]"
+		>
+			<n.current_page_number/>
+		</n.paging.>
+	</n.cache.>
+</macro>
+
+
+
+<macro name="url" parameters="template,index_record,date" requires="node">
+	<n.remove_spaces.>
+		<n.base_url/>
+		<n.path
+			template="[n.template/]"
+			index_record="[n.index_record/]"
+			date="[n.date/]"
+		/>
+	</n.remove_spaces.>
+</macro>
+
+<macro name="path" parameters="template,index_record,date" requires="node">
+	<n.if.is_app>
+		<then>
+			<n.app_path
+				template="[n.template/]"
+				index_record="[n.index_record/]"
+				date="[n.date/]"
+			/>
+		</then>
+		<else>
+			<n.post_path />
+		</else>
+	</n.if.is_app>
+</macro>
+
+<macro name="app_path" parameters="template,index_record,date" requires="node">
+	<n.encode_url.remove_spaces.>
+		<n.set_var. name="index_record">
+			<n.to_null_if. equals="0">
+				<n.index_record/>
+			</n.to_null_if.>
+		</n.set_var.>
+		<n.set_var. name="template">
+			<n.to_null_if. equals="[n.app_default_template/]">
+				<n.template/>
+			</n.to_null_if.>
+		</n.set_var.>
+		<n.if.not.is_null.var name="template">
+			<then>
+				<n.if.not.starts_with prefix="view_" text="[n.var name='template'/]">
+					<then>
+						<n.throw_runtime_exception.>
+							template = <n.var name='template'/>
+						</n.throw_runtime_exception.>
+					</then>
+				</n.if.not.starts_with>
+				<n.set_var. name="template">
+					<n.substring text="[n.var name='template'/]" begin="5" />
+				</n.set_var.>
+			</then>
+		</n.if.not.is_null.var>
+		/
+		<n.if>
+			<condition>
+				<n.not.all_true.>
+					<n.is_root/>
+					<n.is_null.var name='template'/>
+					<n.is_null.date/>
+					<n.is_null.var name='index_record'/>
+				</n.not.all_true.>
+			</condition>
+			<then>
+				<n.url_encoded_subject/>
+				-f<n.id/>
+				<n.hide_null.prepend. prefix="i"><n.var name="index_record"/></n.hide_null.prepend.>
+				<n.hide_null.prepend. prefix="d"><n.date/></n.hide_null.prepend.>
+				<n.hide_null.prepend. prefix="."><n.var name="template"/></n.hide_null.prepend.>
+				.html
+			</then>
+		</n.if>
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="app_path_by_date" parameters="date" requires="node">
+	<n.app_path
+		template="[n.app_template/]"
+		index_record="[n.app_index_record/]"
+		date="[n.date/]"
+	/>
+</macro>
+
+<macro name="app_url" parameters="template,index_record,date" requires="node">
+	<n.base_url/><n.app_path
+		template="[n.template/]"
+		index_record="[n.index_record/]"
+		date="[n.date/]"
+	/>
+</macro>
+
+<macro name="app_default_template">
+	view_app
+</macro>
+
+
+
+<macro name="is_pinned_in_loop" requires="node,node_list">
+	<n.both>
+		<condition1>
+			<n.is_pinned/>
+		</condition1>
+		<condition2>
+			<n.parent_node.equals.loop_node/>
+		</condition2>
+	</n.both>
+</macro>
+
+<macro name="is_in_subapp" requires="node,node_list">
+	<n.both>
+		<condition1>
+			<n.is_post/>
+		</condition1>
+		<condition2>
+			<n.both>
+				<condition1>
+					<n.has_loop_node/>
+				</condition1>
+				<condition2>
+					<n.get_app_node.not.equals.loop_node/>
+				</condition2>
+			</n.both>
+		</condition2>
+	</n.both>
+</macro>
+
+<macro name="subapp_link" requires="node,node_list">
+	<n.if.is_in_subapp>
+		<then>
+			<n.get_app_node.node_link/>
+		</then>
+	</n.if.is_in_subapp>
+</macro>
+
+<macro name="subapp_link_on_hover" requires="node,node_list">
+	<n.if.is_in_subapp>
+		<then>
+			<n.link_on_hover
+				id = "[n.id/]"
+				href = "[n.get_app_node.path/]"
+				text = "[n.get_app_node.subject/]"
+			/>
+		</then>
+	</n.if.is_in_subapp>
+</macro>
+
+<macro name="link_on_hover" parameters="id,href,text">
+	<n.put_in_head.>
+		<script type="text/javascript">
+<![CDATA[
+			function createMouseTrick(nodeid) {
+				$(document).ready(function() {
+					var $text = $('#hover-text'+nodeid);
+					var $link = $('#hover-link'+nodeid);
+					var entered = false;
+					var count = 0;
+					$text.mouseover(function() {
+						$(this).hide();
+						$link.show();
+						checkMouse();
+					});
+
+					$link.mouseover(function() {
+						entered = true;
+					});
+
+					$link.mouseout(function() {
+						$link.hide();
+						$text.show();
+						count = 0;
+						entered = false;
+					});
+
+					function checkMouse() {
+						if (!entered) {
+							count++;
+							if (count < 5) {
+								setTimeout(checkMouse, 100);
+							} else {
+								$link.hide();
+								$text.show();
+								count = 0;
+								entered = false;
+							}
+						}
+					}
+				});
+			};
+]]>
+		</script>
+	</n.put_in_head.>
+	<span id="hover-text[n.id/]"><n.text/></span>
+	<script type="text/javascript">
+		document.write('<a id="hover-link[n.id/]" href="[n.href/]" style="display:none">');
+		document.write('<n.javascript_string_encode.get_app_node.text/>');
+		document.write('</a>');
+		createMouseTrick(<n.id/>);
+	</script>
+</macro>
+
+
+
+just for now:
+
+<macro name="view_comment" requires="servlet">
+	<n.redirect_to.node_page.page_node.url/>
+</macro>
+
+<macro name="view_blog_entry" requires="servlet">
+	<n.redirect_to.node_page.page_node.url/>
+</macro>
+
+<macro name="view_gallery_entry" requires="servlet">
+	<n.redirect_to.node_page.page_node.url/>
+</macro>
+
+<macro name="view_news_entry" requires="servlet">
+	<n.redirect_to.node_page.page_node.url/>
+</macro>
+
+
+
+<macro name="children_list" parameters="start,length,filter,sort" dot_parameter="do" requires="node">
+	<n.children_list_standard start="[n.start/]" length="[n.length/]" filter="[n.filter/]" do="[n.do/]" />
+</macro>
+
+<macro name="topics_list" parameters="start,length,filter,sort" dot_parameter="do" requires="node">
+	<n.topics_list_standard start="[n.start/]" length="[n.length/]" filter="[n.filter/]" sort="[n.sort/]" do="[n.do/]" />
+</macro>
+
+<macro name="app_hardcoded_notices" requires="node_page,servlet">
+	<n.inactive_node_deletion_ui/>
+
+	<script type="text/javascript">
+		$(document).ready(function() {
+			if (Nabble.appnotice) {
+				$('#creation-ad').slideDown();
+				Nabble.setVar('appnotice',null);
+			}
+		});
+	</script>
+
+	<n.if.should_show_creation_notice>
+		<then>
+			<div id="creation-ad" class="app-notice light-border-color info-message" style="display:none">
+				<n.congratulations_notice/>
+			</div>
+			<script type="text/javascript">
+				/* Fix for widget creation (weebly)  */
+				if (Nabble.getParent().nabbleinfo && !Nabble.getParent().nabbleinfo.what) {
+					Nabble.getParent().nabbleinfo.what = '<n.page_node.lower_case_view_name/>';
+				}
+			</script>
+		</then>
+	</n.if.should_show_creation_notice>
+</macro>
+
+<macro name="congratulations_notice">
+	<div class="big-title second-font"><t>Congratulations!</t></div>
+	<t>Your <t.app.page_node.lower_case_view_name/> has been successfully created.</t><br/>
+	<t>Please check your inbox now and activate your account in order to have access to all features.</t><br/>
+	<button class="toolbar action-button" onclick="$('#creation-ad').slideUp();" style="margin:.5em 1em 0">
+		<t>Close this message</t>
+	</button>
+</macro>
+
+<macro name="inactive_node_deletion_ui" requires="node_page">
+	<n.if.page_node_is_scheduled_for_deletion>
+		<then>
+			<script type="text/javascript">
+				function clearDeleteDate() {
+					$.getScript('/forum/ClearDeleteDate.jtp');
+				}
+			</script>
+			<div id="inactive-delete" class="app-notice info-message">
+				<div class="big-title second-font">This <n.page_node.lower_case_view_name/> will be deleted soon!</div>
+				Nabble has scheduled this <n.page_node.lower_case_view_name/> to be deleted on
+				<script type="text/javascript">document.write(Nabble.formatDateLong(new Date(<n.site_delete_date.raw_time/>)))</script>
+				due to its inactivity.<br/>
+				Click on the button below if you want to save this <n.page_node.lower_case_view_name/> from deletion.<br/>
+				<input type="button" value="Don't delete this [n.page_node.lower_case_view_name/]" onclick="clearDeleteDate()" style="margin:.5em 1em 0"></input>
+			</div>
+		</then>
+	</n.if.page_node_is_scheduled_for_deletion>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/backup.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,112 @@
+
+<macro name="download_backup_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=download_backup_page
+	</n.encode_url.>
+</macro>
+
+<macro name="download_backup_link" dot_parameter="text" parameters="title, class">
+	<a href="[n.download_backup_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Download backup[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="download_backup_page" requires="servlet">
+	<n.if.not.visitor.is_site_admin>
+		<then>
+			<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+		</then>
+	</n.if.not.visitor.is_site_admin>
+	<n.html>
+		<head>
+			<n.title.><t>Backup</t></n.title.>
+		</head>
+		<body>
+			<h1><t>Backup</t></h1>
+
+			<n.if.is_running_backup>
+				<then>
+					<p>
+						<t>The system is generating the backup now. When it is finished, a link to the backup file will be emailed to you.</t>
+					</p>
+				</then>
+				<else>
+					<p>
+						<t>Here you can download a backup of <t.location.root_node.subject/>.</t>
+						<t>When you press the button below, the system will start the generation of the backup file and
+						this may take some minutes to finish. You will receive an email with a link to download the file when it is ready.</t>
+					</p>
+
+					<p>
+						<n.form. macro="start_backup_process">
+							<input type="submit" class="toolbar action-button" value="[t]Generate backup file[/t]"/>
+							<t>or</t>
+							<a href="[n.root_node.url/]"><t>Cancel</t></a>
+						</n.form.>
+					</p>
+
+					<p class="light-bg-color rounded" style="padding:.5em 1em;margin-top:2em">
+						<t>The backup file is generated with the <a href="https://github.com/tig100/JdbcPgBackup">JdbcPgBackup</a> tool,
+						which is an open source project in Java. You should visit the project website if you want to restore your backup
+						into a Postgresql database.</t>
+					</p>
+				</else>
+			</n.if.is_running_backup>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="start_backup_process" requires="servlet">
+	<n.if.visitor.is_site_admin>
+		<then>
+			<n.make_backup email="[n.visitor.user_email/]"/>
+			<n.redirect_to.download_backup_path/>
+		</then>
+	</n.if.visitor.is_site_admin>
+</macro>
+
+<macro name="backup email" parameters="email, file" unindent="true">
+	<n.new_email.>
+		<n.send>
+			<to><n.email/></to>
+			<subject><t>Backup of <t.location.root_node.subject/></t></subject>
+			<text_part>
+				<t>Dear user,</t>
+
+				<t>Here is your backup file:</t>
+
+				<n.server_url/>/backups/<n.encode_url.file/>
+
+				<t>Sincerely,</t>
+				<t>The Nabble team</t>
+				________________________________________
+				<t>Free Embeddable <t.app.root_node.view_name/></t> powered by Nabble
+				<n.nabble_homepage/>
+			</text_part>
+		</n.send>
+	</n.new_email.>
+</macro>
+
+<macro name="site deletion email" parameters="file" unindent="true">
+	<n.new_email.>
+		<n.send>
+			<to><n.root_node.owner.user_email/></to>
+			<subject><t><t.location.root_node.subject/> has been deleted</t></subject>
+			<text_part>
+				<t>Dear user,</t>
+
+				<t>Your Nabble site "<t.location.root_node.subject/>" has been deleted.</t>
+
+				<t>You can download a backup of this site from the link below.
+					Nabble will try to keep this backup available for a few months, but this is not guaranteed.
+					If this content is important to you, save this copy as soon as possible.</t>
+
+				<n.server_url/>/backups/<n.encode_url.file/>
+
+				<t>Sincerely,</t>
+				<t>The Nabble team</t>
+				________________________________________
+				<t>Free Embeddable <t.app.root_node.view_name/></t> powered by Nabble
+				<n.nabble_homepage/>
+			</text_part>
+		</n.send>
+	</n.new_email.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/ban_user.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,139 @@
+<macro name="ban_user" requires="servlet">
+	<n.user_page.>
+		<n.if.not.visitor.can_manage_banned_users>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_manage_banned_users>
+		<n.if.is_submitted_form>
+			<then>
+				<n.if.ban_user_field.is_checked>
+					<then><n.page_user.ban/></then>
+				</n.if.ban_user_field.is_checked>
+
+				<n.if.delete_posts_field.is_checked>
+					<then><n.page_user.delete_user_nodes/></then>
+				</n.if.delete_posts_field.is_checked>
+			</then>
+		</n.if.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Ban User</t></n.title.>
+				<script type="text/javascript">
+					$(document).ready(function() {
+						function enableButton() {
+							var $submit = $('input[type="submit"]');
+							var disabled = !$('#ban_this_user').is(':checked') && !$('#delete_posts').is(':checked');
+							if (disabled)
+								$submit.attr('disabled','true');
+							else
+								$submit.removeAttr('disabled');
+						}
+						$('input[type="checkbox"]').click(enableButton);
+						enableButton();
+					});
+				</script>
+				<n.bold_label_style/>
+			</head>
+			<body>
+				<n.edit_header first_text="[t]Ban User[/t]" second_text="[n.page_user.name/]" />
+
+				<n.form.>
+					<table style="margin-bottom: .5em">
+						<tr valign="top">
+							<td><n.page_user.avatar size="big"/></td>
+							<td>
+								<div style="padding:.3em .5em">
+								<t>Select below the actions you want to take:</t>
+								</div>
+								<n.ban_user_control/>
+								<n.delete_posts_control/>
+							</td>
+						</tr>
+					</table>
+
+					<n.if.not.is_submitted_form>
+						<then>
+							<div style="margin-top:1.4em">
+								<input type="submit" value="[t]Take Action[/t]" disabled="true"/>
+								<t>or</t> <a href="[n.page_user.url/]"><t>Cancel</t></a>
+							</div>
+						</then>
+					</n.if.not.is_submitted_form>
+				</n.form.>
+
+				<p><t>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location.root_node.subject/>.</t></p>
+			</body>
+		</n.html>
+	</n.user_page.>
+</macro>
+
+<macro name="ban_user_control">
+	<n.if.page_user.is_authenticated>
+		<then>
+			<table style="margin-bottom:1em">
+				<tr valign="top">
+					<n.ban_user_field.>
+						<n.if.not.is_submitted_form>
+							<then>
+								<td><n.checkbox/></td>
+								<td>
+									<label for="[n.name/]"><t>Ban this user</t></label>
+									<div class="weak-color" style="margin-top:.3em">
+										<t>If you ban this user, he/she won't be able to do anything in <t.location.root_node.subject/>.</t>
+										<t>Remember that the banning action isn't efficient because the user can always come back with a different account.</t>
+									</div>
+								</td>
+							</then>
+							<else>
+								<n.if.is_checked>
+									<then>
+										<td><img src="/images/success.png" class="image16"/></td>
+										<td><t><t.author><b><n.page_user.name/></b></t.author> has been successfully banned.</t></td>
+									</then>
+								</n.if.is_checked>
+							</else>
+						</n.if.not.is_submitted_form>
+					</n.ban_user_field.>
+				</tr>
+			</table>
+		</then>
+	</n.if.page_user.is_authenticated>
+</macro>
+
+<macro name="delete_posts_control">
+	<table style="margin-bottom:1em">
+		<tr valign="top">
+			<n.delete_posts_field.>
+				<n.if.not.is_submitted_form>
+					<then>
+						<td><n.checkbox/></td>
+						<td>
+							<label for="[n.name/]"><t>Delete all posts from this user</t></label>
+							<div class="weak-color" style="margin-top:.3em">
+								<t>The user will receive a copy of all delete posts by email, so that he/she can have a chance to save them.</t>
+							</div>
+						</td>
+					</then>
+					<else>
+						<n.if.is_checked>
+							<then>
+								<td><img src="/images/success.png" class="image16"/></td>
+								<td><t>All posts from <t.author><b><n.page_user.name/></b></t.author> have been successfully removed.</t></td>
+							</then>
+						</n.if.is_checked>
+					</else>
+				</n.if.not.is_submitted_form>
+			</n.delete_posts_field.>
+		</tr>
+	</table>
+</macro>
+
+<macro name="ban_user_field" dot_parameter="do">
+	<n.field. name="ban_this_user"><n.do/></n.field.>
+</macro>
+
+<macro name="delete_posts_field" dot_parameter="do">
+	<n.field. name="delete_posts"><n.do/></n.field.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/change_app_type.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,57 @@
+<macro name="change_app_type" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_edit.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_edit.page_node>
+		<n.if.not.is_submitted_form>
+			<then>
+				<n.type_field.set_value value="[n.page_node.type/]" />
+			</then>
+			<else>
+				<n.catch_exception. id="save-block">
+					<n.edit_page_node.>
+						<n.set_type type="[n.type_field.value/]" />
+						<n.save_node/>
+					</n.edit_page_node.>
+					<n.redirect_to.page_node.path/>
+				</n.catch_exception.>
+			</else>
+		</n.if.not.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Change Application Type</t></n.title.>
+			</head>
+			<body>
+				<n.edit_header first_text="[t]Change Application Type[/t]" second_text="[n.page_node.get_app_node.subject/]" />
+
+				<n.form.>
+					<n.type_option option_value="forum" text="[t]Forum[/t]" description="[t]Page with topics and discussions.[/t]"/>
+					<n.type_option option_value="mixed" text="[t]Mixed[/t]" description="[t]Page with topics grouped by sub-applications. If no sub-apps exist, a normal list of topics is displayed.[/t]"/>
+					<n.type_option option_value="category" text="[t]Category[/t]" description="[t]Page with a simple list of sub-applications and discussions.[/t]"/>
+					<n.type_option option_value="board" text="[t]Board[/t]" description="[t]Page that groups sub-applications and discussions.[/t]"/>
+					<n.type_option option_value="gallery" text="[t]Gallery[/t]" description="[t]Photo and image gallery.[/t]"/>
+					<n.type_option option_value="blog" text="[t]Blog[/t]" description="[t]Page with full messages and related comments.[/t]"/>
+					<n.type_option option_value="news" text="[t]News[/t]" description="[t]Page with headlines and posts.[/t]"/>
+
+					<div style="margin-top:1.4em">
+						<input type="submit" class="toolbar action-button" value="[t]Save Changes[/t]" />
+						<t>or</t> <a href="[n.page_node.path/]"><t>Cancel</t></a>
+					</div>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="type_option" parameters="option_value,text,description">
+	<div class="second-font field-title" style="margin-top:1em">
+		<n.type_field.radio id="[n.option_value/]" option_value="[n.option_value/]"/>
+		<label for="[n.option_value/]"><n.text/></label>
+	</div>
+	<div class="weak-color" style="margin-left:1.9em">
+		<n.description/>
+	</div>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/change_appearance.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1451 @@
+<macro name="change_appearance" requires="servlet">
+	<n.if.not.visitor.can_edit.root_node>
+		<then>
+			<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+		</then>
+	</n.if.not.visitor.can_edit.root_node>
+
+	<n.nabble_html>
+		<do>
+			<n.embedding_redirection_js/>
+			<n.change_appearance_body/>
+		</do>
+		<output>
+			<![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">]]>
+			<html>
+				<head>
+					<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+					<title>Change appearance of <n.root_node.subject/></title>
+					<link rel="stylesheet" href="/nabble.css?v=[n.css_version/]" type="text/css" />
+					<n.change_appearance_stylesheet/>
+					<n.nabble_javascript_libraries/>
+					<script type="text/javascript" src="/util/jscolor/jscolor.js"></script>
+					<script type="text/javascript">
+						jscolor.dir = '/images/jscolor/';
+					</script>
+					<n.change_appearance_js/>
+					<n.html_head_content/>
+				</head>
+				<body>
+					<n.html_body_content/>
+				</body>
+			</html>
+		</output>
+	</n.nabble_html>
+</macro>
+
+<macro name="change_appearance_stylesheet">
+	<style type="text/css">
+		body {
+			margin:0;
+			padding:0;
+			overflow:hidden;
+		}
+		#change-appearance {
+			padding:.8em;
+			font: normal 1em 'Open Sans', Verdana, sans-serif;
+			color: #EEE;
+			text-shadow: 1px 1px 0 #000;
+			background: url('gradients/v30_DDDDDD88_CCCCCC00') #000 repeat-x;
+		}
+		#preview-frame {
+			position:absolute;
+			width:100%;
+		}
+		span.change-appearance-group {
+			background: url("gradients/v30_FFFFFF65_DDDDDD00") repeat-x scroll 0 0 #222222;
+			font-weight:bold;
+			padding: .2em .4em;
+			border-color: #333 #555 #888;
+			border-width:2px;
+			border-style: solid;
+			margin-right:.7em;
+			cursor: pointer;
+			white-space:nowrap;
+		}
+		div.change-appearance-group-panel {
+			background-color: #000;
+			border: 2px solid #888;
+			width:30em;
+			padding:.5em;
+			margin-top:.3em;
+			position:absolute;
+			z-index:1000;
+			display:none;
+		}
+		.weak-color {
+			color: #bbb;
+		}
+		td.label-column {
+			white-space:nowrap;
+			font-weight:bold;
+		}
+		div.sub-section-title {
+			font-size:80%;
+			margin:.3em 0 .1em;
+			padding:.2em .3em;
+			border-bottom:1px solid #777;
+		}
+		span.option-button {
+			background:#444;
+			padding:.2em .4em;
+			color:#dd0;
+			margin:0 .3em .3em 0;
+			white-space:nowrap;
+			cursor:pointer;
+		}
+		div.dropdown-box {
+			width:20em;
+			padding: .3em;
+			background:#000;
+			border:2px solid #777;
+			display:none;
+			-moz-box-shadow: 2px 2px 10px #FFFFFF;
+			-webkit-box-shadow: 2px 2px 10px #FFFFFF;
+			box-shadow: 2px 2px 10px #FFFFFF;
+		}
+		.section-title {
+			font-weight:bold;
+			color: #dd0;
+		}
+		.small-description {
+			font-size:80%;
+			padding:.3em 0;
+			color:#ddd;
+		}
+		table {
+			border-spacing:0;
+			width:100%;
+		}
+		table td {
+			padding:0;
+		}
+		.error-message {
+			color:red;
+			font-weight:bold;
+			display:none;
+		}
+		.error-field { border: 3px solid red; }
+		a:link { color:#78F; }
+		a:visited { color:#76D; }
+		a.close-link {
+			font-size:200%;
+			position:absolute;
+			right:10px;
+			top:0;
+			text-decoration:none;
+			color:white;
+		}
+		div.color-scheme-row {
+			clear:both;
+			cursor:pointer;
+			margin-bottom:.2em;
+		}
+		div.color-scheme {
+			width:8px;
+			float:right;
+			font-size:80%;
+			margin-top:2px;
+		}
+	</style>
+</macro>
+
+<macro name="change_appearance_js">
+	<script type="text/javascript">
+		window.isChangeAppearance = true; /* see dropdown.naml */
+		var selectedFont = '#ADE';
+		function layout() {
+			var $frm = $('#preview-frame');
+			var $w = $(window);
+			var $tbar = $('#change-appearance');
+			$frm.height($w.height()-$tbar.height()-30);
+			$frm.css('top', $tbar.height()+20);
+		};
+		function closeFrame() {
+			location = '/';
+		};
+		function hideOpenMenus() {
+			$('span.change-appearance-group').next().slideUp('fast');
+		};
+
+		var font_list = [];
+		var color_list = [];
+		var preferences_list = [];
+		var css_list = [];
+		function addField(list, id, initial, def, type) {
+			list.push({
+				'id': id,
+				'initial': initial,
+				'default': def,
+				'type': type
+			});
+		};
+<![CDATA[
+		function setFieldValues0(list, source) {
+			for (var i=0; i < list.length; i++) {
+				var o = list[i];
+				var value = o[source];
+				if (o.type == 'checkbox')
+					checkboxValue('#'+o.id, value);
+				else if (o.type == 'radio')
+					$('#'+o.id+value).attr('checked', true);
+				else if (o.type == 'color') {
+					var input = document.getElementById(o.id);
+					if (input.color)
+						input.color.fromString(value);
+					else
+						$(input).val(value);
+				} else
+					$('#'+o.id).val(value);
+			}
+		};
+]]>
+		function setFieldValues() {
+			addField(font_list, 'font-size', '<n.javascript_string_encode.naml_configuration.get_value name="fontSize" default="84"/>', '84');
+			addField(font_list, 'main-font', '<n.javascript_string_encode.naml_configuration.get_value name="mainFontFamily" default=""/>', '');
+			addField(font_list, 'title-font', '<n.javascript_string_encode.naml_configuration.get_value name="titleFontFamily" default=""/>', '');
+			setFieldValues0(font_list, 'initial');
+
+			addField(color_list, 'bg-color', '<n.javascript_string_encode.naml_configuration.get_value name="bgColor" default="FFFFFF"/>', 'FFFFFF', 'color');
+			addField(color_list, 'light-bg-color', '<n.javascript_string_encode.naml_configuration.get_value name="lightBgColor" default="F2F2F2"/>', 'F2F2F2', 'color');
+			addField(color_list, 'shaded-bg-color', '<n.javascript_string_encode.naml_configuration.get_value name="shadedBgColor" default="EEEEEE"/>', 'EEEEEE', 'color');
+			addField(color_list, 'dark-bg-color', '<n.javascript_string_encode.naml_configuration.get_value name="darkBgColor" default="DDDDDD"/>', 'DDDDDD', 'color');
+			addField(color_list, 'highlight-bg-color', '<n.javascript_string_encode.naml_configuration.get_value name="highlightBgColor" default="FFFF99"/>', 'FFFF99', 'color');
+			addField(color_list, 'text-color', '<n.javascript_string_encode.naml_configuration.get_value name="textColor" default="000000"/>', '000000', 'color');
+			addField(color_list, 'text-weak-color', '<n.javascript_string_encode.naml_configuration.get_value name="textWeakColor" default="666666"/>', '666666', 'color');
+			addField(color_list, 'title-color', '<n.javascript_string_encode.naml_configuration.get_value name="titleColor" default="333333"/>', '333333', 'color');
+			addField(color_list, 'link-color', '<n.javascript_string_encode.naml_configuration.get_value name="linkColor" default="0000EE"/>', '0000EE', 'color');
+			addField(color_list, 'link-visited-color', '<n.javascript_string_encode.naml_configuration.get_value name="linkVisitedColor" default="551A8B"/>', '551A8B', 'color');
+			addField(color_list, 'input-text-color', '<n.javascript_string_encode.naml_configuration.get_value name="inputTextColor" default="000000"/>', '000000', 'color');
+			addField(color_list, 'input-bg-color', '<n.javascript_string_encode.naml_configuration.get_value name="inputBgColor" default="FFFFFF"/>', 'FFFFFF', 'color');
+			addField(color_list, 'light-border-color', '<n.javascript_string_encode.naml_configuration.get_value name="lightBorderColor" default="EEEEEE"/>', 'EEEEEE', 'color');
+			addField(color_list, 'medium-border-color', '<n.javascript_string_encode.naml_configuration.get_value name="mediumBorderColor" default="CCCCCC"/>', 'CCCCCC', 'color');
+			addField(color_list, 'dark-border-color', '<n.javascript_string_encode.naml_configuration.get_value name="darkBorderColor" default="666666"/>', '666666', 'color');
+			addField(color_list, 'info-bg-color', '<n.javascript_string_encode.naml_configuration.get_value name="infoBgColor" default="FFFFCC"/>', 'FFFFCC', 'color');
+			addField(color_list, 'info-text-color', '<n.javascript_string_encode.naml_configuration.get_value name="infoTextColor" default="000000"/>', '000000', 'color');
+			addField(color_list, 'error-bg-color', '<n.javascript_string_encode.naml_configuration.get_value name="errorBgColor" default="FFFFCC"/>', 'FFFFCC', 'color');
+			addField(color_list, 'error-text-color', '<n.javascript_string_encode.naml_configuration.get_value name="errorTextColor" default="000000"/>', '000000', 'color');
+			setFieldValues0(color_list, 'initial');
+
+			addField(preferences_list, 'show-search-box', '<n.javascript_string_encode.naml_configuration.get_value name="showSearchBox" default="true"/>', 'true', 'checkbox');
+			addField(preferences_list, 'search-box-alignment', '<n.javascript_string_encode.naml_configuration.get_value name="searchBoxAlignment" default="right"/>', 'right');
+			addField(preferences_list, 'show-app-title', '<n.javascript_string_encode.naml_configuration.get_value name="showAppTitle" default="true"/>', 'true', 'checkbox');
+			addField(preferences_list, 'app-title-alignment', '<n.javascript_string_encode.naml_configuration.get_value name="appTitleAlignment" default="center"/>', 'center');
+			addField(preferences_list, 'app-description-alignment', '<n.javascript_string_encode.naml_configuration.get_value name="appDescriptionAlignment" default="center"/>', 'center');
+			addField(preferences_list, 'page-layout', '<n.javascript_string_encode.naml_configuration.get_value name="pageLayout" default="wide"/>', 'wide');
+			addField(preferences_list, 'forum-topics-per-page', '<n.javascript_string_encode.naml_configuration.get_value name="forumTopicsPerPage" default="35"/>', '35');
+			addField(preferences_list, 'blog-topics-per-page', '<n.javascript_string_encode.naml_configuration.get_value name="blogTopicsPerPage" default="10"/>', '10');
+			addField(preferences_list, 'news-topics-per-page', '<n.javascript_string_encode.naml_configuration.get_value name="newsTopicsPerPage" default="25"/>', '25');
+			addField(preferences_list, 'gallery-topics-per-page', '<n.javascript_string_encode.naml_configuration.get_value name="galleryTopicsPerPage" default="16"/>', '16');
+			addField(preferences_list, 'mixed-topics-per-page', '<n.javascript_string_encode.naml_configuration.get_value name="mixedTopicsPerPage" default="6"/>', '6');
+			addField(preferences_list, 'classic-posts-per-page', '<n.javascript_string_encode.naml_configuration.get_value name="classicPostsPerPage" default="20"/>', '20');
+			addField(preferences_list, 'picture-size-classic', '<n.javascript_string_encode.naml_configuration.get_value name="pictureSizeClassic" default="big"/>', 'big');
+			addField(preferences_list, 'forum-format-', '<n.javascript_string_encode.naml_configuration.get_value name="forumFormat" default="standard"/>', 'standard', 'radio');
+			setFieldValues0(preferences_list, 'initial');
+
+			addField(css_list, 'custom-css', '<n.javascript_string_encode.naml_configuration.get_value name="customCss" default=""/>', '');
+			setFieldValues0(css_list, 'initial');
+		};
+
+		function confirmRestore() {
+			return confirm('Do you really want to restore the default values and lose your changes?');
+		};
+
+		function restoreFontDefaults() {
+			if (confirmRestore()) {
+				setFieldValues0(font_list, 'default');
+				applyFont();
+			}
+		};
+
+		function applyFont() {
+			try {
+				clearValidation();
+				validateRange('#font-size', 70, 140);
+			} catch(err) {
+				showErrorMessage('font');
+				return;
+			}
+			var fontSize = $('#font-size').val();
+			var mainFont = $('#main-font').val();
+			var titleFont = $('#title-font').val();
+			var params = {};
+			params['macro'] = 'save_font_config';
+			params['fontSize'] = fontSize;
+			params['mainFontFamily'] = mainFont;
+			params['titleFontFamily'] = titleFont;
+			apply(params);
+		};
+		function checkboxValue(selector, value) {
+			if (value) $(selector).attr('checked', true);
+			else $(selector).removeAttr('checked');
+		};
+
+		function restoreColorDefaults() {
+			if (confirmRestore()) {
+				setFieldValues0(color_list, 'default');
+				applyColor();
+			}
+		};
+
+		function applyColor() {
+			var params = {};
+			params['macro'] = 'save_color_config';
+			params['bgColor'] = $('#bg-color').val();
+			params['lightBgColor'] = $('#light-bg-color').val();
+			params['shadedBgColor'] = $('#shaded-bg-color').val();
+			params['darkBgColor'] = $('#dark-bg-color').val();
+			params['highlightBgColor'] = $('#highlight-bg-color').val();
+			params['textColor'] = $('#text-color').val();
+			params['textWeakColor'] = $('#text-weak-color').val();
+			params['titleColor'] = $('#title-color').val();
+			params['linkColor'] = $('#link-color').val();
+			params['linkVisitedColor'] = $('#link-visited-color').val();
+			params['inputTextColor'] = $('#input-text-color').val();
+			params['inputBgColor'] = $('#input-bg-color').val();
+			params['lightBorderColor'] = $('#light-border-color').val();
+			params['mediumBorderColor'] = $('#medium-border-color').val();
+			params['darkBorderColor'] = $('#dark-border-color').val();
+			params['infoBgColor'] = $('#info-bg-color').val();
+			params['infoTextColor'] = $('#info-text-color').val();
+			params['errorBgColor'] = $('#error-bg-color').val();
+			params['errorTextColor'] = $('#error-text-color').val();
+			apply(params);
+		};
+
+		function applyPreferences() {
+			try {
+				clearValidation();
+				validateRange('#forum-topics-per-page', 1, 100);
+				validateRange('#blog-topics-per-page', 1, 100);
+				validateRange('#gallery-topics-per-page', 1, 100);
+				validateRange('#news-topics-per-page', 1, 100);
+				validateRange('#classic-posts-per-page', 1, 100);
+				validateMixedCsv('#mixed-topics-per-page', 1, 20);
+			} catch(err) {
+				showErrorMessage('preferences');
+				return;
+			}
+			var showSearchBox = $('#show-search-box').is(':checked');
+			var searchBoxAlignment = $('#search-box-alignment').val();
+			var showAppTitle = $('#show-app-title').is(':checked');
+			var appTitleAlignment = $('#app-title-alignment').val();
+			var appDescriptionAlignment = $('#app-description-alignment').val();
+			var pageLayout = $('#page-layout').val();
+			var forumTopicsPerPage = $('#forum-topics-per-page').val();
+			var blogTopicsPerPage = $('#blog-topics-per-page').val();
+			var galleryTopicsPerPage = $('#gallery-topics-per-page').val();
+			var newsTopicsPerPage = $('#news-topics-per-page').val();
+			var mixedTopicsPerPage = $('#mixed-topics-per-page').val();
+			var pictureSizeClassic = $('#picture-size-classic').val();
+			var classicPostsPerPage = $('#classic-posts-per-page').val();
+			var forumFormat = $('#forum-format-standard').is(':checked')?'standard':'topics';
+			var params = {};
+			params['macro'] = 'save_preferences_config';
+			params['showSearchBox'] = showSearchBox;
+			params['searchBoxAlignment'] = searchBoxAlignment;
+			params['showAppTitle'] = showAppTitle;
+			params['appTitleAlignment'] = appTitleAlignment;
+			params['appDescriptionAlignment'] = appDescriptionAlignment;
+			params['pageLayout'] = pageLayout;
+			params['forumTopicsPerPage'] = forumTopicsPerPage;
+			params['blogTopicsPerPage'] = blogTopicsPerPage;
+			params['galleryTopicsPerPage'] = galleryTopicsPerPage;
+			params['newsTopicsPerPage'] = newsTopicsPerPage;
+			params['mixedTopicsPerPage'] = mixedTopicsPerPage;
+			params['pictureSizeClassic'] = pictureSizeClassic;
+			params['classicPostsPerPage'] = classicPostsPerPage;
+			params['forumFormat'] = forumFormat;
+			apply(params);
+		};
+		function restorePreferencesDefaults() {
+			if (confirmRestore()) {
+				setFieldValues0(preferences_list, 'default');
+				updateUI();
+				applyPreferences();
+			}
+		};
+		function applyCss() {
+			var customCss = $('#custom-css').val();
+			var params = {};
+			params['macro'] = 'save_css_config';
+			params['customCss'] = customCss;
+			apply(params);
+		};
+		function restoreCssDefaults() {
+			if (confirmRestore()) {
+				setFieldValues0(css_list, 'default');
+				applyCss();
+			}
+		};
+		function apply(params) {
+			hideOpenMenus();
+			window.preview.notice('<t>Loading...</t>');
+			$('.error-message').hide();
+			clearValidation();
+			$.post("/template/NamlServlet.jtp", params,
+				function(data) {
+					window.preview.location = '/';
+				}
+			);
+		};
+		function showErrorMessage(group) {
+			$('#error-message-'+group).show();
+		};
+		function clearValidation() {
+			$('.error-field').removeClass('error-field');
+		};
+		function updateUI() {
+			checkboxEnable('#show-search-box', '#search-box-alignment');
+			checkboxEnable('#show-app-title', '#app-title-alignment');
+		};
+		function checkboxEnable(checkbox, control) {
+			var checked = $(checkbox).is(':checked');
+			var $control = $(control);
+			if (checked) $control.removeAttr('disabled');
+			else $control.attr('disabled', true);
+		};
+		function setEventHandlers() {
+			$('#show-search-box').change(updateUI);
+			$('#show-app-title').change(updateUI);
+		};
+		$(document).ready(function() {
+			layout();
+			$(window).resize(layout);
+			setEventHandlers();
+			setFieldValues();
+			updateUI();
+			var $groups = $('span.change-appearance-group');
+			$groups.hover(function(){
+					$(this).css('color', selectedFont);
+				}, function(){
+					$(this).css('color', 'inherit');
+				}
+			);
+			$groups.click(function(e){
+				e.stopPropagation();
+				var $panel = $(this).next();
+				var isVisible = $panel.css('display') != 'none';
+				hideOpenMenus();
+				if (!isVisible)
+					$panel.css('left', $(this).offset().left).slideDown('fast');
+			});
+			$(document).click(function(e) {
+				var $t = $(e.target);
+				if ($t.parents().hasClass('jscolor'))
+					return;
+				if ($t.parents().hasClass('change-appearance-group-panel'))
+					return;
+				hideOpenMenus();
+			});
+		});
+		<![CDATA[
+		function validateRange(id,from,to) {
+			var $f = $(id);
+			var v = $f.val();
+			if (!isInteger(v) || parseInt(v) < from || parseInt(v) > to) {
+				$f.addClass('error-field').focus();
+				throw new Error();
+			}
+		};
+		function validateMixedCsv(id,from,to) {
+			var $f = $(id);
+			var v = $f.val();
+			var parts = v.split(',');
+			for (var i=0; i<parts.length; i++) {
+				var v = parts[i];
+				if (!isInteger(v) || parseInt(v) < from || parseInt(v) > to) {
+					$f.addClass('error-field').focus();
+					throw new Error();
+				}
+			}
+		};
+		function isInteger(s) {
+		  return (s.toString().search(/^-?[0-9]+$/) == 0);
+		}
+		var schemes = [];
+		function addColorScheme(name, values) {
+			var index = schemes.length;
+			schemes.push({
+				name: name,
+				values: values
+			});
+			var h = '<div id="scheme'+index+'" class="color-scheme-row">';
+			h += '<b>'+name+'</b>';
+			for (var i=values.length-1;i>=0;i--) {
+				h += '<div class="color-scheme" style="background:#'+values[i]+'">&nbsp;</div>';
+			}
+			h += '</div>';
+			$('#color-schemes-panel').append(h);
+			var $scheme = $(Nabble.get('scheme'+index));
+			$scheme.click(function() {
+				loadColorScheme(name);
+			});
+			$scheme.hover(function() {
+				$(this).css('background-color', '#777');
+			}, function() {
+				$(this).css('background-color', 'transparent');
+			});
+		}
+		function loadColorScheme(name) {
+			for (var i=0;i<schemes.length;i++) {
+				if (schemes[i].name == name) {
+					var values = schemes[i].values;
+					Nabble.get('bg-color').color.fromString(values[0]);
+					Nabble.get('light-bg-color').color.fromString(values[1]);
+					Nabble.get('shaded-bg-color').color.fromString(values[2]);
+					Nabble.get('dark-bg-color').color.fromString(values[3]);
+					Nabble.get('highlight-bg-color').color.fromString(values[4]);
+					Nabble.get('text-color').color.fromString(values[5]);
+					Nabble.get('text-weak-color').color.fromString(values[6]);
+					Nabble.get('title-color').color.fromString(values[7]);
+					Nabble.get('link-color').color.fromString(values[8]);
+					Nabble.get('link-visited-color').color.fromString(values[9]);
+					Nabble.get('input-text-color').color.fromString(values[10]);
+					Nabble.get('input-bg-color').color.fromString(values[11]);
+					Nabble.get('light-border-color').color.fromString(values[12]);
+					Nabble.get('medium-border-color').color.fromString(values[13]);
+					Nabble.get('dark-border-color').color.fromString(values[14]);
+					Nabble.get('info-bg-color').color.fromString(values[15]);
+					Nabble.get('info-text-color').color.fromString(values[16]);
+					Nabble.get('error-bg-color').color.fromString(values[17]);
+					Nabble.get('error-text-color').color.fromString(values[18]);
+					return;
+				}
+			}
+		};
+		]]>
+		$(document).ready(function() {
+			addColorScheme("<t>Default</t>", ["FFFFFF", "F2F2F2", "EEEEEE", "DDDDDD", "FFFF99", "000000", "666666", "333333", "0000EE", "551A8B", "000000", "FFFFFF", "EEEEEE", "CCCCCC", "666666", "FFFFCC", "000000", "FFFFCC", "000000"]);
+			addColorScheme("Desert 1", ["faf9f5", "f6f3ed", "efebe0", "e7dcc1", "ffff66", "454d4b", "807379", "4057ae", "4057ae", "4089AE", "000000", "FFFFFF", "EBE7DA", "E0DBCA", "D4CFBC", "FFFFCC", "000000", "FFFFCC", "000000"]);
+			addColorScheme("Desert 2", ["F2F1ED", "E7E4DE", "D9D5C8", "D9CCAC", "FFFF66", "323836", "665C61", "465FBC", "014885", "2F6480", "000000", "FFFFFF", "C9C6B8", "B8B2A1", "A19B89", "FAFAC6", "000000", "FAFAC6", "000000"]);
+			addColorScheme("Darkness", ["000000", "191919", "333333", "666666", "545500", "FFFFFF", "CCCCCC", "EEEEEE", "FFCC66", "FFFFCC", "FFFFFF", "332D25", "333333", "666666", "CCCCCC", "631900", "FFFFFF", "631900", "FFFFFF"]);
+			addColorScheme("Moonlight", ["0F1528", "151C33", "1A2237", "21366E", "001C72", "9DAAD9", "CCCCCC", "EEEEEE", "75ADF5", "C4C3DF", "FFFFFF", "1E354D", "263A52", "3A536B", "4E5D7D", "1A2237", "FFFFFF", "4A0D0D", "FFFFFF"]);
+			addColorScheme("Moonlight 2", ["1E2E40", "24374D", "2E4863", "365373", "7A6F33", "E3F0FF", "83ADDE", "B8B85C", "AEE0F2", "8AD1C1", "000000", "334A5E", "2D445E", "3C5A7D", "466A94", "59591D", "000000", "4A2125", "000000"]);
+			addColorScheme("Jungle", ["2D3D1E", "3C4A2F", "000000", "OE24OC", "787D2F", "D6E6D4", "B1B888", "C9C42E", "64C447", "8EB85D", "000000", "0B3811", "0C1F09", "19360D", "154511", "544E14", "FFFFFF", "4D2020", "FFFFFF"]);
+			addColorScheme("Princess", ["EDDCFF", "DACAE8", "FFEDF2", "DEC1E3", "C1B8FF", "000000", "26233B", "363BD1", "5A236B", "4E3170", "000000", "F0D3F0", "F3E0F6", "BBA3C4", "947696", "ABB1EB", "000000", "B2DFDF", "000000"]);
+			addColorScheme("Autumn", ["FFFDF3", "FAF3DC", "F5ECD3", "E8DDC2", "FFD996", "0D0D0D", "998262", "71180C", "8B2113", "85610F", "000000", "FFFFFF", "F7E5AE", "EDDBA7", "DBCB9B", "F1E27D", "000000", "963330", "FFFFFF"]);
+			addColorScheme("Dark Gray", ["3D3D3D", "333333", "191919", "000000", "545500", "FFFFFF", "CCCCCC", "EEEEEE", "FFCC66", "FFFFCC", "EEEEEE", "222222", "333333", "666666", "CCCCCC", "631900", "FFFFFF", "631900", "FFFFFF"]);
+		});
+	</script>
+</macro>
+
+<macro name="change_appearance_body">
+	<div id="change-appearance">
+		<n.add_group name="Font" contents="[n.font_editor_panel/]" apply_call="applyFont()" restore_defaults_call="restoreFontDefaults()" width="25em"/>
+		<n.add_group name="Color" contents="[n.color_editor_panel/]" apply_call="applyColor()" restore_defaults_call="restoreColorDefaults()" width="25em"/>
+		<n.add_group name="Preferences" contents="[n.preferences_editor_panel/]" apply_call="applyPreferences()" restore_defaults_call="restorePreferencesDefaults()" width="30em"/>
+		<n.add_group name="CSS" contents="[n.css_editor_panel/]" apply_call="applyCss()" restore_defaults_call="restoreCssDefaults()" width="30em"/>
+		<button style="padding:0;margin-top:-.1em" onclick="closeFrame()"><t>Close</t></button>
+	</div>
+	<iframe id="preview-frame" name="preview" src="/" allowTransparency="false" border="0"/>
+</macro>
+
+<macro name="add_group" parameters="name, contents, apply_call, restore_defaults_call, width">
+	<span class="change-appearance-group rounded">
+		<n.name/>
+	</span>
+	<div class="change-appearance-group-panel drop-shadow" style="width:[n.width/]">
+		<a class="close-link" href="javascript:void(0)" onclick="hideOpenMenus()">&times;</a>
+		<n.contents/>
+		<div style="text-align:right;padding-top:.5em">
+			<span id="error-message-[n.to_lower_case.name/]" class="error-message">Please fix error(s) above.</span>
+			<button class="toolbar action-button" onclick="[n.restore_defaults_call/]">Restore Defaults</button>
+			<button class="toolbar action-button" onclick="[n.apply_call/]">
+				<img src="/images/success.png" style="vertical-align:middle"/>
+				Apply
+			</button>
+		</div>
+	</div>
+</macro>
+
+<macro name="font_editor_panel">
+	<table>
+		<tr>
+			<td class="label-column">Font Size:</td>
+			<td><input type="text" size="3" maxlength="3" id="font-size" name="font-size" />%</td>
+		</tr>
+		<tr>
+			<td></td>
+			<td><div class="small-description" style="margin-bottom:1em">Value between 70 and 140% (default is 84%)</div></td>
+		</tr>
+		<tr>
+			<td class="label-column">Main Font:</td>
+			<td>
+				<input type="text" size="20" maxlength="20" id="main-font" name="font" />
+				<button id="main-dropdown" class="toolbar" style="font-size:80%;vertical-align:middle"><b>Choose Font</b></button>
+				<n.font_selector_panel id="main-font-selector" input_id="main-font"/>
+				<n.custom_dropdown clickable_id="main-dropdown" panel_id="main-font-selector"/>
+			</td>
+		</tr>
+		<tr>
+			<td></td>
+			<td><div class="small-description" style="margin-bottom:1em">Leave blank for default font.</div></td>
+		</tr>
+		<tr>
+			<td class="label-column">Title Font:</td>
+			<td>
+				<input type="text" size="20" maxlength="20" id="title-font" name="font" />
+				<button id="title-dropdown" class="toolbar" style="font-size:80%;vertical-align:middle"><b>Choose Font</b></button>
+				<n.font_selector_panel id="title-font-selector" input_id="title-font"/>
+				<n.custom_dropdown clickable_id="title-dropdown" panel_id="title-font-selector"/>
+			</td>
+		</tr>
+		<tr>
+			<td></td>
+			<td><div class="small-description">Leave blank for default font.</div></td>
+		</tr>
+	</table>
+</macro>
+
+<macro name="font_selector_panel" parameters="id, input_id">
+	<div id="[n.id/]" class="dropdown-box">
+		<div class="sub-section-title">Basic Fonts</div>
+		<div style="padding:0 .5em;line-height:1.8em">
+			<n.basic_font_option input_id="[n.input_id/]" font="Sans-serif"/>
+			<n.basic_font_option input_id="[n.input_id/]" font="Tahoma"/>
+			<n.basic_font_option input_id="[n.input_id/]" font="Arial"/>
+			<n.basic_font_option input_id="[n.input_id/]" font="Times"/>
+			<n.basic_font_option input_id="[n.input_id/]" font="Courier New"/>
+			<n.basic_font_option input_id="[n.input_id/]" font="Garamond"/>
+			<n.basic_font_option input_id="[n.input_id/]" font="Georgia"/>
+			<n.basic_font_option input_id="[n.input_id/]" font="Trebuchet MS"/>
+			<n.basic_font_option input_id="[n.input_id/]" font="Verdana"/>
+		</div>
+
+		<div class="sub-section-title">
+			Google Web Fonts
+		</div>
+		<div style="padding:0 .5em;line-height:1.8em">
+			<n.google_font_option input_id="[n.input_id/]" font="Short Stack"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Open Sans"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Alice"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Questrial"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Rokkitt"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Varela Round"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Days One"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Copse"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Quattrocento"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Comfortaa"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Play"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Delius Swash Caps"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Lobster"/>
+			<n.google_font_option input_id="[n.input_id/]" font="Kelly Slab"/>
+		</div>
+		<div class="small-description">
+			You can use any Google Web Font available (<a href="http://www.google.com/webfonts" target="_blank" rel="nofollow">view fonts</a>).
+			Just type the name of the font with the prefix "GWF=" (for example, try "GWF=Marvel").
+		</div>
+	</div>
+</macro>
+
+<macro name="preferences_editor_panel">
+	<table>
+		<tr>
+			<td><input type="checkbox" id="show-search-box"/></td>
+			<td class="label-column"><label for="show-search-box">Show search box</label></td>
+			<td>
+				<select id="search-box-alignment">
+					<option value="left">Left</option>
+					<option value="center">Center</option>
+					<option value="right">Right</option>
+				</select>
+			</td>
+			<td colspan="2"></td>
+		</tr>
+		<tr>
+			<td><input type="checkbox" id="show-app-title"/></td>
+			<td class="label-column"><label for="show-app-title">Show app title</label></td>
+			<td>
+				<select id="app-title-alignment">
+					<option value="left">Left</option>
+					<option value="center">Center</option>
+					<option value="right">Right</option>
+				</select>
+			</td>
+			<td colspan="2"></td>
+		</tr>
+		<tr>
+			<td></td>
+			<td class="label-column">Description alignment</td>
+			<td>
+				<select id="app-description-alignment">
+					<option value="left">Left</option>
+					<option value="center">Center</option>
+					<option value="right">Right</option>
+				</select>
+			</td>
+			<td colspan="2"></td>
+		</tr>
+		<tr>
+			<td></td>
+			<td class="label-column">Page layout</td>
+			<td>
+				<select id="page-layout">
+					<option value="wide">Wide</option>
+					<option value="narrow">Narrow</option>
+				</select>
+			</td>
+			<td colspan="2"></td>
+		</tr>
+		<tr>
+			<td colspan="5">
+				<hr color="#555" style="margin:0 0 .3em"/>
+			</td>
+		</tr>
+		<tr>
+			<td colspan="5" class="section-title">
+				Topics per page
+			</td>
+		</tr>
+		<tr>
+			<td></td>
+			<td class="label-column">Forum</td>
+			<td><input type="text" id="forum-topics-per-page" size="3"/></td>
+			<td class="label-column">Blog</td>
+			<td><input type="text" id="blog-topics-per-page" size="3"/></td>
+		</tr>
+		<tr>
+			<td></td>
+			<td class="label-column">Gallery</td>
+			<td><input type="text" id="gallery-topics-per-page" size="3"/></td>
+			<td class="label-column">Newspaper</td>
+			<td><input type="text" id="news-topics-per-page" size="3"/></td>
+		</tr>
+		<tr>
+			<td></td>
+			<td class="label-column">Mixed</td>
+			<td colspan="3">
+				<input type="text" id="mixed-topics-per-page" size="10"/>
+				<a href="[n.help.mixed_lengths.url/]" target="_blank">help article</a>
+			</td>
+		</tr>
+		<tr>
+			<td colspan="5">
+				<hr color="#555" style="margin:0 0 .3em"/>
+			</td>
+		</tr>
+		<tr>
+			<td colspan="5" class="section-title">
+				Classic topic page
+			</td>
+		</tr>
+		<tr>
+			<td></td>
+			<td class="label-column">Picture size</td>
+			<td colspan="3">
+				<select id="picture-size-classic">
+					<option value="big">Big</option>
+					<option value="small">Small</option>
+				</select>
+			</td>
+		</tr>
+		<tr>
+			<td></td>
+			<td class="label-column">Posts per page</td>
+			<td colspan="3">
+				<input type="text" id="classic-posts-per-page" size="3"/>
+			</td>
+		</tr>
+		<tr>
+			<td colspan="5">
+				<hr color="#555" style="margin:0 0 .3em"/>
+			</td>
+		</tr>
+		<tr>
+			<td colspan="5" class="section-title">
+				Forum Format
+			</td>
+		</tr>
+		<tr>
+			<td style="vertical-align:top"><input type="radio" id="forum-format-topics" name="forum-format"/></td>
+			<td colspan="4">
+				<label for="forum-format-topics"><b>Topics</b></label>
+				<div class="small-description">
+					By selecting this format, your forum will show topics from all sub-forums in a flat list.
+					This means you can browse topics without having to visit sub-forums.
+					This format is only useful when you have sub-forums.
+				</div>
+			</td>
+		</tr>
+		<tr>
+			<td style="vertical-align:top"><input type="radio" id="forum-format-standard" name="forum-format"/></td>
+			<td colspan="4">
+				<label for="forum-format-standard"><b>Standard</b></label>
+				<div class="small-description">
+					This format shows the list of topics on your forum front page.
+					If you create a child forum, it will appear as a folder on top of the topics.
+					Your forum may have multi-level sub-forums, but you only see topics and forums that are 1-level below your current forum.
+				</div>
+			</td>
+		</tr>
+	</table>
+</macro>
+
+<macro name="color_editor_panel">
+	<div style="margin-bottom:.4em">
+		<button id="color-schemes-button" class="toolbar" style="font-size:80%;vertical-align:middle">
+			<img src="/images/colors.png" width="12" height="12" style="vertical-align:middle"/>
+			<b>Color Schemes</b>
+		</button>
+		<div id="color-schemes-panel" class="dropdown-box"></div>
+		<n.custom_dropdown clickable_id="color-schemes-button" panel_id="color-schemes-panel"/>
+	</div>
+
+	<table>
+		<tr>
+			<td class="label-column">Background</td>
+			<td><input class="color" id="bg-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Light Background</td>
+			<td><input class="color" id="light-bg-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Shaded Background</td>
+			<td><input class="color" id="shaded-bg-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Dark Background</td>
+			<td><input class="color" id="dark-bg-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Highlight Background</td>
+			<td><input class="color" id="highlight-bg-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td colspan="2">
+				<hr color="#555" style="margin:0 0 .3em"/>
+			</td>
+		</tr>
+		<tr>
+			<td class="label-column">Text</td>
+			<td><input class="color" id="text-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Text (Weak Color)</td>
+			<td><input class="color" id="text-weak-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Title</td>
+			<td><input class="color" id="title-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Link</td>
+			<td><input class="color" id="link-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Link Visited</td>
+			<td><input class="color" id="link-visited-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td colspan="2">
+				<hr color="#555" style="margin:0 0 .3em"/>
+			</td>
+		</tr>
+		<tr>
+			<td class="label-column">Input Text</td>
+			<td><input class="color" id="input-text-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Input Background</td>
+			<td><input class="color" id="input-bg-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td colspan="2">
+				<hr color="#555" style="margin:0 0 .3em"/>
+			</td>
+		</tr>
+		<tr>
+			<td class="label-column">Light Border</td>
+			<td><input class="color" id="light-border-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Medium Border</td>
+			<td><input class="color" id="medium-border-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Dark Border</td>
+			<td><input class="color" id="dark-border-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td colspan="2">
+				<hr color="#555" style="margin:0 0 .3em"/>
+			</td>
+		</tr>
+		<tr>
+			<td class="label-column">Info Message Background</td>
+			<td><input class="color" id="info-bg-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Info Message Text</td>
+			<td><input class="color" id="info-text-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Error Message Background</td>
+			<td><input class="color" id="error-bg-color" size="6"/></td>
+		</tr>
+		<tr>
+			<td class="label-column">Error Message Text</td>
+			<td><input class="color" id="error-text-color" size="6"/></td>
+		</tr>
+	</table>
+</macro>
+
+<macro name="css_editor_panel">
+	<div style="margin:.2em .2em .4em;font-weight:bold">
+		Enter your custom Cascading Stylesheet (CSS) code:
+	</div>
+	<textarea id="custom-css" style="width:98%;height:20em"></textarea>
+</macro>
+
+<macro name="basic_font_option" parameters="input_id,font">
+	<span
+		class="rounded option-button"
+		onclick="$('#[n.input_id/]').val('[n.font/]')"
+		style="font-family:'[n.font/]'"
+	><n.font/></span>
+</macro>
+
+<macro name="google_font_option" parameters="input_id,font">
+	<n.put_in_head.>
+		<link href="http://fonts.googleapis.com/css?family=[n.encode_url.encode_text.font/]" rel="stylesheet" type="text/css"/>
+	</n.put_in_head.>
+	<span
+		class="rounded option-button"
+		onclick="$('#[n.input_id/]').val('GWF=[n.font/]')"
+		style="font-family:'[n.font/]'"
+	><n.font/></span>
+</macro>
+
+<macro name="save_font_config" requires="servlet">
+	<n.if.visitor.can_edit.root_node>
+		<then>
+			<n.naml_configuration.>
+				<n.set>
+					<name>fontSize</name>
+					<value><n.font_size_value/></value>
+					<default>84</default>
+					<naml>
+						<n.tweak_for_stylesheets.>
+							body, input, button, textarea, select {
+								font-size: <n.font_size_value/>%;
+							}
+						</n.tweak_for_stylesheets.>
+					</naml>
+				</n.set>
+				<n.set>
+					<name>titleFontFamily</name>
+					<value><n.title_font_value/></value>
+					<default></default>
+					<naml>
+						<n.tweak_for_font_family. selector=".second-font, h1, h2, h3, h4, h5, h6">
+							<n.title_font_value/>
+						</n.tweak_for_font_family.>
+					</naml>
+				</n.set>
+				<n.set>
+					<name>mainFontFamily</name>
+					<value><n.main_font_value/></value>
+					<default></default>
+					<naml>
+						<n.tweak_for_font_family. selector="body, input, button, textarea, select">
+							<n.main_font_value/>
+						</n.tweak_for_font_family.>
+					</naml>
+				</n.set>
+				<n.apply/>
+			</n.naml_configuration.>
+		</then>
+	</n.if.visitor.can_edit.root_node>
+</macro>
+
+<macro name="save_color_config" requires="servlet">
+	<n.if.visitor.can_edit.root_node>
+		<then>
+			<n.naml_configuration.>
+				<n.set_color_tweak name="bgColor" value="[n.bg_color_value/]" default="FFFFFF" selector="html,#nabble,.nabble .no-bg-color" property="background-color" macro="bg_color"/>
+				<n.set_color_tweak name="lightBgColor" value="[n.light_bg_color_value/]" default="F2F2F2" selector=".nabble .light-bg-color" property="background-color" macro="light_bg_color"/>
+				<n.set_color_tweak name="shadedBgColor" value="[n.shaded_bg_color_value/]" default="EEEEEE" selector=".nabble .shaded-bg-color" property="background-color" macro="shaded_bg_color"/>
+				<n.set_color_tweak name="darkBgColor" value="[n.dark_bg_color_value/]" default="DDDDDD" selector=".nabble .dark-bg-color" property="background-color" macro="dark_bg_color"/>
+				<n.set_color_tweak name="highlightBgColor" value="[n.highlight_bg_color_value/]" default="FFFF99" selector=".nabble .highlight" property="background-color" macro="highlight_bg_color"/>
+				<n.set_color_tweak name="textColor" value="[n.text_color_value/]" default="000000" selector=".nabble *" property="color" macro="text_color"/>
+				<n.set_color_tweak name="textWeakColor" value="[n.text_weak_color_value/]" default="666666" selector=".nabble .weak-color" property="color" macro="text_weak_color"/>
+				<n.set_color_tweak name="titleColor" value="[n.title_color_value/]" default="333333" selector=".nabble h1, .nabble h2, .nabble h3, .nabble h4, .nabble h5, .nabble h6" property="color" macro="title_color"/>
+				<n.set_color_tweak name="linkColor" value="[n.link_color_value/]" default="0000EE" selector=".nabble a:link, .nabble a.not-visited-link" property="color" macro="link_color"/>
+				<n.set_color_tweak name="linkVisitedColor" value="[n.link_visited_color_value/]" default="551A8B" selector=".nabble a:visited, .nabble a.visited-link" property="color" macro="link_visited_color"/>
+				<n.set_color_tweak name="inputTextColor" value="[n.input_text_color_value/]" default="000000" selector=".nabble select, .nabble input, .nabble textarea" property="color" macro="input_text_color"/>
+				<n.set_color_tweak name="inputBgColor" value="[n.input_bg_color_value/]" default="FFFFFF" selector=".nabble select, .nabble input, .nabble textarea" property="background-color" macro="input_bg_color"/>
+				<n.set_color_tweak name="lightBorderColor" value="[n.light_border_color_value/]" default="EEEEEE" selector=".nabble .light-border-color" property="border-color" macro="light_border_color"/>
+				<n.set_color_tweak name="mediumBorderColor" value="[n.medium_border_color_value/]" default="CCCCCC" selector=".nabble .medium-border-color" property="border-color" macro="medium_border_color"/>
+				<n.set_color_tweak name="darkBorderColor" value="[n.dark_border_color_value/]" default="666666" selector=".nabble .dark-border-color" property="border-color" macro="dark_border_color"/>
+				<n.set_color_tweak name="infoBgColor" value="[n.info_bg_color_value/]" default="FFFFCC" selector=".nabble .info-message, .nabble .info-message *" property="background-color" macro="info_bg_color"/>
+				<n.set_color_tweak name="infoTextColor" value="[n.info_text_color_value/]" default="000000" selector=".nabble .info-message, .nabble .info-message *" property="color" macro="info_text_color"/>
+				<n.set_color_tweak name="errorBgColor" value="[n.error_bg_color_value/]" default="FFFFCC" selector=".nabble .error-message, .nabble .error-message *" property="background-color" macro="error_bg_color"/>
+				<n.set_color_tweak name="errorTextColor" value="[n.error_text_color_value/]" default="000000" selector=".nabble .error-message, .nabble .error-message *" property="color" macro="error_text_color"/>
+				<n.apply/>
+			</n.naml_configuration.>
+		</then>
+	</n.if.visitor.can_edit.root_node>
+</macro>
+
+<macro name="set_color_tweak" parameters="name, value, default, selector, property, macro" requires="naml_configuration">
+	<n.set>
+		<name><n.name/></name>
+		<value><n.value/></value>
+		<default><n.default/></default>
+		<naml>
+			<![CDATA[
+			<override_macro name="]]><n.macro/><![CDATA[">
+				]]><n.value/><![CDATA[
+			</override_macro>
+			<override_macro name="site_style" unindent="true">
+				<n.overridden/>]]>
+				<n.selector/> { <n.property/>: #<n.value/>; }
+				<![CDATA[
+			</override_macro>
+			]]>
+		</naml>
+	</n.set>
+</macro>
+
+<macro name="save_preferences_config" requires="servlet">
+	<n.if.visitor.can_edit.root_node>
+		<then>
+			<n.naml_configuration.>
+				<n.set>
+					<name>showSearchBox</name>
+					<value><n.show_search_box_value/></value>
+					<default>true</default>
+					<naml>
+						<n.tweak_for_stylesheets.>
+							#search-box { display: none; }
+						</n.tweak_for_stylesheets.>
+					</naml>
+				</n.set>
+				<n.set>
+					<name>searchBoxAlignment</name>
+					<value><n.search_box_alignment_value/></value>
+					<default>right</default>
+					<naml>
+						<n.tweak_for_stylesheets.>
+							#search-box { text-align: <n.search_box_alignment_value/>; }
+						</n.tweak_for_stylesheets.>
+					</naml>
+				</n.set>
+				<n.set>
+					<name>showAppTitle</name>
+					<value><n.show_app_title_value/></value>
+					<default>true</default>
+					<naml>
+						<n.tweak_for_stylesheets.>
+							#forum-title { display: none; }
+						</n.tweak_for_stylesheets.>
+					</naml>
+				</n.set>
+				<n.set>
+					<name>appTitleAlignment</name>
+					<value><n.app_title_alignment_value/></value>
+					<default>center</default>
+					<naml>
+						<n.tweak_for_stylesheets.>
+							#forum-title {
+								float: <n.app_title_alignment_value/>;
+							}
+						</n.tweak_for_stylesheets.>
+					</naml>
+				</n.set>
+				<n.set>
+					<name>appDescriptionAlignment</name>
+					<value><n.app_description_alignment_value/></value>
+					<default>center</default>
+					<naml>
+						<n.tweak_for_stylesheets.>
+							#description-box {
+								text-align: <n.app_description_alignment_value/>;
+							}
+						</n.tweak_for_stylesheets.>
+					</naml>
+				</n.set>
+				<n.set>
+					<name>pageLayout</name>
+					<value><n.page_layout_value/></value>
+					<default>wide</default>
+					<naml>
+						<![CDATA[
+							<override_macro name="apply_app_namespace" dot_parameter="do">
+								<n.narrow_app_namespace.do />
+							</override_macro>
+
+							<override_macro name="apply_workgroup_app_namespace" dot_parameter="do">
+								<n.workgroup_narrow_app_namespace.do />
+							</override_macro>
+						]]>
+					</naml>
+				</n.set>
+				<n.set>
+					<name>pictureSizeClassic</name>
+					<value><n.picture_size_classic_value/></value>
+					<default>big</default>
+					<naml>
+						<![CDATA[
+						<override_macro name="has_small_avatar">
+							true
+						</override_macro>
+						]]>
+					</naml>
+				</n.set>
+				<n.set>
+					<name>classicPostsPerPage</name>
+					<value><n.classic_posts_per_page_value/></value>
+					<default>20</default>
+					<naml>
+						<![CDATA[
+						<override_macro name="classic_rows_per_page">
+							]]><n.classic_posts_per_page_value/><![CDATA[
+						</override_macro>
+						]]>
+					</naml>
+				</n.set>
+				<n.set>
+					<name>forumFormat</name>
+					<value><n.forum_format_value/></value>
+					<default>standard</default>
+					<naml>
+						<![CDATA[
+						<override_macro name="call_view_forum">
+							<n.call_view_topics/>
+						</override_macro>
+						]]>
+					</naml>
+				</n.set>
+				<n.set_tweak_for_simple_value
+					name="forumTopicsPerPage"
+					value="[n.forum_topics_per_page_value/]"
+					default="35"
+					macro="forum_topics_per_page"
+				/>
+				<n.set_tweak_for_simple_value
+					name="blogTopicsPerPage"
+					value="[n.blog_topics_per_page_value/]"
+					default="10"
+					macro="blog_topics_per_page"
+				/>
+				<n.set_tweak_for_simple_value
+					name="galleryTopicsPerPage"
+					value="[n.gallery_topics_per_page_value/]"
+					default="16"
+					macro="gallery_topics_per_page"
+				/>
+				<n.set_tweak_for_simple_value
+					name="newsTopicsPerPage"
+					value="[n.news_topics_per_page_value/]"
+					default="25"
+					macro="news_topics_per_page"
+				/>
+				<n.apply/>
+				<n.set_tweak_for_simple_value
+					name="mixedTopicsPerPage"
+					value="[n.mixed_topics_per_page_value/]"
+					default="6"
+					macro="mixed_topics_per_page"
+				/>
+				<n.apply/>
+			</n.naml_configuration.>
+		</then>
+	</n.if.visitor.can_edit.root_node>
+</macro>
+
+<macro name="save_css_config" requires="servlet">
+	<n.if.visitor.can_edit.root_node>
+		<then>
+			<n.naml_configuration.>
+				<n.set>
+					<name>customCss</name>
+					<value><n.custom_css_value/></value>
+					<default></default>
+					<naml>
+						<n.tweak_for_stylesheets.>
+							<n.custom_css_value/>
+						</n.tweak_for_stylesheets.>
+					</naml>
+				</n.set>
+				<n.apply/>
+			</n.naml_configuration.>
+		</then>
+	</n.if.visitor.can_edit.root_node>
+</macro>
+
+<macro name="set_tweak_for_simple_value" parameters="name, value, default, macro" requires="naml_configuration">
+	<n.set>
+		<name><n.name/></name>
+		<value><n.value/></value>
+		<default><n.default/></default>
+		<naml>
+			<![CDATA[
+			<override_macro name="]]><n.macro/><![CDATA[">
+				]]><n.value/><![CDATA[
+			</override_macro>
+			]]>
+		</naml>
+	</n.set>
+</macro>
+
+<macro name="tweak_for_stylesheets" dot_parameter="code">
+	<![CDATA[
+	<override_macro name="site_style">
+		<n.overridden/>
+		]]>
+			<n.compress.code/>
+		<![CDATA[
+	</override_macro>
+	]]>
+</macro>
+
+<macro name="tweak_for_font_family" dot_parameter="font" parameters="selector">
+	<![CDATA[
+	<override_macro name="nabble_stylesheets" unindent="true">
+		<n.overridden/>
+		]]>
+		<n.google_font_link.font/>
+		<style type="text/css">
+			<n.selector/> {
+				font-family: '<n.remove_gwf_prefix.font/>';
+			}
+		</style>
+		<![CDATA[
+	</override_macro>
+	]]>
+</macro>
+
+<macro name="google_font_link" dot_parameter="font">
+	<n.if.starts_with text="[n.font/]" prefix="GWF=">
+		<then>
+			<n.set_var. name="font_name">
+				<n.encode_url.encode_text.substring text="[n.font/]" begin="4"/>
+			</n.set_var.>
+			<![CDATA[
+			<link href='http://fonts.googleapis.com/css?family=]]><n.var name='font_name'/><![CDATA[' rel='stylesheet' type='text/css'/>
+			]]>
+		</then>
+	</n.if.starts_with>
+</macro>
+
+<macro name="remove_gwf_prefix" dot_parameter="font">
+	<n.if.starts_with text="[n.font/]" prefix="GWF=">
+		<then.substring text="[n.font/]" begin="4"/>
+		<else.font/>
+	</n.if.starts_with>
+</macro>
+
+<macro name="font_size_value">
+	<n.get_parameter name="fontSize"/>
+</macro>
+
+<macro name="main_font_value">
+	<n.get_parameter name="mainFontFamily"/>
+</macro>
+
+<macro name="title_font_value">
+	<n.get_parameter name="titleFontFamily"/>
+</macro>
+
+<macro name="show_search_box_value">
+	<n.get_parameter name="showSearchBox"/>
+</macro>
+
+<macro name="search_box_alignment_value">
+	<n.get_parameter name="searchBoxAlignment"/>
+</macro>
+
+<macro name="show_app_title_value">
+	<n.get_parameter name="showAppTitle"/>
+</macro>
+
+<macro name="app_title_alignment_value">
+	<n.get_parameter name="appTitleAlignment"/>
+</macro>
+
+<macro name="app_description_alignment_value">
+	<n.get_parameter name="appDescriptionAlignment"/>
+</macro>
+
+<macro name="picture_size_classic_value">
+	<n.get_parameter name="pictureSizeClassic"/>
+</macro>
+
+<macro name="classic_posts_per_page_value">
+	<n.get_parameter name="classicPostsPerPage"/>
+</macro>
+
+<macro name="forum_format_value">
+	<n.get_parameter name="forumFormat"/>
+</macro>
+
+<macro name="forum_topics_per_page_value">
+	<n.get_parameter name="forumTopicsPerPage"/>
+</macro>
+
+<macro name="blog_topics_per_page_value">
+	<n.get_parameter name="blogTopicsPerPage"/>
+</macro>
+
+<macro name="gallery_topics_per_page_value">
+	<n.get_parameter name="galleryTopicsPerPage"/>
+</macro>
+
+<macro name="news_topics_per_page_value">
+	<n.get_parameter name="newsTopicsPerPage"/>
+</macro>
+
+<macro name="mixed_topics_per_page_value">
+	<n.get_parameter name="mixedTopicsPerPage"/>
+</macro>
+
+<macro name="custom_css_value">
+	<n.get_parameter name="customCss"/>
+</macro>
+
+<macro name="page_layout_value">
+	<n.get_parameter name="pageLayout"/>
+</macro>
+
+<macro name="bg_color_value">
+	<n.get_parameter name="bgColor"/>
+</macro>
+
+<macro name="light_bg_color_value">
+	<n.get_parameter name="lightBgColor"/>
+</macro>
+
+<macro name="shaded_bg_color_value">
+	<n.get_parameter name="shadedBgColor"/>
+</macro>
+
+<macro name="dark_bg_color_value">
+	<n.get_parameter name="darkBgColor"/>
+</macro>
+
+<macro name="highlight_bg_color_value">
+	<n.get_parameter name="highlightBgColor"/>
+</macro>
+
+<macro name="text_color_value">
+	<n.get_parameter name="textColor"/>
+</macro>
+
+<macro name="text_weak_color_value">
+	<n.get_parameter name="textWeakColor"/>
+</macro>
+
+<macro name="title_color_value">
+	<n.get_parameter name="titleColor"/>
+</macro>
+
+<macro name="link_color_value">
+	<n.get_parameter name="linkColor"/>
+</macro>
+
+<macro name="link_visited_color_value">
+	<n.get_parameter name="linkVisitedColor"/>
+</macro>
+
+<macro name="input_text_color_value">
+	<n.get_parameter name="inputTextColor"/>
+</macro>
+
+<macro name="input_bg_color_value">
+	<n.get_parameter name="inputBgColor"/>
+</macro>
+
+<macro name="light_border_color_value">
+	<n.get_parameter name="lightBorderColor"/>
+</macro>
+
+<macro name="medium_border_color_value">
+	<n.get_parameter name="mediumBorderColor"/>
+</macro>
+
+<macro name="dark_border_color_value">
+	<n.get_parameter name="darkBorderColor"/>
+</macro>
+
+<macro name="info_bg_color_value">
+	<n.get_parameter name="infoBgColor"/>
+</macro>
+
+<macro name="info_text_color_value">
+	<n.get_parameter name="infoTextColor"/>
+</macro>
+
+<macro name="error_bg_color_value">
+	<n.get_parameter name="errorBgColor"/>
+</macro>
+
+<macro name="error_text_color_value">
+	<n.get_parameter name="errorTextColor"/>
+</macro>
+
+<macro name="chance_appearance_configurations">
+    fontSize,
+    mainFontFamily,
+    titleFontFamily,
+	bgColor,
+	lightBgColor,
+	shadedBgColor,
+	darkBgColor,
+	highlightBgColor,
+	textColor,
+	textWeakColor,
+	titleColor,
+	linkColor,
+	linkVisitedColor,
+	inputTextColor,
+	inputBgColor,
+	lightBorderColor,
+	mediumBorderColor,
+	darkBorderColor,
+	infoBgColor,
+	infoTextColor,
+	errorBgColor,
+	errorTextColor,
+	showSearchBox,
+	searchBoxAlignment,
+	showAppTitle,
+	appTitleAlignment,
+	appDescriptionAlignment,
+	pageLayout,
+	forumTopicsPerPage,
+	blogTopicsPerPage,
+	newsTopicsPerPage,
+	galleryTopicsPerPage,
+	mixedTopicsPerPage,
+	classicPostsPerPage,
+	pictureSizeClassic,
+	forumFormat,
+	customCss
+</macro>
+
+Macros below define the default colors of Nabble and should be used by custom designs.
+Color configurations override these macros to change the colors of custom designs.
+
+<macro name="bg_color">FFFFFF</macro>
+<macro name="light_bg_color">F2F2F2</macro>
+<macro name="shaded_bg_color">EEEEEE</macro>
+<macro name="dark_bg_color">DDDDDD</macro>
+<macro name="highlight_bg_color">FFFF99</macro>
+<macro name="text_color">000000</macro>
+<macro name="text_weak_color">666666</macro>
+<macro name="title_color">333333</macro>
+<macro name="link_color">0000EE</macro>
+<macro name="link_visited_color">551A8B</macro>
+<macro name="input_text_color">000000</macro>
+<macro name="input_bg_color">FFFFFF</macro>
+<macro name="light_border_color">EEEEEE</macro>
+<macro name="medium_border_color">CCCCCC</macro>
+<macro name="dark_border_color">666666</macro>
+<macro name="info_bg_color">FFFFCC</macro>
+<macro name="info_text_color">000000</macro>
+<macro name="error_bg_color">FFFFCC</macro>
+<macro name="error_text_color">000000</macro>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/change_permissions.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,445 @@
+<macro name="change_permissions" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_change_permissions_of.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_change_permissions_of.page_node>
+
+		<n.if.is_submitted_form>
+			<then>
+				<n.save_all_permissions/>
+				<n.if.page_node.is_root>
+					<then.save_all_site_permissions />
+				</n.if.page_node.is_root>
+				<n.redirect_to.page_node.path/>
+			</then>
+		</n.if.is_submitted_form>
+
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Change Permissions</t></n.title.>
+				<n.permission_javascript/>
+				<n.permission_stylesheet/>
+			</head>
+			<body>
+				<n.edit_header first_text="[t]Change Permissions[/t]" second_text="[n.page_node.get_app_node.subject/]" />
+
+				<n.form.>
+					<n.permissions_table/>
+
+					<div style="margin-top:1.4em">
+						<input type="submit" value="[t]Save Changes[/t]" />
+						<t>or</t> <a href="[n.page_node.path/]"><t>Cancel</t></a>
+					</div>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="save_all_permissions">
+	<n.edit_page_node.>
+		<n.remove_permissions/>
+		<n.all_permissions.loop.>
+			<n.if.current_permission_row_field.equal value1="[n.value/]" value2="override">
+				<then>
+					<n.add_permission permission="[n.current_permission/]"/>
+					<n.user_groups.>
+						<n.add.anyone_group/>
+						<n.add.registered_group/>
+						<n.add.members_group/>
+						<n.add.administrators_group/>
+						<n.add.authors_group/>
+						<n.loop.permission_field. group="[n.current_group/]" permission="[n.current_permission/]">
+							<n.if.is_checked>
+								<then><n.add_permission group="[n.current_group/]" permission="[n.current_permission/]"/></then>
+							</n.if.is_checked>
+						</n.loop.permission_field.>
+					</n.user_groups.>
+				</then>
+			</n.if.current_permission_row_field.equal>
+		</n.all_permissions.loop.>
+	</n.edit_page_node.>
+</macro>
+
+<macro name="save_all_site_permissions">
+	<n.save_site_permissions.>
+		<n.remove_site_permissions/>
+		<n.all_site_permissions.loop.>
+			<n.if.current_permission_row_field.equal value1="[n.value/]" value2="override">
+				<then>
+					<n.add_site_permission permission="[n.current_permission/]"/>
+					<n.user_groups.>
+						<n.add.anyone_group/>
+						<n.add.registered_group/>
+						<n.add.members_group/>
+						<n.add.administrators_group/>
+						<n.loop.permission_field. group="[n.current_group/]" permission="[n.current_permission/]">
+							<n.if.is_checked>
+								<then><n.add_site_permission group="[n.current_group/]" permission="[n.current_permission/]"/></then>
+							</n.if.is_checked>
+						</n.loop.permission_field.>
+					</n.user_groups.>
+				</then>
+			</n.if.current_permission_row_field.equal>
+		</n.all_site_permissions.loop.>
+	</n.save_site_permissions.>
+</macro>
+
+<macro name="permission_stylesheet">
+	<style type="text/css">
+		input[type=radio] {
+			vertical-align:-15%;
+		}
+		table.permissions {
+			border-collapse: collapse;
+			margin-top:1em;
+		}
+		table.permissions th {
+			padding: .3em .6em;
+			border-bottom-style:solid;
+			border-bottom-width:2px;
+		}
+		table.permissions td {
+			padding: .3em .4em;
+			text-align:center;
+		}
+		table.permissions td.col1 {
+			text-align: left;
+		}
+	</style>
+</macro>
+
+<macro name="permission_javascript">
+	<n.zebra_table_javascript table_selector="table.permissions"/>
+	<script type="text/javascript">
+		var fields = [];
+		var defaults = [];
+		$(document).ready(function() {
+			function enableRow(s) {
+				var $select = $(s);
+				var $tr = $select.parent().parent();
+				var $checkboxes = $('input[type="checkbox"]',$tr);
+				if ($select.val() == 'inherit') {
+					$select.css('font-weight','normal').removeClass('highlight');
+					$checkboxes.attr('disabled','y').each(function(){
+						var index = fields.indexOf(this.name);
+						if (defaults[index])
+							$(this).attr('checked','y');
+						else
+							$(this).removeAttr('checked');
+					});
+				} else {
+					$select.css('font-weight','bold').addClass('highlight');
+					$checkboxes.removeAttr('disabled');
+				}
+			};
+			$('table.permissions select')
+				.change(function() { enableRow(this); })
+				.each(function() { enableRow(this); });
+		});
+	</script>
+</macro>
+
+<macro name="permissions_table">
+	<div style="overflow:auto">
+		<table class="permissions">
+			<tr class="shaded-bg-color">
+				<th class="medium-border-color"></th>
+				<th class="medium-border-color"></th>
+				<n.permission_group_heading.anyone_group/>
+				<n.permission_group_heading.registered_group/>
+				<n.permission_group_heading.administrators_group/>
+				<n.permission_group_heading.members_group/>
+				<n.permission_group_heading.authors_group/>
+				<n.permission_group_list.loop.>
+					<n.permission_group_heading.current_group/>
+				</n.permission_group_list.loop.>
+			</tr>
+			<n.permission_rows />
+		</table>
+	</div>
+</macro>
+
+<macro name="permission_rows">
+	<n.permission_row
+		permission = "[n.view_permission/]"
+		description="[t]Who can view this application and its contents[/t]"
+		administrators_cell = "true"
+		authors_cell = "true"
+	/>
+	<n.permission_row
+		permission = "[n.edit_app_permission/]"
+		description="[t]Who can edit applications (e.g., change name, description, etc.)[/t]"
+		authors_cell = "true"
+	/>
+	<n.permission_row
+		permission = "[n.create_topic_permission/]"
+		description="[t]Who can create new topics under this application[/t]"
+	/>
+	<n.permission_row
+		permission = "[n.reply_permission/]"
+		description="[t]Who can reply to messages posted under this application[/t]"
+	/>
+	<n.permission_row
+		permission = "[n.move_permission/]"
+		description="[t]Who can move messages under other destinations (e.g., under other topics or sub-forums)[/t]"
+	/>
+	<n.permission_row
+		permission = "[n.create_sub_apps_permission/]"
+		description="[t]Who can create sub applications (e.g., sub-forums, subcategories, etc.)[/t]"
+	/>
+	<n.permission_row
+		permission = "[n.change_post_date_permission/]"
+		description="[t]Who can change the date and time of messages[/t]"
+	/>
+	<n.permission_row
+		permission = "[n.manage_subscribers_permission/]"
+		description="[t]Who can manage subscribers of this application[/t]"
+	/>
+	<n.permission_row
+		permission = "[n.manage_pinned_topics_permission/]"
+		description="[t]Who can pin/unpin topics in this application[/t]"
+	/>
+	<n.permission_row
+		permission = "[n.manage_locked_topics_permission/]"
+		description="[t]Who can lock/unlock topics in this application[/t]"
+	/>
+	<n.if.page_node.is_root>
+		<then>
+			<n.site_permission_row
+				permission = "[n.manage_banned_users_permission/]"
+				description="[t]Who can ban/unban users[/t]"
+			/>
+		</then>
+	</n.if.page_node.is_root>
+	<n.permission_row
+		permission = "[n.show_group_members_permission/]"
+		description="[t]Which groups allow members to be listed[/t]"
+		anyone_cell = ""
+		authors_cell = ""
+	/>
+	<n.permission_row
+		permission = "[n.unrestricted_posting_permission/]"
+		description="[t]Who can post any content without restriction (including javascript code, &lt;object&gt; and &lt;style&gt; tags, etc.). [b]Security Warning[/b]: Allow this option only for users that you really trust.[/t]"
+		anyone_cell = ""
+		authors_cell = ""
+	/>
+	<n.permission_row
+		permission = "[n.edit_all_permission/]"
+		description="[t]Who can edit any content, both applications and posts.  Note: Please only use this feature in extreme circumstances.  Most users will not like having their posts edited by someone else.[/t]"
+		authors_cell = ""
+		anyone_cell = ""
+		registered_cell = ""
+		members_cell = ""
+		other_groups_cell = ""
+	/>
+</macro>
+
+<macro name="permission_row" parameters="permission,description,anyone_cell,registered_cell,administrators_cell,members_cell,authors_cell,other_groups_cell">
+	<tr>
+		<td class="col1">
+			<strong><n.permission/></strong>
+			<div class="weak-color"><n.description/></div>
+		</td>
+		<td>
+			<n.permission_row_field. permission="[n.permission/]">
+				<n.select.>
+					<n.select_option value="inherit" selectedValue="[n.permission_row_selected_option.permission/]" text="[n.permission_inherit_label/]"/>
+					<n.select_option value="override" selectedValue="[n.permission_row_selected_option.permission/]" text="[t]Modified[/t]"/>
+				</n.select.>
+			</n.permission_row_field.>
+		</td>
+		<n.permission_td cell="[n.anyone_cell/]" group="[n.anyone_group/]" permission="[n.permission/]" />
+		<n.permission_td cell="[n.registered_cell/]" group="[n.registered_group/]" permission="[n.permission/]" />
+		<n.permission_td cell="[n.administrators_cell/]" group="[n.administrators_group/]" permission="[n.permission/]" />
+		<n.permission_td cell="[n.members_cell/]" group="[n.members_group/]" permission="[n.permission/]" />
+		<n.permission_td cell="[n.authors_cell/]" group="[n.authors_group/]" permission="[n.permission/]" />
+		<n.permission_group_list.loop.>
+			<n.permission_td cell="[n.other_groups_cell/]" group="[n.current_group/]" permission="[n.permission/]" />
+		</n.permission_group_list.loop.>
+	</tr>
+</macro>
+
+<macro name="site_permission_row" parameters="permission,description,anyone_cell,administrators_cell">
+	<tr>
+		<td class="col1">
+			<strong><n.permission/></strong>
+			<div class="weak-color"><n.description/></div>
+		</td>
+		<td>
+			<n.permission_row_field. permission="[n.permission/]">
+				<n.select.>
+					<n.select_option value="inherit" selectedValue="[n.site_permission_row_selected_option.permission/]" text="[n.permission_inherit_label/]"/>
+					<n.select_option value="override" selectedValue="[n.site_permission_row_selected_option.permission/]" text="[t]Modified[/t]"/>
+				</n.select.>
+			</n.permission_row_field.>
+		</td>
+		<n.site_permission_td cell="[n.anyone_cell/]" group="[n.anyone_group/]" permission="[n.permission/]" />
+		<n.site_permission_td group="[n.registered_group/]" permission="[n.permission/]" />
+		<n.site_permission_td cell="[n.administrators_cell/]" group="[n.administrators_group/]" permission="[n.permission/]" />
+		<n.site_permission_td group="[n.members_group/]" permission="[n.permission/]" />
+		<td></td>
+		<n.permission_group_list.loop.>
+			<n.site_permission_td group="[n.current_group/]" permission="[n.permission/]" />
+		</n.permission_group_list.loop.>
+	</tr>
+</macro>
+
+<macro name="permission_row_selected_option" dot_parameter="permission">
+	<n.if.page_node.node_has_permission permission="[n.permission/]">
+		<then>override</then>
+		<else>inherit</else>
+	</n.if.page_node.node_has_permission>
+</macro>
+
+<macro name="site_permission_row_selected_option" dot_parameter="permission">
+	<n.if.site_has_site_permission permission="[n.permission/]">
+		<then>override</then>
+		<else>inherit</else>
+	</n.if.site_has_site_permission>
+</macro>
+
+<macro name="permission_inherit_label">
+	<n.if.page_node.is_root>
+		<then><t>Default</t></then>
+		<else><t>Inherit</t></else>
+	</n.if.page_node.is_root>
+</macro>
+
+<macro name="permission_td" parameters="cell,group,permission" requires="node_page">
+	<n.if.not.is_null.cell>
+		<then>
+			<td><n.cell/></td>
+		</then>
+		<else>
+			<td><n.permission_field. group="[n.group/]" permission="[n.permission/]" >
+				<n.set_value.page_node.has_permission group="[n.group/]" permission="[n.permission/]" />
+				<n.checkbox/>
+				<n.backup_default_value field_name="[n.name/]">
+					<default_value>
+						<n.permission_inherited_value group="[n.group/]" permission="[n.permission/]" />
+					</default_value>
+				</n.backup_default_value>
+			</n.permission_field.></td>
+		</else>
+	</n.if.not.is_null.cell>
+</macro>
+
+<macro name="site_permission_td" parameters="cell,group,permission">
+	<n.if.not.is_null.cell>
+		<then>
+			<td><n.cell/></td>
+		</then>
+		<else>
+			<td><n.permission_field. group="[n.group/]" permission="[n.permission/]" >
+				<n.set_value.group_has_site_permission group="[n.group/]" permission="[n.permission/]" />
+				<n.checkbox/>
+				<n.backup_default_value field_name="[n.name/]">
+					<default_value>
+						<n.site_permission_inherited_value group="[n.group/]" permission="[n.permission/]" />
+					</default_value>
+				</n.backup_default_value>
+			</n.permission_field.></td>
+		</else>
+	</n.if.not.is_null.cell>
+</macro>
+
+<macro name="permission_inherited_value" parameters="group,permission" requires="node_page">
+	<n.if.page_node.is_root>
+		<then>
+			<n.has_default_permission group="[n.group/]" permission="[n.permission/]" />
+		</then>
+		<else>
+			<n.page_node.parent_node.has_permission group="[n.group/]" permission="[n.permission/]" />
+		</else>
+	</n.if.page_node.is_root>
+</macro>
+
+<macro name="site_permission_inherited_value" parameters="group,permission">
+	<n.has_site_default_permission group="[n.group/]" permission="[n.permission/]" />
+</macro>
+
+<macro name="backup_default_value" parameters="field_name,default_value">
+	<script type="text/javascript">
+		fields.push('<n.field_name/>');
+		defaults.push(<n.null_to_false.default_value/>);
+	</script>
+</macro>
+
+<macro name="permission_field" parameters="group,permission" dot_parameter="do">
+	<n.field>
+		<name>chk__<n.remove_spaces.permission/>__<n.remove_spaces.group/></name>
+		<do><n.do/></do>
+	</n.field>
+</macro>
+
+<macro name="current_permission_row_field" dot_parameter="do" requires="permission_list">
+	<n.permission_row_field permission="[n.current_permission/]" do="[n.do/]" />
+</macro>
+
+<macro name="permission_row_field" parameters="permission" dot_parameter="do">
+	<n.field>
+		<name>select__<n.remove_spaces.permission/></name>
+		<do><n.do/></do>
+	</n.field>
+</macro>
+
+<macro name="permission_group_list" dot_parameter="do">
+	<n.user_groups.>
+		<n.remove.members_group/>
+		<n.remove.administrators_group/>
+		<n.sort/>
+		<n.do/>
+	</n.user_groups.>
+</macro>
+
+<macro name="permission_group_heading" dot_parameter="group">
+	<th class="medium-border-color">
+		<n.if>
+			<condition>
+				<n.either>
+					<condition1><n.equal value1="[n.group/]" value2="[n.anyone_group/]"/></condition1>
+					<condition2><n.equal value1="[n.group/]" value2="[n.authors_group/]"/></condition2>
+				</n.either>
+			</condition>
+			<then><n.group/></then>
+			<else>
+				<n.set_var. name="url"><n.manage_users_and_groups_path group="[n.group/]"/></n.set_var.>
+				<a href="[n.var name='url'/]"><n.group/></a>
+			</else>
+		</n.if>
+	</th>
+</macro>
+
+<macro name="all_permissions" dot_parameter="do">
+	<n.permissions values="[n.all_permissions_list/]">
+		<do><n.do/></do>
+	</n.permissions>
+</macro>
+
+<macro name="all_permissions_list">
+	<n.view_permission/>,
+	<n.edit_app_permission/>,
+	<n.reply_permission/>,
+	<n.create_topic_permission/>,
+	<n.move_permission/>,
+	<n.create_sub_apps_permission/>,
+	<n.change_post_date_permission/>,
+	<n.manage_pinned_topics_permission/>,
+	<n.manage_locked_topics_permission/>,
+	<n.manage_subscribers_permission/>,
+	<n.show_group_members_permission/>,
+	<n.unrestricted_posting_permission/>,
+	<n.edit_all_permission/>,
+</macro>
+
+<macro name="all_site_permissions" dot_parameter="do">
+	<n.permissions values="
+		[n.manage_banned_users_permission/]
+	">
+		<do><n.do/></do>
+	</n.permissions>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/change_post_date.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,175 @@
+<macro name="change_post_date" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_change_post_date_of.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_change_post_date_of.page_node>
+
+		<n.if.is_submitted_form>
+			<then>
+				<n.edit_page_node.>
+					<n.set_when_created date="[n.when_created_field.value/]"/>
+					<n.save_node/>
+				</n.edit_page_node.>
+				<n.redirect_to.page_node.url/>
+			</then>
+		</n.if.is_submitted_form>
+
+		<n.when_created_field.set_value value="0"/>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Change Post Date</t></n.title.>
+				<style type="text/css">
+					table.calendar { text-align:center; border-collapse: collapse; margin-top: .3em; }
+					table.calendar td.week-header { color:black; background: #eeeeee; }
+					table.calendar td.day { padding:.2em .4em; }
+					table.calendar td.current { background: #eeeeee; color:blue; }
+				</style>
+				<script type="text/javascript">
+					$(document).ready(function() {
+						var date = new Date(<n.page_node.when_created.raw_time/>);
+						$('#hour').val(date.getHours());
+						$('#minute').val(date.getMinutes());
+					});
+					function updateHiddenField() {
+						var date = new Date(
+							nabbleCalendar.getYear(),
+							nabbleCalendar.getMonth(),
+							nabbleCalendar.getDay(),
+							parseInt($('#hour').val()),
+							parseInt($('#minute').val()),
+							0,
+							0);
+						$('#when_created').val(date.getTime());
+					};
+				</script>
+			</head>
+			<body>
+				<n.edit_header first_text="Change Post Date" second_text="[n.page_node.subject/]" />
+				<div style="padding:1em">
+					<div style="float:left">
+						<n.calendar
+							date_time="[n.page_node.when_created.raw_time/]"
+							months="[t]January[/t]|[t]February[/t]|[t]March[/t]|[t]April[/t]|[t]May[/t]|[t]June[/t]|[t]July[/t]|[t]August[/t]|[t]September[/t]|[t]October[/t]|[t]November[/t]|[t]December[/t]"
+							week_days="[t]S|M|T|W|T|F|S[/t]"
+							min_year="2000"
+						/>
+					</div>
+					<div style="float:left;margin-left:1em">
+						<div class="second-font field-title" style="margin-top:0">
+							<t>Time</t>
+						</div>
+						<n.hour_editor/> : <n.minute_editor/>
+					</div>
+					<div style="clear:both;padding-top:1em">
+						<n.form. onsubmit="return updateHiddenField()">
+							<n.when_created_field.hidden/>
+							<input type="submit" value="[t]Save Changes[/t]" />
+							<t>or</t> <a href="[n.page_node.url/]"><t>Cancel</t></a>
+						</n.form.>
+					</div>
+				</div>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="when_created_field" dot_parameter="do">
+	<n.field. name="when_created"><n.do/></n.field.>
+</macro>
+
+<macro name="hour_editor">
+	<select id="hour">
+		<option value="0">00</option>
+		<option value="1">01</option>
+		<option value="2">02</option>
+		<option value="3">03</option>
+		<option value="4">04</option>
+		<option value="5">05</option>
+		<option value="6">06</option>
+		<option value="7">07</option>
+		<option value="8">08</option>
+		<option value="9">09</option>
+		<option value="10">10</option>
+		<option value="11">11</option>
+		<option value="12">12</option>
+		<option value="13">13</option>
+		<option value="14">14</option>
+		<option value="15">15</option>
+		<option value="16">16</option>
+		<option value="17">17</option>
+		<option value="18">18</option>
+		<option value="19">19</option>
+		<option value="20">20</option>
+		<option value="21">21</option>
+		<option value="22">22</option>
+		<option value="23">23</option>
+	</select>
+</macro>
+
+<macro name="minute_editor">
+	<select id="minute">
+		<option value="0">00</option>
+		<option value="1">01</option>
+		<option value="2">02</option>
+		<option value="3">03</option>
+		<option value="4">04</option>
+		<option value="5">05</option>
+		<option value="6">06</option>
+		<option value="7">07</option>
+		<option value="8">08</option>
+		<option value="9">09</option>
+		<option value="10">10</option>
+		<option value="11">11</option>
+		<option value="12">12</option>
+		<option value="13">13</option>
+		<option value="14">14</option>
+		<option value="15">15</option>
+		<option value="16">16</option>
+		<option value="17">17</option>
+		<option value="18">18</option>
+		<option value="19">19</option>
+		<option value="20">20</option>
+		<option value="21">21</option>
+		<option value="22">22</option>
+		<option value="23">23</option>
+		<option value="24">24</option>
+		<option value="25">25</option>
+		<option value="26">26</option>
+		<option value="27">27</option>
+		<option value="28">28</option>
+		<option value="29">29</option>
+		<option value="30">30</option>
+		<option value="31">31</option>
+		<option value="32">32</option>
+		<option value="33">33</option>
+		<option value="34">34</option>
+		<option value="35">35</option>
+		<option value="36">36</option>
+		<option value="37">37</option>
+		<option value="38">38</option>
+		<option value="39">39</option>
+		<option value="40">40</option>
+		<option value="41">41</option>
+		<option value="42">42</option>
+		<option value="43">43</option>
+		<option value="44">44</option>
+		<option value="45">45</option>
+		<option value="46">46</option>
+		<option value="47">47</option>
+		<option value="48">48</option>
+		<option value="49">49</option>
+		<option value="50">50</option>
+		<option value="51">51</option>
+		<option value="52">52</option>
+		<option value="53">53</option>
+		<option value="54">54</option>
+		<option value="55">55</option>
+		<option value="56">56</option>
+		<option value="57">57</option>
+		<option value="58">58</option>
+		<option value="59">59</option>
+	</select>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/change_title_and_meta_tags.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,143 @@
+<macro name="change_title_and_meta_tags" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.is_site_admin>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.is_site_admin>
+
+		<n.if.is_submitted_form>
+			<then>
+				<n.if.has_custom_values_field.is_checked>
+					<then>
+						<n.page_node.set_property name="page_title" value="[n.page_title_field.value/]"/>
+						<n.page_node.set_property name="page_meta_description" value="[n.page_meta_description_field.value/]"/>
+					</then>
+					<else>
+						<n.page_node.delete_property name="page_title"/>
+						<n.page_node.delete_property name="page_meta_description"/>
+					</else>
+				</n.if.has_custom_values_field.is_checked>
+				<n.redirect_to.page_node.path/>
+			</then>
+			<else>
+				<n.has_custom_values_field.set_value value="[n.page_node.has_custom_meta_tags/]"/>
+				<n.page_title_field.set_value.property_default property="page_title" default="[n.page_node.default_title_contents/]"/>
+				<n.page_meta_description_field.set_value.property_default property="page_meta_description" default="[n.page_node.default_meta_description/]"/>
+			</else>
+		</n.if.is_submitted_form>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.title.><t>Change title and meta tags</t></n.title.>
+				<style type="text/css">
+					textarea {
+						margin:.4em 0 2em;
+						width:40em;
+						height:3em;
+					}
+				</style>
+				<script type="text/javascript">
+					var txt_counter = '<t>Current length: <t.number>#1</t.number> characters</t>';
+					function updateCounters(){
+						$('textarea').each(function() {
+							var $textarea = $(this);
+							$textarea.prev().html(txt_counter.replace(/#1/, $textarea.val().length));
+						});
+					};
+
+					function enableFields() {
+						var checked = $('#has_custom_values').is(':checked');
+						var $fields = $('#page_title,#page_meta_description');
+						if (checked) {
+							$('textarea').keyup(updateCounters);
+							$fields.removeAttr('disabled');
+							$fields.eq(0).focus();
+						} else
+							$fields.attr('disabled', true);
+					};
+					$(document).ready(function() {
+						enableFields();
+						updateCounters();
+						$('#has_custom_values').click(enableFields);
+					});
+				</script>
+			</head>
+			<body>
+				<n.edit_header first_text="[n.page_node.subject/]" second_text="[t]Change title and meta tags[/t]" />
+				<div class="weak-color" style="margin:1em 0">
+					<t>Here you can customize the title and meta tags of the <t.location><i><n.page_node.subject/></i></t.location> page.</t>
+					<t>The meta information below can help you optimize this page for search engines (e.g., google, yahoo!, etc.).</t>
+				</div>
+				<n.form.>
+					<div style="margin:1em 0 0;">
+						<n.has_custom_values_field.checkbox />
+						<label for="[n.has_custom_values_field.name/]" class="big-title second-font"><t>Use custom values</t></label><br/>
+					</div>
+
+					<div style="margin:1em 0 0 3em">
+						<div class="big-title second-font"><t>Page Title</t></div>
+						<div class="weak-color"><t>Enter a context-rich title that clearly identifies this page.</t></div>
+						<div class="weak-color"><t>The title should ideally be less than 70 characters in length.</t></div>
+						<div class="weak-color"></div>
+						<n.page_title_field.textarea maxlength="100"/>
+
+						<div class="big-title second-font"><t>Meta Description</t></div>
+						<div class="weak-color"><t>Enter a brief and concise summary of your page's content.</t></div>
+						<div class="weak-color"><t>Limit your description to 155 characters or 170 characters at most.</t></div>
+						<div class="weak-color"></div>
+						<n.page_meta_description_field.textarea maxlength="250"/>
+					</div>
+
+					<div>
+						<input type="submit" class="toolbar action-button" value="[t]Save Changes[/t]" />
+						<t>or</t> <a href="[n.page_node.path/]"><t>Cancel</t></a>
+					</div>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="has_custom_meta_tags" requires="node">
+	<n.both>
+		<condition1.has_property name="page_title"/>
+		<condition2.has_property name="page_meta_description"/>
+	</n.both>
+</macro>
+
+<macro name="property_default" parameters="property, default">
+	<n.if.page_node.has_property name="[n.property/]">
+		<then.page_node.get_property name="[n.property/]"/>
+		<else.compress.default/>
+	</n.if.page_node.has_property>
+</macro>
+
+<macro name="default_title_contents" requires="node">
+	<n.if.is_app>
+		<then.default_app_title_contents/>
+		<else.default_topic_title_contents/>
+	</n.if.is_app>
+</macro>
+
+<macro name="change_title_and_meta_tags_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.change_title_and_meta_tags_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Change title and meta tags[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="change_title_and_meta_tags_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=change_title_and_meta_tags&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="page_title_field" dot_parameter="do">
+	<n.field. name="page_title"><n.do/></n.field.>
+</macro>
+
+<macro name="page_meta_description_field" dot_parameter="do">
+	<n.field. name="page_meta_description"><n.do/></n.field.>
+</macro>
+
+<macro name="has_custom_values_field" dot_parameter="do">
+	<n.field. name="has_custom_values"><n.do/></n.field.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/change_user_groups.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,141 @@
+<macro name="change_user_groups" requires="servlet">
+	<n.user_page.>
+		<n.if.not.visitor.can_manage_users_and_groups>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_manage_users_and_groups>
+		<n.if.is_submitted_form>
+			<then>
+				<n.catch_exception. id="save-block">
+					<n.edit_page_user.>
+						<n.user_group_list.loop.group_field. group="[n.current_group/]">
+							<n.if.is_checked>
+								 <then><n.add_to_group.current_group/></then>
+								 <else><n.remove_from_group.current_group/></else>
+							</n.if.is_checked>
+						</n.user_group_list.loop.group_field.>
+						<n.get_parameter_values. name="newgroup">
+							<n.loop.>
+								<n.if.not.is_empty.current_parameter_value>
+								 	<then><n.add_to_group.current_parameter_value/></then>
+								</n.if.not.is_empty.current_parameter_value>
+							</n.loop.>
+						</n.get_parameter_values.>
+					</n.edit_page_user.>
+					<n.redirect_to.page_user.path/>
+				</n.catch_exception.>
+			</then>
+		</n.if.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Change User Groups</t></n.title.>
+				<style type="text/css">
+					.title-row {
+						padding:.2em;
+						border-bottom-width:2px;
+						border-bottom-style:solid;
+						font-weight:bold;
+					}
+				</style>
+			</head>
+			<body>
+				<n.page_user.profile_header/>
+
+				<div class="title-row light-border-color" style="margin-top:.5em">
+					<t>Groups of this user</t>
+				</div>
+				<n.form.>
+					<table>
+						<n.fixed_group_row name="[n.anyone_group/]" tip="[t]All users belong to this group[/t]"/>
+						<n.if.page_user.is_registered>
+							<then><n.fixed_group_row name="[n.registered_group/]" tip="[t]Users that completed the registration process[/t]"/></then>
+						</n.if.page_user.is_registered>
+
+						<n.if.page_user.is_authenticated>
+							<then>
+								<n.user_group_list.loop.>
+									<n.group_row>
+										<group><n.current_group/></group>
+										<checked><n.page_user.is_in_group.current_group/></checked>
+									</n.group_row>
+								</n.user_group_list.loop.>
+
+								<n.empty_group_controls/>
+							</then>
+						</n.if.page_user.is_authenticated>
+					</table>
+
+					<n.if.page_user.is_authenticated>
+						<then>
+							<div style="margin-top:1.4em">
+								<input type="submit" value="[t]Save Changes[/t]" />
+								<t>or</t> <a href="[n.page_user.url/]"><t>Cancel</t></a>
+							</div>
+						</then>
+					</n.if.page_user.is_authenticated>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.user_page.>
+</macro>
+
+<macro name="fixed_group_row" parameters="name,tip">
+	<tr>
+		<td>&nbsp;</td>
+		<td style="padding:.2em 0">
+			<n.name/>
+		</td>
+		<td class="weak-color">
+			<n.hide_null.tip/>
+		</td>
+	</tr>
+</macro>
+
+<macro name="group_row" parameters="group,checked">
+	<n.group_field. group="[n.group/]">
+		<n.set_value.checked/>
+		<tr>
+			<td>
+				<n.checkbox/>
+			</td>
+			<td><label for="[n.name/]"><n.group/></label></td>
+			<td></td>
+		</tr>
+	</n.group_field.>
+</macro>
+
+<macro name="group_field" parameters="group" dot_parameter="do">
+	<n.field. name="group-[n.remove_spaces.group/]"><n.do/></n.field.>
+</macro>
+
+<macro name="empty_group_controls">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			function addNewGroup() {
+				var r = '<tr>';
+				r += '<td></td>';
+				r += '<td><input type="text" name="newgroup"/> <a href="javascript:void(0)" onclick="$(this).parent().parent().remove()"><t>remove</t></a></td>';
+				r += '</tr>';
+				$('#last-row').before(r);
+				$('input[type="text"]:last').focus();
+				Nabble.resizeFrames();
+			};
+		</script>
+	</n.put_in_head.>
+	<tr id="last-row">
+		<td>&nbsp;</td>
+		<td><a href="javascript:void(0)" onclick="addNewGroup()"><t>Add a new group</t></a></td>
+		<td></td>
+	</tr>
+</macro>
+
+<macro name="user_group_list" dot_parameter="do">
+	<n.user_groups.>
+		<n.add.members_group/>
+		<n.add.administrators_group/>
+		<n.sort/>
+		<n.do/>
+	</n.user_groups.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/columns.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,343 @@
+<macro name="table_header" dot_parameter="do">
+	<n.do/>
+</macro>
+
+<macro name="calc_table_column_count" dot_parameter="columns">
+	<n.global_set_var name="calc_table_column_count" value="0" />
+	<n.columns/>
+	<n.global_var name="calc_table_column_count" />
+</macro>
+
+<macro name="table_column" parameters="head,body">
+	<n.if.is_in_command name="calc_table_column_count">
+		<then.global_set_var. name="calc_table_column_count">
+			<n.int>
+				<i.global_var name="calc_table_column_count" />
+				<do.plus i="1" />
+			</n.int>
+		</then.global_set_var.>
+		<else.if.is_in_command name="table_header">
+			<then.head />
+			<else.body />
+		</else.if.is_in_command>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="topic_count_column" parameters="title,width">
+	<n.table_column>
+		<head>
+			<td class="[n.column_default_border/] topic-count-column nowrap" align="center" style="[n.width_style.width/]">
+				<n.default. to="[t]Topics[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td class="weak-color [n.column_default_border/]" align="center"><n.current_node.topic_count/></td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="post_count_column" parameters="title,width">
+	<n.table_column>
+		<head>
+			<td class="[n.column_default_border/] post_count_column nowrap" align="center" style="[n.width_style.width/]">
+				<n.default. to="[t]Posts[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td class="weak-color [n.column_default_border/]" align="center"><n.current_node.post_count/></td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="last_post_column" parameters="title,width,white_space">
+	<n.table_column>
+		<head>
+			<td class="[n.column_default_border/] nowrap last-post-column" style="[n.width_style.width/]">
+				<n.default. to="[t]Last Post[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<n.current_node.>
+				<td class="[n.column_default_border/] weak-color">
+					<n.if.either condition1="[n.is_post/]" condition2="[n.has_topics/]">
+						<then>
+							<n.last_node.>
+								<table class="avatar-table">
+									<tr>
+										<n.last_post_column_start/>
+										<td style="width:100%;padding:0 .3em;border:none;[n.style. property='white-space'][n.white_space/][/n.style.]">
+											<n.smart_post_link>
+												<text.when_created.short_format/>
+											</n.smart_post_link>
+											<span class="weak-color nowrap" style="padding-left:.2em">
+												<t>by <t.author.owner.name truncate="20"/></t>
+											</span>
+										</td>
+									</tr>
+								</table>
+							</n.last_node.>
+						</then>
+						<else>
+							<span style="padding-left:.4em">
+								<t>Empty</t>
+							</span>
+						</else>
+					</n.if.either>
+				</td>
+			</n.current_node.>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="last_post_column_start">
+</macro>
+
+<macro name="subcategories_column" parameters="title,width">
+	<n.table_column>
+		<head>
+			<td class="[n.column_default_border/] subcategories-column nowrap" style="[n.width_style.width/]" colspan="2">
+				<n.default. to="[t]Subcategories[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td class="[n.column_default_border/]" style="width:1em">
+				<n.if.current_node.is_app>
+					<then><img src="/images/forum.png" width="25" height="23"/></then>
+					<else><img src="/images/thread.png" width="25" height="23"/></else>
+				</n.if.current_node.is_app>
+			</td>
+			<td class="weak-color [n.column_default_border/] adbayes-content" style="width:60%">
+				<div style="font-weight:bold;margin-bottom:.3em">
+					<n.current_node.node_link/>
+				</div>
+				<span class="weak-color node-snippet" style="font-size:90%;display:block;padding-bottom:.5em">
+					<n.if.current_node.is_app>
+						<then.current_node.node_message_as_html/>
+						<else>
+							<n.truncate. size="300">
+								<n.remove_html_tags.current_node.message.as_text/>
+							</n.truncate.>
+						</else>
+					</n.if.current_node.is_app>
+					<n.if.current_node.has_subapps>
+						<then>
+							<div class="sub-forums weak-color">
+								<b><t>Sub-Forums</t>:</b>
+								<n.current_node.subapps_list.loop.>
+									<n.current_node.node_link class="nowrap" />&#32;
+								</n.current_node.subapps_list.loop.>
+							</div>
+						</then>
+					</n.if.current_node.has_subapps>
+				</span>
+			</td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="pin_column">
+	<n.table_column>
+		<head>
+			<td class="[n.column_default_border/] pin-column nowrap" style="width:24px"></td>
+		</head>
+		<body>
+			<n.current_node.>
+				<td class="[n.column_default_border/] pin-column" style="padding-left:.2em">
+					<n.if.is_app>
+						<then>
+							<n.if.is_pinned_in_loop>
+								<then><img src="/images/forum_pin.png" width="29" height="23" title="[t]Pinned sub-forum[/t]" alt="[t]Pinned sub-forum[/t]"/></then>
+								<else><img src="/images/forum.png" width="25" height="23" title="[t]Floating sub-forum[/t]" alt="[t]Floating sub-forum[/t]"/></else>
+							</n.if.is_pinned_in_loop>
+						</then>
+						<else>
+							<n.if.is_pinned_in_loop>
+								<then><img src="/images/pin.png" width="20" height="21" alt="pin"/></then>
+							</n.if.is_pinned_in_loop>
+						</else>
+					</n.if.is_app>
+				</td>
+			</n.current_node.>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="replies_column" parameters="width,title">
+	<n.table_column>
+		<head>
+			<td class="[n.column_default_border/] replies-column nowrap" align="center" style="[n.width_style.width/]">
+				<n.default. to="[t]Replies[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td class="[n.column_default_border/]" align="center"><n.current_node.replies/></td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="topics_column" parameters="width,title,count">
+	<n.table_column>
+		<head>
+			<td class="[n.column_default_border/] topics-column nowrap" style="[n.width_style.width/]">
+				<table class="avatar-table">
+					<tr>
+						<td class="nowrap" style="font-weight:bold;border:none">
+							<n.default. to="[t]Topics[/t]"><n.title/></n.default.>
+							<span class="weak-color" style="font-size:80%;margin-right:1.7em">(<n.count/>)</span>
+						</td>
+					</tr>
+				</table>
+			</td>
+		</head>
+		<body>
+			<n.current_node.>
+				<td class="[n.column_default_border/]">
+					<table>
+						<tr>
+							<n.topics_column_start/>
+							<td class="adbayes-content" style="width:100%;padding-left:.3em;border:none;word-break:break-word">
+								<n.if.is_app>
+									<then>
+										<b><n.node_link/></b>
+										<span class="weak-color">
+											(<n.remove_spaces_between_tags.>
+												<n.one_or_many.topic_count>
+													<one_text><t>topic</t></one_text>
+													<many_text><t>topics</t></many_text>
+												</n.one_or_many.topic_count>
+											</n.remove_spaces_between_tags.>)
+										</span>
+									</then>
+									<else>
+										<n.smart_post_link/>
+										<span class="weak-color">
+											<t>by <t.author.owner.name truncate="20"/></t>
+										</span>
+									</else>
+								</n.if.is_app>
+							</td>
+						</tr>
+					</table>
+				</td>
+			</n.current_node.>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="topics_column_start">
+</macro>
+
+<macro name="mixed_topics_column">
+	<n.topics_column>
+		<title>
+			<n.if.is_in_command name="top_topics">
+				<then><t>Topics</t></then>
+				<else><n.current_node.node_link class='second-font category-link'/></else>
+			</n.if.is_in_command>
+		</title>
+		<count>
+			<n.if.is_in_command name="top_topics">
+				<then><n.page_node.topic_count filter="[n.children_filter/]"/></then>
+				<else><n.current_node.child_count/></else>
+			</n.if.is_in_command>
+		</count>
+	</n.topics_column>
+</macro>
+
+<macro name="top_topics" dot_parameter="do">
+	<n.do/>
+</macro>
+
+
+<macro name="views_column" parameters="title">
+	<n.table_column>
+		<head>
+			<td class="[n.column_default_border/] views-column nowrap" align="center">
+				<n.default. to="[t]Views[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td class="weak-color [n.column_default_border/]" align="center"><n.current_node.views/></td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="subapp_column" parameters="width,title">
+	<n.table_column>
+		<head>
+			<n.if.page_node.has_subapps>
+				<then>
+					<td class="[n.column_default_border/] subapp-column nowrap" align="center" style="[n.width_style.width/]">
+						<n.default. to="[t]Sub Forum[/t]"><n.title/></n.default.>
+					</td>
+				</then>
+			</n.if.page_node.has_subapps>
+		</head>
+		<body>
+			<n.if.page_node.has_subapps>
+				<then>
+					<td class="[n.column_default_border/] adbayes-content" style="font-size:90%" align="center"><n.current_node.subapp_link/></td>
+				</then>
+			</n.if.page_node.has_subapps>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="topics_summary_column" parameters="width,title,count">
+	<n.table_column>
+		<head>
+			<td class="[n.column_default_border/] topics-summary-column nowrap" style="[n.width_style.width/]">
+				<table class="avatar-table">
+					<tr>
+						<td class="nowrap" style="font-weight:bold;border:none">
+							<n.default. to="[t]Topics[/t]"><n.title/></n.default.>
+							<span class="weak-color" style="font-size:80%;margin-right:1.7em">(<n.count/>)</span>
+						</td>
+					</tr>
+				</table>
+			</td>
+		</head>
+		<body>
+			<n.current_node.>
+				<td class="[n.column_default_border/] adbayes-content" style="padding:.4em 0">
+					<table>
+						<tr>
+							<td><n.owner.avatar size="small" group="A"/></td>
+							<td style="width:100%">
+								<n.smart_post_link/>
+								<t>by <t.author.owner.name truncate="20"/></t>
+							</td>
+						</tr>
+					</table>
+					<div class="weak-color" style="width:100%;font-size:80%;margin-left:.4em">
+						<n.if.has_replies>
+							<then.replies_link/>
+							<else>
+								0 <t>replies</t>
+							</else>
+						</n.if.has_replies>,
+						<n.views show_text="true"/>
+						<n.if.is_in_subapp>
+							<then>
+								- <t>in <t.location.italic.subapp_link_on_hover/></t>
+							</then>
+						</n.if.is_in_subapp>
+					</div>
+				</td>
+			</n.current_node.>
+		</body>
+	</n.table_column>
+</macro>
+
+<!-- Table Defaults / To be overridden -->
+
+<macro name="column_default_border">
+	medium-border-color
+</macro>
+
+<macro name="even_row_background">
+</macro>
+
+<macro name="odd_row_background">
+	light-bg-color
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/compile_all.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,231 @@
+
+<macro name="basic_namespace_class">
+	nabble.naml.namespaces.BasicNamespace
+</macro>
+
+<macro name="nabble_namespace_class">
+	nabble.view.web.template.NabbleNamespace
+</macro>
+
+<macro name="servlet_namespace_class">
+	nabble.view.web.template.ServletNamespace
+</macro>
+
+<macro name="html_list_namespace_class">
+	nabble.view.web.template.HtmlListNamespace
+</macro>
+
+<macro name="message_namespace_class">
+	nabble.view.web.template.MessageNamespace
+</macro>
+
+<macro name="user_namespace_class">
+	nabble.view.web.template.UserNamespace
+</macro>
+
+<macro name="request_namespace_class">
+	nabble.view.web.template.RequestNamespace
+</macro>
+
+<macro name="read_authorization_namespace_class">
+	nabble.view.web.template.NamlServlet$ReadAuthorization
+</macro>
+
+<macro name="url_mapper_namespace_class">
+	nabble.view.web.template.UrlMapperNamespace
+</macro>
+
+<macro name="node_namespace_class">
+	nabble.view.web.template.NodeNamespace
+</macro>
+
+<macro name="doc_namespace_class">
+	nabble.view.web.template.DocNamespace
+</macro>
+
+<macro name="binary_doc_namespace_class">
+	nabble.view.web.template.DocNamespace$BinaryDocNamespace
+</macro>
+
+<macro name="instant_mail_namespace_class">
+	nabble.view.web.template.InstantMailNamespace
+</macro>
+
+<macro name="node_list_namespace_class">
+	nabble.view.web.template.NodeList
+</macro>
+
+<macro name="subscription_namespace_class">
+	nabble.view.web.template.SubscriptionNamespace
+</macro>
+
+<macro name="post_by_email_namespace_class">
+	nabble.model.PostByEmail
+</macro>
+
+<macro name="node_page_namespace_class">
+	nabble.view.web.template.NodePageNamespace
+</macro>
+
+<macro name="html_namespace_class">
+	nabble.view.web.template.HtmlNamespace
+</macro>
+
+<macro name="macro_source_namespace_class">
+	nabble.view.web.template.MacroSourceNamespace
+</macro>
+
+
+<macro name="base_classes">
+	<n.basic_namespace_class/>,
+	<n.nabble_namespace_class/>
+</macro>
+
+<macro name="standard_classes">
+	<n.base_classes/>,
+	<n.servlet_namespace_class/>
+</macro>
+
+
+add whatever logging here
+<macro name="compile" parameters="macro,namespaces">
+	<n.compile_template macro="[n.macro/]" namespaces="[n.namespaces/]" />
+</macro>
+
+
+<macro name="compile_all">
+	<n.compile macro="do_nothing" namespaces="" />
+	<n.compile macro="is_valid_configuration" namespaces="[n.base_classes/]" />
+	<n.compile macro="process_message_html" namespaces="[n.base_classes/],[n.html_list_namespace_class/]" />
+	<n.compile macro="message_as_html" namespaces="[n.base_classes/],[n.message_namespace_class/]" />
+
+	<n.compile macro="js_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="site_style" namespaces="[n.standard_classes/]" />
+	<n.compile macro="javascript_library" namespaces="[n.standard_classes/]" />
+	<n.compile macro="send bookmark email" namespaces="[n.standard_classes/]" />
+
+	<n.compile macro="can_edit" namespaces="[n.standard_classes/],[n.user_namespace_class/]" />
+	<n.compile macro="can_move" namespaces="[n.standard_classes/],[n.user_namespace_class/]" />
+	<n.compile macro="can_delete" namespaces="[n.standard_classes/],[n.user_namespace_class/]" />
+	<n.compile macro="can_post_under" namespaces="[n.standard_classes/],[n.user_namespace_class/]" />
+	<n.compile macro="can_view" namespaces="[n.standard_classes/],[n.user_namespace_class/]" />
+
+	<n.compile macro="get read authorization key" namespaces="[n.base_classes/],[n.request_namespace_class/]" />
+	<n.compile macro="authorize for read" namespaces="[n.standard_classes/],[n.read_authorization_namespace_class/]" />
+	<n.compile macro="login_path" namespaces="[n.base_classes/]" />
+	<n.compile macro="url mapper" namespaces="[n.base_classes/],[n.url_mapper_namespace_class/]" />
+	<n.compile macro="view_app canonical path" namespaces="[n.base_classes/],[n.request_namespace_class/]" />
+	<n.compile macro="topic canonical path" namespaces="[n.base_classes/],[n.request_namespace_class/]" />
+	<n.compile macro="page_search_box" namespaces="[n.standard_classes/],[n.node_namespace_class/]" />
+	<n.compile macro="login_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="logout_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="registering_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="start_registration_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="finish_registration_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="forgot_password_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="doc for visible_for_admins" namespaces="[n.base_classes/],[n.doc_namespace_class/]" />
+	<n.compile macro="doc not found" namespaces="[n.base_classes/],[n.doc_namespace_class/]" />
+	<n.compile macro="binary doc" namespaces="[n.base_classes/],[n.binary_doc_namespace_class/]" />
+	<n.compile macro="adv_search_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="search_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="move_node" namespaces="[n.standard_classes/]" />
+	<n.compile macro="edit_post" namespaces="[n.standard_classes/]" />
+	<n.compile macro="edit_app" namespaces="[n.standard_classes/]" />
+	<n.compile macro="reply" namespaces="[n.standard_classes/]" />
+	<n.compile macro="new_topic" namespaces="[n.standard_classes/]" />
+	<n.compile macro="change_post_date" namespaces="[n.standard_classes/]" />
+	<n.compile macro="create_sub_app" namespaces="[n.standard_classes/]" />
+	<n.compile macro="mailing_list_archive_settings" namespaces="[n.standard_classes/]" />
+	<n.compile macro="app_people" namespaces="[n.standard_classes/]" />
+	<n.compile macro="post_by_email_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="subscribe" namespaces="[n.standard_classes/]" />
+	<n.compile macro="subscribe_by_code" namespaces="[n.standard_classes/]" />
+	<n.compile macro="subscription_saved" namespaces="[n.standard_classes/]" />
+	<n.compile macro="unsubscribe" namespaces="[n.standard_classes/]" />
+	<n.compile macro="unsubscribe_by_code" namespaces="[n.standard_classes/]" />
+	<n.compile macro="subscription_removed" namespaces="[n.standard_classes/]" />
+	<n.compile macro="change_app_type" namespaces="[n.standard_classes/]" />
+	<n.compile macro="change_user_groups" namespaces="[n.standard_classes/]" />
+	<n.compile macro="change_permissions" namespaces="[n.standard_classes/]" />
+	<n.compile macro="user_nodes" namespaces="[n.standard_classes/]" />
+	<n.compile macro="delete_post" namespaces="[n.standard_classes/]" />
+	<n.compile macro="delete_from_site" namespaces="[n.standard_classes/]" />
+	<n.compile macro="delete_app" namespaces="[n.standard_classes/]" />
+	<n.compile macro="manage_subscribers" namespaces="[n.standard_classes/]" />
+	<n.compile macro="ban_user" namespaces="[n.standard_classes/]" />
+	<n.compile macro="unban_user" namespaces="[n.standard_classes/]" />
+	<n.compile macro="manage_banned_users" namespaces="[n.standard_classes/]" />
+	<n.compile macro="unauthorized" namespaces="[n.standard_classes/]" />
+	<n.compile macro="manage_users_and_groups" namespaces="[n.standard_classes/]" />
+	<n.compile macro="user_profile" namespaces="[n.standard_classes/]" />
+	<n.compile macro="edit_profile" namespaces="[n.standard_classes/]" />
+	<n.compile macro="edit_signature" namespaces="[n.standard_classes/]" />
+	<n.compile macro="view_app" namespaces="[n.standard_classes/]" />
+	<n.compile macro="raw_mail" namespaces="[n.standard_classes/]" />
+	<n.compile macro="lock_topic" namespaces="[n.standard_classes/]" />
+	<n.compile macro="unlock_topic" namespaces="[n.standard_classes/]" />
+	<n.compile macro="threaded_posts" namespaces="[n.standard_classes/],[n.node_namespace_class/]" />
+	<n.compile macro="change_appearance" namespaces="[n.standard_classes/]" />
+	<n.compile macro="save_font_config" namespaces="[n.standard_classes/]" />
+	<n.compile macro="save_color_config" namespaces="[n.standard_classes/]" />
+	<n.compile macro="save_preferences_config" namespaces="[n.standard_classes/]" />
+	<n.compile macro="change_title_and_meta_tags" namespaces="[n.standard_classes/]" />
+	<n.compile macro="category_ajax" namespaces="[n.standard_classes/],[n.servlet_namespace_class/]" />
+
+	<n.comment.> MACRO VIEWER </n.comment.>
+	<n.compile macro="macro_viewer" namespaces="[n.standard_classes/]" />
+	<n.compile macro="overridden_macro_source" namespaces="[n.standard_classes/],[n.html_namespace_class/],[n.macro_source_namespace_class/]" />
+	<n.compile macro="view_log" namespaces="[n.standard_classes/]" />
+	<n.compile macro="clear_site_log" namespaces="[n.standard_classes/]" />
+	<n.compile macro="get_site_log" namespaces="[n.standard_classes/]" />
+	<n.compile macro="save_tweak" namespaces="[n.standard_classes/]" />
+	<n.compile macro="revert_tweak" namespaces="[n.standard_classes/]" />
+	<n.compile macro="find_usages_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="macro_search_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="new_macro_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="macro_deleted_page" namespaces="[n.standard_classes/]" />
+
+	<n.comment.> FEEDS </n.comment.>
+	<n.compile macro="feeds" namespaces="[n.standard_classes/]" />
+	<n.compile macro="atom_topics_by_date" namespaces="[n.standard_classes/]" />
+	<n.compile macro="atom_posts_by_date" namespaces="[n.standard_classes/]" />
+
+	<n.comment.> REST </n.comment.>
+	<n.compile macro="rest_group_control" namespaces="[n.standard_classes/]" />
+
+	<n.compile macro="view_blog" namespaces="[n.standard_classes/]" />
+	<n.compile macro="view_board" namespaces="[n.standard_classes/]" />
+	<n.compile macro="view_category" namespaces="[n.standard_classes/]" />
+	<n.compile macro="view_gallery" namespaces="[n.standard_classes/]" />
+	<n.compile macro="view_mixed" namespaces="[n.standard_classes/]" />
+	<n.compile macro="view_news" namespaces="[n.standard_classes/]" />
+	<n.compile macro="view_subapps" namespaces="[n.standard_classes/]" />
+
+	<n.compile macro="view_standard" namespaces="[n.standard_classes/]" />
+	<n.compile macro="view_topics" namespaces="[n.standard_classes/]" />
+
+	<n.compile macro="topic" namespaces="[n.standard_classes/]" />
+	<n.compile macro="classic_blog_topic" namespaces="[n.standard_classes/]" />
+	<n.compile macro="list_blog_topic" namespaces="[n.standard_classes/]" />
+	<n.compile macro="threaded_blog_topic" namespaces="[n.standard_classes/]" />
+	<n.compile macro="classic_forum_topic" namespaces="[n.standard_classes/]" />
+	<n.compile macro="list_forum_topic" namespaces="[n.standard_classes/]" />
+	<n.compile macro="threaded_forum_topic" namespaces="[n.standard_classes/]" />
+	<n.compile macro="ajax_post" namespaces="[n.standard_classes/]" />
+
+	<n.compile macro="download_backup_page" namespaces="[n.standard_classes/]" />
+	<n.compile macro="start_backup_process" namespaces="[n.standard_classes/]" />
+
+	<n.comment.> Emails </n.comment.>
+	<n.compile macro="notify_subscribers" namespaces="[n.base_classes/],[n.node_namespace_class/]" />
+	<n.compile macro="digest email" namespaces="[n.base_classes/],[n.node_list_namespace_class/],[n.subscription_namespace_class/]" />
+
+	<n.compile macro="post by email" namespaces="[n.base_classes/],[n.post_by_email_namespace_class/]" />
+	<n.compile macro="backup email" namespaces="[n.base_classes/]" />
+	<n.compile macro="site deletion email" namespaces="[n.base_classes/]" />
+
+	<n.compile macro="compiled_all_check" namespaces="" />
+</macro>
+
+<macro name="compiled_all_check">
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/configurations.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,15 @@
+<macro name="valid_configurations">
+    <n.chance_appearance_configurations/>,
+	<n.google_analytics_configurations/>
+</macro>
+
+<macro name="is_valid_configuration" dot_parameter="config">
+	<n.string_list>
+		<values>
+			<n.valid_configurations/>
+		</values>
+		<do>
+			<n.contains.config/>
+		</do>
+	</n.string_list>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/create_sub_app.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,147 @@
+<macro name="create_sub_app" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_create_sub_apps_under.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_create_sub_apps_under.page_node>
+		<n.if.not.is_submitted_form>
+			<then>
+				<n.type_field.set_value value="[n.page_node.sub_app_type/]" />
+			</then>
+			<else>
+				<n.catch_exception. id="save-block">
+					<n.handle_anonymous_submit/>
+					<n.create_child_of_page_node>
+						<subject><n.subject_field.value/></subject>
+						<message><n.message_field.value/></message>
+						<is_html><n.html_format_field.value/></is_html>
+						<type><n.type_field.value/></type>
+						<kind>app</kind>
+						<do>
+							<n.remember_new_node/>
+							<n.save_node/>
+						</do>
+					</n.create_child_of_page_node>
+					<n.if.visitor.can_edit.page_node>
+						<then.new_node.pin/>
+					</n.if.visitor.can_edit.page_node>
+					<n.if.equal value1="[n.is_mailing_list_field.value/]" value2="[n.true/]">
+						<then><n.redirect_to.new_node.mailing_list_archive_settings_path/></then>
+						<else><n.redirect_to.new_node.path/></else>
+					</n.if.equal>
+				</n.catch_exception.>
+			</else>
+		</n.if.not.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Create <t.element.page_node.child_name/></t></n.title.>
+				<n.subject_field.focus/>
+				<style type="text/css">
+					div.field-title {
+						margin-top: 0;
+					}
+				</style>
+			</head>
+			<body>
+				<n.edit_header first_text="[n.page_node.get_app_node.subject/]" second_text="[t]Create new [t.element.page_node.child_name/][/t]" />
+				<n.if.is_submitted_form>
+					<then>
+						<n.if.has_exception for="save-block">
+							<then.show_new_node_error/>
+						</n.if.has_exception>
+					</then>
+				</n.if.is_submitted_form>
+				<n.form.>
+					<n.type_field.hidden/>
+
+					<n.if.not.visitor.is_registered>
+						<then>
+							<div class="field-box light-border-color">
+								<div class="second-font field-title"><t>Your Name</t></div>
+								<div class="weak-color">
+									<n.if.is_null.visitor.name>
+										<then.anonymous_name_field.input size="30" />
+										<else.visitor.name/>
+									</n.if.is_null.visitor.name>
+								</div>
+							</div>
+						</then>
+					</n.if.not.visitor.is_registered>
+
+					<div class="field-box light-border-color">
+						<div class="second-font field-title"><t>Name</t></div>
+						<div class="weak-color">
+							<n.subject_field.input size="40" />
+							(<t>max. 80 characters</t>)
+						</div>
+					</div>
+
+					<div class="field-box light-border-color">
+						<div class="second-font field-title"><t>Description</t></div>
+						<div class="weak-color">
+							<n.if.visitor.is_registered>
+								<then>
+									<n.html_format_field.checkbox />
+									<label for="[n.html_format_field.name/]"><t>Description is in HTML Format</t></label><br/>
+									<div style="margin:.1em 0">
+										<n.editor_toolbar textarea_id="[n.message_field.name/]"/>
+									</div>
+								</then>
+							</n.if.visitor.is_registered>
+							<n.message_field.textarea wrap="SOFT" style="min-width:30em;max-width:55em;width:100%;height:12em;" />
+						</div>
+					</div>
+
+					<n.if.not.visitor.is_registered>
+						<then>
+							<div class="weak-color field-box light-border-color" style="padding-top:0">
+								<div>
+									<div style="padding-bottom:.4em">
+										<t>Since you are not a registered user, we must check that you are a human.</t>
+									</div>
+									<n.captcha_control/>
+								</div>
+							</div>
+						</then>
+						<else>
+							<n.if.not.page_node.is_associated_with_mailing_list_archive>
+								<then>
+									<div style="margin:.5em 0 1em">
+										<n.is_mailing_list_field.checkbox />
+										<label for="[n.is_mailing_list_field.name/]"><t>This is a mailing list archive</t></label>
+									</div>
+								</then>
+							</n.if.not.page_node.is_associated_with_mailing_list_archive>
+						</else>
+					</n.if.not.visitor.is_registered>
+
+					<input type="submit" class="toolbar action-button" value="[t]Create [t.element.page_node.child_name/][/t]"/>
+					<t>or</t> <a href="[n.page_node.path /]"><t>Cancel</t></a>
+				</n.form.>
+
+				<div class="light-bg-color" style="padding: .5em;margin:1.5em 0 0">
+					<div class="second-font field-title"><t>Related Help Article</t></div>
+					<n.help.cataloging.link/>
+				</div>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="is_mailing_list_field" dot_parameter="do">
+	<n.field. name="is_mailing_list"><n.do/></n.field.>
+</macro>
+
+<macro name="sub_app_type" requires="node">
+	<n.if.regex_matches text="[n.type/]" pattern="forum|category|mixed">
+		<then>forum</then>
+		<else>
+			<n.if.type equals="board">
+				<then>category</then>
+				<else><n.type/></else>
+			</n.if.type>
+		</else>
+	</n.if.regex_matches>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/date.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,22 @@
+<macro name="date_script" parameters="js_function" requires="date">
+	<n.set_var. name='tag_id'>d<n.raw_time/>-<n.random max="999"/></n.set_var.>
+	<span id="[n.var name='tag_id'/]"></span><script type="text/javascript">
+		Nabble.get('<n.var name='tag_id'/>').innerHTML= <n.js_function/>(new Date(<n.raw_time/>));
+	</script>
+</macro>
+
+<macro name="long_format" requires="date">
+	<n.date_script js_function="Nabble.formatDateLong"/>
+</macro>
+
+<macro name="short_format" requires="date">
+	<n.date_script js_function="Nabble.formatDateShort"/>
+</macro>
+
+<macro name="date_only" requires="date">
+	<n.date_script js_function="Nabble.formatDateOnly"/>
+</macro>
+
+<macro name="time_only" requires="date">
+	<n.date_script js_function="Nabble.formatTimeOnly"/>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/delete_app.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,68 @@
+<macro name="delete_app" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_delete_recursively.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_delete_recursively.page_node>
+		<n.if.is_submitted_form>
+			<then>
+				<n.page_node.delete_node_recursively_and_redirect/>
+			</then>
+		</n.if.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Delete Application</t> - <n.page_node.subject/></n.title.>
+			</head>
+			<body>
+				<n.edit_header first_text="[t]Delete Application[/t]" second_text="[n.page_node.subject/]"/>
+
+				<n.form.>
+					<n.if.page_node.is_a_mailing_list_archive>
+						<then><n.mailing_list_unsubscription_notice/></then>
+					</n.if.page_node.is_a_mailing_list_archive>
+
+					<div class="second-font field-title" style="margin-bottom:.6em">
+						<t>Do you really want to permanently <n.important.>delete</n.important.> <t.location><i><n.page_node.subject/></i></t.location>?</t>
+					</div>
+					<div class="weak-color" style="margin-bottom:1em">
+						<t><n.important.>CAUTION</n.important.>: Everything under <t.location.page_node.subject/> will be deleted forever!</t>
+						<ul>
+							<li><t>This includes subcategories, posts, images, files and everything else.</t></li>
+							<li><t>It will NOT be possible to restore deleted items later.</t></li>
+						</ul>
+					</div>
+
+					<div style="margin-top:1.4em">
+						<input type="submit" class="toolbar action-button" value="[t]Yes, delete [t.location.page_node.subject/] forever[/t]"/>
+						<t>or</t> <a href="[n.page_node.path/]"><t>Cancel</t></a>
+					</div>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="mailing_list_unsubscription_notice" requires="node_page">
+	<table style="margin:1em 0 .3em">
+		<tr>
+			<td><img src="/images/icon_alert_sm.png" align="absmiddle" height="16" width="16" alt="Important note"/></td>
+			<td class="second-font" style="font-weight:bold;font-size:110%;"><t>Before deleting this archive...</t></td>
+		</tr>
+	</table>
+	<div class="weak-color" style="margin-bottom:1em">
+		<t>Since this application is a mailing list archive, please unsubscribe the email address below before clicking on the delete button.</t>
+		<div style="padding:1em">
+			<span class="info-message rounded bold" style="padding:.4em .3em">
+				<n.page_node.get_this_mailing_list_archive.subscriber_address/>
+			</span>
+		</div>
+		<t>We have prepared a page with <n.unsubscription_instructions_link.>some instructions on how to unsubscribe this archive</n.unsubscription_instructions_link.>.</t><br/>
+		<t>If you have already removed the subscription, then you can proceed with the deletion.</t>
+	</div>
+</macro>
+
+<macro name="unsubscription_instructions_link" dot_parameter="text" requires="node_page">
+	<a href="[n.page_node.unsubscription_instructions_path/]"><n.text/></a>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/delete_node.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,93 @@
+<macro name="delete_post" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_delete.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_delete.page_node>
+
+		<n.page_node.delete_node_and_redirect/>
+	</n.node_page.>
+</macro>
+
+<macro name="delete_node_and_redirect" requires="node">
+	<n.set_var. name='next_url'>
+		<n.parent_path_or_homepage/>
+	</n.set_var.>
+	<n.delete_message_or_node/>
+	<n.redirect_to.var name='next_url'/>
+</macro>
+
+<macro name="delete_from_site" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_delete_recursively.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_delete_recursively.page_node>
+
+		<n.page_node.delete_node_recursively_and_redirect/>
+	</n.node_page.>
+</macro>
+
+<macro name="delete_node_recursively_and_redirect" requires="node">
+	<n.set_var. name='next_url'>
+		<n.parent_path_or_homepage/>
+	</n.set_var.>
+	<n.send_deleted_nodes_email/>
+	<n.delete_recursively/>
+	<n.redirect_to.var name='next_url'/>
+</macro>
+
+<macro name="parent_path_or_homepage" requires="node">
+	<n.if.has_parent>
+		<then><n.parent_node.path/></then>
+		<else><n.nabble_homepage/></else>
+	</n.if.has_parent>
+</macro>
+
+<macro name="send_deleted_nodes_email" requires="node">
+	<n.descendant_nodes_by_user.>
+		<n.loop.>
+			<n.if.current_user.is_registered>
+				<then>
+					<n.new_email.>
+						<n.nodes_list.attach_nodes/>
+						<n.send>
+							<to.current_user.user_email/>
+							<to_name.current_user.name/>
+							<subject><t>Deleted posts</t></subject>
+							<text_part.deleted_nodes_text/>
+							<bounce_to.current_user.bounces_address/>
+						</n.send>
+					</n.new_email.>
+				</then>
+			</n.if.current_user.is_registered>
+		</n.loop.>
+	</n.descendant_nodes_by_user.>
+</macro>
+
+<macro name="deleted_nodes_text" unindent="true">
+	<t>Dear user,</t>
+
+	<t>Some of your posts have been deleted from <t.location.root_node.subject/>
+	and we are sending you copies so that you have a chance to save them.</t>
+
+	<t>Sincerely,</t>
+	<t>The Nabble team</t>
+	________________________________________
+	<t>Free Embeddable <t.app.root_node.view_name/></t> powered by Nabble
+	<n.nabble_homepage/>
+</macro>
+
+<macro name="attach_nodes" requires="node_list,email">
+	<n.comment.>
+		Attach valid nodes as ZIP files in the email
+		(zip file contains message, images, files from the node)
+	</n.comment.>
+	<n.loop.>
+		<n.if.not.current_node.message.is_deleted>
+			<then.add_node_as_zip_attachment.current_node/>
+		</n.if.not.current_node.message.is_deleted>
+	</n.loop.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/doc.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,81 @@
+
+<macro name="doc not found" requires="doc">
+</macro>
+
+<macro name="doc_text" dot_parameter="text" requires="doc">
+	<div style="clear:both;margin:.5em 0 1em">
+		<n.text/>
+	</div>
+</macro>
+
+<macro name="binary doc" requires="binary_doc">
+	<div style="clear:both;margin:.5em 0 1em">
+		<n.value/>
+
+		<n.if.has_parameters>
+			<then>
+				<h3 style="margin-top:1em">
+					<t>Parameters</t>
+				</h3>
+
+				<style type="text/css">
+					table.doc-parameters {
+						cell-spacing: 0;
+						margin: 0 0 1em .5em;
+					}
+					table.doc-parameters td {
+						padding: .3em .4em;
+					}
+				</style>
+				<table class="doc-parameters">
+					<n.parameter_list.loop.>
+						<n.current_parameter.>
+							<tr>
+								<td class="bold"><n.name/></td>
+								<td class="weak-color">
+									<n.description/>
+								</td>
+								<td style="font-style:italic">
+									<n.if.is_dot_parameter>
+										<then>dot parameter</then>
+									</n.if.is_dot_parameter>
+									<n.if.is_optional>
+										<then>optional</then>
+									</n.if.is_optional>
+								</td>
+							</tr>
+						</n.current_parameter.>
+					</n.parameter_list.loop.>
+				</table>
+			</then>
+		</n.if.has_parameters>
+
+		<h3>Requires</h3>
+		<div style="margin:0 0 1em 1em"><n.requires/></div>
+
+		<n.if.adds_namespace>
+			<then>
+				<h3>Added Namespace</h3>
+				<div style="margin:0 0 1em 1em"><n.added_namespace/></div>
+			</then>
+		</n.if.adds_namespace>
+
+		<h3>Binary Details</h3>
+		<div style="margin:0 0 1em 1em"><n.binary_details/></div>
+
+		<h3>Other Binary Commands in <i><n.requires/></i></h3>
+		<div style="margin:0 0 1em 1em">
+			<n.related_commands.join. separator="&nbsp;&nbsp; ">
+				<n.current_command.>
+					<a href="[n.simple_command_path/]"><n.name/></a>
+				</n.current_command.>
+			</n.related_commands.join.>
+		</div>
+	</div>
+</macro>
+
+<macro name="simple_command_path" requires="command_info">
+	<n.macro_viewer_path>
+		<id><n.use_text_encoder.id/></id>
+	</n.macro_viewer_path>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/dropdown.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,885 @@
+<macro name="app_dropdown" requires="node">
+	<n.dropdown.
+		id="appdropdown"
+		text="[t]Options[/t]"
+		title="[t]Click for more options[/t]"
+		loadOnClick="/template/NamlServlet.jtp?macro=app_dropdown_later&node=[n.id/]"
+	>
+		<n.menu_subscription/>
+		<n.menu_post_by_email/>
+
+		<n.menu_separator/>
+
+		<n.menu_group. text="[t]Application[/t]">
+			<n.menu_edit_name_and_description/>
+			<n.menu_change_type/>
+			<n.menu_change_appearance/>
+			<n.menu_change_domain_name/>
+			<n.menu_mailing_list_archive_settings/>
+			<n.menu_change_language/>
+			<n.menu_change_title_and_meta_tags/>
+			<n.menu_extras_and_addons/>
+			<n.menu_use_google_analytics/>
+		</n.menu_group.>
+
+		<n.menu_group. text="[t]Structure[/t]">
+			<n.menu_create_sub_app/>
+			<n.menu_manage_pinned_topics/>
+			<n.menu_manage_sub_apps/>
+			<n.menu_parent_options/>
+		</n.menu_group.>
+
+		<n.menu_group. text="[t]Users[/t]">
+			<n.menu_change_permissions/>
+			<n.menu_manage_users_and_groups/>
+			<n.menu_manage_subscribers/>
+			<n.menu_manage_banned_users/>
+		</n.menu_group.>
+
+		<n.menu_separator/>
+
+		<n.menu_embedding_options/>
+		<n.menu_download_backup/>
+		<n.menu_delete/>
+
+		<n.other_menu_entries/>
+
+		<n.menu_sysadmin_options/>
+	</n.dropdown.>
+</macro>
+
+<macro name="app_dropdown_later" requires="servlet">
+	<n.javascript_response/>
+	<n.get_node_from_parameter.>
+		<n.menu_subscription/>
+		<n.menu_post_by_email/>
+
+		<n.menu_edit_name_and_description/>
+		<n.menu_change_type/>
+		<n.menu_change_appearance/>
+		<n.menu_change_domain_name/>
+		<n.menu_mailing_list_archive_settings/>
+		<n.menu_change_language/>
+		<n.menu_change_title_and_meta_tags/>
+		<n.menu_extras_and_addons/>
+		<n.menu_use_google_analytics/>
+
+		<n.menu_create_sub_app/>
+		<n.menu_manage_pinned_topics/>
+		<n.menu_manage_sub_apps/>
+		<n.menu_parent_options/>
+
+		<n.menu_change_permissions/>
+		<n.menu_manage_users_and_groups/>
+		<n.menu_manage_subscribers/>
+		<n.menu_manage_banned_users/>
+
+		<n.menu_embedding_options/>
+		<n.menu_download_backup/>
+		<n.menu_delete/>
+
+		<n.other_menu_entries/>
+
+		<n.menu_sysadmin_options/>
+	</n.get_node_from_parameter.>
+</macro>
+
+<macro name="topic_dropdown" requires="node">
+	<n.dropdown.
+		id="topicdropdown"
+		text="[t]Options[/t]"
+		title="[t]Click for more options[/t]"
+		loadOnClick="/template/NamlServlet.jtp?macro=topic_dropdown_later&node=[n.page_node.id/]"
+	>
+		<n.menu_topic_subscription/>
+		<n.menu_separator/>
+		<n.menu_move_topic/>
+		<n.menu_pin_topic/>
+		<n.menu_unpin_topic/>
+		<n.menu_lock_topic/>
+		<n.menu_unlock_topic/>
+		<n.menu_delete_topic/>
+		<n.menu_change_title_and_meta_tags/>
+		<n.menu_embed_post/>
+		<n.menu_permalink/>
+	</n.dropdown.>
+</macro>
+
+<macro name="topic_dropdown_later" requires="servlet">
+	<n.javascript_response/>
+	<n.get_node_from_parameter.>
+		<n.menu_topic_subscription/>
+		<n.menu_delete_topic/>
+		<n.menu_move_topic/>
+		<n.menu_pin_topic/>
+		<n.menu_unpin_topic/>
+		<n.menu_lock_topic/>
+		<n.menu_unlock_topic/>
+		<n.menu_change_title_and_meta_tags/>
+	</n.get_node_from_parameter.>
+</macro>
+
+<macro name="post_dropdown" requires="node">
+	<n.dropdown.
+		id="postdropdown[n.id/]"
+		text="[t]More[/t]"
+		title="[t]Click for more options[/t]"
+		loadOnClick="/template/NamlServlet.jtp?macro=post_dropdown_later&node=[n.id/]"
+	>
+		<n.menu_reply_to_author/>
+		<n.menu_edit_post/>
+		<n.menu_move_post/>
+		<n.menu_delete_post/>
+		<n.menu_change_post_date/>
+		<n.menu_print_post/>
+		<n.menu_permalink/>
+		<n.menu_raw_mail/>
+	</n.dropdown.>
+</macro>
+
+<macro name="post_dropdown_later" requires="servlet">
+	<n.javascript_response/>
+	<n.get_node_from_parameter.>
+		<n.menu_reply_to_author/>
+		<n.menu_edit_post/>
+		<n.menu_move_post/>
+		<n.menu_delete_post/>
+		<n.menu_change_post_date/>
+		<n.menu_raw_mail/>
+	</n.get_node_from_parameter.>
+</macro>
+
+<macro name="root_post_dropdown" requires="node">
+	<n.dropdown.
+		id="rootdropdown[n.id/]"
+		text="[t]Options[/t]"
+		title="[t]Click for more options[/t]"
+		loadOnClick="/template/NamlServlet.jtp?macro=root_post_dropdown_later&node=[n.id/]"
+	>
+		<n.menu_topic_subscription/>
+		<n.menu_reply_to_author/>
+		<n.menu_edit_post/>
+		<n.menu_move_post/>
+		<n.menu_delete_topic/>
+		<n.menu_pin_topic/>
+		<n.menu_unpin_topic/>
+		<n.menu_lock_topic/>
+		<n.menu_unlock_topic/>
+		<n.menu_change_post_date/>
+		<n.menu_change_title_and_meta_tags/>
+		<n.menu_embed_post/>
+		<n.menu_print_post/>
+		<n.menu_permalink/>
+		<n.menu_raw_mail/>
+	</n.dropdown.>
+</macro>
+
+<macro name="root_post_dropdown_later" requires="servlet">
+	<n.javascript_response/>
+	<n.get_node_from_parameter.>
+		<n.menu_topic_subscription/>
+		<n.menu_reply_to_author/>
+		<n.menu_edit_post/>
+		<n.menu_move_post/>
+		<n.menu_delete_topic/>
+		<n.menu_pin_topic/>
+		<n.menu_unpin_topic/>
+		<n.menu_lock_topic/>
+		<n.menu_unlock_topic/>
+		<n.menu_change_post_date/>
+		<n.menu_change_title_and_meta_tags/>
+		<n.menu_raw_mail/>
+	</n.get_node_from_parameter.>
+</macro>
+
+<macro name="menu_subscription" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('subscriptionLink', '<n.javascript_string_encode.subscribe_link/>');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.local_node.visitor_is_subscribed>
+				<then>
+					var text = '<b><t>Your subscription</t></b>: <n.javascript_string_encode.local_node.subject/> ';
+					text += '(<a href="[n.local_node.visitor_subscription.edit_path/]" ignore="y"><t>edit</t></a>)';
+					NabbleDropdown.replaceContents('subscriptionLink',text);
+				</then>
+			</n.if.local_node.visitor_is_subscribed>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_topic_subscription" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('topicSubscriptionLink', '<n.javascript_string_encode.subscribe_link/>');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.local_node.visitor_is_subscribed>
+				<then>
+					var text = '<b>You are subscribed</b> ';
+					text += '(<a href="[n.local_node.unsubscribe_path/]" ignore="y">unsubscribe</a>)';
+					NabbleDropdown.replaceContents('topicSubscriptionLink',text);
+				</then>
+			</n.if.local_node.visitor_is_subscribed>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_post_by_email" requires="node">
+	<n.if.not.is_associated_with_mailing_list_archive>
+		<then>
+			<n.if.is_in_command name="dropdown">
+				<then>
+					dropdown.add('postByEmail', '<n.javascript_string_encode.post_by_email_link/>', 'display:none');
+				</then>
+				<else>
+					<n.set_local_node.this_node/>
+					<n.if.visitor.can_create_topic_in.local_node>
+						<then>
+							NabbleDropdown.show('postByEmail');
+						</then>
+					</n.if.visitor.can_create_topic_in.local_node>
+				</else>
+			</n.if.is_in_command>
+		</then>
+	</n.if.not.is_associated_with_mailing_list_archive>
+</macro>
+
+<macro name="menu_edit_name_and_description" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('editNameDescription', '<n.javascript_string_encode.edit_subject_and_message_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_edit.local_node>
+				<then>
+					NabbleDropdown.show('editNameDescription');
+				</then>
+			</n.if.visitor.can_edit.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_change_appearance" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('changeAppearance', '<n.javascript_string_encode.change_appearance_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.local_node.is_root/]" condition2="[n.visitor.can_edit.local_node/]">
+				<then>
+					function hasChangeAppearanceToolbar() {
+						try {
+							if (parent.isChangeAppearance)
+								return true
+						} catch (err) {}
+						return false;
+					};
+					/* Avoid duplicate toolbars */
+					if (!hasChangeAppearanceToolbar())
+						NabbleDropdown.show('changeAppearance');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_change_language" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('changeLanguage', '<n.javascript_string_encode.change_language_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.local_node.is_root/]" condition2="[n.visitor.can_edit.local_node/]">
+				<then>
+					NabbleDropdown.show('changeLanguage');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_extras_and_addons" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('addons', '<n.javascript_string_encode.extras_and_addons_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.local_node.is_root/]" condition2="[n.visitor.can_edit.local_node/]">
+				<then>
+					NabbleDropdown.show('addons');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_change_title_and_meta_tags" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('changeMetaTags', '<n.javascript_string_encode.change_title_and_meta_tags_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.is_site_admin>
+				<then>
+					NabbleDropdown.show('changeMetaTags');
+				</then>
+			</n.if.visitor.is_site_admin>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_change_type" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('changeType', '<n.javascript_string_encode.change_type_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_edit.local_node>
+				<then>
+					NabbleDropdown.show('changeType');
+				</then>
+			</n.if.visitor.can_edit.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_change_domain_name" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('changeDomainName', '<n.javascript_string_encode.change_domain_name_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.visitor.can_edit.local_node/]" condition2="[n.local_node.is_root/]">
+				<then>
+					NabbleDropdown.show('changeDomainName');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_use_google_analytics" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('useGoogleAnalytics', '<n.javascript_string_encode.use_google_analytics_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.visitor.can_edit.local_node/]" condition2="[n.local_node.is_root/]">
+				<then>
+					NabbleDropdown.show('useGoogleAnalytics');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_create_sub_app" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('createChildForum', '<n.javascript_string_encode.create_sub_app_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_create_sub_apps_under.local_node>
+				<then>
+					NabbleDropdown.show('createChildForum');
+				</then>
+			</n.if.visitor.can_create_sub_apps_under.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_manage_pinned_topics" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('managePinnedTopics', '<n.javascript_string_encode.manage_pinned_topics_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if>
+				<condition>
+					<n.both>
+						<condition1.not.regex_matches text="[n.local_node.type/]" pattern="board|category" />
+						<condition2.visitor.can_manage_pinned_topics_in.local_node/>
+					</n.both>
+				</condition>
+				<then>
+					NabbleDropdown.show('managePinnedTopics');
+				</then>
+			</n.if>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_manage_sub_apps" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('manageSubApps', '<n.javascript_string_encode.manage_sub_apps_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_edit.local_node>
+				<then>
+					NabbleDropdown.show('manageSubApps');
+				</then>
+			</n.if.visitor.can_edit.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_parent_options" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('parentOptions', '<n.javascript_string_encode.parent_options_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_move.local_node>
+				<then>
+					NabbleDropdown.show('parentOptions');
+				</then>
+			</n.if.visitor.can_move.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_change_permissions" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('changePermissions', '<n.javascript_string_encode.change_permissions_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_change_permissions_of.local_node>
+				<then>
+					NabbleDropdown.show('changePermissions');
+				</then>
+			</n.if.visitor.can_change_permissions_of.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_manage_users_and_groups" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('manageGroups', '<n.javascript_string_encode.manage_users_and_groups_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.local_node.is_root/]" condition2="[n.visitor.can_manage_users_and_groups/]">
+				<then>
+					NabbleDropdown.show('manageGroups');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_manage_subscribers" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('manageSubscribers', '<n.javascript_string_encode.manage_subscribers_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_manage_subscribers_of.local_node>
+				<then>
+					NabbleDropdown.show('manageSubscribers');
+				</then>
+			</n.if.visitor.can_manage_subscribers_of.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_manage_banned_users" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('manageBannedUsers', '<n.javascript_string_encode.manage_banned_users_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.local_node.is_root/]" condition2="[n.visitor.can_manage_banned_users/]">
+				<then>
+					NabbleDropdown.show('manageBannedUsers');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_mailing_list_archive_settings" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('mailingListSettings', '<n.javascript_string_encode.mailing_list_archive_settings_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if>
+				<condition>
+					<n.both>
+						<condition1>
+							<n.visitor.can_edit.local_node/>
+						</condition1>
+						<condition2>
+							<n.either>
+								<condition1><n.local_node.is_a_mailing_list_archive/></condition1>
+								<condition2><n.not.local_node.has_sub_archive/></condition2>
+							</n.either>
+						</condition2>
+					</n.both>
+				</condition>
+				<then>
+					NabbleDropdown.show('mailingListSettings');
+				</then>
+			</n.if>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_embedding_options" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('embeddingOptions', '<n.javascript_string_encode.embedding_options_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_edit.local_node>
+				<then>
+					NabbleDropdown.show('embeddingOptions');
+				</then>
+			</n.if.visitor.can_edit.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_download_backup" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('downloadBackup', '<n.javascript_string_encode.download_backup_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.local_node.is_root/]" condition2="[n.visitor.is_site_admin/]">
+				<then>
+					NabbleDropdown.show('downloadBackup');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_delete" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('deleteApp', '<n.javascript_string_encode.delete_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_edit.local_node>
+				<then>
+					NabbleDropdown.show('deleteApp');
+				</then>
+			</n.if.visitor.can_edit.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_delete_topic" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('deleteRecursively', '<n.javascript_string_encode.delete_recursively_link text="[t]Delete this topic[/t]"/>', 'display:none');
+			dropdown.add('deletePost', '<n.javascript_string_encode.delete_post_link text="[t]Delete this topic[/t]"/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_delete_recursively.local_node>
+				<then>
+					NabbleDropdown.show('deleteRecursively');
+				</then>
+				<else.if.visitor.can_delete.local_node>
+					<then>
+						NabbleDropdown.show('deletePost');
+					</then>
+				</else.if.visitor.can_delete.local_node>
+			</n.if.visitor.can_delete_recursively.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_delete_post" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('deletePost<n.id/>', '<n.javascript_string_encode.delete_post_link/>', 'display:none');
+			dropdown.add('deleteRecursively<n.id/>', '<n.javascript_string_encode.delete_recursively_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_delete.local_node>
+				<then>
+					NabbleDropdown.show('deletePost<n.local_node.id/>');
+				</then>
+			</n.if.visitor.can_delete.local_node>
+			<n.if.visitor.can_delete_recursively.local_node>
+				<then>
+					NabbleDropdown.show('deleteRecursively<n.local_node.id/>');
+				</then>
+			</n.if.visitor.can_delete_recursively.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_edit_post" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('editPost<n.id/>', '<n.javascript_string_encode.edit_post_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_edit.local_node>
+				<then>
+					NabbleDropdown.show('editPost<n.local_node.id/>');
+				</then>
+			</n.if.visitor.can_edit.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_move_post" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('movePost<n.id/>', '<n.javascript_string_encode.move_post_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_move.local_node>
+				<then>
+					NabbleDropdown.show('movePost<n.local_node.id/>');
+				</then>
+			</n.if.visitor.can_move.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_move_topic" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('moveTopic', '<n.javascript_string_encode.move_post_link text="[t]Move topic[/t]"/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_move.local_node>
+				<then>
+					NabbleDropdown.show('moveTopic');
+				</then>
+			</n.if.visitor.can_move.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_pin_topic" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('pinTopic', '<n.javascript_string_encode.pin_topic_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.not.local_node.is_pinned/]" condition2="[n.visitor.can_manage_pinned_topics_in.local_node/]">
+				<then>
+					NabbleDropdown.show('pinTopic');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_unpin_topic" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('unpinTopic', '<n.javascript_string_encode.unpin_topic_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.local_node.is_pinned/]" condition2="[n.visitor.can_manage_pinned_topics_in.local_node/]">
+				<then>
+					NabbleDropdown.show('unpinTopic');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_lock_topic" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('lockTopic', '<n.javascript_string_encode.lock_topic_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.not.local_node.is_locked_topic/]" condition2="[n.visitor.can_manage_locked_topics_in.local_node/]">
+				<then>
+					NabbleDropdown.show('lockTopic');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_unlock_topic" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('unlockTopic', '<n.javascript_string_encode.unlock_topic_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.both condition1="[n.local_node.is_locked_topic/]" condition2="[n.visitor.can_manage_locked_topics_in.local_node/]">
+				<then>
+					NabbleDropdown.show('unlockTopic');
+				</then>
+			</n.if.both>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_change_post_date" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('changePostDate<n.id/>', '<n.javascript_string_encode.change_post_date_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.can_change_post_date_of.local_node>
+				<then>
+					NabbleDropdown.show('changePostDate<n.local_node.id/>');
+				</then>
+			</n.if.visitor.can_change_post_date_of.local_node>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_reply_to_author" requires="node">
+	<n.if.owner.is_authenticated>
+		<then>
+			<n.if.is_in_command name="dropdown">
+				<then>
+					dropdown.add('replyToAuthor<n.id/>', '<n.javascript_string_encode.reply_to_author_link/>', 'display:none');
+				</then>
+				<else>
+					<n.set_local_node.this_node/>
+					<n.if.not.visitor.equals.local_node.owner>
+						<then>
+							NabbleDropdown.show('replyToAuthor<n.local_node.id/>');
+						</then>
+					</n.if.not.visitor.equals.local_node.owner>
+				</else>
+			</n.if.is_in_command>
+		</then>
+	</n.if.owner.is_authenticated>
+</macro>
+
+<macro name="menu_embed_post" requires="node">
+	dropdown.add('embedPost<n.id/>', '<n.javascript_string_encode.embed_post_link/>');
+</macro>
+
+<macro name="menu_print_post" requires="node">
+	dropdown.add('print<n.id/>', '<n.javascript_string_encode.print_post_link/>');
+</macro>
+
+<macro name="menu_permalink" requires="node">
+	dropdown.add('permalink<n.id/>', '<n.javascript_string_encode.permalink/>');
+</macro>
+
+<macro name="menu_raw_mail" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('rawMail<n.id/>', '<n.javascript_string_encode.raw_mail_link/>', 'display:none');
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.local_node.message.is_imported_mail>
+				<then.if.either condition1="[n.visitor.can_edit.local_node/]" condition2="[n.visitor.is_sysadmin/]">
+					<then>
+						NabbleDropdown.show('rawMail<n.local_node.id/>');
+					</then>
+				</then.if.either>
+			</n.if.local_node.message.is_imported_mail>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_sysadmin_options" requires="node">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('openShell', null, 'display:none');
+			<n.if.is_a_mailing_list_archive>
+				<then>
+					dropdown.add('uploadMbox', null, 'display:none');
+					dropdown.add('deleteRecursively', null, 'display:none');
+				</then>
+			</n.if.is_a_mailing_list_archive>
+		</then>
+		<else>
+			<n.set_local_node.this_node/>
+			<n.if.visitor.is_sysadmin>
+				<then>
+					NabbleDropdown.addContents('openShell','<n.javascript_string_encode.local_node.shell_link/>');
+					<n.if.local_node.is_a_mailing_list_archive>
+						<then>
+							NabbleDropdown.addContents('uploadMbox','<n.javascript_string_encode.local_node.upload_mbox_link/>');
+						</then>
+					</n.if.local_node.is_a_mailing_list_archive>
+				</then>
+			</n.if.visitor.is_sysadmin>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="shell_link" requires="node">
+	<a href="/tools/Shell.jtp?cmd=node+=+ModelHome.getNode([n.id/])" target="_new"><span class="important">Open in shell</span></a>
+</macro>
+
+<macro name="upload_mbox_link" requires="node">
+	<a href="/tools/UploadMbox.jtp?forum=[n.id/]"><span class="important">Upload Mbox file</span></a>
+</macro>
+
+		------------- Dropdown Implementation -------------
+
+<macro name="dropdown" dot_parameter="options" parameters="id, text, title, element, loadOnClick">
+	<span id="dd_[n.id/]"></span>
+	<script type="text/javascript">
+		var dropdown = new NabbleDropdown("<n.id/>", "<n.javascript_string_encode.hide_null.text/>","<n.javascript_string_encode.hide_null.title/>");
+		<n.if.not.is_null.element>
+			<then>dropdown.customButton("<n.javascript_string_encode.element/>");</then>
+		</n.if.not.is_null.element>
+		<n.options/>
+		dropdown.build('dd_<n.id/>');
+		<n.if.not.is_empty.loadOnClick>
+			<then>dropdown.loadOnClick('<n.loadOnClick/>&_=' + Math.floor(Math.random()*999999));</then>
+		</n.if.not.is_empty.loadOnClick>
+	</script>
+</macro>
+
+<macro name="menu_group" dot_parameter="options" parameters="text">
+	dropdown.startGroup('<n.javascript_string_encode.text/>');
+	<n.options/>
+	dropdown.endGroup();
+</macro>
+
+<macro name="menu_separator">
+	dropdown.addSeparator();
+</macro>
+
+
+<macro name="other_menu_entries" requires="node">
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/edit_app.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,92 @@
+<macro name="edit_app" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_edit.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_edit.page_node>
+		<n.if.not.is_submitted_form>
+			<then>
+				<n.subject_field.set_value value="[n.page_node.raw_subject/]" />
+				<n.html_format_field.set_value value="[n.page_node.message.is_html_format/]" />
+				<n.message_field.set_value value="[n.page_node.message.as_editable/]" />
+			</then>
+			<else>
+				<n.catch_exception. id="save-block">
+					<n.edit_page_node.>
+						<n.set_subject subject="[n.subject_field.value/]" />
+						<n.set_message message="[n.message_field.value/]" is_html="[n.not.is_null.html_format_field.value/]" />
+						<n.save_node/>
+					</n.edit_page_node.>
+					<n.redirect_to.page_node.path/>
+				</n.catch_exception.>
+			</else>
+		</n.if.not.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Edit Name & Description</t></n.title.>
+				<style type="text/css">
+					.title-row {
+						padding:.2em;
+						border-width:2px;
+						border-style:solid;
+						font-weight:bold;
+					}
+					div.field-title {
+						margin-top: 0;
+					}
+				</style>
+			</head>
+			<body>
+				<n.edit_header first_text="[t]Edit Name & Description[/t]" second_text="[n.page_node.get_app_node.subject/]" />
+				<n.if.is_submitted_form>
+					<then>
+						<n.show_edit_post_error/>
+					</then>
+				</n.if.is_submitted_form>
+				<n.form.>
+					<n.if.not.visitor.is_registered>
+						<then>
+							<div class="field-box light-border-color">
+								<div class="second-font field-title"><t>Your Name</t></div>
+								<div class="weak-color">
+									<n.page_node.owner.name/>
+								</div>
+							</div>
+						</then>
+					</n.if.not.visitor.is_registered>
+	
+					<div class="field-box light-border-color">
+						<div class="second-font field-title"><t>Name</t></div>
+						<div class="weak-color">
+							<n.subject_field.input size="60" />
+						</div>
+					</div>
+	
+					<div class="field-box light-border-color">
+						<div class="second-font field-title"><t>Description</t></div>
+						<div class="weak-color">
+							<n.if.visitor.is_registered>
+								<then>
+									<n.html_format_field.checkbox />
+									<label for="[n.html_format_field.name/]"><t>Description is in HTML Format</t></label><br/>
+									<div style="margin:.1em 0">
+										<n.editor_toolbar
+											textarea_id="[n.message_field.name/]"
+											node_id="[n.page_node.id/]"
+										/>
+									</div>
+								</then>
+							</n.if.visitor.is_registered>
+							<n.message_field.textarea wrap="SOFT" style="min-width:30em;max-width:55em;width:100%;height:20em;" />
+						</div>
+					</div>
+	
+					<input type="submit" value="[t]Save Changes[/t]" />
+					<t>or</t> <a href="[n.page_node.path/]"><t>Cancel</t></a>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/edit_post.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,218 @@
+<macro name="edit_post" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_edit.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_edit.page_node>
+		<n.if.not.is_submitted_form>
+			<then>
+				<n.subject_field.set_value value="[n.page_node.raw_subject/]" />
+				<n.html_format_field.set_value value="[n.page_node.message.is_html_format/]" />
+				<n.message_field.set_value value="[n.page_node.message.as_editable/]" />
+				<n.alert_field.set_value value="[n.page_node.visitor_is_subscribed_to_topic/]" />
+				<n.page_node.init_edit_post_custom_fields/>
+			</then>
+			<else>
+				<n.catch_exception. id="save-block">
+					<n.edit_page_node. commit="[n.not.is_preview/]">
+						<n.set_subject subject="[n.subject_field.value/]" />
+						<n.set_message message="[n.message_field.value/]" is_html="[n.not.is_null.html_format_field.value/]" />
+						<n.if.not.is_preview>
+							<then.save_post />
+						</n.if.not.is_preview>
+					</n.edit_page_node.>
+					<n.if.not.is_preview>
+						<then>
+							<n.page_node.save_alert_field/>
+							<n.page_node.save_edit_post_custom_fields/>
+							<n.redirect_to.page_node.path/>
+						</then>
+					</n.if.not.is_preview>
+				</n.catch_exception.>
+			</else>
+		</n.if.not.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Edit Post</t></n.title.>
+				<style type="text/css">
+					.title-row {
+						padding:.2em;
+						border-width:2px;
+						border-style:solid;
+						font-weight:bold;
+					}
+					div.field-title {
+						margin-top: 0;
+					}
+				</style>
+			</head>
+			<body>
+				<n.edit_header first_text="[t]Edit Post[/t]" second_text="[n.page_node.get_app_node.subject/]" />
+				<n.if.is_submitted_form>
+					<then>
+						<n.if.is_preview>
+							<then>
+								<n.page_node.preview/>
+							</then>
+						</n.if.is_preview>
+						<n.show_edit_post_error/>
+					</then>
+				</n.if.is_submitted_form>
+				<n.form.>
+					<n.edit_post_form />
+
+					<div style="margin-top:1em">
+						<input type="submit" class="toolbar action-button" value="[t]Post Message[/t]" />
+						<input type="submit" class="toolbar action-button" name="preview" value="[t]Preview Message[/t]" />
+						<t>or</t> <a href="[n.page_node.path/]"><t>Cancel</t></a>
+					</div>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="save_post" requires="node_editor,servlet">
+	<n.save_node />
+</macro>
+
+<macro name="edit_post_form">
+	<n.if.page_node.is_mail_to_list>
+		<then>
+			<div class="title-row info-message" style="font-weight:normal;padding:.3em .5em">
+				<div class="important" style="font-weight:bold">
+					<t>Your changes will not be sent to the mailing list.</t>
+				</div>
+				<t>If you want others in the mailing list to know of your changes,
+					please compose a new message or reply to your original message.</t>
+			</div>
+		</then>
+	</n.if.page_node.is_mail_to_list>
+
+	<n.if.page_node.message.is_imported_mail>
+		<then>
+			<div class="info-message" style="font-weight:bold;padding:.3em .5em">                
+				<t>The post you are editing was originally imported by email. If this post has attachments, those files will be lost after you save your changes. You may download the attachments and re-upload them to your edited post.</t>
+			</div>
+			</then>
+	</n.if.page_node.message.is_imported_mail>
+	
+	<n.if.not.visitor.is_registered>
+		<then>
+			<div class="field-box light-border-color">
+				<div class="second-font field-title"><t>Your Name</t></div>
+				<div class="weak-color">
+					<n.page_node.owner.name/>
+				</div>
+			</div>
+		</then>
+	</n.if.not.visitor.is_registered>
+
+	<div class="field-box light-border-color">
+		<div class="second-font field-title"><t>Subject</t></div>
+		<div class="weak-color">
+			<n.subject_field.input size="60" tabindex="1" />
+		</div>
+	</div>
+
+	<div class="field-box light-border-color">
+		<div class="second-font field-title"><t>Message</t></div>
+		<div class="weak-color">
+			<n.if.visitor.is_registered>
+				<then>
+					<n.html_format_field.checkbox />
+					<label for="[n.html_format_field.name/]"><t>Message is in HTML Format</t></label><br/>
+					<div style="margin:.1em 0">
+						<n.editor_toolbar
+							textarea_id="[n.message_field.name/]"
+							original_text="[n.if.page_node.parent_node.is_post][then.page_node.parent_node.message_quoted/][/n.if.page_node.parent_node.is_post]"
+							node_id="[n.page_node.id/]"
+						/>
+					</div>
+				</then>
+			</n.if.visitor.is_registered>
+			<n.message_field.textarea wrap="SOFT" tabindex="2" style="min-width:30em;max-width:55em;width:100%;height:20em;" />
+
+			<n.edit_post_extra_fields/>
+		</div>
+	</div>
+</macro>
+
+<macro name="edit_post_extra_fields" requires="node_page">
+	<n.extra_fields.>
+		<n.if.visitor.is_registered>
+			<then>
+				<div class="extra-fields">
+					<n.alert_field.checkbox style="margin-top:.1em"/>
+					<label for="[n.alert_field.name/]"><t>Alert me by email when someone posts to this thread</t></label>
+				</div>
+			</then>
+		</n.if.visitor.is_registered>
+	</n.extra_fields.>
+</macro>
+
+<macro name="is_preview">
+	<n.if.not.global_is_var_set name="is_preview">
+		<then>
+			<n.global_set_var. name="is_preview"><n.not.is_null.get_parameter name="preview" /></n.global_set_var.>
+		</then>
+	</n.if.not.global_is_var_set>
+	<n.global_var name="is_preview" />
+</macro>
+
+<macro name="subject_field" dot_parameter="do">
+	<n.field. name="subject"><n.do/></n.field.>
+</macro>
+
+<macro name="html_format_field" dot_parameter="do">
+	<n.field. name="html_format"><n.do/></n.field.>
+</macro>
+
+<macro name="message_field" dot_parameter="do">
+	<n.field. name="message"><n.do/></n.field.>
+</macro>
+
+<macro name="alert_field" dot_parameter="do">
+	<n.field. name="alert"><n.do/></n.field.>
+</macro>
+
+<macro name="show_edit_post_error">
+	<n.format_error.handle_exception. for="save-block">
+		<n.exception. name="required_subject">
+			<t>The subject is required.</t>
+		</n.exception.>
+		<n.spam_errors/>
+		<n.custom_edit_post_errors/>
+	</n.format_error.handle_exception.>
+</macro>
+
+<macro name="custom_edit_post_errors">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="preview" requires="node">
+	<div class="title-row light-border-color shaded-bg-color" style="margin-top:.5em"><t>Message Preview</t></div>
+	<div class="light-border-color" style="padding-left:.7em;border-width:2px;border-style:solid">
+		<div class="field-box" style="width:100%;border:none">
+			<table style="border-collapse:collapse;margin-bottom:1em">
+				<tr>
+					<td><n.owner.avatar/></td>
+					<td>&nbsp;<a href="[n.owner.url/]"><n.owner.name/></a> &ndash; </td>
+					<td><b><n.subject/></b></td>
+					<td class="weak-color"><t>on <t.date.when_created.long_format/></t></td>
+				</tr>
+			</table>
+			<n.message_text/>
+		</div>
+	</div>
+</macro>
+
+<macro name="init_edit_post_custom_fields" requires="node_page">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="save_edit_post_custom_fields" requires="node_page">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/edit_profile.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,103 @@
+<macro name="edit_profile" requires="servlet">
+	<n.user_page.>
+		<n.if.not.visitor.is_registered>
+			<then>
+				<n.login.><t>You must login to view this page.</t></n.login.>
+			</then>
+		</n.if.not.visitor.is_registered>
+
+		<n.if.both condition1="[n.not.visitor.is_site_admin/]" condition2="[n.not.page_user.equals.visitor/]">
+			<then>
+				<n.login.><t>You must login to view this page.</t></n.login.>
+			</then>
+		</n.if.both>
+
+		<n.if.is_submitted_form>
+			<then>
+				<n.catch_exception. id="save-profile">
+					<n.edit_page_user.>
+						<n.set_name name="[n.user_name_field.value/]"/>
+						<n.set_password password1="[n.password_field.value/]" password2="[n.password2_field.value/]"/>
+						<n.save_user/>
+					</n.edit_page_user.>
+					<n.if.page_user.equals.visitor>
+						<then.profile_update_with_redirection_to.page_user.url/>
+						<else.redirect_to.page_user.url/>
+					</n.if.page_user.equals.visitor>
+				</n.catch_exception.>
+			</then>
+			<else>
+				<n.user_name_field.set_value value="[n.page_user.name/]" />
+			</else>
+		</n.if.is_submitted_form>
+
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Edit Profile</t></n.title.>
+			</head>
+			<body>
+				<style>
+					div.field-title {margin-top:0}
+				</style>
+				<h1><t>Edit Profile</t></h1>
+
+				<div class="field-box light-border-color">
+					<div class="second-font field-title">Email</div>
+					<div class="weak-color">
+						<n.page_user.user_email/>
+						&#187; <a href="[n.page_user.change_email_path/]">Change Email</a>
+					</div>
+				</div>
+
+				<n.form.>
+					<div class="field-box light-border-color" id="username-field">
+						<div class="second-font field-title">User Name</div>
+						<div class="weak-color">
+							Your user name must be unique in <n.root_node.subject/>
+						</div>
+						<div>
+							<n.user_name_field.input size="25" maxlength="25"/>
+						</div>
+					</div>
+
+					<div class="field-box light-border-color">
+						<div class="second-font field-title">Change Password</div>
+						<div class="weak-color">Nabble encrypts your password (<a href="[n.help.password/]">?</a>)</div>
+						<table style="margin:.4em 0" class="shaded-bg-color">
+							<tr valign="top">
+								<td class="form-label" style="padding-top:.6em">Password:&nbsp;</td>
+								<td><n.password_field.input type="password" size="25" maxlength="25"/></td>
+							</tr>
+							<tr>
+								<td class="form-label">Confirm Password:&nbsp;</td>
+								<td><n.password2_field.input type="password" size="25" maxlength="25"/></td>
+							</tr>
+						</table>
+					</div>
+
+					<input type="submit" class="toolbar action-button" value="[t]Save Changes[/t]"/>
+					<t>or</t> <a href="[n.page_user.url/]"><t>Cancel</t></a>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.user_page.>
+</macro>
+
+<macro name="show_edit_profile_error">
+	<n.if.is_submitted_form>
+		<then.if.has_exception for="save-profile">
+			<then.format_error.handle_exception. for="save-profile">
+				<n.exception. name="empty_user_name">
+					<t>User name cannot be empty.</t>
+				</n.exception.>
+				<n.exception. name="passwords_dont_match">
+					<t>The password fields don't match.</t>
+				</n.exception.>
+				<n.exception. name="user_name_already_in_use">
+					<t>This user name is already in use.</t>
+				</n.exception.>
+			</then.format_error.handle_exception.>
+		</then.if.has_exception>
+	</n.if.is_submitted_form>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/edit_signature.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,88 @@
+<macro name="edit_signature" requires="servlet">
+	<n.visitor.as_user_page.>
+		<n.if.not.page_user.is_registered>
+			<then>
+				<n.login.><t>You must login to view this page.</t></n.login.>
+			</then>
+		</n.if.not.page_user.is_registered>
+		<n.if.is_submitted_form>
+			<then>
+				<n.catch_exception. id="save-signature">
+					<n.edit_page_user.>
+						<n.set_signature signature="[n.signature_field.value/]" is_html="[n.html_format_field.value/]"/>
+						<n.save_user/>
+					</n.edit_page_user.>
+					<n.redirect_to.page_user.user_profile_path/>
+				</n.catch_exception.>
+			</then>
+			<else.if.page_user.has_signature>
+				<then>
+					<n.html_format_field.set_value value="[n.page_user.signature.is_html_format/]" />
+					<n.signature_field.set_value value="[n.page_user.signature.as_editable/]" />
+				</then>
+			</else.if.page_user.has_signature>
+		</n.if.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Edit Signature</t></n.title.>
+			</head>
+			<body>
+				<h1><t>Edit Signature</t></h1>
+
+				<n.show_edit_signature_error/>
+
+				<n.if.page_user.has_signature>
+					<then.show_current_signature/>
+				</n.if.page_user.has_signature>
+
+				<n.form.>
+					<n.html_format_field.checkbox />
+					<label for="[n.html_format_field.name/]"><t>Signature is in HTML format</t></label><br/>
+					<n.signature_field.textarea wrap="SOFT" tabindex="2" style="width:35em;height:7em;" />
+
+					<div style="margin-top:1.4em">
+						<input type="submit" value="[t]Save Signature[/t]" />
+						<t>or</t> <a href="[n.page_user.user_profile_path/]"><t>Cancel</t></a>
+					</div>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.visitor.as_user_page.>
+</macro>
+
+<macro name="show_current_signature">
+	<div class="medium-border-color border1 rounded" style="width:34em;padding:.5em;margin:2em 0">
+		<div class="second-font field-title" style="margin-top:0">
+			<t>Current Signature</t>
+		</div>
+		<div class="weak-color" style="margin-left:1em">
+			<n.page_user.signature_as_html/>
+		</div>
+	</div>
+</macro>
+
+<macro name="signature_field" dot_parameter="do">
+	<n.field. name="signature"><n.do/></n.field.>
+</macro>
+
+<macro name="edit_signature_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=edit_signature
+	</n.encode_url.>
+</macro>
+
+
+<macro name="signature_as_html" requires="user">
+	<n.signature.message_as_html />
+</macro>
+
+<macro name="show_edit_signature_error">
+	<n.if.is_submitted_form>
+		<then.if.has_exception for="save-signature">
+			<then.format_error.handle_exception. for="save-signature">
+				<n.spam_errors/>
+			</then.format_error.handle_exception.>
+		</then.if.has_exception>
+	</n.if.is_submitted_form>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/email.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,407 @@
+<macro name="notify_subscribers" requires="node">
+	<n.get_instant_emails.new_node.as_node_page.instant_emails />
+</macro>
+
+<macro name="instant_emails" requires="instant_mail,node_page">
+	<n.subscription_list.loop.>
+		<n.if.current_subscription.user.should_get_instant_mail>
+			<then.current_subscription.send_instant_email />
+		</n.if.current_subscription.user.should_get_instant_mail>
+	</n.subscription_list.loop.>
+</macro>
+
+<macro name="should_get_instant_mail" requires="user,node_page" >
+	<n.this_user.can_view.page_node />
+</macro>
+
+<macro name="send_instant_email" requires="subscription,node_page">
+	<n.set_local_subscription.this_subscription/>
+	<n.send_subscription_email
+		node_attr = "[n.page_node/]"
+		text_part = "[n.local_subscription.instant_text/]"
+		html_part = "[n.local_subscription.instant_html/]"
+	/>
+</macro>
+
+<macro name="send_subscription_email" parameters="node_attr,text_part,html_part" requires="subscription">
+	<n.set_local_subscription.this_subscription/>
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.new_email.>
+			<n.send>
+				<to.local_subscription.user.user_email/>
+				<to_name.local_subscription.user.name/>
+				<from.local_subscription.user.reply_address_for.local_node/>
+				<from_name><n.local_node.owner.name/> [via <n.root_node.subject/>]</from_name>
+				<subject.local_node.subject/>
+				<text_part.text_part/>
+				<html_part.html_part/>
+				<set_headers_for.local_node/>
+				<bounce_to.local_subscription.user.bounces_address/>
+			</n.send>
+		</n.new_email.>
+	</n.block.>
+</macro>
+
+<macro name="send_assign_email" requires="node_page">
+	<n.if.topic_assignee.can_view.page_node>
+		<then.new_email.>
+			<n.send>
+				<to.topic_assignee.user_email/>
+				<to_name.topic_assignee.name/>
+				<from.topic_assignee.reply_address_for.page_node/>
+				<from_name><n.page_node.owner.name/> [via <n.root_node.subject/>]</from_name>
+				<subject.page_node.subject/>
+				<text_part.assign_text/>
+				<html_part.assign_html/>
+				<set_headers_for.page_node/>
+				<bounce_to.topic_assignee.bounces_address/>
+			</n.send>
+		</then.new_email.>
+	</n.if.topic_assignee.can_view.page_node>
+</macro>
+
+<macro name="digest email" requires="subscription,node_list">
+	<n.set_local_subscription.this_subscription/>
+	<n.filter_by.local_subscription.user.can_view.current_node/>
+	<n.new_email.>
+		<n.send>
+			<to.local_subscription.user.user_email/>
+			<to_name.local_subscription.user.name/>
+			<from_name.root_node.subject/>
+			<subject><n.local_subscription.node.subject/> - <t>Digest Email</t></subject>
+			<text_part.local_subscription.digest_text/>
+			<html_part.local_subscription.digest_html/>
+			<bounce_to.local_subscription.user.bounces_address/>
+		</n.send>
+	</n.new_email.>
+</macro>
+
+<macro name="instant_text" requires="subscription,node_page" unindent="true">
+	<n.if.page_node.is_app>
+		<then>
+			<t><t.username.page_node.owner.name/> created a new subcategory</t>:
+			<n.page_node.subject/>
+		</then>
+	</n.if.page_node.is_app>
+
+	<n.page_node.text_email_message_with_signature />
+	_______________________________________________
+	<t>If you reply to this email, your message will be added to the discussion below</t>:
+	<n.page_node.url/>
+	<n.start_new_topic_line/>
+	<n.unsubscribe_line format="text"/>
+</macro>
+
+<macro name="instant_html" requires="subscription,node_page">
+	<n.if.page_node.is_app>
+		<then>
+			<t><t.username.page_node.owner.name/> created a new subcategory</t>:
+			<b><n.page_node.subject/></b><br/><br/>
+		</then>
+	</n.if.page_node.is_app>
+
+	<n.page_node.html_email_message_with_signature/>
+	<br/>
+	<br/>
+	<hr noshade="noshade" size="1" color="#cccccc" />
+	<div style="color:#444; font: 12px tahoma,geneva,helvetica,arial,sans-serif;">
+		<div style="font-weight:bold"><t>If you reply to this email, your message will be added to the discussion below</t>:</div>
+		<a href="[n.page_node.url/]"><n.page_node.url/></a>
+	</div>
+	<div style="color:#666; font: 11px tahoma,geneva,helvetica,arial,sans-serif;margin-top:.4em;line-height:1.5em">
+		<n.start_new_topic_line append="[br/]"/>
+		<n.unsubscribe_line format="html"/><br/>
+		<n.macro_viewer_email_link macro="instant_html"/>
+	</div>
+</macro>
+
+<macro name="assign_text" requires="node_page" unindent="true">
+	<n.page_node.text_email_message_with_signature/>
+	______________________________________
+	<t>This topic is assigned to you at priority <t.priority.topic_priority/></t>
+	<t>View message</t> @ <n.page_node.url/>
+</macro>
+
+<macro name="assign_html" requires="node_page">
+	<n.page_node.html_email_message_with_signature/>
+	<br/>
+	<br/>
+	<hr noshade="noshade" size="1" color="#cccccc" />
+	<div style="color:#666666;font: 11px tahoma,geneva,helvetica,arial,sans-serif;margin-bottom:1.5em;line-height:1.5em">
+		<t>This topic is assigned to you at priority <t.priority><div style="[n.priority_style/]"><n.topic_priority/></div></t.priority></t><br/>
+		<t>View message</t> @ <a href="[n.page_node.url/]"><n.page_node.url/></a><br/>
+		<n.macro_viewer_email_link macro="assign_html"/>
+	</div>
+</macro>
+
+<macro name="priority_style" requires="node_page">
+	text-align:center;
+	background-color: #<n.priority_bg_color/>;
+	border:1px solid #<n.priority_border_color/>;
+	color:#FFFFFF;
+	display:inline;
+	padding:0 .3em;
+	font-weight:bold;
+</macro>
+
+<macro name="priority_bg_color" requires="node_page">
+	<n.string_list. values="0,E94747,D8B23D,BCBCBC,81C7DE,4A7BD5" separator=",">
+		<n.at_index.topic_priority/>
+	</n.string_list.>
+</macro>
+
+<macro name="priority_border_color" requires="node_page">
+	<n.string_list. values="0,F4A2A2,F5DAA9,DCDCDC,B9DDE9,A3BCEA" separator=",">
+		<n.at_index.topic_priority/>
+	</n.string_list.>
+</macro>
+
+<macro name="start_new_topic_line" parameters="append" requires="subscription, node_page">
+    <n.set_local_user.this_subscription.user/>
+    <n.set_local_node.page_node.get_app_node/>
+    <n.if.both condition1="[n.local_user.has_subscription_to_descentants_of.local_node/]" condition2="[n.local_user.can_reply_to.local_node/]">
+        <then>
+            <t>To start a new topic under <t.location.local_node.subject/>, email <t.p2.local_node.user_address email="[n.local_user.user_email/]"/></t> <n.hide_null.append/>
+        </then>
+    </n.if.both>
+</macro>
+
+<macro name="unsubscribe_line" parameters="format" requires="subscription">
+	<n.if.equal value1="[n.format/]" value2="html">
+		<then><t>To unsubscribe from <t.location.node.subject/></t>, <a href="[n.unsubscribe_by_code_url/]"><t>click here</t></a>.</then>
+		<else><t>To unsubscribe from <t.location.node.subject/></t>, <t>visit <t.url.unsubscribe_by_code_url/></t></else>
+	</n.if.equal>
+</macro>
+
+<macro name="remove_unsubscription_link" dot_parameter="html">
+	<n.regex_replace_all
+		text = "[n.html/]"
+		pattern = 'http://[^"\s]+\.jtp\?macro=unsubscribe_by_code[^"\s]*'
+		replacement = ''
+	/>
+</macro>
+
+<macro name="email_message" dot_parameter="do">
+	<n.do/>
+</macro>
+
+<macro name="html_email_message_with_signature" requires="node">
+	<n.email_message.>
+		<n.fix_quotes.>
+			<n.fix_signature.>
+				<n.message_with_signature/>
+			</n.fix_signature.>
+		</n.fix_quotes.>
+	</n.email_message.>
+</macro>
+
+<macro name="text_email_message_with_signature" requires="node" unindent="true">
+	<n.email_message.>
+		<n.node_message_as_text />
+		<n.if.owner.has_signature>
+			<then>
+				<n.crlf/>-----
+				<n.owner.signature.as_text/>
+			</then>
+		</n.if.owner.has_signature>
+	</n.email_message.>
+</macro>
+
+<macro name="fix_quotes" dot_parameter="text">
+	<n.regex_replace_all. pattern="[n.lt/]blockquote" replacement="[n.lt/]blockquote style='border-left:2px solid #CCCCCC;padding:0 1em'">
+		<n.text/>
+	</n.regex_replace_all.>
+</macro>
+
+<macro name="fix_signature" dot_parameter="text">
+	<n.regex_replace_all. pattern='div class="signature weak-color"' replacement='div class="signature" style="margin-top:1em;color:#666666;font-size:11px;"'>
+		<n.text/>
+	</n.regex_replace_all.>
+</macro>
+
+<macro name="digest_text" requires="subscription,node_list" unindent="true">
+	<n.set_local_subscription.this_subscription/>
+	<t>Digest Email</t>
+	"<n.local_subscription.node.subject/>"
+	<n.digest_post_count/>
+
+	<n.reset_list_index/>
+	<n.loop.>
+		[<n.current_node.digest_subject/>]
+		<n.compress.>
+			<n.truncate. size="200">
+				<n.current_node.message.as_text/>
+			</n.truncate.>
+		</n.compress.>
+		<n.compress.>
+			<t>by <t.author.current_node.owner.name/></t>
+			<t>on <t.date.current_node.when_created.custom_format format="yyyy-MM-dd"/></t>
+			<t>in <t.location.current_node.get_app_node.subject/></t>
+		</n.compress.>
+
+		<t>Read more</t>
+		<n.current_node.url/>
+
+		<n.nop/>
+	</n.loop.>
+	<n.unsubscribe_line format="text"/>
+
+	---
+	<t>DO NOT REPLY TO THIS EMAIL</t>
+
+	<t>Replies sent to this address are not read or processed.</t>
+	<t>If you want to respond to a post for which you received this email,
+	please go to the website: <t.url.local_subscription.node.url/></t>
+</macro>
+
+<macro name="digest_html" requires="subscription,node_list" unindent="true">
+	<n.set_local_subscription.this_subscription/>
+	<h2><t>Digest Email</t></h2>
+	<h3><n.local_subscription.node.subject/></h3>
+	<div style="color:#666666;font-weight:bold">
+		<n.digest_post_count/>
+	</div>
+
+	<n.reset_list_index/>
+	<n.loop.>
+		<p>
+			<div style="font-size:120%">
+				<a href="[n.current_node.url/]"><n.current_node.digest_subject/></a>
+			</div>
+			<n.trim.truncate. size="200">
+				<n.compress.current_node.message.as_text/>
+			</n.trim.truncate.>
+			<div style="color:#666666; font: 11px tahoma,geneva,helvetica,arial,sans-serif;">
+				<t>by <t.author><b><n.current_node.owner.name/></b></t.author></t>
+				<t>on <t.date.current_node.when_created.custom_format format="yyyy-MM-dd"/></t>
+				<t>in <t.location><i><n.current_node.get_app_node.subject/></i></t.location></t>
+			</div>
+		</p>
+	</n.loop.>
+
+	<br/>
+	<br/>
+	<n.unsubscribe_line format="html"/>
+
+	<hr noshade="noshade" size="1" color="#cccccc" />
+	<div style="color:#666666; font: 11px tahoma,geneva,helvetica,arial,sans-serif;line-height:1.5em">
+		<t>DO NOT REPLY TO THIS EMAIL</t><br/>
+		<br/>
+		<t>Replies sent to this address are not read or processed.</t>
+		<t>If you want to respond to a post for which you received this email,
+		please go to the website: <t.url.local_subscription.node.url/></t>
+		<br/>
+		<n.macro_viewer_email_link macro="digest_html"/>
+	</div>
+</macro>
+
+<macro name="digest_subject" requires="node">
+	<n.compress.>
+		<n.if.is_post>
+			<then>
+				<n.topic_node.subject/>
+				<n.if.not.ends_with text="[n.subject/]" suffix="[n.topic_node.subject/]" >
+					<then>(<n.subject/>)</then>
+				</n.if.not.ends_with>
+			</then>
+			<else>
+				<n.subject/>
+			</else>
+		</n.if.is_post>
+	</n.compress.>
+</macro>
+
+<macro name="digest_post_count" requires="node_list">
+	<n.one_or_many.element_count>
+		<one_text><t>new post</t></one_text>
+		<many_text><t>new posts</t></many_text>
+	</n.one_or_many.element_count>
+</macro>
+
+<macro name="topic_assignee" dot_parameter="do" requires="node_page">
+	<n.page_node.topic_node.assignee.do/>
+</macro>
+
+<macro name="topic_priority" requires="node_page">
+	<n.page_node.topic_node.priority/>
+</macro>
+
+<macro name="send bookmark email" unindent="true">
+	<n.set_var. name='what'>
+		<n.root_node.lower_case_view_name/>
+	</n.set_var.>
+	<n.new_email.>
+		<n.send>
+			<to><n.root_node.owner.user_email/></to>
+			<subject><t>Link to <t.location.root_node.subject/></t></subject>
+			<text_part>
+				<t>Hi <t.name.root_node.owner.name/>,</t>
+				<t>Congratulations on your new <t.app.var name='what'/>!</t>
+
+				<n.root_node.subject/>
+				<n.base_url/>
+
+				<t>Please bookmark the link above or save this email so you
+				can easily find your <t.app.var name='what'/> in the future.</t>
+
+				<t>You can also promote your <t.app.var name='what'/> by sending the link
+				to your friends, embedding it onto your website or talking about it on other forums.</t>
+
+				<t>Sincerely,</t>
+				<t>The Nabble Team</t>
+				<n.nabble_homepage/>
+				__________________________________________________________
+				<t>This is an automatic email sent by Nabble to confirm the creation of your new <t.app.var name='what'/>.
+				If you didn't create the <t.app.var name='what'/> mentioned above, please contact us through
+				the Nabble Support forum.</t>
+			</text_part>
+			<html_part>
+				<t>Hi <t.name.root_node.owner.name/>,</t><br/>
+				<br/>
+				<t>Congratulations on your new <t.app.var name='what'/>!</t><br/>
+				<br/>
+				<n.root_node.subject/><br/>
+				<n.base_url/><br/>
+				<br/>
+				<t>Please bookmark the link above or save this email so you
+				can easily find your <t.app.var name='what'/> in the future.</t><br/>
+				<br/>
+				<t>You can also promote your <t.app.var name='what'/> by sending the link
+				to your friends, embedding it onto your website or talking about it on other forums.</t><br/>
+				<br/>
+				<t>Sincerely,</t><br/>
+				<t>The Nabble Team</t><br/>
+				<n.nabble_homepage/><br/>
+				<hr noshade="noshade" size="1" color="#cccccc" />
+				<div style="color:#666666; font: 11px tahoma,geneva,helvetica,arial,sans-serif;line-height:1.5em">
+					<t>This is an automatic email sent by Nabble to confirm the creation of your new <t.app.var name='what'/>.
+					If you didn't create the <t.app.var name='what'/> mentioned above, please contact us through
+					the Nabble Support forum.</t>
+					<br/>
+					<n.macro_viewer_email_link macro="send bookmark email"/>
+				</div>
+			</html_part>
+		</n.send>
+	</n.new_email.>
+</macro>
+
+<macro name="macro_viewer_email_link" parameters="macro">
+	<n.set_var. name="url"><n.macro_viewer_email_url macro="[n.macro/]"/></n.set_var.>
+	<a href="[n.var name='url'/]" rel="nofollow" style="font:9px serif">NAML</a>
+</macro>
+
+<macro name="macro_viewer_email_url" parameters="macro">
+	<n.remove_spaces.>
+		<n.base_url/>
+		<n.macro_search. query="[n.macro/]" search_by="name">
+			<n.if.next_element>
+				<then.current_command.command_path/>
+			</n.if.next_element>
+		</n.macro_search.>
+	</n.remove_spaces.>
+</macro>
+
+<macro name="user email changed" parameters="old_email,new_email">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/feeds.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,139 @@
+<macro name="feeds" requires="servlet">
+	<n.node_page.>
+		<n.html>
+			<head>
+				<n.title.><t>Atom feeds for <t.location.page_node.subject/></t></n.title.>
+			</head>
+			<body>
+				<h1><t>Atom feeds for <t.location.page_node.subject/></t></h1>
+				<div style="margin-top:1.5em">
+					<div class="big-title second-font">
+						<img src="/images/feeds.png" width="16" height="16" align="absmiddle" alt="feeds"/>
+						<t>Topics only</t>
+					</div>
+					<a href="[n.page_node.feeds_topics_path/]"><n.request_base_url/><n.page_node.feeds_topics_path/></a>
+				</div>
+				<div style="margin-top:1.5em">
+					<div class="big-title second-font">
+						<img src="/images/feeds.png" width="16" height="16" align="absmiddle" alt="feeds"/>
+						<t>Topics and replies</t>
+					</div>
+					<a href="[n.page_node.feeds_posts_path/]"><n.request_base_url/><n.page_node.feeds_posts_path/></a>
+				</div>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="feeds_topics_path" requires="node">
+	<n.encode_url.>
+		/<n.url_encoded_subject/>-ft<n.id/>.xml
+	</n.encode_url.>
+</macro>
+
+<macro name="feeds_posts_path" requires="node">
+	<n.encode_url.>
+		/<n.url_encoded_subject/>-f<n.id/>.xml
+	</n.encode_url.>
+</macro>
+
+<static>
+	feed
+	id
+	subtitle
+	entry
+	published
+	updated
+	author
+	name
+	content
+	thr:in-reply-to
+</static>
+
+<macro name="as_atom_feeds" dot_parameter="do" requires="node">
+<n.use_html_encoder.>
+<![CDATA[<?xml version="1.0" encoding="utf-8"?>]]>
+<feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0">
+	<n.xml_response/>
+	<id>tag:<n.server_name/>,2006:forum-<n.id/></id>
+	<title>Nabble - <n.subject/></title>
+	<updated><n.now.as_atom_date_format/></updated>
+	<link rel="self" type="application/atom+xml" href="[n.current_url/]" />
+	<link rel="alternate" type="text/html" href="[n.url/]" />
+	<subtitle type="html"><n.encode.message.message_as_html/></subtitle>
+	<n.do/>
+</feed>
+</n.use_html_encoder.>
+</macro>
+
+<macro name="as_atom_entry" requires="node, node_page">
+	<n.nop/>
+	<entry>
+		<id>tag:<n.server_name/>,2006:post-<n.id/></id>
+		<title><n.subject/></title>
+		<published><n.when_created.as_atom_date_format/></published>
+		<updated><n.atom_entry_updated_value/></updated>
+		<author>
+			<name><n.owner.name/></name>
+		</author>
+		<content type="html">
+			<n.encode.message_with_signature/>
+			<n.if.not.this_node.get_app_node.equals.page_node>
+				<then.encode.>
+					<p>Posted in <n.get_app_node.node_link/></p>
+				</then.encode.>
+			</n.if.not.this_node.get_app_node.equals.page_node>
+		</content>
+		<link rel="alternate" type="text/html" href="[n.url/]" />
+		<n.if.parent_node.is_post>
+			<then><thr:in-reply-to ref="tag:[n.server_name/],2006:post-[n.parent_node.id/]"/></then>
+		</n.if.parent_node.is_post>
+	</entry>
+</macro>
+
+<macro name="atom_entry_updated_value" requires="node">
+	<n.if.was_updated>
+		<then.when_updated.as_atom_date_format/>
+		<else.when_created.as_atom_date_format/>
+	</n.if.was_updated>
+</macro>
+
+<macro name="as_atom_date_format" requires="date">
+	<n.custom_format format="yyyy-MM-dd'T'HH:mm:ss'Z'"/>
+</macro>
+
+<macro name="atom_topics_by_date" requires="servlet" unindent="true">
+	<n.node_page.>
+		<n.app_caching/>
+		<n.page_node.as_atom_feeds.>
+			<n.page_node.topics_list.
+				start="0"
+				length="[n.atom_length/]"
+				sort="last-node-date"
+			>
+				<n.loop.current_node.as_atom_entry/>
+			</n.page_node.topics_list.>
+		</n.page_node.as_atom_feeds.>
+	</n.node_page.>
+</macro>
+
+<macro name="atom_posts_by_date" requires="servlet" unindent="true">
+	<n.node_page.>
+		<n.app_caching/>
+		<n.page_node.as_atom_feeds.>
+			<n.page_node.post_list.
+				start="0"
+				length="[n.atom_length/]"
+				sort="date-descending"
+			>
+				<n.loop.current_node.as_atom_entry/>
+			</n.page_node.post_list.>
+		</n.page_node.as_atom_feeds.>
+	</n.node_page.>
+</macro>
+
+<macro name="atom_length">
+	<n.default. to="35">
+		<n.get_parameter name="length"/>
+	</n.default.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/forgot_password.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,126 @@
+<macro name="forgot_password_page" requires="servlet">
+	<n.set_var name="error" value="[n.false/]" />
+	<n.if.is_submitted_form>
+		<then.if.exists_user_for_email.email_field.value>
+			<then>
+				<n.set_local_user.get_user_from_email email="[n.email_field.value/]" />
+				<n.if.local_user.is_registered>
+					<then>
+						<n.local_user.send_password_mail/>
+						<n.password_mail_sent_page/>
+					</then>
+					<else.unregistered_user_forgot_password_page/>
+				</n.if.local_user.is_registered>
+				<n.exit/>
+			</then>
+			<else.set_var name="error" value="[n.true/]" />
+		</then.if.exists_user_for_email.email_field.value>
+	</n.if.is_submitted_form>
+	<n.html>
+		<head>
+			<meta name="robots" content="noindex,nofollow"/>
+			<n.title.><t>Forgot Password?</t></n.title.>
+			<style type="text/css">
+				div.field-title { margin-top: 0; }
+			</style>
+		</head>
+		<body>
+			<h1><t>Forgot Password?</t></h1>
+
+			<p><t>Please enter the email address you used to register and click on "Submit".
+				We will email you a link to reset your password.</t></p>
+			<p>
+				<n.if.var name="error">
+					<then.format_error message="[t]No registered user found with this email.[/t]" prompt="[t]Please enter a correct email address and try again.[/t]" />
+				</n.if.var>
+				<n.form.>
+					<div class="second-font field-title"><t>Email</t></div>
+					<n.email_field.input size="45" />
+					<input type="submit" value="[t]Submit[/t]" />
+				</n.form.>
+			</p>
+			<n.show_email_warning/>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="send_password_mail" requires="user" unindent="true">
+	<n.set_local_user.this_user />
+	<n.block.>
+		<n.new_email.>
+			<n.send>
+				<to.local_user.user_email/>
+				<subject><t>Reset your password / <t.location.root_node.subject/></t></subject>
+				<text_part>
+					<t>Dear user,</t>
+
+					<t>We received a request to reset your password in <t.location.root_node.subject/>.</t>
+
+					<t>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</t>
+					<n.base_url/><n.local_user.reset_password_path email="[n.local_user.user_email/]" q="[n.local_user.resetcode/]" />
+
+					<t>If you don't want to reset your password, please ignore this message. Your password will not be reset.</t>
+
+					<n.root_node.subject/>
+					<n.base_url/>/
+				</text_part>
+				<aol_part>
+					<t>Dear user,</t>
+
+					<t>We received a request to reset your password in <t.location.root_node.subject/>.</t>
+
+					<t>If you want to reset your password, click on the link below (or copy and paste the URL into your browser):</t>
+					<n.base_url/><n.local_user.reset_password_path email="[n.local_user.user_email/]" q="[n.local_user.resetcode/]" />
+
+					<t>If you don't want to reset your password, please ignore this message. Your password will not be reset.</t>
+
+					<n.root_node.subject/>
+					<n.base_url/>/
+				</aol_part>
+			</n.send>
+		</n.new_email.>
+	</n.block.>
+</macro>
+
+<macro name="password_mail_sent_page" requires="servlet">
+	<n.html>
+		<head>
+			<n.title.><t>Password Reset Sent</t></n.title.>
+		</head>
+		<body>
+			<h1><t>Password Reset Sent</t></h1>
+			<p>
+				<t>We have sent you a link to reset your password. Please check your email now.
+					If you don't receive the instructions in a few minutes, check your spam folder
+					or try to resend the request.</t>
+			</p>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="unregistered_user_forgot_password_page" requires="servlet">
+	<n.html>
+		<head>
+			<n.title.><t>Forgot Password?</t></n.title.>
+		</head>
+		<body>
+			<h1><t>Forgot Password?</t></h1>
+
+			<p>
+				<t>There is an unregistered user account associated with the email address <t.email.bold.email_field.value/>.</t>
+				<t>If this email address is yours, you should <n.register_link.>register</n.register_link.> using this same address.
+				After registration, you will own this user account.</t>
+			</p>
+			<p>
+				<t>For more info, see: <t.info.help.userid.link/></t>
+			</p>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="forgot_password_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=forgot_password_page
+	</n.encode_url.>
+</macro>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/javascript_library.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,517 @@
+<macro name="javascript_library" requires="servlet">
+	<n.uncache_for/>
+	<n.javascript_response/>
+	<n.js_basic_nabble_functions/>
+	<n.js_basic_embedding_code/>
+	<n.js_basic_site_functions/>
+	<n.js_date_functions/>
+	<n.js_header_functions/>
+	<n.js_delete_functions/>
+	<n.js_topic_and_post_functions/>
+	<n.js_search_functions/>
+</macro>
+
+<macro name="javascript_library_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=javascript_library&v=<n.javascript_version/>
+	</n.encode_url.>
+</macro>
+
+<macro name="js_basic_embedding_code">
+	var host = "<n.get_request_header name="host"/>";
+	<![CDATA[
+	Nabble.embeddingUrl = null;
+	Nabble.embedForumID = 0;
+	Nabble.isEmbedded = false;
+
+	try {
+		var info = Nabble.getParent().nabbleinfo.location.search.substring(6);
+		info = decodeURIComponent(info);
+		var pos = info.indexOf('&');
+		Nabble.embedForumID = info.substring(0, pos);
+		var hash = Nabble.getParent().nabbleinfo.location.hash.substring(1).split('|');
+		Nabble.embeddingUrl = hash[0];
+		Nabble.isEmbedded = true;
+	} catch(err) {}
+
+	Nabble.getClientID = function(c) {
+		return '';
+	};
+
+	Nabble.embeddedTarget = function(defaultTarget) {
+		return Nabble.isEmbedded? ' target="nabbleiframe" ' : ' target="' + defaultTarget + '" ';
+	};
+
+	Nabble.height = function() {
+		if( typeof( window.innerHeight ) == 'number' ) {
+			return window.innerHeight;
+		} else if( document.documentElement && document.documentElement.clientHeight ) {
+			return document.documentElement.clientHeight;
+		} else if( document.body && document.body.clientHeight ) {
+			return document.body.clientHeight;
+		}
+	};
+
+	Nabble.knowsHeight = navigator.userAgent.toLowerCase().indexOf('safari') == -1;
+
+	Nabble.canScroll = function() {
+		try{
+			return Nabble.getParent().nabbleinfo.canScroll();
+		} catch(er) {}
+		return true;
+	};
+
+	Nabble.hash = function() {
+		var _hash = location.hash;
+		if (Nabble.getParent().nabbleinfo && Nabble.getParent().nabbleinfo.hash) {
+			if (!_hash)
+				_hash = '#' + Nabble.getParent().nabbleinfo.hash;
+			Nabble.getParent().nabbleinfo.hash = null;
+		}
+		return _hash;
+	};
+
+	Nabble.setTop = function(url) {
+		if (Nabble.isEmbedded) Nabble.getParent().nabbleiframe.location=url;
+		else top.location=url;
+	};
+
+	Nabble.getMyHeight = function() {
+		try {
+			return $(document.body).outerHeight();
+		} catch(err) {}
+		return 500;
+	};
+
+	Nabble.evalInTop = function(key, js) {
+		var p = '';
+		for (var i = 0; i < js.length; i++) {
+			p += 'v=' + key[i] + '|' + encodeURIComponent(js[i]) + '&';
+		}
+		if (p.length > 0) {
+			var url = "/util/SessionService.jtp?action=set&" + p + "cid=" + Nabble.getParent().nabbleinfo.clientID + "&_=" + new Date().getTime();
+			$.get(url, function() {
+				Nabble.getParent().nabbleresize.location = "/util/Empty.jtp";
+			});
+		}
+	};
+
+	Nabble.quote = function(s) {
+		return '"' + s.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(/"/g,'\\"') + '"';
+	};
+
+	Nabble.heightLimit = 32767;
+	Nabble.resizeCount = 0;
+
+	Nabble.isValidHeight = function(height) {
+		var isBadBrowser = $.browser.mozilla || $.browser.opera;
+		var exceeded = height > Nabble.heightLimit && isBadBrowser;
+		return !exceeded;
+	};
+
+	Nabble.resizeFrames = function(h, scroll) {
+		if (Nabble.getParent().nabbleresize) {
+			var height = h && typeof h == 'number'? h : Math.max(Nabble.getMyHeight() + 25, 600);
+			var validHeight = Nabble.isValidHeight(height);
+			height = validHeight? height : Nabble.heightLimit;
+			var js = [], key = [];
+			if (scroll) {
+				js.push("Nabble.scroll(" + scroll + ");");
+				key.push("scrolljs");
+			}
+			if (height != $(window).height() || Nabble.resizeCount++ == 0) {
+				js.push("Nabble.resizeFrames(" + height + "," + Nabble.quote(document.title) + "," + validHeight + ");");
+				key.push("resizejs");
+			}
+			if (js.length > 0)
+				Nabble.evalInTop(key, js);
+		}
+	};
+
+	Nabble.restartEmbedding = function(nodeId, baseUrl) {
+		if (Nabble.getParent().nabbleresize) {
+			var js = ['Nabble.restart(' + nodeId + ',"' + baseUrl + '");'];
+			Nabble.evalInTop(['others'], js);
+		}
+	};
+
+	$(document).ready(function(){
+		if (Nabble.isEmbedded) {
+			Nabble.resizeFrames('', 1);
+			$(window).resize(Nabble.resizeFrames);
+			$(window).load(function() {
+			   Nabble.resizeFrames();
+			});
+		}
+	});
+	]]>
+</macro>
+
+<macro name="js_basic_site_functions" requires="servlet">
+	<![CDATA[
+	Nabble.online = function(id) {
+		$('img.online'+id).show();
+	};
+
+	Nabble.openMsg = function(id) {
+		return Nabble.getCookie(id) == null;
+	};
+
+	Nabble.toggleMsg = function(id, hide) {
+		if (hide)
+			Nabble.setPersistentCookie(id, 'closed');
+		else
+			Nabble.deleteCookie(id);
+	};
+	]]>
+</macro>
+
+<macro name="js_date_functions" requires="servlet">
+	Nabble.months = ["<t>Jan</t>","<t>Feb</t>","<t>Mar</t>","<t>Apr</t>","<t>May</t>","<t>Jun</t>","<t>Jul</t>","<t>Aug</t>","<t>Sep</t>","<t>Oct</t>","<t>Nov</t>","<t>Dec</t>"];
+	<![CDATA[
+	Nabble.now = new Date();
+	Nabble.fmt2 = function(i) { return i <= 9? '0'+i:i; };
+
+	Nabble.isToday = function(date) {
+		return date.toDateString() == this.now.toDateString();
+	};
+
+	Nabble.isThisYear = function(date) {
+		return date.getYear() == this.now.getYear();
+	};
+
+	Nabble.dateFormatters = {
+		us: new (function(){
+			this.formatTime = function(date) {
+				var hours = date.getHours();
+				if (hours < 12) {
+					var xm = "am";
+					if (hours==0)
+						hours = 12;
+				} else {
+					var xm = "pm";
+					if (hours > 12)
+						hours -= 12;
+				}
+				return hours + ":" + Nabble.fmt2(date.getMinutes()) + xm;
+			};
+			this.formatDateOnly = function(date) {
+				return Nabble.months[date.getMonth()] + " " + Nabble.fmt2(date.getDate()) + ", " + date.getFullYear();
+			};
+			this.formatDateLong = function(date) {
+				return this.formatDateOnly(date) + "; " + this.formatTime(date);
+			};
+			this.formatDateShort = function(date) {
+				if( Nabble.isToday(date) )
+					return this.formatTime(date);
+				if( Nabble.isThisYear(date) )
+					return Nabble.months[date.getMonth()] + " " + Nabble.fmt2(date.getDate());
+				return this.formatDateOnly(date);
+			};
+		})()
+		,
+		euro: new (function(){
+			this.formatTime = function(date) {
+				return Nabble.fmt2(date.getHours()) + ":" + Nabble.fmt2(date.getMinutes());
+			};
+			this.formatDateOnly = function(date) {
+				return Nabble.fmt2(date.getDate()) + "." + Nabble.months[date.getMonth()] + "." + date.getFullYear();
+			};
+			this.formatDateLong = function(date) {
+				return this.formatTime(date) + ", " + this.formatDateOnly(date);
+			};
+			this.formatDateShort = function(date) {
+				if( Nabble.isToday(date) )
+					return this.formatTime(date);
+				if( Nabble.isThisYear(date) )
+					return Nabble.fmt2(date.getDate()) + "." + Nabble.months[date.getMonth()];
+				return this.formatDateOnly(date);
+			};
+		})()
+		,
+		tech: new (function(){
+			this.formatTime = function(date) {
+				return Nabble.fmt2(date.getHours()) + ":" + Nabble.fmt2(date.getMinutes());
+			};
+			this.formatDateOnly = function(date) {
+				return "" + date.getFullYear() + "-" + Nabble.fmt2(date.getMonth()+1) + "-" + Nabble.fmt2(date.getDate())
+			};
+			this.formatDateLong = function(date) {
+				return this.formatDateOnly(date) + " " + this.formatTime(date);
+			};
+			this.formatDateShort = function(date) {
+				if( Nabble.isToday(date) )
+					return this.formatTime(date);
+				if( Nabble.isThisYear(date) )
+					return Nabble.fmt2(date.getMonth()+1) + "-" + Nabble.fmt2(date.getDate());
+				return this.formatDateOnly(date);
+			};
+		})()
+	};
+
+	Nabble.getDateFmt = function() {
+		var dateFmt = Nabble.getCookie("date_fmt");
+		return dateFmt==null ? "us" : dateFmt;
+	};
+
+	Nabble.formatDateOnly = function(date) {
+		return Nabble.dateFormatters[Nabble.getDateFmt()].formatDateOnly(date);
+	};
+
+	Nabble.formatTimeOnly = function(date) {
+		return Nabble.dateFormatters[Nabble.getDateFmt()].formatTime(date);
+	};
+
+	Nabble.formatDateLong = function(date) {
+		return Nabble.dateFormatters[Nabble.getDateFmt()].formatDateLong(date);
+	};
+
+	Nabble.formatDateShort = function(date) {
+		var fmt = Nabble.dateFormatters[Nabble.getDateFmt()];
+		return '<span title="' + fmt.formatDateLong(date) + '">'
+			+ fmt.formatDateShort(date) + '</span>';
+	};
+	]]>
+</macro>
+
+<macro name="js_topic_and_post_functions" requires="servlet">
+	<![CDATA[
+	Nabble.messageTextWidth = function() {
+		var maxWidth = Nabble.getCookie("max_width");
+		if( maxWidth==null )
+			return;
+		document.write("<style type='text/css'>.nabble .message-text {max-width: "+maxWidth+";}</style>");
+	};
+
+	Nabble.setFontSize = function() {
+		var fontSize = Nabble.getCookie("font_size");
+		if (fontSize)
+			document.write("<style type='text/css'>body, table .nabble {font-size: "+fontSize+";}</style>");
+	};
+
+	Nabble.hideQuotes = function(context) {
+		$('div.shrinkable-quote,blockquote',context).each(function() {
+			var $this = $(this);
+			if ($this.outerHeight() > 300) {
+				$this.after("<div class='shrink-quote'><span></span> [<a href='javascript:void(0)'></a>]</div>");
+			}
+		});
+		$('div.shrink-quote a', context)
+			.click(function(){
+				var $this = $(this);
+				var v = parseInt($this.attr('collapsed') || 1) % 2;
+				if (v == 0) {
+					$this.prev().html( "&#171;&nbsp;" );
+					$this.html( "hide part of quote" );
+					var $text = $this.parent().prev();
+					$text.css( 'height', 'auto' );
+					$text.css( 'overflowY', 'auto' );
+				} else {
+					$this.prev().html( "..." );
+					$this.html( "show rest of quote" );
+					var $text = $this.parent().prev();
+					$text.css('height', '300px');
+					$text.css('overflowY', 'hidden' );
+				}
+				$this.attr('collapsed', v+1);
+				Nabble.resizeFrames();
+			})
+			.click()
+		;
+	};
+	]]>
+</macro>
+
+<macro name="js_header_functions" requires="servlet">
+	var t_login = "<t>Login</t>";
+	var t_register = "<t>Register</t>";
+	var t_logout = "<t>Log out</t>";
+	var t_my_posts = "<t>My posts</t>";
+	var t_account_settings = "<t>Account settings</t>";
+	var t_show_nabble_notice = "<t>Show Nabble notice</t>";
+	var t_permalink = "<t>Permalink</t>";
+	var t_refresh = "<t>Refresh</t>";
+	var login_path = "<n.login_path/>";
+	var logout_path = "<n.logout_path/>";
+	var register_path = "<n.register_path/>";
+	var user_profile_path = "<n.user_profile_path/>";
+	<![CDATA[
+	Nabble.userId = Nabble.getCookie("userId");
+	var encodedUsername = Nabble.getCookie("username");
+	if (encodedUsername) {
+		Nabble.username = decodeURIComponent(encodedUsername)
+			.replace(new RegExp('\\+','g'),' ');
+	}
+
+	if (!Nabble.userId) {
+		Nabble.anonymousId = Nabble.getCookie("anonymousId");
+		var encodedName = Nabble.getCookie("anonymousName");
+		if (encodedName) {
+			Nabble.anonymousName = decodeURIComponent(encodedName)
+				.replace(new RegExp('\\+','g'),' ')
+				.replace(new RegExp('<','g'),'&lt;')
+				.replace(new RegExp('>','g'),'&gt;');
+		}
+	}
+
+	Nabble.getPermalink = function() {
+		return Nabble.getParent().nabbleiframe.location.href;
+	};
+
+	Nabble.permalinkLabel = function() {
+		if (!Nabble.isEmbedded)
+			return '';
+		var p = "<script>function openPermalink() { prompt('Copy this:', Nabble.getPermalink()); };</script>";
+		p += "<a href='javascript: void openPermalink();'>"+t_permalink+"</a>&nbsp;&nbsp;";
+		return p;
+	};
+
+	Nabble.refreshLabel = function() {
+		if (Nabble.isEmbedded)
+			return "<a href='javascript: location.reload(true);'>"+t_refresh+"</a>&nbsp;&nbsp;";
+		return '';
+	};
+
+	Nabble.siteHeader = function() {
+		var s = Nabble.refreshLabel();
+		s += Nabble.permalinkLabel();
+		if (!Nabble.userId) {
+			var nextUrl = typeof loginNextUrl == 'undefined'? window.location.href : loginNextUrl;
+			s += '<a href="'+login_path+'&nextUrl='+encodeURIComponent(nextUrl)+'">'+t_login+'</a>';
+			s += '&nbsp;&nbsp;';
+			s += '<a href="'+register_path+'&nextUrl='+encodeURIComponent(nextUrl)+'">'+t_register+'</a>';
+			if (Nabble.anonymousId && Nabble.anonymousName) {
+				s += '&nbsp;&nbsp;';
+				s += '<a href="/template/NamlServlet.jtp?macro=user_nodes&user=' + Nabble.anonymousId + '~' + Nabble.anonymousName + '">';
+				s += Nabble.anonymousName;
+				s += '</a>';
+			}
+		} else
+			s += '<span id="user-dd"></span>';
+		return s;
+	};
+
+	Nabble.userDropdown = function() {
+		var $t = $('#user-dd');
+		if ($t.size() == 0)
+			return;
+		$t.empty();
+		var dropdown = new NabbleDropdown('usrdd', Nabble.escapeHTML(Nabble.username));
+		dropdown.add('myPosts','<a href="/template/NamlServlet.jtp?macro=user_nodes&user='+Nabble.userId + '">'+t_my_posts+'</a>');
+		dropdown.add('personalSettings','<a href="'+user_profile_path+'">'+t_account_settings+'</a>');
+		dropdown.add('adminNotice','<a href="javascript: void Nabble.showAdminNotice()">'+t_show_nabble_notice+'</a>','display:none');
+		dropdown.add('logout','<a href="javascript: void Nabble.logout()">'+t_logout+'</a>');
+		$t.html(dropdown.getHtml());
+		dropdownInit('usrdd');
+	};
+
+	Nabble.addUserHeaderListener(function() {
+		Nabble.userDropdown();
+	});
+
+	Nabble.showAdminNotice = function() {
+		Nabble.deleteCookie('admin-notice');
+		window.location.reload();
+	};
+
+	Nabble.logout = function() {
+		window.location = '/template/NamlServlet.jtp?macro=logout_page';
+	};
+	]]>
+</macro>
+
+<macro name="js_delete_functions" requires="servlet">
+	var t_confirm_delete_post = "<t>Do you really want to delete this post?</t>";
+	var t_confirm_delete_post_recursively = "<t>Do you really want to permanently delete this message and all replies?</t>";
+	var t_caution_cannot_revert = "<t>CAUTION: this action cannot be reverted.</t>";
+	<![CDATA[
+	Nabble.deletePost = function(postId) {
+		if (!confirm(t_confirm_delete_post))
+			return;
+		var newLocation = "/template/NamlServlet.jtp?macro=delete_post&node="+postId;
+		Nabble.setTop(newLocation);
+	};
+
+	Nabble.deleteFromSite = function(nodeId) {
+		if( !confirm(t_confirm_delete_post_recursively+"\n\n"+t_caution_cannot_revert) )
+			return;
+		var newLocation = "/template/NamlServlet.jtp?macro=delete_from_site&node="+nodeId;
+		Nabble.setTop(newLocation);
+	};
+	]]>
+</macro>
+
+<macro name="js_search_functions" requires="servlet">
+	<![CDATA[
+	Nabble.highlightSearchTerms = function(searchterms, elem) {
+		if (elem.childNodes && elem.childNodes.length > 0) {
+			for (var i=0; i<elem.childNodes.length; i++) {
+				this.highlightSearchTerms(searchterms, elem.childNodes[i]);
+			}
+		} else if (elem.nodeType) {
+			if (elem.nodeType == document.TEXT_NODE || elem.nodeType == 3) {
+				var txt = elem.nodeValue;
+				var rgx = new RegExp("\\b("+searchterms+")\\w*\\b", "gi");
+				var result;
+				var start = 0;
+				var newFragment = document.createElement("span");
+				while ((result = rgx.exec(txt)) != null) {
+					var end = result.index;
+					var textNode = document.createTextNode(txt.slice(start, end));
+					newFragment.appendChild(textNode);
+					var hlNode = document.createElement("span");
+					hlNode.className = "bold highlight search-highlight";
+					hlNode.appendChild(document.createTextNode(result[0]));
+					newFragment.appendChild(hlNode);
+					start = end + result[0].length;
+					Nabble.hasHighlightedTerms = true;
+				}
+				newFragment.appendChild(document.createTextNode(txt.slice(start)));
+				elem.parentNode.replaceChild(newFragment, elem);
+			}
+		}
+	};
+
+	Nabble.getSearchTerms = function() {
+		var searchterms = this.getCookie("searchterms");
+		if (document.referrer) {
+			var result = Nabble.getSearchTerms2(document.referrer);
+			if (result!=null) {
+				var query = decodeURIComponent(result);
+				searchterms = query.replace(/\W+/g,"|").replace(/^\|/,"").replace(/\|$/,"");
+				this.setCookie("searchterms", searchterms);
+				Nabble.gquery = query.replace(/\+/g," ");
+			}
+		}
+		return searchterms;
+	};
+
+	/* Logic from _uOrg() in urchin.js */
+	Nabble.getSearchTerms2 = function(referrer) {
+		if (typeof(_uOsr)=="undefined" || typeof(_uOkw)=="undefined")
+			return null;
+		var searchEngines = _uOsr;  /* from urchin */
+		var searchQueries = _uOkw;  /* from urchin */
+		var i;
+		if (referrer==null || (i=referrer.indexOf("://")) < 0)
+			return null;
+		var h = referrer.substring(i+3);
+		var p = h.indexOf("/");
+		if (p > -1)
+			h = h.substring(0,p);
+		for (var j=0; j<searchEngines.length; j++) {
+			var engine = searchEngines[j];
+			var query = searchQueries[j];
+			if (h.toLowerCase().indexOf(engine.toLowerCase()) > -1) {
+				if ((i=referrer.indexOf("?"+query+"=")) > -1 || (i=referrer.indexOf("&"+query+"=")) > -1) {
+					var k = referrer.substring(i+query.length+2,referrer.length);
+					if ((i=k.indexOf("&")) > -1)
+						k = k.substring(0,i);
+					return k;
+				}
+			}
+		}
+		return null;
+	};
+	]]>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/js_page.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,61 @@
+<macro name="js_page" requires="servlet" unindent="true">
+	<n.dont_cache/>
+	<n.javascript_response/>
+	<n.call_later_handlers/>
+</macro>
+
+<macro name="call_later_handlers" unindent="true">
+	<n.search_terms_special_js/>
+	<n.post_count_js/>
+	<n.newsflash_js/>
+	<n.show_administrator_notice_js/>
+
+	<n.views_js/>
+	<n.mark_unread_topics_js/>
+	<n.mark_unread_posts_js/>
+	<n.mark_as_visited_js/>
+	<n.set_visitor_online_js/>
+	<n.avatar_online_js/>
+	<n.increment_view_count_js/>
+
+	<n.extra_call_later_handlers/>
+</macro>
+
+<macro name="extra_call_later_handlers">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="run_call_later_now">
+	<n.call_later_handlers/>
+</macro>
+
+<macro name="param_loop" dot_parameter="do" parameters="param">
+	<n.if.is_in_command name="run_call_later_now" >
+		<then.get_parameters_from_run_later. name="[n.param/]">
+			<n.loop.do/>
+		</then.get_parameters_from_run_later.>
+		<else.get_parameters_pipe_separated. name="[n.param/]">
+			<n.loop.do/>
+		</else.get_parameters_pipe_separated.>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="page_node" dot_parameter="do" requires="parameter_value_list">
+	<n.if_node_parameter_is_valid.>
+		<n.get_node_from_id. node_id="[n.current_parameter_value/]">
+			<n.do/>
+		</n.get_node_from_id.>
+	</n.if_node_parameter_is_valid.>
+</macro>
+
+<macro name="if_node_parameter_is_valid" dot_parameter="do" requires="parameter_value_list">
+	<n.if.is_valid_node node_id="[n.current_parameter_value/]">
+		<then.do/>
+	</n.if.is_valid_node>
+</macro>
+
+<macro name="page_user" dot_parameter="do" requires="parameter_value_list">
+	<n.get_user_from_id. user_id="[n.current_parameter_value/]">
+		<n.do/>
+	</n.get_user_from_id.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/layout.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,33 @@
+<macro name="column_layout" dot_parameter="columns">
+	<n.put_in_head.>
+		<style type="text/css">
+			#columns {
+				margin-top:1em;
+			}
+			#columns .column {
+				float: left;
+				margin:0;
+				padding:0;
+				overflow-x:hidden;
+			}
+			#columns .widget {
+				margin: 0 .5em 1em .5em;
+			}
+		</style>
+	</n.put_in_head.>
+	<div id="columns">
+		<n.columns/>
+	</div>
+</macro>
+
+<macro name="column" dot_parameter="contents" parameters="width">
+	<div class="column" style="[n.width_style.width/]">
+		<n.contents/>
+	</div>
+</macro>
+
+<macro name="widget" dot_parameter="contents" parameters="title">
+	<div class="widget">
+		<n.contents/>
+	</div>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/login.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,125 @@
+<macro name="login_page" requires="servlet">
+	<n.set_var. name="error"><n.false/></n.set_var.>
+	<n.if.is_submitted_form>
+		<then>
+			<n.do_login>
+				<email><n.email_field.value/></email>
+				<password><n.password_field.value/></password>
+				<nextUrl><n.nextUrl_field.value/></nextUrl>
+			</n.do_login>
+			<n.set_var. name="error"><n.true/></n.set_var.>
+		</then>
+	</n.if.is_submitted_form>
+	<n.html>
+		<head>
+			<meta name="robots" content="noindex,nofollow"/>
+			<n.title.><t>Login</t></n.title.>
+			<n.email_field.focus/>
+			<script type="text/javascript">
+				var loginNextUrl = '<n.default. to="/"><n.nextUrl_field.value/></n.default.>';
+			</script>
+		</head>
+		<body>
+			<h1 class="weak-color"><t>Login</t></h1>
+
+			<n.login_message/>
+
+			<n.if.var name="error">
+				<then.format_error
+					message="[t]Incorrect Login![/t]"
+					prompt="[t]Please re-enter your email/password and click Login.[/t]"
+				/>
+			</n.if.var>
+
+			<n.login_form/>
+			<n.register_now_section/>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="login_message">
+	<div style="margin:.2em 0 1em">
+		<n.hide_null.message_field.value/>
+	</div>
+</macro>
+
+<macro name="login_form">
+	<n.put_in_head.>
+		<style type="text/css">
+			input[type=text],input[type=password] {
+				padding:.4em 0;
+			}
+			div.field-title {
+				margin:.1em .3em 0 1.5em;
+				white-space:nowrap;
+				text-align:right;
+			}
+		</style>
+	</n.put_in_head.>
+	<n.form. macro="login_page">
+		<n.message_field.hidden/>
+		<n.nextUrl_field.hidden/>
+		<table>
+			<tr>
+				<td>
+					<div class="second-font field-title">
+						<t>Email</t>
+					</div>
+				</td>
+				<td><n.email_field.input type="text" size="40" /> <div class="weak-color" style="display:inline;font-size:80%"><t>Example: johnsmith@domain.com</t></div></td>
+			</tr>
+			<tr>
+				<td>
+					<div class="second-font field-title">
+						<t>Password</t>
+					</div>
+				</td>
+				<td><n.password_field.input type="password" size="20" /></td>
+			</tr>
+			<tr>
+				<td align="right" style="padding-right:.3em"><img src="/images/lock.png" width="30" height="46"/></td>
+				<td>
+					<input type="submit" value="[t]Login[/t]" />
+					<div style="padding-top:.2em"><a href="[n.forgot_password_path/]"><t>Forgot your password?</t></a></div>
+				</td>
+			</tr>
+		</table>
+	</n.form.>
+</macro>
+
+<macro name="register_now_section">
+	<div class="light-bg-color rounded" style="padding: .7em .5em;margin-top:1.4em">
+		<div class="second-font big-title"><t>Register</t></div>
+		<div style="margin-top:.5em">
+			<t>If you are still not a member, you can <n.register_link.><b>register now</b></n.register_link.>.</t>
+		</div>
+	</div>
+</macro>
+
+<macro name="email_field" dot_parameter="do">
+	<n.field. name="email"><n.do/></n.field.>
+</macro>
+
+<macro name="password_field" dot_parameter="do">
+	<n.field. name="password"><n.do/></n.field.>
+</macro>
+
+<macro name="nextUrl_field" dot_parameter="do">
+	<n.field. name="nextUrl"><n.do/></n.field.>
+</macro>
+
+<macro name="login_path" parameters="message,nextUrl">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=login_page
+		<n.add_to_path name="message" value="[n.message/]" />
+		<n.add_to_path name="nextUrl" value="[n.nextUrl/]" />
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="login" dot_parameter="message" requires="servlet">
+	<n.redirect_to.login_path message="[n.message/]" nextUrl="[n.current_path/]" />
+</macro>
+
+<macro name="login_link" dot_parameter="text">
+	<a id="login-link" href="[n.login_path/]"><n.text/></a>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/logout.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,36 @@
+<macro name="logout_page" requires="servlet">
+	<n.dont_cache/>
+	<n.do_logout/>
+	<n.html>
+		<head>
+			<n.title.><t>You logged out</t></n.title.>
+			<script type="text/javascript">
+				Nabble.setVar('userId', null);
+				Nabble.setVar('username', null);
+				Nabble.setVar('password', null);
+
+				var loginNextUrl = '/';
+			</script>
+		</head>
+		<body>
+			<h1><t>You logged out</t></h1>
+
+			<p><t>If you logged out by mistake, please <n.login_link.>log in again</n.login_link.>.</t></p>
+
+			<script type="text/javascript">
+				if (Nabble.isEmbedded) {
+					var $l = $('#login-link');
+					$l.attr('href', $l.attr('href') + '&nextUrl=/');
+				}
+			</script>
+			<br/>
+			<br/>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="logout_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=logout_page
+	</n.encode_url.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/macro_viewer.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1316 @@
+<namespace name="naml_tools"/>
+
+<macro name="current_macro_source" dot_parameter="do" requires="naml_tools">
+	<n.if.command_exists id="[n.current_command_id/]">
+		<then>
+			<n.macro_source.
+				id="[n.current_command_id/]"
+				base="[n.current_base_classes/]"
+				breadcrumbs="[n.current_breadcrumbs/]"
+			>
+				<n.do/>
+			</n.macro_source.>
+		</then>
+		<else>
+			<n.invalid_command_html/>
+		</else>
+	</n.if.command_exists>
+</macro>
+
+<macro name="macro_viewer_header" dot_parameter="text">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			function openVideo() {
+				$('#demo-video').slideDown();
+			};
+			function closeVideo() {
+				$('#demo-video').slideUp();
+			};
+		</script>
+	</n.put_in_head.>
+
+	<div style="clear:both;padding-top:1em">
+		<div style="float:left;margin-top:1.1em">
+			<n.macro_options_dropdown/>
+		</div>
+		<h1 style="margin:.5em 0 1em"><n.text/></h1>
+	</div>
+
+	<div class="second-font" style="font-size:80%;float:right;margin-top:-4.3em;padding:.3em 0">
+		<a href="http://naml.nabble.com/" target="_top">NAML documentation</a>
+		&nbsp;
+		<n.naml_video_link/>
+	</div>
+</macro>
+
+<macro name="naml_video_link">
+	<a href="#" onclick="openVideo()">Watch a video</a>
+	<div id="demo-video" style="display:none;text-align:center;position:fixed;z-index:1001;width:100%;top:3em">
+		<div class="shaded-bg-color rounded drop-shadow" style="width:450px;height:400px;margin:0 auto;text-align:center">
+			<div style="text-align:right;padding:.3em .9em">
+				<a href="#" onclick="closeVideo()"><t>Close</t></a>
+			</div>
+			<iframe width="425" height="349" src="http://www.youtube.com/embed/06hd0keRN80" frameborder="0" allowfullscreen="true"></iframe>
+		</div>
+	</div>
+</macro>
+
+<macro name="macro_viewer" requires="servlet">
+	<n.naml_tools.>
+        <n.current_macro_source.>
+            <n.html>
+                <head>
+                    <meta name="robots" content="noindex,nofollow"/>
+                    <n.title.><n.name/></n.title.>
+                    <n.macro_viewer_stylesheet/>
+                    <n.redirect_if_overridden/>
+                    <n.codemirror_shared_head/>
+                    <n.macro_viewer_js/>
+                </head>
+                <body>
+                    <n.macro_viewer_header.>
+                        <n.name/>
+                    </n.macro_viewer_header.>
+
+                    <n.important_notices/>
+                    <n.source_panel/>
+                </body>
+            </n.html>
+        </n.current_macro_source.>
+	</n.naml_tools.>
+</macro>
+
+<macro name="title" dot_parameter="text" requires="macro_source">
+  <title><n.root_node.subject/> | NAML - <n.text/></title>
+</macro>
+
+<macro name="breadcrumbs_content" requires="macro_source, naml_tools">
+	<n.root_node.node_breadcrumbs />
+	| NAML<sup class="important">alpha</sup>
+	| <n.naml_breadcrumbs/>
+</macro>
+
+<macro name="breadcrumbs_content" requires="naml_tools">
+	<n.root_node.node_breadcrumbs />
+	| NAML<sup class="important">alpha</sup>
+</macro>
+
+<macro name="naml_breadcrumbs">
+	<n.navigation_breadcrumbs.loop.parts.>
+		<n.if.has_more_elements>
+			<then>
+				<a href="[n.part_path/]"><n.name/></a>
+				<span class="weak-color breadcrumbs-arrow" style="padding:0 .25em">
+					&rsaquo;
+				</span>
+			</then>
+			<else.name/>
+		</n.if.has_more_elements>
+	</n.navigation_breadcrumbs.loop.parts.>
+</macro>
+
+<macro name="codemirror_shared_head">
+	<style type="text/css">
+		.CodeMirror-line-numbers {
+			font-family: verdana, arial, sans-serif;
+			font-size: 11pt;
+			width: 2.2em;
+			text-align: right;
+			padding-right: .3em;
+			line-height: normal;
+			border-right-width:2px;
+			border-right-style:solid;
+		}
+		#resizebar {
+			height:8px;
+			background:url('/images/grip.png') #d8d8d8 repeat;
+			border:1px solid #aaa;
+			cursor:n-resize;
+		}
+	</style>
+	<script src="/util/codemirror/js/codemirror.js"></script>
+	<script src="/util/codemirror/js/highlight.js"></script>
+	<script src="/util/codemirror/js/stringstream.js"></script>
+	<script src="/util/codemirror/js/tokenize.js"></script>
+	<script src="/util/codemirror/js/parsexml.js"></script>
+	<script type="text/javascript">
+		<![CDATA[
+		function newEditor(height) {
+			var e = CodeMirror.fromTextArea('txt_basic', {
+				parserfile: "parsexml.js",
+				stylesheet: "/util/codemirror/css/xmlcolors.css",
+				path: "/util/codemirror/js/",
+				lineNumbers: true,
+				height: height,
+				indentUnit: 4,
+			});
+			var $wrapping = $('#txt_basic').next();
+			$wrapping.attr('id', 'wrapping_basic');
+			$wrapping.css('margin-top', '.4em');
+			$wrapping.addClass('medium-border-color border1');
+			addResizeBar($wrapping);
+			$('div.CodeMirror-line-numbers', $wrapping).addClass('shaded-bg-color medium-border-color');
+			return e;
+		};
+
+		function addResizeBar($wrapping) {
+			$wrapping.after('<div id="resizebar"></div>');
+
+			var dragging = false;
+			function startDrag(e) {
+				if ($(e.target).attr('id') == 'resizebar')
+					dragging = true;
+				if (dragging && typeof e.preventDefault != 'undefined')
+					e.preventDefault();
+			};
+			function endDrag(e) {
+				dragging = false;
+			};
+			function drag(e, x, y) {
+				e.stopPropagation();
+				if (dragging) {
+					var h = y - $wrapping.offset().top - 5;
+					$wrapping.height(h < 100? 100 : h);
+				}
+			};
+			function onSelectStart() {
+				if (dragging) return false;
+			};
+
+			function isCorrectFrame(f) {
+				try {
+					frame.document.location.href;
+					return true;
+				} catch(err) {
+					return false;
+				}
+			}
+			var index = 0;
+			var frame = window.frames[index++];
+			while (!isCorrectFrame(frame)) {
+				frame = window.frames[index++];
+			}
+			$(frame.document)
+				.mousemove(function(e) {
+					var x = e.pageX + $wrapping.offset().left;
+					var y = e.pageY + $wrapping.offset().top;
+					drag(e, x, y);
+				})
+				.mouseup(function(e) { endDrag(e); })
+				.select(onSelectStart);
+
+			$(document)
+				.mousemove(function(e) { drag(e, e.pageX, e.pageY); })
+				.mousedown(function(e) { startDrag(e); })
+				.mouseup(function(e) { endDrag(e); })
+				.select(onSelectStart);
+		};
+		var basicEditor;
+		function inlineEditor(height, line, col) {
+			if (!basicEditor)
+				basicEditor = newEditor(height);
+			function showCaret() {
+				var $lines = $('#wrapping_basic div.CodeMirror-line-numbers').children();
+				if (basicEditor.editor && $lines.size() > 5) {
+					if (line) {
+						if (col) {
+							var ln = basicEditor.nthLine(line);
+							basicEditor.selectLines(ln, col);
+						} else
+							basicEditor.jumpToLine(line);
+					} else
+						basicEditor.jumpToLine(1);
+				} else
+					setTimeout(showCaret, 200);
+			};
+			showCaret();
+		};
+		function getLineNumber(s, token) {
+			var line = 1;
+			var pos = s.indexOf(token);
+			for (var i=0; i < pos; i++) {
+				if (s.charAt(i) == '\n')
+					line++;
+			}
+			return line;
+		};
+		function formatError(msg) {
+			var h = msg.replace(/</g, "&lt;");
+			h = h.replace(/>/g, "&gt;");
+			h = h.replace(/\n/g, "<br/>");
+			h = h.replace(/(\t)|([ ]{6})/g, "<span style='padding:0 2em'>&nbsp;</span>");
+			h = '<img src="/images/icon_alert_sm.png" class="image16"/> ' + h;
+			return h;
+		};
+		]]>
+	</script>
+</macro>
+
+<macro name="macro_viewer_js">
+	<script type="text/javascript">
+		var isCustomTweak = <n.is_custom_tweak/>;
+		var pBase = '&base=<n.hide_null.current_base_classes/>';
+		var pBreadcrumbs = '<n.hide_null.current_breadcrumbs/>';
+		pBreadcrumbs = pBreadcrumbs == ''? '' : '&breadcrumbs=' + pBreadcrumbs;
+		var isSiteAdmin = <n.visitor.is_site_admin/>;
+
+		var tweakFileContents = "<n.javascript_string_encode.hide_null.tweak_file_contents/>";
+        var macroOpeningTag = "<n.javascript_string_encode.hide_null.macro_opening_tag/>";
+		$(document).ready(function() {
+			function lineSelector(e) {
+				return 'div.line'+e.id.substring(8);
+			};
+			var $lineDivs = $('table.code div.line-contents');
+			$lineDivs.hover(
+				function() { $(lineSelector(this), $(this).parent().prev()).css('font-weight','bold').addClass('dark-bg-color'); },
+				function() { $(lineSelector(this), $(this).parent().prev()).css('font-weight','normal').removeClass('dark-bg-color'); }
+			);
+			function fixHeights() {
+				$lineDivs.each(function() {
+					var h = $(this).outerHeight();
+					var $table = $(this).parents('table');
+					$(lineSelector(this), $table).height(h);
+				});
+			}
+			$(window).resize(fixHeights);
+			fixHeights();
+
+			var $textarea = $('#txt_basic');
+			var textareaValue = $textarea.val().replace(/\s*/g, '');
+			var originalFileContents = tweakFileContents.replace(/\s*/g, '');
+			if (textareaValue != originalFileContents) {
+				showEditor();
+			}
+			window.onbeforeunload = function() {
+				$textarea.val(basicEditor.getCode());
+			};
+		});
+
+		function openNamlLogin() {
+			$('#naml-login').slideDown();
+		};
+
+		function showEditor() {
+			if (!isSiteAdmin) {
+				openNamlLogin();
+				return;
+			}
+			$('#saveButton,#cancelButton,#wrapping_basic,#resizebar').show();
+			$('#editButton,#removeButton').hide();
+			if (isCustomTweak)
+				$('#current-source').hide();
+			else
+				$('#overrides-arrow').show();
+			var line = getLineNumber(tweakFileContents, macroOpeningTag);
+			inlineEditor('20em', line);
+		};
+		function saveChanges() {
+			notice('Saving... Please wait');
+			var call = '/template/NamlServlet.jtp?macro=save_tweak&id=<n.id/>' + pBase + pBreadcrumbs;
+			var code = basicEditor.getCode();
+			if (code.match(/^\s*$/) != null) {
+				notice('Error Found', 3000, 1500);
+				var msg = "Error: invalid macro code.";
+				$('#error-box').html(msg).show();
+				return;
+			}
+			var params = { contents: code };
+			$.post(call, params,
+				function(data) {
+					var result = eval('(' + data + ')');
+					if (result.error != null) {
+						notice('Error Found', 3000, 1500);
+						var $errorBox = $('#error-box');
+						$errorBox.html(formatError(result.error)).show();
+						if ($errorBox.height() > 150) {
+							$errorBox.css('overflow-y','scroll').height(150);
+						}
+					} else {
+						pBase = result.base == null? pBase : '&base=' + result.base;
+						pBreadcrumbs = result.breadcrumbs == null? pBreadcrumbs : '&breadcrumbs=' + result.breadcrumbs;
+						var url = '/template/NamlServlet.jtp?macro=macro_viewer&id=' + result.macroId + pBase + pBreadcrumbs;
+						window.location = url;
+					}
+				}
+			);
+		};
+		function cancelChanges() {
+			$('#saveButton,#cancelButton,#wrapping_basic,#resizebar,#error-box').hide();
+			$('#editButton,#removeButton').show();
+			if (isCustomTweak)
+				$('#current-source').show();
+			else
+				$('#overrides-arrow').hide();
+			basicEditor.setCode(tweakFileContents);
+		};
+		function removeOverride() {
+			if (!isSiteAdmin) {
+				openNamlLogin();
+				return;
+			}
+			<n.if.is_override>
+				<then>
+					var confirmQuestion = "Do you really want to remove this override?";
+					var nextUrl = "<n.use_text_encoder.path_to_overridden_macro/>";
+				</then>
+				<else>
+					var confirmQuestion = "Do you really want to remove this macro?";
+					var nextUrl = "<n.use_text_encoder.macro_deleted_path/>";
+				</else>
+			</n.if.is_override>
+			if( !confirm(confirmQuestion) )
+				return;
+			var call = '/template/NamlServlet.jtp?macro=revert_tweak&id=<n.id/>' + pBase + pBreadcrumbs;
+			$.get(call, function(data) {
+				if (Nabble.trim(data) == 'ok')
+					window.location = nextUrl;
+			});
+		};
+	</script>
+</macro>
+
+<macro name="macro_viewer_stylesheet">
+	<style type="text/css">
+		table.code {
+			width: 100%;
+			font-family: verdana, arial, sans-serif;
+			font-size: 11pt;
+			border-collapse:collapse;
+			margin:.35em 0;
+		}
+		table.code tr {
+			vertical-align:top;
+		}
+		table.code td {
+			padding: 0;
+		}
+		table.code td.line-number {
+			width: 2em;
+			text-align:right;
+			padding-right:.3em;
+			border-right-width:2px;
+			border-right-style:solid;
+		}
+		table.code td.line-contents {
+			padding-left: .5em;
+		}
+		table.code a {
+			text-decoration:none;
+		}
+		table.code a:hover {
+			text-decoration:underline;
+		}
+	</style>
+</macro>
+
+<macro name="source_panel" requires="macro_source">
+	<n.editor_left_controls/>
+
+	<n.documentation/>
+	<div style="clear:both">
+		<n.source_contents/>
+		<n.overridden_macro_source/>
+	</div>
+</macro>
+
+<macro name="source_contents">
+	<n.if.not.is_binary>
+		<then>
+			<div id="error-box" class="error-message" style="padding:.5em;display:none"></div>
+			<textarea id="txt_basic" style="display:none"><n.encode.tweak_file_contents/></textarea>
+			<div id="current-source">
+				<div id="overrides-arrow" style="display:none">
+					<n.overrides_arrow/>
+				</div>
+				<n.show_notice_if_not_compiled/>
+				<table class="code">
+					<n.source_row/>
+					<tr>
+						<n.line_numbers_column/>
+						<n.line_contents_column/>
+					</tr>
+				</table>
+			</div>
+		</then>
+	</n.if.not.is_binary>
+</macro>
+
+<macro name="show_notice_if_not_compiled">
+	<n.if.not.is_compiled>
+		<then>
+			<div class="info-message rounded" style="padding:.4em; margin:.2em 0">
+				The source code below doesn't have navigation links because no usage has been compiled yet.
+				Navigation links depend on how and where the macro is used, so first you may
+				<a id="find-usages" href="#">try finding all usages of "<n.name/>"</a>.
+				<n.current_find_usages_path element_name="find-usages"/>
+			</div>
+		</then>
+	</n.if.not.is_compiled>
+</macro>
+
+<macro name="line_numbers_column">
+	<td class="line-number weak-color medium-border-color shaded-bg-color">
+		<n.rows.loop.>
+			<div class="line[n.file_line_number/]"><n.file_line_number/></div>
+		</n.rows.loop.>
+	</td>
+</macro>
+
+<macro name="line_contents_column">
+	<td class="line-contents">
+		<n.rows.loop.>
+			<div id="contents[n.file_line_number/]" class="line-contents">
+				<n.current_row_contents/>
+			</div>
+		</n.rows.loop.>
+	</td>
+</macro>
+
+<macro name="current_row_contents" requires="macro_row_list">
+	<n.current_row.>
+		<n.if.is_blank>
+			<then>&nbsp;</then>
+			<else>
+				<n.loop.parts.>
+					<n.if.not.is_null.id>
+						<then>
+							<n.remove_spaces_between_tags.>
+								<a href="[n.part_path/]"><n.tag/></a>
+								<n.part_tooltip/>
+							</n.remove_spaces_between_tags.>
+						</then>
+						<else.name/>
+					</n.if.not.is_null.id>
+				</n.loop.parts.>
+			</else>
+		</n.if.is_blank>
+	</n.current_row.>
+</macro>
+
+<macro name="part_tooltip" requires="command_info">
+	<n.tooltip.>
+		<n.if.is_binary>
+			<then>
+				<div class="bold">Binary</div>
+				<n.tooltip_small_row.><b>Namespace:</b> <n.namespace_class/></n.tooltip_small_row.>
+				<n.tooltip_parameters/>
+			</then>
+			<else>
+				<div class="bold">Macro</div>
+				<n.if.not.is_empty.required_namespaces>
+					<then>
+						<n.tooltip_small_row.><b>Requires:</b> <n.required_namespaces/></n.tooltip_small_row.>
+					</then>
+				</n.if.not.is_empty.required_namespaces>
+				<n.tooltip_parameters/>
+			</else>
+		</n.if.is_binary>
+	</n.tooltip.>
+</macro>
+
+<macro name="tooltip_parameters">
+	<n.if.has_parameters>
+		<then>
+			<n.tooltip_small_row.>
+				<b>Parameters:</b>
+				<n.remove_spaces.parameter_names.loop.>
+					<n.current_string/>
+					<n.if.has_more_strings>
+						<then>,&ensp;</then>
+					</n.if.has_more_strings>
+				</n.remove_spaces.parameter_names.loop.>
+			</n.tooltip_small_row.>
+		</then>
+	</n.if.has_parameters>
+</macro>
+
+<macro name="important_notices">
+	<n.if.has_tweak_exception>
+		<then>
+			<div class="weak-color info-message rounded" style="margin:.3em 0;padding:.3em .2em">
+				<table style="width:100%;table-layout:fixed">
+					<tr style="vertical-align:top">
+						<td style="width:32px"><img src="/images/icon_alert.png" style="width:32px;height:32px"/></td>
+						<td>
+							<b>Error Found</b><br/>
+							An error was found in the NAML code of this application &ndash;
+							<span class="view-exception-details">
+								<a href="#" onclick="showExceptionDetails(true)">View Details</a>
+							</span>
+							<span class="hide-exception-details invisible">
+								<a href="#" onclick="showExceptionDetails(false)">Hide Details</a>
+							</span>
+							| <n.advanced_editor_link text="[t]Go to NAML Editor[/t]"/>
+
+							<n.put_in_head.>
+								<style type="text/css">
+									div.stacktrace {
+										width:100%;
+										overflow:auto;
+										white-space:nowrap;
+										display:none;
+										margin-top:.5em;
+										font-size:80%;
+									}
+								</style>
+								<script type="text/javascript">
+									function showExceptionDetails(show) {
+										if (show) {
+											$('div.stacktrace').slideDown(function() { $(this).show() });
+											$('span.view-exception-details').hide();
+											$('span.hide-exception-details').show();
+										} else {
+											$('div.stacktrace').hide();
+											$('span.view-exception-details').show();
+											$('span.hide-exception-details').hide();
+										}
+									};
+								</script>
+							</n.put_in_head.>
+							<div class="stacktrace">
+								<n.regex_replace_all. pattern="\t+" replacement="&nbsp;&nbsp;">
+									<n.regex_replace_all. pattern="\n+" replacement="[br/]">
+										<n.use_html_encoder.encode.tweak_exception_message/>
+									</n.regex_replace_all.>
+								</n.regex_replace_all.>
+							</div>
+						</td>
+					</tr>
+				</table>
+			</div>
+		</then>
+	</n.if.has_tweak_exception>
+</macro>
+
+<macro name="path_to_overridden_macro">
+	<n.macro_viewer_path id="[n.macro_overridden_id/]" base="[n.current_base_classes/]" breadcrumbs="[n.current_breadcrumbs/]"/>
+</macro>
+
+<macro name="path_to_macro_which_overrides">
+	<n.macro_viewer_path id="[n.macro_which_overrides_id/]" base="[n.current_base_classes/]" breadcrumbs="[n.current_breadcrumbs/]"/>
+</macro>
+
+<macro name="save_button" requires="macro_source">
+	<button id="saveButton" class="toolbar" style="font-weight:bold;display:none" onclick="saveChanges()">
+		Save Changes
+	</button>
+</macro>
+
+<macro name="cancel_button" requires="macro_source">
+	<button id="cancelButton" class="toolbar" style="font-weight:bold;display:none" onclick="cancelChanges()">
+		Cancel
+	</button>
+</macro>
+
+<macro name="edit_button" requires="macro_source">
+	<button id="editButton" class="toolbar" style="font-weight:bold" onclick="showEditor()">
+		<img src="/images/tool.png" style="width:16px;height:17px;vertical-align:-25%"/>
+		<n.if.is_custom_tweak>
+			<then>
+				<n.if.is_override>
+					<then>Edit this override</then>
+					<else>Edit this macro</else>
+				</n.if.is_override>
+			</then>
+			<else>Override this macro</else>
+		</n.if.is_custom_tweak>
+	</button>
+</macro>
+
+<macro name="remove_button" requires="macro_source">
+	<n.if.is_custom_tweak>
+		<then>
+			<button id="removeButton" class="toolbar" style="font-weight:bold" onclick="removeOverride()">
+				<img src="/images/remove_sm.png" style="width:15px;height:15px;vertical-align:-25%"/>
+				<n.if.is_override>
+					<then>Remove override</then>
+					<else>Remove macro</else>
+				</n.if.is_override>
+			</button>
+		</then>
+	</n.if.is_custom_tweak>
+</macro>
+
+<macro name="editor_left_controls">
+	<div style="margin:.5em 0">
+		<n.if.not.is_binary>
+			<then>
+				<n.save_button/>
+				<n.cancel_button/>
+				<n.edit_button/>
+				<n.remove_button/>
+				&nbsp;&nbsp;
+				<n.usages_link/>
+			</then>
+		</n.if.not.is_binary>
+	</div>
+
+	<n.if.not.visitor.is_site_admin>
+		<then>
+			<div id="naml-login" class="border1 medium-border-color" style="padding:1em; display:none">
+				<div class="bold">You must login as an administrator of <n.root_node.subject/>.</div>
+				<div class="weak-color" style="margin-bottom:1em">
+					If you are not the administrator,
+					you can <n.nabble_homepage_link.>create your own Nabble app</n.nabble_homepage_link.> and customize its NAML code!
+				</div>
+
+				<n.nextUrl_field.set_value value="[n.use_text_encoder.current_url/]"/>
+				<n.login_form/>
+			</div>
+		</then>
+	</n.if.not.visitor.is_site_admin>
+</macro>
+
+<macro name="usages_link" requires="macro_source">
+	<a id="usages-link" href="#">Usages of this macro</a>
+	<n.current_find_usages_path element_name="usages-link"/>
+</macro>
+
+<macro name="macro_options_dropdown">
+	<n.dropdown.
+		id="macrodropdown"
+		element="[n.options_button/]"
+		loadOnClick="/template/NamlServlet.jtp?macro=macro_dropdown_later"
+	>
+		<n.menu_custom_macros/>
+		<n.menu_configuration_macros/>
+		<n.menu_new_macro/>
+		<n.menu_macro_search/>
+		<n.menu_view_logs/>
+		<n.menu_separator/>
+		<n.menu_open_advanced_editor/>
+	</n.dropdown.>
+
+	<n.custom_macros_contents/>
+	<n.configuration_macros_contents/>
+</macro>
+
+<macro name="options_button">
+	<button class="toolbar" style="font-weight:bold" title="[t]Click for more options[/t]">
+		<img src="/images/gear.png" class="image16"/>
+		<img src="/images/more.png" width="10" height="10"/>
+	</button>
+</macro>
+
+<macro name="macro_dropdown_later" requires="servlet">
+	<n.javascript_response/>
+	<n.menu_new_macro/>
+	<n.menu_open_advanced_editor/>
+</macro>
+
+<macro name="menu_custom_macros">
+	dropdown.addCustomSubmenu('Customized macros', 'custom-macros-box');
+</macro>
+
+<macro name="menu_configuration_macros">
+	<n.if.has_configuration_macros>
+		<then>dropdown.addCustomSubmenu('Macros overridden by configurations', 'configuration-macros-box');</then>
+	</n.if.has_configuration_macros>
+</macro>
+
+<macro name="custom_macros_contents">
+	<n.custom_macros.list_macros_available id="custom-macros-box"/>
+</macro>
+
+<macro name="configuration_macros_contents">
+	<n.if.has_configuration_macros>
+		<then.configuration_macros.list_macros_available id="configuration-macros-box"/>
+	</n.if.has_configuration_macros>
+</macro>
+
+<macro name="list_macros_available" parameters="id" requires="command_list">
+	<n.put_in_head.>
+		<style type="text/css">
+			ul.dropdown-submenu {
+				max-height:20em;
+				overflow-y:auto;
+			}
+			ul.dropdown-submenu li {
+				padding-right:1em;
+			}
+		</style>
+	</n.put_in_head.>
+	<ul id="[n.id/]" class="dropdown-submenu light-bg-color medium-border-color drop-shadow">
+		<n.if.has_more_elements>
+			<then>
+				<n.loop.current_command.>
+					<li class="nowrap">
+						<a href="[n.command_path/]"><n.name/></a>
+						<n.if.not.is_empty.required_namespaces>
+							<then>
+								<n.tooltip. delay="50" position="right">Requires <n.required_namespaces/></n.tooltip.>
+							</then>
+						</n.if.not.is_empty.required_namespaces>
+					</li>
+				</n.loop.current_command.>
+			</then>
+			<else>
+				<li class="weak-color" style="padding:.4em .8em">None</li>
+			</else>
+		</n.if.has_more_elements>
+	</ul>
+</macro>
+
+<macro name="menu_macro_search">
+	dropdown.add('searchMacros', '<n.javascript_string_encode.macro_search_link/>');
+</macro>
+
+<macro name="macro_search_link">
+	<a href="[n.macro_search_path/]">Search macros</a>
+</macro>
+
+<macro name="macro_search_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=macro_search_page
+	</n.encode_url.>
+</macro>
+
+<macro name="menu_view_logs">
+	dropdown.add('viewLogs', '<n.javascript_string_encode.root_node.view_logs_link/>');
+</macro>
+
+<macro name="menu_new_macro">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('newMacro', '<n.javascript_string_encode.new_macro_link/>', 'display:none');
+		</then>
+		<else>
+			<n.if.visitor.is_site_admin>
+				<then>
+					NabbleDropdown.show('newMacro');
+				</then>
+			</n.if.visitor.is_site_admin>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="menu_open_advanced_editor">
+	<n.if.is_in_command name="dropdown">
+		<then>
+			dropdown.add('openAdvEditor', '<n.javascript_string_encode.advanced_editor_link/>', 'display:none');
+		</then>
+		<else>
+			<n.if.visitor.is_site_admin>
+				<then>
+					NabbleDropdown.show('openAdvEditor');
+				</then>
+			</n.if.visitor.is_site_admin>
+		</else>
+	</n.if.is_in_command>
+</macro>
+
+<macro name="advanced_editor_link" dot_parameter="text" parameters="title, class">
+	<a href="[n.current_advanced_editor_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]" target="_top">
+		<n.default. to="[t]Go to Advanced Editor[/t]"><n.text/></n.default.>
+	</a>
+</macro>
+
+<macro name="current_advanced_editor_path">
+	<n.advanced_editor_path prev_url="[n.current_url/]"/>
+</macro>
+
+<macro name="new_macro_link">
+	<a href="[n.new_macro_path/]">New macro</a>
+</macro>
+
+<macro name="macro_viewer_path" parameters="id,base,breadcrumbs">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=macro_viewer
+		&id=<n.encode_text.id/>
+		<n.add_to_path name="base" value="[n.base/]" />
+		<n.add_to_path name="breadcrumbs" value="[n.breadcrumbs/]" />
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="part_path" requires="command_info">
+	<n.macro_viewer_path>
+		<id><n.id/></id>
+		<base><n.base/></base>
+		<breadcrumbs><n.naml_breadcrumbs/></breadcrumbs>
+	</n.macro_viewer_path>
+</macro>
+
+<macro name="macro_viewer_page_link">
+	<a href="[n.macro_viewer_page_path/]" rel="nofollow">Edit this page</a>
+</macro>
+
+<macro name="macro_viewer_page_path">
+	<n.macro_viewer_path>
+		<id><n.page_template_command_id/></id>
+		<base><n.page_base_classes/></base>
+	</n.macro_viewer_path>
+</macro>
+
+<macro name="find_usages_path" parameters="id,base,breadcrumbs">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=find_usages_page
+		&id=<n.encode_text.id/>
+		<n.add_to_path name="base" value="[n.base/]" />
+		<n.add_to_path name="breadcrumbs" value="[n.breadcrumbs/]" />
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="current_find_usages_path" parameters="element_name">
+	<script type="text/javascript">
+		<n.compress.>
+			var _path = '/template/NamlServlet.jtp?';
+			var _p1 = 'macro=find_usages_page';
+			var _p2 = '&id=<n.current_command_id/>';
+
+			<n.if.not.is_null.current_base_classes>
+				<then>var _p3 = '&base=<n.current_base_classes/>';</then>
+				<else> var _p3 = '';</else>
+			</n.if.not.is_null.current_base_classes>
+
+			<n.if.not.is_null.current_breadcrumbs>
+				<then>var _p4 = '&breadcrumbs=<n.current_breadcrumbs/>';</then>
+				<else> var _p4 = '';</else>
+			</n.if.not.is_null.current_breadcrumbs>
+
+			var _link = document.getElementById('<n.element_name/>');
+			$(document).ready(function() {
+				$(_link).click(function() {
+					notice('Finding Usages... Please wait');
+					window.location = _path+_p1+_p2+_p3+_p4+'&js='+Math.random();
+				});
+			});
+		</n.compress.>
+	</script>
+</macro>
+
+<macro name="find_usages_page" requires="servlet">
+	<n.if.not.is_null.get_parameter name="js">
+		<then>
+			<n.if.command_exists id="[n.current_command_id/]">
+				<then>
+					<n.macro_source.
+						id="[n.current_command_id/]"
+						base="[n.current_base_classes/]"
+						breadcrumbs="[n.current_breadcrumbs/]"
+					>
+						<n.find_usages_html/>
+					</n.macro_source.>
+				</then>
+				<else>
+					<n.invalid_command_html/>
+				</else>
+			</n.if.command_exists>
+		</then>
+	</n.if.not.is_null.get_parameter>
+</macro>
+
+<macro name="find_usages_html" requires="macro_source">
+	<n.compile_if_needed/>
+	<n.naml_tools.>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.title.>Usages of <n.name/></n.title.>
+				<style type="text/css">
+					div.usage-path { padding: .5em; }
+				</style>
+			</head>
+			<body>
+				<n.macro_viewer_header.>
+					Usages of <n.name/>
+				</n.macro_viewer_header.>
+
+				<n.macro_usages.>
+					<n.if.not.has_more_elements>
+						<then>
+							<p><b>No usages found.</b> Some possible reasons are:</p>
+							<ul>
+								<li>This macro is called directly from the URL or from the java code;</li>
+								<li>This macro is not used at all;</li>
+							</ul>
+							<p>&laquo; <a href="javascript:void history.back()">Go Back</a></p>
+						</then>
+					</n.if.not.has_more_elements>
+
+					<n.loop.>
+						<div class="[n.alternate var='usages' first_value='usage-path' second_value='usage-path light-bg-color'/]">
+							<n.remove_spaces_between_tags.>
+								<n.current_usage.loop.>
+									<a href="[n.current_command.command_path/]"><n.current_command.name/></a>
+									<n.if.has_more_elements>
+										<then><span style="padding:0 .5em">&rsaquo;</span></then>
+									</n.if.has_more_elements>
+								</n.current_usage.loop.>
+							</n.remove_spaces_between_tags.>
+						</div>
+					</n.loop.>
+				</n.macro_usages.>
+			</body>
+		</n.html>
+	</n.naml_tools.>
+</macro>
+
+<macro name="redirect_if_overridden" requires="macro_source">
+	<n.if.has_macro_which_overrides>
+		<then>
+			<script type="text/javascript">
+				location = '<n.use_text_encoder.path_to_macro_which_overrides/>';
+			</script>
+		</then>
+	</n.if.has_macro_which_overrides>
+</macro>
+
+<subroutine name="overridden_macro_source" requires="basic,nabble,servlet,html,macro_source">
+	<n.if.has_macro_overridden>
+		<then>
+			<n.macro_source. id="[n.macro_overridden_id/]" base="[n.current_base_classes/]" breadcrumbs="[n.current_breadcrumbs/]">
+				<n.overrides_arrow/>
+				<table class="code">
+					<n.source_row/>
+					<tr>
+						<n.line_numbers_column/>
+						<n.line_contents_column/>
+					</tr>
+				</table>
+				<n.overridden_macro_source/>
+			</n.macro_source.>
+		</then>
+	</n.if.has_macro_overridden>
+</subroutine>
+
+<macro name="overrides_arrow" requires="macro_source">
+	<div style="font-weight:bold;margin:.5em">
+		<span style="font-size:300%;font-family:Arial,Sans-serif;line-height:.9em;vertical-align:-15%">&darr;</span>
+		<n.if.is_custom_tweak>
+			<then>Overrides administrator change</then>
+			<else>
+				<n.if.is_configuration_tweak>
+					<then>Overrides configuration</then>
+					<else>Overrides default macro</else>
+				</n.if.is_configuration_tweak>
+			</else>
+		</n.if.is_custom_tweak>
+	</div>
+</macro>
+
+<macro name="source_row" requires="macro_source">
+	<tr>
+		<td class="line-number weak-color medium-border-color shaded-bg-color" style="font-size:75%;padding-bottom:.4em">...</td>
+		<td class="line-contents weak-color" style="font-size:75%;padding-bottom:.4em">
+			in <a href="[n.source_path/]"><n.clean_source_name.source/></a>
+		</td>
+	</tr>
+</macro>
+
+<macro name="clean_source_name" dot_parameter="source">
+	<n.regex_replace_all. pattern="^.+:" replacement="">
+		<n.source/>
+	</n.regex_replace_all.>
+</macro>
+
+<macro name="revert_tweak" requires="servlet">
+	<n.if.visitor.is_site_admin>
+		<then.macro_editor. id="[n.current_command_id/]" base="[n.current_base_classes/]" breadcrumbs="[n.current_breadcrumbs/]">
+			<n.revert/>
+			ok
+		</then.macro_editor.>
+	</n.if.visitor.is_site_admin>
+</macro>
+
+<macro name="save_tweak" requires="servlet">
+	<n.if.visitor.is_site_admin>
+		<then.macro_editor. id="[n.current_command_id/]" base="[n.current_base_classes/]" breadcrumbs="[n.current_breadcrumbs/]">
+			<n.save contents="[n.get_parameter name='contents'/]"/>
+		</then.macro_editor.>
+	</n.if.visitor.is_site_admin>
+</macro>
+
+<macro name="command_path" requires="command_info">
+	<n.macro_viewer_path>
+		<id><n.use_text_encoder.id/></id>
+		<base><n.base/></base>
+		<breadcrumbs><n.naml_breadcrumbs/></breadcrumbs>
+	</n.macro_viewer_path>
+</macro>
+
+<macro name="invalid_command_html">
+	<n.naml_tools.>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.title.>Invalid Command</n.title.>
+			</head>
+			<body>
+				<n.macro_viewer_header.>
+					Invalid Command
+				</n.macro_viewer_header.>
+
+				<n.if.command_is_binary id="[n.current_command_id/]">
+					<then>
+						<h2>Command Not Found</h2>
+						Meaning ID: <n.current_command_id/>
+					</then>
+					<else>
+						<n.set_var. name="macro_name">
+							<n.command_name id="[n.current_command_id/]"/>
+						</n.set_var.>
+
+						The macro you are looking for could not be found (it may have been modified recently):
+						<div style="font-size:120%;margin:.6em">
+							<n.bold.var name="macro_name"/> in <i><n.command_source_name id="[n.current_command_id/]"/></i>
+						</div>
+
+						<n.macro_search. query="[n.var name='macro_name'/]" search_by="name">
+							<n.if.has_more_elements>
+								<then>
+									<div style="margin:1.5em 0 0">
+										Here you can find other related macros:
+									</div>
+									<n.macro_results_table/>
+								</then>
+							</n.if.has_more_elements>
+						</n.macro_search.>
+					</else>
+				</n.if.command_is_binary>
+			</body>
+		</n.html>
+	</n.naml_tools.>
+</macro>
+
+<macro name="macro_search_page" requires="servlet">
+	<n.naml_tools.>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,follow"/>
+				<n.title.>Search Macros</n.title.>
+				<script type="text/javascript">
+					$(document).ready(function() {
+						var $input = $('#search_field');
+						$input.keydown(function(event) {
+							if (event.keyCode == 13) {
+								event.preventDefault();
+								$('form').submit();
+							}
+						});
+						$input.focus();
+					});
+				</script>
+			</head>
+			<body>
+				<n.macro_viewer_header.>
+					Macro Search
+				</n.macro_viewer_header.>
+
+				<n.if.is_empty.search_by_field.value>
+					<then.search_by_field.set_value value="name"/>
+				</n.if.is_empty.search_by_field.value>
+
+				<n.form. macro="macro_search_page" method="GET">
+					<img src="/images/search.png" class="image16"/>
+					<input id="search_field" type="text" size="25" name="query" value="[n.hide_null.macro_search_query/]"/>
+					<input class="toolbar action-button" type="submit" value="[t]Search[/t]"/>
+					<div style="clear:both;padding:.3em 0 0 1em">
+						<n.search_by_field.radio id="by_name" option_value="name"/>
+						<label for="by_name">Macro by name</label><br/>
+						<n.search_by_field.radio id="by_text" option_value="text"/>
+						<label for="by_text">Macro source contains</label><br/>
+					</div>
+				</n.form.>
+
+				<div style="margin-top:1em">
+					<n.macro_search_results query="[n.macro_search_query/]" search_by="[n.search_by_field.value/]"/>
+				</div>
+			</body>
+		</n.html>
+	</n.naml_tools.>
+</macro>
+
+<macro name="macro_search_results" parameters="query, search_by">
+	<n.macro_search. query="[n.query/]" search_by="[n.search_by/]">
+		<n.if.not.has_more_elements>
+			<then>
+				<n.if.not.is_null.macro_search_query>
+					<then>
+						<div class="big-title second-font">No macros found.</div>
+					</then>
+				</n.if.not.is_null.macro_search_query>
+			</then>
+			<else>
+				<div class="big-title second-font">
+					<n.element_count/> macro(s) found.
+				</div>
+				<n.macro_results_table/>
+			</else>
+		</n.if.not.has_more_elements>
+	</n.macro_search.>
+</macro>
+
+<macro name="macro_results_table" requires="command_list">
+	<n.put_in_head.>
+		<style type="text/css">
+			table.macro-results {
+				margin-top:.5em;
+				border-spacing: 0;
+			}
+			table.macro-results td {
+				padding: .4em 1.2em;
+			}
+		</style>
+	</n.put_in_head.>
+	<n.zebra_table_javascript table_selector="#macro-results"/>
+	<table id="macro-results" class="macro-results weak-color">
+		<tr class="shaded-bg-color bold">
+			<td>Macro Name</td>
+			<td>Requires</td>
+			<td>Source File</td>
+		</tr>
+		<n.loop.current_command.>
+			<tr style="[n.if.is_custom_tweak][then]font-weight:bold[/then][/n.if.is_custom_tweak]">
+				<td><a href="[n.command_path/]"><n.name/></a></td>
+				<td><em><n.hide_null.required_namespaces/></em></td>
+				<td><n.clean_source_name.source/></td>
+			</tr>
+		</n.loop.current_command.>
+	</table>
+</macro>
+
+<macro name="macro_search_query">
+	<n.get_parameter name="query"/>
+</macro>
+
+<macro name="current_command_id">
+	<n.get_parameter name="id"/>
+</macro>
+
+<macro name="current_base_classes">
+	<n.get_parameter name="base"/>
+</macro>
+
+<macro name="current_breadcrumbs">
+	<n.get_parameter name="breadcrumbs"/>
+</macro>
+
+<macro name="search_by_field" dot_parameter="do">
+	<n.field. name="search_by"><n.do/></n.field.>
+</macro>
+
+<macro name="compile_if_needed">
+	<n.if.not.is_compiled_all>
+		<then.run_compile_all/>
+	</n.if.not.is_compiled_all>
+</macro>
+
+<macro name="new_macro_page" requires="servlet">
+	<n.naml_tools.>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.title.>New Macro</n.title.>
+				<n.codemirror_shared_head/>
+				<script type="text/javascript">
+					<![CDATA[
+					var defaultMacroBody = '<macro name="">\n\n</'+'macro>';
+					$(document).ready(function() {
+						var $textarea = $('#txt_basic');
+						if ($textarea.val() == '')
+							$textarea.val(defaultMacroBody);
+						var canGoBack = history.length > 1;
+						if (canGoBack)
+							$('#go-back').show().before('<t>or</t> ');
+						inlineEditor('27em', 1, 13);
+
+						window.onbeforeunload = function() {
+							$textarea.val(basicEditor.getCode());
+						};
+					});
+
+					function saveChanges() {
+						notice('Saving... Please wait');
+						var call = '/template/NamlServlet.jtp?macro=save_tweak';
+						var code = basicEditor.getCode();
+						if (code.match(/^\s*$/) != null) {
+							notice('Error Found', 3000, 1500);
+							var msg = "Error: invalid macro code.";
+							var $errorBox = $('#error-box');
+							$errorBox.html(formatError(msg)).show();
+							if ($errorBox.height() > 150) {
+								$errorBox.css('overflow-y','scroll').height(150);
+							}
+							return;
+						}
+						var params = { contents: code };
+						$.post(call, params,
+							function(data) {
+								var result = eval('(' + data + ')');
+								if (result.error != null) {
+									notice('Error Found', 3000, 1500);
+									$('#error-box').html(formatError(result.error)).show();
+								} else {
+									var url = '/template/NamlServlet.jtp?macro=macro_viewer&id=' + result.macroId;
+									window.location = url;
+								}
+							}
+						);
+					};
+					]]>
+				</script>
+			</head>
+			<body>
+				<n.macro_viewer_header.>
+					New Macro
+				</n.macro_viewer_header.>
+
+				<n.important_notices/>
+
+				<div style="clear:both">
+					<n.macro_options_dropdown/>
+					<button id="saveButton" class="toolbar" style="font-weight:bold" onclick="saveChanges()">
+						Save Changes
+					</button>
+					<a id="go-back" href="javascript: void history.back()" style="display:none"><t>Go back</t></a>
+
+					<div style="clear:both">
+						<div id="error-box" class="error-message" style="padding:.5em;display:none"></div>
+						<textarea id="txt_basic" style="display:none"></textarea>
+					</div>
+				</div>
+			</body>
+		</n.html>
+	</n.naml_tools.>
+</macro>
+
+<macro name="macro_deleted_page" requires="servlet">
+	<n.naml_tools.>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.title.>Macro Deleted</n.title.>
+			</head>
+			<body>
+				<n.macro_viewer_header.>
+					Macro Deleted
+				</n.macro_viewer_header.>
+
+				<n.important_notices/>
+
+				<div style="clear:both">
+					<h2 style="padding-bottom:1em">
+						<img src="/images/success.png" class="image16"/>
+						Macro successfully deleted.
+					</h2>
+					<n.macro_options_dropdown/>
+				</div>
+
+				<br/>
+				<br/>
+			</body>
+		</n.html>
+	</n.naml_tools.>
+</macro>
+
+<macro name="new_macro_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=new_macro_page
+	</n.encode_url.>
+</macro>
+
+<macro name="macro_deleted_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=macro_deleted_page
+	</n.encode_url.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/mailing_list.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,28 @@
+<macro name="send_mail_to_list" unindent="true">
+	<n.new_node.get_associated_mailing_list_archive.>
+		<n.new_email.>
+			<n.send>
+				<from><n.new_node.owner.user_email/></from>
+				<from_name><n.new_node.owner.name/></from_name>
+				<to><n.mailing_list_address/></to>
+				<subject><n.new_node.subject/></subject>
+				<text_part>
+					<n.new_node.text_email_message_with_signature />
+					--
+					Sent from: <n.mailing_list_node.url/>
+				</text_part>
+				<html_part>
+					<n.if.either condition1="[n.plain_text_only/]" condition2="[n.new_node.message.is_text_format/]">
+						<then.null/>
+						<else>
+							<n.new_node.html_email_message_with_signature />
+							<br/><hr align="left" width="300" />
+							Sent from the <a href="[n.mailing_list_node.url/]"><n.mailing_list_node.subject/> mailing list archive</a> at Nabble.com.<br/>
+						</else>
+					</n.if.either>
+				</html_part>
+				<set_headers_for.new_node/>
+			</n.send>
+		</n.new_email.>
+	</n.new_node.get_associated_mailing_list_archive.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/mailing_list_settings.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,246 @@
+<macro name="mailing_list_archive_settings" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_edit.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_edit.page_node>
+
+		<n.page_node.get_this_mailing_list_archive.>
+			<n.if.not.is_submitted_form>
+				<then.fill_in_mailing_list_archive_fields/>
+				<else>
+					<n.if.is_remove_mailing_list_archive_settings>
+						<then.remove_mailing_list_archive_settings_and_redirect/>
+						<else.save_mailing_list_archive_settings_and_redirect/>
+					</n.if.is_remove_mailing_list_archive_settings>
+				</else>
+			</n.if.not.is_submitted_form>
+
+			<n.html>
+				<head>
+					<META NAME="robots" CONTENT="noindex,nofollow"/>
+					<n.title.><t>Mailing List Archive Settings</t></n.title.>
+					<n.mailing_list_address_field.focus/>
+					<style type="text/css">
+						div.field-title { margin-top: 0; }
+						div.tip { font-size:80%; margin-top:.4em; }
+					</style>
+					<script type="text/javascript">
+						$(document).ready(function() {
+							$('form').submit(function() {
+								var action = $('#action').val();
+								if (action == 'remove')
+									return confirm('<t>Do you really want to remove the mailing list archive settings?</t>');
+								return true;
+							});
+						});
+					</script>
+				</head>
+				<body>
+					<n.edit_header first_text="[n.page_node.get_app_node.subject/]" second_text="[t]Mailing List Archive Settings[/t]" />
+
+					<n.if.is_submitted_form>
+						<then.show_mailing_list_error/>
+					</n.if.is_submitted_form>
+
+					<n.form.>
+						<input id="action" type="hidden" name="action" value="save"/>
+						<n.mailing_list_address_group/>
+						<n.mailing_list_website_group/>
+						<n.mailing_list_server_group/>
+						<n.mailing_list_subject_prefix_group/>
+						<n.mailing_list_other_settings_group/>
+
+						<input type="submit" class="toolbar action-button" value="[t]Save Settings[/t]" onclick="$('#action').val('save')"/>
+						<input type="submit" class="toolbar action-button" value="[t]Remove Settings[/t]" onclick="$('#action').val('remove')"/>
+						<t>or</t> <a href="[n.page_node.url /]"><t>Cancel</t></a>
+					</n.form.>
+				</body>
+			</n.html>
+		</n.page_node.get_this_mailing_list_archive.>
+	</n.node_page.>
+</macro>
+
+<macro name="fill_in_mailing_list_archive_fields" requires="mailing_list">
+	<n.if.page_node.is_a_mailing_list_archive>
+		<then>
+			<n.mailing_list_address_field.set_value value="[n.mailing_list_address/]" />
+			<n.mailing_list_url_field.set_value value="[n.mailing_list_url/]" />
+			<n.mailing_list_server_field.set_value value="[n.mailing_list_server/]" />
+			<n.mailing_list_prefix_field.set_value value="[n.mailing_list_prefix/]" />
+			<n.plain_text_only_field.set_value value="[n.plain_text_only/]" />
+			<n.ignore_xnoarchive_field.set_value value="[n.ignore_xnoarchive/]" />
+		</then>
+		<else>
+			<n.mailing_list_server_field.set_value value="unknown" />
+		</else>
+	</n.if.page_node.is_a_mailing_list_archive>
+</macro>
+
+<macro name="remove_mailing_list_archive_settings_and_redirect" requires="mailing_list, node_page">
+	<n.catch_exception. id="save-block">
+		<n.remove/>
+		<n.redirect_to.page_node.url/>
+	</n.catch_exception.>
+</macro>
+
+<macro name="save_mailing_list_archive_settings_and_redirect" requires="mailing_list, node_page">
+	<n.catch_exception. id="save-block">
+		<n.set_var. name="is-new">
+			<n.if.page_node.is_a_mailing_list_archive>
+				<then>no</then>
+				<else>yes</else>
+			</n.if.page_node.is_a_mailing_list_archive>
+		</n.set_var.>
+		<n.save>
+			<address><n.mailing_list_address_field.value/></address>
+			<url><n.mailing_list_url_field.value/></url>
+			<server><n.mailing_list_server_field.value/></server>
+			<prefix><n.mailing_list_prefix_field.value/></prefix>
+			<plain_text_only><n.plain_text_only_field.value/></plain_text_only>
+			<ignore_xnoarchive><n.ignore_xnoarchive_field.value/></ignore_xnoarchive>
+			<do>
+			</do>
+		</n.save>
+		<n.if.equal value1="[n.var name='is-new'/]" value2="yes">
+			<then.redirect_to.page_node.subscription_instructions_path/>
+			<else.redirect_to.page_node.url/>
+		</n.if.equal>
+	</n.catch_exception.>
+</macro>
+
+<macro name="mailing_list_address_group" requires="mailing_list">
+	<div class="field-box light-border-color">
+		<div class="second-font field-title"><t>Mailing List Address</t> <span class="important">*</span></div>
+		<div class="weak-color">
+			<n.mailing_list_address_field.input size="40" />
+		</div>
+		<div class="weak-color tip">
+			<t>Example: listname@listdomain.com</t>
+		</div>
+
+		<n.if.page_node.is_a_mailing_list_archive>
+			<then>
+				<div class="shaded-bg-color rounded" style="padding: .6em .5em;margin:.4em 0 0">
+					<div style="float:left;height:2em;margin-right:.5em">
+						<img src="/images/tool_big.png" width="27" height="27"/>
+					</div>
+					<div style="margin-left:.5em">
+						<t><b>IMPORTANT</b>: You must subscribe this archive to the mailing list in order to make it work.</t><br/>
+						<div style="margin-top:.3em">
+							<t>Please follow the <n.subscribe_instructions_link.>subscription instructions</n.subscribe_instructions_link.> for this archive.</t>
+						</div>
+					</div>
+				</div>
+			</then>
+		</n.if.page_node.is_a_mailing_list_archive>
+	</div>
+</macro>
+
+<macro name="mailing_list_website_group" requires="mailing_list">
+	<div class="field-box light-border-color">
+		<div class="second-font field-title"><t>Mailing List Website</t> <span class="important">*</span></div>
+		<div class="weak-color">
+			<t>Enter the homepage of this mailing list, where users can find more information.</t><br/>
+			<n.mailing_list_url_field.input size="72" />
+		</div>
+	</div>
+</macro>
+
+<macro name="mailing_list_server_group" requires="mailing_list">
+	<div class="field-box light-border-color">
+		<div class="second-font field-title"><t>List Server</t></div>
+		<div class="weak-color">
+			<t>If you know, please select the mailing list server application and its version.</t><br/>
+			<n.mailing_list_server_field.select.>
+				<n.available_servers.loop.>
+					<n.set_var. name='server_name'>
+						<n.if.equal value1="[n.current_server_name/]" value2="Unknown or Other">
+							<then><t>Unknown or Other</t></then>
+							<else><n.current_server_name/></else>
+						</n.if.equal>
+					</n.set_var.>
+					<n.select_option value="[n.current_server/]" selectedValue="[n.mailing_list_server_field.value/]" text="[n.var name='server_name'/]"/>
+				</n.available_servers.loop.>
+			</n.mailing_list_server_field.select.>
+		</div>
+	</div>
+</macro>
+
+<macro name="mailing_list_subject_prefix_group" requires="mailing_list">
+	<div class="field-box light-border-color">
+		<div class="second-font field-title"><t>Subject Prefix</t></div>
+		<div class="weak-color">
+			<t>Enter the prefix that this mailing list uses before the subject.
+			The prefix will be automatically removed from the imported emails.</t>
+			<br/>
+			<n.mailing_list_prefix_field.input size="25" />
+			<div class="weak-color tip">
+				<t>Examples: '[the-list]', 'Abc:', etc.</t>
+			</div>
+		</div>
+	</div>
+</macro>
+
+<macro name="mailing_list_other_settings_group" requires="mailing_list">
+	<div class="field-box light-border-color">
+		<div class="second-font field-title"><t>Other Settings</t></div>
+		<table>
+			<tr>
+				<td><n.plain_text_only_field.checkbox /></td>
+				<td><label for="[n.plain_text_only_field.name/]"><t>This list accepts only plain-text emails</t></label></td>
+			</tr>
+			<tr style="vertical-align:top">
+				<td><n.ignore_xnoarchive_field.checkbox /></td>
+				<td style="padding-top:.2em">
+					<label for="[n.ignore_xnoarchive_field.name/]"><t>Ignore X-No-Archive header</t></label>
+					<div class="weak-color tip"><t>TIP: If your archive is subscribed to the mailing list and it is still not working, you may try ignoring the X-No-Archive header.</t></div>
+				</td>
+			</tr>
+		</table>
+	</div>
+</macro>
+
+<macro name="subscribe_instructions_link" dot_parameter="text" requires="node_page">
+	<a href="[n.page_node.subscription_instructions_path/]"><n.text/></a>
+</macro>
+
+<macro name="mailing_list_address_field" dot_parameter="do">
+	<n.field. name="mailing_list_address"><n.do/></n.field.>
+</macro>
+
+<macro name="mailing_list_url_field" dot_parameter="do">
+	<n.field. name="mailing_list_url"><n.do/></n.field.>
+</macro>
+
+<macro name="mailing_list_server_field" dot_parameter="do">
+	<n.field. name="mailing_list_server"><n.do/></n.field.>
+</macro>
+
+<macro name="mailing_list_prefix_field" dot_parameter="do">
+	<n.field. name="mailing_list_prefix"><n.do/></n.field.>
+</macro>
+
+<macro name="plain_text_only_field" dot_parameter="do">
+	<n.field. name="plain_text_only"><n.do/></n.field.>
+</macro>
+
+<macro name="ignore_xnoarchive_field" dot_parameter="do">
+	<n.field. name="ignore_xnoarchive"><n.do/></n.field.>
+</macro>
+
+<macro name="show_mailing_list_error">
+	<n.format_error.handle_exception. for="save-block">
+		<n.exception. name="invalid_list_address">
+			<t>You must enter a valid email address for this mailing list.</t>
+		</n.exception.>
+		<n.exception. name="invalid_list_url">
+			<t>You must enter a valid website URL for this mailing list.</t>
+		</n.exception.>
+	</n.format_error.handle_exception.>
+</macro>
+
+<macro name="is_remove_mailing_list_archive_settings">
+	<n.equal value1="[n.get_parameter name='action'/]" value2="remove"/>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/manage_banned_users.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,59 @@
+<macro name="manage_banned_users">
+	<n.if.not.visitor.can_manage_banned_users>
+		<then>
+			<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+		</then>
+	</n.if.not.visitor.can_manage_banned_users>
+	<n.html>
+		<head>
+			<META NAME="robots" CONTENT="noindex,nofollow"/>
+			<n.title.><t>Banned Users in <t.location.root_node.subject/></t></n.title.>
+			<n.zebra_table_javascript table_selector="table.banned-users"/>
+			<style type="text/css">
+				table.banned-users {
+					border-collapse:collapse;
+				}
+				table.banned-users th {
+					padding: .3em .6em;
+					text-align:left;
+				}
+				table.banned-users td {
+					padding: .5em;
+				}
+				table.banned-users td.avatar-col {
+					text-align:center;
+					padding: .5em 0;
+				}
+			</style>
+		</head>
+		<body>
+			<n.edit_header first_text="[t]Banned Users in [t.location.root_node.subject/][/t]"/>
+
+			<div style="margin-top:1em">
+				<n.banned_users.>
+					<n.if.has_more_elements>
+						<then>
+							<table class="banned-users">
+								<tr>
+									<th style="width:24px"></th>
+									<th style="min-width:15em"><t>Name</t></th>
+									<th><t>Action</t></th>
+								</tr>
+								<n.loop.>
+									<tr>
+										<td class="avatar-col"><n.current_user.avatar/></td>
+										<td><n.current_user.name/></td>
+										<td><a href="[n.current_user.unban_path/]"><t>Unban this user</t></a></td>
+									</tr>
+								</n.loop.>
+							</table>
+						</then>
+						<else>
+							<t>No banned users.</t>
+						</else>
+					</n.if.has_more_elements>
+				</n.banned_users.>
+			</div>
+		</body>
+	</n.html>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/manage_subscribers.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,189 @@
+<macro name="manage_subscribers" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_manage_subscribers_of.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_manage_subscribers_of.page_node>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Manage Subscribers</t></n.title.>
+			</head>
+			<body>
+				<n.edit_header first_text="[t]Manage Subscribers[/t]" second_text="[n.page_node.get_app_node.subject/]" />
+				<n.manage_subscribers_controls/>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="manage_subscribers_controls" requires="node_page">
+	<n.horizontal_tab_control.>
+		<n.current_subscribers_horizontal_tab/>
+		<n.add_subscribers_horizontal_tab/>
+	</n.horizontal_tab_control.>
+</macro>
+
+<macro name="current_subscribers_horizontal_tab">
+	<n.add_horizontal_tab
+		url="[n.page_node.manage_subscribers_path/]"
+		text="[t]Current Subscribers[/t]"
+		selected="[n.is_subscriber_filter/]"
+		details="[n.page_node.list_subscribers/]"
+	/>
+</macro>
+
+<macro name="add_subscribers_horizontal_tab">
+	<n.add_horizontal_tab
+		url="[n.page_node.manage_subscribers_path filter='invite'/]"
+		text="[t]Invite Subscribers[/t]"
+		selected="[n.is_subscriber_filter value='invite'/]"
+		details="[n.page_node.add_subscribers/]"
+	/>
+</macro>
+
+<macro name="add_subscribers" requires="node">
+	<t>If you want to invite subscribers, please request this feature in the <n.support_link/> forum.</t>
+	<t>We can install this module for you, but the Nabble team must approve this request to prevent abuse and spam.</t>
+</macro>
+
+<macro name="if_is_tab" parameters="filter" dot_parameter="do">
+	<n.if.is_subscriber_filter value="[n.filter/]">
+		<then.do/>
+	</n.if.is_subscriber_filter>
+</macro>
+
+<macro name="list_subscribers" requires="node">
+	<n.if.is_submitted_form>
+		<then.process_unsubscription/>
+	</n.if.is_submitted_form>
+
+	<n.put_in_head.>
+		<style type="text/css">
+			table.subscriptions {
+				border-collapse:collapse;
+				margin-top:1em;
+				clear:both;
+				width:100%
+			}
+			table.subscriptions th {
+				padding: .3em .6em;
+				text-align:left;
+				border-bottom-style:solid;
+				border-bottom-width:2px;
+			}
+			table.subscriptions td {
+				padding: .5em;
+			}
+		</style>
+	</n.put_in_head.>
+
+	<n.zebra_table_javascript table_selector="table.subscriptions"/>
+
+	<n.subscribers_pagination/>
+
+	<div class="weak-color" style="float:left;margin:.5em 0">
+		<n.one_or_many.page_node.subscription_count>
+			<one_text><t>subscriber</t></one_text>
+			<many_text><t>subscribers</t></many_text>
+		</n.one_or_many.page_node.subscription_count>
+	</div>
+
+	<n.subscriptions. start="[n.subscriber_page_index_record/]" length="[n.subscriber_page_length/]">
+		<n.if.has_more_elements>
+			<then>
+				<table class="subscriptions">
+					<tr class="shaded-bg-color">
+						<th class="medium-border-color"></th>
+						<th class="medium-border-color"><t>Name</t></th>
+						<th class="medium-border-color"><t>Email</t></th>
+						<th class="medium-border-color"><t>Subscription Type</t></th>
+						<th class="medium-border-color"></th>
+					</tr>
+					<n.loop.>
+						<tr>
+							<td style="width:30px;padding:0"><n.subscription.user.avatar/></td>
+							<td class="nowrap"><n.subscription.user.user_link/></td>
+							<td class="nowrap"><n.subscription.user.user_email/></td>
+							<td class="nowrap"><n.subscription.type_label/></td>
+							<td class="nowrap"><n.subscription.unsubscribe_button/></td>
+						</tr>
+					</n.loop.>
+				</table>
+			</then>
+		</n.if.has_more_elements>
+	</n.subscriptions.>
+
+	<n.subscribers_pagination/>
+</macro>
+
+<macro name="process_unsubscription">
+	<n.if.not.is_null.unsubscribe_email_parameter>
+		<then>
+			<n.set_local_subscription.page_node.subscription_for email="[n.unsubscribe_email_parameter/]" />
+			<n.if.local_subscription.is_subscribed>
+				<then>
+					<n.local_subscription.remove/>
+					<n.local_subscription.send_unsubscription_reminder/>
+				</then>
+			</n.if.local_subscription.is_subscribed>
+		</then>
+	</n.if.not.is_null.unsubscribe_email_parameter>
+</macro>
+
+<macro name="subscribers_pagination" requires="node_page">
+	<n.paging.
+		total_rows="[n.page_node.subscription_count/]"
+		current_row="[n.subscriber_page_index_record/]"
+		rows_per_page="[n.subscriber_page_length/]"
+	>
+		<n.generic_paging>
+			<margin>.55em .2em</margin>
+			<url><n.page_node.manage_subscribers_path index_record="[n.page_row/]"/></url>
+		</n.generic_paging>
+	</n.paging.>
+</macro>
+
+<macro name="type_label" requires="subscription">
+	<n.if.equal value1="[n.to/]" value2="DESCENDANTS">
+		<then><t>All posts</t></then>
+		<else><t>New topics only</t></else>
+	</n.if.equal>
+</macro>
+
+<macro name="unsubscribe_button" requires="subscription">
+	<n.form.>
+		<input type="hidden" name="unsubscribe-email" value="[n.user.user_email/]"/>
+		<input type="submit" class="toolbar" value="[t]Unsubscribe[/t]"/>
+	</n.form.>
+</macro>
+
+<macro name="unsubscribe_email_parameter" requires="servlet">
+	<n.get_parameter name="unsubscribe-email"/>
+</macro>
+
+
+<macro name="manage_subscribers_path" parameters="filter,index_record" requires="node">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=manage_subscribers&node=<n.id/>
+		<n.add_to_path name="filter" value="[n.filter/]"/>
+		<n.add_to_path name="i" value="[n.index_record/]" default_value="0"/>
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="subscriber_filter" requires="servlet">
+	<n.get_parameter name="filter"/>
+</macro>
+
+<macro name="subscriber_page_index_record" requires="servlet">
+	<n.get_parameter name="i"/>
+</macro>
+
+<macro name="subscriber_page_length" requires="servlet">
+	25
+</macro>
+
+<macro name="is_subscriber_filter" parameters="value">
+	<n.equal value1="[n.subscriber_filter/]" value2="[n.value/]" />
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/manage_users_and_groups.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,243 @@
+<macro name="manage_users_and_groups" requires="servlet">
+	<n.if.not.visitor.can_manage_users_and_groups>
+		<then>
+			<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+		</then>
+	</n.if.not.visitor.can_manage_users_and_groups>
+
+	<n.if.is_submitted_form>
+		<then.save_users_and_group/>
+	</n.if.is_submitted_form>
+
+	<n.html>
+		<head>
+			<META NAME="robots" CONTENT="noindex,nofollow"/>
+			<n.title.><t>Manage Users & Groups</t></n.title.>
+			<n.if.is_submitted_form>
+				<then>
+					<script type="text/javascript">
+						$(document).ready(function() {
+							notice('<t>Data successfully saved</t>', 2000, 1000);
+						});
+					</script>
+				</then>
+			</n.if.is_submitted_form>
+		</head>
+		<body>
+			<n.edit_header first_text="[t]Manage Users & Groups[/t]" second_text="[n.root_node.subject/]" />
+
+			<div class="weak-color" style="margin-top:1em">
+				<t>Below you can manage groups and users. You can copy and paste users in order
+				to move them from one group to another.</t>
+			</div>
+
+			<div class="weak-color" style="margin:.5em 0 1em">
+				<t>You can also <n.root_node.change_permissions_link.>change the permissions</n.root_node.change_permissions_link.> of the groups below.</t>
+			</div>
+
+			<n.user_groups.>
+				<n.add.members_group/>
+				<n.add.administrators_group/>
+				<n.sort/>
+				<n.vertical_tab_control.>
+					<n.add_new_group_vertical_tab/>
+					<n.add_all_users_vertical_tab/>
+					<n.add_registered_vertical_tab/>
+					<n.loop.>
+						<n.add_group_vertical_tab group="[n.current_group/]"/>
+					</n.loop.>
+					<n.reset_list_index/>
+				</n.vertical_tab_control.>
+			</n.user_groups.>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="add_new_group_vertical_tab">
+	<n.add_vertical_tab>
+		<icon><img src="/images/add.png" width="12" height="12" style="vertical-align:-10%"/></icon>
+		<text><t>Add New Group</t></text>
+		<url><n.manage_users_and_groups_path/></url>
+		<selected><n.is_null.selected_group/></selected>
+		<details><n.new_group_panel/></details>
+	</n.add_vertical_tab>
+</macro>
+
+<macro name="add_all_users_vertical_tab">
+	<n.add_vertical_tab>
+		<text><t>All Users</t></text>
+		<style>padding-left:20px</style>
+		<url><n.manage_users_and_groups_path group="All_Users"/></url>
+		<selected><n.equal value1="[n.selected_group/]" value2="All_Users"/></selected>
+		<details><n.all_users_panel/></details>
+	</n.add_vertical_tab>
+</macro>
+
+<macro name="add_registered_vertical_tab">
+	<n.add_vertical_tab>
+		<text><t>Registered Users</t></text>
+		<style>padding-left:20px</style>
+		<url><n.manage_users_and_groups_path group="Registered"/></url>
+		<selected><n.equal value1="[n.selected_group/]" value2="Registered"/></selected>
+		<details><n.registered_users_panel/></details>
+	</n.add_vertical_tab>
+</macro>
+
+<macro name="add_group_vertical_tab" parameters="group">
+	<n.add_vertical_tab>
+		<text><n.group/></text>
+		<style>padding-left:20px</style>
+		<url><n.manage_users_and_groups_path group="[n.group/]"/></url>
+		<selected><n.equal value1="[n.selected_group/]" value2="[n.group/]"/></selected>
+		<details><n.group_editor_panel group="[n.group/]"/></details>
+	</n.add_vertical_tab>
+</macro>
+
+<macro name="save_users_and_group">
+	<n.string_list. values="[n.users_field.value/]" separator="\n">
+		<n.filter_by.not.is_empty.current_string/>
+		<n.remove_group group="[n.selected_group/]"/>
+		<n.loop.>
+			<n.set_var. name='email'>
+				<n.get_email_address_from.current_string/>
+			</n.set_var.>
+			<n.if.not.is_empty.var name='email'>
+				<then>
+					<n.get_or_create_user. email="[n.var name='email'/]">
+						<n.as_user_page.edit_page_user.>
+							<n.add_to_group.selected_group/>
+						</n.as_user_page.edit_page_user.>
+					</n.get_or_create_user.>
+				</then>
+			</n.if.not.is_empty.var>
+		</n.loop.>
+
+		<n.if.list_is_empty>
+			<then.redirect_to.manage_users_and_groups_path/>
+		</n.if.list_is_empty>
+	</n.string_list.>
+</macro>
+
+<macro name="manage_users_and_groups_path" parameters="group">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=manage_users_and_groups
+		<n.add_to_path name="group" value="[n.group/]"/>
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="selected_group">
+	<n.get_parameter name='group'/>
+</macro>
+
+<macro name="group_editor_panel" parameters="group">
+	<div class="second-font field-title nowrap" style="margin-top:0">
+		<n.group/>
+	</div>
+	<n.short_description/>
+	<n.users_field.set_value.>
+		<n.users_in_group. group="[n.group/]">
+			<n.loop.>
+				<n.current_user.name/> <n.lt/><n.current_user.user_email/><n.gt/><n.crlf/>
+			</n.loop.>
+		</n.users_in_group.>
+	</n.users_field.set_value.>
+	<n.form.>
+		<input type="hidden" name="group" value="[n.selected_group/]"/>
+		<n.users_field.textarea wrap="SOFT" style="width:100%;height:25em;margin:.3em 0" />
+		<input type="submit" value="[t]Save Changes[/t]"/>
+	</n.form.>
+</macro>
+
+<macro name="short_description">
+	<div class="weak-color nowrap">
+		<t>Enter one user per row</t> (<a href="javascript: void(0)" onclick="$('#help').slideToggle()"><t>more help</t></a>)
+		<div id="help" style="display:none">
+			<b><t>Examples:</t></b>
+			<div style="margin-left:1em">
+				john_smith@example.com<br/>
+				John Smith &lt;john_smith@example.com&gt;
+			</div>
+			<div>
+				<t>To remove a group, empty the text area below and save the changes.</t>
+			</div>
+		</div>
+	</div>
+</macro>
+
+<macro name="all_users_panel">
+	<div class="second-font field-title nowrap" style="margin:0"><t>All Users</t></div>
+	<div class="weak-color" style="margin-bottom:.8em">
+		<t>Read-only list with all users with an email under <t.location.root_node.subject/>.</t>
+		<t>This list shows registered, unregistered and banned users. Anonymous users are not listed because they don't have an email and thus cannot be part of a group.</t>
+	</div>
+
+	<div style="height:25em;overflow:auto">
+		<n.site_users. length="99999">
+			<n.loop.>
+				<n.current_user.name/> &lt;<n.current_user.user_email/>&gt;<br/>
+			</n.loop.>
+		</n.site_users.>
+	</div>
+</macro>
+
+<macro name="registered_users_panel">
+	<div class="second-font field-title nowrap" style="margin:0"><t>Registered Users</t></div>
+	<div class="weak-color" style="margin-bottom:.8em">
+		<t>All users that have registered to <t.location.root_node.subject/>.
+		These users have confirmed their email addresses and are able to login to the system.</t>
+	</div>
+
+	<div style="height:25em;overflow:auto">
+		<n.site_users. length="99999">
+		<n.filter_by.both condition1="[n.current_user.is_registered/]" condition2="[n.current_user.not.is_banned/]"/>
+			<n.loop.>
+				<n.current_user.name/> &lt;<n.current_user.user_email/>&gt;<br/>
+			</n.loop.>
+		</n.site_users.>
+	</div>
+</macro>
+
+<macro name="new_group_panel">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			function checkGroupName() {
+				var name = $('#group-name').val();
+				var isValidName = name && Nabble.trim(name).length > 0 && name != 'Authors' && name != 'authors';
+				if (!isValidName) {
+					alert('<t>The name of the group is not valid.</t>');
+					return false;
+				}
+				return true;
+			}
+		</script>
+	</n.put_in_head.>
+	<n.form. onsubmit="return checkGroupName()">
+		<div class="second-font field-title nowrap" style="margin:0">
+			<t>Group Name:</t>
+			<input id="group-name" type="text" name="group"/>
+		</div>
+		<n.short_description/>
+		<input type="hidden" name="group" value="[n.selected_group/]"/>
+		<n.users_field.textarea wrap="SOFT" style="width:100%;height:25em;margin:.3em 0" />
+		<input type="submit" value="[t]Save Changes[/t]"/>
+	</n.form.>
+</macro>
+
+<macro name="users_field" dot_parameter="do">
+	<n.field. name="users"><n.do/></n.field.>
+</macro>
+
+<macro name="get_email_address_from" dot_parameter="text">
+	<n.if.not.is_null.extract_email_address_from.text>
+		<then.extract_email_address_from.text/>
+		<else>
+			<n.if.has_authenticated_user_with_name name="[n.text/]">
+				<then>
+					<n.get_authenticated_user_with_name. name="[n.text/]">
+						<n.user_email/>
+					</n.get_authenticated_user_with_name.>
+				</then>
+			</n.if.has_authenticated_user_with_name>
+		</else>
+	</n.if.not.is_null.extract_email_address_from.text>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/message.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,157 @@
+<macro name="message_with_signature" requires="node">
+	<n.node_message_as_html />
+	<n.if.both condition1="[n.owner.has_signature/]" condition2="[n.owner.is_active/]">
+		<then>
+			<div class="signature weak-color">
+				<n.owner.signature_as_html/>
+			</div>
+		</then>
+	</n.if.both>
+</macro>
+
+<macro name="node_message_block" dot_parameter="do" requires="node">
+	<n.global_set_var name="message_block_node" value="[n.this_node/]" />
+	<n.do/>
+</macro>
+
+<macro name="message_block_node" dot_parameter="do">
+	<n.get_node node="[n.global_var name='message_block_node'/]" do="[n.do/]" />
+</macro>
+
+<macro name="node_message_as_html" requires="node">
+	<n.node_message_block.message.message_as_html />
+</macro>
+
+<macro name="node_message_as_text" requires="node">
+	<n.node_message_block.message.message_as_text />
+</macro>
+
+<macro name="message_quoted" requires="node" unindent="true">
+	<quote author="[n.owner.name/]">
+		<n.trim.message.as_quoted_text/>
+	</quote>
+	<n.nop/>
+</macro>
+
+<macro name="message_as_html" requires="message">
+	<n.if.either condition1="[n.is_deleted/]" condition2="[n.is_deactivated/]">
+		<then>
+			<b><t>CONTENTS DELETED</t></b>
+			<div class="weak-color"><t>The author has deleted this message.</t></div>
+		</then>
+		<else>
+			<n.set_var. name="output">
+				<n.as_html_list.process_message_html />
+			</n.set_var.>
+			<n.if.is_imported_mail>
+				<then.set_var. name="output">
+					<n.remove_unsubscription_link.var name="output" />
+				</then.set_var.>
+			</n.if.is_imported_mail>
+			<n.var name="output" />
+		</else>
+	</n.if.either>
+</macro>
+
+<macro name="process_message_html" requires="html_list">
+	<n.process_raw_tags/>
+	<n.process_cdata_tags/>
+	<n.process_embed regex="[n.embed_regex/]" />
+	<n.process_file_tags/>
+	<n.apply_message_security/>
+	<n.process_quotes wrote="[t][n.author/] wrote[/t]"/>
+	<n.process_email/>
+	<n.set_target_to_top/>
+	<n.add_nofollow/>
+	<n.process_smilies/>
+</macro>
+
+<macro name="apply_message_security" requires="html_list">
+	<n.if.is_in_command name="node_message_block">
+		<then.if.not.message_block_node.has_unrestricted_posting>
+			<then.message_security/>
+		</then.if.not.message_block_node.has_unrestricted_posting>
+		<else.message_security/>
+	</n.if.is_in_command >
+</macro>
+
+<macro name="message_security" requires="html_list">
+	<n.disable_banned_tags.>
+		script,
+		style,
+		html,
+		head,
+		body,
+		link
+	</n.disable_banned_tags.>
+	<n.disable_invalid_urls.>
+		href,
+		src,
+		action
+	</n.disable_invalid_urls.>
+	<n.disable_javascript_urls.>
+		href,
+		src,
+		action
+	</n.disable_javascript_urls.>
+	<n.disable_on_event/>
+	<n.disable_scripts/>
+	<n.disable_style_blocks/>
+</macro>
+
+<macro name="message_as_text" requires="message">
+	<n.as_text_list.process_message_text />
+</macro>
+
+<macro name="process_message_text" requires="html_list">
+	<n.process_quotes_as_text wrote="[t][n.author/] wrote[/t]"/>
+	<n.process_smilies_as_text/>
+	<n.process_file_tags/>
+	<n.process_tag tag="b" do="*" />
+	<n.process_tag tag="/b" do="*" />
+	<n.process_tag tag="i" do="/" />
+	<n.process_tag tag="/i" do="/" />
+	<n.process_tag tag="u" do="_" />
+	<n.process_tag tag="/u" do="_" />
+	<n.process_text text="&lt;" replacement="[n.lt/]" />
+	<n.process_text text="&gt;" replacement="[n.gt/]" />
+</macro>
+
+
+<macro name="embed_regex">
+	<n.if.is_in_command name="node_message_block">
+		<then.if.message_block_node.has_unrestricted_posting>
+			<then>(?s).*</then>
+			<else.embed_regex_list/>
+		</then.if.message_block_node.has_unrestricted_posting>
+		<else.embed_regex_list/>
+	</n.if.is_in_command >
+</macro>
+
+<macro name="embed_regex_list">
+	(<n.youtube_object_regex/>)|(<n.youtube_object_regex2/>)|(<n.youtube_iframe_regex/>)|(<n.liveleak_regex/>)|(<n.polldaddy_regex/>)|(<n.vimeo_regex/>)
+</macro>
+
+<macro name="youtube_object_regex">
+	<![CDATA[\s*<object width="\d+" height="\d+"><param name="movie" value="http://www.youtube.com/v/[^"]+"></param><param name="allowFullScreen" value="\w+"></param><param name="allowscriptaccess" value="\w+"></param><embed src="http://www.youtube.com/v/[^"]+" type="application/x-shockwave-flash" allowscriptaccess="\w+" allowfullscreen="\w+" width="\d+" height="\d+"></embed></object>\s*]]>
+</macro>
+
+<macro name="youtube_object_regex2">
+	<![CDATA[\s*<object width="\d+" height="\d+"><param name="movie" value="https?://www.youtube(-nocookie)?.com/v/[^"]+"></param><param name="allowFullScreen" value="\w+"></param><param name="allowscriptaccess" value="\w+"></param><embed src="https?://www.youtube(-nocookie)?.com/v/[^"]+" type="application/x-shockwave-flash" width="\d+" height="\d+"( allowscriptaccess="\w+")?( allowfullscreen="\w+")?></embed></object>\s*]]>
+</macro>
+
+<macro name="youtube_iframe_regex">
+	<![CDATA[\s*<iframe[^>]* src="(https?:)?//www.youtube.com/embed/[^"]+"[^>]*></iframe>\s*]]>
+</macro>
+
+<macro name="liveleak_regex">
+	<![CDATA[\s*<object width="\d+" height="\d+"><param name="movie" value="http://www.liveleak.com/e/\w+"></param><param name="wmode" value="\w+"></param><param name="allowscriptaccess" value="\w+"></param><embed src="http://www.liveleak.com/e/\w+" type="application/x-shockwave-flash" wmode="\w+" allowscriptaccess="\w+" width="\d+" height="\d+"></embed></object>\s*]]>
+</macro>
+
+<macro name="polldaddy_regex">
+	<![CDATA[\s*<embed allowScriptAccess="\w+" saveEmbedTags="\w+" quality="\w+" wmode="\w+" bgcolor="#\w+" name="\w+" salign="\w+" scale="\w+" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" FlashVars="p=\d+" src="http://i.polldaddy.com/poll.swf" width="\d+" height="\d+"></embed>\s*]]>
+</macro>
+
+<macro name="vimeo_regex">
+	<![CDATA[\s*<iframe src="http://player.vimeo.com/video/[^"]+" width="\d+" height="\d+" frameborder="0"( webkitAllowFullScreen)?( mozallowfullscreen)?( allowFullScreen)?></iframe>.*]]>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/move_node.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,97 @@
+<macro name="move_node" requires="servlet">
+	<n.node_page.>
+		<n.if.not.visitor.can_move.page_node>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_move.page_node>
+
+		<n.if.is_submitted_form>
+			<then.save_parent_changes/>
+		</n.if.is_submitted_form>
+
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Move Post</t></n.title.>
+				<n.parent_url_field.focus/>
+			</head>
+			<body>
+				<n.edit_header first_text="Move Post" second_text="[n.page_node.subject/]" />
+				<n.show_move_node_error/>
+				<n.form.>
+					<div class="weak-color" style="margin:1.5em 0 .5em">
+						<t>Enter permalink of the <b>post</b> or <b>forum</b> that will be the new parent,
+							or leave blank to make this message an independent topic:</t>
+					</div>
+					<n.parent_url_field.input size="60" />
+					<div style="margin-top:1.4em">
+						<input type="submit" value="[t]Move Post[/t]" /> <t>or</t> <a href="[n.page_node.path/]"><t>Cancel</t></a>
+					</div>
+				</n.form.>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="save_parent_changes">
+	<n.catch_exception. id="save-block">
+		<n.edit_page_node.>
+			<!-- If the URL is empty, we use the current app -->
+			<n.if.is_empty.trim.parent_url_field.value>
+				<then.parent_url_field.set_value value="[n.page_node.get_app_node.url/]"/>
+			</n.if.is_empty.trim.parent_url_field.value>
+
+			<!-- If same site, just move. Otherwise, export it. -->
+			<n.if.url_belongs_to_site url="[n.parent_url_field.value/]">
+				<then>
+					<n.set_parent_url parent_url="[n.parent_url_field.value/]" />
+					<n.save_node/>
+				</then>
+				<else>
+					<n.if.is_valid_export_permalink permalink="[n.parent_url_field.value/]">
+						<then>
+							<!-- Hardcoded link until ExportConfirmation is templated -->
+							<n.set_var. name='url'>/catalog/ExportConfirmation.jtp?node=<n.page_node.id/>&url=<n.parent_url_field.value/></n.set_var.>
+							<n.redirect_to.var name='url'/>
+						</then>
+						<else>
+							<n.throw_template_exception name="invalid_permalink"/>
+						</else>
+					</n.if.is_valid_export_permalink>
+				</else>
+			</n.if.url_belongs_to_site>
+		</n.edit_page_node.>
+		<n.redirect_to.page_node.path/>
+	</n.catch_exception.>
+</macro>
+
+<macro name="parent_url_field" dot_parameter="do">
+	<n.field. name="parent_url"><n.do/></n.field.>
+</macro>
+
+<macro name="show_move_node_error">
+	<n.if.is_submitted_form>
+		<then>
+			<n.if.has_exception for="save-block">
+				<then>
+					<n.format_error.handle_exception. for="save-block">
+						<n.exception. name="banned">
+							<t>Sorry, but the administrators have banned you.</t>
+							<t>You can't move the post to anywhere.</t>
+						</n.exception.>
+						<n.exception. name="same_node_loop">
+							<t>The new parent cannot be the post itself.</t>
+						</n.exception.>
+						<n.exception. name="invalid_permalink">
+							<t>Please provide a valid permalink.</t>
+						</n.exception.>
+						<n.exception. name="no_anonymous">
+							<t>You cannot move this post to that destination because the new parent doesn't allow anonymous users.</t>
+						</n.exception.>
+					</n.format_error.handle_exception.>
+				</then>
+			</n.if.has_exception>
+		</then>
+	</n.if.is_submitted_form>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/new_topic.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,14 @@
+<macro name="new_topic" requires="servlet">
+	<n.new_post>
+		<page_name>
+			<t>Post New Message</t>
+		</page_name>
+		<focus>
+			<n.subject_field.focus/>
+		</focus>
+		<mailing_list_etiquette>
+			<li><t>If you are posting a question, please try search first. Your question may have already been answered.</t></li>
+			<li><t>Don't post repeatedly. Wait for a few days. People will read your post by email.</t></li>
+		</mailing_list_etiquette>
+	</n.new_post>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/people.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,337 @@
+<macro name="app_people" requires="servlet">
+	<n.app_people_caching/>
+	<n.node_page.>
+		<n.html>
+			<head>
+				<n.people_head/>
+			</head>
+			<body>
+				<n.people_header/>
+				<n.people_tabbed_pane/>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="app_people_caching">
+	<n.if.not.is_online_users>
+		<then.uncache_for.>
+			<n.new_user/>
+			<n.user_group_change/>
+		</then.uncache_for.>
+	</n.if.not.is_online_users>
+</macro>
+
+<macro name="people_head">
+	<n.people_title/>
+	<n.if.is_null.people_filter>
+		<then>
+			<META NAME="description" CONTENT="[t]People in [t.location.page_node.subject/][/t]"/>
+			<META NAME="keywords" CONTENT="[n.page_node.subject/], users, posters, members, administrators, community"/>
+		</then>
+	</n.if.is_null.people_filter>
+</macro>
+
+<macro name="people_title">
+	<n.title.>
+		<t>People in <t.location.page_node.subject/></t>
+		<n.if.not.is_null.people_filter>
+			<then>(<n.people_filter/>)</then>
+		</n.if.not.is_null.people_filter>
+	</n.title.>
+</macro>
+
+<macro name="people_header">
+	<h2 class="weak-color">
+		<img src="/images/people.png" class="image24" alt="[t]People[/t]"/>
+		<t>People in <t.location.page_node.subject/></t>
+	</h2>
+</macro>
+
+<macro name="people_tabbed_pane">
+	<div style="margin:.5em 0">
+		<n.horizontal_tab_control.>
+			<n.add_horizontal_tab
+				url="[n.page_node.people_path/]"
+				text="[t]Users & Groups[/t]"
+				selected="[n.not.is_online_users/]"
+				details="[n.visible_users_table.people_columns/]"
+			/>
+			<n.add_horizontal_tab
+				url="[n.page_node.people_path filter='online-users'/]"
+				text="[t]Online Users[/t]"
+				selected="[n.is_online_users/]"
+				details="[n.online_users_table.people_columns/]"
+			/>
+		</n.horizontal_tab_control.>
+	</div>
+</macro>
+
+<macro name="people_columns">
+	<n.set_visitor_online/>
+	<n.user_column width="70%"/>
+	<n.user_state_column/>
+	<n.user_post_count_column/>
+</macro>
+
+<macro name="online_users_table" dot_parameter="columns" requires="node_page">
+	<n.online_users. include_invisible_users="[n.visitor.is_site_admin/]">
+		<n.filter_by.current_user.can_view.page_node/>
+		<n.sort_by_node_count_desc/>
+		<n.people_table.>
+			<n.columns/>
+		</n.people_table.>
+	</n.online_users.>
+	
+	<div class="weak-color" style="margin-top:1em">
+		<n.one_or_many.online_anonymous_users_count>
+			<one_text><t>anonymous user</t></one_text>
+			<many_text><t>anonymous users</t></many_text>
+		</n.one_or_many.online_anonymous_users_count>
+		<br/>
+		<n.one_or_many.online_invisible_users_count>
+			<one_text><t>invisible user</t></one_text>
+			<many_text><t>invisible users</t></many_text>
+		</n.one_or_many.online_invisible_users_count>
+	</div>
+</macro>
+
+<macro name="visible_users_list" dot_parameter="do" requires="node_page">
+	<n.if.is_null.people_filter>
+		<then>
+			<n.if.page_node.allows_showing_members_of.registered_group>
+				<then>
+					<n.site_users. start="0" length="99999" filter="[n.registered_filter/]">
+						<n.filter_by.current_user.not.is_banned/>
+						<n.do/>
+					</n.site_users.>
+				</then>
+				<else>
+					<!-- get the first group available -->
+					<n.available_groups.>
+						<n.if.next_element>
+							<then>
+								<n.users_in_group. group="[n.current_group/]">
+									<n.filter_by.current_user.both condition1="[n.not.is_deactivated/]" condition2="[n.not.is_banned/]"/>
+									<n.sort_by_node_count_desc/>
+									<n.do/>
+								</n.users_in_group.>
+							</then>
+							<else.empty_users_list.do/>
+						</n.if.next_element>
+					</n.available_groups.>
+				</else>
+			</n.if.page_node.allows_showing_members_of.registered_group>
+		</then>
+		<else>
+			<!-- get the filtered group -->
+			<n.if.page_node.allows_showing_members_of.people_filter>
+				<then>
+					<n.users_in_group. group="[n.people_filter/]">
+						<n.filter_by.current_user.both condition1="[n.not.is_deactivated/]" condition2="[n.not.is_banned/]"/>
+						<n.sort_by_node_count_desc/>
+						<n.do/>
+					</n.users_in_group.>
+				</then>
+				<else.empty_users_list.do/>
+			</n.if.page_node.allows_showing_members_of.people_filter>
+		</else>
+	</n.if.is_null.people_filter>
+</macro>
+
+<macro name="visible_users_table" dot_parameter="columns" requires="node_page">
+	<n.visible_users_list.>
+		<n.show_people_filter total_rows="[n.element_count/]"/>
+		<n.people_pagination total_rows="[n.element_count/]" />
+		<n.sub_list. start="[n.people_page_index_record/]" length="[n.people_page_length/]">
+			<n.people_table.>
+				<n.columns/>
+			</n.people_table.>
+		</n.sub_list.>
+	</n.visible_users_list.>
+</macro>
+
+<macro name="show_people_filter" parameters="total_rows" requires="node_page">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			$(document).ready(function() {
+				function applyFilter() {
+					window.location = $('#filter').val();
+				};
+				$('#filter').change(applyFilter);
+			});
+		</script>
+	</n.put_in_head.>
+	<div class="nowrap float-left" style="margin-bottom:.3em">
+		<b><t>Filter by group</t></b>
+		<select id="filter">
+			<n.if.page_node.allows_showing_members_of.registered_group>
+				<then.select_option value="[n.page_node.people_path/]" text="[t]Registered Users[/t]"/>
+			</n.if.page_node.allows_showing_members_of.registered_group>
+
+			<n.available_groups.loop.>
+				<n.select_option value="[n.page_node.people_path.current_group/]" selectedValue="[n.page_node.people_path.people_filter/]" text="[n.current_group/]"/>
+			</n.available_groups.loop.>
+		</select>
+
+		<span class="bold" style="padding-left:1em">
+			<n.one_or_many.total_rows>
+				<one_text><t>user</t></one_text>
+				<many_text><t>users</t></many_text>
+			</n.one_or_many.total_rows>
+		</span>
+	</div>
+</macro>
+
+<macro name="available_groups" dot_parameter="do" requires="node_page">
+	<n.page_node.groups_with_permission. permission="[n.show_group_members_permission/]">
+		<n.remove.registered_group/>
+		<n.sort/>
+		<n.do/>
+	</n.page_node.groups_with_permission.>
+</macro>
+
+<macro name="people_table" dot_parameter="columns" requires="user_list">
+	<n.put_in_head.>
+		<style type="text/css">
+			table.people {
+				width:100%;
+				border-collapse:collapse;
+				clear:both;
+			}
+			table.people td {
+				padding:.1em .3em;
+			}
+			tr.header-row td {
+				font-weight:bold;
+				padding: .3em;
+			}
+		</style>
+	</n.put_in_head.>
+	<n.zebra_table_javascript table_selector="table.people"/>
+
+	<table class="people">
+		<n.table_header.>
+			<tr class="header-row shaded-bg-color">
+				<n.columns/>
+			</tr>
+		</n.table_header.>
+		<n.loop.>
+			<tr class="people-row">
+				<n.columns/>
+			</tr>
+		</n.loop.>
+	</table>
+</macro>
+
+<macro name="user_column" parameters="title,width" requires="user_list">
+	<n.table_column>
+		<head>
+			<td style="[n.width_style.width/]">
+				<n.default. to="[t]Name[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td class="weak-color">
+				<table class="avatar-table">
+					<tr>
+						<td style="padding:0;vertical-align:top">
+							<n.current_user.avatar size="small"/>
+						</td>
+						<td class="nowrap" style="width:100%;padding:0 .3em">
+							<n.current_user.user_link/>
+						</td>
+					</tr>
+				</table>
+			</td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="user_state_column" parameters="title,width" requires="user_list">
+	<n.table_column>
+		<head>
+			<td></td>
+		</head>
+		<body>
+			<td class="weak-color">
+				<n.if.current_user.is_anonymous>
+					<then><t>Anonymous</t></then>
+					<else>
+						<n.if.current_user.is_registered>
+							<then><t>Registered</t></then>
+							<else>
+								<n.if.current_user.is_deactivated>
+									<then><t>Unregistered / Deactivated</t></then>
+									<else><t>Unregistered</t></else>
+								</n.if.current_user.is_deactivated>
+							</else>
+						</n.if.current_user.is_registered>
+					</else>
+				</n.if.current_user.is_anonymous>
+			</td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="user_post_count_column" parameters="title,width" requires="user_list">
+	<n.table_column>
+		<head>
+			<td class="nowrap" style="text-align:center;[n.width_style.width/]">
+				<n.default. to="[t]Post Count[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td class="weak-color" style="text-align:center">
+				<n.current_user.node_count/>
+			</td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="people_pagination" parameters="total_rows" requires="node_page">
+	<n.paging.
+		total_rows="[n.total_rows/]"
+		current_row="[n.people_page_index_record/]"
+		rows_per_page="[n.people_page_length/]"
+	>
+		<n.generic_paging>
+			<margin>.55em .2em</margin>
+			<url><n.page_node.people_path filter="[n.people_filter/]" index_record="[n.page_row/]"/></url>
+		</n.generic_paging>
+	</n.paging.>
+</macro>
+
+<macro name="people_path" requires="node" dot_parameter="filter" parameters="index_record">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=app_people&node=<n.id/>
+		<n.add_to_path name="filter" value="[n.filter/]"/>
+		<n.add_to_path name="i" value="[n.index_record/]" default_value="0"/>
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="people_filter" requires="servlet">
+	<n.get_parameter name="filter"/>
+</macro>
+
+<macro name="people_page_index_record">
+	<n.get_parameter name="i"/>	
+</macro>
+
+<macro name="people_page_length">
+	20
+</macro>
+
+<macro name="is_online_users">
+	<n.is_people_filter value='online-users' />
+</macro>
+
+<macro name="is_people_filter" parameters="value">
+	<n.equal value1="[n.people_filter/]" value2="[n.value/]" />
+</macro>
+
+<macro name="empty_users_list" dot_parameter="do">
+	<n.site_users. start="0" length="0">
+		<n.do/>
+	</n.site_users.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/permissions.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,382 @@
+<macro name="current_permission_version">
+	standard-6
+</macro>
+
+<macro name="update_default_permissions">
+	<n.set_default_permissions. version="[n.current_permission_version/]" >
+		<n.add_permission permission="[n.view_permission/]" group="[n.anyone_group/]" />
+		<n.add_permission permission="[n.edit_app_permission/]" group="[n.administrators_group/]" />
+		<n.add_permission permission="[n.reply_permission/]" group="[n.anyone_group/]" />
+		<n.add_permission permission="[n.create_topic_permission/]" group="[n.anyone_group/]" />
+		<n.add_permission permission="[n.move_permission/]" group="[n.authors_group/]" />
+		<n.add_permission permission="[n.move_permission/]" group="[n.administrators_group/]" />
+		<n.add_permission permission="[n.create_sub_apps_permission/]" group="[n.administrators_group/]" />
+		<n.add_permission permission="[n.change_post_date_permission/]" group="[n.administrators_group/]" />
+		<n.add_permission permission="[n.manage_subscribers_permission/]" group="[n.administrators_group/]" />
+		<n.add_site_permission permission="[n.manage_banned_users_permission/]" group="[n.administrators_group/]" />
+		<n.add_permission permission="[n.manage_pinned_topics_permission/]" group="[n.administrators_group/]" />
+		<n.add_permission permission="[n.manage_locked_topics_permission/]" group="[n.administrators_group/]" />
+		<n.add_permission permission="[n.show_group_members_permission/]" group="[n.registered_group/]" />
+		<n.add_permission permission="[n.show_group_members_permission/]" group="[n.administrators_group/]" />
+		<n.add_permission permission="[n.show_group_members_permission/]" group="[n.members_group/]" />
+	</n.set_default_permissions.>
+</macro>
+
+<macro name="banned_group">
+	Banned
+</macro>
+
+<macro name="members_group">
+	Members
+</macro>
+
+<macro name="registered_user_groups">
+	<n.anyone_group/>,<n.registered_group/>
+</macro>
+
+<macro name="edit_app_permission">
+	Edit_app
+</macro>
+
+<macro name="edit_all_permission">
+	Edit_all
+</macro>
+
+<macro name="reply_permission">
+	Reply
+</macro>
+
+<macro name="create_topic_permission">
+	Create_topic
+</macro>
+
+<macro name="move_permission">
+	Move
+</macro>
+
+<macro name="manage_subscribers_permission">
+	Manage_Subscribers
+</macro>
+
+<macro name="create_sub_apps_permission">
+	Create_sub_apps
+</macro>
+
+<macro name="change_post_date_permission">
+	Change_post_date
+</macro>
+
+<macro name="show_group_members_permission">
+	Show_group_members
+</macro>
+
+<macro name="manage_banned_users_permission">
+	Manage_banned_users
+</macro>
+
+<macro name="manage_pinned_topics_permission">
+	Manage_pinned_topics
+</macro>
+
+<macro name="manage_locked_topics_permission">
+	Manage_locked_topics
+</macro>
+
+<macro name="unrestricted_posting_permission">
+	Unrestricted_posting
+</macro>
+
+<macro name="is_site_owner" requires="user">
+	<n.owns.root_node />
+</macro>
+
+<macro name="is_site_admin" requires="user">
+	<n.either>
+		<condition1.either>
+			<condition1.is_site_owner />
+			<condition2.is_sysadmin />
+		</condition1.either>
+		<condition2.is_in_group group="[n.administrators_group/]" />
+	</n.either>
+</macro>
+
+
+<macro name="can_delete" requires="user" dot_parameter="node_attr">
+	<n.both condition1="[n.not.is_banned/]" condition2="[n.owns.node_attr/]"/>
+</macro>
+
+<macro name="can_delete_recursively" requires="user" dot_parameter="node">
+	<n.is_site_admin/>
+</macro>
+
+<macro name="can_edit" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr />
+	<n.block.>
+		<n.both>
+			<condition1.not.local_user.is_banned/>
+			<condition2.either>
+				<condition1.local_user.owns.local_node />
+				<condition2.either>
+					<condition1.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.edit_all_permission/]" />
+					<condition2.both>
+						<condition1.local_node.is_app/>
+						<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.edit_app_permission/]" />
+					</condition2.both>
+				</condition2.either>
+			</condition2.either>
+		</n.both>
+	</n.block.>
+</macro>
+
+<macro name="app_or_root" requires="node" dot_parameter="do">
+	<n.if.is_in_app>
+		<then.get_app_node.do/>
+		<else.root_node.do/>
+	</n.if.is_in_app>
+</macro>
+
+<macro name="topic_or_app" requires="node" dot_parameter="do">
+	<n.set_local_node.this_node/>
+	<n.block.>
+		<n.if.local_node.is_post>
+			<then.local_node.topic_node.do/>
+			<else.local_node.do/>
+		</n.if.local_node.is_post>
+	</n.block.>
+</macro>
+
+<macro name="can_change_post_date_of" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.both>
+			<condition1.not.local_user.is_banned/>
+			<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.change_post_date_permission/]" />
+		</n.both>
+	</n.block.>
+</macro>
+
+<macro name="can_move" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.both>
+			<condition1.not.local_user.is_banned/>
+			<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.move_permission/]" />
+		</n.both>
+	</n.block.>
+</macro>
+
+<macro name="can_manage_subscribers_of" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.both>
+			<condition1.not.local_user.is_banned/>
+			<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.manage_subscribers_permission/]" />
+		</n.both>
+	</n.block.>
+</macro>
+
+<macro name="can_create_topic_in" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.both>
+			<condition1.not.local_user.is_banned/>
+			<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node/]" permission="[n.create_topic_permission/]" />
+		</n.both>
+	</n.block.>
+</macro>
+
+<macro name="can_reply_to" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.both>
+			<condition1.not.local_user.is_banned/>
+			<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.topic_or_app/]" permission="[n.reply_permission/]" />
+		</n.both>
+	</n.block.>
+</macro>
+
+<macro name="can_post_under" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.if.local_node.is_app>
+			<then.local_user.can_create_topic_in.local_node/>
+			<else.local_user.can_reply_to.local_node/>
+		</n.if.local_node.is_app>
+	</n.block.>
+</macro>
+
+<macro name="check_posting_under" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.if.local_user.is_banned>
+			<then.throw_template_exception name="banned"/>
+		</n.if.local_user.is_banned>
+		<n.if.both condition1="[n.local_node.is_associated_with_mailing_list_archive/]" condition2="[n.not.local_user.is_authenticated/]">
+			<then.throw_template_exception name="no_anonymous"/>
+		</n.if.both>
+		<n.if.local_node.is_app>
+			<then.if.not.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node/]" permission="[n.create_topic_permission/]" >
+				<then.if.local_user.is_anonymous>
+					<then.throw_template_exception name="no_anonymous"/>
+					<else.throw_template_exception name="no_create_topic_permission"/>
+				</then.if.local_user.is_anonymous>
+			</then.if.not.local_user.has_permission>
+			<else.if.not.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.topic_or_app/]" permission="[n.reply_permission/]" >
+				<then.if.local_user.is_anonymous>
+					<then.throw_template_exception name="no_anonymous"/>
+					<else.throw_template_exception name="no_reply_permission"/>
+				</then.if.local_user.is_anonymous>
+			</else.if.not.local_user.has_permission>
+		</n.if.local_node.is_app>
+	</n.block.>
+</macro>
+
+<macro name="any_registered_user_can_create_topics" requires="node">
+	<n.groups_have_permission groups="[n.registered_user_groups/]" permission="[n.create_topic_permission/]" />
+</macro>
+
+<macro name="only_members_can_create_topics" requires="node">
+	<n.not.any_registered_user_can_create_topics/>
+</macro>
+
+<macro name="can_view" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.either>
+			<condition1.local_user.owns.local_node/>
+			<condition2.either>
+				<condition1.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.view_permission/]" />
+				<condition2.local_user.is_site_admin />
+			</condition2.either>
+		</n.either>
+	</n.block.>
+</macro>
+
+<macro name="can_manage_users_and_groups" requires="user">
+	<n.is_site_admin/>
+</macro>
+
+<macro name="can_manage_banned_users" requires="user">
+	<n.has_site_permission permission="[n.manage_banned_users_permission/]" />
+</macro>
+
+<macro name="can_change_permissions_of" requires="user" dot_parameter="node_attr">
+	<n.is_site_admin/>
+</macro>
+
+<macro name="can_create_sub_apps_under" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.both>
+			<condition1.not.local_user.is_banned/>
+			<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node/]" permission="[n.create_sub_apps_permission/]" />
+		</n.both>
+	</n.block.>
+</macro>
+
+<macro name="can_manage_pinned_topics_in" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.both>
+			<condition1.not.local_user.is_banned/>
+			<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.manage_pinned_topics_permission/]" />
+		</n.both>
+	</n.block.>
+</macro>
+
+<macro name="can_manage_locked_topics_in" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.both>
+			<condition1.not.local_user.is_banned/>
+			<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.manage_locked_topics_permission/]" />
+		</n.both>
+	</n.block.>
+</macro>
+
+<macro name="has_unrestricted_posting" requires="node">
+	<n.set_local_node.this_node/>
+	<n.local_node.owner.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.unrestricted_posting_permission/]" />
+</macro>
+
+<macro name="allows_showing_members_of" requires="node" dot_parameter="group">
+	<n.has_permission permission="[n.show_group_members_permission/]" group="[n.group/]" />
+</macro>
+
+<macro name="has_people_page" requires="node">
+	<n.has_groups_with_permission.show_group_members_permission/>
+</macro>
+
+<macro name="can_be_displayed_in" requires="user" dot_parameter="node_attr">
+	<n.set_local_user.this_user />
+	<n.set_local_node.node_attr/>
+	<n.block.>
+		<n.both>
+			<condition1.not.local_user.is_banned/>
+			<condition2.local_user.has_permission node="[n.local_node/]" permission_node="[n.local_node.app_or_root/]" permission="[n.show_group_members_permission/]" />
+		</n.both>
+	</n.block.>
+</macro>
+
+
+
+<macro name="get read authorization key" requires="http_request">
+	<n.if.not.has_parameter name="node">
+		<then.exit/>
+	</n.if.not.has_parameter>
+	<n.get_node_from_parameter.>
+		<n.if.equal value1="[n.get_parameter name='macro'/]" value2="unauthorized">
+			<then.exit/>
+		</n.if.equal>
+		<n.if.not.is_private>
+			<then.exit/>
+		</n.if.not.is_private>
+		<n.get_private_node.id />
+	</n.get_node_from_parameter.>
+</macro>
+
+<macro name="authorization_node" dot_parameter="do" requires="read_authorization">
+	<n.get_node_from_id node_id="[n.authorization_key/]" do="[n.do/]" />
+</macro>
+
+<macro name="authorize for read" requires="read_authorization,servlet">
+	<n.if.visitor.is_anonymous>
+		<then>
+			<n.redirect_to.>
+				<n.login_path>
+					<message>
+						<t>You must login to view <t.subject.authorization_node.subject/>.</t>
+					</message>
+					<nextUrl>
+						<n.current_path/>
+					</nextUrl>
+				</n.login_path>
+			</n.redirect_to.>
+			<n.false />
+			<n.exit />
+		</then>
+	</n.if.visitor.is_anonymous>
+	<n.if>
+		<condition.either>
+			<condition1.visitor.can_view.authorization_node />
+			<condition2.visitor.owns.get_node_from_parameter />
+		</condition.either>
+		<then.true />
+		<else>
+			<n.redirect_to.authorization_node.unauthorized_path />
+			<n.false />
+		</else>
+	</n.if>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/post_by_email.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,48 @@
+<macro name="post by email" requires="post_by_email" unindent="true">
+	<n.catch_exception. id="save-block">
+		<n.save_post_by_email />
+	</n.catch_exception.>
+	<n.format_error.handle_exception. for="save-block">
+		<n.exception. name="subscription_processing_bad_user">
+			<n.send_standard_failure_mail.>
+				This email address only works for the registered Nabble user who it is assigned to, and you (<n.email_from/>) are not that user.
+			</n.send_standard_failure_mail.>
+		</n.exception.>
+		<n.exception. name="banned">
+			<n.send_standard_failure_mail.>
+				Your email to <n.email_to/> has been rejected because you are not allowed to post to <n.replied_to_node.url/> . Please contact the owner about permissions or visit the Nabble Support forum.
+			</n.send_standard_failure_mail.>
+		</n.exception.>
+		<n.exception. name="bad_mail">
+			<n.send_standard_failure_mail.>
+				Your email to <n.email_to/> couldn't be parsed.
+			</n.send_standard_failure_mail.>
+		</n.exception.>
+	</n.format_error.handle_exception.>
+</macro>
+
+<macro name="save_post_by_email" requires="post_by_email" unindent="true">
+	<n.fix_threading/>
+	<n.if.not.mail_author.can_post_under.replied_to_node>
+		<then.throw_template_exception name="banned" />
+	</n.if.not.mail_author.can_post_under.replied_to_node>
+	<n.save_to_post/>
+</macro>
+
+<macro name="fix_threading" requires="post_by_email">
+	<n.thread_by_subject prefixes="[n.prefixes/]" />
+</macro>
+
+<macro name="prefixes" requires="post_by_email">
+	re|aw|res|fwd|答复
+</macro>
+
+<macro name="send_standard_failure_mail" dot_parameter="text" requires="post_by_email" unindent="true">
+	<n.send_failure_mail.>
+		Delivery to the following recipient failed permanently:
+
+		<n.nop/>    <n.email_to/>
+
+		<n.text/>
+	</n.send_failure_mail.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/post_by_email_page.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,152 @@
+<macro name="post_by_email_page" requires="servlet">
+	<n.node_page.>
+		<n.if.is_submitted_form>
+			<then>
+				<n.catch_exception. id="send-email-block">
+					<n.send_email_address/>
+				</n.catch_exception.>
+			</then>
+		</n.if.is_submitted_form>
+		<n.html>
+			<head>
+				<n.title.><t>Post by Email</t></n.title.>
+			</head>
+			<body>
+				<n.edit_header first_text="[t]Post by Email[/t]" second_text="[n.page_node.subject/]" />
+	
+				<n.if.is_submitted_form>
+					<then>
+						<n.if.has_exception for="send-email-block">
+							<then>
+								<n.show_email_error/>
+							</then>
+							<else>
+								<table class="info-message" style="width:100%;padding:.5em">
+									<tr>
+										<td><img src="/images/success.png"/></td>
+										<td style="width:100%;font-weight:bold"><t>An email has been sent to you.</t></td>
+									</tr>
+								</table>
+							</else>
+						</n.if.has_exception>
+					</then>
+				</n.if.is_submitted_form>
+	
+				<div style="margin:1em 0 2em">
+					<n.if.visitor.is_anonymous>
+						<then>
+							<t>To prevent spam, the email address to use when posting by email is <b>unique</b> for each user.</t>
+							<t>To see which email address you should use to post, please <n.login_link.>login</n.login_link.> or <n.register_link.>register</n.register_link.>.</t>
+							<n.if.page_node.only_members_can_create_topics>
+								<then>
+									<t>You will need authorization to post new topics in <t.location.page_node.subject/>, so in addition to registering you will have to be
+									approved by the administrators.</t>
+								</then>
+								<else>
+									<t>If you don't want to register yet, just enter the email address from which you intend to post,
+									and your personal address will be emailed to you.</t>
+									<div class="second-font big-title" style="margin:1.2em 0 .2em">
+										<t>Enter your email address</t>
+									</div>
+									<n.form.>
+										<input type="hidden" name="node" value="[n.page_node.id/]"/>
+										<input type="hidden" name="action" value="send"/>
+										<input type="text" size="40" maxlength="80" name="email"/><br/>
+										<input type="submit" value="[t]Send email to me[/t]" style="margin-top:.5em"/>
+									</n.form.>
+								</else>
+							</n.if.page_node.only_members_can_create_topics>
+						</then>
+						<else> 
+							<n.if.either condition1="[n.not.page_node.only_members_can_create_topics/]" condition2="[n.visitor.can_create_topic_in.page_node/]">
+								<then>
+									<t>Instead of posting via the web interface, you can also post new topics by sending emails to the following email address:</t>
+									<div style="padding:1em">
+										<span class="info-message rounded bold" style="padding:.4em .3em">
+											<n.page_node.subject/>
+											&lt;<span class="weak-color" style="font-weight:bold"><n.page_node.user_address email="[n.visitor.user_email/]"/></span>&gt;
+										</span>
+									</div>
+									<table style="width:60em">
+										<tr valign="top">
+											<td><img src="/images/icon_alert_sm.png"/></td>
+											<td>
+												<t>Note that this address is unique to you and only accepts emails sent from <t.address><b><n.visitor.user_email/></b></t.address>.
+												The purpose of this design is to help prevent spam.</t>
+											</td>
+										</tr>
+									</table>
+								</then>
+								<else>
+									<t>Sorry, but only members can post messages under <t.app.page_node.subject/>.</t><br/>
+									<t>Please contact the administrators if you need help.</t>
+								</else>
+							</n.if.either>
+						</else>
+					</n.if.visitor.is_anonymous>
+				</div>
+				&laquo; <n.page_node.node_link text="[t]Go back[/t]"/>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="show_email_error">
+	<n.format_error.handle_exception. for="send-email-block">
+		<n.exception. name="invalid_email">
+			<t>Enter a valid email address.</t>
+		</n.exception.>
+	</n.format_error.handle_exception.>
+</macro>
+
+<macro name="send_email_address" requires="node_page,servlet" unindent="true">
+	<n.new_email.>
+		<n.send>
+			<to><n.get_parameter name="email"/></to>
+			<subject><t>Email for <t.app.page_node.subject/></t></subject>
+			<text_part>
+				<t>Dear user,</t>
+
+				<t>As you requested, the email address to post new topics under <t.app.page_node.subject/> is:</t>
+
+				<n.page_node.subject/> &lt;<n.page_node.user_address email="[n.get_parameter name='email'/]"/>&gt;
+
+				<t>Note that this address is unique to you and only accepts emails sent from <t.address.get_parameter name="email"/>.
+				The purpose of this design is to help prevent spam.</t>
+
+				<t>Visit <t.app.page_node.subject/> at:</t>
+				<n.page_node.url/>
+
+				<t>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</t>
+
+				<t>Sincerely,</t>
+				<t>The Nabble team</t>
+				________________________________________
+				<t>Free Embeddable <t.app.page_node.view_name/></t> powered by Nabble
+				<n.nabble_homepage/>
+			</text_part>
+			<html_part>
+				<t>Dear user,</t><br/>
+				<br/>
+				<t>As you requested, the email address to post new topics under <t.app><b><n.page_node.subject/></b></t.app> is:</t>
+				<div style="background-color:#FFFADB;border:#EDDD79 solid 1px;margin:1.2em 0;padding:.5em">
+				<n.page_node.subject/> &lt;<n.page_node.user_address email="[n.get_parameter name='email'/]"/>&gt;
+				</div>
+				<t>Note that this address is unique to you and only accepts emails sent from <t.address.get_parameter name="email"/>.
+				The purpose of this design is to help prevent spam.</t><br/>
+				<br/>
+				<t>Visit <t.app.page_node.subject/> at:</t><br/>
+				<n.page_node.url/><br/>
+				<br/>
+				<t>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</t>
+				<br/>
+				<br/>
+				<t>Sincerely,</t><br/>
+				<t>The Nabble team</t><br/>
+				________________________________________<br/>
+				<t>Free Embeddable <t.app.page_node.view_name/></t> powered by Nabble<br/>
+				<n.nabble_homepage/><br/><br/>
+			</html_part>
+		</n.send>
+	</n.new_email.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/print_post.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,21 @@
+<macro name="print_post" requires="servlet">
+	<n.node_page.>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><n.page_node.subject/></n.title.>
+			</head>
+			<body>
+				<h2><n.page_node.subject/></h2>
+
+				<div style="margin-bottom:1em">
+					Posted by <b><n.page_node.owner.name/></b> on <i><n.page_node.when_created.long_format/></i>
+					<br/>
+					<b>URL:</b> <n.page_node.url/><br/>
+					<br/>
+					<n.page_node.message_with_signature/>
+				</div>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/register.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,371 @@
+<macro name="start_registration_page" requires="servlet">
+	<n.if.is_submitted_form>
+		<then.process_registration/>
+	</n.if.is_submitted_form>
+
+	<n.html>
+		<head>
+			<n.title.><t>Register to <t.app.root_node.subject/></t></n.title.>
+			<n.registration_stylesheet/>
+		</head>
+		<body>
+			<div class="center-content">
+				<h1 class="weak-color"><t>Register</t></h1>
+				<span class="second-font shaded-bg-color rounded big-title" style="padding:.2em .7em">
+					<n.root_node.subject/>
+				</span>
+
+				<n.handle_registration_errors/>
+
+				<n.form.>
+					<n.nextUrl_field.hidden/>
+					<div style="text-align:left;margin:0 auto">
+						<n.registration_fields/>
+						<div class="weak-color" style="margin-top:1em;text-align:center;">
+							<input type="submit" class="toolbar action-button" value="[t]Register Now[/t]"/>
+						</div>
+					</div>
+				</n.form.>
+			</div>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="registration_stylesheet">
+	<style type="text/css">
+		body { text-align:center; }
+		div.center-content { margin:0px auto; margin-bottom: 3em; }
+		table.field-table { margin:1em auto; border-collapse:collapse; }
+		table.field-table td { vertical-align:middle; }
+		td.column1 {
+			text-align:right;
+			width:7em;
+			white-space:nowrap;
+			padding-right:.3em;
+		}
+		input[type=text],input[type=password] { padding:.4em 0; }
+		div.field-title { margin-top:.1em; white-space:nowrap; }
+		div.field-title { margin-top:.1em; white-space:nowrap; }
+		.important { font-weight:bold }
+		#nabble-captcha { vertical-align:-.8em }
+	</style>
+</macro>
+
+<macro name="process_registration">
+	<n.catch_exception. id="save-block">
+		<n.validate_registration_form/>
+		<n.check_captcha/>
+
+		<n.registration>
+			<user_name><n.user_name_field.value/></user_name>
+			<email><n.email_field.value/></email>
+			<password><n.password_field.value/></password>
+			<next_url><n.nextUrl_field.value/></next_url>
+			<do>
+				<n.send_registration_email
+					email="[n.email_field.value/]"
+					next_url="[n.nextUrl_field.value/]"
+					key="[n.key/]"
+				/>
+				<n.redirect_to.registering_page_path/>
+			</do>
+		</n.registration>
+	</n.catch_exception.>
+</macro>
+
+<macro name="handle_registration_errors">
+	<n.if.is_submitted_form>
+		<then>
+			<n.if.has_exception for="save-block">
+				<then>
+					<n.handle_exception. for="save-block">
+						<div class="error-message important" style="margin:1em;padding:.5em 0">
+							<n.exception. name="user_already_registered">
+								<t>You have already been registered.</t>
+								<a href="[n.forgot_password_path/]"><t>Forgot your password?</t></a>
+							</n.exception.>
+							<n.exception. name="invalid_recaptcha">
+								<t>Please verify that you are not a robot.</t>
+							</n.exception.>
+							<n.exception. name="empty_registration_field">
+								<t>You must fill in all fields below.</t>
+							</n.exception.>
+							<n.exception. name="must_accept_terms_of_use">
+								<t>You must agree to the Terms of Use.</t>
+							</n.exception.>
+							<n.exception. name="invalid_email">
+								<t>Enter a valid email address.</t>
+							</n.exception.>
+							<n.exception. name="passwords_dont_match">
+								<t>The password fields don't match.</t>
+							</n.exception.>
+							<n.exception. name="user_name_already_in_use">
+								<t>This user name is already in use.</t>
+							</n.exception.>
+						</div>
+					</n.handle_exception.>
+				</then>
+			</n.if.has_exception>
+		</then>
+	</n.if.is_submitted_form>
+</macro>
+
+<macro name="registration_fields">
+	<table class="field-table">
+		<tr>
+			<td class="column1"><div class="second-font field-title"><t>User Name</t></div></td>
+			<td>
+				<n.user_name_field.input type="text" size="35" maxlength="30"/>
+				<n.user_name_field.highlight_if_empty/>
+			</td>
+		</tr>
+		<tr>
+			<td class="column1"><div class="second-font field-title"><t>Email</t></div></td>
+			<td>
+				<n.email_field.input type="text" size="35" maxlength="60"/>
+				<n.email_field.highlight_if_empty/>
+			</td>
+		</tr>
+		<tr>
+			<td></td>
+			<td class="weak-color" style="margin-top:1em;font-size:80%;padding-bottom:1em">
+				<t>You will receive an email with a link to activate your account.</t>
+			</td>
+		</tr>
+		<tr>
+			<td class="column1"><div class="second-font field-title"><t>Password</t></div></td>
+			<td>
+				<n.password_field.input type="password" size="15" maxlength="15"/>
+				<n.password_field.highlight_if_empty/>
+			</td>
+		</tr>
+		<tr>
+			<td class="column1"><div class="second-font field-title"><t>Confirm Password</t></div></td>
+			<td>
+				<n.password2_field.input type="password" size="15" maxlength="15"/>
+				<n.password2_field.highlight_if_empty/>
+			</td>
+		</tr>
+		<tr>
+			<td class="column1" style="padding-top:.5em">
+				<n.accept_terms_field.checkbox/>
+			</td>
+			<td class="nowrap" style="padding-top:.5em">
+				<label for="accept_terms"><t>I have read and I agree to Nabble's <n.terms_link.>Terms of Use</n.terms_link.>.</t></label>
+			</td>
+		</tr>
+		<tr>
+			<td></td>
+			<td style="padding-top:.5em">
+				<n.captcha_control/>
+			</td>
+		</tr>
+	</table>
+</macro>
+
+<macro name="validate_registration_form">
+	<n.user_name_field.notify_if_empty/>
+	<n.email_field.notify_if_empty/>
+	<n.password_field.notify_if_empty/>
+	<n.password2_field.notify_if_empty/>
+
+	<n.if.not.equal value1="[n.password_field.value/]" value2="[n.password2_field.value/]">
+		<then.throw_template_exception name="passwords_dont_match"/>
+	</n.if.not.equal>
+
+	<n.if.not.accept_terms_field.is_checked>
+		<then.throw_template_exception name="must_accept_terms_of_use"/>
+	</n.if.not.accept_terms_field.is_checked>
+</macro>
+
+<macro name="notify_if_empty" requires="field">
+	<n.if.is_empty.value>
+		<then.throw_template_exception name="empty_registration_field"/>
+	</n.if.is_empty.value>
+</macro>
+
+<macro name="highlight_if_empty" requires="field">
+	<n.if.both condition1="[n.is_submitted_form/]" condition2="[n.is_empty.value/]">
+		<then><span class="important"><t>required</t></span></then>
+	</n.if.both>
+</macro>
+
+<macro name="accept_terms_field" dot_parameter="do">
+	<n.field. name="accept_terms"><n.do/></n.field.>
+</macro>
+
+<macro name="user_name_field" dot_parameter="do">
+	<n.field. name="user_name"><n.do/></n.field.>
+</macro>
+
+<macro name="password2_field" dot_parameter="do">
+	<n.field. name="password2"><n.do/></n.field.>
+</macro>
+
+<macro name="registering_page" requires="servlet">
+	<n.html>
+		<head>
+			<n.title.><t>Registering...</t></n.title.>
+			<style type="text/css">
+				body { text-align:center; }
+				div.center-content { margin:0px auto; margin-bottom: 3em; }
+			</style>
+		</head>
+		<body>
+			<div class="center-content">
+				<h1 class="weak-color"><t>Registering...</t></h1>
+				<p><t>An email has been sent to you.</t></p>
+				<p><t>Please follow the instructions in the email to complete the registration process.</t></p>
+				<n.show_email_warning/>
+			</div>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="registering_page_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=registering_page
+	</n.encode_url.>
+</macro>
+
+<macro name="show_email_warning" requires="servlet">
+	<div class="info-message" style="padding: .45em;margin: 1em 0">
+		<t>If you are not receiving emails from Nabble, please check your <b>spam</b> folder.</t>
+	</div>
+</macro>
+
+
+<macro name="finish_registration_page" requires="servlet">
+	<n.html>
+		<head>
+			<n.title.><t>Registration Confirmed</t></n.title.>
+		</head>
+		<body>
+			<n.set_local_user.get_user_from_email email="[n.email_field.value/]" />
+			<n.if.both condition1="[n.not.is_null.local_user/]" condition2="[n.local_user.is_registered/]">
+				<then>
+					<h1><t>Registration Confirmed</t></h1>
+					<p><t>You have already been registered.</t></p>
+				</then>
+				<else>
+					<n.save_registration/>
+				</else>
+			</n.if.both>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="save_registration">
+	<n.catch_exception. id="save-block">
+		<n.set_local_user.get_registration registration_key="[n.get_parameter name='key'/]" email="[n.email_field.value/]" />
+		<n.if.is_null.local_user>
+			<then>
+				<h1><t>Registration Failed</t></h1>
+				<p><t>Please make sure you are using the same browser that you used to fill in the registration request.</t></p>
+				<p><t>You can try <n.register_link.>registering again</n.register_link.> or contact <n.support_link/>.</t></p>
+			</then>
+			<else>
+				<n.local_user.register/>
+				<n.set_var. name="next_url">
+					<n.get_next_url_from_registration registration_key="[n.get_parameter name='key'/]" />
+				</n.set_var.>
+
+				<h1><t>Registration Confirmed</t></h1>
+				<p><t>You have been registered to <t.subject.root_node.subject/>.</t></p>
+				<p><a href="[n.var name='next_url'/]"><t>Continue</t></a></p>
+
+				<n.local_user.after_registration/>
+			</else>
+		</n.if.is_null.local_user>
+	</n.catch_exception.>
+	<n.if.has_exception for="save-block">
+		<then>
+			<h1><t>Registration Failed</t></h1>
+			<br />
+			<n.handle_exception. for="save-block">
+				<n.comment.>
+					fill in as needed
+				</n.comment.>
+			</n.handle_exception.>
+		</then>
+	</n.if.has_exception>
+</macro>
+
+<macro name="after_registration" requires="user">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="send_registration_email" parameters="email, next_url, key" unindent="true">
+	<n.set_var. name='confirm_url'>
+		<n.remove_spaces.>
+			<n.base_url/>
+			<n.encode_url.>
+				/template/NamlServlet.jtp?macro=finish_registration_page
+				&email=<n.email/>
+				&key=<n.key/>
+				&date=<n.now.raw_time/>
+			</n.encode_url.>
+		</n.remove_spaces.>
+	</n.set_var.>
+	<n.new_email.>
+		<n.send>
+			<to><n.email/></to>
+			<subject><t><t.app.root_node.subject/> Registration</t></subject>
+			<text_part>
+				<t>Dear user,</t>
+
+				<t>Thank you for registering with <t.app.root_node.subject/>!</t>
+
+				*<t>Email Confirmation</t>*
+				<t>Please click on the confirmation link below to activate your account:</t>
+
+				<n.var name='confirm_url'/>
+
+				<t>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</t>
+
+				<t>Sincerely,</t>
+				<n.root_node.subject/>
+				<n.base_url/>
+			</text_part>
+			<aol_part>
+				<t>Dear user,</t>
+
+				<t>Thank you for registering with <t.app.root_node.subject/>!</t>
+
+				*<t>Email Confirmation</t>*
+				<t>Please click on the confirmation link below to activate your account:</t>
+
+				<n.var name='confirm_url'/>
+
+				<t>If you didn't request this email or have no idea why you received it, please ignore it. It might have been a mistake from someone else.</t>
+
+				<t>Sincerely,</t>
+				<n.root_node.subject/>
+				<n.base_url/>
+			</aol_part>
+			<html_part>
+				<t>Dear user,</t><br/>
+				<br/>
+				<t>Thank you for registering with <t.app.root_node.subject/>!</t><br/>
+				<br/>
+				<div style="font-weight:bold;margin-bottom:.25em">
+					<t>Email Confirmation</t>
+				</div>
+				<t>Please click on the confirmation link below to activate your account:</t>
+				<div style="background-color:#FFFADB;border:#EDDD79 solid 1px;margin:1.2em 0;padding:.5em">
+					<a href="[n.var name='confirm_url'/]"><n.var name='confirm_url'/></a>
+				</div>
+				<div style="margin:.3em 0 1.2em">
+					<t>If you didn't request this email or have no idea why you received it, please ignore it.
+						It might have been a mistake from someone else.</t>
+				</div>
+				<t>Sincerely,</t><br/>
+				<n.root_node.subject/><br/>
+				<a href="[n.base_url/]"><n.base_url/></a>
+				<div style="margin:1.5em 0;color:#666666;font: 11px tahoma,geneva,helvetica,arial,sans-serif;">
+					<n.macro_viewer_email_link macro="send_registration_email"/>
+				</div>
+			</html_part>
+		</n.send>
+	</n.new_email.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/reply.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,704 @@
+<macro name="reply" requires="servlet">
+	<n.new_post>
+		<page_name>
+			<t>Reply</t>
+		</page_name>
+		<focus>
+			<n.message_field.focus/>
+		</focus>
+		<mailing_list_etiquette>
+			<li><t>Quote what you reply to and trim it to only the relevant parts. This provides context for those who will read your message by email.</t></li>
+			<li><t>Avoid small talk such as "Thank you", "Great"... You can <n.page_node.reply_to_author_link.>send a private email</n.page_node.reply_to_author_link.> if you want.</t></li>
+		</mailing_list_etiquette>
+		<bottom>
+			<n.in_reply_to/>
+		</bottom>
+	</n.new_post>
+</macro>
+
+<macro name="new_post" parameters="page_name,mailing_list_etiquette,bottom,focus" requires="servlet">
+	<n.node_page.>
+		<n.handle_new_node_permission_error/>
+		<n.if.not.is_submitted_form>
+			<then>
+				<n.subject_field.set_value value="[n.page_node.default_reply_subject/]" />
+				<n.alert_field.set_value value="[n.page_node.alert_default_value/]" />
+				<n.init_new_post_custom_fields/>
+			</then>
+			<else>
+				<n.catch_exception. id="save-block">
+					<n.handle_anonymous_submit/>
+					<n.check_antispam_submit bypass="preview"/>
+					<n.check_recent_post_limit/>
+					<n.create_child_of_page_node commit="[n.not.is_preview/]">
+						<subject><n.subject_field.value/></subject>
+						<message><n.message_field.value/></message>
+						<is_html><n.html_format_field.value/></is_html>
+						<type><n.type_field.value/></type>
+						<kind>post</kind>
+						<do>
+							<n.remember_new_node/>
+							<n.if.not.is_preview>
+								<then>
+									<n.if.new_node.is_associated_with_mailing_list_archive>
+										<then>
+											<n.send_mail_to_list/>
+										</then>
+										<else>
+											<n.save_post/>
+											<n.save_new_post_custom_fields/>
+											<n.new_node.send_node_as_email/>
+										</else>
+									</n.if.new_node.is_associated_with_mailing_list_archive>
+								</then>
+							</n.if.not.is_preview>
+						</do>
+					</n.create_child_of_page_node>
+					<n.if.not.is_preview>
+						<then>
+							<n.if.not.new_node.is_associated_with_mailing_list_archive>
+								<then>
+									<n.new_node.save_alert_field/>
+									<n.redirect_to.new_node.path/>
+								</then>
+							</n.if.not.new_node.is_associated_with_mailing_list_archive>
+						</then>
+					</n.if.not.is_preview>
+				</n.catch_exception.>
+			</else>
+		</n.if.not.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><n.page_name/></n.title.>
+				<n.focus/>
+				<style type="text/css">
+					.title-row {
+						padding:.6em .8em;
+						font-weight:bold;
+					}
+					div.field-title {
+						margin-top: 0;
+					}
+				</style>
+			</head>
+			<body>
+				<n.edit_header first_text="[n.page_name/]" second_text="[n.truncate. size='80'][n.page_node.subject/][/n.truncate.]" />
+
+				<n.if.is_submitted_form>
+					<then>
+						<n.if.has_exception for="save-block">
+							<then.show_new_node_error/>
+							<else>
+								<n.if.is_preview>
+									<then.new_node.preview/>
+									<else.if.new_node.is_associated_with_mailing_list_archive>
+										<then>
+											<n.new_node.preview/>
+											<n.after_send/>
+											<n.global_set_var name="sent" value="true" />
+										</then>
+									</else.if.new_node.is_associated_with_mailing_list_archive>
+								</n.if.is_preview>
+							</else>
+						</n.if.has_exception>
+					</then>
+				</n.if.is_submitted_form>
+
+				<n.if.not.global_is_var_set name="sent">
+					<then>
+						<n.subscription_reminder/>
+		
+						<n.form. onsubmit="return singleSubmit(this)">
+							<n.type_field.hidden/>
+							<n.mailing_list_notice.mailing_list_etiquette/>
+		
+							<n.reply_form />
+		
+							<div style="margin-top:1em">
+								<n.antispam_submit_button class="toolbar action-button" value="[t]Post Message[/t]"/>
+								<input type="submit" class="toolbar action-button" name="preview" value="[t]Preview Message[/t]"/>
+								<t>or</t>
+								<a href="[n.page_node.path /]"><t>Cancel</t></a>
+							</div>
+						</n.form.>
+		
+						<n.hide_null.bottom/>
+					</then>
+				</n.if.not.global_is_var_set>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="reply_form">
+	<n.if.not.visitor.is_registered>
+		<then.anonymous_name_control/>
+	</n.if.not.visitor.is_registered>
+
+	<n.subject_control/>
+	<n.message_control/>
+
+	<n.if.either condition1="[n.not.visitor.is_registered/]" condition2="[n.visitor.has_too_many_posts/]">
+		<then.captcha_control/>
+	</n.if.either>
+</macro>
+
+<macro name="remember_new_node" requires="node_editor">
+	<n.global_set_var name="new_node" value="[n.edited_node/]" />
+</macro>
+
+<macro name="new_node" dot_parameter="do" requires="node_page">
+	<n.get_node node="[n.global_var name='new_node'/]" do="[n.do/]" />
+</macro>
+
+<macro name="subject_control">
+	<div class="field-box light-border-color">
+		<div class="second-font field-title"><t>Subject</t></div>
+		<div class="weak-color">
+			<n.subject_field.input type="text" size="60" tabindex="1" />
+		</div>
+	</div>
+</macro>
+
+<macro name="message_control" requires="node_page">
+	<div class="field-box light-border-color">
+		<div class="second-font field-title"><t>Message</t></div>
+		<div class="weak-color">
+			<n.if.visitor.is_registered>
+				<then>
+					<n.html_format_field.checkbox />
+					<label for="[n.html_format_field.name/]"><t>Message is in HTML Format</t></label><br/>
+					<div style="margin:.1em 0">
+						<n.editor_toolbar
+							textarea_id="[n.message_field.name/]"
+							original_text="[n.if.page_node.is_post][then.page_node.message_quoted/][/n.if.page_node.is_post]"
+						/>
+					</div>
+				</then>
+			</n.if.visitor.is_registered>
+			<n.message_field.textarea wrap="SOFT" tabindex="2" style="min-width:30em;max-width:55em;width:100%;height:20em;" />
+			<n.new_post_extra_fields/>
+		</div>
+	</div>
+</macro>
+
+<macro name="extra_fields" dot_parameter="fields" requires="node_page">
+	<n.put_in_head.>
+		<style type="text/css">
+			div.extra-fields {
+				margin-top:.5em;
+			}
+			div.extra-field {
+				width:2em;
+				float:left;
+				text-align:center;
+			}
+			div.extra-field-details {
+				border-left-width:2px;
+				border-left-style:dotted;
+				margin-left:2.5em;
+				padding:0 0 1em 1em;
+			}
+		</style>
+		<script type="text/javascript">
+			$(document).ready(function(){
+				$('div.extra-fields').each(function() {
+					$(this).children().first().wrap('<div class="extra-field"></div>');
+				});
+			});
+		</script>
+	</n.put_in_head.>
+	<n.fields/>
+</macro>
+
+<macro name="new_post_extra_fields" requires="node_page">
+	<n.extra_fields.>
+		<n.if.not.page_node.is_associated_with_mailing_list_archive>
+			<then>
+				<n.if.visitor.is_registered>
+					<then>
+						<div class="extra-fields">
+							<n.alert_field.checkbox style="margin-top:.1em"/>
+							<label for="[n.alert_field.name/]"><t>Alert me by email when someone posts to this thread</t></label>
+						</div>
+		
+						<div class="extra-fields">
+							<img src="/images/mail.png" width="18" height="13" style="margin-top:.2em"/>
+							<n.send_node_as_email_input />
+						</div>
+					</then>
+				</n.if.visitor.is_registered>
+			</then>
+		</n.if.not.page_node.is_associated_with_mailing_list_archive>
+	</n.extra_fields.>
+</macro>
+
+<macro name="anonymous_name_control">
+	<div class="field-box light-border-color">
+		<div class="second-font field-title"><t>Your Name</t></div>
+		<div class="weak-color">
+			<n.if.is_null.visitor.name>
+				<then.anonymous_name_field.input size="30" tabindex="0" />
+				<else.visitor.name/>
+			</n.if.is_null.visitor.name>
+		</div>
+	</div>
+</macro>
+
+<macro name="captcha_control">
+	<n.captcha_div/>
+</macro>
+
+<macro name="save_alert_field" requires="node,servlet">
+	<n.if.null_to_false.alert_field.value>
+		<then.topic_node.subscribe_visitor/>
+		<else.topic_node.unsubscribe_visitor/>
+	</n.if.null_to_false.alert_field.value>
+</macro>
+
+<macro name="visitor_is_subscribed_to_topic" requires="node,servlet">
+	<n.both condition1="[n.is_post/]" condition2="[n.topic_node.visitor_is_subscribed/]" />
+</macro>
+
+<macro name="anonymous_name_field" dot_parameter="do">
+	<n.field. name="anonymous_name"><n.do/></n.field.>
+</macro>
+
+<macro name="type_field" dot_parameter="do">
+	<n.field. name="type"><n.do/></n.field.>
+</macro>
+
+<macro name="show_new_node_error">
+	<n.format_error.handle_exception. for="save-block">
+		<n.field_errors/>
+		<n.file_errors/>
+		<n.permission_errors/>
+		<n.spam_errors/>
+		<n.custom_new_node_errors/>
+	</n.format_error.handle_exception.>
+</macro>
+
+<macro name="permission_errors" requires="error">
+	<n.exception. name="banned">
+		<t>You can't post a message here, but you can post in other places.</t>
+	</n.exception.>
+	<n.exception. name="too_many_posts">
+		<t>You have made too many posts in a short time. Please try again later.</t>
+	</n.exception.>
+</macro>
+
+<macro name="field_errors" requires="error">
+	<n.exception. name="invalid_recaptcha">
+		<t>Please verify that you are not a robot.</t>
+	</n.exception.>
+	<n.exception. name="required_subject">
+		<t>The subject is required.</t>
+	</n.exception.>
+	<n.exception. name="required_name">
+		<t>You must provide a user name.</t>
+	</n.exception.>
+	<n.invalid_email_exception.>
+		<t>Invalid email address: <n.email/></t>
+	</n.invalid_email_exception.>
+</macro>
+
+<macro name="file_errors" requires="error">
+	<n.invalid_file_exception.>
+		<t>File was not uploaded: <t.file.filename/> (please upload again or remove the tag)</t>
+	</n.invalid_file_exception.>
+	<n.invalid_image_exception.>
+		<t>Image was not uploaded: <t.image.filename/> (please upload again or remove the tag)</t>
+	</n.invalid_image_exception.>
+</macro>
+
+<macro name="spam_errors" requires="error">
+	<n.spam_exception. name="subject_contains_invalid_spam_word">
+		<t>[Spam Detector] Message contains common spam words.</t>
+	</n.spam_exception.>
+	<n.spam_exception. name="message_contains_invalid_spam_word">
+		<t>[Spam Detector] Message contains common spam words.</t>
+	</n.spam_exception.>
+	<n.spam_exception. name="message_contains_many_invalid_spam_words">
+		<t>[Spam Detector] Message contains common spam words.</t>
+	</n.spam_exception.>
+	<n.exception. name="subject_contains_common_spam_words">
+		<t>[Spam Detector] Message contains common spam words.</t>
+	</n.exception.>
+	<n.exception. name="message_contains_common_spam_words">
+		<t>[Spam Detector] Message contains common spam words.</t>
+	</n.exception.>
+	<n.exception. name="suspicious_request">
+		<t>Submit failed, please try again.</t>
+	</n.exception.>
+</macro>
+
+<macro name="custom_new_node_errors">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="handle_new_node_permission_error">
+	<n.catch_exception. id="check-permission">
+		<n.visitor.check_posting_under.page_node/>
+	</n.catch_exception.>
+	<n.handle_exception. for="check-permission">
+		<n.exception. name="no_create_topic_permission">
+			<n.new_topic_forbidden_page/>
+		</n.exception.>
+		<n.exception. name="read_only">
+			<n.new_topic_forbidden_page/>
+		</n.exception.>
+		<n.exception. name="no_reply_permission">
+			<n.redirect_to.page_node.node_with_permission permission="[n.reply_permission/]" do="[n.unauthorized_path/]" />
+		</n.exception.>
+		<n.exception. name="banned">
+			<n.ban_error_page/>
+		</n.exception.>
+		<n.exception. name="no_anonymous">
+			<n.login.><t>You must login to your account.</t></n.login.>
+		</n.exception.>
+	</n.handle_exception.>
+</macro>
+
+<macro name="new_topic_forbidden_page">
+	<n.set_var name="has_descendants_where_can_reply" value="[n.false/]" />
+	<n.page_node.descendant_apps_list.loop.>
+		<n.if.visitor.can_create_topic_in.current_node>
+			<then>
+				<n.set_var name="has_descendants_where_can_reply" value="[n.true/]" />
+				<n.break/>
+			</then>
+		</n.if.visitor.can_create_topic_in.current_node>
+	</n.page_node.descendant_apps_list.loop.>
+	<n.html>
+		<head>
+			<META NAME="robots" CONTENT="noindex,nofollow"/>
+			<n.title.>
+				<n.if.var name="has_descendants_where_can_reply">
+					<then><t>Choose a Subcategory</t></then>
+					<else><t>Authorized Users Only</t></else>
+				</n.if.var>
+			</n.title.>
+		</head>
+		<body>
+			<div style="font-size:140%;margin:.5em 0 1em">
+				<n.if.var name="has_descendants_where_can_reply">
+					<then><t>Choose a subcategory to post your message</t></then>
+					<else><t>You Cannot Post Here</t></else>
+				</n.if.var>
+			</div>
+			<n.if.var name="has_descendants_where_can_reply">
+				<then>
+					<n.page_node.descendant_apps_list.loop.>
+						<n.if.visitor.can_create_topic_in.current_node>
+							<then>
+								<div style="margin:.3em"><n.current_node.new_topic_link text="[n.subject/]" /></div>
+							</then>
+						</n.if.visitor.can_create_topic_in.current_node>
+					</n.page_node.descendant_apps_list.loop.>
+				</then>
+				<else>
+					<t>Sorry, but you can't create new topics here.<br/>Note that you may still be able to reply to posts.</t>
+					<br/><br/>
+					<n.if.visitor.is_site_admin>
+						<then><t><b>Note</b>: Since you are an administrator, you can <n.page_node.change_permissions_link.>change the permissions of <t.location.page_node.subject/></n.page_node.change_permissions_link.> and make sure you can create new topics here.</t></then>
+						<else><t>You may <n.page_node.unauthorized_link.>request permission to post</n.page_node.unauthorized_link.> here or contact <n.root_node.owner.send_email_link.><n.root_node.owner.name/></n.root_node.owner.send_email_link.> if you have questions.</t></else>
+					</n.if.visitor.is_site_admin>
+				</else>
+			</n.if.var>
+		</body>
+	</n.html>
+	<n.exit/>
+</macro>
+
+<macro name="ban_error_page">
+	<n.html>
+		<head>
+			<META NAME="robots" CONTENT="noindex,nofollow"/>
+			<n.title.><t>Unable to Post</t></n.title.>
+		</head>
+		<body>
+			<div style="font-size:140%;margin:.5em 0 1em">
+				<t>Unable to Post</t>
+			</div>
+			<t>Sorry, but the administrators have banned you.</t>
+			<t>You can't post a message here, but you can post in other places.</t>
+			<br/><br/>
+			<t>Please contact Nabble Support if you need help.</t>
+		</body>
+	</n.html>
+	<n.exit/>
+</macro>
+
+<macro name="subscription_reminder" requires="node_page">
+	<n.if.page_node.is_associated_with_mailing_list_archive>
+		<then>
+			<n.page_node.get_associated_mailing_list_archive.>
+				<div id="window" class="window-content no-bg-color medium-border-color">
+					<n.put_in_head.>
+						<style type="text/css">
+							div.subscription-reminder {
+								font-weight:bold;
+								font-size:110%;
+								margin-bottom:1em;
+								text-align:center;
+								padding:.5em;
+							}
+						</style>
+					</n.put_in_head.>
+					<div class="second-font shaded-bg-color subscription-reminder">
+						<t>Mailing List Subscription Reminder</t>
+					</div>
+					<t>This forum is an archive/gateway which will forward your post to the <b><n.mailing_list_address/></b> mailing list.</t>
+					<br/><br/>
+					<t>The mailing list may require your subscription before accepting your post. Please note that being registered with Nabble does NOT automatically subscribe you to this mailing list. If you haven't subscribed yet, please do it now. If you aren't sure or don't remember, just subscribe again because there is no harm.</t>
+					<br/><br/>
+
+					<div class="nowrap">
+						<n.if.can_subscribe>
+							<then><n.subscribe_button.><t>Subscribe</t></n.subscribe_button.></then>
+							<else><a href="[n.mailing_list_url/]"><t>View mailing list website</t></a></else>
+						</n.if.can_subscribe>
+						<t>or</t>
+						<n.mailing_list_options_link.><t>Learn more</t></n.mailing_list_options_link.>
+					</div>
+
+					<br/>
+					<table style="margin-bottom:1em">
+						<tr>
+							<td><input type="checkbox" id="do-not-show" name="subscribe" onclick="Nabble.toggleMsg('ml-reminder', this.checked)"/></td>
+							<td><label for="do-not-show"><t>Don't show this message again</t></label></td>
+						</tr>
+					</table>
+					<div style="text-align:center;">
+						<input type="button" onclick="$('#window,#black-overlay').fadeOut();" value="[t]I'm a subscriber, let me post now[/t]"></input>
+					</div>
+				</div>
+				<div id="black-overlay" class="black-overlay"></div>
+				<script type="text/javascript">
+					if (Nabble.openMsg('ml-reminder'))
+						$('#window,#black-overlay').fadeIn();
+				</script>
+			</n.page_node.get_associated_mailing_list_archive.>
+		</then>
+	</n.if.page_node.is_associated_with_mailing_list_archive>
+</macro>
+
+<macro name="subscribe_button" requires="mailing_list" dot_parameter="text">
+	<form action="/mailing_list/Subscribe.jtp" style="display:inline">
+		<input type="hidden" name="forum" value="[n.mailing_list_node.id/]" />
+		<input type="submit" value="[n.text/]" />
+	</form>
+</macro>
+
+<macro name="mailing_list_notice" requires="node_page" dot_parameter="etiquette">
+	<n.if.page_node.is_associated_with_mailing_list_archive>
+		<then>
+			<n.page_node.get_associated_mailing_list_archive.>
+				<div class="title-row info-message" style="font-weight:normal">
+					<t>This message will be sent from <b><t.from.visitor.user_email/></b> to the <b><t.to.mailing_list_address/></b> mailing list.</t>
+					<br/>
+					<t>You may need to <n.mailing_list_options_link.>subscribe to this mailing list</n.mailing_list_options_link.> for your message to be accepted.</t>
+					<div class="important" style="font-size:90%; margin-top: 6px;">
+						<img src="/images/icon_alert_sm.png" width="16"  height="16" alt="" border="0" style="float:left; margin-right: 6px;" />
+						<t>Please respect mailing list etiquette</t>:
+						<ul>
+							<n.etiquette/>
+						</ul>
+					</div>
+					<n.help.mailing_list_intro.link/>
+				</div>
+			</n.page_node.get_associated_mailing_list_archive.>
+		</then>
+	</n.if.page_node.is_associated_with_mailing_list_archive>
+</macro>
+
+<macro name="in_reply_to">
+	<div class="title-row light-border-color shaded-bg-color" style="margin-top:2.5em"><t>In Reply To</t></div>
+	<div style="margin-left:1.2em;padding-top:1em">
+		<n.page_node.>
+			<n.put_in_head.>
+				<style type="text/css">
+					div.in-reply-to {
+						margin-bottom:1em;
+						padding-bottom:.4em;
+						border-bottom-width:1px;
+						border-bottom-style:dotted;
+					}
+				</style>
+			</n.put_in_head.>
+			<div class="medium-border-color in-reply-to">
+				<b><n.break_up.subject/></b>
+				<div class="weak-color" style="padding:.2em 0">
+					<span class="weak-color"><n.when_created.long_format/></span>
+					&mdash;
+					<t>by
+						<t.author>
+							<n.owner.avatar/>
+							<span class="nowrap"><n.owner.user_link/></span>
+						</t.author>
+					</t>
+				</div>
+			</div>
+			<n.message_with_signature/>
+		</n.page_node.>
+	</div>
+</macro>
+
+<macro name="new_post_path" requires="node">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?
+		<n.if.is_app>
+			<then>macro=new_topic</then>
+			<else>macro=reply</else>
+		</n.if.is_app>
+		&node=<n.id/>
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="new_topic_path" requires="node" parameters="type">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=new_topic&node=<n.id/>
+		<n.add_to_path name="type" value="[n.type/]" />
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="alert_default_value" requires="node">
+	<n.visitor_is_subscribed_to_topic/>
+</macro>
+
+<macro name="send_node_as_email_input">
+	<script type="text/javascript">
+		function addFirstAddress() {
+			$('#add-another-address').show();
+			addAddress();
+		};
+		function removeAddress(e) {
+			$(e).parent().parent().remove();
+			var s = $('#address-list table tr').size();
+			if (s == 0)
+				$('#add-another-address').hide();
+			Nabble.resizeFrames();
+		};
+		function addAddress() {
+			$('#address-list').append('<n.javascript_string_encode.remove_spaces_between_tags.send_node_as_email_input_row email=""/>');
+			Nabble.resizeFrames();
+		};
+	</script>
+	<a href="javascript: void(0)" onclick="addFirstAddress()"><t>Email this post to...</t></a>
+	<div id="add-another-address" class="extra-field-details medium-border-color" style="[n.if.not.has_parameter name='email-to'][then]display:none[/then][/n.if.not.has_parameter]">
+		<div id="address-list">
+			<n.get_parameter_values. name="email-to">
+				<n.loop.send_node_as_email_input_row.current_parameter_value/>
+			</n.get_parameter_values.>
+		</div>
+		<a href="javascript: void addAddress()"><t>Add another address</t></a>
+		&nbsp;
+		<span class="weak-color">(<t>one email per input box</t>)</span>
+	</div>
+</macro>
+
+<macro name="send_node_as_email_input_row" dot_parameter="email">
+	<table>
+		<tr>
+			<td class="sendto"><img src="/images/people_sm.png" class="image16"/></td>
+			<td class="sendto"><t>Send To:</t></td>
+			<td><input type="text" class="email-to" name="email-to" size="40" maxlength="80" value="[n.email/]" /></td>
+			<td><a class="removeAddress" href="javascript:void(0)" onclick="removeAddress(this)"><t>remove</t></a></td>
+		</tr>
+	</table>
+</macro>
+
+<macro name="send_node_as_email" requires="node">
+	<n.set_local_node.this_node/>
+	<n.block.>
+		<n.if.visitor.is_registered>
+			<then.get_parameter_values. name="email-to">
+				<n.loop.>
+					<n.set_var name="email" value="[n.parse_email.current_parameter_value/]" />
+					<n.if.not.is_null.var name="email">
+						<then.local_node.topic_or_app.subscription_for. email="[n.var name='email'/]">
+							<n.send_node_as_email_to_user.local_node/>
+						</then.local_node.topic_or_app.subscription_for.>
+					</n.if.not.is_null.var>
+				</n.loop.>
+			</then.get_parameter_values.>
+		</n.if.visitor.is_registered>
+	</n.block.>
+</macro>
+
+<macro name="send_node_as_email_to_user" dot_parameter="node_attr" requires="subscription" unindent="true">
+	<n.set_local_subscription.this_subscription/>
+	<n.set_local_node.node_attr/>
+	<n.send_subscription_email node_attr="[n.node_attr/]">
+		<text_part>
+			<n.local_node.text_email_message_with_signature/>
+
+			______________________________________
+			<t>If you reply to this email, your message will be added to the discussion below</t>:
+			<n.local_node.url/>
+			This email was sent by <n.visitor.name/> (via Nabble)
+			<n.if.not.local_subscription.is_subscribed>
+				<then>
+					To receive all replies by email, subscribe to this discussion: <n.local_subscription.subscribe_by_code_url/>
+				</then>
+			</n.if.not.local_subscription.is_subscribed>
+		</text_part>
+		<html_part>
+			<n.local_node.html_email_message_with_signature/>
+			<br/><br/>
+			<hr noshade="noshade" size="1" color="#cccccc" />
+			<div style="color:#444; font: 12px tahoma,geneva,helvetica,arial,sans-serif;">
+				<div style="font-weight:bold"><t>If you reply to this email, your message will be added to the discussion below</t>:</div>
+				<a href="[n.local_node.url/]"><n.local_node.url/></a>
+			</div>
+			<div style="color:#666; font: 11px tahoma,geneva,helvetica,arial,sans-serif;margin-top:.4em">
+				This email was sent by <a href="[n.visitor.url/]"><n.visitor.name/></a> (via Nabble)<br/>
+				<n.if.not.local_subscription.is_subscribed>
+					<then>
+						To receive all replies by email, <a href="[n.local_subscription.subscribe_by_code_url/]">subscribe to this discussion</a><br/>
+					</then>
+				</n.if.not.local_subscription.is_subscribed>
+			</div>
+		</html_part>
+	</n.send_subscription_email>
+</macro>
+
+<macro name="handle_anonymous_submit" requires="node_page">
+	<n.if.visitor.is_anonymous>
+		<then>
+			<n.if.not.is_preview>
+				<then.check_captcha/>
+			</n.if.not.is_preview>
+			<n.set_anonymous_name name="[n.anonymous_name_field.value/]" />
+		</then>
+	</n.if.visitor.is_anonymous>
+</macro>
+
+<macro name="check_recent_post_limit" requires="node_page">
+	<n.if.visitor.has_too_many_posts>
+		<then.check_captcha/>
+	</n.if.visitor.has_too_many_posts>
+</macro>
+
+<macro name="init_new_post_custom_fields">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="save_new_post_custom_fields">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="has_spambot_security">
+	true
+</macro>
+
+<macro name="after_send" requires="node_page" dot_parameter="etiquette">
+	<n.page_node.get_associated_mailing_list_archive.>
+		<div class="title-row info-message" style="font-weight:normal">
+			<t>This message was sent from <b><t.from.visitor.user_email/></b> to the <b><t.to.mailing_list_address/></b> mailing list.</t>
+			<br/>
+			<t>This message will not appear in this archive until it has been accepted by the mailing list.  You may want to save a copy of this message in case it is not accepted.</t>
+		</div>
+	</n.page_node.get_associated_mailing_list_archive.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/rest_group_control.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,68 @@
+<!--
+Add/Remove users to/from groups.
+http://<site-domain>/template/NamlServlet.jtp?macro=rest_group_control&owner=owner@example.com&password=123456&user=user@example.com&action=add&group=Members
+http://<site-domain>/template/NamlServlet.jtp?macro=rest_group_control&owner=owner@example.com&password=123456&user=user@example.com&action=remove&group=Members
+Sample URLs
+-->
+<macro name="rest_group_control" requires="servlet">
+	<n.check_not_null value="[n.rest_owner_email/]" error_msg="Invalid owner email."/>
+	<n.check_not_null value="[n.rest_owner_password_hash/]" error_msg="Invalid password hash."/>
+	<n.check_not_null value="[n.rest_user_email/]" error_msg="Invalid user email."/>
+	<n.check_not_null value="[n.rest_action/]" error_msg="Missing action."/>
+	<n.check_not_null value="[n.rest_group/]" error_msg="Missing group name."/>
+
+	<n.catch_exception. id="save-block">
+		<n.if.check_registered_user email="[n.rest_owner_email/]" password_hash="[n.rest_owner_password_hash/]">
+			<then>
+				<n.if.regex_matches text="[n.rest_action/]" pattern="add|remove">
+					<then>
+						<n.get_or_create_user. email="[n.rest_owner_email/]">
+							<n.as_user_page.>
+								<n.edit_page_user.>
+									<n.if.equal value1="[n.rest_action/]" value2="add">
+										<then><n.add_to_group.rest_group/></then>
+										<else><n.remove_from_group.rest_group/></else>
+									</n.if.equal>
+								</n.edit_page_user.>
+								Success.
+							</n.as_user_page.>
+						</n.get_or_create_user.>
+					</then>
+					<else>
+						Actions should be "add" or "remove".
+					</else>
+				</n.if.regex_matches>
+			</then>
+			<else>Invalid owner email and password.</else>
+		</n.if.check_registered_user>
+	</n.catch_exception.>
+</macro>
+
+<macro name="check_not_null" parameters="value,error_msg">
+	<n.if.is_null.value>
+		<then>
+			<n.error_msg/>
+			<n.exit/>
+		</then>
+	</n.if.is_null.value>
+</macro>
+
+<macro name="rest_owner_email">
+	<n.get_parameter name="owner"/>
+</macro>
+
+<macro name="rest_owner_password_hash">
+	<n.get_parameter name="password"/>
+</macro>
+
+<macro name="rest_action">
+	<n.get_parameter name="action"/>
+</macro>
+
+<macro name="rest_group">
+	<n.get_parameter name="group"/>
+</macro>
+
+<macro name="rest_user_email">
+	<n.get_parameter name="user"/>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/search.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,462 @@
+<macro name="search_page" requires="servlet">
+	<n.search_page_html/>
+</macro>
+
+<macro name="search_page_html" requires="servlet">
+	<n.define_search_query_field/>
+	<n.node_page.search_namespace.>
+		<n.set_var name='didSearch' value="false" />
+		<n.catch_exception. id="search-block">
+			<n.do_search_using_params />
+			<n.set_var name='didSearch' value="true" />
+		</n.catch_exception.>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.search_page_title/>
+				<n.set_cookies did_search="[n.var name='didSearch'/]" />
+				<n.search_page_style/>
+			</head>
+			<body>
+				<n.show_search_form/>
+				<n.show_search_error/>
+				<n.search_results_header/>
+
+				<n.results.loop.current_node.>
+					<n.search_result_row/>
+				</n.results.loop.current_node.>
+
+				<n.search_pagination/>
+
+				<n.if.not.lucene_is_ready>
+					<then.index_rebuilt_notice/>
+				</n.if.not.lucene_is_ready>
+
+				<n.if.has_results>
+					<then.show_search_form/>
+				</n.if.has_results>
+			</body>
+		</n.html>
+	</n.node_page.search_namespace.>
+</macro>
+
+<macro name="search_page_title" requires="search">
+	<n.title.>
+		<n.case_all.>Search for '<n.search_query_field.value/>'</n.case_all.>
+		<n.case_author_search.>Messages of <n.user.name/></n.case_author_search.>
+	</n.title.>
+</macro>
+
+<macro name="search_page_style" requires="search">
+	<style type="text/css">
+		.nabble td.verbose-search-box {
+			text-align: left;
+			padding-top: .33em;
+			padding-bottom: .33em;
+			padding-left: .17em;
+		}
+		div.search-results-header {
+	        margin:2em 0 1em;
+	        font-size:90%;
+		}
+	</style>
+</macro>
+
+<macro name="search_results_header" requires="search">
+	<div class="search-results-header">
+		<n.if.has_resort>
+			<then>
+				<n.sort_controls/>
+			</then>
+		</n.if.has_resort>
+
+		<img src="/images/search.png" class="image16"/>
+		Found <n.total_posts/> <n.search_description/>
+	</div>
+</macro>
+
+<macro name="search_result_row" requires="node,search">
+	<div style="margin-bottom:1.5em">
+		<div style="margin-bottom:.2em" class="adbayes-content">
+			<n.search_result_topic_subject/>
+			<n.search_result_post_subject/>
+		</div>
+
+		<div style="margin-bottom:.2em" class="adbayes-content">
+			<n.search_result_message_fragment/>
+		</div>
+
+		<div class="weak-color" style="font-size:80%">
+			<n.search_result_in_app/>
+			<n.search_result_on_date/>
+			<n.if.not.is_author_search>
+				<then>
+					by <n.owner.user_link/>
+				</then>
+			</n.if.not.is_author_search>
+			&mdash; <n.topic_node.replies/> replies in thread
+		</div>
+	</div>
+</macro>
+
+<macro name="search_result_topic_subject" requires="node,search">
+	<span class="second-font big-title">
+		<n.node_link text="[n.highlight.topic_node.subject/]" />
+	</span>
+</macro>
+
+<macro name="search_result_post_subject" requires="node,search">
+	<n.if.not.regex_matches pattern=".*[n.regex_quote.topic_node.subject/]" text="[n.subject/]">
+		<then>&nbsp; (<n.highlight.subject/>)</then>
+	</n.if.not.regex_matches>
+</macro>
+
+<macro name="search_result_message_fragment" requires="node,search">
+	<n.highlight.hide_emails.fragment. size="300">
+		<n.message.as_text/>
+	</n.highlight.hide_emails.fragment.>
+</macro>
+
+<macro name="search_result_in_app" requires="node,search">
+	<n.if.is_in_app>
+		<then>
+			in <n.get_app_node.><n.node_link text="[n.highlight.subject/]" /></n.get_app_node.>
+		</then>
+	</n.if.is_in_app>
+</macro>
+
+<macro name="search_result_on_date" requires="node,search">
+	<script type="text/javascript">
+		Nabble.postDate = new Date(<n.when_created.raw_time/>);
+		document.write(Nabble.isToday(Nabble.postDate) ? " at " : " on ");
+		document.write(Nabble.formatDateShort(Nabble.postDate));
+	</script>
+</macro>
+
+
+<macro name="search_pagination" requires="search">
+	<n.search_paging.>
+		<n.if.has_paging>
+			<then>
+				<div style="font-size:90%;text-align:right;margin-bottom:1em">
+					<n.if.has_previous_page>
+						<then>
+							<a href="[n.previous_page_path/]">&laquo; Prev <n.rows_per_page/></a>
+							<n.if.has_next_page>
+								<then> - </then>
+							</n.if.has_next_page>
+						</then>
+					</n.if.has_previous_page>
+					<n.if.has_next_page>
+						<then>
+							<a href="[n.next_page_path/]">Next <n.rows_per_page/> &raquo;</a>
+						</then>
+					</n.if.has_next_page>
+				</div>
+			</then>
+		</n.if.has_paging>
+	</n.search_paging.>
+</macro>
+
+<macro name="index_rebuilt_notice" requires="search">
+	<div class="info-message rounded" style="text-align:center;padding:.5em;margin:.5em 0">
+		<t><b>Warning:</b> The search index is currently being rebuilt. Search results may be incomplete.</t>
+	</div>
+</macro>
+
+<macro name="sort_controls" requires="search">
+	<div style="float:right;margin:0 1em">
+		<n.if.sorted_by_date>
+			<then>
+				<a href="[n.sort_by_relevance_path/]"><t>Sort by relevance</t></a> | <t>Sorted by date</t>
+			</then>
+			<else>
+				<t>Sorted by relevance</t> | <a href="[n.sort_by_date_path/]"><t>Sort by date</t></a>
+			</else>
+		</n.if.sorted_by_date>
+	</div>
+</macro>
+
+<macro name="search_description" requires="search">
+	<n.if.has_query>
+		<then>matching posts for <b><n.search_query_field.value/></b></then>
+		<else>posts</else>
+	</n.if.has_query>
+	in <n.page_node.node_link/>
+	<n.case_author_search.>
+		by <n.user.user_link/>
+	</n.case_author_search.>
+	<n.if.has_days>
+		<then>within <n.days/> days</then>
+	</n.if.has_days>
+	<n.search_paging.>
+		<n.if.has_paging>
+			<then>Showing posts <n.current_page.page_row.plus.one/> to <n.current_page.page_row.plus.rows_per_page/>.</then>
+		</n.if.has_paging>
+	</n.search_paging.>
+</macro>
+
+<macro name="show_search_error" requires="search">
+	<n.format_error.handle_exception. for="search-block">
+		<n.exception. name="search_query_parse_error">
+			This query is invalid.
+			<br/>For more information, read about <a href="http://lucene.apache.org/java/2_4_1/queryparsersyntax.html">Lucene query parser syntax</a>
+		</n.exception.>
+		<n.exception. name="too_many_search_clauses">
+			Your search will give too many matches.
+		</n.exception.>
+	</n.format_error.handle_exception.>
+</macro>
+
+<macro name="show_search_form" requires="search">
+	<div class="shaded-bg-color rounded">
+		<n.page_node.search_form.>
+			<table>
+				<tr>
+					<td class="verbose-search-box">
+						<style type="text/css">
+							div.field-title { margin-top: 0; }
+						</style>
+						<div class="second-font field-title">Search</div>
+						<n.case_author_search.>
+							<select name="author">
+								<option value="[n.author_param/]" selected="true">posts by '<n.user.name/>'</option>
+								<option value="">all posts</option>
+							</select>
+						</n.case_author_search.>
+						<n.search_query_field.input size="40" />
+						<select name="days">
+							<n.if.not.contains_substring string="-0-1-7-30-90-180-365-" substring="-[n.days/]-">
+								<then>
+									<n.days_option. value="[n.days/]">past <n.days/> days</n.days_option.>
+								</then>
+							</n.if.not.contains_substring>
+							<n.days_option. value="0">all dates</n.days_option.>
+							<n.days_option. value="1">past 24 hours</n.days_option.>
+							<n.days_option. value="7">past week</n.days_option.>
+							<n.days_option. value="30">past month</n.days_option.>
+							<n.days_option. value="90">past 3 months</n.days_option.>
+							<n.days_option. value="180">past 6 months</n.days_option.>
+							<n.days_option. value="365">past year</n.days_option.>
+						</select>
+						&nbsp;<input type="submit" value="Go &raquo;" />
+					</td>
+					<td style="font-size:90%;padding:0 0 .3em 1em;vertical-align:bottom">
+						<div style="margin-bottom:.4em">
+							<a href="[n.advanced_search_path_using_params/]">Advanced Search</a>
+						</div>
+						<a href="[n.help.search.url/]">Show Tips</a>
+					</td>
+				</tr>
+			</table>
+		</n.page_node.search_form.>
+	</div>
+</macro>
+
+<macro name="case_all" requires="search" dot_parameter="do">
+	<n.if.is_all>
+		<then><n.do/></then>
+	</n.if.is_all>
+</macro>
+
+<macro name="case_author_search" requires="search" dot_parameter="do">
+	<n.if.is_author_search>
+		<then><n.do/></then>
+	</n.if.is_author_search>
+</macro>
+
+<macro name="days_option" requires="search" parameters="value" dot_parameter="text">
+	<n.option. value="[n.value/]" selected="[n.days/]"><n.text/></n.option.>
+</macro>
+
+<macro name="option" parameters="value,selected" dot_parameter="text">
+	<n.if.equal value1="[n.value/]" value2="[n.selected/]">
+		<then>
+			<option value="[n.value/]" selected="true"><n.text/></option>
+		</then>
+		<else>
+			<option value="[n.value/]"><n.text/></option>
+		</else>
+	</n.if.equal>
+</macro>
+
+<macro name="define_search_query_field">
+	<n.if.not.is_null.search_query_field.value>
+		<then>
+			<n.search_query_field.set_value.>
+				<n.regex_replace_all. pattern="\band\b" replacement="AND">
+					<n.regex_replace_all. pattern="\bor\b" replacement="OR">
+						<n.trim.search_query_field.value/>
+					</n.regex_replace_all.>
+				</n.regex_replace_all.>
+			</n.search_query_field.set_value.>
+		</then>
+	</n.if.not.is_null.search_query_field.value>
+</macro>
+
+<macro name="search_query_field" dot_parameter="do">
+	<n.field. name="query"><n.do/></n.field.>
+</macro>
+
+<macro name="do_search_using_params" requires="search">
+	<n.if.not.is_empty.author_param>
+		<then.query_author.author_param/>
+	</n.if.not.is_empty.author_param>
+	<n.do_search length="[n.search_page_length/]"
+		query="[n.query_param/]"
+		days="[n.get_parameter name='days'/]"
+		start="[n.search_page_index_record/]"
+		sort="[n.get_parameter name='sort'/]"
+	/>
+</macro>
+
+<macro name="author_param">
+	<n.get_parameter name='author'/>
+</macro>
+
+<macro name="query_param">
+	<n.get_parameter name='query'/>
+</macro>
+
+
+
+<macro name="previous_page_path" requires="paging,search">
+	<n.search_path_using_params index_record="[n.previous_page.page_row/]" />
+</macro>
+
+<macro name="next_page_path" requires="paging,search">
+	<n.search_path_using_params index_record="[n.next_page.page_row/]" />
+</macro>
+
+<macro name="sort_by_relevance_path">
+	<n.search_path_using_params sort="relevance" index_record="0" />
+</macro>
+
+<macro name="sort_by_date_path">
+	<n.search_path_using_params sort="date" index_record="0" />
+</macro>
+
+<macro name="search_path_using_params" parameters="query,author,days,index_record,sort" requires="node_page,servlet">
+	<n.page_node.>
+		<n.search_path>
+			<query><n.default_to_param name="query" value="[n.query/]" /></query>
+			<author><n.default_to_param name="author" value="[n.author/]" /></author>
+			<days><n.default_to_param name="days" value="[n.days/]" /></days>
+			<index_record><n.default_to_param name="index_record" value="[n.index_record/]" /></index_record>
+			<sort><n.default_to_param name="sort" value="[n.sort/]" /></sort>
+		</n.search_path>
+	</n.page_node.>
+</macro>
+
+<macro name="default_to_param" parameters="name" dot_parameter="value">
+	<n.default>
+		<text><n.value/></text>
+		<to><n.get_parameter name="[n.name/]"/></to>
+	</n.default>
+</macro>
+
+<macro name="search_path" parameters="query,author,days,index_record,sort" requires="node">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=search_page&node=<n.id/>
+		<n.add_to_path name="query" value="[n.query/]" />
+		<n.add_to_path name="author" value="[n.author/]" />
+		<n.add_to_path name="days" value="[n.days/]" />
+		<n.add_to_path name="i" value="[n.index_record/]" default_value="0" />
+		<n.add_to_path name="sort" value="[n.sort/]" default_value="relevance" />
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="page_search_box" parameters="author" requires="node,servlet">
+	<n.search_form.
+		style="display: inline; margin: 0;"
+		author="[n.author/]"
+	>
+		<input id="nabble.searchQuery" type="text" class="medium-border-color" name="query" size="20" />
+	</n.search_form.>
+</macro>
+
+<macro name="search_form" dot_parameter="do" parameters="style,query,author,days" requires="node">
+	<form style="[n.style/]" action="/template/NamlServlet.jtp">
+		<input type="hidden" name="macro" value="search_page" />
+		<input type="hidden" name="node" value="[n.id/]" />
+		<n.hidden_field name="query" value="[n.query/]" />
+		<n.hidden_field name="author" value="[n.author/]" />
+		<n.hidden_field name="days" value="[n.days/]" />
+		<n.do/>
+	</form>
+</macro>
+
+<macro name="hidden_field" parameters="name,default_value" dot_parameter="value">
+	<n.if.not.is_null.value>
+		<then>
+			<n.if.not.equal value1="[n.value/]" value2="[n.default_value/]">
+				<then>
+					<input type='hidden' name="[n.name/]" value="[n.value/]"/>
+				</then>
+			</n.if.not.equal>
+		</then>
+	</n.if.not.is_null.value>
+</macro>
+
+<macro name="advanced_search_path_using_params" parameters="days" requires="node">
+	<n.advanced_search_path>
+		<days><n.default_to_param name="days" value="[n.days/]" /></days>
+	</n.advanced_search_path>
+</macro>
+
+<macro name="advanced_search_path" parameters="days" requires="node">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=adv_search_page&node=<n.id/>
+		<n.add_to_path name="days" value="[n.days/]" />
+	</n.encode_url.remove_spaces.>
+</macro>
+
+
+<macro name="search_page_index_record">
+	<n.get_parameter name="i"/>
+</macro>
+
+<macro name="search_page_length">
+	12
+</macro>
+
+<macro name="search_paging" dot_parameter="do">
+	<n.paging
+		total_rows = "[n.total_posts/]"
+		current_row="[n.search_page_index_record/]"
+		rows_per_page="[n.search_page_length/]"
+		do = "[n.do/]"
+	/>
+</macro>
+
+
+<macro name="is_all">
+	<n.not.is_author_search/>
+</macro>
+
+<macro name="is_author_search">
+	<n.not.is_empty.author_param />
+</macro>
+
+<macro name="set_cookies" parameters="did_search">
+	<script type="text/javascript">
+		<n.if.both condition1="[n.not.is_empty.query_param/]" condition2="[n.did_search/]">
+			<then>
+				Nabble.setCookie("query", "<n.javascript_string_encode.query_param/>");
+				Nabble.setCookie("searchterms", "<n.search_terms/>");
+			</then>
+			<else>
+				Nabble.deleteCookie("query");
+				Nabble.deleteCookie("searchterms");
+			</else>
+		</n.if.both>
+		<n.if.is_author_search>
+			<then>
+				Nabble.setCookie("searchuser", "<n.user.search_id/>");
+			</then>
+			<else>
+				Nabble.deleteCookie("searchuser");
+			</else>
+		</n.if.is_author_search>
+	</script>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/show_macro.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,31 @@
+<macro name="show_macro" requires="servlet">
+	<n.text_response/>
+	<n.add_line_numbers_to.macro_text macro="[n.macro_param/]" namespace="[n.namespace_param/]"/>
+</macro>
+
+<macro name="macro_param" requires="servlet">
+	<n.get_parameter name="macro"/>
+</macro>
+
+<macro name="namespace_param" requires="servlet">
+	<n.get_parameter name="namespace"/>
+</macro>
+
+
+<macro name="map_show_macro" requires="url_mapper">
+	<n.regex text="[n.path/]">
+		<pattern>
+			^/([^.]+)(?:\.([^.]+))?\.macro\.txt$
+		</pattern>
+		<do>
+			<n.if.find>
+				<then>
+					<n.set_parameter name="macro" value="show_macro" />
+					<n.set_parameter_to_found name="macro" group="1" />
+					<n.set_parameter_if_found name="namespace" group="2" />
+					<n.exit/>
+				</then>
+			</n.if.find>
+		</do>
+	</n.regex>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/static.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,55 @@
+<static>
+	a
+	b
+	big
+	body
+	br
+	button
+	center
+	div
+	em
+	embed
+	font
+	form
+	h1
+	h2
+	h3
+	h4
+	h5
+	head
+	hr
+	html
+	i
+	iframe
+	img
+	input
+	label
+	li
+	link
+	meta
+	nobr
+	noscript
+	object
+	ol
+	option
+	p
+	param
+	pre
+	quote
+	script
+	select
+	small
+	span
+	strong
+	style
+	sup
+	table
+	tbody
+	thead
+	td
+	textarea
+	th
+	title
+	tr
+	ul
+</static>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/subapps.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,99 @@
+<macro name="view_subapps">
+	<n.app_min_html>
+		<head>
+			<n.subapps_title/>
+		</head>
+		<body>
+			<h2><t>Subcategories under <t.location.page_node.subject/></t></h2>
+
+			<n.subapps_table.>
+				<n.subcategories_column/>
+				<n.topic_count_column width="5em"/>
+				<n.post_count_column width="5em"/>
+				<n.last_post_column/>
+			</n.subapps_table.>
+		</body>
+	</n.app_min_html>
+</macro>
+
+<macro name="subapps_title">
+	<title><n.compress.>
+		<n.page_node.>
+			<n.if.not.is_root>
+				<then><n.root_node.subject/> -</then>
+			</n.if.not.is_root>
+			<n.subject/>
+		</n.page_node.>
+		 - <t>List of Subcategories</t>
+	</n.compress.></title>
+</macro>
+
+<macro name="subapps_table" dot_parameter="columns">
+	<n.put_in_head.>
+		<style type="text/css">
+			table.main {
+				margin-top:.2em;
+				border-collapse:collapse;
+				width:100%;
+			}
+			table.main tr.header-row td {
+				padding: .3em .4em;
+				font-weight: bold;
+			}
+			table.main tr.main-row td {
+				padding:.3em .5em;
+				border-bottom-width: 1px;
+				border-bottom-style: dotted;
+			}
+			table.main tr.main-row,
+			table.avatar-table tr {
+				vertical-align:top;
+			}
+			div.sub-forums {
+				margin-top:.8em;
+				font-size:90%;
+				clear:both;
+			}
+		</style>
+	</n.put_in_head.>
+	<div style="clear:both"></div>
+	<table class="main">
+		<n.table_header.>
+			<tr class="header-row shaded-bg-color">
+				<n.columns/>
+			</tr>
+		</n.table_header.>
+		<n.if.page_node.has_subapps>
+			<then>
+				<n.page_node.subapps_list.>
+					<n.preload_messages/>
+					<n.loop.>
+						<n.current_node.>
+							<tr class="main-row [n.category_row_classes/]" node="[n.id/]">
+								<n.columns/>
+							</tr>
+						</n.current_node.>
+					</n.loop.>
+					<n.if.there_is_more>
+						<then>
+							<tr>
+								<td></td>
+								<td colspan="4"><n.page_node.node_link href="[n.url template='view_standard'/]" text="[t]View more[/t]"/> &raquo;</td>
+							</tr>
+						</then>
+					</n.if.there_is_more>
+				</n.page_node.subapps_list.>
+			</then>
+			<else>
+				<tr>
+					<td colspan="4"><t>Empty</t></td>
+				</tr>
+			</else>
+		</n.if.page_node.has_subapps>
+	</table>
+	<div style="clear:both"></div>
+
+	<n.if.page_node.has_private_subapps>
+		<then.category_privacy_js/>
+	</n.if.page_node.has_private_subapps>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/subscribe.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,442 @@
+<macro name="subscribe" requires="servlet">
+	<n.node_page.>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.title.><t>Subscribe via email</t></n.title.>
+				<n.main_title_css/>
+			</head>
+			<body>
+				<n.if.visitor.is_anonymous>
+					<then>
+						<n.edit_header first_text="[n.page_node.subject/]" second_text="[t]Email Subscription[/t]" />
+						<n.if.is_submitted_form>
+							<then>
+								<n.if.equal value1="send-anonymous" value2="[n.action_parameter/]">
+									<then>
+										<n.catch_exception. id="send-email-block">
+											<n.handle_anonymous_subscription/>
+										</n.catch_exception.>
+									</then>
+								</n.if.equal>
+								<n.if.has_exception for="send-email-block">
+									<then.show_subscription_error/>
+									<else.show_success_message/>
+								</n.if.has_exception>
+							</then>
+						</n.if.is_submitted_form>
+						<n.anonymous_subscription_form/>
+					</then>
+					<else>
+						<n.visitor.profile_header/>
+
+						<n.if.is_submitted_form>
+							<then.save_field_values/>
+							<else.load_field_values/>
+						</n.if.is_submitted_form>
+
+						<n.subscription_form/>
+					</else>
+				</n.if.visitor.is_anonymous>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="save_field_values">
+	<n.if.equal value1="save-subscription" value2="[n.action_parameter/]">
+		<then>
+			<n.page_node.visitor_subscription.save
+				to="[n.subscription_to_field.value/]"
+				type="[n.subscription_type_field.value/]"
+			/>
+			<n.redirect_to.subscription_saved_url/>
+		</then>
+	</n.if.equal>
+</macro>
+
+<macro name="load_field_values">
+	<n.subscription_to_field.set_value.>
+		<n.if.page_node.visitor_is_subscribed>
+			<then.page_node.visitor_subscription.to/>
+			<else>DESCENDANTS</else>
+		</n.if.page_node.visitor_is_subscribed>
+	</n.subscription_to_field.set_value.>
+
+	<n.subscription_type_field.set_value.>
+		<n.if.page_node.visitor_is_subscribed>
+			<then.page_node.visitor_subscription.type/>
+			<else>INSTANT</else>
+		</n.if.page_node.visitor_is_subscribed>
+	</n.subscription_type_field.set_value.>
+</macro>
+
+<macro name="subscription_form" requires="node_page">
+	<div class="shaded-bg-color rounded second-font main-title">
+		<n.if.page_node.visitor_is_subscribed>
+			<then><t>Edit Subscription</t></then>
+			<else><t>Confirm Subscription</t></else>
+		</n.if.page_node.visitor_is_subscribed>
+	</div>
+	<n.form.>
+		<input type="hidden" name="action" value="save-subscription"/>
+
+		<n.if.page_node.is_app>
+			<then.app_subscription_controls/>
+			<else.post_subscription_controls/>
+		</n.if.page_node.is_app>
+
+		<n.show_mailing_list_alert/>
+
+		<br/>
+		<input type="submit" class="toolbar action-button" value="[t]Save Subscription[/t]"/>
+
+		<n.if.page_node.visitor_is_subscribed>
+			<then>
+				<input type="button" class="toolbar action-button" value="[t]Unsubscribe[/t]" onclick="window.location='[n.page_node.unsubscribe_path/]'"/>
+			</then>
+		</n.if.page_node.visitor_is_subscribed>
+
+		<t>or</t> <a href="javascript:history.back()"><t>Cancel</t></a>
+	</n.form.>
+</macro>
+
+<macro name="app_subscription_controls">
+	<table style="margin:.2em 0 .5em">
+		<tr>
+			<td><img src="[n.page_node.image_icon/]" class="image16"/></td>
+			<td class="weak-color" style="font-weight:bold">
+				<n.page_node.subject/>
+			</td>
+		</tr>
+	</table>
+	<div style="margin:.5em 1em .2em">
+		<n.subscription_to_field.radio id="all" option_value="DESCENDANTS"/>
+		<label for="all">
+			<n.if.page_node.is_app>
+				<then><t>Receive every message posted in <t.location><n.italic.page_node.subject/></t.location>.</t></then>
+				<else><t>Receive every reply under this topic.</t></else>
+			</n.if.page_node.is_app>
+		</label>
+	</div>
+	<div style="margin:0 1em .2em">
+		<n.subscription_to_field.radio id="direct" option_value="CHILDREN"/>
+		<label for="direct">
+			<n.if.page_node.is_app>
+				<then><t>Receive new topics only.</t></then>
+				<else><t>Receive direct replies only.</t></else>
+			</n.if.page_node.is_app>
+		</label>
+	</div>
+	<div style="margin:1em 0;font-weight:bold">
+		<t>Subscription Format</t>
+	</div>
+	<div style="margin:.5em 1em .2em">
+		<n.subscription_type_field.radio id="individual" option_value="INSTANT"/>
+		<label for="individual"><t>Individual emails</t> <span class="weak-color"><t>(you can reply by email)</t></span></label>
+	</div>
+	<div style="margin:0 1em .2em">
+		<n.subscription_type_field.radio id="daily" option_value="DAILY_DIGEST"/>
+		<label for="daily"><t>Daily digest</t></label>
+	</div>
+</macro>
+
+<macro name="post_subscription_controls" requires="node_page">
+	<n.subscription_to_field.set_value value="DESCENDANTS"/>
+	<n.subscription_to_field.hidden/>
+
+	<n.subscription_type_field.set_value value="INSTANT"/>
+	<n.subscription_type_field.hidden/>
+
+	<div style="font-weight:bold;margin: 2em 0 .5em">
+		<t>Do you really want to subscribe to <t.location><a href="[n.page_node.url/]"><n.page_node.subject/></a></t.location>?</t>
+	</div>
+	<div class="weak-color">
+		<t>You will receive an email for each new message posted under this topic.</t>
+	</div>
+</macro>
+
+<macro name="unsubscribe_path" requires="node">
+	/template/NamlServlet.jtp?macro=unsubscribe&node=<n.id/>
+</macro>
+
+<macro name="show_mailing_list_alert" requires="node_page">
+	<n.if>
+		<condition>
+			<n.either>
+				<condition1.page_node.is_associated_with_mailing_list_archive/>
+				<condition2.page_node.has_sub_archive/>
+			</n.either>
+		</condition>
+		<then>
+			<div class="info-message rounded" style="padding:.5em;margin-top:1em">
+				<t><b>IMPORTANT:</b> This subscription is independent of the real mailing list subscription.
+				Basically, you will subscribe to the forum archive, not to the mailing list itself.
+				The archive subscription won't guarantee that your messages will be accepted by mailing list.</t>
+				<n.if.page_node.is_a_mailing_list_archive>
+					<then>
+						<n.page_node.get_associated_mailing_list_archive.>
+						<t>If you want to subscribe to the mailing list instead,
+						<n.mailing_list_options_link.>visit this page</n.mailing_list_options_link.>.</t>
+						</n.page_node.get_associated_mailing_list_archive.>
+					</then>
+				</n.if.page_node.is_a_mailing_list_archive>
+			</div>
+		</then>
+	</n.if>
+</macro>
+
+<macro name="subscription_to_field" dot_parameter="do">
+	<n.field. name="subscription_to"><n.do/></n.field.>
+</macro>
+
+<macro name="subscription_type_field" dot_parameter="do">
+	<n.field. name="subscription_type"><n.do/></n.field.>
+</macro>
+
+<macro name="image_icon" requires="node">
+	<n.if.is_app>
+		<then>/images/forum_sm.png</then>
+		<else>/images/thread_sm.png</else>
+	</n.if.is_app>
+</macro>
+
+<macro name="show_success_message">
+	<table class="info-message" style="width:100%;padding:.5em">
+		<tr>
+			<td><img src="/images/success.png"/></td>
+			<td style="width:100%;font-weight:bold">
+				<t>Success: a confirmation email has been sent to you.</t>
+			</td>
+		</tr>
+	</table>
+</macro>
+
+<macro name="action_parameter">
+	<n.get_parameter name='action'/>
+</macro>
+
+<macro name="email_parameter">
+	<n.get_parameter name='email'/>
+</macro>
+
+<macro name="code_parameter">
+	<n.get_parameter name='code'/>
+</macro>
+
+<macro name="handle_anonymous_subscription" requires="node_page">
+	<n.set_local_subscription.page_node.subscription_for email="[n.email_parameter/]" />
+	<n.if.visitor.can_view.page_node>
+		<then>
+			<n.if.local_subscription.is_subscribed>
+				<then.throw_template_exception name="already_subscribed"/>
+				<else.local_subscription.send_subscription_confirmation/>
+			</n.if.local_subscription.is_subscribed>
+		</then>
+		<else.throw_template_exception name="not_authorized"/>
+	</n.if.visitor.can_view.page_node>
+</macro>
+
+<macro name="show_subscription_error" requires="node_page">
+	<n.format_error.handle_exception. for="send-email-block">
+		<n.exception. name="invalid_email">
+			<t>Enter a valid email address.</t>
+		</n.exception.>
+		<n.exception. name="not_authorized">
+			<t>Sorry, but this email is not authorized to view messages under <t.location.page_node.subject/>.</t>
+		</n.exception.>
+		<n.exception. name="already_subscribed">
+			<t>This email is already subscribed.</t>
+		</n.exception.>
+	</n.format_error.handle_exception.>
+</macro>
+
+<macro name="anonymous_subscription_form">
+	<div style="margin:1em 0">
+		<t>Enter below your email address and we will send a confirmation email to you.</t>
+		<div class="second-font big-title" style="margin:1em 0 .2em">
+			<t>Enter your email address</t>
+		</div>
+		<n.form.>
+			<input type="hidden" name="node" value="[n.page_node.id/]"/>
+			<input type="hidden" name="action" value="send-anonymous"/>
+			<input type="text" size="40" maxlength="80" name="email"/><br/>
+			<n.if.page_node.is_post>
+				<then>
+					<input type="hidden" name="subscription_to" value="DESCENDANTS"/>
+					<div class="weak-color">
+						<t>You will receive an email for each new message posted under this topic.</t>
+					</div>
+				</then>
+				<else>
+					<input id="sChildren" type="radio" name="subscription_to" value="CHILDREN" checked="true"><label for="sChildren"><t>New topics only</t></label></input><br/>
+					<input id="sDescendants" type="radio" name="subscription_to" value="DESCENDANTS"><label for="sDescendants"><t>All posts</t></label></input><br/>
+				</else>
+			</n.if.page_node.is_post>
+			<input type="submit" class="toolbar action-button" value="[t]Subscribe[/t]" style="margin-top:.5em"/>
+		</n.form.>
+	</div>
+</macro>
+
+<macro name="send_subscription_confirmation" requires="subscription,node_page" unindent="true">
+	<n.set_local_subscription.this_subscription />
+	<n.new_email.>
+		<n.send>
+			<to><n.email_parameter/></to>
+			<subject><t>Subscribe to <t.location.page_node.subject/></t></subject>
+			<text_part>
+				<t>Dear user,</t>
+
+				<t>You have been invited to subscribe to <t.location.page_node.subject/>, which is available at:</t>
+				<n.page_node.url/>
+
+				<t>With your subscription, updates will be sent directly to your email address
+				and you can reply to them to participate in the discussion. Your subscription works the same
+				as a mailing list.</t>
+
+				<t>To confirm your subscription, click on the link below:</t>
+				<n.local_subscription.subscription_confirmation_subscribe_by_code_url/>
+
+				<t>Sincerely,</t>
+				<t>The Nabble team</t>
+				________________________________________
+				<t>Free Embeddable <t.app.page_node.view_name/></t> powered by Nabble
+				<n.nabble_homepage/>
+			</text_part>
+			<html_part>
+				<t>Dear user,</t><br/>
+				<br/>
+				<t>You have been invited to subscribe to <t.location.bold.page_node.subject/>, which is available at:</t><br/>
+				<a href="[n.page_node.url/]"><n.page_node.url/></a><br/>
+				<br/>
+				<t>With your subscription, updates will be sent directly to your email address
+				and you can reply to them to participate in the discussion. Your subscription works the same
+				as a mailing list.</t><br/>
+				<br/>
+				<t>To confirm your subscription, click on the link below:</t>
+				<div style="background-color:#FFFADB;border:#EDDD79 solid 1px;margin:1.2em 0;padding:.5em">
+					<a href="[n.local_subscription.subscription_confirmation_subscribe_by_code_url/]">
+						<n.local_subscription.subscription_confirmation_subscribe_by_code_url/>
+					</a>
+				</div>
+				<t>Sincerely,</t><br/>
+				<t>The Nabble team</t><br/>
+				________________________________________<br/>
+				<t>Free Embeddable <t.app.page_node.view_name/></t> powered by Nabble<br/>
+				<n.nabble_homepage/>
+			</html_part>
+		</n.send>
+	</n.new_email.>
+</macro>
+
+<macro name="subscription_confirmation_subscribe_by_code_url" requires="subscription">
+	<n.subscribe_by_code_url subscription_to="[n.subscription_to_field.value/]"/>
+</macro>
+
+<macro name="subscribe_by_code_url" parameters="subscription_to" requires="subscription">
+	<n.remove_spaces.>
+		<n.base_url/>
+		<n.encode_url.>
+			/template/NamlServlet.jtp?macro=subscribe_by_code&node=<n.node.id/>&code=<n.subscription_code/>
+			<n.add_to_path name="subscription_to" value="[n.subscription_to/]" />
+		</n.encode_url.>
+	</n.remove_spaces.>
+</macro>
+
+<macro name="subscribe_by_code" requires="servlet">
+	<n.node_page.>
+		<n.catch_exception. id="save-subscription">
+			<n.page_node.get_subscription_by_code. code="[n.code_parameter/]">
+				<n.if.not.is_subscribed>
+					<then.save to="[n.subscription_to_field.value/]" type="INSTANT" />
+					<else.throw_template_exception name="already_subscribed"/>
+				</n.if.not.is_subscribed>
+			</n.page_node.get_subscription_by_code.>
+		</n.catch_exception.>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.title.><t>Subscription Confirmation</t></n.title.>
+			</head>
+			<body>
+				<n.edit_header first_text="[n.page_node.subject/]" second_text="Subscription Confirmation" />
+				<br/>
+				<n.if.has_exception for="save-subscription">
+					<then>
+						<n.handle_exception. for="save-subscription">
+							<n.exception. name="invalid_link">
+								<h2><t>Invalid Code</t></h2>
+								<p><t>The code in the URL is not valid.</t>
+								<t>Please contact Nabble Support if you need help.</t></p>
+							</n.exception.>
+							<n.exception. name="already_subscribed">
+								<h2><t>Already Subscribed</t></h2>
+								<p><t>You are already subscribed to <n.page_node.subject/>.</t></p>
+							</n.exception.>
+						</n.handle_exception.>
+					</then>
+					<else>
+						<h2><t>Subscription Confirmed</t></h2>
+						<p><t>From now on, you will receive an email for each message posted under <t.location.page_node.subject/>.</t></p>
+					</else>
+				</n.if.has_exception>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="subscription_saved_url">
+	<n.remove_spaces.>
+		<n.page_node.base_url/>
+		/template/NamlServlet.jtp?macro=subscription_saved&node=<n.page_node.id/>
+	</n.remove_spaces.>
+</macro>
+
+<macro name="subscription_saved" requires="servlet">
+	<n.node_page.>
+		<n.subscription_msg
+			header="[t]Subscription Confirmed[/t]"
+			message="[t]Your subscription has been successfully saved.[/t]"
+		/>
+	</n.node_page.>
+</macro>
+
+<macro name="subscription_msg" parameters="header,message">
+	<n.html>
+		<head>
+			<n.title.><n.header/></n.title.>
+			<n.main_title_css/>
+		</head>
+		<body>
+			<n.visitor.profile_header/>
+			<div class="shaded-bg-color rounded second-font main-title">
+				<n.header/>
+			</div>
+
+			<table style="margin-bottom:1em">
+				<tr valign="top">
+					<td><img src="/images/success.png" class="image16"/></td>
+					<td>
+						<b><n.message/></b>
+						<p><a href="[n.page_node.url/]"><t>Return to <t.location.page_node.subject/></t></a></p>
+					</td>
+				</tr>
+			</table>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="main_title_css">
+	<style type="text/css">
+		div.main-title {
+			font-size:120%;
+			font-weight:bold;
+			margin:1em 0;
+			padding: .2em;
+		}
+	</style>
+</macro>
+
+<macro name="edit_path" requires="subscription">
+	/template/NamlServlet.jtp?macro=subscribe&node=<n.node.id/>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/text_editor.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,503 @@
+<macro name="editor_toolbar" parameters="textarea_id, original_text, node_id">
+	<n.put_in_head.>
+		<n.editor_stylesheet/>
+		<n.editor_shared_scripts textarea_id="[n.textarea_id/]" node_id="[n.node_id/]"/>
+		<script type="text/javascript" src="/util/minmax.js"></script>
+	</n.put_in_head.>
+	
+	<div class="toolbar rounded-top shaded-bg-color">
+		<table class="toobar">
+			<tr>
+				<n.editor_quote_button original_text="[n.original_text/]"/>
+				<n.editor_insert_image_button/>
+				<n.editor_bold_button/>
+				<n.editor_italic_button/>
+				<n.editor_link_button/>
+				<n.editor_smiley_button/>
+				<n.editor_subheaders_button/>
+				<n.editor_embed_button/>
+				<n.editor_more_options_button/>
+			</tr>
+		</table>
+	</div>
+</macro>
+
+<macro name="editor_shared_scripts" parameters="textarea_id, node_id">
+	<script type="text/javascript">
+		var textareaID = "<n.textarea_id/>";
+		var nodeId = "<n.hide_null.node_id/>";
+		<![CDATA[
+		Nabble.closeWindows = function() {
+			$('div.editor-dropdown').fadeOut('fast');
+		};
+
+		Nabble.insert = function(t) {
+			Nabble.insertTag(t);
+			Nabble.closeWindows();
+		};
+
+		Nabble.insertTag = function(tag) {
+			var textarea = Nabble.get(textareaID);
+			var s = this.getSelection(textarea);
+			if( s != "") {
+				this.setSelection( textarea, "<" + tag + ">" + s + "</" + tag + ">" );
+			} else {
+				this.setSelection( textarea, "<" + tag + "></" + tag + ">" );
+			}
+			textarea.focus();
+		};
+
+		Nabble.getSelection = function(textarea) {
+			if (document.selection) {
+				textarea.focus();
+				return document.selection.createRange().text;
+			} else if (textarea.selectionStart) {
+				return textarea.value.substr(textarea.selectionStart,textarea.selectionEnd-textarea.selectionStart);
+			} else if (textarea.selectionEnd) {
+				return textarea.value.substr(0,textarea.selectionEnd);
+			}
+			return '';
+		};
+
+		Nabble.setSelection = function(textarea,text) {
+			var hasStart = typeof (textarea.selectionStart) != 'undefined';
+			var hasEnd = typeof (textarea.selectionEnd) != 'undefined';
+			if (hasStart || hasEnd) {
+				var start = textarea.selectionStart? textarea.selectionStart : 0;
+				var t = textarea.value;
+				var s = start + text.length;
+				var scrollTop;
+				if (textarea.scrollTop)
+					scrollTop = textarea.scrollTop;
+
+				textarea.value = t.substr(0,start) + text + t.substr(textarea.selectionEnd);
+
+				if (typeof scrollTop != 'undefined')
+					textarea.scrollTop = scrollTop;
+
+				textarea.setSelectionRange(s,s);
+				return;
+			}
+			if( document.selection ) {
+				var r = document.selection.createRange();
+				if( r.parentElement()==textarea ) {
+					r.text = text;
+					r.select();
+					return;
+				}
+			}
+			textarea.value += text;
+		};
+
+		$(document).ready(function() {
+			var $buttons = $('td button.toolbar');
+			var h = $buttons.eq(0).outerHeight();
+			$buttons.each(function() {
+				$(this).height(h);
+			});
+			$(document).click(function(o){
+				var $btn = $(o.target).parents();
+				if ($btn.hasClass('has-dropdown'))
+					return;
+				Nabble.closeWindows();
+			});
+		});
+		]]>
+	</script>
+</macro>
+
+<macro name="editor_quote_button" parameters="original_text">
+	<n.if.not.is_empty.original_text>
+		<then>
+			<n.put_in_head.>
+				<script type="text/javascript">
+					Nabble.quoteOriginal = function(f) {
+						var $m = $("[name='message']");
+						$m.val($m.val() + $('#original').val());
+						$m.focus();
+					};
+				</script>
+			</n.put_in_head.>
+			<td class="nowrap">
+				<input type="hidden" id="original" name="original" value="[n.encode.original_text/]"/>
+				<button type="button" onclick="Nabble.quoteOriginal()" class="toolbar" title="[t]Quote the original message[/t]">
+					<img src="/images/quote.png" border="0" height="12" alt="Quote" style="vertical-align:middle"/>
+					<t>Quote</t>
+				</button>
+				<n.tooltip use_title="true"/>
+			</td>
+		</then>
+	</n.if.not.is_empty.original_text>
+</macro>
+
+<macro name="editor_insert_image_button">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			<![CDATA[
+			Nabble.uploadImage = function() {
+				Nabble.closeWindows();
+				var $imgDiv = $('#image-upload');
+				var isOpen = $imgDiv.css("display") != 'none';
+				var alreadyLoaded = window.imageuploader && $('#image-upload-form', window.imageuploader.document).size() == 1;
+				if (isOpen)
+					return;
+				else if (alreadyLoaded)
+					$imgDiv.show();
+				else {
+					var f = '';
+					if ($.browser.msie)
+						f += '<br style="line-height:1px"/>';
+					f += "<iframe id='imageuploader' name='imageuploader' src='/forum/UploadImage.jtp?node=" + nodeId + "' width='20em' height='20em' frameBorder='0' scrolling='no' allowtransparency='true'>";
+					$imgDiv.html(f).show();
+				}
+			};
+			Nabble.uploadedImage = function(name,float,desc) {
+				var textarea = Nabble.get(textareaID);
+				var tag = '<nabble_img src="' + name + '" border="0"';
+				if (float == 'left')
+					tag += ' style="float:left; margin:.4em;"';
+				else if (float == 'right')
+					tag += ' style="float:right; margin:.4em;"';
+				else if (float == 'center')
+					tag += ' style="display: block; margin-left:auto; margin-right:auto;"';
+				if (desc)
+					tag += ' alt="' + desc + '"';
+				tag += '/>';
+				this.setSelection(textarea, tag);
+				textarea.focus();
+				Nabble.closeWindows();
+			};
+			]]>
+		</script>
+	</n.put_in_head.>
+	<td class="nowrap has-dropdown">
+		<div id="image-upload" class="editor-dropdown image-upload medium-border-color light-bg-color drop-shadow"></div>
+		<button type="button" onclick="Nabble.uploadImage()" class="toolbar image-upload" title="[t]Add an image to your post[/t]">
+			<img src="/images/image.png" border="0" height="12" alt="Picture" style="vertical-align:middle"/>
+			<t>Insert Image</t>
+		</button>
+		<n.tooltip use_title="true"/>
+	</td>
+</macro>
+
+<macro name="editor_bold_button">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			Nabble.bold = function() {
+				var textarea = Nabble.get(textareaID);
+				var s = this.getSelection(textarea);
+				if( s != "" || (s=prompt("Enter text to make bold:","")) != null ) {
+					this.setSelection( textarea, "<b>" + s + "</b>" );
+				}
+				textarea.focus();
+			};
+		</script>
+	</n.put_in_head.>
+	<td class="nowrap">
+		<button type="button" onclick="Nabble.bold()" class="toolbar bold" style="height:1.65em" title="[t]Bold[/t]">
+			<img src="/images/bold.png"/>
+		</button>
+		<n.tooltip use_title="true"/>
+	</td>
+</macro>
+
+<macro name="editor_italic_button">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			Nabble.italics = function() {
+				var textarea = Nabble.get(textareaID);
+				var s = this.getSelection(textarea);
+				if( s != "" || (s=prompt("Enter text to make italics:","")) != null ) {
+					this.setSelection( textarea, "<i>" + s + "</i>" );
+				}
+				textarea.focus();
+			};
+		</script>
+	</n.put_in_head.>
+	<td class="nowrap">
+		<button type="button" onclick="Nabble.italics()" class="toolbar" style="font-style:italic;height:1.65em" title="[t]Italic[/t]">
+			<img src="/images/italic.png"/>
+		</button>
+		<n.tooltip use_title="true"/>
+	</td>
+</macro>
+
+<macro name="editor_link_button">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			<![CDATA[
+			Nabble.link = function() {
+				var textarea = Nabble.get(textareaID);
+				var url = prompt("Enter URL:","http://");
+				if (url != null) {
+					if (url.indexOf('http') != 0 && url.indexOf('ftp:') != 0)
+						url = 'http://' + url;
+					var text = this.getSelection(textarea);
+					if (text!="" || (text = prompt("Enter link text:",url)) != null) {
+						this.setSelection( textarea, "<a href=\"" + url + "\">" + text + "</a>" );
+					}
+				}
+				textarea.focus();
+			};
+			]]>
+		</script>
+	</n.put_in_head.>
+	<td class="nowrap">
+		<button type="button" onclick="Nabble.link()" class="toolbar" title="[t]Add a link to another page[/t]">
+			<img src="/images/link.png" width="13" height="12" alt="Link" style="vertical-align:middle"/> <t>Link</t>
+		</button>
+		<n.tooltip use_title="true"/>
+	</td>
+</macro>
+
+<macro name="editor_smiley_button">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			var smileyTable = "<n.javascript_string_encode.compress.smiley_table/>";
+			<![CDATA[
+			Nabble.smiley = function(image) {
+				var textarea = Nabble.get(textareaID);
+				if (image.indexOf('http://')==0)
+					this.setSelection( textarea, "<img src=\""+image+"\"/>" );
+				else
+					this.setSelection( textarea, "<smiley image=\""+image+"\"/>" );
+				textarea.focus();
+				Nabble.closeWindows();
+			};
+			var smileysLoaded = false;
+			Nabble.openSmileys = function() {
+				Nabble.closeWindows();
+				var $smileys = $('#smiley-dropdown');
+				if (!smileysLoaded) {
+					$smileys.html(smileyTable);
+					smileysLoaded = true;
+				}
+				$smileys.show();
+			};
+			]]>
+		</script>
+	</n.put_in_head.>
+	
+	<td class="has-dropdown">
+		<div id="smiley-dropdown" class="editor-dropdown medium-border-color light-bg-color drop-shadow" style="margin-left:-15em"></div>
+		<button type="button" onclick="Nabble.openSmileys()" class="toolbar" title="[t]Add smileys and funny animations[/t]">
+			<img src="/images/icon_happy.png" border="0" style="vertical-align:middle"/>
+			<img src="/images/more.png" width="10" height="10"/>
+		</button>
+		<n.tooltip use_title="true"/>
+	</td>
+</macro>
+
+<macro name="editor_subheaders_button">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			Nabble.headersDropdown = function() {
+				Nabble.closeWindows();
+				$('#headers-dropdown').show();
+			};
+		</script>
+	</n.put_in_head.>
+
+	<td class="has-dropdown">
+		<div id="headers-dropdown" class="editor-dropdown medium-border-color light-bg-color drop-shadow" style="padding:.5em;width:25em;margin-left:-15em">
+			<h2><t>Adding Sub-Headers</t></h2>
+			<t>Use tags like <t.example1.bold text="&lt;h2&gt;...&lt;/h2&gt;"/> or <t.example2.bold text="&lt;h3&gt;...&lt;/h3&gt;"/> to create sub-headers.</t>
+			<div style="margin:.5em 0 1em;">
+				<t>Insert</t>
+				<button type="button" class="toolbar" onclick="Nabble.insert('h2')"><t>H2</t></button>
+				<button type="button" class="toolbar" onclick="Nabble.insert('h3')"><t>H3</t></button>
+				<button type="button" class="toolbar" onclick="Nabble.insert('h4')"><t>H4</t></button>
+				<button type="button" class="toolbar" onclick="Nabble.insert('h5')"><t>H5</t></button>
+				<button type="button" class="toolbar" onclick="Nabble.insert('h6')"><t>H6</t></button>
+			</div>
+		</div>
+		<button type="button" class="toolbar" onclick="Nabble.headersDropdown()">
+			<t>H2</t><img src="/images/more.png" width="10" height="10"/>
+		</button>
+	</td>
+</macro>
+
+<macro name="editor_embed_button">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			Nabble.embedDropdown = function() {
+				Nabble.closeWindows();
+				$('#tips-dropdown').show();
+			};
+		</script>
+	</n.put_in_head.>
+	
+	<td class="has-dropdown">
+		<div id="tips-dropdown" class="editor-dropdown medium-border-color light-bg-color drop-shadow" style="padding:.5em;width:25em;margin-left:-15em">
+			<h2><t>Embedding Contents</t></h2>
+			<t>Use <t.tag_names.bold text="&lt;nabble_embed&gt;...&lt;/nabble_embed&gt;"/> to embed widgets from other websites.</t>
+			<t>Currently Nabble supports</t>:
+			<ul style="margin:.3em">
+				<li><t>Videos from Youtube, Vimeo and LiveLeak.</t></li>
+				<li><t>Polls from Polldaddy.com (flash polls only)</t></li>
+			</ul>
+			<t>Just paste the code (provided by the sites above) within these tags and you are ready to post it.</t>
+			<div style="margin-top:.5em">
+				<t>Insert</t>
+				<button type="button" class="toolbar" onclick="Nabble.insert('nabble_embed')"><t>Embed Tags</t></button>
+			</div>
+		</div>
+		<button type="button" class="toolbar" onclick="Nabble.embedDropdown()">
+			<t>Embed</t><img src="/images/more.png" width="10" height="10"/>
+		</button>
+	</td>
+</macro>
+
+<macro name="editor_more_options_button">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			<![CDATA[
+			Nabble.hideEmail = function() {
+				var textarea = Nabble.get(textareaID);
+				var s = this.getSelection(textarea);
+				if( s != "" || (s=prompt("Enter the email address to obfuscate/hide (e.g., show user@host.com as user@...):","")) != null ) {
+					this.setSelection( textarea, "<email>" + s + "</email>" );
+				}
+				textarea.focus();
+			}
+			Nabble.uploadFile = function() {
+				Nabble.closeWindows();
+				var $fileDiv = $('#file-upload');
+				var isOpen = $fileDiv.css("display") != 'none';
+				var alreadyLoaded = window.fileuploader && $('#file-upload-form', window.fileuploader.document).size() == 1;
+				if (isOpen)
+					return;
+				else if (alreadyLoaded)
+					$fileDiv.show();
+				else {
+					var f = '';
+					if ($.browser.msie)
+						f += '<br style="line-height:1px"/>';
+					f += "<iframe id='fileuploader' name='fileuploader' src='/forum/UploadFile.jtp?node=" + nodeId + "' width='380' height='100' frameBorder='0' scrolling='no' allowtransparency='true'>";
+					$fileDiv.html(f).show();
+				}
+			};
+			Nabble.uploadedFile = function(name) {
+				var textarea = Nabble.get(textareaID);
+				this.setSelection( textarea, '<nabble_a href="'+name+'">'+name+'</nabble_a>' );
+				textarea.focus();
+				Nabble.closeWindows();
+			};
+			]]>
+		</script>
+	</n.put_in_head.>
+	<td class="nowrap has-dropdown">
+		<div id="file-upload" class="editor-dropdown medium-border-color light-bg-color drop-shadow" style="margin-left:-5em"></div>
+		<span id="tb-dropdown" class="dropdown">
+			<button class="toolbar" title="[t]Click for more options[/t]" onclick="return false;"><t>More</t><img src="/images/more.png" width="10" height="10"/></button>
+			<n.tooltip use_title="true"/>
+			<table class="light-bg-color medium-border-color drop-shadow">
+				<tr><td title="[t]Upload a file[/t]"><a href="javascript:void Nabble.uploadFile();"><t>Upload a file</t></a></td></tr>
+				<tr><td title="[t]Hide email address (e.g., user@host.com to [n.lt/]hidden email[n.gt/] link)[/t]"><a href="javascript:void Nabble.hideEmail();"><t>Hide email</t></a></td></tr>
+				<tr><td title="[t]Add content that should not be encoded (e.g., code)[/t]"><a href="javascript:void Nabble.insert('raw');"><t>Raw text</t></a></td></tr>
+			</table>
+		</span>
+		<script type="text/javascript">
+			dropdownInit('tb-dropdown');
+		</script>
+	</td>
+</macro>
+
+<macro name="smiley_table">
+	<n.comment.>
+		You can edit the table below if you want to customize the list of smileys.
+		Use the 'smiley' command just like in the default cases, but you can print the
+		full URL of your smileys in the 'src' attribute. Example:
+		<n.smiley name="My Smiley" src="http://domain.com/image.gif"/>
+	</n.comment.>
+	
+	<table style="text-align:center">
+		<tr>
+			<td><n.smiley name="Working" src="anim_working.gif"/></td>
+			<td><n.smiley name="Super" src="smiley_super.gif"/></td>
+			<td><n.smiley name="Whistling" src="smiley_whistling.gif"/></td>
+			<td><n.smiley name="Clapping" src="anim_claps.gif"/></td>
+			<td colspan="2"><n.smiley name="Handshake" src="anim_handshake.gif"/></td>
+			<td><n.smiley name="Jumping" src="anim_jump.gif"/></td>
+			<td colspan="2"><n.smiley name="Rules" src="anim_rules.gif"/></td>
+			<td><n.smiley name="Drunk" src="anim_drunk.gif"/></td>
+		</tr>
+		<tr>
+			<td><n.smiley name="What?" src="smiley_what.gif"/></td>
+			<td><n.smiley name="Confused" src="anim_confused.gif"/></td>
+			<td><n.smiley name="Uh?" src="smiley_uh.gif"/></td>
+			<td><n.smiley name="Crazy" src="anim_crazy.gif"/></td>
+			<td><n.smiley name="Angry" src="smiley_angry.gif"/></td>
+			<td><n.smiley name="Hurt" src="smiley_hurt.gif"/></td>
+			<td><n.smiley name="Pirate" src="smiley_pirate.gif"/></td>
+			<td><n.smiley name="Mustach" src="smiley_mustach.gif"/></td>
+			<td><n.smiley name="Ninja" src="smiley_ninja.gif"/></td>
+			<td><n.smiley name="Music" src="smiley_music.gif"/></td>
+		</tr>
+		<tr>
+			<td><n.smiley name="Unhappy" src="smiley_unhappy.gif"/></td>
+			<td><n.smiley name="Sad" src="smiley_sad.gif"/></td>
+			<td><n.smiley name="Cry" src="smiley_cry.gif"/></td>
+			<td><n.smiley name="Sleep" src="smiley_sleep.gif"/></td>
+			<td><n.smiley name="Argh" src="smiley_argh.gif"/></td>
+			<td><n.smiley name="Scared" src="smiley_scared.gif"/></td>
+			<td><n.smiley name="Oh No" src="smiley_oh_no.gif"/></td>
+			<td><n.smiley name="Oh" src="smiley_oh.gif"/></td>
+			<td><n.smiley name="Evil" src="smiley_evil.gif"/></td>
+			<td><n.smiley name="Teeth" src="smiley_teeth.gif"/></td>
+		</tr>
+		<tr>
+			<td><n.smiley name="Smile" src="smiley_beam.gif"/></td>
+			<td><n.smiley name="Happy" src="smiley_happy.gif"/></td>
+			<td><n.smiley name="Wink" src="smiley_wink.gif"/></td>
+			<td><n.smiley name="Thinking" src="smiley_thinking.gif"/></td>
+			<td><n.smiley name="Grin" src="smiley_grin.gif"/></td>
+			<td><n.smiley name="Good" src="smiley_good.gif"/></td>
+			<td><n.smiley name="Cool" src="smiley_cool.gif"/></td>
+			<td><n.smiley name="Blush" src="smiley_blush.gif"/></td>
+			<td><n.smiley name="Tongue" src="smiley_tongue.gif"/></td>
+			<td><n.smiley name="Blbl" src="anim_blbl.gif"/></td>
+		</tr>
+	</table>
+</macro>
+
+<macro name="smiley" parameters="name,src">
+	<a href="javascript:Nabble.smiley('[n.src/]')">
+		<n.if.starts_with text="[n.src/]" prefix="http://">
+			<then><img src="[n.src/]" alt="[n.name/]" border="0" /></then>
+			<else><img src="/images/smiley/[n.src/]" alt="[n.name/]" border="0" /></else>
+		</n.if.starts_with>
+	</a>
+</macro>
+
+<macro name="editor_stylesheet">
+	<style type="text/css">
+		div.editor-dropdown {
+			margin-top:1.8em;
+			position:absolute;
+			display:none;
+			z-index:1000;
+			border-width:1px;
+			border-style:solid;
+		}
+		.nabble div.toolbar {
+			min-width:30em;
+			max-width:55em;
+			width:100%;
+			padding:.1em;
+		}
+		table.toolbar {
+			border-spacing:0;
+		}
+		table.toolbar td {
+			padding:0;
+		}
+		button {
+			white-space:nowrap;
+		}
+		div > h2 {
+			margin:0;
+			padding:0;
+		}
+	</style>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/tools.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,62 @@
+<macro name="view_log" requires="servlet">
+	<n.if.not.visitor.is_site_admin>
+		<then>
+			<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+		</then>
+	</n.if.not.visitor.is_site_admin>
+	<n.html>
+		<head>
+			<n.title.>Template Logs</n.title.>
+			<script type="text/javascript">
+				function refreshLog() {
+					var call = '/template/NamlServlet.jtp?macro=get_site_log';
+					$.get(call, function(data) {
+						data = Nabble.trim(data);
+						data = data.length == 0? 'Log is empty' : '<pre>'+data+'</pre>';
+						$('#log_data').html(data);
+					});
+				};
+				function clearLog() {
+					var call = '/template/NamlServlet.jtp?macro=clear_site_log';
+					$.get(call, function(data) {
+						if (Nabble.trim(data) == 'ok')
+							$('#log_data').html('Log is empty');
+					});
+				};
+			</script>
+		</head>
+		<body>
+			<n.edit_header first_text="Template Logs" second_text="[n.root_node.get_app_node.subject/]" />
+			<div style="margin:.5em 0">
+				<button class="toolbar" style="font-weight:bold" onclick="refreshLog()"><t>Refresh</t></button>
+				<button class="toolbar" style="font-weight:bold" onclick="clearLog()"><t>Clear Log</t></button>
+			</div>
+			<div id="log_data" style="padding:.3em;margin-top:.5em;overflow-x:auto">
+				<n.if.is_empty.get_log>
+					<then><t>Log is empty</t></then>
+					<else>
+						<pre><n.get_log/></pre>
+					</else>
+				</n.if.is_empty.get_log>
+			</div>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="clear_site_log" requires="servlet">
+	<n.if.visitor.is_site_admin>
+		<then>
+			<n.root_node.clear_log/>
+			ok
+		</then>
+	</n.if.visitor.is_site_admin>
+</macro>
+
+<macro name="get_site_log" requires="servlet">
+	<n.if.visitor.is_site_admin>
+		<then.get_log/>
+	</n.if.visitor.is_site_admin>
+</macro>
+
+<macro name="do_nothing">
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/topic.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1659 @@
+<macro name="topic" requires="servlet">
+	<n.if.is_blog_topic>
+		<then.switch. value="[n.topic_view/]">
+			<n.case value="[n.classic_view/]" do="[n.classic_blog_topic/]" />
+			<n.case value="[n.list_view/]" do="[n.list_blog_topic/]" />
+			<n.case value="[n.threaded_view/]" do="[n.threaded_blog_topic/]" />
+		</then.switch.>
+		<else.switch. value="[n.topic_view/]">
+			<n.case value="[n.classic_view/]" do="[n.classic_forum_topic/]" />
+			<n.case value="[n.list_view/]" do="[n.list_forum_topic/]" />
+			<n.case value="[n.threaded_view/]" do="[n.threaded_forum_topic/]" />
+		</else.switch.>
+	</n.if.is_blog_topic>
+</macro>
+
+<macro name="topic canonical path" requires="http_request">
+	<n.get_node_from_parameter.topic_path
+		view="[n.get_parameter name='view'/]"
+		index_record="[n.get_parameter name='index_record'/]"
+	/>
+</macro>
+
+<macro name="is_blog_topic">
+	<n.regex_matches text="[n.node_page.page_node.app_or_root.type/]" pattern="news|gallery|blog"/>
+</macro>
+
+<namespace name="blog_topic_namespace" />
+<namespace name="forum_topic_namespace" />
+<namespace name="classic_view_namespace" />
+<namespace name="list_view_namespace" />
+<namespace name="threaded_view_namespace" />
+
+<subroutine name="classic_blog_topic" requires="basic,nabble,servlet">
+	<n.classic_view_namespace.blog_topic_namespace.topic_html_page/>
+</subroutine>
+
+<subroutine name="list_blog_topic" requires="basic,nabble,servlet">
+	<n.list_view_namespace.blog_topic_namespace.topic_html_page/>
+</subroutine>
+
+<subroutine name="threaded_blog_topic" requires="basic,nabble,servlet">
+	<n.threaded_view_namespace.blog_topic_namespace.topic_html_page/>
+</subroutine>
+
+<subroutine name="classic_forum_topic" requires="basic,nabble,servlet">
+	<n.classic_view_namespace.forum_topic_namespace.topic_html_page/>
+</subroutine>
+
+<subroutine name="list_forum_topic" requires="basic,nabble,servlet">
+	<n.list_view_namespace.forum_topic_namespace.topic_html_page/>
+</subroutine>
+
+<subroutine name="threaded_forum_topic" requires="basic,nabble,servlet">
+	<n.threaded_view_namespace.forum_topic_namespace.topic_html_page/>
+</subroutine>
+
+
+<macro name="topic_html_page" requires="servlet">
+	<n.topic_html/>
+</macro>
+
+<macro name="topic_html" requires="servlet">
+	<n.topic_min_html>
+		<head>
+			<n.topic_common_head/>
+			<n.topic_head/>
+			<n.topic_meta/>
+			<n.increment_view_count/>
+		</head>
+		<body>
+			<n.page_node.topic_hardcoded_notices/>
+			<n.newsflash/>
+			<n.topic_header/>
+			<n.topic_controls/>
+			<n.topic_contents.view_contents.page_node.topic_rows/>
+			<n.topic_footer/>
+		</body>
+	</n.topic_min_html>
+</macro>
+
+
+<macro name="topic_rows" requires="node,blog_topic_namespace,classic_view_namespace">
+	<n.post_list.
+		sort="date-ascending"
+		start="[n.classic_index_record/]"
+		length="[n.classic_rows_per_page/]"
+	>
+		<n.preload_messages/>
+		<n.loop.classic_row/>
+	</n.post_list.>
+</macro>
+
+<macro name="topic_rows" requires="node,blog_topic_namespace,list_view_namespace">
+	<n.post_list.
+		sort="date-ascending"
+		start="1"
+		length="10000"
+	>
+		<n.preload_messages/>
+		<n.loop.current_node.clickable_post/>
+	</n.post_list.>
+</macro>
+
+<macro name="topic_rows" requires="node,blog_topic_namespace,threaded_view_namespace">
+	<n.children_list. start="0" length="10000">
+		<n.loop.current_node.threaded_posts/>
+	</n.children_list.>
+</macro>
+
+<macro name="topic_rows" requires="node,forum_topic_namespace,classic_view_namespace">
+	<n.post_list.
+		sort="date-ascending"
+		start="[n.classic_index_record/]"
+		length="[n.classic_rows_per_page/]"
+	>
+		<n.preload_messages/>
+		<n.loop.classic_row/>
+	</n.post_list.>
+</macro>
+
+<macro name="topic_rows" requires="node,forum_topic_namespace,list_view_namespace">
+	<n.post_list.
+		sort="date-ascending"
+		start="0"
+		length="10000"
+	>
+		<n.preload_messages/>
+		<n.loop.current_node.clickable_post/>
+	</n.post_list.>
+</macro>
+
+<macro name="topic_rows" requires="node,forum_topic_namespace,threaded_view_namespace">
+	<n.threaded_posts/>
+</macro>
+
+
+<macro name="topic_min_html" parameters="head,body" requires="servlet">
+	<n.node_page.>
+		<n.topic_caching/>
+		<n.check_that_is_post/>
+		<n.html>
+			<head>
+				<n.head/>
+			</head>
+			<body>
+				<n.body/>
+				<n.topic_bottom/>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="topic_bottom">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="topic_caching">
+	<n.uncache_for.>
+		<n.descendant_changes.page_node.id/>
+		<n.bread_crumb_changes.page_node.id/>
+	</n.uncache_for.>
+</macro>
+
+<macro name="topic_title" dot_parameter="ending">
+	<title><n.compress.>
+		<n.page_node.topic_title_contents/>
+		<n.ending/>
+	</n.compress.></title>
+</macro>
+
+<macro name="topic_title_contents" requires="node">
+	<n.if.has_property name="page_title">
+		<then.get_property name="page_title"/>
+		<else.default_topic_title_contents/>
+	</n.if.has_property>
+</macro>
+
+<macro name="default_topic_title_contents" requires="node">
+	<n.app_or_root.subject/> -
+	<n.subject/>
+</macro>
+
+<macro name="topic_meta" requires="node_page,servlet">
+	<n.if.equal value1="[n.topic_view/]" value2="[n.classic_view/]">
+		<then>
+			<n.topic_meta_description/>
+			<n.topic_meta_keywords/>
+		</then>
+		<else>
+			<n.set_var. name='canonical_url'><n.page_node.topic_path view="[n.classic_view/]"/></n.set_var.>
+			<link rel="canonical" href="[n.var name='canonical_url'/]" />
+			<META NAME="robots" CONTENT="noindex,follow"/>
+		</else>
+	</n.if.equal>
+</macro>
+
+<macro name="topic_meta_description" requires="node_page">
+	<META NAME="description" CONTENT="[n.page_number_in_brackets/][n.page_node.build_meta_description/]"/>
+</macro>
+
+<macro name="topic_meta_keywords" requires="node_page">
+	<META NAME="keywords" CONTENT="[n.page_node.build_meta_keywords/]"/>
+</macro>
+
+<macro name="check_that_is_post">
+	<n.if.page_node.is_app>
+		<then.redirect_to.page_node.url/>
+	</n.if.page_node.is_app>
+</macro>
+
+<macro name="is_blog_style">
+	<n.cache. var="is_blog_style">
+		<n.regex_matches text="[n.page_node.app_or_root.type/]" pattern="news|gallery|blog"/>
+	</n.cache.>
+</macro>
+
+<macro name="topic_common_head">
+	<n.search_highlight_js/>
+	<n.message_width_js/>
+</macro>
+
+<macro name="message_width_js">
+	<script type="text/javascript">
+		Nabble.messageTextWidth();
+	</script>
+</macro>
+
+<macro name="search_highlight_js">
+	<script type="text/javascript">
+		var terms = Nabble.getSearchTerms();
+		var hasTurnOff = false;
+		Nabble.searchHighlight = function($elem) {
+			if (terms != null && terms.length > 0) {
+				$elem.each(function() {
+					Nabble.highlightSearchTerms(terms, this);
+				});
+				if (Nabble.hasHighlightedTerms && !hasTurnOff) {
+					var turnOffLink = '<span id="turn-off-highlight-control"><span class="highlight">&nbsp;X&nbsp;</span> ';
+					turnOffLink += '<a href="javascript:void(0)" onclick="Nabble.turnOffHighlight()"><t>Turn off highlighting</t></a></span>';
+					$('#topics-controls-right').prepend(turnOffLink);
+					hasTurnOff = true;
+				}
+			}
+		};
+		Nabble.turnOffHighlight = function() {
+			Nabble.deleteCookie("query");
+			Nabble.deleteCookie("searchuser");
+			Nabble.deleteCookie("searchterms");
+			$('span.search-highlight').removeClass('bold highlight');
+			$('#turn-off-highlight-control').hide();
+		};
+	</script>
+</macro>
+
+<macro name="topic_head" requires="classic_view_namespace">
+	<n.compress.>
+		<n.search_terms_js selector="h2.post-subject,div.message-text"/>
+		<n.classic_js/>
+		<n.if.has_small_avatar>
+			<then.classic_stylesheet_small_avatar/>
+			<else.classic_stylesheet_big_avatar/>
+		</n.if.has_small_avatar>
+	</n.compress.>
+
+	<n.topic_title.>
+		<n.hide_if_equals. value1="[n.topic_page_number/]" value2="1">
+			| <t>Page <t.number.topic_page_number/></t>
+		</n.hide_if_equals.>
+	</n.topic_title.>
+</macro>
+
+<macro name="page_number_in_brackets">
+	<n.hide_if_equals. value1="[n.topic_page_number/]" value2="1">
+		[<t>Page <t.number.topic_page_number/></t>]<n.space/>
+	</n.hide_if_equals.>
+</macro>
+
+<macro name="has_small_avatar">
+	<n.is_blog_style/>
+</macro>
+
+<macro name="search_terms_js" parameters="selector">
+	<script type="text/javascript">
+		$(document).ready(function() {
+			Nabble.searchHighlight($('<n.selector/>'));
+		});
+	</script>
+	<n.call_later value="[n.page_node.id/]" param="searchSpecial"/>
+</macro>
+
+<macro name="search_terms_special_js">
+	<n.param_loop. param="searchSpecial">
+		var nodeIds = [<n.page_node.as_node_page.search_namespace.descendants_ids_matching_search_query/>];
+		for (var i=0; i <n.lt/> nodeIds.length; i++)
+			$('span.post-date'+nodeIds[i]).addClass('highlight');
+	</n.param_loop.>
+</macro>
+
+<macro name="topic_head" requires="list_view_namespace">
+	<n.compress.>
+		<n.tooltip_head/>
+		<n.search_terms_js selector="span.post-subject"/>
+		<n.ajax_posts_js view_name="list"/>
+		<n.list_stylesheet/>
+	</n.compress.>
+	<n.topic_title.>
+		| <t>List View</t>
+	</n.topic_title.>
+</macro>
+
+<macro name="topic_head" requires="threaded_view_namespace">
+	<n.compress.>
+		<n.tooltip_head/>
+		<n.search_terms_js selector="span.post-subject"/>
+		<n.ajax_posts_js view_name="threaded"/>
+		<n.tree_panning_js/>
+		<n.threaded_stylesheet/>
+	</n.compress.>
+	<n.topic_title.>
+		| <t>Threaded View</t>
+	</n.topic_title.>
+</macro>
+
+<macro name="topic_contents" dot_parameter="contents" requires="forum_topic_namespace">
+	<n.topic_contents_div.contents />
+</macro>
+
+<macro name="topic_contents_div" dot_parameter="contents">
+	<div id="topic-contents" style="margin-top:1em;clear:both">
+		<n.contents/>
+	</div>
+</macro>
+
+<macro name="classic_row_count" requires="blog_topic_namespace">
+	<n.page_node.replies/>
+</macro>
+
+<macro name="classic_row_count" requires="forum_topic_namespace">
+	<n.page_node.descendant_count/>
+</macro>
+
+<macro name="classic_row" requires="node_list">
+	<n.if.has_small_avatar>
+		<then.current_node.classic_row_with_small_avatar/>
+		<else.current_node.classic_row_with_big_avatar/>
+	</n.if.has_small_avatar>
+	<n.if.is_last_element>
+		<then.current_node.mark_as_visited/>
+	</n.if.is_last_element>
+</macro>
+
+<macro name="mark_as_visited" requires="node">
+	<n.call_later value="[n.id/]" param="markVisited"/>
+</macro>
+
+<macro name="mark_as_visited_js">
+	<n.if.visitor.is_registered>
+		<then>
+			<n.param_loop. param="markVisited">
+				<n.if_node_parameter_is_valid.>
+					<n.visitor.mark_visited node="[n.page_node.topic_node/]" last_node_id="[n.page_node.id/]"/>
+				</n.if_node_parameter_is_valid.>
+			</n.param_loop.>
+		</then>
+	</n.if.visitor.is_registered>
+</macro>
+
+<macro name="classic_pagination" parameters="margin,row_count">
+	<n.paging.
+		total_rows="[n.row_count/]"
+		current_row="[n.classic_index_record/]"
+		rows_per_page="[n.classic_rows_per_page/]"
+	>
+		<n.generic_paging>
+			<margin><n.margin/></margin>
+			<url><n.page_node.topic_path view="[n.classic_view/]" index_record="[n.page_row/]"/></url>
+		</n.generic_paging>
+	</n.paging.>
+</macro>
+
+<macro name="classic_stylesheet_big_avatar">
+	<style type="text/css">
+		div.classic-header {
+			height:2.2em;
+			clear:both;
+			overflow:hidden;
+		}
+		div.classic-author-name {
+			float:left;
+			width: 140px;
+			overflow: hidden;
+			text-align:center;
+			font-weight:bold;
+		}
+		div.classic-subject-line {
+			left:.5em;
+			overflow:hidden;
+			height:1.3em;
+			position:relative;
+		}
+		div.classic-right-menu {
+			float:right;
+			padding-left:1em;
+		}
+		div.classic-bar {
+			padding:.5em .3em;
+			clear:both;
+			height:1.8em;
+		}
+		table.classic-body {
+			border-collapse:collapse;
+			margin-bottom:1em;
+			table-layout: fixed;
+			width:100%;
+		}
+		td.classic-author {
+			vertical-align: top;
+			text-align:center;
+			width:140px;
+			padding-bottom:1em;
+		}
+		td.classic-message {
+			vertical-align:top;
+			padding:1em;
+		}
+		div.message-text {
+		    cursor:text;
+			overflow-x:auto;
+		}
+		div.avatar-inner {
+			margin-left:20px;
+		}
+		div.avatar-outer {
+			width:140px;
+			text-align:left;
+		}
+		div.avatar-label {
+			white-space:nowrap;
+			font-size:80%;
+		}
+	</style>
+</macro>
+
+<macro name="classic_stylesheet_small_avatar">
+	<style type="text/css">
+		div.classic-header {
+			height:2.2em;
+			min-height:33px;
+			clear:both;
+			overflow:hidden;
+		}
+		div.classic-bar {
+			padding:0 .4em .7em;
+			clear:both;
+			height:1.7em;
+			min-height:33px;
+		}
+		table.classic-author-name {
+			border-spacing:0;
+			width: 140px;
+			float:left;
+		}
+		table.classic-author-name td {
+			overflow-x: hidden;
+			height:2.2em;
+		}
+		table.classic-author-name td.avatar {
+			width:30px;
+			overflow:visible;
+		}
+		div.classic-subject-line {
+			left:.5em;
+			overflow-x:hidden;
+			line-height:2.4em;
+			position:relative;
+		}
+		div.classic-right-menu {
+			float:right;
+			line-height:2.4em;
+			padding-left:1em;
+		}
+		table.classic-body {
+			border-collapse:collapse;
+			margin-bottom:1em;
+			table-layout: fixed;
+			width:100%;
+		}
+		td.classic-author {
+			vertical-align: top;
+			text-align:center;
+			width:5px;
+			padding:1em 0;
+		}
+		td.classic-message {
+			vertical-align:top;
+			padding:1em;
+		}
+		div.message-text {
+		    cursor:text;
+			overflow-x:auto;
+		}
+	</style>
+</macro>
+
+<macro name="classic_row_with_small_avatar" requires="node, node_list">
+	<div class="classic-row">
+		<div class="classic-header">
+			<div class="classic-bar shaded-bg-color rounded-top">
+				<table class="classic-author-name">
+					<tr>
+						<td class="avatar"><n.owner.avatar border_class="medium-border-color"/></td>
+						<td class="bold nowrap"><n.owner.user_link class="owner-link"/></td>
+					</tr>
+				</table>
+				<div class="classic-right-menu shaded-bg-color weak-color">
+					<n.reply_link/> |
+					<n.threaded_link/> |
+					<n.post_dropdown/>
+				</div>
+				<div class="classic-subject-line">
+					<n.classic_post_date/>
+					<n.classic_subject_line/>
+				</div>
+			</div>
+		</div>
+		<table class="classic-body">
+			<tr>
+				<td class="classic-author shaded-bg-color rounded-bottom">
+					<n.red_arrow_icon/>
+				</td>
+				<td class="classic-message">
+					<n.classic_message_cell/>
+				</td>
+			</tr>
+		</table>
+	</div>
+</macro>
+
+<macro name="classic_row_with_big_avatar" requires="node, node_list">
+	<div class="classic-row">
+		<div class="classic-header">
+			<div class="classic-bar shaded-bg-color rounded-top">
+				<div class="classic-author-name nowrap">
+					<n.owner.user_link/>
+				</div>
+				<div class="classic-right-menu shaded-bg-color weak-color">
+					<n.reply_link/> |
+					<n.threaded_link/> |
+					<n.post_dropdown/>
+				</div>
+				<div class="classic-subject-line">
+					<n.red_arrow_icon/>
+					<n.classic_post_date/>
+					<n.classic_subject_line/>
+				</div>
+			</div>
+		</div>
+		<table class="classic-body">
+			<tr>
+				<td class="classic-author shaded-bg-color rounded-bottom">
+					<n.classic_big_avatar_cell/>
+				</td>
+				<td class="classic-message">
+					<n.classic_message_cell/>
+				</td>
+			</tr>
+		</table>
+	</div>
+</macro>
+
+<macro name="classic_big_avatar_cell" requires="node">
+	<div class="avatar-outer">
+		<div class="avatar-inner">
+			<n.owner.avatar size="big" border_class="medium-border-color"/>
+		</div>
+	</div>
+	<n.avatar_label condition="[n.owner.is_site_admin/]" label="[b][t]Administrator[/t][/b]"/>
+	<n.avatar_label condition="[n.owner.is_banned/]" label="[t]Banned User[/t]"/>
+	<n.owner.post_count/>
+</macro>
+
+<macro name="avatar_label" parameters="condition" dot_parameter="label">
+	<n.if.condition>
+		<then>
+			<div class="avatar-label weak-color"><n.label/></div>
+		</then>
+	</n.if.condition>
+</macro>
+
+<macro name="classic_message_cell" requires="node">
+	<n.pending_notice/>
+	<n.updated_notice/>
+	<n.reply_notice/>
+	<n.message_text/>
+</macro>
+
+<macro name="post_count" requires="user">
+	<div class="post-count[n.user_tag_id/] avatar-label weak-color"></div>
+	<n.call_later value="[n.id/]" param="postCount"/>
+</macro>
+
+<macro name="post_count_js">
+	<n.param_loop. param="postCount">
+		<n.if.not.page_user.is_deactivated>
+			<then>
+				$('div.post-count<n.page_user.user_tag_id/>')
+					.html('<n.one_or_many n="[n.page_user.post_count_value/]" one_text="[t]post[/t]" many_text="[t]posts[/t]"/>');
+			</then>
+		</n.if.not.page_user.is_deactivated>
+	</n.param_loop.>
+</macro>
+
+<macro name="updated_notice" requires="node">
+	<n.if.was_updated>
+		<then>
+			<div class="weak-color" style="font-size:80%;padding-bottom:1em">
+				<t>This post was updated on <t.date.when_updated.long_format/>.</t>
+			</div>
+		</then>
+	</n.if.was_updated>
+</macro>
+
+<macro name="pending_notice" requires="node">
+	<n.if.is_pending>
+		<then>
+			<div class="info-message rounded" style="padding:.5em;margin:-.8em -.8em .8em -.8em;font-size:90%">
+				<t>This post has NOT been accepted by the mailing list yet.</t>
+			</div>
+		</then>
+	</n.if.is_pending>
+</macro>
+
+<macro name="reply_notice" requires="node">
+	<n.if.both condition1="[n.has_previous_node/]" condition2="[n.not.parent_node.equals.previous_node/]">
+		<then>
+			<div class="weak-color" style="font-size:80%;padding-bottom:1em">
+				<t>In reply to <n.parent_link.>this post</n.parent_link.> by <t.author.parent_node.owner.name/></t>
+			</div>
+		</then>
+	</n.if.both>
+</macro>
+
+<macro name="parent_link" dot_parameter="text" requires="node">
+	<a href="[n.parent_node.url/]" rel="nofollow"><n.text/></a>
+</macro>
+
+<macro name="classic_subject_line" requires="node">
+	<h2 class="post-subject float-left adbayes-content" style="width:30%;overflow:visible;font-family:inherit">
+		<n.break_up.subject/>
+	</h2>
+</macro>
+
+<macro name="red_arrow_icon" requires="node">
+	<span id="red-arrow[n.id/]" class="float-left invisible" style="margin-top:.2em">
+		<img title="Selected post" width="15" height="15" src="/images/arrow.png" alt="Selected post"/>
+	</span>
+</macro>
+
+<macro name="all_posts_js_array">
+	<n.page_node.post_list.
+		sort="date-ascending"
+		start="0"
+		length="10000"
+	>
+		<n.loop.>
+			<n.if.not.is_first_element>
+				<then>,</then>
+			</n.if.not.is_first_element>
+			<n.current_node.id/>
+		</n.loop.>
+	</n.page_node.post_list.>
+</macro>
+
+<macro name="all_urls_js_array">
+	<n.page_node.post_list.
+		sort="date-ascending"
+		start="0"
+		length="10000"
+	>
+		<n.no_output.next_node/>
+		<n.all_urls_js_array_url/>
+		<n.loop. by="[n.classic_rows_per_page/]">
+			,<n.all_urls_js_array_url/>
+		</n.loop.>
+	</n.page_node.post_list.>
+</macro>
+
+<macro name="all_urls_js_array_url">
+	"<n.page_node.topic_path view="[n.classic_view/]" index_record="[n.current_index/]"/>"
+</macro>
+
+<macro name="classic_js">
+	<script type="text/javascript">
+		var _hash = Nabble.hash();
+		if (_hash) {
+			(function(){
+				var post = _hash.substr(2);
+				var allPosts = [<n.compress.all_posts_js_array/>];
+				var allURLs = [<n.compress.all_urls_js_array/>];
+				var iPost = allPosts.indexOf(parseInt(post));
+				var lower = <n.default. to="0"><n.classic_index_record/></n.default.>;
+				var upper = lower + <n.classic_rows_per_page/>;
+<![CDATA[
+				if (iPost != -1 && (iPost < lower || iPost >= upper))
+]]>
+					location.replace(allURLs[Math.floor(iPost/<n.classic_rows_per_page/>)]+_hash);
+			})();
+		}
+
+		$(document).ready(function() {
+			var rootId = '<n.page_node.id/>';
+<![CDATA[
+			var currentPostId = rootId;
+			var isChangingViews = _hash == '#none';
+			if (_hash && !isChangingViews)
+				currentPostId = _hash.substr(2);
+
+			Nabble.hideQuotes();
+
+			function scrollToSelectedPost() {
+				var $arrow = $('#red-arrow'+currentPostId).show();
+				if ($arrow.size() > 0) {
+					var isRootPost = currentPostId == rootId;
+					if (Nabble.isEmbedded) {
+						if (Nabble.canScroll()) scrollTo(0, 0);
+						var y = isChangingViews? null : isRootPost? 1 : $arrow.parents('div.classic-row').offset().top;
+						Nabble.resizeFrames('', y);
+					} else if (Nabble.canScroll() && !isRootPost) {
+						var yPos = $arrow.offset().top;
+						scrollTo(0,yPos-20);
+					}
+				} else {
+					if (Nabble.isEmbedded && Nabble.canScroll()) {
+						Nabble.resizeFrames('', 1);
+					} else {
+						var tb = $('div.top-bar').get(0);
+						if (tb)
+							tb.scrollIntoView();
+					}
+				}
+			};
+			$(window).load(scrollToSelectedPost);
+
+			if (Nabble.isEmbedded) {
+				$('div.message-text img').load(Nabble.resizeFrames);
+			}
+]]>
+		});
+	</script>
+</macro>
+
+<macro name="classic_post_date" requires="node">
+	<span class="post-date float-left">
+		<n.when_created.long_format/>
+	</span>
+</macro>
+
+<macro name="classic_index_record" requires="servlet">
+	<n.get_parameter name="index_record"/>
+</macro>
+
+<macro name="classic_index_record" requires="blog_topic_namespace, servlet">
+	<n.int. i="[n.default. to='0'][n.get_parameter name='index_record'/][/n.default.]">
+		<n.plus i="1"/>
+	</n.int.>
+</macro>
+
+<macro name="classic_rows_per_page">
+	20
+</macro>
+
+<macro name="view_contents" dot_parameter="rows" requires="classic_view_namespace">
+	<div id="classic-contents">
+		<n.classic_pagination margin="0 .2em .5em" row_count="[n.classic_row_count/]" />
+		<n.rows/>
+		<n.classic_pagination margin="1em .2em 0" row_count="[n.classic_row_count/]" />
+	</div>
+</macro>
+
+<macro name="view_contents" dot_parameter="rows" requires="list_view_namespace">
+	<div id="list-contents">
+		<n.rows/>
+	</div>
+</macro>
+
+<macro name="view_contents" dot_parameter="rows" requires="threaded_view_namespace">
+	<div id="threaded-contents" style="margin-left:0">
+		<n.rows/>
+	</div>
+</macro>
+
+<macro name="threaded_stylesheet">
+	<style type="text/css">
+		#topic-contents {
+			overflow:hidden;
+			cursor: url("/images/hand.png"),w-resize;
+		}
+		div.clickable-row {
+			white-space:nowrap;
+			right:1em;
+			padding:.2em 0;
+			cursor:pointer;
+		}
+		div.full-row {
+			border-width:2px;
+			border-style:solid;
+			padding:.3em .8em .5em;
+			cursor:text;
+		}
+		div.full-row-header {
+			padding-bottom:1em;
+			cursor:pointer;
+		}
+		div.full-row-header > * {
+			cursor:pointer;
+		}
+		div.connect-outer {
+			background-image:url("/images/connect-line.gif");
+		}
+		div.outer-line {
+			background-repeat: repeat-y;
+		}
+		div.outer-end {
+			background-repeat: no-repeat;
+		}
+		div.connect-inner {
+			padding-left:1em;
+			cursor: url("/images/hand.png"),w-resize;
+		}
+		div.inner-line {
+			background:none;
+		}
+		div.inner-end {
+			background:url("/images/connect-end.gif") no-repeat;
+		}
+		span.post-snippet { font-size:75%; }
+
+		div.message-text {
+		    cursor:text;
+			overflow-x:auto;
+		}
+		div.right-menu {
+			float:right;
+			padding:.3em;
+		}
+		img.view-icon {
+			border:none;
+			width:18px;
+			height:18px;
+			vertical-align:middle;
+		}
+		img.close {
+			width:11px;
+			height:11px;
+		}
+	</style>
+</macro>
+
+<macro name="tree_panning_js">
+	<script type="text/javascript">
+		$(document).ready(function() {
+			(function() {
+				var down = null;
+				var initialLeft = null;
+				var $area = $('#threaded-contents');
+				function startDrag(e) {
+					if (e.target == this) {
+						down = e.pageX;
+						initialLeft = parseInt($area.css('margin-left'));
+						return false;
+					}
+				};
+				function drag(e) {
+					if (down) {
+						var m = initialLeft + (e.pageX - down);
+						$area.css('margin-left',m > 0? '0' : m+'px');
+					}
+				};
+				function stopDrag(e){
+					down = null;
+					initialLeft = null;
+				};
+				$('div.connect-inner,#topic-contents')
+					.mousedown(startDrag)
+					.mousemove(drag)
+					.mouseup(stopDrag);
+				$(document).mouseup(stopDrag);
+			})();
+		});
+	</script>
+</macro>
+
+<macro name="ajax_posts_js" parameters="view_name">
+	<script type="text/javascript">
+		var historyLength = history.length;
+		var loadedPosts = [];
+		var allPosts = [<n.compress.all_posts_js_array/>];
+
+		function setPostHover($e) {
+			$e.hover(
+				function(){ $(this).addClass('post-hover'); },
+				function(){ $(this).removeClass('post-hover'); });
+		};
+
+		var lastPostId = <n.page_node.last_node.id/>;
+<![CDATA[
+		Nabble.markUnreadPosts = function(lastVisitedId) {
+			if (lastVisitedId == null)
+				$('div.clickable-row').css('font-weight','bold');
+			if (lastVisitedId == lastPostId)
+				return;
+			else {
+				for (var i = 0; i < allPosts.length; i++) {
+					if (allPosts[i] > lastVisitedId) {
+						$('#clickable-row'+allPosts[i]).css('font-weight','bold');
+					}
+				}
+			}
+		};
+
+		$(document).ready(function() {
+			var $dummy = $('div.clickable-row');
+			setPostHover($dummy);
+			$dummy.click(function(){
+				var postId = $(this).attr('post-id');
+				Nabble.loadPost(postId);
+			});
+
+			/* Code to open initial post */
+			(function() {
+			var _hash = Nabble.hash();
+				var postId = allPosts[0];
+				if (_hash && _hash != '#none')
+					postId = parseInt(_hash.substr(2));
+				else if (_hash == '#none')
+					postId = allPosts.length == 1? allPosts[0] : null;
+				if (postId) {
+					if (allPosts.indexOf(postId) >= 0) {
+						var $dummy = $('#clickable-row'+postId);
+						$dummy.click();
+					}
+				}
+			})();
+		});
+]]>
+
+		var isFirst = true;
+		Nabble.loadPost = function(nodeId) {
+			if (loadedPosts.indexOf(nodeId) == -1) {
+				$('#loading'+nodeId).show("fast");
+				var url = "/"+"template/NamlServlet.jtp?macro=ajax_post&view=<n.view_name/>&node="+nodeId;
+				$.get(url, function(data) {
+					$('#clickable-row'+nodeId).replaceWith(data);
+
+					var $dummy = $('#clickable-row'+nodeId);
+					var $full = $('#full-row'+nodeId);
+
+					/* Click event that collapses the post  */
+					$full.children('div.full-row-header').click(function(e){
+						var t = e.target;
+						if (t == this || $(t).hasClass('close') || (t.nodeName != 'A' && t.nodeName != 'IMG')) {
+							$full.slideUp("normal", function() {
+								$dummy.show();
+								Nabble.resizeFrames();
+							});
+						}
+					});
+
+					/* Click event that expands the post  */
+					$dummy.click(function() {
+						$dummy.hide();
+						$full.slideDown('normal',Nabble.resizeFrames);
+					});
+					setPostHover($dummy);
+
+					var $msg = $('#message'+nodeId);
+					if (Nabble.isEmbedded) {
+						$('img',$msg).load(Nabble.resizeFrames);
+					}
+					Nabble.searchHighlight($msg);
+
+					<n.ajax_post_custom_scripts/>
+
+					$full.slideDown('normal',function(){
+						Nabble.hideQuotes(this);
+						if (isFirst) {
+							var isRootPost = nodeId == allPosts[0];
+							if (Nabble.isEmbedded) {
+								if (Nabble.canScroll()) scrollTo(0,0);
+								var y = isRootPost? 1 : $full.offset().top-30;
+								Nabble.resizeFrames('', y);
+							} else {
+								scrollTo(0,0);
+								if (!isRootPost)
+									scrollBy(0,$full.offset().top);
+							}
+							isFirst = false;
+						}
+					});
+					loadedPosts.push(nodeId);
+				});
+			}
+		};
+	</script>
+	<n.call_later value="[n.page_node.id/]" param="markUnreadPosts"/>
+</macro>
+
+<macro name="ajax_post_custom_scripts">
+	<n.comment>To be overridden</n.comment>
+</macro>
+
+<macro name="mark_unread_posts_js">
+	<n.if.visitor.is_registered>
+		<then>
+			<n.param_loop. param="markUnreadPosts">
+				<n.set_var. name='last_id'>
+					<n.if.visitor.has_visited_node node_id="[n.current_parameter_value/]">
+						<then.visitor.last_visited_node. node_id="[n.current_parameter_value/]">
+							<n.id/>
+						</then.visitor.last_visited_node.>
+						<else>null</else>
+					</n.if.visitor.has_visited_node>
+				</n.set_var.>
+				Nabble.markUnreadPosts(<n.var name='last_id'/>);
+			</n.param_loop.>
+		</then>
+	</n.if.visitor.is_registered>
+</macro>
+
+<subroutine name="threaded_posts" requires="basic,nabble,servlet,node">
+	<n.if.call_depth.is_greater_than i="60">
+		<then>
+				<div class="info-message" style="padding:.5em 1em">
+					<t>This branch is too big and some posts were omitted. Use the other views to read all posts.</t>
+				</div>
+			</then>
+		<else>
+			<div>
+				<n.clickable_post/>
+				<n.children_list. start="0" length="10000">
+					<n.loop.>
+						<div class="connect-outer outer-[n.connect_class_suffix/]">
+							<div class="connect-inner inner-[n.connect_class_suffix/]">
+								<n.current_node.threaded_posts/>
+							</div>
+						</div>
+					</n.loop.>
+				</n.children_list.>
+			</div>
+		</else>
+	</n.if.call_depth.is_greater_than>
+</subroutine>
+
+<macro name="connect_class_suffix" requires="node_list">
+	<n.if.is_last_element>
+		<then>end</then>
+		<else>line</else>
+	</n.if.is_last_element>
+</macro>
+
+<macro name="clickable_post" requires="node">
+	<div id="clickable-row[n.id/]" class="clickable-row" post-id="[n.id/]">
+		<n.post_short_date/>
+		<n.post_author/>
+		<n.post_subject separator="&ndash;"/>
+		<n.post_snippet/>
+		<div id="loading[n.id/]" class="important invisible"><t>Loading...</t></div>
+	</div>
+</macro>
+
+<macro name="ajax_post" requires="servlet">
+	<n.uncache_for.node_changes.get_parameter name="node"/>
+	<n.ajax. is_cached="true">
+		<n.node_page.>
+			<n.page_node.>
+				<n.mark_as_visited/>
+				<div id="clickable-row[n.id/]" class="clickable-row invisible">
+					<n.post_short_date/>
+					<n.post_author/>
+					<n.post_subject/>
+					<n.post_snippet/>
+				</div>
+				<div id="full-row[n.id/]" class="full-row medium-border-color invisible">
+					<div class="full-row-header">
+						<div class="right-menu">
+							<n.change_view_links/>
+							&nbsp;
+							<n.reply_link/>
+							&nbsp;
+							<n.post_dropdown/>
+							&nbsp;
+							<img src="/images/close.png" class="close" alt="Close"/>
+						</div>
+						<n.post_long_date/>
+						<n.owner.avatar size="small"/>
+						<n.owner.user_link/>
+						<n.post_subject/>
+					</div>
+					<n.pending_notice/>
+					<n.updated_notice/>
+					<n.message_text/>
+				</div>
+			</n.page_node.>
+		</n.node_page.>
+	</n.ajax.>
+</macro>
+
+<macro name="message_text" requires="node">
+	<div id="message[n.id/]" class="message-text adbayes-content">
+		<n.message_with_signature/>
+	</div>
+</macro>
+
+<macro name="change_view_links" requires="node">
+	<n.if.equal value1="[n.get_parameter name='view'/]" value2="[n.threaded_view/]">
+		<then>
+			<n.classic_icon_link/>
+			<n.list_icon_link/>
+		</then>
+		<else>
+			<n.classic_icon_link/>
+			<n.threaded_icon_link/>
+		</else>
+	</n.if.equal>
+</macro>
+
+<macro name="list_stylesheet">
+	<style type="text/css">
+		#topic-contents {
+			overflow:hidden;
+		}
+		div.clickable-row {
+			white-space:nowrap;
+			right:1em;
+			padding:.2em 0;
+			cursor:pointer;
+		}
+		div.full-row {
+			border-width:2px;
+			border-style:solid;
+			padding:.3em .8em .5em;
+			cursor:text;
+		}
+		div.full-row-header {
+			padding-bottom:1em;
+			cursor:pointer;
+		}
+		div.full-row-header > * {
+			cursor:pointer;
+		}
+		span.post-snippet { font-size:75%; }
+
+		div.message-text {
+		    cursor:text;
+			overflow-x:auto;
+		}
+		div.right-menu {
+			float:right;
+			padding:.3em;
+		}
+		img.view-icon {
+			border:none;
+			width:18px;
+			height:18px;
+			vertical-align:middle;
+		}
+		img.close {
+			width:11px;
+			height:11px;
+		}
+	</style>
+</macro>
+
+<macro name="post_short_date" requires="node">
+	<span class="post-date post-date[n.id/]">
+		<n.when_created.short_format/>
+	</span>
+</macro>
+
+<macro name="post_long_date" requires="node">
+	<span class="post-date post-date[n.id/]">
+		<n.when_created.long_format />
+	</span>
+</macro>
+
+<macro name="post_author" requires="node">
+	<span class="post-author">
+		<n.owner.name/>
+	</span>
+</macro>
+
+<macro name="post_subject" parameters="separator" requires="node">
+	<n.set_var. name='parent_subject'>
+		<n.if.has_parent>
+			<then.parent_node.subject/>
+		</n.if.has_parent>
+	</n.set_var.>
+	<n.if.not.ends_with text="[n.subject/]" suffix="[n.var name='parent_subject'/]">
+		<then>
+			<span class="post-subject adbayes-content"><n.subject/> <n.hide_null.separator/></span>
+		</then>
+	</n.if.not.ends_with>
+</macro>
+
+<macro name="post_snippet" requires="node">
+	<span class="post-snippet adbayes-content">
+		<n.truncate. size="150"><n.hide_emails.message.as_text_without_quotes/></n.truncate.>
+	</span>
+</macro>
+
+<macro name="topic_footer">
+	<div id="topic-footer" class="weak-color" style="padding-top:1em">
+		<n.page_node.return_to_link/>
+		&nbsp;|&nbsp;
+		<n.page_node.views show_text="true"/>
+	</div>
+</macro>
+
+<macro name="message_count">
+	<span style="padding-right:1em">
+		<n.one_or_many.page_node.descendant_count>
+			<one_text><t>message</t></one_text>
+			<many_text><t>messages</t></many_text>
+		</n.one_or_many.page_node.descendant_count>
+	</span>
+</macro>
+
+<macro name="topic_header">
+	<div id="topic-search-box" class="search-box float-right" style="padding:.5em 0">
+		<n.page_node.search_box/>
+	</div>
+
+	<h1 id="post-title" class="adbayes-content" style="margin:0.25em 0 .8em">
+		<n.break_up.page_node.subject/>
+	</h1>
+</macro>
+
+<macro name="topic_controls" requires="forum_topic_namespace">
+	<div style="margin:1.2em 0 5em">
+		<div id="topics-controls-left" class="float-left nowrap">
+			<n.topic_controls_left/>
+		</div>
+		<div id="topics-controls-right" class="float-right nowrap" style="padding-top:.3em">
+			<n.topic_controls_right/>
+		</div>
+	</div>
+</macro>
+
+<macro name="topic_controls_left" requires="forum_topic_namespace">
+	<n.view_selectors/>
+</macro>
+
+<macro name="topic_controls_right" requires="forum_topic_namespace">
+	<n.pin_icon/>
+	<n.lock_icon/>
+	<n.message_count/>
+	<img src="/images/gear.png" class="image16" alt="[t]Options[/t]"/>
+	<n.page_node.topic_dropdown/>
+</macro>
+
+<macro name="topic_controls" requires="blog_topic_namespace">
+	<div class="weak-color float-left" style="font-variant:small-caps">
+		<n.topic_left_controls/>
+	</div>
+
+	<div class="float-right" style="padding:.4em 0">
+		<n.topic_right_controls/>
+	</div>
+
+	<n.root_post_message/>
+</macro>
+
+<macro name="topic_right_controls" requires="blog_topic_namespace">
+	<n.root_dropdown/>
+</macro>
+
+<macro name="root_post_message">
+	<div class="root-text adbayes-content" style="margin:6em 0 2em;clear:both">
+		<n.page_node.node_message_as_html/>
+	</div>
+</macro>
+
+<macro name="topic_left_controls" requires="blog_topic_namespace">
+	<t>Posted by
+		<t.author>
+			<n.page_node.owner.avatar size="small"/>
+			<n.page_node.owner.user_link/>
+		</t.author></t>
+	&ndash;
+	<n.page_node.when_created.long_format/>
+</macro>
+
+<macro name="root_dropdown" requires="node_page">
+	<img src="/images/gear.png" class="image16" alt="[t]Options[/t]"/>
+	<n.page_node.root_post_dropdown/>
+</macro>
+
+<macro name="topic_contents" dot_parameter="contents" requires="blog_topic_namespace">
+	<div id="comment-section">
+		<n.if.page_node.has_replies>
+			<then>
+				<h2 class="second-font big-title" style="margin-bottom: .5em">
+					<n.one_or_many.page_node.replies>
+						<one_text><t>Comment</t></one_text>
+						<many_text><t>Comments</t></many_text>
+					</n.one_or_many.page_node.replies>
+				</h2>
+				<n.view_selectors/>
+			</then>
+		</n.if.page_node.has_replies>
+
+		<n.topic_contents_div.contents/>
+
+		<n.add_new_comment_link/>
+	</div>
+</macro>
+
+<macro name="add_new_comment_link">
+	<div class="shaded-bg-color rounded" style="width:25em;margin:1em 0 2em;padding: .5em">
+		<div style="float:left;margin-right:.5em">
+			<img src="/images/icon_post_message.png" class="image16" alt="[t]Post message[/t]"/>
+		</div>
+		<a href="[n.page_node.reply_path/]" rel="nofollow"><t>Add a new comment</t></a>
+	</div>
+</macro>
+
+<macro name="view_selectors">
+	<table>
+		<tr>
+			<n.topic_view_option name="[t]Classic[/t]" value="classic" image="view-classic.gif">
+				<url><n.page_node.topic_path view="[n.classic_view/]"/></url>
+			</n.topic_view_option>
+
+			<n.topic_view_option name="[t]List[/t]" value="list" image="view-list.gif">
+				<url><n.page_node.topic_path view="[n.list_view/]"/></url>
+			</n.topic_view_option>
+
+			<n.topic_view_option name="[t]Threaded[/t]" value="threaded" image="view-threaded.gif">
+				<url><n.page_node.topic_path view="[n.threaded_view/]"/></url>
+			</n.topic_view_option>
+		</tr>
+	</table>
+</macro>
+
+<macro name="topic_view" requires="servlet">
+	<n.get_parameter name="view"/>
+</macro>
+
+<macro name="classic_view">
+	classic
+</macro>
+
+<macro name="list_view">
+	list
+</macro>
+
+<macro name="threaded_view">
+	threaded
+</macro>
+
+<macro name="topic_page_number" requires="servlet">
+	<n.paging.
+		total_rows="0"
+		current_row="[n.classic_index_record/]"
+		rows_per_page="[n.classic_rows_per_page/]"
+	>
+		<n.current_page_number/>
+	</n.paging.>
+</macro>
+
+<macro name="topic_path" parameters="view,index_record,selected_id" requires="node">
+	<n.set_var. name="index_record">
+		<n.to_null_if. equals="0">
+			<n.index_record/>
+		</n.to_null_if.>
+	</n.set_var.>
+	<n.remove_spaces.>
+		/<n.url_encoded_subject/>
+		-t<n.view_char view="[n.view/]"/><n.id/>
+		<n.hide_null.prepend. prefix="i"><n.var name="index_record"/></n.hide_null.prepend.>
+		.html
+		<n.if>
+			<condition.both>
+				<condition1><n.not.is_null.selected_id/></condition1>
+				<condition2><n.not.equal value1="[n.id/]" value2="[n.selected_id/]"/></condition2>
+			</condition.both>
+			<then>
+				#a<n.selected_id/>
+			</then>
+		</n.if>
+	</n.remove_spaces.>
+</macro>
+
+<macro name="view_char" parameters="view">
+	<n.if.equal value1="[n.view/]" value2="[n.classic_view/]">
+		<then>d</then>
+		<else.if.equal value1="[n.view/]" value2="[n.threaded_view/]">
+			<then>t</then>
+			<else>c</else>
+		</else.if.equal>
+	</n.if.equal>
+</macro>
+
+<macro name="topic_path_for" dot_parameter="view" requires="node">
+	<n.set_var. name='id'><n.id/></n.set_var.>
+	<n.set_var. name='path'><n.topic_node.topic_path view="[n.view/]" selected_id="[n.var name='id'/]"/></n.set_var.>
+	<n.var name="path"/>
+</macro>
+
+<macro name="threaded_link" requires="node">
+	<a href="javascript:void(0)" onclick="Nabble.setView('threaded', '[n.topic_path_for.threaded_view/]',[n.id/])"><t>Threaded</t></a>
+	<n.tooltip.>
+		<t>Open this post in threaded view</t>
+	</n.tooltip.>
+</macro>
+
+<macro name="classic_icon_link" requires="node">
+	<a href="javascript:void(0)" onclick="Nabble.setView('classic', '[n.topic_path_for.classic_view/]',[n.id/])"><img src="/images/view-classic.gif" class="view-icon" alt="Classic"/></a>
+	<n.tooltip.>
+		<t>Open this post in classic view</t>
+	</n.tooltip.>
+</macro>
+
+<macro name="list_icon_link" requires="node">
+	<a href="javascript:void(0)" onclick="Nabble.setView('list', '[n.topic_path_for.list_view/]',[n.id/])"><img src="/images/view-list.gif" class="view-icon" alt="List"/></a>
+	<n.tooltip.>
+		<t>Open this post in list view</t>
+	</n.tooltip.>
+</macro>
+
+<macro name="threaded_icon_link" requires="node">
+	<a href="javascript:void(0)" onclick="Nabble.setView('threaded', '[n.topic_path_for.threaded_view/]',[n.id/])"><img src="/images/view-threaded.gif" class="view-icon" alt="Threaded"/></a>
+	<n.tooltip.>
+		<t>Open this post in threaded view</t>
+	</n.tooltip.>
+</macro>
+
+<macro name="topic_view_option" parameters="name,value,image,url">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			Nabble.setView = function(view,url,post) {
+				Nabble.setVar("tview",view);
+				if (url.indexOf('#') == -1)
+					url += '#none';
+				location.replace(url);
+			};
+		</script>
+	</n.put_in_head.>
+
+	<n.topic_view_option_td value="[n.value/]" url="[n.url/]" style="padding-right:.1em">
+		<contents><img src="/images/[n.image/]" width="18" height="18" style="border:none" alt="[n.value/]"/></contents>
+	</n.topic_view_option_td>
+
+	<n.topic_view_option_td value="[n.value/]" url="[n.url/]" style="padding-right:1.1em">
+		<contents><n.name/></contents>
+	</n.topic_view_option_td>
+</macro>
+
+<macro name="topic_view_option_td" parameters="value,url,contents,style">
+	<td style="[n.style/]">
+		<n.if.equal value1="[n.topic_view/]" value2="[n.value/]">
+			<then><n.contents/></then>
+			<else><a href="javascript:void(0)" onclick="Nabble.setView('[n.value/]', '[n.url/]',null)"><n.contents/></a></else>
+		</n.if.equal>
+	</td>
+</macro>
+
+<macro name="raw_mail" requires="servlet">
+	<n.set_response_header name="Content-Type" value="text/plain; charset=iso-8859-1"/>
+	<n.node_page.>
+		<n.if.either condition1="[n.visitor.can_edit.page_node/]" condition2="[n.visitor.is_sysadmin/]">
+			<then.page_node.message.as_raw/>
+		</n.if.either>
+	</n.node_page.>
+</macro>
+
+<macro name="topic_hardcoded_notices" requires="node_page">
+	<n.inactive_node_deletion_ui/>
+</macro>
+
+<macro name="page_node_is_scheduled_for_deletion" requires="node_page">
+	<n.both>
+		<condition1><n.page_node.is_root/></condition1>
+		<condition2><n.site_has_delete_date/></condition2>
+	</n.both>
+</macro>
+
+<macro name="pin_icon">
+	<span style="padding-right:1em;height:21px">
+		<img id="pin-icon" src="/images/pin.png" width="20" height="21" title="[t]This topic has been pinned in [t.location.page_node.app_or_root.subject/].[/t]" style="vertical-align:middle;[n.hidden_if.not.page_node.is_pinned/]"/>
+		<n.tooltip use_title="true"/>
+	</span>
+</macro>
+
+<macro name="pin_topic_link" requires="node" dot_parameter="text" parameters="title, class">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			Nabble.pinTopic = function(id) {
+				var call = '/' + 'template/NamlServlet.jtp?macro=pin_topic&node=' + id;
+				$.getScript(call, function() {
+					$('#pin-icon').show();
+					NabbleDropdown.show('unpinTopic');
+					NabbleDropdown.hide('pinTopic');
+					alert('<t>This topic has been pinned.</t>');
+				});
+			};
+		</script>
+	</n.put_in_head.>
+	<a href="javascript: void Nabble.pinTopic([n.id/])" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Pin topic[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="unpin_topic_link" requires="node" dot_parameter="text" parameters="title, class">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			Nabble.unpinTopic = function(id) {
+				var call = '/'+'template/NamlServlet.jtp?macro=unpin_topic&node=' + id;
+				$.getScript(call, function() {
+					$('#pin-icon').hide();
+					NabbleDropdown.hide('unpinTopic');
+					NabbleDropdown.show('pinTopic');
+					alert('<t>This topic has been unpinned.</t>');
+				});
+			};
+		</script>
+	</n.put_in_head.>
+	<a href="javascript: void Nabble.unpinTopic([n.id/])" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Unpin topic[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="pin_topic" requires="servlet">
+	<n.node_page.>
+		<n.if.both condition1="[n.visitor.can_manage_pinned_topics_in.page_node/]" condition2="[n.not.page_node.is_pinned/]">
+			<then><n.page_node.pin/></then>
+		</n.if.both>
+	</n.node_page.>
+</macro>
+
+<macro name="unpin_topic" requires="servlet">
+	<n.node_page.>
+		<n.if.both condition1="[n.visitor.can_manage_pinned_topics_in.page_node/]" condition2="[n.page_node.is_pinned/]">
+			<then><n.page_node.unpin/></then>
+		</n.if.both>
+	</n.node_page.>
+</macro>
+
+<macro name="lock_icon">
+	<span id="lock-icon" class="weak-color" style="padding:0 .5em;margin-right:.5em;[n.hidden_if.not.page_node.is_locked_topic/]">
+		<img src="/images/lock_sm.png" width="10" height="15" style="vertical-align:middle"/> <t>Locked</t>
+	</span>
+</macro>
+
+<macro name="lock_topic_link" requires="node" dot_parameter="text" parameters="title, class">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			Nabble.lockTopic = function(id) {
+				var call = '/'+'template/NamlServlet.jtp?macro=lock_topic&node=' + id;
+				$.getScript(call, function() {
+					$('#lock-icon').show();
+					NabbleDropdown.show('unlockTopic');
+					NabbleDropdown.hide('lockTopic');
+					alert('<t>This topic has been locked.</t>');
+				});
+			};
+		</script>
+	</n.put_in_head.>
+	<a href="javascript: void Nabble.lockTopic([n.id/])" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Lock topic[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="lock_topic" requires="servlet">
+	<n.node_page.>
+		<n.if.both condition1="[n.visitor.can_manage_locked_topics_in.page_node/]" condition2="[n.not.page_node.is_locked_topic/]">
+			<then.edit_page_node.>
+				<n.add_permission permission="[n.reply_permission/]"/>
+			</then.edit_page_node.>
+		</n.if.both>
+	</n.node_page.>
+</macro>
+
+<macro name="unlock_topic_link" requires="node" dot_parameter="text" parameters="title, class">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			Nabble.unlockTopic = function(id) {
+				var call = '/'+'template/NamlServlet.jtp?macro=unlock_topic&node=' + id;
+				$.getScript(call, function() {
+					$('#lock-icon').hide();
+					NabbleDropdown.hide('unlockTopic');
+					NabbleDropdown.show('lockTopic');
+					alert('<t>This topic has been unlocked.</t>');
+				});
+			};
+		</script>
+	</n.put_in_head.>
+	<a href="javascript: void Nabble.unlockTopic([n.id/])" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Unlock topic[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="unlock_topic" requires="servlet">
+	<n.node_page.>
+		<n.if.both condition1="[n.visitor.can_manage_locked_topics_in.page_node/]" condition2="[n.page_node.is_locked_topic/]">
+			<then.edit_page_node.>
+				<n.remove_permission permission="[n.reply_permission/]"/>
+			</then.edit_page_node.>
+		</n.if.both>
+	</n.node_page.>
+</macro>
+
+<macro name="is_locked_topic" requires="node">
+	<n.both>
+		<condition1.node_has_permission.reply_permission />
+		<condition2.not.groups_have_permission groups="[n.registered_user_groups/]" permission="[n.reply_permission/]" />
+	</n.both>
+</macro>
+
+<macro name="smart_post_link" requires="node" dot_parameter="href" parameters="text, title, class">
+	<n.if.is_post>
+		<then>
+			<a href="[n.node_href.href/]" title="[n.title/]" class="[n.hide_null.class/] post-link[n.topic_node.id/]" node-id="[n.id/]"><n.node_text.text/></a>
+			<n.call_later value="[n.topic_node.id/]" param="markUnreadTopics"/>
+		</then>
+		<else.node_link text="[n.text/]"/>
+	</n.if.is_post>
+</macro>
+
+<macro name="mark_unread_topics_js">
+	<n.if.visitor.is_registered>
+		<then>
+			<![CDATA[
+			$('#nabble').append('<a id="not-visited" class="invisible" href="/any-url">x</a> <a id="visited" class="visited-link invisible" href="#">x</a>');
+			var notVisitedColor = $('#not-visited').css('color');
+			var visitedColor = $('#visited').css('color');
+			Nabble.markUnreadTopics = function(topic, lastVisitedId) {
+				$('a.post-link'+topic).each(function() {
+					var nodeId = $(this).attr('node-id');
+					var isVisited = lastVisitedId && lastVisitedId > 0 && parseInt(nodeId) <= lastVisitedId;
+					if (isVisited)
+						$(this).addClass('visited-link');
+					else {
+						var $this = $(this);
+						$this
+							.css('color', notVisitedColor)
+							.click(function() {
+								$this.css('color',visitedColor);
+							});
+					}
+				});
+			};
+			]]>
+			<n.param_loop. param="markUnreadTopics">
+				<n.set_var. name='last_visited_id'>
+					<n.if.visitor.has_visited_node node_id="[n.current_parameter_value/]">
+						<then.visitor.last_visited_node. node_id="[n.current_parameter_value/]">
+							<n.id/>
+						</then.visitor.last_visited_node.>
+						<else>null</else>
+					</n.if.visitor.has_visited_node>
+				</n.set_var.>
+				Nabble.markUnreadTopics(<n.current_parameter_value/>, <n.var name='last_visited_id'/>);
+			</n.param_loop.>
+		</then>
+	</n.if.visitor.is_registered>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/trkg.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,16 @@
+<macro name="trkg" requires="servlet">
+	<n.nabble_html>
+		<do>
+		</do>
+		<output>
+<![CDATA[<!doctype html>]]>
+<html>
+	<body>
+		<script>
+			sessionStorage.trkg = "x";
+		</script>
+	</body>
+</html>
+		</output>
+	</n.nabble_html>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/ui_components.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,404 @@
+==========================================
+	VERTICAL TAB CONTROL
+==========================================
+
+<macro name="vertical_tab_control" dot_parameter="vertical_tabs">
+	<n.put_in_head.>
+		<style type="text/css">
+			table.vertical-control {
+				margin: 1em 0;
+				width:100%;
+				border-collapse:collapse;
+			}
+			table.vertical-control td {
+				padding:0;
+				vertical-align:top;
+			}
+			table.vertical-control td.details {
+				width:90%;
+				padding:.5em 1em;
+			}
+			ul.vertical-control {
+				width:100%;
+				list-style-type:none;
+				padding:0;
+				margin:0;
+			}
+			ul.vertical-control li {
+				padding: .3em;
+				white-space:nowrap;
+			}
+			ul.vertical-control li.selected {
+				border-top-left-radius:5px;
+				border-bottom-left-radius:5px;
+				-moz-border-radius-topleft:5px;
+				-moz-border-radius-bottomleft:5px;
+			}
+		</style>
+	</n.put_in_head.>
+	<table class="vertical-control">
+		<tr>
+			<td>
+				<ul class="vertical-control">
+					<n.vertical_tab.>
+						<n.vertical_tabs/>
+					</n.vertical_tab.>
+				</ul>
+			</td>
+			<td class="details shaded-bg-color">
+				<n.vertical_tabs/>
+			</td>
+		</tr>
+	</table>
+</macro>
+
+<macro name="vertical_tab" dot_parameter="do">
+	<n.do/>
+</macro>
+
+<macro name="is_vertical_tab">
+	<n.is_in_command name="vertical_tab" />
+</macro>
+
+<macro name="add_vertical_tab" dot_parameter="text" parameters="url,selected,details,icon,style">
+	<n.if.is_vertical_tab>
+		<then>
+			<n.if.selected>
+				<then><li class="selected shaded-bg-color" style="[n.style/]"><n.hide_null.icon/> <n.truncate. size="25"><n.text/></n.truncate.></li></then>
+				<else><li style="[n.style/]"><n.hide_null.icon/> <a href="[n.url/]"><n.truncate. size="25"><n.text/></n.truncate.></a></li></else>
+			</n.if.selected>
+		</then>
+		<else>
+			<n.if.selected>
+				<then.details/>
+			</n.if.selected>
+		</else>
+	</n.if.is_vertical_tab>
+</macro>
+
+==========================================
+	HORIZONTAL TAB CONTROL
+==========================================
+
+<macro name="horizontal_tab_control" dot_parameter="horizontal_tabs" parameters="is_live">
+	<n.put_in_head.>
+		<script src="[n.tabs_library_path/]" type="text/javascript"></script>
+		<style type="text/css">
+			div.tab-contents {
+				margin-top:1.5em;
+			}
+		</style>
+	</n.put_in_head.>
+	<script type="text/javascript">
+		NabbleTabs.startTabControl(<n.hide_null.is_live/>);
+		<n.horizontal_tab.><n.horizontal_tabs/></n.horizontal_tab.>
+		NabbleTabs.endTabControl();
+	</script>
+	<n.if.is_null.is_live>
+		<then>
+			<div class="tab-contents">
+				<n.horizontal_tabs/>
+			</div>
+		</then>
+	</n.if.is_null.is_live>
+</macro>
+
+<macro name="horizontal_tab" dot_parameter="do">
+	<n.do/>
+</macro>
+
+<macro name="is_horizontal_tab">
+	<n.is_in_command name="horizontal_tab" />
+</macro>
+
+<macro name="add_horizontal_tab" parameters="url,text,selected,details">
+	<n.if.is_horizontal_tab>
+		<then>
+			NabbleTabs.addTab('<n.url/>', '<n.javascript_string_encode.text/>', <n.selected/>);
+		</then>
+		<else>
+			<n.if.selected>
+				<then.details/>
+			</n.if.selected>
+		</else>
+	</n.if.is_horizontal_tab>
+</macro>
+
+<macro name="add_live_horizontal_tab" parameters="element_id,text,selected,details,onclick">
+	<n.if.is_horizontal_tab>
+		<then>
+			<n.if.is_null.onclick>
+				<then>NabbleTabs.addLiveTab('<n.element_id/>', '<n.javascript_string_encode.text/>', <n.selected/>);</then>
+				<else>NabbleTabs.addLiveTab('<n.element_id/>', '<n.javascript_string_encode.text/>', <n.selected/>, <n.onclick/>);</else>
+			</n.if.is_null.onclick>
+		</then>
+		<else.details/>
+	</n.if.is_horizontal_tab>
+</macro>
+
+==========================================
+	SLIDER
+==========================================
+
+<macro name="slider" dot_parameter="contents" requires="html">
+	<n.put_in_head.>
+		<script src="/util/nabblegallery-1.2.js"></script>
+	</n.put_in_head.>
+	<n.slider_counter.increment/>
+	<div id="slider[n.slider_id/]" style="width:100%">
+		<table id="gallery-view[n.slider_id/]">
+			<tr>
+				<td style="width:20px">
+					<img id="prev[n.slider_id/]" src="/images/left.png" style="cursor:pointer;display:none"/>
+				</td>
+				<td>
+					<div id="gallery[n.slider_id/]" style="width:500px;overflow-x:hidden;position:relative">
+						<table id="images[n.slider_id/]" style="border-collapse:collapse;margin-top:.7em;">
+							<tr style="vertical-align:top">
+								<n.contents/>
+							</tr>
+						</table>
+					</div>
+				</td>
+				<td style="width:20px">
+					<img id="next[n.slider_id/]" src="/images/right.png" style="cursor:pointer"/>
+				</td>
+			</tr>
+		</table>
+	</div>
+	<script type="text/javascript">
+		function update<n.slider_id/>() {
+			fixGalleryWidth(<n.slider_id/>);
+		};
+		galleryReady(<n.slider_id/>);
+		$(window).load(update<n.slider_id/>).resize(update<n.slider_id/>);
+	</script>
+</macro>
+
+<macro name="slider_id">
+	<n.slider_counter.value/>
+</macro>
+
+<macro name="slider_counter" dot_parameter="do">
+	<n.counter name="slider" do="[n.do/]" />
+</macro>
+
+==========================================
+	TOOLTIP
+==========================================
+
+<macro name="tooltip" dot_parameter="contents" parameters="use_title, delay, position">
+	<n.if.not.is_ajax>
+		<then.put_in_head.compress.tooltip_head/>
+	</n.if.not.is_ajax>
+	<n.set_var. name='tag_id'>tooltip<n.random max="99999"/></n.set_var.>
+	<div id="[n.var name='tag_id'/]" class="nabble-tooltip" use_title="[n.null_to_false.use_title/]">
+		<n.hide_null.contents/>
+		<div class="nabble-tooltip-arrow">
+			<div class="d1">&diams;</div>
+			<div class="d2">&diams;</div>
+		</div>
+	</div>
+	<script type="text/javascript">
+		Nabble.startTooltip(Nabble.get('<n.var name='tag_id'/>'), '<n.default to="up" text="[n.position/]"/>', <n.default to="400" text="[n.delay/]"/>);
+	</script>
+</macro>
+
+<macro name="tooltip_head">
+	<n.tooltip_css/>
+	<n.tooltip_js/>
+</macro>
+
+<macro name="tooltip_small_row" dot_parameter="contents">
+	<div class="nabble-tooltip-small-row second-font">
+		<n.contents/>
+	</div>
+</macro>
+
+<macro name="tooltip_css">
+	<style type="text/css">
+		div.nabble-tooltip,
+		div.nabble-tooltip * {
+			color: #EEE;
+			font-weight:bold;
+		}
+		div.nabble-tooltip {
+			background: #000;
+			font-size:90%;
+			line-height:normal;
+			display: none;
+			position: absolute;
+			z-index: 88888;
+			padding: .5em;
+			border: 1px solid #FFF;
+			white-space:normal;
+			-moz-border-radius: 3px;
+			-webkit-border-radius: 3px;
+			border-radius: 3px;
+		}
+		div.nabble-tooltip-small-row,
+		div.nabble-tooltip-small-row * {
+			color:#D0EAF2;
+		}
+		div.nabble-tooltip-small-row {
+			font-size:80%;
+			font-weight:normal;
+			padding-top: .4em;
+		}
+		div.nabble-tooltip-arrow {
+			font: 40px Arial, Sans-serif;
+			line-height:1em;
+			left:15px;
+			position:absolute;
+			bottom:-15px;
+			height:15px;
+			width:30px;
+			overflow:hidden;
+		}
+		div.nabble-tooltip-arrow div {
+			position:absolute;
+		}
+		div.nabble-tooltip-arrow div.d1 {
+			top:-22px;
+			color: #FFF;
+		 }
+		div.nabble-tooltip-arrow div.d2 {
+			top:-25px;
+			color: #000;
+		}
+	</style>
+</macro>
+
+<macro name="tooltip_js">
+	<script type="text/javascript">
+		<![CDATA[
+		Nabble.startTooltip = function(e, position, delay) {
+			if (e.nabbletooltip)
+				return;
+			e.nabbletooltip = true;
+			var $this = $(e);
+			var $arrow = $this.children().last();
+			var $elem = $this.prev();
+			$elem.hover(
+				function() {
+					setTip();
+					setTimer();
+				},
+				function() {
+					stopTimer();
+					$this.hide();
+				}
+			);
+			function setTimer() {
+				$this.showTipTimer = setTimeout(function() {
+					$('div.nabble-tooltip').hide();
+					stopTimer();
+					$this.fadeTo('fast', .8);
+				}, delay);
+			};
+			function stopTimer() {
+				clearInterval($this.showTipTimer);
+			};
+			function setTip(){
+				if ($this.parent().get() != document.body)
+					$(document.body).append($this);
+				var useTitle = $this.attr('use_title') == 'true';
+				if (useTitle) {
+					var title = $elem.attr('title');
+					if (title != '') {
+						$arrow.remove();
+						$this.html(title);
+						$elem.attr('title','');
+						$this.append($arrow);
+					}
+				}
+				var win = $(window).width();
+				if (position == 'up') {
+					var w = $this.outerWidth();
+					if (w > 250) {
+						w = 250;
+						$this.width(w);
+					}
+					var xMid = $elem.offset().left + $elem.outerWidth()/2;
+					var xTip = xMid - w/2;
+					if (xTip+w > win-5)
+						xTip = win-w-5;
+					if (xTip < 0)
+						xTip = 0;
+					var xArrow = xMid-xTip-11;
+					var yTip = $elem.offset().top-$this.outerHeight()-12;
+					$arrow.css('left', xArrow);
+					$this.css({'top' : yTip, 'left' : xTip});
+				} else if (position == 'right') {
+					var h = $this.outerHeight();
+					var yMid = $elem.offset().top + $elem.outerHeight()/2;
+					var yTip = yMid - h/2;
+					var xTip = $elem.offset().left + $elem.outerWidth() + 10;
+					$arrow.width(8).height(24).css({bottom:0,left:-8});
+					var yArrow = (h - 24)/2;
+					$arrow.css({top:yArrow});
+					var $d1 = $arrow.children().first();
+					var $d2 = $arrow.children().last();
+					$d1.css({top:-11});
+					$d2.css({top:-11,left:1});
+					$this.css({'top' : yTip, 'left' : xTip});
+				}
+			};
+		};
+		]]>
+	</script>
+</macro>
+
+==========================================
+	CUSTOM DROPDOWN
+==========================================
+
+<macro name="custom_dropdown" parameters="clickable_id, panel_id">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			<![CDATA[
+			var ddPanels = [];
+			Nabble.asDropdown = function(clickableId, panelId) {
+				ddPanels.push(panelId);
+				$(document).ready(function() {
+					var $clickable = $('#'+clickableId);
+					var $panel = $('#'+panelId);
+					$panel.css({
+						position:'absolute',
+						zIndex: 5555,
+						display:'none'
+					});
+					$clickable.addClass('custom-dropdown');
+					$clickable.click(function(e) {
+						e.stopPropagation();
+						var isVisible = $panel.css('display') != 'none';
+						closeDropdowns();
+						if (!isVisible) {
+							var left = $(this).position().left - 5;
+							var w = $panel.outerWidth();
+							var win = $(window).width() - 10;
+							if (left+w > win)
+								left = win - w -10;
+							left = left < 0? 0 : left;
+							$panel.css('left', left);
+							$panel.show();
+						}
+					});
+					$(document).click(function(e) {
+						if ($(e.target).hasClass('custom-dropdown'))
+							return;
+						closeDropdowns();
+					});
+					function closeDropdowns() {
+						for (var i=0;i<ddPanels.length;i++)
+							$('#'+ddPanels[i]).hide();
+					};
+				});
+			};
+			]]>
+		</script>
+	</n.put_in_head.>
+	<script type="text/javascript">
+		Nabble.asDropdown('<n.clickable_id/>', '<n.panel_id/>');
+	</script>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/unauthorized.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,109 @@
+<macro name="unauthorized" requires="servlet">
+	<n.node_page.>
+		<n.if.is_submitted_form>
+			<then>
+				<n.users_in_group. group="[n.administrators_group/]">
+					<n.loop.>
+						<n.current_user.send_request_email
+							explanation="[n.message_field.value/]"
+						/>
+					</n.loop.>
+				</n.users_in_group.>
+			</then>
+		</n.if.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Unauthorized</t></n.title.>
+			</head>
+			<body>
+				<div style="font-size:140%;margin:.5em 0 1em">
+					<t>Authorized Users Only</t>
+				</div>
+				<t>Only authorized users can proceed in this area.</t>
+				<t>You can use the form below to send a request to the administrators.</t>
+				<div class="second-font big-title" style="margin:1.5em 0 .5em">
+					<t>Access Request</t>
+				</div>
+				<n.if.not.is_submitted_form>
+					<then>
+						<div class="weak-color" style="margin:.3em 0">
+							<t>Explain to the administrator(s) why you want to access this restricted area.</t>
+						</div>
+						<n.form.>
+							<n.message_field.textarea wrap="SOFT" style="width:35em;height:5em;" />
+							<n.message_field.focus/>
+							<br/>
+							<input type="submit" id="send-request" value="[t]Send Request[/t]"/>
+							<t>or</t> <a href="[n.page_node.url/]"><t>Cancel</t></a>
+						</n.form.>
+					</then>
+					<else>
+						<img src="/images/success.png" class="image16"/>
+						<t>Your request has been successfully sent.</t>
+					</else>
+				</n.if.not.is_submitted_form>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="send_request_email" parameters="explanation" requires="user,node_page" unindent="true">
+	<n.new_email.>
+		<n.send>
+			<to><n.user_email/></to>
+			<subject><t>User requested authorization to join <t.location.page_node.subject/></t></subject>
+			<text_part>
+				<t>Dear <t.name.name/>,</t>
+
+				<t><t.name.visitor.name/> requested authorization to join the <t.location.page_node.subject/>:</t>
+				<n.page_node.url/>
+
+				* <t>Explanation from this user:</t>
+				<n.explanation/>
+
+				* <t>User email:</t>
+				<n.visitor.user_email/>
+
+				<t>To accept this request, you should add this user to at least one group that has access to this area:</t>
+				<n.base_url/><n.visitor.change_user_groups_path/>
+
+				<t>Or you can ignore this email if it is better to keep this user away from that area.</t>
+				________________________________________
+				<t>Free Embeddable <t.app.page_node.view_name/></t> powered by Nabble
+				<n.nabble_homepage/>
+			</text_part>
+			<html_part>
+				<t>Dear <t.name.name/>,</t><br/>
+				<br/>
+				<t><t.name.visitor.name/> requested authorization to join the <t.location.page_node.subject/>:</t><br/>
+				<a href="[n.page_node.url/]"><n.page_node.url/></a><br/>
+				<br/>
+				<b><t>Explanation from this user:</t></b><br/>
+				<n.explanation/><br/>
+				<br/>
+				<b><t>User email:</t></b><br/>
+				<n.visitor.user_email/><br/>
+				<br/>
+				<t>To accept this request, you should add this user to at least one group that has access to this area:</t>
+				<div style="background-color:#FFFADB;border:#EDDD79 solid 1px;margin:1.2em 0;padding:.5em">
+					<n.base_url/><n.visitor.change_user_groups_path/>
+				</div>
+				<t>Or you can ignore this email if it is better to keep this user away from that area.</t><br/>
+				________________________________________<br/>
+				<t>Free Embeddable <t.app.page_node.view_name/></t> powered by Nabble<br/>
+				<n.nabble_homepage/><br/><br/>
+			</html_part>
+		</n.send>
+	</n.new_email.>
+</macro>
+
+<macro name="unauthorized_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=unauthorized&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="unauthorized_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.unauthorized_path/]" title="[n.title/]" class="[n.class/]"><n.text/></a>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/unban_user.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,68 @@
+<macro name="unban_user" requires="servlet">
+	<n.user_page.>
+		<n.if.not.visitor.can_manage_banned_users>
+			<then>
+				<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+			</then>
+		</n.if.not.visitor.can_manage_banned_users>
+		<n.if.is_submitted_form>
+			<then>
+				<n.page_user.unban/>
+			</then>
+		</n.if.is_submitted_form>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.><t>Unban User</t></n.title.>
+			</head>
+			<body>
+				<n.edit_header first_text="[t]Unban User[/t]" second_text="[n.page_user.name/]" />
+
+				<n.form.>
+					<table style="margin-bottom: .5em">
+						<tr valign="top">
+							<td><n.page_user.avatar size="big"/></td>
+							<td><n.unban_user_control/></td>
+						</tr>
+					</table>
+
+					<n.if.not.is_submitted_form>
+						<then>
+							<div style="margin-top:1.4em">
+								<input type="submit" value="[t]Unban User[/t]"/>
+								<t>or</t> <a href="[n.page_user.url/]"><t>Cancel</t></a>
+							</div>
+						</then>
+					</n.if.not.is_submitted_form>
+				</n.form.>
+
+				<p><t>You can also <n.manage_banned_users_link.>manage banned users</n.manage_banned_users_link.> in <t.location.root_node.subject/>.</t></p>
+			</body>
+		</n.html>
+	</n.user_page.>
+</macro>
+
+<macro name="unban_user_control">
+	<n.if.page_user.is_authenticated>
+		<then>
+			<table style="margin-bottom:1em">
+				<tr valign="top">
+					<n.if.not.is_submitted_form>
+						<then>
+							<td>
+								<b><t>Do you really want to unban this user?</t></b>
+								<div class="weak-color" style="margin-top:.3em">
+									<t>If you unban this user, he/she will be able to post messages in <t.location.root_node.subject/> again.</t>
+								</div>
+							</td>
+						</then>
+						<else>
+							<td><img src="/images/success.png" class="image16"/></td>
+							<td><t><t.author><b><n.page_user.name/></b></t.author> has been successfully unbanned.</t></td>
+						</else>
+					</n.if.not.is_submitted_form>
+				</tr>
+			</table>
+		</then>
+	</n.if.page_user.is_authenticated>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/unsubscribe.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,170 @@
+<macro name="unsubscribe" requires="servlet">
+	<n.node_page.>
+		<n.if.is_submitted_form>
+			<then>
+				<n.if.equal value1="remove-subscription" value2="[n.action_parameter/]">
+					<then>
+						<n.if.page_node.visitor_is_subscribed>
+							<then.page_node.visitor_subscription.remove/>
+						</n.if.page_node.visitor_is_subscribed>
+						<n.redirect_to.subscription_removed_url/>
+					</then>
+				</n.if.equal>
+			</then>
+		</n.if.is_submitted_form>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.title.><t>Unsubscribe</t></n.title.>
+				<n.main_title_css/>
+			</head>
+			<body>
+				<n.visitor.profile_header/>
+	
+				<div class="shaded-bg-color rounded second-font main-title">
+					<t>Remove Subscription</t>
+				</div>
+				<div style="font-weight:bold;margin: 2em 0 1em">
+					<t>Do you really want to unsubscribe from <t.location><a href="[n.page_node.url/]"><n.page_node.subject/></a></t.location>?</t>
+				</div>
+	
+				<n.unsubscription_form/>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="unsubscription_form" requires="node_page">
+	<n.form.>
+		<input type="hidden" name="node" value="[n.page_node.id/]"/>
+		<input type="hidden" name="action" value="remove-subscription"/>
+		<input type="submit" class="toolbar action-button" value="[t]Yes, unsubscribe now[/t]"/>
+		<t>or</t> <a href="[n.page_node.url/]"><t>Cancel</t></a>
+	</n.form.>
+</macro>
+
+<macro name="subscription_removed" requires="servlet">
+	<n.node_page.>
+		<n.subscription_msg
+			header="[t]Subscription Removed[/t]"
+			message="[t]Your subscription to [t.location.page_node.subject/] has been successfully removed.[/t]"
+		/>
+	</n.node_page.>
+</macro>
+
+<macro name="subscription_removed_url">
+	<n.remove_spaces.>
+		<n.page_node.base_url/>
+		/template/NamlServlet.jtp?macro=subscription_removed&node=<n.page_node.id/>
+	</n.remove_spaces.>
+</macro>
+
+<macro name="unsubscribe_by_code_url" requires="subscription">
+	<n.remove_spaces.>
+		<n.base_url/>
+		<n.encode_url.>
+			/template/NamlServlet.jtp?macro=unsubscribe_by_code&node=<n.node.id/>&code=<n.subscription_code/>
+		</n.encode_url.>
+	</n.remove_spaces.>
+</macro>
+
+<!--
+	Unsubscribe by code has a confirmation step to prevent crawlers from unsubscribing users without intention.
+-->
+<macro name="unsubscribe_by_code" requires="servlet">
+	<n.node_page.>
+		<n.catch_exception. id="remove-subscription">
+			<n.page_node.get_subscription_by_code. code="[n.code_parameter/]">
+				<n.if.not.is_subscribed>
+					<then.throw_template_exception name="not_subscribed"/>
+				</n.if.not.is_subscribed>
+				<n.if.is_submitted_form>
+					<then>
+						<n.remove/>
+						<n.send_unsubscription_reminder/>
+					</then>
+				</n.if.is_submitted_form>
+			</n.page_node.get_subscription_by_code.>
+		</n.catch_exception.>
+		<n.html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<n.title.><t>Remove Subscription</t></n.title.>
+			</head>
+			<body>
+				<n.edit_header first_text="[n.page_node.subject/]" second_text="[t]Remove Subscription[/t]" />
+
+				<n.if.has_exception for="remove-subscription">
+					<then.handle_unsubscription_by_code_errors/>
+					<else>
+						<n.if.is_submitted_form>
+							<then>
+								<h2><t>Subscription Removed</t></h2>
+								<t>Your subscription to <t.location.page_node.subject/> has been successfully removed.</t>
+							</then>
+							<else>
+								<h2><t>Do you really want to unsubscribe from <t.location><a href="[n.page_node.url/]"><n.page_node.subject/></a></t.location>?</t></h2>
+								<n.form.>
+									<input type="hidden" name="code" value="[n.code_parameter/]"/>
+									<input type="hidden" name="action" value="remove-subscription"/>
+									<input type="submit" class="toolbar action-button" value="[t]Yes, unsubscribe now[/t]"/>
+									<t>or</t> <a href="[n.page_node.url/]"><t>Cancel</t></a>
+								</n.form.>
+							</else>
+						</n.if.is_submitted_form>
+					</else>
+				</n.if.has_exception>
+			</body>
+		</n.html>
+	</n.node_page.>
+</macro>
+
+<macro name="handle_unsubscription_by_code_errors">
+	<n.handle_exception. for="remove-subscription">
+		<n.exception. name="invalid_link">
+			<h2><t>Invalid Code</t></h2>
+			<t>The code in the URL is not valid.</t>
+			<t>Please contact Nabble Support if you need help.</t>
+		</n.exception.>
+		<n.exception. name="not_subscribed">
+			<h2><t>You're not a subscriber</t></h2>
+			<t>You are not subscribed to <t.location.page_node.subject/>.</t>
+		</n.exception.>
+	</n.handle_exception.>
+</macro>
+
+<macro name="send_unsubscription_reminder" requires="subscription,node_page,servlet">
+	<n.set_local_subscription.this_subscription />
+	<n.new_email.>
+		<n.send>
+			<to><n.user.user_email/></to>
+			<subject><t>You have been unsubscribed from <t.location.page_node.subject/></t></subject>
+			<text_part>
+				<t>Dear user,</t>
+
+				<t>Your subscription to <t.location.page_node.subject/> has been removed.
+				If this was a mistake, you can re-subscribe by following the link below:</t>
+				<n.local_subscription.subscribe_by_code_url subscription_to="[n.to/]"/>
+
+				<t>Sincerely,</t>
+				<t>The Nabble team</t>
+				________________________________________
+				<t>Free Embeddable <t.app.page_node.view_name/></t> powered by Nabble
+				<n.nabble_homepage/>
+			</text_part>
+			<html_part>
+				<t>Dear user,</t><br/>
+				<br/>
+				<t>Your subscription to <t.location.page_node.subject/> has been removed.
+				If this was a mistake, you can re-subscribe by following the link below:</t><br/>
+				<n.local_subscription.subscribe_by_code_url subscription_to="[n.to/]"/><br/>
+				<br/>
+				<t>Sincerely,</t><br/>
+				<t>The Nabble team</t><br/>
+				________________________________________<br/>
+				<t>Free Embeddable <t.app.page_node.view_name/></t> powered by Nabble<br/>
+				<n.nabble_homepage/><br/><br/>
+			</html_part>
+		</n.send>
+	</n.new_email.>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/url_mapper.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,126 @@
+
+<macro name="url mapper" requires="url_mapper">
+	<n.map_root/>
+	<n.map_app/>
+	<n.map_topic/>
+	<n.map_atom_feeds/>
+</macro>
+
+<macro name="set_parameter_if_found" parameters="name,group" requires="url_mapper,regex">
+	<n.if.did_find group="[n.group/]">
+		<then.set_parameter_to_found name="[n.name/]" group="[n.group/]" />
+	</n.if.did_find>
+</macro>
+
+<macro name="set_parameter_char_if_found" parameters="name,group,char,value" requires="url_mapper,regex">
+	<n.if.did_find group="[n.group/]">
+		<then>
+			<n.set_var. name='found'><n.found group="[n.group/]"/></n.set_var.>
+			<n.if.equal value1="[n.char/]" value2="[n.var name='found'/]">
+				<then.set_parameter. name="[n.name/]">
+					<n.value/>
+				</then.set_parameter.>
+			</n.if.equal>
+		</then>
+	</n.if.did_find>
+</macro>
+
+<macro name="set_parameter_to_found" parameters="name,group" requires="url_mapper,regex">
+	<n.set_parameter. name="[n.name/]">
+		<n.found group="[n.group/]"/>
+	</n.set_parameter.>
+</macro>
+
+
+individual mappings
+
+<macro name="map_root" requires="url_mapper">
+	<n.regex text="[n.path/]">
+		<pattern>
+			^/$
+		</pattern>
+		<do>
+			<n.if.find>
+				<then>
+					<n.set_parameter. name="node">
+						<n.root_node.id/>
+					</n.set_parameter.>
+					<n.set_parameter name="macro" value="view_app" />
+					<n.exit/>
+				</then>
+			</n.if.find>
+		</do>
+	</n.regex>
+</macro>
+
+<macro name="map_app" requires="url_mapper">
+	<n.regex text="[n.path/]">
+		<pattern>
+			-f(\d+)(?:p(\d+))?(?:a(\d+))?(?:i(\d+))?(?:d(\d+))?(?:ef\d+)?(?:\.([a-z]+))?\.html$
+		</pattern>
+		<do>
+			<n.if.find>
+				<then>
+					<n.set_parameter_to_found name="node" group="1" />
+					<n.set_parameter_if_found name="priority" group="2" />
+					<n.set_parameter_if_found name="assignee" group="3" />
+					<n.set_parameter_if_found name="index_record" group="4" />
+					<n.set_parameter_if_found name="date" group="5" />
+					<n.if.did_find group="6">
+						<then>
+							<n.set_parameter. name="macro">
+								view_<n.found group="6"/>
+							</n.set_parameter.>
+						</then>
+						<else>
+							<n.set_parameter name="macro" value="view_app" />
+						</else>
+					</n.if.did_find>
+					<n.exit/>
+				</then>
+			</n.if.find>
+		</do>
+	</n.regex>
+</macro>
+
+<macro name="map_topic" requires="url_mapper">
+	<n.regex text="[n.path/]">
+		<pattern>
+			-t(d|c|t)(\d+)(?:i(\d+))?(?:ef\d+)?\.html$
+		</pattern>
+		<do>
+			<n.if.find>
+				<then>
+					<n.set_parameter_char_if_found name="view" group="1" char="d" value="classic" />
+					<n.set_parameter_char_if_found name="view" group="1" char="c" value="list" />
+					<n.set_parameter_char_if_found name="view" group="1" char="t" value="threaded" />
+					<n.set_parameter_to_found name="node" group="2" />
+					<n.set_parameter_if_found name="index_record" group="3" />
+					<n.set_parameter name="macro" value="topic" />
+					<n.exit/>
+				</then>
+			</n.if.find>
+		</do>
+	</n.regex>
+</macro>
+
+<macro name="map_atom_feeds" requires="url_mapper">
+	<n.regex text="[n.path/]">
+		<pattern>
+			-f(t)?(\d+)(n(\d+))?.xml$
+		</pattern>
+		<do>
+			<n.if.find>
+				<then>
+					<n.if.did_find group="1">
+						<then.set_parameter name="macro" value="atom_topics_by_date" />
+						<else.set_parameter name="macro" value="atom_posts_by_date" />
+					</n.if.did_find>
+					<n.set_parameter_to_found name="node" group="2" />
+					<n.set_parameter_if_found name="length" group="4" />
+					<n.exit/>
+				</then>
+			</n.if.find>
+		</do>
+	</n.regex>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/use_google_analytics.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,107 @@
+<macro name="use_google_analytics" requires="servlet">
+	<n.if.not.visitor.is_site_admin>
+		<then>
+			<n.login.><t>Only authorized users can proceed in this area.</t></n.login.>
+		</then>
+	</n.if.not.visitor.is_site_admin>
+
+	<n.if.is_submitted_form>
+		<then>
+			<n.catch_exception. id="save-block">
+				<n.if.is_valid_analytics_id.google_analytics_code_field.value>
+					<then>
+						<n.save_google_analytics_tweak/>
+						<n.redirect_to.root_node.url/>
+					</then>
+					<else.throw_template_exception name="invalid_analytics_id"/>
+				</n.if.is_valid_analytics_id.google_analytics_code_field.value>
+			</n.catch_exception.>
+		</then>
+		<else>
+			<n.google_analytics_code_field.set_value value="[n.naml_configuration.get_value name='googleAnalyticsId' default=''/]"/>
+		</else>
+	</n.if.is_submitted_form>
+
+	<n.html>
+		<head>
+			<meta name="robots" content="noindex,nofollow"/>
+			<n.title.><t>Use Google Analytics</t></n.title.>
+			<n.google_analytics_code_field.focus/>
+		</head>
+		<body>
+			<n.edit_header first_text="[n.root_node.subject/]" second_text="[t]Use Google Analytics[/t]" />
+
+			<n.if.both condition1="[n.is_submitted_form/]" condition2="[n.has_exception for='save-block'/]">
+				<then>
+					<n.format_error.handle_exception. for="save-block">
+						<n.exception. name="invalid_analytics_id">
+							<t>Enter a valid analytics account ID.</t>
+						</n.exception.>
+					</n.format_error.handle_exception.>
+				</then>
+			</n.if.both>
+
+			<div style="margin:1.5em 0">
+				<t>Here you can use Google Analytics to measure the success of your app.</t>
+				<t>Enter below your analytics account ID and you will be able to track visits, visitors and other important statistics about your web traffic.</t>
+			</div>
+
+			<n.form.>
+				<t>Analytics Account ID:</t>
+				<n.google_analytics_code_field.input type="text" size="20" maxlength="20"/>
+				<t>(e.g., UA-12345-0)</t>
+				<div style="margin-top:1.5em">
+					<input type="submit" class="toolbar action-button" value="[t]Save Changes[/t]" />
+					<t>or</t> <a href="[n.root_node.url/]"><t>Cancel</t></a>
+				</div>
+			</n.form.>
+		</body>
+	</n.html>
+</macro>
+
+<macro name="is_valid_analytics_id" dot_parameter="code">
+	<n.either>
+		<condition1.is_empty.code/>
+		<condition2.regex_matches text="[n.code/]" pattern="UA-\d+-\d+"/>
+	</n.either>
+</macro>
+
+<macro name="save_google_analytics_tweak">
+	<n.naml_configuration.>
+		<n.set>
+			<name>googleAnalyticsId</name>
+			<value><n.google_analytics_code_field.value/></value>
+			<default></default>
+			<naml>
+				<![CDATA[
+				<override_macro name="js_google_analytics">
+					<n.overridden/>
+					<script>
+						ga('create', ']]><n.google_analytics_code_field.value/><![CDATA[', 'auto');
+						ga('send', 'pageview');
+					</script>
+				</override_macro>
+				]]>
+			</naml>
+		</n.set>
+		<n.apply/>
+	</n.naml_configuration.>
+</macro>
+
+<macro name="google_analytics_code_field" dot_parameter="do">
+	<n.field. name="google_analytics_code"><n.do/></n.field.>
+</macro>
+
+<macro name="use_google_analytics_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=use_google_analytics
+	</n.encode_url.>
+</macro>
+
+<macro name="use_google_analytics_link" dot_parameter="text" parameters="title, class">
+	<a href="[n.use_google_analytics_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Use Google Analytics[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="google_analytics_configurations">
+	googleAnalyticsId
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/user.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,98 @@
+<macro name="user_header">
+	<span style="white-space:nowrap;" id="nabble-user-header"></span>
+	<script type="text/javascript">Nabble.userHeader();</script>
+</macro>
+
+<macro name="is_active" requires="user">
+	<n.both>
+		<condition1.not.is_banned/>
+		<condition2.not.is_deactivated/>
+	</n.both>
+</macro>
+
+<!-- user_tag_id fixes the tilde char (~) in anonymous names (e.g., "a123~Anon") -->
+<macro name="user_tag_id" requires="user">
+	<n.regex_replace_all. pattern='[^\w]' replacement='_'><n.id/></n.regex_replace_all.>
+</macro>
+
+<macro name="set_visitor_online_js">
+	<n.param_loop. param="visitorOnline">
+		<n.set_visitor_online/>
+	</n.param_loop.>
+</macro>
+
+<macro name="avatar_online_js">
+	<n.param_loop. param="avatarOnline">
+		<n.if.author_is_online search_id="[n.current_parameter_value/]">
+			<then>
+				Nabble.online('<n.current_parameter_value/>');
+			</then>
+		</n.if.author_is_online>
+	</n.param_loop.>
+</macro>
+
+<macro name="avatar" requires="user" parameters="size,group,border_class">
+	<n.set_var. name="image_url">
+		<n.if.is_banned>
+			<then.default_avatar_url size="[n.size/]"/>
+			<else.avatar_url size="[n.size/]"/>
+		</n.if.is_banned>
+	</n.set_var.>
+	<n.remove_spaces_between_tags.>
+		<n.if.is_empty.group>
+			<then>
+				<a href="[n.url/]" rel="nofollow" title="[t]View profile of [t.author.name/][/t]" class="nowrap no-decoration">
+					<img class="avatar [n.avatar_border.border_class/]" src="[n.var name='image_url'/]" height="[n.avatar_pixels.size/]" width="[n.avatar_pixels.size/]" alt="[n.name/]" title="[n.name/]"/>
+					<img src="/images/online.png" class="online[n.search_id/] online invisible" title="[t]User is online[/t]" alt="online"/>
+				</a>
+			</then>
+			<else>
+				<n.put_in_head.>
+					<script type="text/javascript">
+						var avatarBorder = "<n.avatar_border.border_class/>";
+						var userIsOnline = "<t>User is online</t>";
+<![CDATA[
+						Nabble.createAvatar = function(showAvatar, url, id, size) {
+							var dim = size == 'small'? 24 : 100;
+							document.write("<img ");
+							if (showAvatar || showAvatar == null)
+								document.write("src='"+url+"' ");
+							else
+								document.write("src='/images/nop.gif' ");
+							document.write("xsrc='"+url+"' ");
+							document.write("width='"+dim+"' height='"+dim+"' class='"+avatarBorder+" avatar'/>");
+							if (id)
+								document.write('<img src="/images/online.png" class="online'+id+' online invisible" title="'+userIsOnline+'" alt="online"/>');
+						};
+]]>
+					</script>
+				</n.put_in_head.>
+				<span class="avatar[n.group/]">
+					<a href="[n.url/]" rel="nofollow" title="[t]View profile of [t.author.name/][/t]" class="nowrap no-decoration">
+						<script type="text/javascript">
+							Nabble.createAvatar(window.hasAvatar<n.group/>, '<n.var name='image_url'/>', '<n.search_id/>', '<n.size/>');
+						</script>
+					</a>
+				</span>
+			</else>
+		</n.if.is_empty.group>
+	</n.remove_spaces_between_tags.>
+
+	<n.call_later param="visitorOnline"/>
+	<n.call_later value="[n.search_id/]" param="avatarOnline"/>
+</macro>
+
+<macro name="avatar_border" dot_parameter="class">
+	<n.default. to="light-border-color"><n.class/></n.default.>
+</macro>
+
+<macro name="avatar_pixels" dot_parameter="size">
+	<n.if.equal value1="[n.size/]" value2="big">
+		<then>100</then>
+		<else>24</else>
+	</n.if.equal>
+</macro>
+
+<macro name="post_count_value" requires="user">
+	<n.page_user.node_count filter="[n.post_filter/]"/>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/user_nodes.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,299 @@
+<macro name="user_nodes" requires="servlet">
+	<n.user_page.>
+		<n.html>
+			<head>
+				<n.user_nodes_meta/>
+				<n.title.>
+					<t>Profile of <t.author.page_user.name/></t>
+					<n.hide_if_equals. value1="[n.user_nodes_page_number/]" value2="1">
+						| <t>Page <t.number.user_nodes_page_number/></t>
+					</n.hide_if_equals.>
+				</n.title.>
+				<style type="text/css">
+					table.nodes {
+						width:100%;
+						border-collapse:collapse;
+					}
+					table.nodes td {
+						padding:.3em;
+					}
+					table.nodes td.header {
+						padding: .2em;
+						font-weight:bold;
+					}
+					div.table-title {
+						font-weight:bold;
+						font-size:120%;
+						margin: 1em .1em 1em;
+					}
+				</style>
+			</head>
+			<body>
+				<n.page_user.profile_header/>
+
+				<n.user_nodes_search_box/>
+
+				<div class="second-font table-title">
+					<t>Posts in <t.location.root_node.subject/></t>
+				</div>
+
+				<n.user_nodes_table.>
+					<n.date_column width="7em"/>
+					<n.subject_column/>
+					<n.count_column/>
+					<n.location_column/>
+				</n.user_nodes_table.>
+			</body>
+		</n.html>
+	</n.user_page.>
+</macro>
+
+<macro name="user_nodes_search_box" requires="user_page">
+	<div class="search-box" style="float:right;white-space:nowrap;margin-top:1em">
+		<n.root_node.page_search_box author="[n.page_user.id/]"/>
+	</div>
+</macro>
+
+<macro name="user_nodes_table" dot_parameter="columns">
+	<n.page_user.nodes_list.
+		start="[n.user_nodes_index_record/]"
+		length="[n.user_nodes_length/]"
+		filter="[n.user_nodes_filter_condition/]"
+	>
+		<div style="position:relative;margin:-.6em 0 .2em">
+			<n.user_nodes_pagination/>
+			<n.user_nodes_filter_control/>
+		</div>
+		<table class="nodes">
+			<n.table_header.>
+				<tr class="header-row shaded-bg-color">
+					<n.columns/>
+				</tr>
+			</n.table_header.>
+			<n.loop.>
+				<tr class="[n.alternate var='row-class' first_value='main-row' second_value='light-bg-color main-row'/]">
+					<n.columns/>
+				</tr>
+			</n.loop.>
+		</table>
+		<n.check_omitted_nodes/>
+		<n.user_nodes_pagination/>
+	</n.page_user.nodes_list.>
+</macro>
+
+<macro name="user_nodes_index_record">
+	<n.get_parameter name="i"/>
+</macro>
+
+<macro name="user_nodes_filter">
+	<n.get_parameter name="filter"/>
+</macro>
+
+<macro name="user_nodes_length">
+	20
+</macro>
+
+<macro name="user_nodes_filter_control" requires="user_page">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			$(document).ready(function() {
+				function applyFilter() {
+					window.location = $('#filter').val();
+				};
+				$('#filter').change(applyFilter);
+			});
+		</script>
+	</n.put_in_head.>
+	<div class="float-left nowrap" style="margin:.3em .2em">
+		<b><t>Show</t></b>
+		<n.set_var. name='url'>
+			<n.page_user.url filter="[n.user_nodes_filter/]"/>
+		</n.set_var.>
+		<select id="filter">
+			<n.select_option value="[n.page_user.url/]" text="[t]All[/t]"/>
+			<n.select_option value="[n.page_user.url filter='apps_only'/]" selectedValue="[n.var name='url'/]" text="[t]Apps[/t]"/>
+			<n.select_option value="[n.page_user.url filter='topics_only'/]" selectedValue="[n.var name='url'/]" text="[t]Topics[/t]"/>
+		</select>
+		&nbsp;
+		<span class="weak-color" style="font-style: italic">
+			<t>Total</t>:
+			<n.one_or_many.user_nodes_count>
+				<one_text><t>item</t></one_text>
+				<many_text><t>items</t></many_text>
+			</n.one_or_many.user_nodes_count>
+		</span>
+	</div>
+</macro>
+
+<macro name="user_nodes_filter_condition" requires="user_page">
+	<n.if.equal value1="[n.user_nodes_filter/]" value2="apps_only">
+		<then.page_user.app_filter/>
+		<else.if.equal value1="[n.user_nodes_filter/]" value2="topics_only">
+			<then.page_user.topic_filter/>
+			<else.null/>
+		</else.if.equal>
+	</n.if.equal>
+</macro>
+
+<macro name="user_nodes_pagination" requires="user_page">
+	<n.paging.
+		total_rows="[n.user_nodes_count/]"
+		current_row="[n.user_nodes_index_record/]"
+		rows_per_page="[n.user_nodes_length/]"
+	>
+		<n.generic_paging>
+			<margin>.55em .2em</margin>
+			<url><n.page_user.path index_record="[n.page_row/]" filter="[n.user_nodes_filter/]"/></url>
+		</n.generic_paging>
+	</n.paging.>
+</macro>
+
+<macro name="user_nodes_page_number" requires="servlet">
+	<n.paging.
+		total_rows="0"
+		current_row="[n.user_nodes_index_record/]"
+		rows_per_page="[n.user_nodes_length/]"
+	>
+		<n.current_page_number/>
+	</n.paging.>
+</macro>
+
+<macro name="user_nodes_count" requires="user_page">
+	<n.cache. var="user_nodes_count" >
+		<n.page_user.node_count filter="[n.user_nodes_filter_condition/]"/>
+	</n.cache.>
+</macro>
+
+<macro name="path" parameters="index_record,filter" requires="user">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=user_nodes&user=<n.id/>
+		<n.add_to_path name="i" value="[n.index_record/]" default_value="0"/>
+		<n.add_to_path name="filter" value="[n.filter/]"/>
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="url" parameters="index_record,filter" requires="user">
+	<n.base_url /><n.path index_record="[n.index_record/]" filter="[n.filter/]" />
+</macro>
+
+<macro name="date_column" parameters="title,width">
+	<n.table_column>
+		<head>
+			<td class="header" style="[n.width_style.width/]">
+				<n.default. to="[t]Date[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td class="weak-color">
+				<n.current_node.when_created.short_format/>
+			</td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="subject_column" parameters="title,width">
+	<n.table_column>
+		<head>
+			<td class="header" colspan="2" style="[n.width_style.width/]">
+				<n.default. to="[t]Subject[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td style="width:25px;padding:.2em">
+				<n.if.current_node.is_app>
+					<then><img src="/images/forum_sm.png" class="image16"/></then>
+					<else><img src="/images/thread_sm.png" class="image16"/></else>
+				</n.if.current_node.is_app>
+			</td>
+			<td>
+				<n.current_node.node_link/>
+				<n.if.current_node.is_private>
+					<then><span class="weak-color"><t>(private)</t></span></then>
+				</n.if.current_node.is_private>
+				<n.if.current_node.is_pending>
+					<then><img src="/images/icon_pending.png" class="image16" title="[t]This post has NOT been accepted by the mailing list yet.[/t]"/></then>
+				</n.if.current_node.is_pending>
+			</td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="count_column" parameters="title,width">
+	<n.table_column>
+		<head>
+			<td class="header" style="[n.width_style.width/]">
+				<n.default. to="[t]Count[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td class="weak-color nowrap">
+				<n.if.current_node.is_app>
+					<then>
+						<n.one_or_many.current_node.topic_count>
+							<one_text><t>topic</t></one_text>
+							<many_text><t>topics</t></many_text>
+						</n.one_or_many.current_node.topic_count>
+					</then>
+					<else>
+						<n.one_or_many.current_node.replies>
+							<one_text><t>reply</t></one_text>
+							<many_text><t>replies</t></many_text>
+						</n.one_or_many.current_node.replies>
+					</else>
+				</n.if.current_node.is_app>
+			</td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="location_column" parameters="title,width">
+	<n.table_column>
+		<head>
+			<td class="header" style="[n.width_style.width/]">
+				<n.default. to="[t]Location[/t]"><n.title/></n.default.>
+			</td>
+		</head>
+		<body>
+			<td class="weak-color">
+				<n.if.not.current_node.is_app>
+					<then><n.current_node.app_or_root.node_link/></then>
+				</n.if.not.current_node.is_app>
+			</td>
+		</body>
+	</n.table_column>
+</macro>
+
+<macro name="check_omitted_nodes" requires="node_list, user_page">
+	<n.comment.>
+		Here we check if this page contains the correct number of rows. If this is not the case,
+		it is because the visitor doesn't have permission to view them and we must display a message.
+		The check is simple: we get the number of rows this page is meant to show (current_page_rows
+		command in pagination namespace) and subtract the number of rows displayed (current_index
+		of the node_list loop). If the result if greater than zero, then some rows were not displayed and
+		we show the message.
+	</n.comment.>
+	<n.paging.
+		total_rows="[n.user_nodes_count/]"
+		current_row="[n.user_nodes_index_record/]"
+		rows_per_page="[n.user_nodes_length/]"
+	>
+		<n.int. i="[n.current_page_rows/]">
+			<n.no_output.minus i="[n.current_index/]"/>
+			<n.if.is_greater_than i="0">
+				<then>
+					<div class="info-message" style="margin-top:.2em;padding:.7em .5em">
+						<t>Some private items have been omitted because you don't have permission to view them.</t>
+					</div>
+				</then>
+			</n.if.is_greater_than>
+		</n.int.>
+	</n.paging.>
+</macro>
+
+<macro name="user_nodes_meta" requires="user_page">
+	<n.if.not.equal value1="[n.user_nodes_page_number/]" value2="1">
+		<then>
+			<meta name="robots" content="noindex,follow"/>
+		</then>
+	</n.if.not.equal>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/user_profile.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,233 @@
+<macro name="user_profile" requires="servlet">
+	<n.visitor.as_user_page.>
+		<n.if.not.page_user.is_authenticated>
+			<then>
+				<n.login.><t>You must login to view this page.</t></n.login.>
+			</then>
+		</n.if.not.page_user.is_authenticated>
+		<n.html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<n.title.>Profile of <n.visitor.name/></n.title.>
+				<n.zebra_table_javascript table_selector="table.profile"/>
+			</head>
+			<body>
+				<n.page_user.profile_header/>
+				<n.profile_options/>
+			</body>
+		</n.html>
+	</n.visitor.as_user_page.>
+</macro>
+
+<macro name="profile_options">
+	<n.put_in_head.>
+		<style type="text/css">
+			table.profile {
+				border-collapse:collapse;
+				width:100%;
+				margin-top:.5em;
+			}
+			table.profile td {
+				padding:.7em .3em;
+			}
+			table.profile td.title-row {
+				background: transparent;
+				padding:.2em .4em;
+				border-bottom-width:2px;
+				border-bottom-style:solid;
+				font-weight:bold;
+			}
+		</style>
+	</n.put_in_head.>
+	<table class="profile">
+		<n.profile_table_header.>
+			<t>Account Settings</t>
+		</n.profile_table_header.>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.edit_profile_path/]"><t>Edit Personal Information</t></a></link>
+			<hint><t>Change your registered email address, password, and your user name.</t></hint>
+		</n.profile_option>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.edit_signature_path/]"><t>Edit Your Signature</t></a></link>
+			<hint><t>Change the signature text that is displayed at the bottom of your posts.</t></hint>
+		</n.profile_option>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.change_avatar_path/]"><t>Change Your Picture</t></a></link>
+			<hint><t>Change or remove your avatar image.</t></hint>
+		</n.profile_option>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.advanced_settings_path/]"><t>Advanced Settings</t></a></link>
+			<hint><t>Change viewing preferences and other settings.</t></hint>
+		</n.profile_option>
+
+		<n.profile_option>
+			<link><a href="[n.visitor.remove_account_path/]"><t>Remove Your Account</t></a></link>
+			<hint><t>Remove your account and all your posts from <t.subject><n.root_node.subject/></t.subject>.</t></hint>
+		</n.profile_option>
+	</table>
+</macro>
+
+<macro name="profile_table_header" dot_parameter="text">
+	<tr>
+		<td class="title-row medium-border-color" colspan="2">
+			<n.text/>
+		</td>
+	</tr>
+</macro>
+
+<macro name="profile_option" parameters="link, hint">
+	<tr>
+		<td style="width:14em">
+			<n.link/>
+		</td>
+		<td class="weak-color">
+			<n.hint/>
+		</td>
+	</tr>
+</macro>
+
+<macro name="profile_header" requires="user">
+	<n.set_local_user.this_user />
+	<n.block.>
+		<div class="second-font shaded-bg-color rounded" style="font-size:170%;padding:.2em .5em;margin-left:.1em">
+			<n.local_user.name/>
+		</div>
+		<table>
+			<tr valign="top">
+				<td><n.local_user.avatar size="big"/></td>
+				<td style="width:100%;padding-left:.5em">
+					<n.if.both condition1="[n.visitor.is_site_admin/]" condition2="[n.local_user.is_authenticated/]">
+						<then>
+							<strong>Email</strong>: <n.local_user.user_email/>
+						</then>
+					</n.if.both>
+
+					<n.if.local_user.is_banned>
+						<then>
+							<n.local_user.banned_label/>
+						</then>
+						<else>
+							<n.local_user.registration_label/>
+							<n.local_user.list_current_groups/>
+							<n.if.not.local_user.is_deactivated>
+								<then.local_user.send_email_to_user_link/>
+							</n.if.not.local_user.is_deactivated>
+						</else>
+					</n.if.local_user.is_banned>
+
+					<!-- If this is the profile of the visitor -->
+					<n.if.visitor.equals.local_user>
+						<then>
+							<!-- if the user is authenticated -->
+							<n.if.local_user.is_authenticated>
+								<then.local_user.authenticated_self_profile_header/>
+								<else>
+									<div style="margin-top:.5em">
+										<t><n.register_link.>Register now</n.register_link.> if you want to edit your profile, receive posts via email or have access to your global profile.</t>
+									</div>
+								</else>
+							</n.if.local_user.is_authenticated>
+						</then>
+						<else>
+							<n.if.visitor.can_manage_banned_users>
+								<then>
+									<div style="margin-top:.5em">
+										<img src="/images/icon_blocked.png" class="image16" style="margin:0 1px"/>
+										<n.if.local_user.is_banned>
+											<then><a href="[n.local_user.unban_path/]"><t>Unban this user</t></a></then>
+											<else><a href="[n.local_user.ban_path/]"><t>Ban this user</t></a></else>
+										</n.if.local_user.is_banned>
+									</div>
+								</then>
+							</n.if.visitor.can_manage_banned_users>
+						</else>
+					</n.if.visitor.equals.local_user>
+				</td>
+			</tr>
+		</table>
+	</n.block.>
+</macro>
+
+<macro name="authenticated_self_profile_header" requires="user">
+	<!-- Show basic profile links -->
+	<n.if.not.equal value1="[n.get_parameter name='macro'/]" value2="user_profile">
+		<then>
+			<div style="margin-top:.5em">
+				<img src="/images/tool.png" align="absmiddle" width="16" height="17" style="margin:0 1px"/>
+				<a href="[n.user_profile_path/]"><t>Account Settings</t></a>
+			</div>
+		</then>
+	</n.if.not.equal>
+
+	<n.if.root_node.has_sub_archive>
+		<then>
+			<div style="margin-top:.5em">
+				<img src="/images/icon_pending.png" class="image16" style="margin:0 1px" />
+				<a href="[n.pending_posts_path/]"><t>My Pending Posts</t></a>
+			</div>
+		</then>
+	</n.if.root_node.has_sub_archive>
+
+	<div style="margin-top:.5em">
+		<img src="/images/forum_sm.png" class="image16" style="margin:0 1px"/>
+		<a href="[n.nabble_global_apps_url/]" target="_top"><t>My Nabble Applications</t></a>
+	</div>
+</macro>
+
+<macro name="banned_label" requires="user">
+	<div style="margin-top:.4em">
+		<strong><t>Banned User</t></strong>
+	</div>
+</macro>
+
+<macro name="registration_label" requires="user">
+	<div style="margin-top:.4em">
+		<n.if.is_registered>
+				<then><strong><t>Registered</t></strong>: <n.registration_date.date_only/></then>
+				<else><strong><t>Unregistered User</t></strong></else>
+		</n.if.is_registered>
+	</div>
+</macro>
+
+<macro name="list_current_groups" requires="user">
+	<!-- List the groups of the user-->
+	<n.set_local_user.this_user />
+	<div style="margin-top:.4em">
+		<strong><t>Groups</t>:</strong>
+		<n.local_user.groups.>
+			<n.sort/>
+			<n.loop.>
+				<n.if.has_more_elements>
+					<then><n.current_group/>,&nbsp;</then>
+					<else><n.current_group/></else>
+				</n.if.has_more_elements>
+			</n.loop.>
+		</n.local_user.groups.>
+		<n.if.visitor.is_site_admin>
+			<then>
+				<div style="margin-top:.3em">
+					<img src="/images/user_group.png" align="absmiddle" width="18" height="16"/>
+					<a href="[n.local_user.change_user_groups_path/]"><t>Add / Remove Groups</t></a>
+					| <a href="[n.local_user.edit_profile_path/]"><t>Edit Profile</t></a>
+				</div>
+			</then>
+		</n.if.visitor.is_site_admin>
+	</div>
+</macro>
+
+<macro name="send_email_to_user_link" requires="user">
+	<n.set_local_user.this_user />
+	<!-- If this is not the profile of the visitor AND the user is authenticated -->
+	<n.if.both condition1="[n.not.visitor.equals.local_user/]" condition2="[n.local_user.is_authenticated/]">
+		<then>
+			<div style="margin-top:.5em">
+				<img src="/images/mail.png" align="absmiddle" width="18" height="13"/>
+				<a href="[n.local_user.send_email_path/]"><t>Send Email to <t.author.local_user.name/></t></a>
+			</div>
+		</then>
+	</n.if.both>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/utilities.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1256 @@
+
+override this macro to create skins
+<macro name="html" parameters="head,body">
+	<n.html_impl>
+		<head>
+			<n.head/>
+		</head>
+		<body>
+			<n.top_bar/>
+			<n.body/>
+			<n.nabble_footer/>
+		</body>
+	</n.html_impl>
+</macro>
+
+<macro name="html_impl" parameters="head,body" requires="servlet">
+	<n.page_start/>
+	<n.update_default_permissions/>
+	<n.nabble_html>
+		<do>
+			<n.embedding_redirection_js/>
+			<n.put_in_head.head/>
+			<n.body/>
+			<n.load_call_later_script/>
+		</do>
+		<output>
+			<![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">]]>
+			<html>
+				<head>
+					<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+					<n.nabble_stylesheets/>
+					<n.nabble_javascript_libraries/>
+					<n.html_head_content/>
+					<n.nabble_shared_scripts/>
+				</head>
+				<body>
+					<div id="notice" class="notice rounded-bottom"></div>
+					<div class="nabble macro_[n.page_template/]" id="nabble">
+						<n.apply_filters.html_body_content/>
+					</div>
+					<n.bottom_scripts/>
+					<n.as_html_comments.site_information/>
+				</body>
+			</html>
+		</output>
+	</n.nabble_html>
+</macro>
+
+<macro name="page_start">
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="apply_filters" dot_parameter="do">
+	<n.comment.>To be overridden</n.comment.>
+	<n.do/>
+</macro>
+
+<macro name="bottom_scripts">
+	<script src="/trk5.js" async="true"></script>
+	<script src="/trk0.js" async="true"></script>
+</macro>
+
+<macro name="site_information">
+	<n.default_host_name/> | Site ID = <n.site_id/>
+</macro>
+
+<macro name="nabble_stylesheets">
+	<link rel="stylesheet" href="/nabble.css?v=[n.css_version/]" type="text/css" />
+	<link rel="stylesheet" href="/template/NamlServlet.jtp?macro=site_style" type="text/css" />
+</macro>
+
+<macro name="site_style" unindent="true">
+	<n.uncache_for/>
+	<n.set_response_header name="Content-Type" value="text/css"/>
+	<n.comment.>To be overridden</n.comment.>
+</macro>
+
+<macro name="jquery_library">
+	<script src="/util/jquery-1.7.2.pack.js" type="text/javascript"></script>
+</macro>
+
+<macro name="nabble_javascript_libraries">
+	<n.jquery_library/>
+	<script src="/util/nabbledropdown-2.4.1.js" type="text/javascript"></script>
+	<script src="[n.javascript_library_path/]" type="text/javascript"></script>
+</macro>
+
+<macro name="nabble_shared_scripts">
+	<script type="text/javascript">
+		Nabble.setFontSize();
+		<n.if.request_method_is_post>
+			<then>var loginNextUrl = '/';</then>
+		</n.if.request_method_is_post>
+	</script>
+	<n.nabble_analytics/>
+</macro>
+
+<macro name="nabble_analytics">
+	<script type="text/javascript">
+		if (Nabble.analytics) Nabble.analytics();
+	</script>
+	<n.js_google_analytics/>
+</macro>
+
+<macro name="js_google_analytics">
+	<!-- Start Google Analytics -->
+	<script>
+		(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+		(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+		m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+		})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
+		
+		ga('create', 'UA-91855-9', 'auto', 'nabble');
+		ga('nabble.send', 'pageview');
+	</script>
+	<!-- End Google Analytics -->
+</macro>
+
+<macro name="nabble_footer">
+	<table class="footer-table shaded-bg-color">
+		<tr>
+			<td class="footer-left weak-color">
+				<n.nabble_footer_left/>
+			</td>
+			<td class="footer-right">
+				<n.disable_ads_link/>
+				<n.macro_viewer_page_link/>
+			</td>
+		</tr>
+	</table>
+</macro>
+
+<macro name="disable_ads_link">
+	<script>
+		if( window.nbl_disableAdsLink && window.localStorage && !localStorage.nbl_noAds ) {
+			document.write("<a href='javascript:localStorage.nbl_noAds=true;location.reload();'>"+nbl_disableAdsLink+"</a> | ");
+		}
+	</script>
+</macro>
+
+<macro name="nabble_footer_left">
+	<n.nabble_homepage_link.>Free forum by Nabble</n.nabble_homepage_link.>
+</macro>
+
+<macro name="nabble_homepage_link" dot_parameter="text" parameters="class">
+	<a href="[n.nabble_homepage/]" class="[n.class/]" target="_top"><n.default. to="Nabble"><n.text/></n.default.></a>
+</macro>
+
+<macro name="forum_footer">
+	<n.page_node.>
+		<div id="forum-footer" class="forum-footer nowrap">
+			<img src="/images/feeds.png" width="16" height="16" align="absmiddle" alt="feeds"/>
+			<n.feeds_link/>
+			<span class="weak-color" style="padding-left:.5em">|</span>
+			<t>Created by <t.author.owner.avatar_and_link/></t>
+			<span class="weak-color" style="padding-left:.5em">|</span>
+			<n.views show_text="true"/>
+		</div>
+	</n.page_node.>
+</macro>
+
+<macro name="avatar_and_link" requires="user">
+	<n.avatar size="small"/>
+	<n.user_link/>
+</macro>
+
+<macro name="top_bar">
+	<div class="top-bar">
+		<div class="breadcrumbs" style="float:left">
+			<n.breadcrumbs/>
+		</div>
+		<div style="text-align:right;">
+			<n.user_header/>
+		</div>
+	</div>
+</macro>
+
+<macro name="breadcrumbs">
+	<span id="breadcrumbs" class="weak-color">
+		<n.breadcrumbs_content/>
+	</span>
+</macro>
+
+<macro name="breadcrumbs_content">
+	<n.root_node.node_breadcrumbs />
+</macro>
+
+<macro name="breadcrumbs_content" requires="node_page">
+	<n.page_node.node_breadcrumbs />
+</macro>
+
+<macro name="node_breadcrumbs" requires="node">
+	<n.if.is_in_app>
+		<then>
+			<n.get_app_node.>
+				<n.ancestors_list. order="reverse">
+					<n.loop.>
+						<n.current_node.node_link/>
+						<span> &rsaquo; </span>
+					</n.loop.>
+				</n.ancestors_list.>
+				<n.node_link/>
+			</n.get_app_node.>
+		</then>
+	</n.if.is_in_app>
+</macro>
+
+<macro name="separator">
+	<div style="height:.8em"></div>
+</macro>
+
+<macro name="hide_if" dot_parameter="text" parameters="equals">
+	<n.if.not.equal value1="[n.text/]" value2="[n.equals/]">
+		<then><n.text/></then>
+	</n.if.not.equal>
+</macro>
+
+<macro name="hide_if_equals" dot_parameter="text" parameters="value1,value2">
+	<n.if.not.equal value1="[n.value1/]" value2="[n.value2/]">
+		<then><n.text/></then>
+	</n.if.not.equal>
+</macro>
+
+<macro name="newsflash">
+	<div id="nabble-newsflash" class="info-message" style="display:none;padding:.5em;margin-bottom:.5em"></div>
+	<n.call_later param="newsflash"/>
+</macro>
+
+<macro name="newsflash_js">
+	<n.param_loop. param="newsflash">
+		<n.if.not.is_null.get_newsflash>
+			<then>
+				var newsDiv = Nabble.get('nabble-newsflash');
+				newsDiv.innerHTML = "<n.get_newsflash/>";
+				newsDiv.style.display = 'block';
+			</then>
+		</n.if.not.is_null.get_newsflash>
+	</n.param_loop.>
+</macro>
+
+<macro name="show_administrator_notice">
+	<div id="admin-notice" class="info-message" style="display:none;padding:.5em;margin-bottom:.5em"></div>
+	<n.call_later param="adminNotice"/>
+</macro>
+
+<macro name="show_administrator_notice_js">
+	<n.param_loop. param="adminNotice">
+		<n.if.both condition1="[n.visitor.is_site_admin/]" condition2="[n.not.is_empty.administrator_notice/]">
+			<then>
+				function closeAdminNotice() {
+					$('#admin-notice').slideUp();
+					Nabble.setPersistentCookie('admin-notice','<n.administrator_notice_version/>');
+					NabbleDropdown.show('adminNotice');
+				 };
+				if (Nabble.getCookie('admin-notice') != '<n.administrator_notice_version/>') {
+					var html = "<table><tr><td style='width:100%'>";
+					html += "<n.javascript_string_encode.administrator_notice/>";
+					html += "</td><td style='vertical-align:top;cursor:pointer;line-height:.3em;font-size:200%' onclick='closeAdminNotice()'>&times;</td>";
+					html += "</tr></table>";
+					$("#admin-notice").html(html).show();
+				} else {
+					Nabble.addUserHeaderListener(function() {
+						NabbleDropdown.show('adminNotice');
+					});
+				}
+			</then>
+		</n.if.both>
+	</n.param_loop.>
+</macro>
+
+<macro name="increment_view_count" requires="node_page,servlet">
+	<n.call_later value="[n.page_node.id/]" param="incViewCount"/>
+</macro>
+
+<macro name="increment_view_count_js">
+	<n.param_loop. param="incViewCount">
+		<n.increment_node_view_count node_id="[n.current_parameter_value/]" />
+	</n.param_loop.>
+</macro>
+
+<macro name="mailing_list_information">
+	<n.if.is_associated_with_mailing_list_archive>
+		<then>
+			<n.get_associated_mailing_list_archive.>
+				<div style="margin:.5em 0">
+					<t>This forum is an archive for the mailing list</t>
+					<strong><n.mailing_list_address/></strong> (<n.mailing_list_options_link.><t>more options</t></n.mailing_list_options_link.>)
+					<t>Messages posted here will be sent to this mailing list.</t>
+				</div>
+			</n.get_associated_mailing_list_archive.>
+		</then>
+	</n.if.is_associated_with_mailing_list_archive>
+</macro>
+
+
+EDIT NODE
+
+<macro name="edit_header" parameters="first_text,second_text">
+	<div class="big-title second-font shaded-bg-color" style="padding: .4em;margin-top:.1em">
+		<n.first_text/>
+		<n.if.not.is_null.second_text>
+			<then>
+				<span class="weak-color">
+					<n.prepend. prefix="&ndash; "><n.second_text/></n.prepend.>
+				</span>
+			</then>
+		</n.if.not.is_null.second_text>
+	</div>
+</macro>
+
+<macro name="input" requires="field" parameters="size,tabindex,type,maxlength">
+	<input id="[n.name/]" name="[n.name/]" value="[n.value/]" size="[n.size/]" maxlength="[n.maxlength/]" tabindex="[n.tabindex/]" type="[n.type/]" />
+</macro>
+
+<macro name="hidden" requires="field">
+	<n.if.not.is_null.value>
+		<then>
+			<input type="hidden" id="[n.name/]" name="[n.name/]" value="[n.value/]"/>
+		</then>
+	</n.if.not.is_null.value>
+</macro>
+
+<macro name="checkbox" parameters="style" requires="field">
+	<input id="[n.name/]" type="checkbox" name="[n.name/]" value="[n.true/]" checked="[n.false_to_null.value/]" style="[n.style/]"/>
+</macro>
+
+<macro name="is_checked" requires="field">
+	<n.null_to_false.value/>
+</macro>
+
+<macro name="select" dot_parameter="options" parameters="onchange,style" requires="field">
+	<select id="[n.name/]" name="[n.name/]" onchange="[n.onchange/]" style="[n.style/]">
+		<n.options/>
+	</select>
+</macro>
+
+<macro name="focus" requires="field">
+	<script type="text/javascript">
+		$(document).ready(function() {
+			if (Nabble.isEmbedded) {
+				Nabble.resizeFrames('', 1);
+			}
+			$("#<n.name/>").focus();
+		});
+	</script>
+</macro>
+
+<macro name="select_option" parameters="value,selectedValue,class" dot_parameter="text">
+	<option value="[n.value/]" class="[n.class/]" selected="[n.if][condition.equal][value1.value/][value2.selectedValue/][/condition.equal][then.true/][else.null/][/n.if]"><n.text/></option>
+</macro>
+
+<macro name="null_to_false" dot_parameter="value">
+	<n.default to="[n.false/]" text="[n.value/]" />
+</macro>
+
+<macro name="false_to_null" dot_parameter="value">
+	<n.to_null_if value="[n.value/]" equals="[n.false/]" />
+</macro>
+
+<macro name="to_null_if" dot_parameter="value" parameters="equals">
+	<n.if.equal value1="[n.value/]" value2="[n.equals/]">
+		<then><n.null/></then>
+		<else><n.value/></else>
+	</n.if.equal>
+</macro>
+
+<macro name="radio" requires="field" parameters="id,option_value">
+	<n.if.equal value1="[n.value/]" value2="[n.option_value/]">
+		<then><input type="radio" id="[n.id/]" name="[n.name/]" value="[n.option_value/]" checked="true"/></then>
+		<else><input type="radio" id="[n.id/]" name="[n.name/]" value="[n.option_value/]" /></else>
+	</n.if.equal>
+</macro>
+
+<macro name="textarea" requires="field" parameters="wrap,tabindex,style,maxlength">
+	<textarea name="[n.name/]" id="[n.name/]" wrap="[n.wrap/]" maxlength="[n.maxlength/]" tabindex="[n.tabindex/]" style="[n.style/]"><n.hide_null.value/></textarea>
+</macro>
+
+<macro name="format_error" dot_parameter="message" parameters="prompt">
+	<n.set_var. name="message"><n.message/></n.set_var.>
+	<n.set_var. name="prompt"><n.prompt/></n.set_var.>
+	<n.if.not.is_empty.trim.var name="message">
+		<then>
+			<table class="error-message" style="width:100%">
+				<tr>
+					<td style="padding:.4em;width:40px;">
+						<img src="/images/icon_alert.png" class="image32"/>
+					</td>
+					<td style="padding:.4em;">
+						<strong><n.var name="message"/></strong>
+						<n.if.not.is_null.var name="prompt">
+							<then><div style="padding-top:.3em"><n.prompt/></div></then>
+						</n.if.not.is_null.var>
+					</td>
+				</tr>
+			</table>
+		</then>
+	</n.if.not.is_empty.trim.var>
+</macro>
+
+<macro name="view_name" requires="node">
+	<n.trim.switch. value="[n.app_or_root.type/]">
+		<n.case. value="category"><t>Category</t></n.case.>
+		<n.case. value="board"><t>Board</t></n.case.>
+		<n.case. value="gallery"><t>Gallery</t></n.case.>
+		<n.case. value="blog"><t>Blog</t></n.case.>
+		<n.case. value="news"><t>News</t></n.case.>
+		<n.default_case.><t>Forum</t></n.default_case.>
+	</n.trim.switch.>
+</macro>
+
+<macro name="lower_case_view_name" requires="node">
+	<n.trim.switch. value="[n.app_or_root.type/]">
+		<n.case. value="category"><t>category</t></n.case.>
+		<n.case. value="board"><t>board</t></n.case.>
+		<n.case. value="gallery"><t>gallery</t></n.case.>
+		<n.case. value="blog"><t>blog</t></n.case.>
+		<n.case. value="news"><t>news</t></n.case.>
+		<n.default_case.><t>forum</t></n.default_case.>
+	</n.trim.switch.>
+</macro>
+
+<macro name="child_name" requires="node">
+	<n.if.regex_matches text="[n.type/]" pattern="forum|category|mixed">
+		<then><t>Sub-Forum</t></then>
+		<else><t>Subcategory</t></else>
+	</n.if.regex_matches>
+</macro>
+
+<macro name="child_name_plural" requires="node">
+	<n.if.type equals="forum">
+		<then><t>Sub-Forums</t></then>
+		<else><t>Subcategories</t></else>
+	</n.if.type>
+</macro>
+
+<macro name="user_link" requires="user" parameters="title, class">
+	<a href="[n.path/]" title="[n.title/]" class="[n.class/]"><n.name/></a>
+</macro>
+
+<macro name="node_text" requires="node" dot_parameter="text">
+	<n.default. to="[n.subject/]"><n.text/></n.default.>
+</macro>
+
+<macro name="node_href" requires="node" dot_parameter="href">
+	<n.default. to="[n.path/]"><n.href/></n.default.>
+</macro>
+
+<macro name="node_link" requires="node" dot_parameter="href" parameters="text, title, class, target">
+	<a href="[n.node_href.href/]" title="[n.title/]" class="[n.class/]" target="[n.target/]"><n.node_text.text/></a>
+</macro>
+
+<macro name="title" dot_parameter="text">
+	<title><n.root_node.subject/> - <n.text/></title>
+</macro>
+
+<macro name="register_link" dot_parameter="text" parameters="title">
+	<a href="[n.register_path /]" title="[n.title/]" rel="nofollow"><n.default to="[t]Register[/t]" text="[n.text/]"/></a>
+</macro>
+
+<macro name="register_path" parameters="nextUrl">
+	<n.encode_url.remove_spaces.>
+		/template/NamlServlet.jtp?macro=start_registration_page
+		<n.add_to_path name="nextUrl" value="[n.nextUrl/]" />
+	</n.encode_url.remove_spaces.>
+</macro>
+
+<macro name="terms_link" dot_parameter="text" parameters="title">
+	<a href="[n.terms_of_use_url is_registration_form='true'/]" target="_new" title="[n.title/]" rel="nofollow"><n.default to="[t]Terms of Use[/t]" text="[n.text/]"/></a>
+</macro>
+
+<macro name="reply_link" requires="node" parameters="title, href">
+	<n.set_var. name="href"><n.default text="[n.href/]" to="[n.reply_path/]" /></n.set_var.>
+	<a href="[n.var name='href'/]" title="[n.title/]" rel="nofollow"><t>Reply</t></a>
+</macro>
+
+<macro name="reply_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=reply&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="new_topic_link" requires="node" dot_parameter="text" parameters="title, class, href">
+	<n.set_var. name="href"><n.default text="[n.href/]" to="[n.new_topic_path/]" /></n.set_var.>
+	<a href="[n.var name='href'/]" class="[n.class/]" title="[n.title/]" rel="nofollow"><n.text/></a>
+</macro>
+
+<macro name="people_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.people_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]People[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="feeds_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.feeds_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Feeds[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="feeds_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=feeds&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="subscribe_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.subscribe_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Subscribe via email[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="subscribe_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=subscribe&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="post_by_email_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.post_by_email_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Post by email[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="post_by_email_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=post_by_email_page&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="edit_subject_and_message_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.edit_subject_and_message_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Edit name & description[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="edit_subject_and_message_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=edit_app&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="change_appearance_link" dot_parameter="text" parameters="title, class">
+	<a href="[n.change_appearance_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Change appearance[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="change_appearance_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=change_appearance
+	</n.encode_url.>
+</macro>
+
+<macro name="change_language_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.change_language_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Change language[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="extras_and_addons_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.extras_and_addons_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Extras & add-ons[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="change_type_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.change_type_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Change application type[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="send_email_link" requires="user" dot_parameter="text" parameters="title, class">
+	<a href="[n.send_email_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.text/></a>
+</macro>
+
+<macro name="mailing_list_options_link" requires="mailing_list" dot_parameter="text" parameters="title, class">
+	<a href="[n.mailing_list_options_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.text/></a>
+</macro>
+
+<macro name="change_type_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=change_app_type&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="change_domain_name_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.change_domain_name_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Change domain name[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="create_sub_app_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.create_sub_app_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Create new [t.element.to_lower_case.child_name/][/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="create_sub_app_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=create_sub_app&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="manage_pinned_topics_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.manage_pinned_topics_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Manage pinned topics[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="manage_sub_apps_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.manage_sub_apps_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Manage [t.items.to_lower_case.child_name_plural/][/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="parent_options_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.parent_options_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Change parent[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="change_permissions_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.change_permissions_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Change permissions[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="change_permissions_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=change_permissions&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="manage_users_and_groups_link" dot_parameter="text" parameters="title, class">
+	<a href="[n.manage_users_and_groups_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Manage users & groups[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="manage_subscribers_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.manage_subscribers_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Manage subscribers[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="manage_banned_users_link" dot_parameter="text" parameters="title, class">
+	<a href="[n.manage_banned_users_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Manage banned users[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="manage_banned_users_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=manage_banned_users
+	</n.encode_url.>
+</macro>
+
+<macro name="mailing_list_archive_settings_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.mailing_list_archive_settings_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Mailing list archive settings[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="mailing_list_archive_settings_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=mailing_list_archive_settings&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="embedding_options_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.embedding_options_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Embedding options[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="delete_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.delete_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Delete[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="delete_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=delete_app&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="delete_post_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="javascript: void(0)" onclick="Nabble.deletePost([n.id/])" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Delete this post[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="delete_recursively_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="javascript: void(0)" onclick="Nabble.deleteFromSite([n.id/])" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Delete this post and replies[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="edit_post_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.edit_post_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Edit post[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="edit_post_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=edit_post&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="move_post_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.move_post_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Move post[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="move_post_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=move_node&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="permalink" requires="node">
+	<a href="javascript: void(0)" onclick="prompt('Copy this:','[n.url/]')"><t>Permalink</t></a>
+</macro>
+
+<macro name="embed_post_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.embed_post_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Embed post[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="print_post_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.print_post_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Print post[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="print_post_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=print_post&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="reply_to_author_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.reply_to_author_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Reply to author[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="change_post_date_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.change_post_date_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Change post date[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="change_post_date_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=change_post_date&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="raw_mail_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.raw_mail_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]Raw mail[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="raw_mail_path" requires="node">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=raw_mail&node=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="change_user_groups_path" requires="user">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=change_user_groups&user=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="send_email_path" requires="user">
+	<n.encode_url.>
+		/user/SendEmail.jtp?type=user&user=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="user_profile_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=user_profile
+	</n.encode_url.>
+</macro>
+
+<macro name="edit_profile_path" requires="user">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=edit_profile&user=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="ban_path" requires="user">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=ban_user&user=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="unban_path" requires="user">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=unban_user&user=<n.id/>
+	</n.encode_url.>
+</macro>
+
+<macro name="one_or_many" dot_parameter="n" parameters="one_text, many_text">
+	<n.if.equal value1="[n.n/]" value2="1">
+		<then>
+			1 <n.one_text/>
+		</then>
+		<else>
+			<n.n/> <n.many_text/>
+		</else>
+	</n.if.equal>
+</macro>
+
+<macro name="comments_link" requires="node">
+	<a href="[n.first_reply.url/]"><n.one_or_many.replies>
+		<one_text><t>comment</t></one_text>
+		<many_text><t>comments</t></many_text>
+	</n.one_or_many.replies></a>
+</macro>
+
+<macro name="replies_link" requires="node">
+	<a href="[n.first_reply.url/]"><n.one_or_many.replies>
+		<one_text><t>reply</t></one_text>
+		<many_text><t>replies</t></many_text>
+	</n.one_or_many.replies></a>
+</macro>
+
+<macro name="view_logs_link" requires="node" dot_parameter="text" parameters="title, class">
+	<a href="[n.view_log_path/]" class="[n.class/]" rel="nofollow" title="[n.title/]"><n.default. to="[t]View logs[/t]"><n.text/></n.default.></a>
+</macro>
+
+<macro name="view_log_path">
+	<n.encode_url.>
+		/template/NamlServlet.jtp?macro=view_log
+	</n.encode_url.>
+</macro>
+
+<macro name="search_box" requires="node">
+	<n.search_form.>
+		<input id="search-input" name="query" size="18" class="medium-border-color"/>
+		<n.search_box_dropdown/>
+	</n.search_form.>
+</macro>
+
+<macro name="search_box_dropdown" requires="node">
+	<n.put_in_head.>
+		<style type="text/css">
+			#search-box-dropdown {
+				text-align:left;
+				position:absolute;
+				display:none;
+				z-index:1000;
+				overflow:hidden;
+			}
+		</style>
+		<script type="text/javascript">
+			$(document).ready(function() {
+				var $sdd = $('#search-box-dropdown');
+				var $sb = $('#search-input');
+				var $form = $sb.parent();
+				var timeout;
+				$(document).click(function(o){
+					var $target = $(o.target);
+					if ($target.parents().hasClass('search-box-dropdown')) {
+						clearTimeout(timeout);
+						$sb.focus();
+					}
+				});
+				$sb.focusin(function(e) {
+					$sdd.css('left', $sb.position().left - 5);
+					$sdd.width($sb.outerWidth() + 10);
+					$sdd.show();
+				});
+				$sb.focusout(function() {
+					timeout = setTimeout(function() {
+						$sdd.hide();
+					},250);
+				});
+				<n.if.not.is_root>
+				<then>
+				$('input[type=radio]', $sdd).change(function() {
+					var nodeId = $(this).val();
+					$('input[name="node"]', $form).val(nodeId);
+				});
+				$('input[name="node"]', $form).val(<n.root_node.id/>);
+				</then>
+				</n.if.not.is_root>
+			});
+		</script>
+	</n.put_in_head.>
+	<div id="search-box-dropdown" class="search-box-dropdown light-bg-color drop-shadow border1 medium-border-color rounded-bottom">
+		<n.if.not.is_root>
+			<then>
+				<div style="margin:.5em .5em 0 .5em">
+					<b><t>Search</t></b><br/>
+					<input id="search-root-node" type="radio" name="n" value="[n.root_node.id/]" checked="true"/>
+					<label for="search-root-node"><t>everywhere</t></label><br/>
+
+					<input id="search-this-node" type="radio" name="n" value="[n.id/]"/>
+					<label for="search-this-node">
+						<n.if.is_post>
+							<then><t>only in this topic</t></then>
+							<else><t>in <t.location.subject/></t></else>
+						</n.if.is_post>
+					</label>
+				</div>
+			</then>
+		</n.if.not.is_root>
+		<div style="margin:.5em;line-height:2em">
+			<input class="toolbar action-button float-right" type="submit" value="[t]Search[/t]"/>
+			<a href="[n.advanced_search_path/]" rel="nofollow" style="font-size:80%"><t>Advanced Search</t></a>
+		</div>
+	</div>
+</macro>
+
+ <macro name="force_wrap" dot_parameter="text">
+	<n.regex_replace_all. pattern="\s+" replacement="[n.lt/]br/[n.gt/]">
+		<n.text/>
+	</n.regex_replace_all.>
+ </macro>
+
+<macro name="truncated" requires="message" parameters="size">
+	<n.truncate. size="[n.size/]"><n.as_text/></n.truncate.>
+</macro>
+
+<macro name="views" requires="node" parameters="show_text">
+	<n.if.equal value1="[n.show_text/]" value2="true">
+		<then>
+			<span id="v[n.id/]" style="display:none">1 <t>view</t>|%1 <t>views</t></span>
+		</then>
+		<else>
+			<span id="v[n.id/]" style="display:none"></span>
+		</else>
+	</n.if.equal>
+	<n.put_in_head.>
+		<script type="text/javascript">
+			Nabble.nViews = function(id, views) {
+				var $v = $('#v'+id);
+				var pos = views=='1'?0:1;
+				var t = $v.html()? $v.html().split('|')[pos]:'';
+				$v.html(t == ''? views : t.replace(/%1/g,views)).show();
+			};
+		</script>
+	</n.put_in_head.>
+	<n.call_later value="[n.id/]" param="views"/>
+</macro>
+
+<macro name="views_js">
+	<n.param_loop. param="views">
+		Nabble.nViews(<n.current_parameter_value/>,'<n.node_views node_id="[n.current_parameter_value/]" />');
+	</n.param_loop.>
+</macro>
+
+<macro name="loop" requires="sequence" dot_parameter="do" parameters="by">
+	<n.while.next_element inc="[n.by/]">
+		<loop>
+			<n.do/>
+		</loop>
+	</n.while.next_element>
+</macro>
+
+<macro name="filter_by" requires="list" dot_parameter="filter">
+	<n.loop.>
+		<n.if.not.filter>
+			<then.remove_current_element/>
+		</n.if.not.filter>
+	</n.loop.>
+	<n.reset_list_index/>
+</macro>
+
+<macro name="regex_matches" parameters="pattern,text">
+	<n.regex. pattern="[n.pattern/]" text="[n.text/]">
+		<n.matches/>
+	</n.regex.>
+</macro>
+
+<macro name="did_find" parameters="group" requires="regex">
+	<n.not.is_null.found group="[n.group/]" />
+</macro>
+
+<macro name="all_true" dot_parameter="text">
+	<n.regex_matches pattern="(\s*true)*\s*" text="[n.text/]" />
+</macro>
+
+<macro name="neither" parameters="condition1,condition2">
+	<n.both condition1="[n.not.condition1/]" condition2="[n.not.condition2/]"/>
+</macro>
+
+<macro name="page_node" dot_parameter="do" requires="js_loop">
+	<n.get_node_from_id. node_id="[n.value/]">
+		<n.do/>
+	</n.get_node_from_id.>
+</macro>
+
+<macro name="page_user" dot_parameter="do" requires="js_loop">
+	<n.get_user_from_id. user_id="[n.value/]">
+		<n.do/>
+	</n.get_user_from_id.>
+</macro>
+
+<macro name="form" dot_parameter="content" parameters="macro,method,onsubmit" requires="servlet">
+	<n.set_var. name="macro"><n.default. to="[n.page_template/]"><n.macro/></n.default.></n.set_var.>
+	<form method="[n.default. to='POST'][n.method/][/n.default.]" action="/template/NamlServlet.jtp?[n.var name='macro'/]" onsubmit="[n.onsubmit/]" accept-charset="UTF-8">
+		<input type="hidden" name="macro" value="[n.var name='macro'/]" />
+		<n.node_field.hidden/>
+		<n.user_field.hidden/>
+		<n.content/>
+	</form>
+</macro>
+
+<macro name="is_submitted_form" requires="servlet">
+	<n.request_method_is_post/>
+</macro>
+
+<macro name="request_method_is_post" requires="servlet">
+	<n.equal value1="[n.request_method/]" value2="POST"/>
+</macro>
+
+<macro name="node_field" dot_parameter="do">
+	<n.field. name="node"><n.do/></n.field.>
+</macro>
+
+<macro name="user_field" dot_parameter="do">
+	<n.field. name="user"><n.do/></n.field.>
+</macro>
+
+<macro name="node_page" dot_parameter="do" requires="servlet">
+	<n.get_node_from_parameter.as_node_page.do/>
+</macro>
+
+<macro name="get_node_from_parameter" dot_parameter="do" requires="servlet">
+	<n.catch_exception. id="get-node-block">
+		<n.get_node_from_request_parameter.do/>
+	</n.catch_exception.><n.handle_exception. for="get-node-block">
+		<n.exception. name="node_not_found">
+			<n.send_http_error code="404" text="Node Not Found" />
+		</n.exception.>
+	</n.handle_exception.>
+</macro>
+
+<macro name="get_node_from_parameter" dot_parameter="do">
+	<n.get_node_from_request_parameter.do/>
+</macro>
+
+<macro name="user_page" dot_parameter="do" requires="servlet">
+	<n.get_user_from_parameter.as_user_page.do/>
+</macro>
+
+<macro name="width_style" dot_parameter="width">
+	<n.style. property='width'><n.width/></n.style.>
+</macro>
+
+<macro name="has_parent" parameters="node">
+	<n.not.is_root/>
+</macro>
+
+<macro name="zebra_table_javascript" parameters="table_selector">
+	<n.put_in_head.>
+		<script type="text/javascript">
+			$(document).ready(function() {
+				$('<n.table_selector/> > tbody > tr:even').filter(':gt(0)').addClass('light-bg-color');
+			});
+		</script>
+	</n.put_in_head.>
+</macro>
+
+<macro name="bold_label_style">
+	<style type="text/css">
+		label { font-weight:bold; }
+	</style>
+</macro>
+
+<macro name="fix_link_in" dot_parameter="text" parameters="url">
+	<n.regex_replace_all. pattern="[n.lt/]a[n.gt/]" replacement="[n.lt/]a href='[n.url/]' rel='nofollow'[n.gt/]">
+		<n.text/>
+	</n.regex_replace_all.>
+</macro>
+
+<macro name="remove_spaces" dot_parameter="text">
+	<n.regex_replace_all. pattern="\s+" replacement="">
+		<n.text/>
+	</n.regex_replace_all.>
+</macro>
+
+<macro name="remove_spaces_between_tags" dot_parameter="text">
+	<n.regex_replace_all. pattern="[n.gt/]\s+[n.lt/]" replacement="[n.gt/][n.lt/]">
+		<n.text/>
+	</n.regex_replace_all.>
+</macro>
+
+<macro name="compress" dot_parameter="text">
+	<n.regex_replace_all. pattern="\s+" replacement=" ">
+		<n.trim.text/>
+	</n.regex_replace_all.>
+</macro>
+
+<macro name="remove_html_tags" dot_parameter="text">
+	<n.set_var. name='tag_regex'><n.lt/>[^<n.gt/>]*<n.gt/></n.set_var.>
+	<n.regex_replace_all. pattern="[n.var name='tag_regex'/]" replacement="">
+		<n.text/>
+	</n.regex_replace_all.>
+</macro>
+
+<macro name="add_line_numbers_to" dot_parameter="text">
+	<n.string_list. values="[n.text/]" separator="\n" trim="[n.false/]">
+		<n.int. n="1">
+			<n.loop.>
+				<n.inc/>	<n.current_string/><n.crlf/>
+			</n.loop.>
+		</n.int.>
+	</n.string_list.>
+</macro>
+
+<macro name="hidden_if" dot_parameter="condition">
+	<n.if.condition>
+		<then>display:none;</then>
+	</n.if.condition>
+</macro>
+
+<macro name="return_to_link" requires="node">
+	<n.app_or_root.>
+		&laquo;
+		<a href="[n.path/]"><t>Return to <t.location.subject/></t></a>
+	</n.app_or_root.>
+</macro>
+
+<macro name="small_notice" dot_parameter="text">
+	<p style="clear:both;font-size:80%;padding-top:.5em">
+		<n.text/>
+	</p>
+</macro>
+
+<macro name="is_ajax">
+	<n.is_in_command name="ajax" />
+</macro>
+
+<macro name="ajax" dot_parameter="do" parameters="is_cached" requires="servlet">
+	<n.use_html_encoder.>
+		<n.do/>
+		<n.if.is_cached>
+			<then.load_call_later_script/>
+			<else>
+				<script type="text/javascript">
+					<n.compress.run_call_later_now/>
+				</script>
+			</else>
+		</n.if.is_cached>
+	</n.use_html_encoder.>
+</macro>
+
+<macro name="cache" parameters="var" dot_parameter="value">
+	<n.if.not.global_is_var_set name="[n.var/]">
+		<then.global_set_var name="[n.var/]" value="[n.value/]" />
+	</n.if.not.global_is_var_set>
+	<n.global_var name="[n.var/]"/>
+</macro>
+
+<macro name="has_parameter" dot_parameter="name" requires="http_request">
+	<n.not.is_null.get_parameter name="[n.name/]" />
+</macro>
+
+<macro name="javascript_response" requires="servlet">
+	<n.set_response_header name="Content-Type" value="application/x-javascript" />
+</macro>
+
+<macro name="xml_response" requires="servlet">
+	<n.set_response_header name="Content-Type" value="application/xml" />
+</macro>
+
+<macro name="text_response" requires="servlet">
+	<n.set_response_header name="Content-Type" value="text/plain" />
+</macro>
+
+<macro name="italic" dot_parameter="text">
+	<i><n.text/></i>
+</macro>
+
+<macro name="bold" dot_parameter="text">
+	<b><n.text/></b>
+</macro>
+
+
+<macro name="set_local_user" dot_parameter="user">
+	<n.uplevel_set_var name="local_user" value="[n.user/]" />
+</macro>
+
+<macro name="local_user" dot_parameter="do">
+	<n.get_user user="[n.uplevel_var name='local_user'/]" do="[n.do/]" />
+</macro>
+
+<macro name="set_local_node" dot_parameter="node">
+	<n.if.is_null.node>
+		<then.throw_runtime_exception text="can't set_local_node to null" />
+	</n.if.is_null.node>
+	<n.uplevel_set_var name="local_node" value="[n.node/]" />
+</macro>
+
+<macro name="local_node" dot_parameter="do">
+	<n.get_node node="[n.uplevel_var name='local_node'/]" do="[n.do/]" />
+</macro>
+
+<macro name="set_local_subscription" dot_parameter="subscription">
+	<n.uplevel_set_var name="local_subscription" value="[n.subscription/]" />
+</macro>
+
+<macro name="local_subscription" dot_parameter="do">
+	<n.get_subscription subscription="[n.uplevel_var name='local_subscription'/]" do="[n.do/]" />
+</macro>
+
+<macro name="has_topic_node" requires="node">
+	<n.is_post/>
+</macro>
+
+<macro name="as_html_comments" dot_parameter="text">
+	<n.lt/>!-- <n.text/> --<n.gt/>
+</macro>
+
+<macro name="encode_text" dot_parameter="text">
+	<n.encode.use_text_encoder.text/>
+</macro>
+
+<macro name="encode_url" dot_parameter="text">
+	<n.encode.use_url_encoder.text/>
+</macro>
+
+<macro name="comment" dot_parameter="text">
+</macro>
+
+<macro name="array_of_user_ids" parameters="var_name, group">
+	<script type="text/javascript">
+		var <n.var_name/> = [<n.compress.>
+		<n.users_in_group. group="[n.group/]">
+			<n.loop.>
+				<n.if.not.is_first_element>
+					<then>,</then>
+				</n.if.not.is_first_element>
+				<n.current_user.id/>
+			</n.loop.>
+		</n.users_in_group.>
+	</n.compress.>];
+	</script>
+</macro>
+
+<macro name="doc for visible_for_admins">
+	<n.doc_text.>
+		Shows the HTML elements returned by the <i>selector</i> parameter if the current visitor is an administrator.
+		This is very helpful in cached pages, where you can't use the visitor command.
+		So the solution is to use javascript to perform this check.
+	</n.doc_text.>
+</macro>
+
+<macro name="visible_for_admins" parameters="selector">
+	<n.put_in_head.>
+		<n.array_of_user_ids var_name="site_admins" group="[n.administrators_group/]"/>
+	</n.put_in_head.>
+	<script type="text/javascript">
+		$(document).ready(function() {
+			if (Nabble.userId && site_admins.indexOf(Number(Nabble.userId)) >= 0)
+				$('<n.selector/>').show();
+		});
+	</script>
+</macro>
+
+<macro name="alternate" parameters="var, first_value, second_value">
+	<n.if.not.global_is_var_set name="[n.var/]">
+		<then.global_set_var name="[n.var/]" value="[n.true/]" />
+	</n.if.not.global_is_var_set>
+	<n.if.global_var name="[n.var/]">
+		<then.first_value/>
+		<else.second_value/>
+	</n.if.global_var>
+	<n.global_set_var. name="[n.var/]">
+		<n.not.global_var name="[n.var/]" />
+	</n.global_set_var.>
+</macro>
+
+<macro name="add_to_path" parameters="name,default_value" dot_parameter="value">
+	<n.if.not.is_null.value>
+		<then>
+			<n.if.not.equal value1="[n.value/]" value2="[n.default_value/]">
+				<then>
+					&<n.name/>=<n.encode_text.value/>
+				</then>
+			</n.if.not.equal>
+		</then>
+	</n.if.not.is_null.value>
+</macro>
+
+<macro name="support_link">
+	<n.if.is_null.support_url>
+		<then.null/>
+		<else>
+			<a href="[n.support_url/]" target='_top'><t>Nabble Support</t></a>
+		</else>
+	</n.if.is_null.support_url>
+</macro>
+
+<macro name="important" dot_parameter="text">
+	<span class="important"><n.text/></span>
+</macro>
+
+<macro name="subject" requires="node">
+	<n.subject_impl/>
+</macro>
+
+<macro name="signature" dot_parameter="do" requires="user">
+	<n.signature_impl.do/>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/view_blog.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,171 @@
+<subroutine name="view_blog" requires="basic,nabble,servlet">
+	<n.apply_app_namespace.view_blog_page />
+</subroutine>
+
+<macro name="view_blog_page">
+	<n.set_app_rows_per_page rows_per_page="[n.blog_topics_per_page/]"/>
+	<n.app_html>
+		<head>
+			<n.app_title/>
+			<n.blog_table_stylesheet/>
+		</head>
+		<body>
+			<n.blog_page_layout/>
+		</body>
+	</n.app_html>
+</macro>
+
+<macro name="blog_topics_per_page">
+	10
+</macro>
+
+<macro name="blog_table_stylesheet">
+	<style type="text/css">
+		div.pinned {
+			position:relative;
+			z-index:999;
+			background-image:url('/images/pin.png');
+			width:20px;
+			height:21px;
+			float:right;
+		}
+		div.blog-header {
+			padding:.3em;
+			-moz-border-radius: 6px;
+			-webkit-border-radius: 6px;
+		}
+		div.blog-date {
+			text-transform:uppercase;
+			letter-spacing:.2em;
+			font-size:70%;
+			margin-bottom:.2em;
+			padding: .2em;
+		}
+		div.blog-title {
+			font-size:150%;
+			font-weight:bold;
+		}
+		table.blog-details {
+			border-collapse:collapse;
+			margin-top:.2em;
+			font-variant: small-caps;
+		}
+		div.blog-text {
+			margin-top:.7em;
+			padding:.3em;
+		}
+		div.blog-footer {
+			padding:.3em;
+			clear:both;
+		}
+	</style>
+</macro>
+
+<macro name="blog_page_layout" requires="app_namespace">
+	<n.column_layout.>
+		<n.column. width="70%">
+			<n.widget.>
+				<n.blog_table/>
+				<n.app_topic_pagination margin=".5em .3em"/>
+			</n.widget.>
+		</n.column.>
+		<n.column. width="30%">
+			<n.sidebar_widget/>
+	   </n.column.>
+	</n.column_layout.>
+</macro>
+
+<macro name="blog_page_layout" requires="narrow_app_namespace">
+	<n.new_topic_action_link text="[t]New Post[/t]"/>
+	<n.subapps_action_link text="[t]Subcategories[/t]"/>
+	<n.people_action_link/>
+	<n.options_action_menu/>
+
+	<div style="clear:both"/>
+	<n.widget.>
+		<n.blog_table/>
+		<n.app_topic_pagination margin=".5em .3em"/>
+	</n.widget.>
+	<n.forum_footer/>
+</macro>
+
+<macro name="blog_table">
+	<n.page_node.topics_list. sort="pinned-and-root-node-date" start="[n.app_index_record/]" length="[n.app_rows_per_page/]" filter="[n.app_topic_filter/]" >
+		<n.handle_empty_blog/>
+		<n.loop.current_node.>
+			<div style="margin-bottom:2.5em;clear:both">
+				<n.blog_post_header/>
+				<div class="blog-text adbayes-content">
+					<n.node_message_as_html/>
+				</div>
+				<n.blog_post_footer/>
+			</div>
+		</n.loop.current_node.>
+	</n.page_node.topics_list.>
+</macro>
+
+<macro name="blog_post_header" requires="node">
+	<div class="blog-header shaded-bg-color">
+		<n.if.is_pinned_in_loop>
+			<then>
+				<div class="pinned"></div>
+			</then>
+		</n.if.is_pinned_in_loop>
+		<div class="blog-date weak-color">
+			<n.when_created.date_only/>
+		</div>
+		<div class="blog-title second-font adbayes-content">
+			<n.node_link/>
+		</div>
+		<table class="blog-details weak-color">
+			<tr>
+				<td class="nowrap">
+					<n.owner.avatar size="small" border_class="medium-border-color"/>
+				</td>
+				<td class="nowrap">
+					<t>by <t.author.owner.name truncate="20"/></t>
+					@
+					<n.when_created.time_only/>
+					<n.if.is_in_subapp>
+						<then><t>in <t.location.italic.subapp_link_on_hover/></t></then>
+					</n.if.is_in_subapp>
+				</td>
+			</tr>
+		</table>
+	</div>
+</macro>
+
+<macro name="blog_post_footer" requires="node">
+	<div class="blog-footer">
+		<n.if.not.is_locked_topic>
+			<then>
+				<n.permalink/>
+				&nbsp;
+				<img src="/images/icon_message.png" align="absmiddle" height="14" width="15"/>
+				<n.if.has_replies>
+					<then.comments_link/>
+					<else>
+						<a href="[n.new_post_path/]" rel="nofollow"><t>Leave a comment</t></a>
+					</else>
+				</n.if.has_replies>
+			</then>
+		</n.if.not.is_locked_topic>
+	</div>
+</macro>
+
+<macro name="handle_empty_blog" requires="node_list,node_page">
+	<n.if.not.has_more_elements>
+		<then>
+			<div class="rounded light-border-color" style="border-width:2px;border-style:solid;padding:1.5em .5em">
+				<n.page_node.>
+					<n.new_topic_link>
+						<text>
+							<div class="blog-title second-font weak-color" style="margin-bottom:.5em"><t>Write Your First Post</t></div>
+							<t>Click here to make your first post</t>
+						</text>
+					</n.new_topic_link>
+				</n.page_node.>
+			</div>
+		</then>
+	</n.if.not.has_more_elements>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/view_board.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,128 @@
+<subroutine name="view_board" requires="basic,nabble,servlet">
+	<n.view_board_page/>
+</subroutine>
+
+<macro name="view_board_page">
+	<n.app_html>
+		<head>
+			<n.app_title/>
+			<n.board_table_stylesheet/>
+		</head>
+		<body>
+			<n.topics_action_link/>
+			<n.people_action_link/>
+			<n.options_action_menu/>
+
+			<n.board_table.>
+				<n.subcategories_column title="[n.current_node.node_link class='second-font category-link'/]"/>
+				<n.topic_count_column width="5em"/>
+				<n.post_count_column width="5em"/>
+				<n.last_post_column/>
+			</n.board_table.>
+
+			<n.forum_footer/>
+		</body>
+	</n.app_html>
+</macro>
+
+<macro name="board_table_stylesheet">
+	<style type="text/css">
+		table.main {
+			margin-top:.2em;
+			border-collapse:collapse;
+			width:100%;
+			border-width:1px;
+			border-style:solid;
+		}
+		table.main tr.category-row td {
+			padding: .3em .4em;
+			font-weight: bold;
+			border-width: 1px;
+			border-style: solid;
+			border-left: none;
+			border-right: none;
+		}
+		table.main tr.main-row td {
+			padding:.3em .5em;
+			border-bottom-width: 1px;
+			border-bottom-style: dotted;
+		}
+		table.main tr.main-row,
+		table.avatar-table tr {
+			vertical-align:top;
+		}
+		div.sub-forums {
+			margin-top:.8em;
+			font-size:90%;
+			clear:both;
+		}
+		a.category-link {
+			text-decoration:none;
+			font-weight:bold;
+			font-size:110%;
+		}
+	</style>
+</macro>
+
+<macro name="board_table" dot_parameter="columns">
+	<div style="clear:both"></div>
+	<table class="main medium-border-color">
+		<n.if.page_node.has_children>
+			<then>
+				<n.page_node.children_list. length="20">
+					<n.loop.>
+						<n.table_header.>
+							<tr class="category-row shaded-bg-color">
+								<n.columns/>
+							</tr>
+						</n.table_header.>
+						<n.if.current_node.has_children>
+							<then>
+								<n.current_node.children_list. length="20">
+									<n.loop.>
+										<tr class="main-row">
+											<n.columns/>
+										</tr>
+									</n.loop.>
+									<n.if.there_is_more>
+										<then>
+											<tr>
+												<td></td>
+												<td colspan="4"><n.page_node.node_link href="[n.url template='view_standard'/]" text="[t]View more[/t]"/> &raquo;</td>
+											</tr>
+										</then>
+									</n.if.there_is_more>
+								</n.current_node.children_list.>
+							</then>
+							<else>
+								<tr>
+									<td style="width:35px"></td>
+									<td style="padding:.7em .5em">
+										<n.if.current_node.is_app>
+											<then><t>No sub-forums</t></then>
+											<else><t>No replies</t></else>
+										</n.if.current_node.is_app>
+									</td>
+									<td colspan="3"></td>
+								</tr>
+							</else>
+						</n.if.current_node.has_children>
+					</n.loop.>
+					<n.if.there_is_more>
+						<then>
+							<tr>
+								<td colspan="5" style="padding:.5em"><n.page_node.node_link href="[n.url template='view_standard'/]" text="[t]More categories[/t]"/> &raquo;</td>
+							</tr>
+						</then>
+					</n.if.there_is_more>
+				</n.page_node.children_list.>
+			</then>
+			<else>
+				<tr>
+					<td colspan="4"><t>No sub-forums</t></td>
+				</tr>
+			</else>
+		</n.if.page_node.has_children>
+	</table>
+	<div style="clear:both"></div>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/view_category.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,142 @@
+<subroutine name="view_category" requires="basic,nabble,servlet">
+	<n.view_category_page/>
+</subroutine>
+
+<macro name="view_category_page">
+	<n.app_html>
+		<head>
+			<n.app_title/>
+			<n.category_table_stylesheet/>
+		</head>
+		<body>
+			<n.topics_action_link/>
+			<n.people_action_link/>
+			<n.options_action_menu/>
+
+			<n.category_table.>
+				<n.subcategories_column/>
+				<n.topic_count_column width="5em"/>
+				<n.post_count_column width="5em"/>
+				<n.last_post_column/>
+			</n.category_table.>
+
+			<n.forum_footer/>
+		</body>
+	</n.app_html>
+</macro>
+
+<macro name="category_table_stylesheet">
+	<style type="text/css">
+		table.main {
+			margin-top:.2em;
+			border-collapse:collapse;
+			width:100%;
+			border-width:1px;
+			border-style:solid;
+		}
+		table.main tr.header-row td {
+			padding: .3em .4em;
+			font-weight: bold;
+			border-bottom-width: 1px;
+			border-bottom-style: solid;
+		}
+		table.main tr.main-row td {
+			padding:.3em .5em;
+			border-bottom-width: 1px;
+			border-bottom-style: dotted;
+		}
+		table.main tr.main-row,
+		table.avatar-table tr {
+			vertical-align:top;
+		}
+		div.sub-forums {
+			margin-top:.8em;
+			font-size:90%;
+			clear:both;
+		}
+	</style>
+</macro>
+
+<macro name="category_table" dot_parameter="columns">
+	<div style="clear:both"></div>
+	<table class="main medium-border-color">
+		<n.table_header.>
+			<tr class="header-row shaded-bg-color">
+				<n.columns/>
+			</tr>
+		</n.table_header.>
+		<n.if.page_node.has_children>
+			<then>
+				<n.page_node.children_list. length="80">
+						<n.preload_messages/>
+						<n.loop.>
+							<n.current_node.>
+								<tr class="main-row [n.category_row_classes/]" node="[n.id/]">
+									<n.columns/>
+								</tr>
+							</n.current_node.>
+						</n.loop.>
+					<n.if.there_is_more>
+						<then>
+							<tr>
+								<td></td>
+								<td><n.page_node.node_link href="[n.url template='view_standard'/]" text="[t]View more[/t]"/> &raquo;</td>
+							</tr>
+						</then>
+					</n.if.there_is_more>
+				</n.page_node.children_list.>
+			</then>
+			<else>
+				<tr>
+					<td><t>Empty</t></td>
+				</tr>
+			</else>
+		</n.if.page_node.has_children>
+	</table>
+	<div style="clear:both"></div>
+
+	<n.if.page_node.has_private_subapps>
+		<then.category_privacy_js/>
+	</n.if.page_node.has_private_subapps>
+</macro>
+
+<macro name="category_row_classes" requires="node">
+	<n.if.is_private>
+		<then>
+			private private<n.id/> invisible
+		</then>
+	</n.if.is_private>
+</macro>
+
+<macro name="category_privacy_js">
+	<script type="text/javascript">
+		var url = '/template/NamlServlet.jtp?macro=category_ajax&node=<n.root_node.id/>';
+		$(document).ready(function() {
+			function showPrivateRows() {
+				$('tr.private').each(function() {
+					var nodeId = $(this).attr('node');
+					url += '&node=' + nodeId;
+				});
+				$.getScript(url);
+			}
+			if (Nabble.userId)
+				showPrivateRows();
+		});
+	</script>
+</macro>
+
+<macro name="category_ajax" requires="servlet">
+	<n.javascript_response/>
+	<n.get_parameter_values. name="node">
+		<n.loop.>
+			<n.get_node_from_id. node_id="[n.current_parameter_value/]">
+				<n.set_local_node.this_node />
+				<n.if.visitor.can_view.local_node>
+					<then>
+						$('tr.private<n.local_node.id/>').show();
+					</then>
+				</n.if.visitor.can_view.local_node>
+			</n.get_node_from_id.>
+		</n.loop.>
+	</n.get_parameter_values.>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/view_gallery.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,176 @@
+<subroutine name="view_gallery" requires="basic,nabble,servlet">
+	<n.apply_app_namespace.view_gallery_page />
+</subroutine>
+
+<macro name="view_gallery_page">
+	<n.set_app_rows_per_page rows_per_page="[n.gallery_topics_per_page/]"/>
+	<n.app_html>
+		<head>
+			<n.app_title/>
+			<n.gallery_table_stylesheet/>
+		</head>
+		<body>
+			<n.column_layout.>
+				<n.column. width="70%">
+					<n.widget.>
+						<n.gallery_table/>
+						<n.app_topic_pagination margin=".5em .3em"/>
+					</n.widget.>
+				</n.column.>
+				<n.column. width="30%">
+					<n.sidebar_widget/>
+				</n.column.>
+			</n.column_layout.>
+		</body>
+	</n.app_html>
+</macro>
+
+<macro name="gallery_topics_per_page">
+	16
+</macro>
+
+<macro name="gallery_table_stylesheet">
+	<style type="text/css">
+		table.gallery {
+			margin-top:1em;
+			width:100%;
+		}
+		table.gallery tr {
+			vertical-align:top;
+		}
+		table.gallery td {
+			padding: .5em;
+			text-align:center;
+		}
+	</style>
+</macro>
+
+<macro name="gallery_table">
+	<table class="gallery">
+		<n.page_node.topics_list. sort="pinned-and-root-node-date" start="[n.app_index_record/]" length="[n.app_rows_per_page/]" filter="[n.app_topic_filter/]" >
+			<n.handle_empty_gallery/>
+			<n.loop.>
+				<n.gallery_table_row/>
+			</n.loop.>
+		</n.page_node.topics_list.>
+	</table>
+</macro>
+
+<macro name="gallery_table_row" requires="app_namespace">
+	<tr>
+		<td style="width:33%">
+			<n.current_node.gallery_cell/>
+		</td>
+		<td style="width:33%">
+			<n.if.next_node>
+				<then.current_node.gallery_cell/>
+			</n.if.next_node>
+		</td>
+		<td style="width:33%">
+			<n.if.next_node>
+				<then.current_node.gallery_cell/>
+			</n.if.next_node>
+		</td>
+	</tr>
+</macro>
+
+<macro name="gallery_table_row" requires="narrow_app_namespace">
+	<tr>
+		<td style="width:50%">
+			<n.current_node.gallery_cell/>
+		</td>
+		<td style="width:50%">
+			<n.if.next_node>
+				<then.current_node.gallery_cell/>
+			</n.if.next_node>
+		</td>
+	</tr>
+</macro>
+
+
+<macro name="handle_empty_gallery" requires="node_list,node_page,servlet">
+	<n.if.not.has_more_elements>
+		<then.empty_gallery_row/>
+	</n.if.not.has_more_elements>
+</macro>
+
+<macro name="empty_gallery_row" requires="app_namespace">
+	<tr>
+		<td style="width:33%"><n.page_node.empty_gallery_cell/></td>
+		<td style="width:33%"></td>
+		<td style="width:33%"></td>
+	</tr>
+</macro>
+
+<macro name="empty_gallery_row" requires="narrow_app_namespace">
+	<tr>
+		<td style="width:50%"><n.page_node.empty_gallery_cell/></td>
+		<td style="width:50%"></td>
+	</tr>
+</macro>
+
+<macro name="gallery_cell">
+	<n.cell_thumbnail/>
+	<n.cell_link_and_star/>
+	<n.cell_details/>
+</macro>
+
+<macro name="cell_thumbnail">
+	<n.if.is_pinned_in_loop>
+		<then>
+			<div class="pinned-box"></div>
+		</then>
+	</n.if.is_pinned_in_loop>
+	<n.if.has_thumbnail>
+		<then.thumbnail_box/>
+		<else>
+			<span class="weak-color box-text adbayes-content">
+				<n.message.truncated size="100"/>
+			</span>
+		</else>
+	</n.if.has_thumbnail>
+</macro>
+
+<macro name="cell_link_and_star">
+	<div class="adbayes-content"><n.node_link/></div>
+</macro>
+
+<macro name="cell_details">
+	<div class="weak-color" style="font-variant: small-caps;">
+		<t>by <t.author.owner.name truncate="20"/></t>
+	</div>
+
+	<div class="weak-color" style="width:100%;font-size:80%;margin:.3em 0;font-variant: small-caps;">
+		<n.if.not.is_locked_topic>
+			<then>
+				<n.if.has_replies>
+					<then.comments_link/>
+					<else>
+						0 <t>comments</t>
+					</else>
+				</n.if.has_replies>
+			</then>
+		</n.if.not.is_locked_topic>
+		&ndash;
+		<n.views show_text="true"/>
+		<n.if.is_in_subapp>
+			<then>
+				- <t>in <t.location.italic.subapp_link_on_hover/></t>
+			</then>
+		</n.if.is_in_subapp>
+	</div>
+</macro>
+
+<macro name="thumbnail_box">
+	<span class="box">
+		<a href="[n.url/]"><img src="[n.thumbnail_url/]" style="border:none"/></a>
+	</span>
+</macro>
+
+<macro name="empty_gallery_cell" requires="node">
+	<span class="weak-color box-text">
+		<div style="position:relative;top:30%">
+			<n.new_topic_link text="[t]Click here to make your first post[/t]"/>
+		</div>
+	</span>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/view_mixed.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,273 @@
+<macro name="call_view_mixed">
+	<n.view_mixed />
+</macro>
+
+<subroutine name="view_mixed" requires="basic,nabble,servlet">
+	<n.apply_app_namespace.view_mixed_page />
+</subroutine>
+
+<macro name="view_mixed_page">
+	<n.if.not.get_node_from_parameter.has_pinned_subapps>
+		<then>
+			<n.call_view_standard/>
+			<n.exit/>
+		</then>
+	</n.if.not.get_node_from_parameter.has_pinned_subapps>
+	<n.view_mixed_html/>
+</macro>
+
+<macro name="view_mixed_html">
+	<n.app_html>
+		<head>
+			<n.app_title/>
+			<n.mixed_table_stylesheet/>
+		</head>
+		<body>
+			<n.new_topic_action_link/>
+			<n.topics_action_link/>
+			<n.people_action_link/>
+			<n.options_action_menu/>
+
+			<n.mixed_table.mixed_table_columns />
+			<n.forum_footer/>
+		</body>
+	</n.app_html>
+</macro>
+
+<macro name="mixed_table_columns" requires="app_namespace">
+	<n.pin_column/>
+	<n.mixed_topics_column/>
+	<n.replies_column/>
+	<n.last_post_column white_space="nowrap"/>
+	<n.views_column/>
+</macro>
+
+<macro name="mixed_table_columns" requires="narrow_app_namespace">
+	<n.pin_column/>
+	<n.mixed_topics_column/>
+	<n.replies_column/>
+	<n.last_post_column white_space="nowrap"/>
+</macro>
+
+<macro name="mixed_table_stylesheet">
+	<style type="text/css">
+		table.main {
+			border-spacing:0;
+			width:100%;
+			border-width:1px;
+			border-style:solid;
+		}
+		table.main td {
+			padding:.1em;
+			height:2.2em;
+		}
+		tr.header-row td {
+			font-weight:bold;
+			padding: .2em .2em .1em;
+			border-bottom-width: 1px;
+			border-bottom-style: solid;
+		}
+		tr.category-row td {
+			border-width: 1px;
+			border-style: solid;
+			border-left: none;
+			border-right: none;
+		}
+		tr.main-row td {
+			border-width: 1px;
+			border-style: dotted;
+			border-left: none;
+			border-right: none;
+			border-top:none;
+		}
+
+		tr.main-row td td {
+			border-color: transparent;
+		}
+		a.category-link {
+			text-decoration:none;
+			font-size:110%;
+		}
+		td.topics-column {
+			width:70%;
+		}
+	</style>
+</macro>
+
+<macro name="mixed_table" dot_parameter="columns">
+	<div style="clear:both"></div>
+
+	<n.global_set_var name="mixed_table_column_count" value="[n.calc_table_column_count.columns/]" />
+
+	<table class="main medium-border-color">
+		<n.string_list. values="[n.mixed_topics_per_page/]" >
+			<n.no_output.next_string/>
+			<n.child_topics_section. length="[n.current_string/]">
+				<n.columns/>
+			</n.child_topics_section.>
+
+			<n.page_node.subapps_list. filter="[n.pinned_filter/]">
+				<n.while.has_more_strings n="2">
+					<loop>
+						<n.no_output.next_string/>
+						<n.next_section. length="[n.current_string/]"><n.columns/></n.next_section.>
+					</loop>
+				</n.while.has_more_strings>
+				<n.if.has_more_strings>
+					<then.no_output.next_string/>
+				</n.if.has_more_strings>
+				<n.while.next_node>
+					<loop>
+						<n.sub_section. length="[n.current_string/]"><n.columns/></n.sub_section.>
+					</loop>
+				</n.while.next_node>
+			</n.page_node.subapps_list.>
+		</n.string_list.>
+	</table>
+	<div style="clear:both"></div>
+</macro>
+
+<macro name="mixed_topics_per_page">
+	6
+</macro>
+
+<macro name="mixed_table_column_count" dot_parameter="do">
+	<n.int>
+		<i.global_var name="mixed_table_column_count" />
+		<do.do />
+	</n.int>
+</macro>
+
+<macro name="next_section" dot_parameter="columns" parameters="length">
+	<n.if.next_node>
+		<then.sub_section. length="[n.length/]">
+			<n.columns/>
+		</then.sub_section.>
+	</n.if.next_node>
+</macro>
+
+<macro name="sub_section" dot_parameter="columns" parameters="length">
+	<n.if.current_node.can_be_viewed_by_visitor>
+		<then>
+			<n.if.current_node.type equals="gallery">
+				<then.current_node.gallery_section length="[n.length/]"/>
+				<else>
+					<n.current_node.subapp_section. length="[n.length/]">
+						<n.columns/>
+					</n.current_node.subapp_section.>
+				</else>
+			</n.if.current_node.type>
+		</then>
+	</n.if.current_node.can_be_viewed_by_visitor>
+</macro>
+
+<macro name="child_topics_section" dot_parameter="columns" parameters="length">
+	<n.if.page_node.has_child_topics>
+		<then>
+			<n.top_topics.>
+				<n.table_header.>
+					<tr class="header-row shaded-bg-color">
+						<n.columns/>
+					</tr>
+				</n.table_header.>
+				<n.page_node.children_list. length="[n.length/]" filter="[n.no_pinned_subapps_filter/]">
+					<n.loop.>
+						<tr class="main-row">
+							<n.columns/>
+						</tr>
+					</n.loop.>
+					<n.if.there_is_more>
+						<then>
+							<tr class="main-row">
+								<td>&nbsp;</td>
+								<td class="medium-border-color" colspan="[n.mixed_table_column_count.minus i='1'/]" style="padding:.6em .8em">
+									<n.page_node.node_link
+										href="[n.url template='view_standard'/]"
+										title="[t]View all messages under this sub-forum[/t]"
+										text="[t]View more[/t]"
+									/> &raquo;
+								</td>
+							</tr>
+						</then>
+					</n.if.there_is_more>
+				</n.page_node.children_list.>
+			</n.top_topics.>
+		</then>
+	</n.if.page_node.has_child_topics>
+</macro>
+
+<macro name="gallery_section" requires="node" parameters="length">
+	<tr class="header-row category-row shaded-bg-color">
+		<n.table_header.>
+			<td class="medium-border-color"></td>
+			<n.topics_column title="[n.node_link class='second-font category-link'/]" count="[n.topic_count/]"/>
+			<td class="medium-border-color" colspan="[n.mixed_table_column_count.minus i='1'/]"></td>
+		</n.table_header.>
+	</tr>
+	<tr class="main-row">
+		<td colspan="[n.mixed_table_column_count/]" class="medium-border-color" style="width:auto;overflow:hidden">
+			<n.slider.>
+				<n.topics_list. sort="pinned-and-last-node-date" length="[n.length/]" filter="[n.app_topic_filter/]">
+					<n.loop.>
+						<td align="center" style="padding:0 .5em">
+							<n.current_node.gallery_cell/>
+						</td>
+					</n.loop.>
+					<n.set_var. name="last_loop_has_more"><n.there_is_more/></n.set_var.>
+				</n.topics_list.>
+			</n.slider.>
+		</td>
+	</tr>
+	<n.subapp_section_extra_row has_more="[n.var name='has_more_rows'/]"/>
+</macro>
+
+<macro name="subapp_section" requires="node" dot_parameter="columns" parameters="length">
+	<tr class="header-row category-row shaded-bg-color">
+		<n.table_header.>
+			<n.columns/>
+		</n.table_header.>
+	</tr>
+	<n.if.has_children>
+		<then>
+			<n.children_list. length="[n.length/]">
+				<n.loop.>
+					<tr class="main-row">
+						<n.columns/>
+					</tr>
+				</n.loop.>
+				<n.set_var. name="has_more_rows"><n.there_is_more/></n.set_var.>
+			</n.children_list.>
+			<n.subapp_section_extra_row has_more="[n.var name='has_more_rows'/]"/>
+		</then>
+		<else.subapp_section_extra_row has_more="[n.false/]"/>
+	</n.if.has_children>
+</macro>
+
+<macro name="subapp_section_extra_row" parameters="has_more">
+	<tr>
+		<td>&nbsp;</td>
+		<td colspan="[n.mixed_table_column_count.minus i='1'/]" style="padding:.6em .8em">
+			<n.separate>
+				<text1>
+					<n.new_topic_link
+						text="[t]New Topic[/t]"
+						title="[t]Post new message in [t.location.subject/][/t]"
+					/>
+				</text1>
+				<separator>
+					<span class="weak-color" style="padding: 0 .5em">&bull;</span>
+				</separator>
+				<text2>
+					<n.if.equal value1="[n.has_more/]" value2="[n.true/]">
+						<then>
+							<n.node_link
+								title="[t]View all messages under this sub forum[/t]"
+								text="[t]View more[/t]"
+							/> &raquo;
+						</then>
+					</n.if.equal>
+				</text2>
+			</n.separate>
+		</td>
+	</tr>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/view_news.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,227 @@
+<subroutine name="view_news" requires="basic,nabble,servlet">
+	<n.apply_app_namespace.view_news_page />
+</subroutine>
+
+<macro name="view_news_page">
+	<n.set_app_rows_per_page rows_per_page="[n.news_topics_per_page/]"/>
+	<n.app_html>
+		<head>
+			<n.app_title/>
+			<n.news_table_stylesheet/>
+		</head>
+		<body>
+			<n.news_page_layout/>
+		</body>
+	</n.app_html>
+</macro>
+
+<macro name="news_topics_per_page">
+	25
+</macro>
+
+<macro name="news_table_stylesheet">
+	<style type="text/css">
+		span.topic-count {
+			font-size:80%;
+			margin-right:1.7em;
+		}
+		h2.news-header {
+			margin:0;
+			padding:0;
+		}
+		table.news-table {
+			width:100%;
+			border-collapse:collapse;
+		}
+		table.news-table td.header {
+			padding: .2em .3em;
+			border-bottom-width: 1px;
+			border-bottom-style: solid;
+		}
+		td.news-row {
+			padding:.7em .1em .6em .5em;
+			border-width: 1px;
+			border-style: solid;
+			border-left: none;
+			border-right: none;
+			border-top: none;
+		}
+		td.thumbnail-cell {
+			width:140px;
+			text-align:center;
+			padding-top:1em;
+			vertical-align:top;
+		}
+		div.news-details {
+			text-align:right;
+			clear:both;
+			font-variant: small-caps;
+			padding:.5em 0 0 0;
+		}
+		div.news-title h2 {
+			display:inline;
+		}
+	</style>
+</macro>
+
+<macro name="news_page_layout" requires="app_namespace">
+	<n.column_layout.>
+		<n.column. width="70%">
+			<n.news_sidebar_widget/>
+		</n.column.>
+		<n.column. width="30%">
+			<n.sidebar_widget/>
+	   </n.column.>
+	</n.column_layout.>
+</macro>
+
+<macro name="news_page_layout" requires="narrow_app_namespace">
+	<n.new_topic_action_link text="[t]New Post[/t]"/>
+	<n.subapps_action_link text="[t]Subcategories[/t]"/>
+	<n.people_action_link/>
+	<n.options_action_menu/>
+
+	<div style="clear:both"/>
+	<n.widget.>
+		<div class="sidebar-section shaded-bg-color">
+			<n.app_topic_pagination margin=".2em .5em"/>
+			<h2 class="news-header">
+				<t>Topics</t>
+				<span class="weak-color topic-count">
+					(<n.app_topic_count/>)
+				</span>
+			</h2>
+		</div>
+		<n.news_table/>
+		<n.app_topic_pagination margin=".5em .3em"/>
+	</n.widget.>
+	<n.forum_footer/>
+</macro>
+
+<macro name="news_sidebar_widget">
+	<n.widget.>
+		<div class="sidebar-section shaded-bg-color">
+			<n.app_topic_pagination margin=".2em .5em"/>
+			<h2 class="news-header">
+				<t>Topics</t>
+				<span class="weak-color topic-count">
+					(<n.app_topic_count/>)
+				</span>
+			</h2>
+		</div>
+		<n.news_table/>
+		<n.app_topic_pagination margin=".5em .3em"/>
+	</n.widget.>
+</macro>
+
+<macro name="news_table">
+	<table id="news-table" class="news-table medium-border-color">
+		<n.page_node.topics_list.
+			sort="pinned-and-root-node-date"
+			start="[n.app_index_record/]"
+			length="[n.app_rows_per_page/]"
+			filter="[n.app_topic_filter/]"
+		>
+			<n.handle_empty_newspaper/>
+			<n.loop.current_node.>
+				<tr>
+					<n.if.has_thumbnail>
+						<then>
+							<td class="news-row thumbnail-cell light-border-color">
+								<n.thumbnail_box/>
+							</td>
+							<td class="news-row light-border-color" style="vertical-align:top">
+								<n.news_text_cell/>
+							</td>
+						</then>
+						<else>
+							<td class="news-row light-border-color" style="vertical-align:top" colspan="2">
+								<n.news_text_cell/>
+							</td>
+						</else>
+					</n.if.has_thumbnail>
+				</tr>
+			</n.loop.current_node.>
+		</n.page_node.topics_list.>
+	</table>
+</macro>
+
+<macro name="news_text_cell" requires="node">
+	<n.news_title_row/>
+	<n.news_details_row/>
+	<n.news_snippet_row/>
+</macro>
+
+<macro name="news_title_row" requires="node">
+	<div class="news-title">
+		<h2 class="news-title adbayes-content">
+			<n.node_link/>
+		</h2>
+		<n.if.is_in_subapp>
+			<then>
+				<t>in <t.location.italic.subapp_link_on_hover/></t>
+			</then>
+		</n.if.is_in_subapp>
+	</div>
+</macro>
+
+<macro name="news_snippet_row" requires="node">
+	<div class="node-snippet adbayes-content" style="padding:1em 0 .5em;clear:both">
+		<n.truncate size="300">
+			<text><n.remove_html_tags.message.as_text/></text>
+			<if_truncated>
+				<n.news_snippet_read_more_link/>
+			</if_truncated>
+		</n.truncate>
+	</div>
+</macro>
+
+<macro name="news_snippet_read_more_link" requires="node">
+	<n.node_link text="[t]read more[/t]"/>
+</macro>
+
+<macro name="news_details_row" requires="node">
+	<div class="news-details weak-color">
+		<span class="nowrap">
+			<t>by <t.author.owner.name truncate="20"/></t>
+			&bull;
+		</span>
+		<span class="nowrap"><n.when_created.short_format/></span>
+		<span>|</span>
+		<span class="nowrap"><n.views show_text="true"/></span>
+		<n.if.not.is_locked_topic>
+			<then>
+				<span>|</span>
+				<span class="nowrap">
+					<img src="/images/icon_message.png" align="absmiddle" height="14" width="15"/>
+					<n.if.has_replies>
+						<then><n.comments_link/></then>
+						<else>0 <t>comments</t></else>
+					</n.if.has_replies>
+				</span>
+			</then>
+		</n.if.not.is_locked_topic>
+		<n.if.is_pinned_in_loop>
+			<then><span><img src="/images/pin.png" width="20" height="21" style="margin:0 .5em" align="absmiddle"/></span></then>
+		</n.if.is_pinned_in_loop>
+	</div>
+</macro>
+
+<macro name="handle_empty_newspaper" requires="node_list,node_page">
+	<n.if.not.has_more_elements>
+		<then>
+			<div class="light-border-color" style="border-width:2px;border-style:solid;padding:1.5em .5em">
+				<n.page_node.>
+					<n.new_topic_link>
+						<text>
+							<div class="second-font weak-color" style="font-size:150%;font-weight:bold;margin-bottom:.5em">
+								<t>Write Your First Headline</t>
+							</div>
+							<t>Click here to make your first post</t>
+						</text>
+					</n.new_topic_link>
+				</n.page_node.>
+			</div>
+		</then>
+	</n.if.not.has_more_elements>
+</macro>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/view_standard.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,115 @@
+<macro name="call_view_standard">
+	<n.view_standard />
+</macro>
+
+<subroutine name="view_standard" requires="basic,nabble,servlet">
+	<n.apply_app_namespace.view_standard_page />
+</subroutine>
+
+<macro name="view_standard_page">
+	<n.set_app_rows_per_page rows_per_page="[n.forum_topics_per_page/]"/>
+	<n.app_html>
+		<head>
+			<n.app_title />
+			<n.standard_table_stylesheet/>
+		</head>
+		<body>
+			<n.new_topic_action_link/>
+			<n.topics_action_link only_if_has_subapps="true"/>
+			<n.people_action_link/>
+			<n.options_action_menu/>
+
+			<n.app_child_pagination margin=".8em .3em .5em 0"/>
+			<n.standard_table.standard_table_columns />
+			<n.app_child_pagination margin=".8em .3em .5em 0"/>
+			<n.forum_footer/>
+		</body>
+	</n.app_html>
+</macro>
+
+<macro name="forum_topics_per_page">
+	35
+</macro>
+
+<macro name="standard_table_columns" requires="app_namespace">
+	<n.pin_column/>
+	<n.topics_column title="[n.standard_topics_column_title/]" count="[n.page_node.child_count/]"/>
+	<n.replies_column/>
+	<n.last_post_column white_space="nowrap"/>
+	<n.views_column/>
+</macro>
+
+<macro name="standard_table_columns" requires="narrow_app_namespace">
+	<n.pin_column/>
+	<n.topics_summary_column title="[n.standard_topics_column_title/]" count="[n.page_node.child_count/]" width="50%"/>
+	<n.last_post_column white_space="nowrap" width="50%"/>
+</macro>
+
+<macro name="standard_topics_column_title">
+    <n.if.page_node.has_subapps>
+        <then><t>Sub-Forums & Topics</t></then>
+        <else><t>Topics</t></else>
+    </n.if.page_node.has_subapps>
+</macro>
+
+<macro name="standard_table_stylesheet">
+	<style type="text/css">
+		table.main {
+			width:100%;
+			border-width: 1px;
+			border-style: solid;
+			border-collapse:collapse;
+		}
+		table.main td {
+			padding:.1em;
+			height:2.2em;
+		}
+		tr.header-row td {
+			font-weight:bold;
+			padding: .1em .2em;
+			border-bottom-width: 1px;
+			border-bottom-style: solid;
+		}
+	</style>
+</macro>
+
+<macro name="standard_table" dot_parameter="columns">
+	<div style="clear:both"></div>
+	<table class="main medium-border-color">
+		<n.table_header.>
+			<tr class="header-row shaded-bg-color">
+				<n.columns/>
+			</tr>
+		</n.table_header.>
+		<n.page_node.children_list.
+			start="[n.app_index_record/]"
+			length="[n.app_rows_per_page/]"
+			filter="[n.app_topic_filter/]"
+			sort="[n.if.app_is_by_priority][then]priority[/then][else]pinned-and-last-node-date[/else][/n.if.app_is_by_priority]"
+		>
+			<n.loop.>
+				<tr class="[n.even_row_background/] main-row">
+					<n.columns/>
+				</tr>
+				<n.if.next_node>
+					<then>
+						<tr class="[n.odd_row_background/] main-row">
+							<n.columns/>
+						</tr>
+					</then>
+				</n.if.next_node>
+			</n.loop.>
+		</n.page_node.children_list.>
+		<n.if.not.page_node.has_children>
+			<then>
+				<tr>
+					<td></td>
+					<td colspan="10" style="padding:.8em 0">
+						<t>Empty</t>
+					</td>
+				</tr>
+			</then>
+		</n.if.not.page_node.has_children>
+	</table>
+	<div style="clear:both"></div>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/view_topics.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,110 @@
+<macro name="call_view_topics">
+	<n.view_topics />
+</macro>
+
+<subroutine name="view_topics" requires="basic,nabble,servlet">
+	<n.apply_app_namespace.view_topics_page />
+</subroutine>
+
+<macro name="view_topics_page">
+	<n.set_app_rows_per_page rows_per_page="[n.forum_topics_per_page/]"/>
+	<n.app_html>
+		<head>
+			<n.app_title/>
+			<n.topics_table_stylesheet/>
+		</head>
+		<body>
+			<n.new_topic_action_link/>
+			<n.subapps_action_link/>
+			<n.people_action_link/>
+			<n.options_action_menu/>
+
+			<n.app_topic_pagination margin=".8em .3em .5em 0"/>
+			<n.topics_table.topics_table_columns />
+			<n.app_topic_pagination margin=".8em .3em .5em 0"/>
+			<n.forum_footer/>
+		</body>
+	</n.app_html>
+</macro>
+
+<macro name="topics_table_columns" requires="app_namespace">
+	<n.pin_column/>
+	<n.topics_column count="[n.app_topic_count/]"/>
+	<n.replies_column/>
+	<n.last_post_column white_space="nowrap"/>
+	<n.views_column/>
+	<n.subapp_column/>
+</macro>
+
+<macro name="topics_table_columns" requires="narrow_app_namespace">
+	<n.pin_column/>
+	<n.topics_summary_column count="[n.app_topic_count/]" width="50%"/>
+	<n.last_post_column white_space="nowrap" width="50%"/>
+</macro>
+
+<macro name="topics_table_stylesheet">
+	<style type="text/css">
+		table.main {
+			width:99.9%;
+			border-width: 1px;
+			margin:0 1px;
+			border-style: solid;
+			border-collapse:collapse;
+		}
+		table.main td {
+			padding:.1em;
+			height:2.2em;
+		}
+		tr.header-row td {
+			font-weight:bold;
+			padding: .1em .2em;
+			border-bottom-width: 1px;
+			border-bottom-style: solid;
+		}
+	</style>
+</macro>
+
+<macro name="topics_table" dot_parameter="columns">
+	<div style="clear:both"></div>
+	<table class="main medium-border-color">
+		<n.table_header.>
+			<tr class="header-row shaded-bg-color">
+				<n.columns/>
+			</tr>
+		</n.table_header.>
+		<n.page_node.topics_list.
+			start="[n.app_index_record/]"
+			length="[n.app_rows_per_page/]"
+			sort="[n.topics_table_sort/]"
+			filter="[n.app_topic_filter/]"
+		>
+			<n.loop.>
+				<tr class="[n.even_row_background/] main-row">
+					<n.columns/>
+				</tr>
+				<n.if.next_node>
+					<then>
+						<tr class="[n.odd_row_background/] light-bg-color main-row">
+							<n.columns/>
+						</tr>
+					</then>
+				</n.if.next_node>
+			</n.loop.>
+		</n.page_node.topics_list.>
+		<n.if.not.page_node.has_topics filter="[n.app_topic_filter/]">
+			<then>
+				<tr>
+					<td></td>
+					<td colspan="10" style="padding:.8em 0">
+						<t>Empty</t>
+					</td>
+				</tr>
+			</then>
+		</n.if.not.page_node.has_topics>
+	</table>
+	<div style="clear:both"></div>
+</macro>
+
+<macro name="topics_table_sort">
+	pinned-and-last-node-date
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/naml/widget.naml	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,246 @@
+<macro name="sidebar_widget" requires="node_page,servlet">
+	<n.actions_widget/>
+	<n.people_widget/>
+	<n.subscribe_widget/>
+	<n.categories_widget/>
+	<n.archives_widget/>
+</macro>
+
+<macro name="people_widget">
+	<n.put_in_head.>
+		<style type="text/css">
+			a.people-small-link {
+				float:right;
+				font-size:80%;
+				padding:.1em .3em;
+			}
+		</style>
+	</n.put_in_head.>
+	<n.site_users. length="50">
+		<n.filter_by.current_user.can_be_displayed_in.page_node/>
+		<n.if.has_more_elements n="2">
+			<then>
+				<n.widget.>
+					<h2 class="sidebar-section shaded-bg-color">
+						<n.page_node.people_link text="[t]All Users[/t]" class="people-small-link"/>
+						<t>People</t>
+					</h2>
+					<div id="recent-posters">
+						<n.sub_list. start="0" length="12">
+							<n.people_widget_table/>
+						</n.sub_list.>
+					</div>
+				</n.widget.>
+			</then>
+		</n.if.has_more_elements>
+	</n.site_users.>
+</macro>
+
+<macro name="people_widget_table" requires="user_list">
+	<table style="width:100%">
+		<n.loop.>
+			<tr>
+				<td><n.current_user.avatar size="small"/></td>
+				<n.if.next_user>
+					<then><td><n.current_user.avatar size="small"/></td></then>
+				</n.if.next_user>
+				<n.if.next_user>
+					<then><td><n.current_user.avatar size="small"/></td></then>
+				</n.if.next_user>
+				<n.if.next_user>
+					<then><td><n.current_user.avatar size="small"/></td></then>
+				</n.if.next_user>
+			</tr>
+		</n.loop.>
+	</table>
+</macro>
+
+<macro name="actions_widget" requires="node_page">
+	<n.widget.>
+		<table class="actions" style="border-collapse:collapse;width:100%">
+			<n.new_topic_action_row/>
+			<n.app_dropdown_action_row/>
+			<n.app_views_action_row/>
+			<n.app_feeds_action_row/>
+		</table>
+	</n.widget.>
+</macro>
+
+<macro name="new_topic_action_row" requires="node_page">
+	<tr>
+		<td style="width:20px">
+			<img src="/images/icon_post_message.png" class="image16" border="0"/>
+		</td>
+		<td class="nowrap" style="padding-left:.2em">
+			<n.page_node.new_topic_link
+				text="[t]New Post[/t]"
+				title="[t]Post new message in [t.location.page_node.subject/][/t]"
+			/>
+		</td>
+	</tr>
+</macro>
+
+<macro name="app_dropdown_action_row" requires="node_page">
+	<tr>
+		<td style="width:20px;padding-top:.5em">
+			<img src="/images/gear.png" class="image16" border="0" />
+		</td>
+		<td class="nowrap" style="padding:.5em 0 0 .2em">
+			<n.page_node.app_dropdown/>
+		</td>
+	</tr>
+</macro>
+
+<macro name="app_views_action_row" requires="node_page">
+	<tr>
+		<td style="width:20px;padding-top:.5em">
+			<img src="/images/eye.png" class="image16"/>
+		</td>
+		<td class="nowrap" style="padding:.5em 0 0 .2em">
+			<n.page_node.views show_text="true"/>
+		</td>
+	</tr>
+</macro>
+
+<macro name="app_feeds_action_row" requires="node_page">
+	<tr>
+		<td style="width:20px;padding-top:.5em">
+			<img src="/images/feeds.png" width="16" height="16" border="0" alt="feeds"/>
+		</td>
+		<td class="nowrap" style="padding:.5em 0 0 .2em">
+			<n.page_node.feeds_link/>
+		</td>
+	</tr>
+</macro>
+
+<macro name="subscribe_widget">
+	<n.widget.>
+		<div id="subscribe-widget" class="invisible">
+			<h2 class="sidebar-section shaded-bg-color"><t>Subscribe via email</t></h2>
+			<ul id="subscribe-widget-form" class="sidebar-section">
+				<n.form. macro="subscribe">
+					<input type="hidden" name="node" value="[n.page_node.id/]"/>
+					<input type="hidden" name="action" value="send-anonymous"/>
+					<input type="hidden" name="subscription_to" value="CHILDREN"/>
+					<table>
+						<tr>
+							<td><b><t>Email</t></b></td>
+							<td><input type="text" size="25" maxlength="80" name="email"/></td>
+						</tr>
+						<tr>
+							<td></td>
+							<td><input type="submit" value="[t]Subscribe[/t]" style="margin-top:.5em"/></td>
+						</tr>
+					</table>
+				</n.form.>
+			</ul>
+		</div>
+		<script type="text/javascript">
+			if (!Nabble.userId)
+				Nabble.get('subscribe-widget').style.display = 'block';
+		</script>
+	</n.widget.>
+</macro>
+
+<macro name="categories_widget">
+	<n.widget.>
+		<n.if.page_node.has_subapps>
+			<then>
+				<h2 class="sidebar-section shaded-bg-color"><t>Subcategories</t></h2>
+				<ul class="sidebar-section">
+					<n.page_node.subapps_list.loop.>
+						<li class="medium-border-color"><n.current_node.node_link.url/></li>
+					</n.page_node.subapps_list.loop.>
+				</ul>
+				<n.separator/>
+			</then>
+		</n.if.page_node.has_subapps>
+	</n.widget.>
+</macro>
+
+<macro name="archives_widget" requires="node_page,servlet">
+	<n.widget.>
+		<h2 class="sidebar-section shaded-bg-color"><t>Archives</t></h2>
+		<ul class="sidebar-section">
+			<n.page_node.monthly_archives.>
+				<li class="medium-border-color">
+					<a href="[n.page_node.path/]"><t>All</t></a>
+					<span class="weak-color">(<n.total/>)</span>
+				</li>
+
+				<n.year_list.loop.>
+					<li class="medium-border-color">
+						<div class="year bold">
+							<span>&#9656;</span>
+							<n.current_year.year/>
+						</div>
+						<div style="margin:.3em 0 .3em 1em; display:none">
+							<n.current_year.month_list.loop.>
+								<n.set_var. name="year-month"><n.current_month.year/><n.current_month.month/></n.set_var.>
+								<n.set_var. name="class">
+									<n.if.equal value1="[n.var name='year-month'/]" value2="[n.app_date/]">
+										<then>highlight</then>
+									</n.if.equal>
+								</n.set_var.>
+								<n.set_var. name="archive-url">
+									<n.page_node.app_path_by_date date="[n.var name='year-month'/]"/>
+								</n.set_var.>
+								<div class="[n.var name='class'/]" style="margin:.2em 0; padding: .2em .3em">
+									<a href="[n.var name='archive-url'/]" rel="nofollow"><n.current_month.archive_text/></a>
+									<span class="weak-color">(<n.current_month.count/>)</span>
+								</div>
+							</n.current_year.month_list.loop.>
+						</div>
+					</li>
+				</n.year_list.loop.>
+			</n.page_node.monthly_archives.>
+		</ul>
+		<script type="text/javascript">
+			$(document).ready(function() {
+				function collapse(e) {
+					$(e).children().first().html('&#9656;');
+					$(e).next().slideUp();
+				};
+				function expand(e) {
+					$(e).children().first().html('&#9662;');
+					$(e).next().slideDown();
+				};
+				var $years = $('.year');
+				$years.css('cursor', 'pointer');
+				var expanded = false;
+				$years.each(function() {
+					var isOpen = $('div.highlight', $(this).next()).size() > 0;
+					if (isOpen) {
+						expand(this);
+						expanded = true;
+					}
+					$(this).click(function() {
+						var isOpen = $(this).next().is(':visible');
+						if (isOpen)
+							collapse(this);
+						else
+							expand(this);
+					});
+				});
+				if (!expanded) {
+					expand($years.eq(0).get());
+				}
+			});
+		</script>
+	</n.widget.>
+</macro>
+
+<macro name="archive_text" requires="month_row">
+	<n.if.equal value1="[n.month/]" value2="01"><then><t>January</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="02"><then><t>February</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="03"><then><t>March</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="04"><then><t>April</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="05"><then><t>May</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="06"><then><t>June</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="07"><then><t>July</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="08"><then><t>August</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="09"><then><t>September</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="10"><then><t>October</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="11"><then><t>November</t></then></n.if.equal>
+	<n.if.equal value1="[n.month/]" value2="12"><then><t>December</t></then></n.if.equal>
+</macro>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/Index.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,67 @@
+
+package nabble.view.web;
+
+import fschmidt.util.servlet.CanonicalUrl;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.UpdatingException;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import nabble.view.lib.UrlMappable;
+import nabble.view.lib.help.Help;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+
+public final class Index extends HttpServlet implements UrlMappable, CanonicalUrl {
+
+	private static final Logger logger = LoggerFactory.getLogger(Index.class);
+
+	private static final Pattern URL_PATTERN = Pattern.compile( "://[^/]+/$");
+
+	public static String url() {
+		return Jtp.homeContextUrl() + path();
+	}
+
+	public static String path() {
+		return "/";
+	}
+
+	public String getCanonicalUrl(HttpServletRequest request) {
+		return url();
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		return Collections.emptyMap();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\nplease set homeContextUrl\r\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/Index.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,67 @@
+<%
+package nabble.view.web;
+
+import fschmidt.util.servlet.CanonicalUrl;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.UpdatingException;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import nabble.view.lib.UrlMappable;
+import nabble.view.lib.help.Help;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+
+public final class Index extends HttpServlet implements UrlMappable, CanonicalUrl {
+
+	private static final Logger logger = LoggerFactory.getLogger(Index.class);
+
+	private static final Pattern URL_PATTERN = Pattern.compile( "://[^/]+/$");
+
+	public static String url() {
+		return Jtp.homeContextUrl() + path();
+	}
+
+	public static String path() {
+		return "/";
+	}
+
+	public String getCanonicalUrl(HttpServletRequest request) {
+		return url();
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		return Collections.emptyMap();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		please set homeContextUrl
+		<%
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/Javascript.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,51 @@
+
+package nabble.view.web;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.Site;
+import nabble.model.SystemProperties;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.lib.Jtp;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.ServletNamespace;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+
+
+public final class Javascript extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		response.setHeader("Content-Type","application/x-javascript");
+		PrintWriter out = response.getWriter();
+
+		basicNabbleFunctions(out);
+		dateFunctionsJs(out);
+	}
+
+	public static void basicNabbleFunctions(PrintWriter out) {
+		
+		out.print( "\r\nvar Nabble = new Object();\r\nNabble.main = 1;\r\n\r\nNabble.getParent = function() {\r\n	if (typeof(customParent) != 'undefined')\r\n		return customParent();\r\n	try {\r\n		if (parent.Nabble.main == 1)\r\n			return parent.parent;\r\n	} catch(err) {}\r\n	return parent;\r\n};\r\n\r\nif (!Array.prototype.indexOf) {\r\n	Array.prototype.indexOf = function(e, start) {\r\n		start = start || 0;\r\n		if (start < 0)\r\n			start += this.length;\r\n		for (var i=start; i<this.length; i++)\r\n			if (this[i] == e)\r\n				return i;\r\n		return -1;\r\n	};\r\n}\r\n\r\nNabble.get = function(id) {\r\n	return document.getElementById(id);\r\n};\r\n\r\nNabble.loadScript = function(url) {\r\n	var e = document.createElement(\"script\");\r\n	e.src = url;\r\n	e.type=\"text/javascript\";\r\n	document.getElementsByTagName(\"head\")[0].appendChild(e);\r\n};\r\n\r\nNabble.escapeHTML = function(str) {\r\n	var div = document.createElement('div');\r\n	var text = document.createTextNode(str);\r\n	div.appendChild(text);\r\n	return div.innerHTML;\r\n};\r\n\r\nNabble.escape = function(value) {\r\n	if (typeof value == 'string') {\r\n		var hasSpace = value.indexOf(' ') >= 0;\r\n		var hasQuote = value.indexOf('\"') >= 0;\r\n\r\n		value = value.replace(/\\;/g, '%3B');\r\n		value = value.replace(/\"/g, '\\\\\"');\r\n\r\n		if (hasSpace || hasQuote)\r\n			value = '\"' + value + '\"';\r\n	}\r\n	return value;\r\n};\r\n\r\nNabble.unescape = function(value) {\r\n	if (value.charAt(0) == '\"' && value.charAt(value.length-1) == '\"')\r\n		value = value.substring(1, value.length-1);\r\n\r\n	value = value.replace(/\\\\\"/g, '\"');\r\n	value = value.replace(/%3B/g, ';');\r\n	return value;\r\n};\r\n\r\nNabble.getCookie = function(name) {\r\n	var dc = document.cookie;\r\n	var prefix = name + \"=\";\r\n	var begin = dc.indexOf(\"; \" + prefix);\r\n	if (begin == -1) {\r\n		begin = dc.indexOf(prefix);\r\n		if (begin != 0) return null;\r\n	} else\r\n		begin += 2;\r\n	var end = document.cookie.indexOf(\";\", begin);\r\n	if (end == -1)\r\n		end = dc.length;\r\n	return Nabble.unescape(dc.substring(begin + prefix.length, end));\r\n};\r\n\r\nNabble.setCookie = function(name, value) {\r\n	var curCookie = name + \"=\" + Nabble.escape(value) + \";path=/\";\r\n	document.cookie = curCookie;\r\n};\r\n\r\nNabble.setPersistentCookie = function(name, value) {\r\n	var expires = new Date();\r\n	expires.setFullYear(expires.getFullYear()+10);\r\n	var curCookie = name + \"=\" + Nabble.escape(value) + \"; expires=\" + expires.toGMTString() + \"; path=/\";\r\n	document.cookie = curCookie;\r\n};\r\n\r\nNabble.deleteCookie = function(name) {\r\n	if (this.getCookie(name)) {\r\n		document.cookie = name + \"=\" +\r\n			\"; path=/\"  +\r\n			\"; expires=Thu, 01-Jan-1970 00:00:01 GMT\";\r\n	}\r\n};\r\n\r\nNabble.vars = [\"appnotice\"];\r\nNabble.pvars = [\"tview\"];  /* persistent */\r\n\r\n(function(){\r\n	for(var i=0;i<Nabble.vars.length;i++) {\r\n		var v = Nabble.vars[i];\r\n		Nabble[v] = Nabble.getCookie(v);\r\n	}\r\n	for(var i=0;i<Nabble.pvars.length;i++) {\r\n		var v = Nabble.pvars[i];\r\n		Nabble[v] = Nabble.getCookie(v);\r\n	}\r\n})();\r\n\r\nNabble.handleVars = function() {\r\n	for( var i=0; i<Nabble.vars.length; i++ ) {\r\n		var v = Nabble.vars[i];\r\n		if( Nabble[v] != Nabble.getCookie(v) ) {\r\n			Nabble.setVar(v,Nabble[v]);\r\n		}\r\n	}\r\n	for( var i=0; i<Nabble.pvars.length; i++ ) {\r\n		var v = Nabble.pvars[i];\r\n		if( Nabble[v] != Nabble.getCookie(v) ) {\r\n			Nabble.setVar(v,this[v]);\r\n		}\r\n	}\r\n};\r\n\r\nNabble.contains = function(a,v) {\r\n	for( var i=0; i<a.length; i++ ) {\r\n		if( a[i]==v )\r\n			return true;\r\n	}\r\n	return false;\r\n};\r\n\r\nNabble.setVar = function(v,val) {\r\n	Nabble[v] = val;\r\n	try {\r\n		Nabble.getParent().Nabble[v] = val;\r\n	} catch(err) {}\r\n	if(val) {\r\n		if( this.contains(this.vars,v) ) {\r\n			this.setCookie(v,val);\r\n		} else if( this.contains(this.pvars,v) ) {\r\n			this.setPersistentCookie(v,val);\r\n		} else {\r\n			throw new Error(\"var not found: \"+v);\r\n		}\r\n	} else {\r\n		this.deleteCookie(v);\r\n	}\r\n};\r\n\r\nNabble.toggle = function(id, callback) {\r\n	$('#'+id).slideToggle('slow', function(){\r\n		if (callback) callback();\r\n		Nabble.resizeFrames();\r\n	});\r\n};\r\n\r\nNabble.trim = function(s) {\r\n	return s.replace(/^\\s+|\\s+$/g,'');\r\n};\r\n\r\n/* overridden in javascript_library macro */\r\n//		Nabble.getClientID = function(){};\r\n\r\nvar userHeaderListeners = [];\r\nvar userHeaderReady = false;\r\nNabble.addUserHeaderListener = function(listener){\r\n	if (!userHeaderReady) userHeaderListeners.push(listener);\r\n	else listener();\r\n};\r\n\r\nNabble.userHeader = function() {\r\n	$(document).ready(function(){\r\n		var s = '';\r\n		if (Nabble.siteHeader)\r\n			s += Nabble.siteHeader();\r\n		$(\"#nabble-user-header\").html(s);\r\n		for(var i=0;i<userHeaderListeners.length;i++)\r\n			userHeaderListeners[i]();\r\n		userHeaderReady = true;\r\n	});\r\n};\r\n\r\nfunction notice(s, wait, fade) {\r\n	var $n = $('#notice');\r\n	$n.html(s);\r\n	var hw = $n.width()/2;\r\n	$n.css('margin-left', -hw + 'px');\r\n	$n.show();\r\n	if (wait && fade)\r\n		setTimeout(function() {\r\n			$n.fadeOut(fade);\r\n		}, wait);\r\n};\r\n\r\nfunction singleSubmit(f) {\r\n	if (f.done)\r\n		return false;\r\n	f.done = true;\r\n	return true;\r\n};\r\n\r\nNabble.analytics = function() {\r\n	if (navigator.cookieEnabled && !Nabble.getCookie(\"v\")) {\r\n		var visitCounter = \"/util/VisitCounter.jtp?referrer=\" + encodeURIComponent(document.referrer);\r\n		Nabble.loadScript(visitCounter);\r\n	}\r\n	var expires = new Date();\r\n	expires.setTime(expires.getTime()+30*60*1000);\r\n	document.cookie = \"v=x; expires=\" + expires.toGMTString() + \"; path=/\";\r\n};\r\n" );
+
+	} // end genericJs
+
+	private static void dateFunctionsJs(PrintWriter out) {
+		
+		out.print( "\r\nNabble.months = [\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"];\r\nNabble.now = new Date();\r\nNabble.fmt2 = function(i) { return i <= 9? '0'+i:i; };\r\n\r\nNabble.isToday = function(date) {\r\n	return date.toDateString() == this.now.toDateString();\r\n};\r\n\r\nNabble.isThisYear = function(date) {\r\n	return date.getYear() == this.now.getYear();\r\n};\r\n\r\nNabble.dateFormatters = {\r\n	us: new (function(){\r\n		this.formatTime = function(date) {\r\n			var hours = date.getHours();\r\n			if (hours < 12) {\r\n				var xm = \"am\";\r\n				if (hours==0)\r\n					hours = 12;\r\n			} else {\r\n				var xm = \"pm\";\r\n				if (hours > 12)\r\n					hours -= 12;\r\n			}\r\n			return hours + \":\" + Nabble.fmt2(date.getMinutes()) + xm;\r\n		};\r\n		this.formatDateOnly = function(date) {\r\n			return Nabble.months[date.getMonth()] + \" \" + Nabble.fmt2(date.getDate()) + \", \" + date.getFullYear();\r\n		};\r\n		this.formatDateLong = function(date) {\r\n			return this.formatDateOnly(date) + \"; \" + this.formatTime(date);\r\n		};\r\n		this.formatDateShort = function(date) {\r\n			if( Nabble.isToday(date) )\r\n				return this.formatTime(date);\r\n			if( Nabble.isThisYear(date) )\r\n				return Nabble.months[date.getMonth()] + \" \" + Nabble.fmt2(date.getDate());\r\n			return this.formatDateOnly(date);\r\n		};\r\n	})()\r\n	,\r\n	euro: new (function(){\r\n		this.formatTime = function(date) {\r\n			return Nabble.fmt2(date.getHours()) + \":\" + Nabble.fmt2(date.getMinutes());\r\n		};\r\n		this.formatDateOnly = function(date) {\r\n			return Nabble.fmt2(date.getDate()) + \".\" + Nabble.months[date.getMonth()] + \".\" + date.getFullYear();\r\n		};\r\n		this.formatDateLong = function(date) {\r\n			return this.formatTime(date) + \", \" + this.formatDateOnly(date);\r\n		};\r\n		this.formatDateShort = function(date) {\r\n			if( Nabble.isToday(date) )\r\n				return this.formatTime(date);\r\n			if( Nabble.isThisYear(date) )\r\n				return Nabble.fmt2(date.getDate()) + \".\" + Nabble.months[date.getMonth()];\r\n			return this.formatDateOnly(date);\r\n		};\r\n	})()\r\n	,\r\n	tech: new (function(){\r\n		this.formatTime = function(date) {\r\n			return Nabble.fmt2(date.getHours()) + \":\" + Nabble.fmt2(date.getMinutes());\r\n		};\r\n		this.formatDateOnly = function(date) {\r\n			return \"\" + date.getFullYear() + \"-\" + Nabble.fmt2(date.getMonth()+1) + \"-\" + Nabble.fmt2(date.getDate())\r\n		};\r\n		this.formatDateLong = function(date) {\r\n			return this.formatDateOnly(date) + \" \" + this.formatTime(date);\r\n		};\r\n		this.formatDateShort = function(date) {\r\n			if( Nabble.isToday(date) )\r\n				return this.formatTime(date);\r\n			if( Nabble.isThisYear(date) )\r\n				return Nabble.fmt2(date.getMonth()+1) + \"-\" + Nabble.fmt2(date.getDate());\r\n			return this.formatDateOnly(date);\r\n		};\r\n	})()\r\n};\r\n\r\nNabble.getDateFmt = function() {\r\n	var dateFmt = Nabble.getCookie(\"date_fmt\");\r\n	return dateFmt==null ? \"us\" : dateFmt;\r\n};\r\n\r\nNabble.formatDateOnly = function(date) {\r\n	return Nabble.dateFormatters[Nabble.getDateFmt()].formatDateOnly(date);\r\n};\r\n\r\nNabble.formatTimeOnly = function(date) {\r\n	return Nabble.dateFormatters[Nabble.getDateFmt()].formatTime(date);\r\n};\r\n\r\nNabble.formatDateLong = function(date) {\r\n	return Nabble.dateFormatters[Nabble.getDateFmt()].formatDateLong(date);\r\n};\r\n\r\nNabble.formatDateShort = function(date) {\r\n	var fmt = Nabble.dateFormatters[Nabble.getDateFmt()];\r\n	return '<span title=\"' + fmt.formatDateLong(date) + '\">'\r\n		+ fmt.formatDateShort(date) + '</span>';\r\n};\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/Javascript.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,369 @@
+<%
+package nabble.view.web;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.Site;
+import nabble.model.SystemProperties;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.lib.Jtp;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.ServletNamespace;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+
+
+public final class Javascript extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		response.setHeader("Content-Type","application/x-javascript");
+		PrintWriter out = response.getWriter();
+
+		basicNabbleFunctions(out);
+		dateFunctionsJs(out);
+	}
+
+	public static void basicNabbleFunctions(PrintWriter out) {
+		%>
+		var Nabble = new Object();
+		Nabble.main = 1;
+
+		Nabble.getParent = function() {
+			if (typeof(customParent) != 'undefined')
+				return customParent();
+			try {
+				if (parent.Nabble.main == 1)
+					return parent.parent;
+			} catch(err) {}
+			return parent;
+		};
+
+		if (!Array.prototype.indexOf) {
+			Array.prototype.indexOf = function(e, start) {
+				start = start || 0;
+				if (start < 0)
+					start += this.length;
+				for (var i=start; i<this.length; i++)
+					if (this[i] == e)
+						return i;
+				return -1;
+			};
+		}
+
+		Nabble.get = function(id) {
+			return document.getElementById(id);
+		};
+
+		Nabble.loadScript = function(url) {
+			var e = document.createElement("script");
+			e.src = url;
+			e.type="text/javascript";
+			document.getElementsByTagName("head")[0].appendChild(e);
+		};
+
+		Nabble.escapeHTML = function(str) {
+			var div = document.createElement('div');
+			var text = document.createTextNode(str);
+			div.appendChild(text);
+			return div.innerHTML;
+		};
+
+		Nabble.escape = function(value) {
+			if (typeof value == 'string') {
+				var hasSpace = value.indexOf(' ') >= 0;
+				var hasQuote = value.indexOf('"') >= 0;
+
+				value = value.replace(/\;/g, '%3B');
+				value = value.replace(/"/g, '\\"');
+
+				if (hasSpace || hasQuote)
+					value = '"' + value + '"';
+			}
+			return value;
+		};
+
+		Nabble.unescape = function(value) {
+			if (value.charAt(0) == '"' && value.charAt(value.length-1) == '"')
+				value = value.substring(1, value.length-1);
+
+			value = value.replace(/\\"/g, '"');
+			value = value.replace(/%3B/g, ';');
+			return value;
+		};
+
+		Nabble.getCookie = function(name) {
+			var dc = document.cookie;
+			var prefix = name + "=";
+			var begin = dc.indexOf("; " + prefix);
+			if (begin == -1) {
+				begin = dc.indexOf(prefix);
+				if (begin != 0) return null;
+			} else
+				begin += 2;
+			var end = document.cookie.indexOf(";", begin);
+			if (end == -1)
+				end = dc.length;
+			return Nabble.unescape(dc.substring(begin + prefix.length, end));
+		};
+
+		Nabble.setCookie = function(name, value) {
+			var curCookie = name + "=" + Nabble.escape(value) + ";path=/";
+			document.cookie = curCookie;
+		};
+
+		Nabble.setPersistentCookie = function(name, value) {
+			var expires = new Date();
+			expires.setFullYear(expires.getFullYear()+10);
+			var curCookie = name + "=" + Nabble.escape(value) + "; expires=" + expires.toGMTString() + "; path=/";
+			document.cookie = curCookie;
+		};
+
+		Nabble.deleteCookie = function(name) {
+			if (this.getCookie(name)) {
+				document.cookie = name + "=" +
+					"; path=/"  +
+					"; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+			}
+		};
+
+		Nabble.vars = ["appnotice"];
+		Nabble.pvars = ["tview"];  /* persistent */
+
+		(function(){
+			for(var i=0;i<Nabble.vars.length;i++) {
+				var v = Nabble.vars[i];
+				Nabble[v] = Nabble.getCookie(v);
+			}
+			for(var i=0;i<Nabble.pvars.length;i++) {
+				var v = Nabble.pvars[i];
+				Nabble[v] = Nabble.getCookie(v);
+			}
+		})();
+
+		Nabble.handleVars = function() {
+			for( var i=0; i<Nabble.vars.length; i++ ) {
+				var v = Nabble.vars[i];
+				if( Nabble[v] != Nabble.getCookie(v) ) {
+					Nabble.setVar(v,Nabble[v]);
+				}
+			}
+			for( var i=0; i<Nabble.pvars.length; i++ ) {
+				var v = Nabble.pvars[i];
+				if( Nabble[v] != Nabble.getCookie(v) ) {
+					Nabble.setVar(v,this[v]);
+				}
+			}
+		};
+
+		Nabble.contains = function(a,v) {
+			for( var i=0; i<a.length; i++ ) {
+				if( a[i]==v )
+					return true;
+			}
+			return false;
+		};
+
+		Nabble.setVar = function(v,val) {
+			Nabble[v] = val;
+			try {
+				Nabble.getParent().Nabble[v] = val;
+			} catch(err) {}
+			if(val) {
+				if( this.contains(this.vars,v) ) {
+					this.setCookie(v,val);
+				} else if( this.contains(this.pvars,v) ) {
+					this.setPersistentCookie(v,val);
+				} else {
+					throw new Error("var not found: "+v);
+				}
+			} else {
+				this.deleteCookie(v);
+			}
+		};
+
+		Nabble.toggle = function(id, callback) {
+			$('#'+id).slideToggle('slow', function(){
+				if (callback) callback();
+				Nabble.resizeFrames();
+			});
+		};
+
+		Nabble.trim = function(s) {
+			return s.replace(/^\s+|\s+$/g,'');
+		};
+
+		/* overridden in javascript_library macro */
+//		Nabble.getClientID = function(){};
+
+		var userHeaderListeners = [];
+		var userHeaderReady = false;
+		Nabble.addUserHeaderListener = function(listener){
+			if (!userHeaderReady) userHeaderListeners.push(listener);
+			else listener();
+		};
+
+		Nabble.userHeader = function() {
+			$(document).ready(function(){
+				var s = '';
+				if (Nabble.siteHeader)
+					s += Nabble.siteHeader();
+				$("#nabble-user-header").html(s);
+				for(var i=0;i<userHeaderListeners.length;i++)
+					userHeaderListeners[i]();
+				userHeaderReady = true;
+			});
+		};
+
+		function notice(s, wait, fade) {
+			var $n = $('#notice');
+			$n.html(s);
+			var hw = $n.width()/2;
+			$n.css('margin-left', -hw + 'px');
+			$n.show();
+			if (wait && fade)
+				setTimeout(function() {
+					$n.fadeOut(fade);
+				}, wait);
+		};
+
+		function singleSubmit(f) {
+			if (f.done)
+				return false;
+			f.done = true;
+			return true;
+		};
+
+		Nabble.analytics = function() {
+			if (navigator.cookieEnabled && !Nabble.getCookie("v")) {
+				var visitCounter = "/util/VisitCounter.jtp?referrer=" + encodeURIComponent(document.referrer);
+				Nabble.loadScript(visitCounter);
+			}
+			var expires = new Date();
+			expires.setTime(expires.getTime()+30*60*1000);
+			document.cookie = "v=x; expires=" + expires.toGMTString() + "; path=/";
+		};
+		<%
+	} // end genericJs
+
+	private static void dateFunctionsJs(PrintWriter out) {
+		%>
+		Nabble.months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
+		Nabble.now = new Date();
+		Nabble.fmt2 = function(i) { return i <= 9? '0'+i:i; };
+
+		Nabble.isToday = function(date) {
+			return date.toDateString() == this.now.toDateString();
+		};
+
+		Nabble.isThisYear = function(date) {
+			return date.getYear() == this.now.getYear();
+		};
+
+		Nabble.dateFormatters = {
+			us: new (function(){
+				this.formatTime = function(date) {
+					var hours = date.getHours();
+					if (hours < 12) {
+						var xm = "am";
+						if (hours==0)
+							hours = 12;
+					} else {
+						var xm = "pm";
+						if (hours > 12)
+							hours -= 12;
+					}
+					return hours + ":" + Nabble.fmt2(date.getMinutes()) + xm;
+				};
+				this.formatDateOnly = function(date) {
+					return Nabble.months[date.getMonth()] + " " + Nabble.fmt2(date.getDate()) + ", " + date.getFullYear();
+				};
+				this.formatDateLong = function(date) {
+					return this.formatDateOnly(date) + "; " + this.formatTime(date);
+				};
+				this.formatDateShort = function(date) {
+					if( Nabble.isToday(date) )
+						return this.formatTime(date);
+					if( Nabble.isThisYear(date) )
+						return Nabble.months[date.getMonth()] + " " + Nabble.fmt2(date.getDate());
+					return this.formatDateOnly(date);
+				};
+			})()
+			,
+			euro: new (function(){
+				this.formatTime = function(date) {
+					return Nabble.fmt2(date.getHours()) + ":" + Nabble.fmt2(date.getMinutes());
+				};
+				this.formatDateOnly = function(date) {
+					return Nabble.fmt2(date.getDate()) + "." + Nabble.months[date.getMonth()] + "." + date.getFullYear();
+				};
+				this.formatDateLong = function(date) {
+					return this.formatTime(date) + ", " + this.formatDateOnly(date);
+				};
+				this.formatDateShort = function(date) {
+					if( Nabble.isToday(date) )
+						return this.formatTime(date);
+					if( Nabble.isThisYear(date) )
+						return Nabble.fmt2(date.getDate()) + "." + Nabble.months[date.getMonth()];
+					return this.formatDateOnly(date);
+				};
+			})()
+			,
+			tech: new (function(){
+				this.formatTime = function(date) {
+					return Nabble.fmt2(date.getHours()) + ":" + Nabble.fmt2(date.getMinutes());
+				};
+				this.formatDateOnly = function(date) {
+					return "" + date.getFullYear() + "-" + Nabble.fmt2(date.getMonth()+1) + "-" + Nabble.fmt2(date.getDate())
+				};
+				this.formatDateLong = function(date) {
+					return this.formatDateOnly(date) + " " + this.formatTime(date);
+				};
+				this.formatDateShort = function(date) {
+					if( Nabble.isToday(date) )
+						return this.formatTime(date);
+					if( Nabble.isThisYear(date) )
+						return Nabble.fmt2(date.getMonth()+1) + "-" + Nabble.fmt2(date.getDate());
+					return this.formatDateOnly(date);
+				};
+			})()
+		};
+
+		Nabble.getDateFmt = function() {
+			var dateFmt = Nabble.getCookie("date_fmt");
+			return dateFmt==null ? "us" : dateFmt;
+		};
+
+		Nabble.formatDateOnly = function(date) {
+			return Nabble.dateFormatters[Nabble.getDateFmt()].formatDateOnly(date);
+		};
+
+		Nabble.formatTimeOnly = function(date) {
+			return Nabble.dateFormatters[Nabble.getDateFmt()].formatTime(date);
+		};
+
+		Nabble.formatDateLong = function(date) {
+			return Nabble.dateFormatters[Nabble.getDateFmt()].formatDateLong(date);
+		};
+
+		Nabble.formatDateShort = function(date) {
+			var fmt = Nabble.dateFormatters[Nabble.getDateFmt()];
+			return '<span title="' + fmt.formatDateLong(date) + '">'
+				+ fmt.formatDateShort(date) + '</span>';
+		};
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/ads/ads.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,9 @@
+div[ads] {
+	text-align: center;
+	margin-bottom: 8px;
+}
+
+div[ads] span {
+	background-color: #F2F2F2;
+	padding: 3px;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/ads/ads.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,2 @@
+var adDiv = document.querySelector('div[ads]');
+adDiv.innerHTML = "<span>Sponsored by <a href='http://www.singlesushi.com/?nabble' rel='nofollow'>Single Sushi - International dating that really works</a></span>";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/app/Addons.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,221 @@
+
+package nabble.view.web.app;
+
+import fschmidt.db.DbDatabase;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+
+public final class Addons extends HttpServlet {
+
+	private static class ModuleInfo {
+		String moduleName;
+		String name;
+		String description;
+	}
+
+	private static Map<String, List<ModuleInfo>> modulesByCategory = new LinkedHashMap<String, List<ModuleInfo>>();
+
+	private static String firstTab;
+
+	private static void addModule(String category, String moduleName, String name, String description) {
+		if (firstTab == null)
+			firstTab = category;
+		List<ModuleInfo> list = modulesByCategory.get(category);
+		if (list == null) {
+			list = new ArrayList<ModuleInfo>();
+			modulesByCategory.put(category, list);
+		}
+		ModuleInfo info = new ModuleInfo();
+		info.moduleName = moduleName;
+		info.name = name;
+		info.description = description;
+		list.add(info);
+	}
+
+	static {
+		addModule("Social", "social_dropdown_links", "Social dropdown links", "Show social links under the \"More\" menu of posts. With these links, users will be able to easily post to Twitter, Facebook, Delicious, Stumble Upon, LinkedIn, Google Bookmarks and Digg, which helps promoting your site.");
+		addModule("Social", "social_google_plus_one", "Google +1 button", "Show the Google +1 button on the top of the topic page.<br/><a href=\"http://www.google.com/+1/button/\" rel=\"nofollow\">Learn more</a>");
+		addModule("Social", "social_facebook_like", "Facebook like button", "Show the Facebook Like button on the top of the topic page. <br/><a href=\"http://developers.facebook.com/docs/reference/plugins/like/\" rel=\"nofollow\">Learn more</a>");
+		addModule("Social",  "social_tweet", "Tweet button", "Show Twitter / Tweet button on the top of the topic page. <br/><a href=\"http://twitter.com/about/resources/tweetbutton\" rel=\"nofollow\">Learn more</a>");
+
+		addModule("Content",  "content_noindex", "Hide contents from search engines", "Select this checkbox if you want to prevent Google and other search engines from indexing the contents of this application.");
+		addModule("Content",  "content_open_links_in_new_window", "Open links in new window", "Links posted by users will open in a different tab or window.");
+		addModule("Content",  "content_smart_cache", "Smart application pages",
+			"If your application has private sub-forums (or any other private sub-app), you can use this feature to make app pages always display the contents that the user can view. "+
+			"In other words, if a user has access to a private sub-forum, he/she will be able to view the private contents from the top-level app. "+
+			"Other users (without access) will only view the public contents. "+
+			"<b>Note</b>: this feature may slow down your app pages a little bit because the system must generate them from scratch for each user. You will probably not notice any difference if your application is small (e.g., less than 5,000 topics).");
+		addModule("Content",  "content_news_summary", "Custom summary for Newspaper",
+			"If you use the Newspaper application type, you can use this add-on to define custom snippets for your posts "+
+			"(the snippet is the text that shows on the front page of the app, immediately below the title of the post). " +
+			"You can specify the snippet directly in the post message like this: \"{summary_start} Here is your summary text {summary_end} Here is the post message.\" "+
+			"The text between the tags is the snippet to be displayed on the news page and will NOT appear on the post page."
+		);
+		addModule("Content",  "forum_avatars", "Show avatars on forum page", "Shows the avatar for the first and last post of each topic on the forum page.");
+		addModule("Content",  "ads", "Show ads", "Support Nabble by allowing us to show ads.");
+
+		addModule("Email & Notifications",  "email_registration_notification", "Notify me when someone registers", "When a user completes the registration process, an email is sent to all administrators with the details of the user (e.g., username, email, link to profile page).");
+
+		addModule("Mailing List Archives",  "expire_old_threads", "Lock old threads", "This add-on prevents new replies to old threads, which could be confusing to the mailing list users. Users that try to reply to an old thread will see a message that suggests them to create a new topic instead. By default, a thread is locked if it was created more than 365 days ago. You can change this number by tweaking the \"thread_expiration_days\" macro. ");
+
+		addModule("Privacy & Law",  "cookie_policy", "Cookie Law Compliance Solution", "Add-on for obtaining a user's explicit consent for the use of cookies on their browsers. This is required by some countries (mainly UK and EU) and visitors must explicitly agree with the E-Privacy Directive before having cookies read from/written to their browsers. The consent is requested just once. By default, the consent is requested to UK visitors only, but you can change the list of countries with NAML (i.e., override the <i>cookie_countries</i> macro).");
+
+		addModule("Responsiveness",  "responsive", "Responsive layout", "Add-on that improves the responsiveness of Nabble apps and make pages more readable on small screens.");
+		addModule("Responsiveness",  "mobile", "Better new post interface for mobile users", "Add-on that provides a better \"new topic\" and \"reply\" interfaces for mobile users. Posting new messages from cell phones (or any device with a small screen) will be easier with this module. Only mobile users will see the optimized pages (other users will still see the default new topic and reply pages).");
+
+		addModule("Navigation",  "thread_navigation", "Show links to next/previous threads", "This add-on adds \"Previous Thread\" and \"Next Thread\" links to the top of each thread page. Navigation through the threads becomes easier since users don't have to return to the front page in order to visit other threads.");
+
+		addModule("Work", "workgroup", "Workgroup",
+			"This feature allows a team to work integrated with a forum. " +
+			"Basically, a user can assign a topic to another user with a priority that ranges from 1 (highest) to 5 (lowest).This feature allows a team to work integrated with a forum." +
+			"Each topic represents a task that holds the discussion about the work to be accomplished.<br/>" +
+			"<a href=\"http://nabble-support.1.n2.nabble.com/Workgroups-td1559166.html\">Learn more</a>"
+		);
+
+		addModule("Voting & Survey",  "poll", "Polls", "Allows you to add polls/surveys to your posts and collect votes from your visitors. " +
+				"You will find options to add a new poll when you create a new topic or reply to a post." +
+				"By default, only administrators can create polls. If you want to let other groups create polls, just modify the Create_poll permission. ");
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "No application match the \"<i>" + request.getHeader("host") + "</i>\" domain.");
+			return;
+		}
+
+		User user = Jtp.getUser(request, response);
+		if (user == null) {
+			response.sendRedirect(site.getBaseUrl() + Jtp.loginPath(site,null,response.encodeURL("/forum/Addons.jtp?site="+site.getId())) );
+			return;
+		}
+
+		boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+		boolean isSysAdmin = Permissions.isSysAdmin(user);
+		if (!isSiteAdmin && !isSysAdmin) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		boolean isSave = "save".equals(request.getParameter("action"));
+
+		String errorMsg = null;
+		if (isSave && "POST".equals(request.getMethod())) {
+			DbDatabase db = site.getDb();
+			db.beginTransaction();
+			try {
+				site = site.getGoodCopy();
+				for( Map.Entry<String,List<ModuleInfo>> entry : modulesByCategory.entrySet() ) {
+					for (ModuleInfo info : entry.getValue()) {
+						boolean isEnabled = "y".equals(request.getParameter(info.moduleName));
+						site.setModuleEnabled(info.moduleName, isEnabled);
+					}
+				}
+
+				db.commitTransaction();
+				Jtp.sendRedirect(request,response,Jtp.path(site.getRootNode()));
+				return;
+			} finally {
+				db.endTransaction();
+			}
+		}
+		Node rootNode = site.getRootNode();
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n	<head>\n		<META NAME=\"robots\" CONTENT=\"noindex,nofollow\">\n		" );
+ Shared.title(request, response, "Extras & add-ons"); 
+		out.print( "\n		" );
+ style(out); 
+		out.print( "\n		" );
+ js(out); 
+		out.print( "\n	</head>\n	<body>\n		" );
+ Shared.minHeader(request,response, rootNode); 
+		out.print( "\n\n		<div style=\"padding:.5em 0 .5em 60px;background:url('/images/box.png') 0 5px no-repeat;min-height:40px\">\n			<div class=\"big-title second-font\">Extras & Add-ons</div>\n			<div class=\"weak-color\">\n				Select below the extra features and add-ons for your Nabble application.\n			</div>\n		</div>\n\n		" );
+ Shared.errorMessage(request, response, errorMsg, null); 
+		out.print( "\n\n		<form method=\"post\" action=\"/app/Addons.jtp\" accept-charset=\"UTF-8\">\n			<input type=\"hidden\" name=\"action\" value=\"save\"/>\n			<table class=\"vertical-control\">\n				<tr>\n					<td>\n						<ul class=\"vertical-control\">\n							" );
+ for( Map.Entry<String,List<ModuleInfo>> entry : modulesByCategory.entrySet() ) { 
+		out.print( "\n							<li id=\"tab_" );
+		out.print( (elementId(entry.getKey())) );
+		out.print( "\" " );
+		out.print( (entry.getKey().equals(firstTab)?"class=\"selected shaded-bg-color\"":"") );
+		out.print( ">" );
+		out.print( (entry.getKey()) );
+		out.print( "</li>\n							" );
+ } 
+		out.print( "\n						</ul>\n					</td>\n					<td class=\"details shaded-bg-color\">\n						" );
+ for( Map.Entry<String,List<ModuleInfo>> entry : modulesByCategory.entrySet() ) { 
+		out.print( "\n						<div id=\"div_" );
+		out.print( (elementId(entry.getKey())) );
+		out.print( "\" " );
+		out.print( (entry.getKey().equals(firstTab)?"":"style=\"display:none\"") );
+		out.print( ">\n							" );
+ for (ModuleInfo info : entry.getValue()) { 
+		out.print( "\n								" );
+ boolean isChecked = site.isModuleEnabled(info.moduleName); 
+		out.print( "\n								<div class=\"addon-option\">\n									<input id=\"_" );
+		out.print( (info.moduleName) );
+		out.print( "\" type=\"checkbox\" name=\"" );
+		out.print( (info.moduleName) );
+		out.print( "\" value=\"y\" " );
+		out.print( (isChecked?"checked=\"true\"":"") );
+		out.print( "/>\n									<label for=\"_" );
+		out.print( (info.moduleName) );
+		out.print( "\">" );
+		out.print( (info.name) );
+		out.print( "</label>\n									" );
+ if (info.description != null && info.description.length() > 0) { 
+		out.print( "\n									<div class=\"weak-color\" style=\"margin:.25em 1.8em 1.4em\">" );
+		out.print( (info.description) );
+		out.print( "</div>\n									" );
+ } 
+		out.print( "\n								</div>\n							" );
+ } 
+		out.print( "\n						</div>\n						" );
+ } 
+		out.print( "\n					</td>\n				</tr>\n			</table>\n			<input type=\"submit\" class=\"toolbar action-button\" value=\"Save Changes\"/>\n			or <a href=\"" );
+		out.print( (Jtp.path(rootNode)) );
+		out.print( "\">Cancel</a>\n		</form>\n\n		" );
+ Shared.footer(request, response); 
+		out.print( "\n		" );
+ Shared.analytics(request, response); 
+		out.print( "\n	</body>\n</html>\n" );
+
+	}
+
+	private static String elementId(String name) {
+		return name.replaceAll(" ","_").replaceAll("&","");
+	}
+
+	private static void js(PrintWriter out) {
+		
+		out.print( "\n<script type=\"text/javascript\">\n	var selectedTab = '" );
+		out.print( (firstTab) );
+		out.print( "';\n	$(document).ready(function() {\n		var $li = $('ul.vertical-control li');\n		$li.click(function() {\n			var $this = $(this);\n			if ($this.hasClass('selected'))\n				return;\n			$('#tab_'+selectedTab).removeClass('selected shaded-bg-color');\n			$('#div_'+selectedTab).hide();\n			selectedTab = $this.attr('id').substring(4);\n			$('#tab_'+selectedTab).addClass('selected shaded-bg-color');\n			$('#div_'+selectedTab).show();\n			Nabble.resizeFrames();\n		});\n		$li.hover(function() {\n			var $this = $(this);\n			if ($this.hasClass('selected'))\n				return;\n			$this.addClass('light-bg-color');\n		}, function() {\n			var $this = $(this);\n			$this.removeClass('light-bg-color');\n		});\n	});\n</script>\n" );
+
+	}
+
+	private static void style(PrintWriter out) {
+		
+		out.print( "\n<style type=\"text/css\">\n	input { vertical-align:-10%; }\n	label {\n		font-weight: bold;\n		cursor: pointer;\n	}\n\n	div.addon-option {\n		margin: 0 0 .5em;\n	}\n	table.vertical-control {\n		margin: 1em 0;\n		width:100%;\n		border-collapse:collapse;\n	}\n	table.vertical-control td {\n		padding:0;\n		vertical-align:top;\n	}\n	table.vertical-control td.details {\n		width:85%;\n		padding:1em .8em .8em;\n	}\n	ul.vertical-control {\n		width:100%;\n		list-style-type:none;\n		padding:0;\n		margin:0;\n	}\n	ul.vertical-control li {\n		padding: .3em 0 .3em 1em;\n		font-weight:bold;\n		white-space:nowrap;\n		cursor:pointer;\n		border-top-left-radius:5px;\n		border-bottom-left-radius:5px;\n		-moz-border-radius-topleft:5px;\n		-moz-border-radius-bottomleft:5px;\n	}\n	ul.vertical-control li.selected {\n		cursor:text;\n		text-shadow:1px 1px 0 white;\n	}\n</style>\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/app/Addons.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,287 @@
+<%
+package nabble.view.web.app;
+
+import fschmidt.db.DbDatabase;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+
+public final class Addons extends HttpServlet {
+
+	private static class ModuleInfo {
+		String moduleName;
+		String name;
+		String description;
+	}
+
+	private static Map<String, List<ModuleInfo>> modulesByCategory = new LinkedHashMap<String, List<ModuleInfo>>();
+
+	private static String firstTab;
+
+	private static void addModule(String category, String moduleName, String name, String description) {
+		if (firstTab == null)
+			firstTab = category;
+		List<ModuleInfo> list = modulesByCategory.get(category);
+		if (list == null) {
+			list = new ArrayList<ModuleInfo>();
+			modulesByCategory.put(category, list);
+		}
+		ModuleInfo info = new ModuleInfo();
+		info.moduleName = moduleName;
+		info.name = name;
+		info.description = description;
+		list.add(info);
+	}
+
+	static {
+		addModule("Social", "social_dropdown_links", "Social dropdown links", "Show social links under the \"More\" menu of posts. With these links, users will be able to easily post to Twitter, Facebook, Delicious, Stumble Upon, LinkedIn, Google Bookmarks and Digg, which helps promoting your site.");
+		addModule("Social", "social_google_plus_one", "Google +1 button", "Show the Google +1 button on the top of the topic page.<br/><a href=\"http://www.google.com/+1/button/\" rel=\"nofollow\">Learn more</a>");
+		addModule("Social", "social_facebook_like", "Facebook like button", "Show the Facebook Like button on the top of the topic page. <br/><a href=\"http://developers.facebook.com/docs/reference/plugins/like/\" rel=\"nofollow\">Learn more</a>");
+		addModule("Social",  "social_tweet", "Tweet button", "Show Twitter / Tweet button on the top of the topic page. <br/><a href=\"http://twitter.com/about/resources/tweetbutton\" rel=\"nofollow\">Learn more</a>");
+
+		addModule("Content",  "content_noindex", "Hide contents from search engines", "Select this checkbox if you want to prevent Google and other search engines from indexing the contents of this application.");
+		addModule("Content",  "content_open_links_in_new_window", "Open links in new window", "Links posted by users will open in a different tab or window.");
+		addModule("Content",  "content_smart_cache", "Smart application pages",
+			"If your application has private sub-forums (or any other private sub-app), you can use this feature to make app pages always display the contents that the user can view. "+
+			"In other words, if a user has access to a private sub-forum, he/she will be able to view the private contents from the top-level app. "+
+			"Other users (without access) will only view the public contents. "+
+			"<b>Note</b>: this feature may slow down your app pages a little bit because the system must generate them from scratch for each user. You will probably not notice any difference if your application is small (e.g., less than 5,000 topics).");
+		addModule("Content",  "content_news_summary", "Custom summary for Newspaper",
+			"If you use the Newspaper application type, you can use this add-on to define custom snippets for your posts "+
+			"(the snippet is the text that shows on the front page of the app, immediately below the title of the post). " +
+			"You can specify the snippet directly in the post message like this: \"{summary_start} Here is your summary text {summary_end} Here is the post message.\" "+
+			"The text between the tags is the snippet to be displayed on the news page and will NOT appear on the post page."
+		);
+		addModule("Content",  "forum_avatars", "Show avatars on forum page", "Shows the avatar for the first and last post of each topic on the forum page.");
+		addModule("Content",  "ads", "Show ads", "Support Nabble by allowing us to show ads.");
+
+		addModule("Email & Notifications",  "email_registration_notification", "Notify me when someone registers", "When a user completes the registration process, an email is sent to all administrators with the details of the user (e.g., username, email, link to profile page).");
+
+		addModule("Mailing List Archives",  "expire_old_threads", "Lock old threads", "This add-on prevents new replies to old threads, which could be confusing to the mailing list users. Users that try to reply to an old thread will see a message that suggests them to create a new topic instead. By default, a thread is locked if it was created more than 365 days ago. You can change this number by tweaking the \"thread_expiration_days\" macro. ");
+
+		addModule("Privacy & Law",  "cookie_policy", "Cookie Law Compliance Solution", "Add-on for obtaining a user's explicit consent for the use of cookies on their browsers. This is required by some countries (mainly UK and EU) and visitors must explicitly agree with the E-Privacy Directive before having cookies read from/written to their browsers. The consent is requested just once. By default, the consent is requested to UK visitors only, but you can change the list of countries with NAML (i.e., override the <i>cookie_countries</i> macro).");
+
+		addModule("Responsiveness",  "responsive", "Responsive layout", "Add-on that improves the responsiveness of Nabble apps and make pages more readable on small screens.");
+		addModule("Responsiveness",  "mobile", "Better new post interface for mobile users", "Add-on that provides a better \"new topic\" and \"reply\" interfaces for mobile users. Posting new messages from cell phones (or any device with a small screen) will be easier with this module. Only mobile users will see the optimized pages (other users will still see the default new topic and reply pages).");
+
+		addModule("Navigation",  "thread_navigation", "Show links to next/previous threads", "This add-on adds \"Previous Thread\" and \"Next Thread\" links to the top of each thread page. Navigation through the threads becomes easier since users don't have to return to the front page in order to visit other threads.");
+
+		addModule("Work", "workgroup", "Workgroup",
+			"This feature allows a team to work integrated with a forum. " +
+			"Basically, a user can assign a topic to another user with a priority that ranges from 1 (highest) to 5 (lowest).This feature allows a team to work integrated with a forum." +
+			"Each topic represents a task that holds the discussion about the work to be accomplished.<br/>" +
+			"<a href=\"http://nabble-support.1.n2.nabble.com/Workgroups-td1559166.html\">Learn more</a>"
+		);
+
+		addModule("Voting & Survey",  "poll", "Polls", "Allows you to add polls/surveys to your posts and collect votes from your visitors. " +
+				"You will find options to add a new poll when you create a new topic or reply to a post." +
+				"By default, only administrators can create polls. If you want to let other groups create polls, just modify the Create_poll permission. ");
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "No application match the \"<i>" + request.getHeader("host") + "</i>\" domain.");
+			return;
+		}
+
+		User user = Jtp.getUser(request, response);
+		if (user == null) {
+			response.sendRedirect(site.getBaseUrl() + Jtp.loginPath(site,null,response.encodeURL("/forum/Addons.jtp?site="+site.getId())) );
+			return;
+		}
+
+		boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+		boolean isSysAdmin = Permissions.isSysAdmin(user);
+		if (!isSiteAdmin && !isSysAdmin) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		boolean isSave = "save".equals(request.getParameter("action"));
+
+		String errorMsg = null;
+		if (isSave && "POST".equals(request.getMethod())) {
+			DbDatabase db = site.getDb();
+			db.beginTransaction();
+			try {
+				site = site.getGoodCopy();
+				for( Map.Entry<String,List<ModuleInfo>> entry : modulesByCategory.entrySet() ) {
+					for (ModuleInfo info : entry.getValue()) {
+						boolean isEnabled = "y".equals(request.getParameter(info.moduleName));
+						site.setModuleEnabled(info.moduleName, isEnabled);
+					}
+				}
+
+				db.commitTransaction();
+				Jtp.sendRedirect(request,response,Jtp.path(site.getRootNode()));
+				return;
+			} finally {
+				db.endTransaction();
+			}
+		}
+		Node rootNode = site.getRootNode();
+		PrintWriter out = response.getWriter();
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow">
+				<% Shared.title(request, response, "Extras & add-ons"); %>
+				<% style(out); %>
+				<% js(out); %>
+			</head>
+			<body>
+				<% Shared.minHeader(request,response, rootNode); %>
+
+				<div style="padding:.5em 0 .5em 60px;background:url('/images/box.png') 0 5px no-repeat;min-height:40px">
+					<div class="big-title second-font">Extras & Add-ons</div>
+					<div class="weak-color">
+						Select below the extra features and add-ons for your Nabble application.
+					</div>
+				</div>
+
+				<% Shared.errorMessage(request, response, errorMsg, null); %>
+
+				<form method="post" action="/app/Addons.jtp" accept-charset="UTF-8">
+					<input type="hidden" name="action" value="save"/>
+					<table class="vertical-control">
+						<tr>
+							<td>
+								<ul class="vertical-control">
+									<% for( Map.Entry<String,List<ModuleInfo>> entry : modulesByCategory.entrySet() ) { %>
+									<li id="tab_<%=elementId(entry.getKey())%>" <%=entry.getKey().equals(firstTab)?"class=\"selected shaded-bg-color\"":""%>><%=entry.getKey()%></li>
+									<% } %>
+								</ul>
+							</td>
+							<td class="details shaded-bg-color">
+								<% for( Map.Entry<String,List<ModuleInfo>> entry : modulesByCategory.entrySet() ) { %>
+								<div id="div_<%=elementId(entry.getKey())%>" <%=entry.getKey().equals(firstTab)?"":"style=\"display:none\""%>>
+									<% for (ModuleInfo info : entry.getValue()) { %>
+										<% boolean isChecked = site.isModuleEnabled(info.moduleName); %>
+										<div class="addon-option">
+											<input id="_<%=info.moduleName%>" type="checkbox" name="<%=info.moduleName%>" value="y" <%=isChecked?"checked=\"true\"":""%>/>
+											<label for="_<%=info.moduleName%>"><%=info.name%></label>
+											<% if (info.description != null && info.description.length() > 0) { %>
+											<div class="weak-color" style="margin:.25em 1.8em 1.4em"><%=info.description%></div>
+											<% } %>
+										</div>
+									<% } %>
+								</div>
+								<% } %>
+							</td>
+						</tr>
+					</table>
+					<input type="submit" class="toolbar action-button" value="Save Changes"/>
+					or <a href="<%=Jtp.path(rootNode)%>">Cancel</a>
+				</form>
+
+				<% Shared.footer(request, response); %>
+				<% Shared.analytics(request, response); %>
+			</body>
+		</html>
+		<%
+	}
+
+	private static String elementId(String name) {
+		return name.replaceAll(" ","_").replaceAll("&","");
+	}
+
+	private static void js(PrintWriter out) {
+		%>
+		<script type="text/javascript">
+			var selectedTab = '<%=firstTab%>';
+			$(document).ready(function() {
+				var $li = $('ul.vertical-control li');
+				$li.click(function() {
+					var $this = $(this);
+					if ($this.hasClass('selected'))
+						return;
+					$('#tab_'+selectedTab).removeClass('selected shaded-bg-color');
+					$('#div_'+selectedTab).hide();
+					selectedTab = $this.attr('id').substring(4);
+					$('#tab_'+selectedTab).addClass('selected shaded-bg-color');
+					$('#div_'+selectedTab).show();
+					Nabble.resizeFrames();
+				});
+				$li.hover(function() {
+					var $this = $(this);
+					if ($this.hasClass('selected'))
+						return;
+					$this.addClass('light-bg-color');
+				}, function() {
+					var $this = $(this);
+					$this.removeClass('light-bg-color');
+				});
+			});
+		</script>
+		<%
+	}
+
+	private static void style(PrintWriter out) {
+		%>
+		<style type="text/css">
+			input { vertical-align:-10%; }
+			label {
+				font-weight: bold;
+				cursor: pointer;
+			}
+
+			div.addon-option {
+				margin: 0 0 .5em;
+			}
+			table.vertical-control {
+				margin: 1em 0;
+				width:100%;
+				border-collapse:collapse;
+			}
+			table.vertical-control td {
+				padding:0;
+				vertical-align:top;
+			}
+			table.vertical-control td.details {
+				width:85%;
+				padding:1em .8em .8em;
+			}
+			ul.vertical-control {
+				width:100%;
+				list-style-type:none;
+				padding:0;
+				margin:0;
+			}
+			ul.vertical-control li {
+				padding: .3em 0 .3em 1em;
+				font-weight:bold;
+				white-space:nowrap;
+				cursor:pointer;
+				border-top-left-radius:5px;
+				border-bottom-left-radius:5px;
+				-moz-border-radius-topleft:5px;
+				-moz-border-radius-bottomleft:5px;
+			}
+			ul.vertical-control li.selected {
+				cursor:text;
+				text-shadow:1px 1px 0 white;
+			}
+		</style>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/app/Languages.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,126 @@
+
+package nabble.view.web.app;
+
+import fschmidt.db.DbDatabase;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+public final class Languages extends HttpServlet {
+
+	public static Map<String, String> languages = new LinkedHashMap<String, String>();
+
+	static {
+		languages.put("lang_cs_cz", "Čeština (Czech Republic)");
+		languages.put("none", "English");
+		languages.put("lang_de", "Deutsch");
+		languages.put("lang_es", "Español");
+		languages.put("lang_fr_fr", "Français (France)");
+		languages.put("lang_pl", "Polski");
+		languages.put("lang_pt_br", "Português (Brasil)");
+		languages.put("lang_sv", "Svenska (<span class='important'>Incomplete</span>)");
+		languages.put("lang_tu", "Türkçe");
+		languages.put("lang_rus_ru", "Русский");
+		languages.put("lang_ell", "Ελληνικά");
+		languages.put("lang_ch_si", "中文 (简体)");
+		languages.put("lang_ch_tr", "中文 (繁體)");
+		languages.put("lang_arabic", "Arabic");
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "No application match the \"<i>" + request.getHeader("host") + "</i>\" domain.");
+			return;
+		}
+
+		User user = Jtp.getUser(request, response);
+		if (user == null) {
+			response.sendRedirect(site.getBaseUrl() + Jtp.loginPath(site,null,response.encodeURL("/forum/Addons.jtp?site="+site.getId())) );
+			return;
+		}
+
+		boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+		boolean isSysAdmin = Permissions.isSysAdmin(user);
+		if (!isSiteAdmin && !isSysAdmin) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		boolean isSave = "save".equals(request.getParameter("action"));
+
+		if (isSave && "POST".equals(request.getMethod())) {
+			DbDatabase db = site.getDb();
+			db.beginTransaction();
+			try {
+				site = site.getGoodCopy();
+				String selectedLanguage = request.getParameter("lang");
+				for( Map.Entry<String,String> entry : languages.entrySet() ) {
+					boolean isEnglish = "none".equals(entry.getKey());
+					if (!isEnglish) {
+						boolean isEnabled = entry.getKey().equals(selectedLanguage);
+						site.setModuleEnabled(entry.getKey(), isEnabled);
+					}
+				}
+				db.commitTransaction();
+				Jtp.sendRedirect(request,response,Jtp.path(site.getRootNode()));
+				return;
+			} finally {
+				db.endTransaction();
+			}
+		}
+		Node rootNode = site.getRootNode();
+		PrintWriter out = response.getWriter();
+
+		String selected = "none";
+		for( Map.Entry<String,String> entry : languages.entrySet() ) {
+			if (site.isModuleEnabled(entry.getKey())) {
+				selected = entry.getKey();
+				break;
+			}
+		}
+		
+		out.print( "\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n	<head>\n		<META NAME=\"robots\" CONTENT=\"noindex,nofollow\">\n		" );
+ Shared.title(request, response, "Languages"); 
+		out.print( "\n		<style type=\"text/css\">\n			div.language {\n				margin: .4em .8em;\n			}\n		</style>\n	</head>\n	<body>\n		" );
+ Shared.minHeader(request,response, rootNode); 
+		out.print( "\n\n		<div style=\"padding:.5em 0 .5em 60px;background:url('/images/world.png') 5px 5px no-repeat;min-height:45px\">\n			<div class=\"big-title second-font\">Languages</div>\n			<div class=\"weak-color\">\n				Select a language for this Nabble application.\n			</div>\n		</div>\n\n		<form method=\"post\" action=\"/app/Languages.jtp\" accept-charset=\"UTF-8\">\n			<input type=\"hidden\" name=\"action\" value=\"save\"/>\n\n			" );
+ for( Map.Entry<String,String> entry : languages.entrySet() ) { 
+		out.print( "\n				" );
+ boolean isChecked = selected.equals(entry.getKey()); 
+		out.print( "\n				<div class=\"language\">\n					<input id=\"_" );
+		out.print( (entry.getKey()) );
+		out.print( "\" type=\"radio\" name=\"lang\" value=\"" );
+		out.print( (entry.getKey()) );
+		out.print( "\" " );
+		out.print( (isChecked?"checked=\"true\"":"") );
+		out.print( ">\n					<label for=\"_" );
+		out.print( (entry.getKey()) );
+		out.print( "\">" );
+		out.print( (entry.getValue()) );
+		out.print( "</label>\n				</div>\n			" );
+ } 
+		out.print( "\n\n			<br/>\n			<input type=\"submit\" class=\"toolbar action-button\" value=\"Save Changes\"/>\n			or <a href=\"/\">Cancel</a>\n		</form>\n\n		" );
+ Shared.footer(request, response); 
+		out.print( "\n		" );
+ Shared.analytics(request, response); 
+		out.print( "\n	</body>\n</html>\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/app/Languages.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,142 @@
+<%
+package nabble.view.web.app;
+
+import fschmidt.db.DbDatabase;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+public final class Languages extends HttpServlet {
+
+	public static Map<String, String> languages = new LinkedHashMap<String, String>();
+
+	static {
+		languages.put("lang_cs_cz", "Čeština (Czech Republic)");
+		languages.put("none", "English");
+		languages.put("lang_de", "Deutsch");
+		languages.put("lang_es", "Español");
+		languages.put("lang_fr_fr", "Français (France)");
+		languages.put("lang_pl", "Polski");
+		languages.put("lang_pt_br", "Português (Brasil)");
+		languages.put("lang_sv", "Svenska (<span class='important'>Incomplete</span>)");
+		languages.put("lang_tu", "Türkçe");
+		languages.put("lang_rus_ru", "Русский");
+		languages.put("lang_ell", "Ελληνικά");
+		languages.put("lang_ch_si", "中文 (简体)");
+		languages.put("lang_ch_tr", "中文 (繁體)");
+		languages.put("lang_arabic", "Arabic");
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "No application match the \"<i>" + request.getHeader("host") + "</i>\" domain.");
+			return;
+		}
+
+		User user = Jtp.getUser(request, response);
+		if (user == null) {
+			response.sendRedirect(site.getBaseUrl() + Jtp.loginPath(site,null,response.encodeURL("/forum/Addons.jtp?site="+site.getId())) );
+			return;
+		}
+
+		boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+		boolean isSysAdmin = Permissions.isSysAdmin(user);
+		if (!isSiteAdmin && !isSysAdmin) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		boolean isSave = "save".equals(request.getParameter("action"));
+
+		if (isSave && "POST".equals(request.getMethod())) {
+			DbDatabase db = site.getDb();
+			db.beginTransaction();
+			try {
+				site = site.getGoodCopy();
+				String selectedLanguage = request.getParameter("lang");
+				for( Map.Entry<String,String> entry : languages.entrySet() ) {
+					boolean isEnglish = "none".equals(entry.getKey());
+					if (!isEnglish) {
+						boolean isEnabled = entry.getKey().equals(selectedLanguage);
+						site.setModuleEnabled(entry.getKey(), isEnabled);
+					}
+				}
+				db.commitTransaction();
+				Jtp.sendRedirect(request,response,Jtp.path(site.getRootNode()));
+				return;
+			} finally {
+				db.endTransaction();
+			}
+		}
+		Node rootNode = site.getRootNode();
+		PrintWriter out = response.getWriter();
+
+		String selected = "none";
+		for( Map.Entry<String,String> entry : languages.entrySet() ) {
+			if (site.isModuleEnabled(entry.getKey())) {
+				selected = entry.getKey();
+				break;
+			}
+		}
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow">
+				<% Shared.title(request, response, "Languages"); %>
+				<style type="text/css">
+					div.language {
+						margin: .4em .8em;
+					}
+				</style>
+			</head>
+			<body>
+				<% Shared.minHeader(request,response, rootNode); %>
+
+				<div style="padding:.5em 0 .5em 60px;background:url('/images/world.png') 5px 5px no-repeat;min-height:45px">
+					<div class="big-title second-font">Languages</div>
+					<div class="weak-color">
+						Select a language for this Nabble application.
+					</div>
+				</div>
+
+				<form method="post" action="/app/Languages.jtp" accept-charset="UTF-8">
+					<input type="hidden" name="action" value="save"/>
+
+					<% for( Map.Entry<String,String> entry : languages.entrySet() ) { %>
+						<% boolean isChecked = selected.equals(entry.getKey()); %>
+						<div class="language">
+							<input id="_<%=entry.getKey()%>" type="radio" name="lang" value="<%=entry.getKey()%>" <%=isChecked?"checked=\"true\"":""%>>
+							<label for="_<%=entry.getKey()%>"><%=entry.getValue()%></label>
+						</div>
+					<% } %>
+
+					<br/>
+					<input type="submit" class="toolbar action-button" value="Save Changes"/>
+					or <a href="/">Cancel</a>
+				</form>
+
+				<% Shared.footer(request, response); %>
+				<% Shared.analytics(request, response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/assets/bootstrap/css/bootstrap.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,5851 @@
+/*!
+ * Bootstrap v2.3.0
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+.clearfix {
+  *zoom: 1;
+}
+.clearfix:before,
+.clearfix:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.clearfix:after {
+  clear: both;
+}
+.hide-text {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+.input-block-level {
+  display: block;
+  width: 100%;
+  min-height: 30px;
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+nav,
+section {
+  display: block;
+}
+audio,
+canvas,
+video {
+  display: inline-block;
+  *display: inline;
+  *zoom: 1;
+}
+audio:not([controls]) {
+  display: none;
+}
+html {
+  font-size: 100%;
+  -webkit-text-size-adjust: 100%;
+  -ms-text-size-adjust: 100%;
+}
+a:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+a:hover,
+a:active {
+  outline: 0;
+}
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+sup {
+  top: -0.5em;
+}
+sub {
+  bottom: -0.25em;
+}
+img {
+  /* Responsive images (ensure images don't scale beyond their parents) */
+
+  max-width: 100%;
+  /* Part 1: Set a maxium relative to the parent */
+
+  width: auto\9;
+  /* IE7-8 need help adjusting responsive images */
+
+  height: auto;
+  /* Part 2: Scale the height according to the width, otherwise you get stretching */
+
+  vertical-align: middle;
+  border: 0;
+  -ms-interpolation-mode: bicubic;
+}
+#map_canvas img,
+.google-maps img {
+  max-width: none;
+}
+button,
+input,
+select,
+textarea {
+  margin: 0;
+  font-size: 100%;
+  vertical-align: middle;
+}
+button,
+input {
+  *overflow: visible;
+  line-height: normal;
+}
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+  -webkit-appearance: button;
+  cursor: pointer;
+}
+label,
+select,
+button,
+input[type="button"],
+input[type="reset"],
+input[type="submit"],
+input[type="radio"],
+input[type="checkbox"] {
+  cursor: pointer;
+}
+input[type="search"] {
+  -webkit-box-sizing: content-box;
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+  -webkit-appearance: textfield;
+}
+input[type="search"]::-webkit-search-decoration,
+input[type="search"]::-webkit-search-cancel-button {
+  -webkit-appearance: none;
+}
+textarea {
+  overflow: auto;
+  vertical-align: top;
+}
+@media print {
+  * {
+    text-shadow: none !important;
+    color: #000 !important;
+    background: transparent !important;
+    box-shadow: none !important;
+  }
+  a,
+  a:visited {
+    text-decoration: underline;
+  }
+  a[href]:after {
+    content: " (" attr(href) ")";
+  }
+  abbr[title]:after {
+    content: " (" attr(title) ")";
+  }
+  .ir a:after,
+  a[href^="javascript:"]:after,
+  a[href^="#"]:after {
+    content: "";
+  }
+  pre,
+  blockquote {
+    border: 1px solid #999;
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  img {
+    max-width: 100% !important;
+  }
+  @page  {
+    margin: 0.5cm;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+}
+body {
+  margin: 0;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 14px;
+  line-height: 20px;
+  color: #333333;
+  background-color: #ffffff;
+}
+a {
+  color: #0088cc;
+  text-decoration: none;
+}
+a:hover,
+a:focus {
+  color: #005580;
+  text-decoration: underline;
+}
+.img-rounded {
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+}
+.img-polaroid {
+  padding: 4px;
+  background-color: #fff;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+.img-circle {
+  -webkit-border-radius: 500px;
+  -moz-border-radius: 500px;
+  border-radius: 500px;
+}
+.row {
+  margin-left: -20px;
+  *zoom: 1;
+}
+.row:before,
+.row:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.row:after {
+  clear: both;
+}
+[class*="span"] {
+  float: left;
+  min-height: 1px;
+  margin-left: 20px;
+}
+.container,
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+  width: 940px;
+}
+.span12 {
+  width: 940px;
+}
+.span11 {
+  width: 860px;
+}
+.span10 {
+  width: 780px;
+}
+.span9 {
+  width: 700px;
+}
+.span8 {
+  width: 620px;
+}
+.span7 {
+  width: 540px;
+}
+.span6 {
+  width: 460px;
+}
+.span5 {
+  width: 380px;
+}
+.span4 {
+  width: 300px;
+}
+.span3 {
+  width: 220px;
+}
+.span2 {
+  width: 140px;
+}
+.span1 {
+  width: 60px;
+}
+.offset12 {
+  margin-left: 980px;
+}
+.offset11 {
+  margin-left: 900px;
+}
+.offset10 {
+  margin-left: 820px;
+}
+.offset9 {
+  margin-left: 740px;
+}
+.offset8 {
+  margin-left: 660px;
+}
+.offset7 {
+  margin-left: 580px;
+}
+.offset6 {
+  margin-left: 500px;
+}
+.offset5 {
+  margin-left: 420px;
+}
+.offset4 {
+  margin-left: 340px;
+}
+.offset3 {
+  margin-left: 260px;
+}
+.offset2 {
+  margin-left: 180px;
+}
+.offset1 {
+  margin-left: 100px;
+}
+.row-fluid {
+  width: 100%;
+  *zoom: 1;
+}
+.row-fluid:before,
+.row-fluid:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.row-fluid:after {
+  clear: both;
+}
+.row-fluid [class*="span"] {
+  display: block;
+  width: 100%;
+  min-height: 30px;
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  float: left;
+  margin-left: 2.127659574468085%;
+  *margin-left: 2.074468085106383%;
+}
+.row-fluid [class*="span"]:first-child {
+  margin-left: 0;
+}
+.row-fluid .controls-row [class*="span"] + [class*="span"] {
+  margin-left: 2.127659574468085%;
+}
+.row-fluid .span12 {
+  width: 100%;
+  *width: 99.94680851063829%;
+}
+.row-fluid .span11 {
+  width: 91.48936170212765%;
+  *width: 91.43617021276594%;
+}
+.row-fluid .span10 {
+  width: 82.97872340425532%;
+  *width: 82.92553191489361%;
+}
+.row-fluid .span9 {
+  width: 74.46808510638297%;
+  *width: 74.41489361702126%;
+}
+.row-fluid .span8 {
+  width: 65.95744680851064%;
+  *width: 65.90425531914893%;
+}
+.row-fluid .span7 {
+  width: 57.44680851063829%;
+  *width: 57.39361702127659%;
+}
+.row-fluid .span6 {
+  width: 48.93617021276595%;
+  *width: 48.88297872340425%;
+}
+.row-fluid .span5 {
+  width: 40.42553191489362%;
+  *width: 40.37234042553192%;
+}
+.row-fluid .span4 {
+  width: 31.914893617021278%;
+  *width: 31.861702127659576%;
+}
+.row-fluid .span3 {
+  width: 23.404255319148934%;
+  *width: 23.351063829787233%;
+}
+.row-fluid .span2 {
+  width: 14.893617021276595%;
+  *width: 14.840425531914894%;
+}
+.row-fluid .span1 {
+  width: 6.382978723404255%;
+  *width: 6.329787234042553%;
+}
+.row-fluid .offset12 {
+  margin-left: 104.25531914893617%;
+  *margin-left: 104.14893617021275%;
+}
+.row-fluid .offset12:first-child {
+  margin-left: 102.12765957446808%;
+  *margin-left: 102.02127659574467%;
+}
+.row-fluid .offset11 {
+  margin-left: 95.74468085106382%;
+  *margin-left: 95.6382978723404%;
+}
+.row-fluid .offset11:first-child {
+  margin-left: 93.61702127659574%;
+  *margin-left: 93.51063829787232%;
+}
+.row-fluid .offset10 {
+  margin-left: 87.23404255319149%;
+  *margin-left: 87.12765957446807%;
+}
+.row-fluid .offset10:first-child {
+  margin-left: 85.1063829787234%;
+  *margin-left: 84.99999999999999%;
+}
+.row-fluid .offset9 {
+  margin-left: 78.72340425531914%;
+  *margin-left: 78.61702127659572%;
+}
+.row-fluid .offset9:first-child {
+  margin-left: 76.59574468085106%;
+  *margin-left: 76.48936170212764%;
+}
+.row-fluid .offset8 {
+  margin-left: 70.2127659574468%;
+  *margin-left: 70.10638297872339%;
+}
+.row-fluid .offset8:first-child {
+  margin-left: 68.08510638297872%;
+  *margin-left: 67.9787234042553%;
+}
+.row-fluid .offset7 {
+  margin-left: 61.70212765957446%;
+  *margin-left: 61.59574468085106%;
+}
+.row-fluid .offset7:first-child {
+  margin-left: 59.574468085106375%;
+  *margin-left: 59.46808510638297%;
+}
+.row-fluid .offset6 {
+  margin-left: 53.191489361702125%;
+  *margin-left: 53.085106382978715%;
+}
+.row-fluid .offset6:first-child {
+  margin-left: 51.063829787234035%;
+  *margin-left: 50.95744680851063%;
+}
+.row-fluid .offset5 {
+  margin-left: 44.68085106382979%;
+  *margin-left: 44.57446808510638%;
+}
+.row-fluid .offset5:first-child {
+  margin-left: 42.5531914893617%;
+  *margin-left: 42.4468085106383%;
+}
+.row-fluid .offset4 {
+  margin-left: 36.170212765957444%;
+  *margin-left: 36.06382978723405%;
+}
+.row-fluid .offset4:first-child {
+  margin-left: 34.04255319148936%;
+  *margin-left: 33.93617021276596%;
+}
+.row-fluid .offset3 {
+  margin-left: 27.659574468085104%;
+  *margin-left: 27.5531914893617%;
+}
+.row-fluid .offset3:first-child {
+  margin-left: 25.53191489361702%;
+  *margin-left: 25.425531914893618%;
+}
+.row-fluid .offset2 {
+  margin-left: 19.148936170212764%;
+  *margin-left: 19.04255319148936%;
+}
+.row-fluid .offset2:first-child {
+  margin-left: 17.02127659574468%;
+  *margin-left: 16.914893617021278%;
+}
+.row-fluid .offset1 {
+  margin-left: 10.638297872340425%;
+  *margin-left: 10.53191489361702%;
+}
+.row-fluid .offset1:first-child {
+  margin-left: 8.51063829787234%;
+  *margin-left: 8.404255319148938%;
+}
+[class*="span"].hide,
+.row-fluid [class*="span"].hide {
+  display: none;
+}
+[class*="span"].pull-right,
+.row-fluid [class*="span"].pull-right {
+  float: right;
+}
+.container {
+  margin-right: auto;
+  margin-left: auto;
+  *zoom: 1;
+}
+.container:before,
+.container:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.container:after {
+  clear: both;
+}
+.container-fluid {
+  padding-right: 20px;
+  padding-left: 20px;
+  *zoom: 1;
+}
+.container-fluid:before,
+.container-fluid:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.container-fluid:after {
+  clear: both;
+}
+p {
+  margin: 0 0 10px;
+}
+.lead {
+  margin-bottom: 20px;
+  font-size: 21px;
+  font-weight: 200;
+  line-height: 30px;
+}
+small {
+  font-size: 85%;
+}
+strong {
+  font-weight: bold;
+}
+em {
+  font-style: italic;
+}
+cite {
+  font-style: normal;
+}
+.muted {
+  color: #999999;
+}
+a.muted:hover,
+a.muted:focus {
+  color: #808080;
+}
+.text-warning {
+  color: #c09853;
+}
+a.text-warning:hover,
+a.text-warning:focus {
+  color: #a47e3c;
+}
+.text-error {
+  color: #b94a48;
+}
+a.text-error:hover,
+a.text-error:focus {
+  color: #953b39;
+}
+.text-info {
+  color: #3a87ad;
+}
+a.text-info:hover,
+a.text-info:focus {
+  color: #2d6987;
+}
+.text-success {
+  color: #468847;
+}
+a.text-success:hover,
+a.text-success:focus {
+  color: #356635;
+}
+.text-left {
+  text-align: left;
+}
+.text-right {
+  text-align: right;
+}
+.text-center {
+  text-align: center;
+}
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+  margin: 10px 0;
+  font-family: inherit;
+  font-weight: bold;
+  line-height: 20px;
+  color: inherit;
+  text-rendering: optimizelegibility;
+}
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small {
+  font-weight: normal;
+  line-height: 1;
+  color: #999999;
+}
+h1,
+h2,
+h3 {
+  line-height: 40px;
+}
+h1 {
+  font-size: 38.5px;
+}
+h2 {
+  font-size: 31.5px;
+}
+h3 {
+  font-size: 24.5px;
+}
+h4 {
+  font-size: 17.5px;
+}
+h5 {
+  font-size: 14px;
+}
+h6 {
+  font-size: 11.9px;
+}
+h1 small {
+  font-size: 24.5px;
+}
+h2 small {
+  font-size: 17.5px;
+}
+h3 small {
+  font-size: 14px;
+}
+h4 small {
+  font-size: 14px;
+}
+.page-header {
+  padding-bottom: 9px;
+  margin: 20px 0 30px;
+  border-bottom: 1px solid #eeeeee;
+}
+ul,
+ol {
+  padding: 0;
+  margin: 0 0 10px 25px;
+}
+ul ul,
+ul ol,
+ol ol,
+ol ul {
+  margin-bottom: 0;
+}
+li {
+  line-height: 20px;
+}
+ul.unstyled,
+ol.unstyled {
+  margin-left: 0;
+  list-style: none;
+}
+ul.inline,
+ol.inline {
+  margin-left: 0;
+  list-style: none;
+}
+ul.inline > li,
+ol.inline > li {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  padding-left: 5px;
+  padding-right: 5px;
+}
+dl {
+  margin-bottom: 20px;
+}
+dt,
+dd {
+  line-height: 20px;
+}
+dt {
+  font-weight: bold;
+}
+dd {
+  margin-left: 10px;
+}
+.dl-horizontal {
+  *zoom: 1;
+}
+.dl-horizontal:before,
+.dl-horizontal:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.dl-horizontal:after {
+  clear: both;
+}
+.dl-horizontal dt {
+  float: left;
+  width: 160px;
+  clear: left;
+  text-align: right;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.dl-horizontal dd {
+  margin-left: 180px;
+}
+hr {
+  margin: 20px 0;
+  border: 0;
+  border-top: 1px solid #eeeeee;
+  border-bottom: 1px solid #ffffff;
+}
+abbr[title],
+abbr[data-original-title] {
+  cursor: help;
+  border-bottom: 1px dotted #999999;
+}
+abbr.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+blockquote {
+  padding: 0 0 0 15px;
+  margin: 0 0 20px;
+  border-left: 5px solid #eeeeee;
+}
+blockquote p {
+  margin-bottom: 0;
+  font-size: 17.5px;
+  font-weight: 300;
+  line-height: 1.25;
+}
+blockquote small {
+  display: block;
+  line-height: 20px;
+  color: #999999;
+}
+blockquote small:before {
+  content: '\2014 \00A0';
+}
+blockquote.pull-right {
+  float: right;
+  padding-right: 15px;
+  padding-left: 0;
+  border-right: 5px solid #eeeeee;
+  border-left: 0;
+}
+blockquote.pull-right p,
+blockquote.pull-right small {
+  text-align: right;
+}
+blockquote.pull-right small:before {
+  content: '';
+}
+blockquote.pull-right small:after {
+  content: '\00A0 \2014';
+}
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+  content: "";
+}
+address {
+  display: block;
+  margin-bottom: 20px;
+  font-style: normal;
+  line-height: 20px;
+}
+code,
+pre {
+  padding: 0 3px 2px;
+  font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+  font-size: 12px;
+  color: #333333;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+code {
+  padding: 2px 4px;
+  color: #d14;
+  background-color: #f7f7f9;
+  border: 1px solid #e1e1e8;
+  white-space: nowrap;
+}
+pre {
+  display: block;
+  padding: 9.5px;
+  margin: 0 0 10px;
+  font-size: 13px;
+  line-height: 20px;
+  word-break: break-all;
+  word-wrap: break-word;
+  white-space: pre;
+  white-space: pre-wrap;
+  background-color: #f5f5f5;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+pre.prettyprint {
+  margin-bottom: 20px;
+}
+pre code {
+  padding: 0;
+  color: inherit;
+  white-space: pre;
+  white-space: pre-wrap;
+  background-color: transparent;
+  border: 0;
+}
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+.label,
+.badge {
+  display: inline-block;
+  padding: 2px 4px;
+  font-size: 11.844px;
+  font-weight: bold;
+  line-height: 14px;
+  color: #ffffff;
+  vertical-align: baseline;
+  white-space: nowrap;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #999999;
+}
+.label {
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.badge {
+  padding-left: 9px;
+  padding-right: 9px;
+  -webkit-border-radius: 9px;
+  -moz-border-radius: 9px;
+  border-radius: 9px;
+}
+.label:empty,
+.badge:empty {
+  display: none;
+}
+a.label:hover,
+a.label:focus,
+a.badge:hover,
+a.badge:focus {
+  color: #ffffff;
+  text-decoration: none;
+  cursor: pointer;
+}
+.label-important,
+.badge-important {
+  background-color: #b94a48;
+}
+.label-important[href],
+.badge-important[href] {
+  background-color: #953b39;
+}
+.label-warning,
+.badge-warning {
+  background-color: #f89406;
+}
+.label-warning[href],
+.badge-warning[href] {
+  background-color: #c67605;
+}
+.label-success,
+.badge-success {
+  background-color: #468847;
+}
+.label-success[href],
+.badge-success[href] {
+  background-color: #356635;
+}
+.label-info,
+.badge-info {
+  background-color: #3a87ad;
+}
+.label-info[href],
+.badge-info[href] {
+  background-color: #2d6987;
+}
+.label-inverse,
+.badge-inverse {
+  background-color: #333333;
+}
+.label-inverse[href],
+.badge-inverse[href] {
+  background-color: #1a1a1a;
+}
+.btn .label,
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+.btn-mini .label,
+.btn-mini .badge {
+  top: 0;
+}
+table {
+  max-width: 100%;
+  background-color: transparent;
+  border-collapse: collapse;
+  border-spacing: 0;
+}
+.table {
+  width: 100%;
+  margin-bottom: 20px;
+}
+.table th,
+.table td {
+  padding: 8px;
+  line-height: 20px;
+  text-align: left;
+  vertical-align: top;
+  border-top: 1px solid #dddddd;
+}
+.table th {
+  font-weight: bold;
+}
+.table thead th {
+  vertical-align: bottom;
+}
+.table caption + thead tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child th,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child th,
+.table thead:first-child tr:first-child td {
+  border-top: 0;
+}
+.table tbody + tbody {
+  border-top: 2px solid #dddddd;
+}
+.table .table {
+  background-color: #ffffff;
+}
+.table-condensed th,
+.table-condensed td {
+  padding: 4px 5px;
+}
+.table-bordered {
+  border: 1px solid #dddddd;
+  border-collapse: separate;
+  *border-collapse: collapse;
+  border-left: 0;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.table-bordered th,
+.table-bordered td {
+  border-left: 1px solid #dddddd;
+}
+.table-bordered caption + thead tr:first-child th,
+.table-bordered caption + tbody tr:first-child th,
+.table-bordered caption + tbody tr:first-child td,
+.table-bordered colgroup + thead tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child th,
+.table-bordered colgroup + tbody tr:first-child td,
+.table-bordered thead:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child th,
+.table-bordered tbody:first-child tr:first-child td {
+  border-top: 0;
+}
+.table-bordered thead:first-child tr:first-child > th:first-child,
+.table-bordered tbody:first-child tr:first-child > td:first-child,
+.table-bordered tbody:first-child tr:first-child > th:first-child {
+  -webkit-border-top-left-radius: 4px;
+  -moz-border-radius-topleft: 4px;
+  border-top-left-radius: 4px;
+}
+.table-bordered thead:first-child tr:first-child > th:last-child,
+.table-bordered tbody:first-child tr:first-child > td:last-child,
+.table-bordered tbody:first-child tr:first-child > th:last-child {
+  -webkit-border-top-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  border-top-right-radius: 4px;
+}
+.table-bordered thead:last-child tr:last-child > th:first-child,
+.table-bordered tbody:last-child tr:last-child > td:first-child,
+.table-bordered tbody:last-child tr:last-child > th:first-child,
+.table-bordered tfoot:last-child tr:last-child > td:first-child,
+.table-bordered tfoot:last-child tr:last-child > th:first-child {
+  -webkit-border-bottom-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+  border-bottom-left-radius: 4px;
+}
+.table-bordered thead:last-child tr:last-child > th:last-child,
+.table-bordered tbody:last-child tr:last-child > td:last-child,
+.table-bordered tbody:last-child tr:last-child > th:last-child,
+.table-bordered tfoot:last-child tr:last-child > td:last-child,
+.table-bordered tfoot:last-child tr:last-child > th:last-child {
+  -webkit-border-bottom-right-radius: 4px;
+  -moz-border-radius-bottomright: 4px;
+  border-bottom-right-radius: 4px;
+}
+.table-bordered tfoot + tbody:last-child tr:last-child td:first-child {
+  -webkit-border-bottom-left-radius: 0;
+  -moz-border-radius-bottomleft: 0;
+  border-bottom-left-radius: 0;
+}
+.table-bordered tfoot + tbody:last-child tr:last-child td:last-child {
+  -webkit-border-bottom-right-radius: 0;
+  -moz-border-radius-bottomright: 0;
+  border-bottom-right-radius: 0;
+}
+.table-bordered caption + thead tr:first-child th:first-child,
+.table-bordered caption + tbody tr:first-child td:first-child,
+.table-bordered colgroup + thead tr:first-child th:first-child,
+.table-bordered colgroup + tbody tr:first-child td:first-child {
+  -webkit-border-top-left-radius: 4px;
+  -moz-border-radius-topleft: 4px;
+  border-top-left-radius: 4px;
+}
+.table-bordered caption + thead tr:first-child th:last-child,
+.table-bordered caption + tbody tr:first-child td:last-child,
+.table-bordered colgroup + thead tr:first-child th:last-child,
+.table-bordered colgroup + tbody tr:first-child td:last-child {
+  -webkit-border-top-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  border-top-right-radius: 4px;
+}
+.table-striped tbody > tr:nth-child(odd) > td,
+.table-striped tbody > tr:nth-child(odd) > th {
+  background-color: #f9f9f9;
+}
+.table-hover tbody tr:hover > td,
+.table-hover tbody tr:hover > th {
+  background-color: #f5f5f5;
+}
+table td[class*="span"],
+table th[class*="span"],
+.row-fluid table td[class*="span"],
+.row-fluid table th[class*="span"] {
+  display: table-cell;
+  float: none;
+  margin-left: 0;
+}
+.table td.span1,
+.table th.span1 {
+  float: none;
+  width: 44px;
+  margin-left: 0;
+}
+.table td.span2,
+.table th.span2 {
+  float: none;
+  width: 124px;
+  margin-left: 0;
+}
+.table td.span3,
+.table th.span3 {
+  float: none;
+  width: 204px;
+  margin-left: 0;
+}
+.table td.span4,
+.table th.span4 {
+  float: none;
+  width: 284px;
+  margin-left: 0;
+}
+.table td.span5,
+.table th.span5 {
+  float: none;
+  width: 364px;
+  margin-left: 0;
+}
+.table td.span6,
+.table th.span6 {
+  float: none;
+  width: 444px;
+  margin-left: 0;
+}
+.table td.span7,
+.table th.span7 {
+  float: none;
+  width: 524px;
+  margin-left: 0;
+}
+.table td.span8,
+.table th.span8 {
+  float: none;
+  width: 604px;
+  margin-left: 0;
+}
+.table td.span9,
+.table th.span9 {
+  float: none;
+  width: 684px;
+  margin-left: 0;
+}
+.table td.span10,
+.table th.span10 {
+  float: none;
+  width: 764px;
+  margin-left: 0;
+}
+.table td.span11,
+.table th.span11 {
+  float: none;
+  width: 844px;
+  margin-left: 0;
+}
+.table td.span12,
+.table th.span12 {
+  float: none;
+  width: 924px;
+  margin-left: 0;
+}
+.table tbody tr.success > td {
+  background-color: #dff0d8;
+}
+.table tbody tr.error > td {
+  background-color: #f2dede;
+}
+.table tbody tr.warning > td {
+  background-color: #fcf8e3;
+}
+.table tbody tr.info > td {
+  background-color: #d9edf7;
+}
+.table-hover tbody tr.success:hover > td {
+  background-color: #d0e9c6;
+}
+.table-hover tbody tr.error:hover > td {
+  background-color: #ebcccc;
+}
+.table-hover tbody tr.warning:hover > td {
+  background-color: #faf2cc;
+}
+.table-hover tbody tr.info:hover > td {
+  background-color: #c4e3f3;
+}
+form {
+  margin: 0 0 20px;
+}
+fieldset {
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+legend {
+  display: block;
+  width: 100%;
+  padding: 0;
+  margin-bottom: 20px;
+  font-size: 21px;
+  line-height: 40px;
+  color: #333333;
+  border: 0;
+  border-bottom: 1px solid #e5e5e5;
+}
+legend small {
+  font-size: 15px;
+  color: #999999;
+}
+label,
+input,
+button,
+select,
+textarea {
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 20px;
+}
+input,
+button,
+select,
+textarea {
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+label {
+  display: block;
+  margin-bottom: 5px;
+}
+select,
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+  display: inline-block;
+  height: 20px;
+  padding: 4px 6px;
+  margin-bottom: 10px;
+  font-size: 14px;
+  line-height: 20px;
+  color: #555555;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  vertical-align: middle;
+}
+input,
+textarea,
+.uneditable-input {
+  width: 206px;
+}
+textarea {
+  height: auto;
+}
+textarea,
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"],
+input[type="color"],
+.uneditable-input {
+  background-color: #ffffff;
+  border: 1px solid #cccccc;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -webkit-transition: border linear .2s, box-shadow linear .2s;
+  -moz-transition: border linear .2s, box-shadow linear .2s;
+  -o-transition: border linear .2s, box-shadow linear .2s;
+  transition: border linear .2s, box-shadow linear .2s;
+}
+textarea:focus,
+input[type="text"]:focus,
+input[type="password"]:focus,
+input[type="datetime"]:focus,
+input[type="datetime-local"]:focus,
+input[type="date"]:focus,
+input[type="month"]:focus,
+input[type="time"]:focus,
+input[type="week"]:focus,
+input[type="number"]:focus,
+input[type="email"]:focus,
+input[type="url"]:focus,
+input[type="search"]:focus,
+input[type="tel"]:focus,
+input[type="color"]:focus,
+.uneditable-input:focus {
+  border-color: rgba(82, 168, 236, 0.8);
+  outline: 0;
+  outline: thin dotted \9;
+  /* IE6-9 */
+
+  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);
+  -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);
+  box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);
+}
+input[type="radio"],
+input[type="checkbox"] {
+  margin: 4px 0 0;
+  *margin-top: 0;
+  /* IE7 */
+
+  margin-top: 1px \9;
+  /* IE8-9 */
+
+  line-height: normal;
+}
+input[type="file"],
+input[type="image"],
+input[type="submit"],
+input[type="reset"],
+input[type="button"],
+input[type="radio"],
+input[type="checkbox"] {
+  width: auto;
+}
+select,
+input[type="file"] {
+  height: 30px;
+  /* In IE7, the height of the select element cannot be changed by height, only font-size */
+
+  *margin-top: 4px;
+  /* For IE7, add top margin to align select with labels */
+
+  line-height: 30px;
+}
+select {
+  width: 220px;
+  border: 1px solid #cccccc;
+  background-color: #ffffff;
+}
+select[multiple],
+select[size] {
+  height: auto;
+}
+select:focus,
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+.uneditable-input,
+.uneditable-textarea {
+  color: #999999;
+  background-color: #fcfcfc;
+  border-color: #cccccc;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+  -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
+  cursor: not-allowed;
+}
+.uneditable-input {
+  overflow: hidden;
+  white-space: nowrap;
+}
+.uneditable-textarea {
+  width: auto;
+  height: auto;
+}
+input:-moz-placeholder,
+textarea:-moz-placeholder {
+  color: #999999;
+}
+input:-ms-input-placeholder,
+textarea:-ms-input-placeholder {
+  color: #999999;
+}
+input::-webkit-input-placeholder,
+textarea::-webkit-input-placeholder {
+  color: #999999;
+}
+.radio,
+.checkbox {
+  min-height: 20px;
+  padding-left: 20px;
+}
+.radio input[type="radio"],
+.checkbox input[type="checkbox"] {
+  float: left;
+  margin-left: -20px;
+}
+.controls > .radio:first-child,
+.controls > .checkbox:first-child {
+  padding-top: 5px;
+}
+.radio.inline,
+.checkbox.inline {
+  display: inline-block;
+  padding-top: 5px;
+  margin-bottom: 0;
+  vertical-align: middle;
+}
+.radio.inline + .radio.inline,
+.checkbox.inline + .checkbox.inline {
+  margin-left: 10px;
+}
+.input-mini {
+  width: 60px;
+}
+.input-small {
+  width: 90px;
+}
+.input-medium {
+  width: 150px;
+}
+.input-large {
+  width: 210px;
+}
+.input-xlarge {
+  width: 270px;
+}
+.input-xxlarge {
+  width: 530px;
+}
+input[class*="span"],
+select[class*="span"],
+textarea[class*="span"],
+.uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"] {
+  float: none;
+  margin-left: 0;
+}
+.input-append input[class*="span"],
+.input-append .uneditable-input[class*="span"],
+.input-prepend input[class*="span"],
+.input-prepend .uneditable-input[class*="span"],
+.row-fluid input[class*="span"],
+.row-fluid select[class*="span"],
+.row-fluid textarea[class*="span"],
+.row-fluid .uneditable-input[class*="span"],
+.row-fluid .input-prepend [class*="span"],
+.row-fluid .input-append [class*="span"] {
+  display: inline-block;
+}
+input,
+textarea,
+.uneditable-input {
+  margin-left: 0;
+}
+.controls-row [class*="span"] + [class*="span"] {
+  margin-left: 20px;
+}
+input.span12,
+textarea.span12,
+.uneditable-input.span12 {
+  width: 926px;
+}
+input.span11,
+textarea.span11,
+.uneditable-input.span11 {
+  width: 846px;
+}
+input.span10,
+textarea.span10,
+.uneditable-input.span10 {
+  width: 766px;
+}
+input.span9,
+textarea.span9,
+.uneditable-input.span9 {
+  width: 686px;
+}
+input.span8,
+textarea.span8,
+.uneditable-input.span8 {
+  width: 606px;
+}
+input.span7,
+textarea.span7,
+.uneditable-input.span7 {
+  width: 526px;
+}
+input.span6,
+textarea.span6,
+.uneditable-input.span6 {
+  width: 446px;
+}
+input.span5,
+textarea.span5,
+.uneditable-input.span5 {
+  width: 366px;
+}
+input.span4,
+textarea.span4,
+.uneditable-input.span4 {
+  width: 286px;
+}
+input.span3,
+textarea.span3,
+.uneditable-input.span3 {
+  width: 206px;
+}
+input.span2,
+textarea.span2,
+.uneditable-input.span2 {
+  width: 126px;
+}
+input.span1,
+textarea.span1,
+.uneditable-input.span1 {
+  width: 46px;
+}
+.controls-row {
+  *zoom: 1;
+}
+.controls-row:before,
+.controls-row:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.controls-row:after {
+  clear: both;
+}
+.controls-row [class*="span"],
+.row-fluid .controls-row [class*="span"] {
+  float: left;
+}
+.controls-row .checkbox[class*="span"],
+.controls-row .radio[class*="span"] {
+  padding-top: 5px;
+}
+input[disabled],
+select[disabled],
+textarea[disabled],
+input[readonly],
+select[readonly],
+textarea[readonly] {
+  cursor: not-allowed;
+  background-color: #eeeeee;
+}
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+input[type="radio"][readonly],
+input[type="checkbox"][readonly] {
+  background-color: transparent;
+}
+.control-group.warning .control-label,
+.control-group.warning .help-block,
+.control-group.warning .help-inline {
+  color: #c09853;
+}
+.control-group.warning .checkbox,
+.control-group.warning .radio,
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+  color: #c09853;
+}
+.control-group.warning input,
+.control-group.warning select,
+.control-group.warning textarea {
+  border-color: #c09853;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+.control-group.warning input:focus,
+.control-group.warning select:focus,
+.control-group.warning textarea:focus {
+  border-color: #a47e3c;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+.control-group.warning .input-prepend .add-on,
+.control-group.warning .input-append .add-on {
+  color: #c09853;
+  background-color: #fcf8e3;
+  border-color: #c09853;
+}
+.control-group.error .control-label,
+.control-group.error .help-block,
+.control-group.error .help-inline {
+  color: #b94a48;
+}
+.control-group.error .checkbox,
+.control-group.error .radio,
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+  color: #b94a48;
+}
+.control-group.error input,
+.control-group.error select,
+.control-group.error textarea {
+  border-color: #b94a48;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+.control-group.error input:focus,
+.control-group.error select:focus,
+.control-group.error textarea:focus {
+  border-color: #953b39;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+.control-group.error .input-prepend .add-on,
+.control-group.error .input-append .add-on {
+  color: #b94a48;
+  background-color: #f2dede;
+  border-color: #b94a48;
+}
+.control-group.success .control-label,
+.control-group.success .help-block,
+.control-group.success .help-inline {
+  color: #468847;
+}
+.control-group.success .checkbox,
+.control-group.success .radio,
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+  color: #468847;
+}
+.control-group.success input,
+.control-group.success select,
+.control-group.success textarea {
+  border-color: #468847;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+.control-group.success input:focus,
+.control-group.success select:focus,
+.control-group.success textarea:focus {
+  border-color: #356635;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+.control-group.success .input-prepend .add-on,
+.control-group.success .input-append .add-on {
+  color: #468847;
+  background-color: #dff0d8;
+  border-color: #468847;
+}
+.control-group.info .control-label,
+.control-group.info .help-block,
+.control-group.info .help-inline {
+  color: #3a87ad;
+}
+.control-group.info .checkbox,
+.control-group.info .radio,
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+  color: #3a87ad;
+}
+.control-group.info input,
+.control-group.info select,
+.control-group.info textarea {
+  border-color: #3a87ad;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+.control-group.info input:focus,
+.control-group.info select:focus,
+.control-group.info textarea:focus {
+  border-color: #2d6987;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
+}
+.control-group.info .input-prepend .add-on,
+.control-group.info .input-append .add-on {
+  color: #3a87ad;
+  background-color: #d9edf7;
+  border-color: #3a87ad;
+}
+input:focus:invalid,
+textarea:focus:invalid,
+select:focus:invalid {
+  color: #b94a48;
+  border-color: #ee5f5b;
+}
+input:focus:invalid:focus,
+textarea:focus:invalid:focus,
+select:focus:invalid:focus {
+  border-color: #e9322d;
+  -webkit-box-shadow: 0 0 6px #f8b9b7;
+  -moz-box-shadow: 0 0 6px #f8b9b7;
+  box-shadow: 0 0 6px #f8b9b7;
+}
+.form-actions {
+  padding: 19px 20px 20px;
+  margin-top: 20px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border-top: 1px solid #e5e5e5;
+  *zoom: 1;
+}
+.form-actions:before,
+.form-actions:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.form-actions:after {
+  clear: both;
+}
+.help-block,
+.help-inline {
+  color: #595959;
+}
+.help-block {
+  display: block;
+  margin-bottom: 10px;
+}
+.help-inline {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  vertical-align: middle;
+  padding-left: 5px;
+}
+.input-append,
+.input-prepend {
+  display: inline-block;
+  margin-bottom: 10px;
+  vertical-align: middle;
+  font-size: 0;
+  white-space: nowrap;
+}
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input,
+.input-append .dropdown-menu,
+.input-prepend .dropdown-menu,
+.input-append .popover,
+.input-prepend .popover {
+  font-size: 14px;
+}
+.input-append input,
+.input-prepend input,
+.input-append select,
+.input-prepend select,
+.input-append .uneditable-input,
+.input-prepend .uneditable-input {
+  position: relative;
+  margin-bottom: 0;
+  *margin-left: 0;
+  vertical-align: top;
+  -webkit-border-radius: 0 4px 4px 0;
+  -moz-border-radius: 0 4px 4px 0;
+  border-radius: 0 4px 4px 0;
+}
+.input-append input:focus,
+.input-prepend input:focus,
+.input-append select:focus,
+.input-prepend select:focus,
+.input-append .uneditable-input:focus,
+.input-prepend .uneditable-input:focus {
+  z-index: 2;
+}
+.input-append .add-on,
+.input-prepend .add-on {
+  display: inline-block;
+  width: auto;
+  height: 20px;
+  min-width: 16px;
+  padding: 4px 5px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 20px;
+  text-align: center;
+  text-shadow: 0 1px 0 #ffffff;
+  background-color: #eeeeee;
+  border: 1px solid #ccc;
+}
+.input-append .add-on,
+.input-prepend .add-on,
+.input-append .btn,
+.input-prepend .btn,
+.input-append .btn-group > .dropdown-toggle,
+.input-prepend .btn-group > .dropdown-toggle {
+  vertical-align: top;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.input-append .active,
+.input-prepend .active {
+  background-color: #a9dba9;
+  border-color: #46a546;
+}
+.input-prepend .add-on,
+.input-prepend .btn {
+  margin-right: -1px;
+}
+.input-prepend .add-on:first-child,
+.input-prepend .btn:first-child {
+  -webkit-border-radius: 4px 0 0 4px;
+  -moz-border-radius: 4px 0 0 4px;
+  border-radius: 4px 0 0 4px;
+}
+.input-append input,
+.input-append select,
+.input-append .uneditable-input {
+  -webkit-border-radius: 4px 0 0 4px;
+  -moz-border-radius: 4px 0 0 4px;
+  border-radius: 4px 0 0 4px;
+}
+.input-append input + .btn-group .btn:last-child,
+.input-append select + .btn-group .btn:last-child,
+.input-append .uneditable-input + .btn-group .btn:last-child {
+  -webkit-border-radius: 0 4px 4px 0;
+  -moz-border-radius: 0 4px 4px 0;
+  border-radius: 0 4px 4px 0;
+}
+.input-append .add-on,
+.input-append .btn,
+.input-append .btn-group {
+  margin-left: -1px;
+}
+.input-append .add-on:last-child,
+.input-append .btn:last-child,
+.input-append .btn-group:last-child > .dropdown-toggle {
+  -webkit-border-radius: 0 4px 4px 0;
+  -moz-border-radius: 0 4px 4px 0;
+  border-radius: 0 4px 4px 0;
+}
+.input-prepend.input-append input,
+.input-prepend.input-append select,
+.input-prepend.input-append .uneditable-input {
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.input-prepend.input-append input + .btn-group .btn,
+.input-prepend.input-append select + .btn-group .btn,
+.input-prepend.input-append .uneditable-input + .btn-group .btn {
+  -webkit-border-radius: 0 4px 4px 0;
+  -moz-border-radius: 0 4px 4px 0;
+  border-radius: 0 4px 4px 0;
+}
+.input-prepend.input-append .add-on:first-child,
+.input-prepend.input-append .btn:first-child {
+  margin-right: -1px;
+  -webkit-border-radius: 4px 0 0 4px;
+  -moz-border-radius: 4px 0 0 4px;
+  border-radius: 4px 0 0 4px;
+}
+.input-prepend.input-append .add-on:last-child,
+.input-prepend.input-append .btn:last-child {
+  margin-left: -1px;
+  -webkit-border-radius: 0 4px 4px 0;
+  -moz-border-radius: 0 4px 4px 0;
+  border-radius: 0 4px 4px 0;
+}
+.input-prepend.input-append .btn-group:first-child {
+  margin-left: 0;
+}
+input.search-query {
+  padding-right: 14px;
+  padding-right: 4px \9;
+  padding-left: 14px;
+  padding-left: 4px \9;
+  /* IE7-8 doesn't have border-radius, so don't indent the padding */
+
+  margin-bottom: 0;
+  -webkit-border-radius: 15px;
+  -moz-border-radius: 15px;
+  border-radius: 15px;
+}
+/* Allow for input prepend/append in search forms */
+.form-search .input-append .search-query,
+.form-search .input-prepend .search-query {
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.form-search .input-append .search-query {
+  -webkit-border-radius: 14px 0 0 14px;
+  -moz-border-radius: 14px 0 0 14px;
+  border-radius: 14px 0 0 14px;
+}
+.form-search .input-append .btn {
+  -webkit-border-radius: 0 14px 14px 0;
+  -moz-border-radius: 0 14px 14px 0;
+  border-radius: 0 14px 14px 0;
+}
+.form-search .input-prepend .search-query {
+  -webkit-border-radius: 0 14px 14px 0;
+  -moz-border-radius: 0 14px 14px 0;
+  border-radius: 0 14px 14px 0;
+}
+.form-search .input-prepend .btn {
+  -webkit-border-radius: 14px 0 0 14px;
+  -moz-border-radius: 14px 0 0 14px;
+  border-radius: 14px 0 0 14px;
+}
+.form-search input,
+.form-inline input,
+.form-horizontal input,
+.form-search textarea,
+.form-inline textarea,
+.form-horizontal textarea,
+.form-search select,
+.form-inline select,
+.form-horizontal select,
+.form-search .help-inline,
+.form-inline .help-inline,
+.form-horizontal .help-inline,
+.form-search .uneditable-input,
+.form-inline .uneditable-input,
+.form-horizontal .uneditable-input,
+.form-search .input-prepend,
+.form-inline .input-prepend,
+.form-horizontal .input-prepend,
+.form-search .input-append,
+.form-inline .input-append,
+.form-horizontal .input-append {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  margin-bottom: 0;
+  vertical-align: middle;
+}
+.form-search .hide,
+.form-inline .hide,
+.form-horizontal .hide {
+  display: none;
+}
+.form-search label,
+.form-inline label,
+.form-search .btn-group,
+.form-inline .btn-group {
+  display: inline-block;
+}
+.form-search .input-append,
+.form-inline .input-append,
+.form-search .input-prepend,
+.form-inline .input-prepend {
+  margin-bottom: 0;
+}
+.form-search .radio,
+.form-search .checkbox,
+.form-inline .radio,
+.form-inline .checkbox {
+  padding-left: 0;
+  margin-bottom: 0;
+  vertical-align: middle;
+}
+.form-search .radio input[type="radio"],
+.form-search .checkbox input[type="checkbox"],
+.form-inline .radio input[type="radio"],
+.form-inline .checkbox input[type="checkbox"] {
+  float: left;
+  margin-right: 3px;
+  margin-left: 0;
+}
+.control-group {
+  margin-bottom: 10px;
+}
+legend + .control-group {
+  margin-top: 20px;
+  -webkit-margin-top-collapse: separate;
+}
+.form-horizontal .control-group {
+  margin-bottom: 20px;
+  *zoom: 1;
+}
+.form-horizontal .control-group:before,
+.form-horizontal .control-group:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.form-horizontal .control-group:after {
+  clear: both;
+}
+.form-horizontal .control-label {
+  float: left;
+  width: 160px;
+  padding-top: 5px;
+  text-align: right;
+}
+.form-horizontal .controls {
+  *display: inline-block;
+  *padding-left: 20px;
+  margin-left: 180px;
+  *margin-left: 0;
+}
+.form-horizontal .controls:first-child {
+  *padding-left: 180px;
+}
+.form-horizontal .help-block {
+  margin-bottom: 0;
+}
+.form-horizontal input + .help-block,
+.form-horizontal select + .help-block,
+.form-horizontal textarea + .help-block,
+.form-horizontal .uneditable-input + .help-block,
+.form-horizontal .input-prepend + .help-block,
+.form-horizontal .input-append + .help-block {
+  margin-top: 10px;
+}
+.form-horizontal .form-actions {
+  padding-left: 180px;
+}
+.btn {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  padding: 4px 12px;
+  margin-bottom: 0;
+  font-size: 14px;
+  line-height: 20px;
+  text-align: center;
+  vertical-align: middle;
+  cursor: pointer;
+  color: #333333;
+  text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+  background-color: #f5f5f5;
+  background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+  background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+  background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
+  border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  *background-color: #e6e6e6;
+  /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  border: 1px solid #cccccc;
+  *border: 0;
+  border-bottom-color: #b3b3b3;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  *margin-left: .3em;
+  -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+  -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+}
+.btn:hover,
+.btn:focus,
+.btn:active,
+.btn.active,
+.btn.disabled,
+.btn[disabled] {
+  color: #333333;
+  background-color: #e6e6e6;
+  *background-color: #d9d9d9;
+}
+.btn:active,
+.btn.active {
+  background-color: #cccccc \9;
+}
+.btn:first-child {
+  *margin-left: 0;
+}
+.btn:hover,
+.btn:focus {
+  color: #333333;
+  text-decoration: none;
+  background-position: 0 -15px;
+  -webkit-transition: background-position 0.1s linear;
+  -moz-transition: background-position 0.1s linear;
+  -o-transition: background-position 0.1s linear;
+  transition: background-position 0.1s linear;
+}
+.btn:focus {
+  outline: thin dotted #333;
+  outline: 5px auto -webkit-focus-ring-color;
+  outline-offset: -2px;
+}
+.btn.active,
+.btn:active {
+  background-image: none;
+  outline: 0;
+  -webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);
+  -moz-box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);
+  box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);
+}
+.btn.disabled,
+.btn[disabled] {
+  cursor: default;
+  background-image: none;
+  opacity: 0.65;
+  filter: alpha(opacity=65);
+  -webkit-box-shadow: none;
+  -moz-box-shadow: none;
+  box-shadow: none;
+}
+.btn-large {
+  padding: 11px 19px;
+  font-size: 17.5px;
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+}
+.btn-large [class^="icon-"],
+.btn-large [class*=" icon-"] {
+  margin-top: 4px;
+}
+.btn-small {
+  padding: 2px 10px;
+  font-size: 11.9px;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.btn-small [class^="icon-"],
+.btn-small [class*=" icon-"] {
+  margin-top: 0;
+}
+.btn-mini [class^="icon-"],
+.btn-mini [class*=" icon-"] {
+  margin-top: -1px;
+}
+.btn-mini {
+  padding: 0 6px;
+  font-size: 10.5px;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.btn-block {
+  display: block;
+  width: 100%;
+  padding-left: 0;
+  padding-right: 0;
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+}
+.btn-block + .btn-block {
+  margin-top: 5px;
+}
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+.btn-primary.active,
+.btn-warning.active,
+.btn-danger.active,
+.btn-success.active,
+.btn-info.active,
+.btn-inverse.active {
+  color: rgba(255, 255, 255, 0.75);
+}
+.btn-primary {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #006dcc;
+  background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
+  background-image: -o-linear-gradient(top, #0088cc, #0044cc);
+  background-image: linear-gradient(to bottom, #0088cc, #0044cc);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
+  border-color: #0044cc #0044cc #002a80;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  *background-color: #0044cc;
+  /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active,
+.btn-primary.disabled,
+.btn-primary[disabled] {
+  color: #ffffff;
+  background-color: #0044cc;
+  *background-color: #003bb3;
+}
+.btn-primary:active,
+.btn-primary.active {
+  background-color: #003399 \9;
+}
+.btn-warning {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #faa732;
+  background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+  background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+  background-image: -o-linear-gradient(top, #fbb450, #f89406);
+  background-image: linear-gradient(to bottom, #fbb450, #f89406);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+  border-color: #f89406 #f89406 #ad6704;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  *background-color: #f89406;
+  /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning:active,
+.btn-warning.active,
+.btn-warning.disabled,
+.btn-warning[disabled] {
+  color: #ffffff;
+  background-color: #f89406;
+  *background-color: #df8505;
+}
+.btn-warning:active,
+.btn-warning.active {
+  background-color: #c67605 \9;
+}
+.btn-danger {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #da4f49;
+  background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
+  background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
+  background-image: linear-gradient(to bottom, #ee5f5b, #bd362f);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);
+  border-color: #bd362f #bd362f #802420;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  *background-color: #bd362f;
+  /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger:active,
+.btn-danger.active,
+.btn-danger.disabled,
+.btn-danger[disabled] {
+  color: #ffffff;
+  background-color: #bd362f;
+  *background-color: #a9302a;
+}
+.btn-danger:active,
+.btn-danger.active {
+  background-color: #942a25 \9;
+}
+.btn-success {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #5bb75b;
+  background-image: -moz-linear-gradient(top, #62c462, #51a351);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
+  background-image: -webkit-linear-gradient(top, #62c462, #51a351);
+  background-image: -o-linear-gradient(top, #62c462, #51a351);
+  background-image: linear-gradient(to bottom, #62c462, #51a351);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);
+  border-color: #51a351 #51a351 #387038;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  *background-color: #51a351;
+  /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.btn-success:hover,
+.btn-success:focus,
+.btn-success:active,
+.btn-success.active,
+.btn-success.disabled,
+.btn-success[disabled] {
+  color: #ffffff;
+  background-color: #51a351;
+  *background-color: #499249;
+}
+.btn-success:active,
+.btn-success.active {
+  background-color: #408140 \9;
+}
+.btn-info {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #49afcd;
+  background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
+  background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
+  background-image: linear-gradient(to bottom, #5bc0de, #2f96b4);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);
+  border-color: #2f96b4 #2f96b4 #1f6377;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  *background-color: #2f96b4;
+  /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.btn-info:hover,
+.btn-info:focus,
+.btn-info:active,
+.btn-info.active,
+.btn-info.disabled,
+.btn-info[disabled] {
+  color: #ffffff;
+  background-color: #2f96b4;
+  *background-color: #2a85a0;
+}
+.btn-info:active,
+.btn-info.active {
+  background-color: #24748c \9;
+}
+.btn-inverse {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #363636;
+  background-image: -moz-linear-gradient(top, #444444, #222222);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));
+  background-image: -webkit-linear-gradient(top, #444444, #222222);
+  background-image: -o-linear-gradient(top, #444444, #222222);
+  background-image: linear-gradient(to bottom, #444444, #222222);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);
+  border-color: #222222 #222222 #000000;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  *background-color: #222222;
+  /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.btn-inverse:hover,
+.btn-inverse:focus,
+.btn-inverse:active,
+.btn-inverse.active,
+.btn-inverse.disabled,
+.btn-inverse[disabled] {
+  color: #ffffff;
+  background-color: #222222;
+  *background-color: #151515;
+}
+.btn-inverse:active,
+.btn-inverse.active {
+  background-color: #080808 \9;
+}
+button.btn,
+input[type="submit"].btn {
+  *padding-top: 3px;
+  *padding-bottom: 3px;
+}
+button.btn::-moz-focus-inner,
+input[type="submit"].btn::-moz-focus-inner {
+  padding: 0;
+  border: 0;
+}
+button.btn.btn-large,
+input[type="submit"].btn.btn-large {
+  *padding-top: 7px;
+  *padding-bottom: 7px;
+}
+button.btn.btn-small,
+input[type="submit"].btn.btn-small {
+  *padding-top: 3px;
+  *padding-bottom: 3px;
+}
+button.btn.btn-mini,
+input[type="submit"].btn.btn-mini {
+  *padding-top: 1px;
+  *padding-bottom: 1px;
+}
+.btn-link,
+.btn-link:active,
+.btn-link[disabled] {
+  background-color: transparent;
+  background-image: none;
+  -webkit-box-shadow: none;
+  -moz-box-shadow: none;
+  box-shadow: none;
+}
+.btn-link {
+  border-color: transparent;
+  cursor: pointer;
+  color: #0088cc;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.btn-link:hover,
+.btn-link:focus {
+  color: #005580;
+  text-decoration: underline;
+  background-color: transparent;
+}
+.btn-link[disabled]:hover,
+.btn-link[disabled]:focus {
+  color: #333333;
+  text-decoration: none;
+}
+.btn-group {
+  position: relative;
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  font-size: 0;
+  vertical-align: middle;
+  white-space: nowrap;
+  *margin-left: .3em;
+}
+.btn-group:first-child {
+  *margin-left: 0;
+}
+.btn-group + .btn-group {
+  margin-left: 5px;
+}
+.btn-toolbar {
+  font-size: 0;
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+.btn-toolbar > .btn + .btn,
+.btn-toolbar > .btn-group + .btn,
+.btn-toolbar > .btn + .btn-group {
+  margin-left: 5px;
+}
+.btn-group > .btn {
+  position: relative;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.btn-group > .btn + .btn {
+  margin-left: -1px;
+}
+.btn-group > .btn,
+.btn-group > .dropdown-menu,
+.btn-group > .popover {
+  font-size: 14px;
+}
+.btn-group > .btn-mini {
+  font-size: 10.5px;
+}
+.btn-group > .btn-small {
+  font-size: 11.9px;
+}
+.btn-group > .btn-large {
+  font-size: 17.5px;
+}
+.btn-group > .btn:first-child {
+  margin-left: 0;
+  -webkit-border-top-left-radius: 4px;
+  -moz-border-radius-topleft: 4px;
+  border-top-left-radius: 4px;
+  -webkit-border-bottom-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+  border-bottom-left-radius: 4px;
+}
+.btn-group > .btn:last-child,
+.btn-group > .dropdown-toggle {
+  -webkit-border-top-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  border-top-right-radius: 4px;
+  -webkit-border-bottom-right-radius: 4px;
+  -moz-border-radius-bottomright: 4px;
+  border-bottom-right-radius: 4px;
+}
+.btn-group > .btn.large:first-child {
+  margin-left: 0;
+  -webkit-border-top-left-radius: 6px;
+  -moz-border-radius-topleft: 6px;
+  border-top-left-radius: 6px;
+  -webkit-border-bottom-left-radius: 6px;
+  -moz-border-radius-bottomleft: 6px;
+  border-bottom-left-radius: 6px;
+}
+.btn-group > .btn.large:last-child,
+.btn-group > .large.dropdown-toggle {
+  -webkit-border-top-right-radius: 6px;
+  -moz-border-radius-topright: 6px;
+  border-top-right-radius: 6px;
+  -webkit-border-bottom-right-radius: 6px;
+  -moz-border-radius-bottomright: 6px;
+  border-bottom-right-radius: 6px;
+}
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active {
+  z-index: 2;
+}
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+  outline: 0;
+}
+.btn-group > .btn + .dropdown-toggle {
+  padding-left: 8px;
+  padding-right: 8px;
+  -webkit-box-shadow: inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+  -moz-box-shadow: inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+  box-shadow: inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
+  *padding-top: 5px;
+  *padding-bottom: 5px;
+}
+.btn-group > .btn-mini + .dropdown-toggle {
+  padding-left: 5px;
+  padding-right: 5px;
+  *padding-top: 2px;
+  *padding-bottom: 2px;
+}
+.btn-group > .btn-small + .dropdown-toggle {
+  *padding-top: 5px;
+  *padding-bottom: 4px;
+}
+.btn-group > .btn-large + .dropdown-toggle {
+  padding-left: 12px;
+  padding-right: 12px;
+  *padding-top: 7px;
+  *padding-bottom: 7px;
+}
+.btn-group.open .dropdown-toggle {
+  background-image: none;
+  -webkit-box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);
+  -moz-box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);
+  box-shadow: inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);
+}
+.btn-group.open .btn.dropdown-toggle {
+  background-color: #e6e6e6;
+}
+.btn-group.open .btn-primary.dropdown-toggle {
+  background-color: #0044cc;
+}
+.btn-group.open .btn-warning.dropdown-toggle {
+  background-color: #f89406;
+}
+.btn-group.open .btn-danger.dropdown-toggle {
+  background-color: #bd362f;
+}
+.btn-group.open .btn-success.dropdown-toggle {
+  background-color: #51a351;
+}
+.btn-group.open .btn-info.dropdown-toggle {
+  background-color: #2f96b4;
+}
+.btn-group.open .btn-inverse.dropdown-toggle {
+  background-color: #222222;
+}
+.btn .caret {
+  margin-top: 8px;
+  margin-left: 0;
+}
+.btn-large .caret {
+  margin-top: 6px;
+}
+.btn-large .caret {
+  border-left-width: 5px;
+  border-right-width: 5px;
+  border-top-width: 5px;
+}
+.btn-mini .caret,
+.btn-small .caret {
+  margin-top: 8px;
+}
+.dropup .btn-large .caret {
+  border-bottom-width: 5px;
+}
+.btn-primary .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret,
+.btn-success .caret,
+.btn-inverse .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+.btn-group-vertical {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+}
+.btn-group-vertical > .btn {
+  display: block;
+  float: none;
+  max-width: 100%;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.btn-group-vertical > .btn + .btn {
+  margin-left: 0;
+  margin-top: -1px;
+}
+.btn-group-vertical > .btn:first-child {
+  -webkit-border-radius: 4px 4px 0 0;
+  -moz-border-radius: 4px 4px 0 0;
+  border-radius: 4px 4px 0 0;
+}
+.btn-group-vertical > .btn:last-child {
+  -webkit-border-radius: 0 0 4px 4px;
+  -moz-border-radius: 0 0 4px 4px;
+  border-radius: 0 0 4px 4px;
+}
+.btn-group-vertical > .btn-large:first-child {
+  -webkit-border-radius: 6px 6px 0 0;
+  -moz-border-radius: 6px 6px 0 0;
+  border-radius: 6px 6px 0 0;
+}
+.btn-group-vertical > .btn-large:last-child {
+  -webkit-border-radius: 0 0 6px 6px;
+  -moz-border-radius: 0 0 6px 6px;
+  border-radius: 0 0 6px 6px;
+}
+.nav {
+  margin-left: 0;
+  margin-bottom: 20px;
+  list-style: none;
+}
+.nav > li > a {
+  display: block;
+}
+.nav > li > a:hover,
+.nav > li > a:focus {
+  text-decoration: none;
+  background-color: #eeeeee;
+}
+.nav > li > a > img {
+  max-width: none;
+}
+.nav > .pull-right {
+  float: right;
+}
+.nav-header {
+  display: block;
+  padding: 3px 15px;
+  font-size: 11px;
+  font-weight: bold;
+  line-height: 20px;
+  color: #999999;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+  text-transform: uppercase;
+}
+.nav li + .nav-header {
+  margin-top: 9px;
+}
+.nav-list {
+  padding-left: 15px;
+  padding-right: 15px;
+  margin-bottom: 0;
+}
+.nav-list > li > a,
+.nav-list .nav-header {
+  margin-left: -15px;
+  margin-right: -15px;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+}
+.nav-list > li > a {
+  padding: 3px 15px;
+}
+.nav-list > .active > a,
+.nav-list > .active > a:hover,
+.nav-list > .active > a:focus {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+  background-color: #0088cc;
+}
+.nav-list [class^="icon-"],
+.nav-list [class*=" icon-"] {
+  margin-right: 2px;
+}
+.nav-list .divider {
+  *width: 100%;
+  height: 1px;
+  margin: 9px 1px;
+  *margin: -5px 0 5px;
+  overflow: hidden;
+  background-color: #e5e5e5;
+  border-bottom: 1px solid #ffffff;
+}
+.nav-tabs,
+.nav-pills {
+  *zoom: 1;
+}
+.nav-tabs:before,
+.nav-pills:before,
+.nav-tabs:after,
+.nav-pills:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.nav-tabs:after,
+.nav-pills:after {
+  clear: both;
+}
+.nav-tabs > li,
+.nav-pills > li {
+  float: left;
+}
+.nav-tabs > li > a,
+.nav-pills > li > a {
+  padding-right: 12px;
+  padding-left: 12px;
+  margin-right: 2px;
+  line-height: 14px;
+}
+.nav-tabs {
+  border-bottom: 1px solid #ddd;
+}
+.nav-tabs > li {
+  margin-bottom: -1px;
+}
+.nav-tabs > li > a {
+  padding-top: 8px;
+  padding-bottom: 8px;
+  line-height: 20px;
+  border: 1px solid transparent;
+  -webkit-border-radius: 4px 4px 0 0;
+  -moz-border-radius: 4px 4px 0 0;
+  border-radius: 4px 4px 0 0;
+}
+.nav-tabs > li > a:hover,
+.nav-tabs > li > a:focus {
+  border-color: #eeeeee #eeeeee #dddddd;
+}
+.nav-tabs > .active > a,
+.nav-tabs > .active > a:hover,
+.nav-tabs > .active > a:focus {
+  color: #555555;
+  background-color: #ffffff;
+  border: 1px solid #ddd;
+  border-bottom-color: transparent;
+  cursor: default;
+}
+.nav-pills > li > a {
+  padding-top: 8px;
+  padding-bottom: 8px;
+  margin-top: 2px;
+  margin-bottom: 2px;
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+}
+.nav-pills > .active > a,
+.nav-pills > .active > a:hover,
+.nav-pills > .active > a:focus {
+  color: #ffffff;
+  background-color: #0088cc;
+}
+.nav-stacked > li {
+  float: none;
+}
+.nav-stacked > li > a {
+  margin-right: 0;
+}
+.nav-tabs.nav-stacked {
+  border-bottom: 0;
+}
+.nav-tabs.nav-stacked > li > a {
+  border: 1px solid #ddd;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.nav-tabs.nav-stacked > li:first-child > a {
+  -webkit-border-top-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  border-top-right-radius: 4px;
+  -webkit-border-top-left-radius: 4px;
+  -moz-border-radius-topleft: 4px;
+  border-top-left-radius: 4px;
+}
+.nav-tabs.nav-stacked > li:last-child > a {
+  -webkit-border-bottom-right-radius: 4px;
+  -moz-border-radius-bottomright: 4px;
+  border-bottom-right-radius: 4px;
+  -webkit-border-bottom-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+  border-bottom-left-radius: 4px;
+}
+.nav-tabs.nav-stacked > li > a:hover,
+.nav-tabs.nav-stacked > li > a:focus {
+  border-color: #ddd;
+  z-index: 2;
+}
+.nav-pills.nav-stacked > li > a {
+  margin-bottom: 3px;
+}
+.nav-pills.nav-stacked > li:last-child > a {
+  margin-bottom: 1px;
+}
+.nav-tabs .dropdown-menu {
+  -webkit-border-radius: 0 0 6px 6px;
+  -moz-border-radius: 0 0 6px 6px;
+  border-radius: 0 0 6px 6px;
+}
+.nav-pills .dropdown-menu {
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+}
+.nav .dropdown-toggle .caret {
+  border-top-color: #0088cc;
+  border-bottom-color: #0088cc;
+  margin-top: 6px;
+}
+.nav .dropdown-toggle:hover .caret,
+.nav .dropdown-toggle:focus .caret {
+  border-top-color: #005580;
+  border-bottom-color: #005580;
+}
+/* move down carets for tabs */
+.nav-tabs .dropdown-toggle .caret {
+  margin-top: 8px;
+}
+.nav .active .dropdown-toggle .caret {
+  border-top-color: #fff;
+  border-bottom-color: #fff;
+}
+.nav-tabs .active .dropdown-toggle .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+.nav > .dropdown.active > a:hover,
+.nav > .dropdown.active > a:focus {
+  cursor: pointer;
+}
+.nav-tabs .open .dropdown-toggle,
+.nav-pills .open .dropdown-toggle,
+.nav > li.dropdown.open.active > a:hover,
+.nav > li.dropdown.open.active > a:focus {
+  color: #ffffff;
+  background-color: #999999;
+  border-color: #999999;
+}
+.nav li.dropdown.open .caret,
+.nav li.dropdown.open.active .caret,
+.nav li.dropdown.open a:hover .caret,
+.nav li.dropdown.open a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+  opacity: 1;
+  filter: alpha(opacity=100);
+}
+.tabs-stacked .open > a:hover,
+.tabs-stacked .open > a:focus {
+  border-color: #999999;
+}
+.tabbable {
+  *zoom: 1;
+}
+.tabbable:before,
+.tabbable:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.tabbable:after {
+  clear: both;
+}
+.tab-content {
+  overflow: auto;
+}
+.tabs-below > .nav-tabs,
+.tabs-right > .nav-tabs,
+.tabs-left > .nav-tabs {
+  border-bottom: 0;
+}
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+  display: none;
+}
+.tab-content > .active,
+.pill-content > .active {
+  display: block;
+}
+.tabs-below > .nav-tabs {
+  border-top: 1px solid #ddd;
+}
+.tabs-below > .nav-tabs > li {
+  margin-top: -1px;
+  margin-bottom: 0;
+}
+.tabs-below > .nav-tabs > li > a {
+  -webkit-border-radius: 0 0 4px 4px;
+  -moz-border-radius: 0 0 4px 4px;
+  border-radius: 0 0 4px 4px;
+}
+.tabs-below > .nav-tabs > li > a:hover,
+.tabs-below > .nav-tabs > li > a:focus {
+  border-bottom-color: transparent;
+  border-top-color: #ddd;
+}
+.tabs-below > .nav-tabs > .active > a,
+.tabs-below > .nav-tabs > .active > a:hover,
+.tabs-below > .nav-tabs > .active > a:focus {
+  border-color: transparent #ddd #ddd #ddd;
+}
+.tabs-left > .nav-tabs > li,
+.tabs-right > .nav-tabs > li {
+  float: none;
+}
+.tabs-left > .nav-tabs > li > a,
+.tabs-right > .nav-tabs > li > a {
+  min-width: 74px;
+  margin-right: 0;
+  margin-bottom: 3px;
+}
+.tabs-left > .nav-tabs {
+  float: left;
+  margin-right: 19px;
+  border-right: 1px solid #ddd;
+}
+.tabs-left > .nav-tabs > li > a {
+  margin-right: -1px;
+  -webkit-border-radius: 4px 0 0 4px;
+  -moz-border-radius: 4px 0 0 4px;
+  border-radius: 4px 0 0 4px;
+}
+.tabs-left > .nav-tabs > li > a:hover,
+.tabs-left > .nav-tabs > li > a:focus {
+  border-color: #eeeeee #dddddd #eeeeee #eeeeee;
+}
+.tabs-left > .nav-tabs .active > a,
+.tabs-left > .nav-tabs .active > a:hover,
+.tabs-left > .nav-tabs .active > a:focus {
+  border-color: #ddd transparent #ddd #ddd;
+  *border-right-color: #ffffff;
+}
+.tabs-right > .nav-tabs {
+  float: right;
+  margin-left: 19px;
+  border-left: 1px solid #ddd;
+}
+.tabs-right > .nav-tabs > li > a {
+  margin-left: -1px;
+  -webkit-border-radius: 0 4px 4px 0;
+  -moz-border-radius: 0 4px 4px 0;
+  border-radius: 0 4px 4px 0;
+}
+.tabs-right > .nav-tabs > li > a:hover,
+.tabs-right > .nav-tabs > li > a:focus {
+  border-color: #eeeeee #eeeeee #eeeeee #dddddd;
+}
+.tabs-right > .nav-tabs .active > a,
+.tabs-right > .nav-tabs .active > a:hover,
+.tabs-right > .nav-tabs .active > a:focus {
+  border-color: #ddd #ddd #ddd transparent;
+  *border-left-color: #ffffff;
+}
+.nav > .disabled > a {
+  color: #999999;
+}
+.nav > .disabled > a:hover,
+.nav > .disabled > a:focus {
+  text-decoration: none;
+  background-color: transparent;
+  cursor: default;
+}
+.navbar {
+  overflow: visible;
+  margin-bottom: 20px;
+  *position: relative;
+  *z-index: 2;
+}
+.navbar-inner {
+  min-height: 40px;
+  padding-left: 20px;
+  padding-right: 20px;
+  background-color: #fafafa;
+  background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));
+  background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);
+  background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);
+  background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);
+  border: 1px solid #d4d4d4;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+  -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
+  *zoom: 1;
+}
+.navbar-inner:before,
+.navbar-inner:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.navbar-inner:after {
+  clear: both;
+}
+.navbar .container {
+  width: auto;
+}
+.nav-collapse.collapse {
+  height: auto;
+  overflow: visible;
+}
+.navbar .brand {
+  float: left;
+  display: block;
+  padding: 10px 20px 10px;
+  margin-left: -20px;
+  font-size: 20px;
+  font-weight: 200;
+  color: #777777;
+  text-shadow: 0 1px 0 #ffffff;
+}
+.navbar .brand:hover,
+.navbar .brand:focus {
+  text-decoration: none;
+}
+.navbar-text {
+  margin-bottom: 0;
+  line-height: 40px;
+  color: #777777;
+}
+.navbar-link {
+  color: #777777;
+}
+.navbar-link:hover,
+.navbar-link:focus {
+  color: #333333;
+}
+.navbar .divider-vertical {
+  height: 40px;
+  margin: 0 9px;
+  border-left: 1px solid #f2f2f2;
+  border-right: 1px solid #ffffff;
+}
+.navbar .btn,
+.navbar .btn-group {
+  margin-top: 5px;
+}
+.navbar .btn-group .btn,
+.navbar .input-prepend .btn,
+.navbar .input-append .btn,
+.navbar .input-prepend .btn-group,
+.navbar .input-append .btn-group {
+  margin-top: 0;
+}
+.navbar-form {
+  margin-bottom: 0;
+  *zoom: 1;
+}
+.navbar-form:before,
+.navbar-form:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.navbar-form:after {
+  clear: both;
+}
+.navbar-form input,
+.navbar-form select,
+.navbar-form .radio,
+.navbar-form .checkbox {
+  margin-top: 5px;
+}
+.navbar-form input,
+.navbar-form select,
+.navbar-form .btn {
+  display: inline-block;
+  margin-bottom: 0;
+}
+.navbar-form input[type="image"],
+.navbar-form input[type="checkbox"],
+.navbar-form input[type="radio"] {
+  margin-top: 3px;
+}
+.navbar-form .input-append,
+.navbar-form .input-prepend {
+  margin-top: 5px;
+  white-space: nowrap;
+}
+.navbar-form .input-append input,
+.navbar-form .input-prepend input {
+  margin-top: 0;
+}
+.navbar-search {
+  position: relative;
+  float: left;
+  margin-top: 5px;
+  margin-bottom: 0;
+}
+.navbar-search .search-query {
+  margin-bottom: 0;
+  padding: 4px 14px;
+  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+  font-size: 13px;
+  font-weight: normal;
+  line-height: 1;
+  -webkit-border-radius: 15px;
+  -moz-border-radius: 15px;
+  border-radius: 15px;
+}
+.navbar-static-top {
+  position: static;
+  margin-bottom: 0;
+}
+.navbar-static-top .navbar-inner {
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+  position: fixed;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+  margin-bottom: 0;
+}
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+  border-width: 0 0 1px;
+}
+.navbar-fixed-bottom .navbar-inner {
+  border-width: 1px 0 0;
+}
+.navbar-fixed-top .navbar-inner,
+.navbar-fixed-bottom .navbar-inner {
+  padding-left: 0;
+  padding-right: 0;
+  -webkit-border-radius: 0;
+  -moz-border-radius: 0;
+  border-radius: 0;
+}
+.navbar-static-top .container,
+.navbar-fixed-top .container,
+.navbar-fixed-bottom .container {
+  width: 940px;
+}
+.navbar-fixed-top {
+  top: 0;
+}
+.navbar-fixed-top .navbar-inner,
+.navbar-static-top .navbar-inner {
+  -webkit-box-shadow: 0 1px 10px rgba(0,0,0,.1);
+  -moz-box-shadow: 0 1px 10px rgba(0,0,0,.1);
+  box-shadow: 0 1px 10px rgba(0,0,0,.1);
+}
+.navbar-fixed-bottom {
+  bottom: 0;
+}
+.navbar-fixed-bottom .navbar-inner {
+  -webkit-box-shadow: 0 -1px 10px rgba(0,0,0,.1);
+  -moz-box-shadow: 0 -1px 10px rgba(0,0,0,.1);
+  box-shadow: 0 -1px 10px rgba(0,0,0,.1);
+}
+.navbar .nav {
+  position: relative;
+  left: 0;
+  display: block;
+  float: left;
+  margin: 0 10px 0 0;
+}
+.navbar .nav.pull-right {
+  float: right;
+  margin-right: 0;
+}
+.navbar .nav > li {
+  float: left;
+}
+.navbar .nav > li > a {
+  float: none;
+  padding: 10px 15px 10px;
+  color: #777777;
+  text-decoration: none;
+  text-shadow: 0 1px 0 #ffffff;
+}
+.navbar .nav .dropdown-toggle .caret {
+  margin-top: 8px;
+}
+.navbar .nav > li > a:focus,
+.navbar .nav > li > a:hover {
+  background-color: transparent;
+  color: #333333;
+  text-decoration: none;
+}
+.navbar .nav > .active > a,
+.navbar .nav > .active > a:hover,
+.navbar .nav > .active > a:focus {
+  color: #555555;
+  text-decoration: none;
+  background-color: #e5e5e5;
+  -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+  -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+  box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
+}
+.navbar .btn-navbar {
+  display: none;
+  float: right;
+  padding: 7px 10px;
+  margin-left: 5px;
+  margin-right: 5px;
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #ededed;
+  background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
+  background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
+  background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
+  background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
+  border-color: #e5e5e5 #e5e5e5 #bfbfbf;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  *background-color: #e5e5e5;
+  /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);
+  -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);
+  box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);
+}
+.navbar .btn-navbar:hover,
+.navbar .btn-navbar:focus,
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active,
+.navbar .btn-navbar.disabled,
+.navbar .btn-navbar[disabled] {
+  color: #ffffff;
+  background-color: #e5e5e5;
+  *background-color: #d9d9d9;
+}
+.navbar .btn-navbar:active,
+.navbar .btn-navbar.active {
+  background-color: #cccccc \9;
+}
+.navbar .btn-navbar .icon-bar {
+  display: block;
+  width: 18px;
+  height: 2px;
+  background-color: #f5f5f5;
+  -webkit-border-radius: 1px;
+  -moz-border-radius: 1px;
+  border-radius: 1px;
+  -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+  -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
+}
+.btn-navbar .icon-bar + .icon-bar {
+  margin-top: 3px;
+}
+.navbar .nav > li > .dropdown-menu:before {
+  content: '';
+  display: inline-block;
+  border-left: 7px solid transparent;
+  border-right: 7px solid transparent;
+  border-bottom: 7px solid #ccc;
+  border-bottom-color: rgba(0, 0, 0, 0.2);
+  position: absolute;
+  top: -7px;
+  left: 9px;
+}
+.navbar .nav > li > .dropdown-menu:after {
+  content: '';
+  display: inline-block;
+  border-left: 6px solid transparent;
+  border-right: 6px solid transparent;
+  border-bottom: 6px solid #ffffff;
+  position: absolute;
+  top: -6px;
+  left: 10px;
+}
+.navbar-fixed-bottom .nav > li > .dropdown-menu:before {
+  border-top: 7px solid #ccc;
+  border-top-color: rgba(0, 0, 0, 0.2);
+  border-bottom: 0;
+  bottom: -7px;
+  top: auto;
+}
+.navbar-fixed-bottom .nav > li > .dropdown-menu:after {
+  border-top: 6px solid #ffffff;
+  border-bottom: 0;
+  bottom: -6px;
+  top: auto;
+}
+.navbar .nav li.dropdown > a:hover .caret,
+.navbar .nav li.dropdown > a:focus .caret {
+  border-top-color: #333333;
+  border-bottom-color: #333333;
+}
+.navbar .nav li.dropdown.open > .dropdown-toggle,
+.navbar .nav li.dropdown.active > .dropdown-toggle,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle {
+  background-color: #e5e5e5;
+  color: #555555;
+}
+.navbar .nav li.dropdown > .dropdown-toggle .caret {
+  border-top-color: #777777;
+  border-bottom-color: #777777;
+}
+.navbar .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {
+  border-top-color: #555555;
+  border-bottom-color: #555555;
+}
+.navbar .pull-right > li > .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right {
+  left: auto;
+  right: 0;
+}
+.navbar .pull-right > li > .dropdown-menu:before,
+.navbar .nav > li > .dropdown-menu.pull-right:before {
+  left: auto;
+  right: 12px;
+}
+.navbar .pull-right > li > .dropdown-menu:after,
+.navbar .nav > li > .dropdown-menu.pull-right:after {
+  left: auto;
+  right: 13px;
+}
+.navbar .pull-right > li > .dropdown-menu .dropdown-menu,
+.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu {
+  left: auto;
+  right: 100%;
+  margin-left: 0;
+  margin-right: -1px;
+  -webkit-border-radius: 6px 0 6px 6px;
+  -moz-border-radius: 6px 0 6px 6px;
+  border-radius: 6px 0 6px 6px;
+}
+.navbar-inverse .navbar-inner {
+  background-color: #1b1b1b;
+  background-image: -moz-linear-gradient(top, #222222, #111111);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));
+  background-image: -webkit-linear-gradient(top, #222222, #111111);
+  background-image: -o-linear-gradient(top, #222222, #111111);
+  background-image: linear-gradient(to bottom, #222222, #111111);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);
+  border-color: #252525;
+}
+.navbar-inverse .brand,
+.navbar-inverse .nav > li > a {
+  color: #999999;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+.navbar-inverse .brand:hover,
+.navbar-inverse .nav > li > a:hover,
+.navbar-inverse .brand:focus,
+.navbar-inverse .nav > li > a:focus {
+  color: #ffffff;
+}
+.navbar-inverse .brand {
+  color: #999999;
+}
+.navbar-inverse .navbar-text {
+  color: #999999;
+}
+.navbar-inverse .nav > li > a:focus,
+.navbar-inverse .nav > li > a:hover {
+  background-color: transparent;
+  color: #ffffff;
+}
+.navbar-inverse .nav .active > a,
+.navbar-inverse .nav .active > a:hover,
+.navbar-inverse .nav .active > a:focus {
+  color: #ffffff;
+  background-color: #111111;
+}
+.navbar-inverse .navbar-link {
+  color: #999999;
+}
+.navbar-inverse .navbar-link:hover,
+.navbar-inverse .navbar-link:focus {
+  color: #ffffff;
+}
+.navbar-inverse .divider-vertical {
+  border-left-color: #111111;
+  border-right-color: #222222;
+}
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
+  background-color: #111111;
+  color: #ffffff;
+}
+.navbar-inverse .nav li.dropdown > a:hover .caret,
+.navbar-inverse .nav li.dropdown > a:focus .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret {
+  border-top-color: #999999;
+  border-bottom-color: #999999;
+}
+.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret,
+.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret {
+  border-top-color: #ffffff;
+  border-bottom-color: #ffffff;
+}
+.navbar-inverse .navbar-search .search-query {
+  color: #ffffff;
+  background-color: #515151;
+  border-color: #111111;
+  -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);
+  -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);
+  box-shadow: inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);
+  -webkit-transition: none;
+  -moz-transition: none;
+  -o-transition: none;
+  transition: none;
+}
+.navbar-inverse .navbar-search .search-query:-moz-placeholder {
+  color: #cccccc;
+}
+.navbar-inverse .navbar-search .search-query:-ms-input-placeholder {
+  color: #cccccc;
+}
+.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder {
+  color: #cccccc;
+}
+.navbar-inverse .navbar-search .search-query:focus,
+.navbar-inverse .navbar-search .search-query.focused {
+  padding: 5px 15px;
+  color: #333333;
+  text-shadow: 0 1px 0 #ffffff;
+  background-color: #ffffff;
+  border: 0;
+  -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+  -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+  box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
+  outline: 0;
+}
+.navbar-inverse .btn-navbar {
+  color: #ffffff;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #0e0e0e;
+  background-image: -moz-linear-gradient(top, #151515, #040404);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));
+  background-image: -webkit-linear-gradient(top, #151515, #040404);
+  background-image: -o-linear-gradient(top, #151515, #040404);
+  background-image: linear-gradient(to bottom, #151515, #040404);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);
+  border-color: #040404 #040404 #000000;
+  border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+  *background-color: #040404;
+  /* Darken IE7 buttons by default so they stand out more given they won't have borders */
+
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+}
+.navbar-inverse .btn-navbar:hover,
+.navbar-inverse .btn-navbar:focus,
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active,
+.navbar-inverse .btn-navbar.disabled,
+.navbar-inverse .btn-navbar[disabled] {
+  color: #ffffff;
+  background-color: #040404;
+  *background-color: #000000;
+}
+.navbar-inverse .btn-navbar:active,
+.navbar-inverse .btn-navbar.active {
+  background-color: #000000 \9;
+}
+.breadcrumb {
+  padding: 8px 15px;
+  margin: 0 0 20px;
+  list-style: none;
+  background-color: #f5f5f5;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.breadcrumb > li {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  text-shadow: 0 1px 0 #ffffff;
+}
+.breadcrumb > li > .divider {
+  padding: 0 5px;
+  color: #ccc;
+}
+.breadcrumb > .active {
+  color: #999999;
+}
+.pagination {
+  margin: 20px 0;
+}
+.pagination ul {
+  display: inline-block;
+  *display: inline;
+  /* IE7 inline-block hack */
+
+  *zoom: 1;
+  margin-left: 0;
+  margin-bottom: 0;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+  -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+.pagination ul > li {
+  display: inline;
+}
+.pagination ul > li > a,
+.pagination ul > li > span {
+  float: left;
+  padding: 4px 12px;
+  line-height: 20px;
+  text-decoration: none;
+  background-color: #ffffff;
+  border: 1px solid #dddddd;
+  border-left-width: 0;
+}
+.pagination ul > li > a:hover,
+.pagination ul > li > a:focus,
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+  background-color: #f5f5f5;
+}
+.pagination ul > .active > a,
+.pagination ul > .active > span {
+  color: #999999;
+  cursor: default;
+}
+.pagination ul > .disabled > span,
+.pagination ul > .disabled > a,
+.pagination ul > .disabled > a:hover,
+.pagination ul > .disabled > a:focus {
+  color: #999999;
+  background-color: transparent;
+  cursor: default;
+}
+.pagination ul > li:first-child > a,
+.pagination ul > li:first-child > span {
+  border-left-width: 1px;
+  -webkit-border-top-left-radius: 4px;
+  -moz-border-radius-topleft: 4px;
+  border-top-left-radius: 4px;
+  -webkit-border-bottom-left-radius: 4px;
+  -moz-border-radius-bottomleft: 4px;
+  border-bottom-left-radius: 4px;
+}
+.pagination ul > li:last-child > a,
+.pagination ul > li:last-child > span {
+  -webkit-border-top-right-radius: 4px;
+  -moz-border-radius-topright: 4px;
+  border-top-right-radius: 4px;
+  -webkit-border-bottom-right-radius: 4px;
+  -moz-border-radius-bottomright: 4px;
+  border-bottom-right-radius: 4px;
+}
+.pagination-centered {
+  text-align: center;
+}
+.pagination-right {
+  text-align: right;
+}
+.pagination-large ul > li > a,
+.pagination-large ul > li > span {
+  padding: 11px 19px;
+  font-size: 17.5px;
+}
+.pagination-large ul > li:first-child > a,
+.pagination-large ul > li:first-child > span {
+  -webkit-border-top-left-radius: 6px;
+  -moz-border-radius-topleft: 6px;
+  border-top-left-radius: 6px;
+  -webkit-border-bottom-left-radius: 6px;
+  -moz-border-radius-bottomleft: 6px;
+  border-bottom-left-radius: 6px;
+}
+.pagination-large ul > li:last-child > a,
+.pagination-large ul > li:last-child > span {
+  -webkit-border-top-right-radius: 6px;
+  -moz-border-radius-topright: 6px;
+  border-top-right-radius: 6px;
+  -webkit-border-bottom-right-radius: 6px;
+  -moz-border-radius-bottomright: 6px;
+  border-bottom-right-radius: 6px;
+}
+.pagination-mini ul > li:first-child > a,
+.pagination-small ul > li:first-child > a,
+.pagination-mini ul > li:first-child > span,
+.pagination-small ul > li:first-child > span {
+  -webkit-border-top-left-radius: 3px;
+  -moz-border-radius-topleft: 3px;
+  border-top-left-radius: 3px;
+  -webkit-border-bottom-left-radius: 3px;
+  -moz-border-radius-bottomleft: 3px;
+  border-bottom-left-radius: 3px;
+}
+.pagination-mini ul > li:last-child > a,
+.pagination-small ul > li:last-child > a,
+.pagination-mini ul > li:last-child > span,
+.pagination-small ul > li:last-child > span {
+  -webkit-border-top-right-radius: 3px;
+  -moz-border-radius-topright: 3px;
+  border-top-right-radius: 3px;
+  -webkit-border-bottom-right-radius: 3px;
+  -moz-border-radius-bottomright: 3px;
+  border-bottom-right-radius: 3px;
+}
+.pagination-small ul > li > a,
+.pagination-small ul > li > span {
+  padding: 2px 10px;
+  font-size: 11.9px;
+}
+.pagination-mini ul > li > a,
+.pagination-mini ul > li > span {
+  padding: 0 6px;
+  font-size: 10.5px;
+}
+.pager {
+  margin: 20px 0;
+  list-style: none;
+  text-align: center;
+  *zoom: 1;
+}
+.pager:before,
+.pager:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.pager:after {
+  clear: both;
+}
+.pager li {
+  display: inline;
+}
+.pager li > a,
+.pager li > span {
+  display: inline-block;
+  padding: 5px 14px;
+  background-color: #fff;
+  border: 1px solid #ddd;
+  -webkit-border-radius: 15px;
+  -moz-border-radius: 15px;
+  border-radius: 15px;
+}
+.pager li > a:hover,
+.pager li > a:focus {
+  text-decoration: none;
+  background-color: #f5f5f5;
+}
+.pager .next > a,
+.pager .next > span {
+  float: right;
+}
+.pager .previous > a,
+.pager .previous > span {
+  float: left;
+}
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+  color: #999999;
+  background-color: #fff;
+  cursor: default;
+}
+.thumbnails {
+  margin-left: -20px;
+  list-style: none;
+  *zoom: 1;
+}
+.thumbnails:before,
+.thumbnails:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.thumbnails:after {
+  clear: both;
+}
+.row-fluid .thumbnails {
+  margin-left: 0;
+}
+.thumbnails > li {
+  float: left;
+  margin-bottom: 20px;
+  margin-left: 20px;
+}
+.thumbnail {
+  display: block;
+  padding: 4px;
+  line-height: 20px;
+  border: 1px solid #ddd;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+  -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
+  -webkit-transition: all 0.2s ease-in-out;
+  -moz-transition: all 0.2s ease-in-out;
+  -o-transition: all 0.2s ease-in-out;
+  transition: all 0.2s ease-in-out;
+}
+a.thumbnail:hover,
+a.thumbnail:focus {
+  border-color: #0088cc;
+  -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+  -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+  box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
+}
+.thumbnail > img {
+  display: block;
+  max-width: 100%;
+  margin-left: auto;
+  margin-right: auto;
+}
+.thumbnail .caption {
+  padding: 9px;
+  color: #555555;
+}
+.alert {
+  padding: 8px 35px 8px 14px;
+  margin-bottom: 20px;
+  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+  background-color: #fcf8e3;
+  border: 1px solid #fbeed5;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.alert,
+.alert h4 {
+  color: #c09853;
+}
+.alert h4 {
+  margin: 0;
+}
+.alert .close {
+  position: relative;
+  top: -2px;
+  right: -21px;
+  line-height: 20px;
+}
+.alert-success {
+  background-color: #dff0d8;
+  border-color: #d6e9c6;
+  color: #468847;
+}
+.alert-success h4 {
+  color: #468847;
+}
+.alert-danger,
+.alert-error {
+  background-color: #f2dede;
+  border-color: #eed3d7;
+  color: #b94a48;
+}
+.alert-danger h4,
+.alert-error h4 {
+  color: #b94a48;
+}
+.alert-info {
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+  color: #3a87ad;
+}
+.alert-info h4 {
+  color: #3a87ad;
+}
+.alert-block {
+  padding-top: 14px;
+  padding-bottom: 14px;
+}
+.alert-block > p,
+.alert-block > ul {
+  margin-bottom: 0;
+}
+.alert-block p + p {
+  margin-top: 5px;
+}
+@-webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+@-moz-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+@-ms-keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+@-o-keyframes progress-bar-stripes {
+  from {
+    background-position: 0 0;
+  }
+  to {
+    background-position: 40px 0;
+  }
+}
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 40px 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+.progress {
+  overflow: hidden;
+  height: 20px;
+  margin-bottom: 20px;
+  background-color: #f7f7f7;
+  background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
+  background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
+  background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
+  -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.progress .bar {
+  width: 0%;
+  height: 100%;
+  color: #ffffff;
+  float: left;
+  font-size: 12px;
+  text-align: center;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+  background-color: #0e90d2;
+  background-image: -moz-linear-gradient(top, #149bdf, #0480be);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
+  background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
+  background-image: -o-linear-gradient(top, #149bdf, #0480be);
+  background-image: linear-gradient(to bottom, #149bdf, #0480be);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
+  -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+  -webkit-box-sizing: border-box;
+  -moz-box-sizing: border-box;
+  box-sizing: border-box;
+  -webkit-transition: width 0.6s ease;
+  -moz-transition: width 0.6s ease;
+  -o-transition: width 0.6s ease;
+  transition: width 0.6s ease;
+}
+.progress .bar + .bar {
+  -webkit-box-shadow: inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);
+  -moz-box-shadow: inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);
+  box-shadow: inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);
+}
+.progress-striped .bar {
+  background-color: #149bdf;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  -webkit-background-size: 40px 40px;
+  -moz-background-size: 40px 40px;
+  -o-background-size: 40px 40px;
+  background-size: 40px 40px;
+}
+.progress.active .bar {
+  -webkit-animation: progress-bar-stripes 2s linear infinite;
+  -moz-animation: progress-bar-stripes 2s linear infinite;
+  -ms-animation: progress-bar-stripes 2s linear infinite;
+  -o-animation: progress-bar-stripes 2s linear infinite;
+  animation: progress-bar-stripes 2s linear infinite;
+}
+.progress-danger .bar,
+.progress .bar-danger {
+  background-color: #dd514c;
+  background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
+  background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
+  background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);
+}
+.progress-danger.progress-striped .bar,
+.progress-striped .bar-danger {
+  background-color: #ee5f5b;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.progress-success .bar,
+.progress .bar-success {
+  background-color: #5eb95e;
+  background-image: -moz-linear-gradient(top, #62c462, #57a957);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
+  background-image: -webkit-linear-gradient(top, #62c462, #57a957);
+  background-image: -o-linear-gradient(top, #62c462, #57a957);
+  background-image: linear-gradient(to bottom, #62c462, #57a957);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);
+}
+.progress-success.progress-striped .bar,
+.progress-striped .bar-success {
+  background-color: #62c462;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.progress-info .bar,
+.progress .bar-info {
+  background-color: #4bb1cf;
+  background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
+  background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
+  background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);
+}
+.progress-info.progress-striped .bar,
+.progress-striped .bar-info {
+  background-color: #5bc0de;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.progress-warning .bar,
+.progress .bar-warning {
+  background-color: #faa732;
+  background-image: -moz-linear-gradient(top, #fbb450, #f89406);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
+  background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
+  background-image: -o-linear-gradient(top, #fbb450, #f89406);
+  background-image: linear-gradient(to bottom, #fbb450, #f89406);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
+}
+.progress-warning.progress-striped .bar,
+.progress-striped .bar-warning {
+  background-color: #fbb450;
+  background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+  background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+.hero-unit {
+  padding: 60px;
+  margin-bottom: 30px;
+  font-size: 18px;
+  font-weight: 200;
+  line-height: 30px;
+  color: inherit;
+  background-color: #eeeeee;
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+}
+.hero-unit h1 {
+  margin-bottom: 0;
+  font-size: 60px;
+  line-height: 1;
+  color: inherit;
+  letter-spacing: -1px;
+}
+.hero-unit li {
+  line-height: 30px;
+}
+.media,
+.media-body {
+  overflow: hidden;
+  *overflow: visible;
+  zoom: 1;
+}
+.media,
+.media .media {
+  margin-top: 15px;
+}
+.media:first-child {
+  margin-top: 0;
+}
+.media-object {
+  display: block;
+}
+.media-heading {
+  margin: 0 0 5px;
+}
+.media > .pull-left {
+  margin-right: 10px;
+}
+.media > .pull-right {
+  margin-left: 10px;
+}
+.media-list {
+  margin-left: 0;
+  list-style: none;
+}
+.tooltip {
+  position: absolute;
+  z-index: 1030;
+  display: block;
+  visibility: visible;
+  font-size: 11px;
+  line-height: 1.4;
+  opacity: 0;
+  filter: alpha(opacity=0);
+}
+.tooltip.in {
+  opacity: 0.8;
+  filter: alpha(opacity=80);
+}
+.tooltip.top {
+  margin-top: -3px;
+  padding: 5px 0;
+}
+.tooltip.right {
+  margin-left: 3px;
+  padding: 0 5px;
+}
+.tooltip.bottom {
+  margin-top: 3px;
+  padding: 5px 0;
+}
+.tooltip.left {
+  margin-left: -3px;
+  padding: 0 5px;
+}
+.tooltip-inner {
+  max-width: 200px;
+  padding: 8px;
+  color: #ffffff;
+  text-align: center;
+  text-decoration: none;
+  background-color: #000000;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+.tooltip.top .tooltip-arrow {
+  bottom: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-width: 5px 5px 0;
+  border-top-color: #000000;
+}
+.tooltip.right .tooltip-arrow {
+  top: 50%;
+  left: 0;
+  margin-top: -5px;
+  border-width: 5px 5px 5px 0;
+  border-right-color: #000000;
+}
+.tooltip.left .tooltip-arrow {
+  top: 50%;
+  right: 0;
+  margin-top: -5px;
+  border-width: 5px 0 5px 5px;
+  border-left-color: #000000;
+}
+.tooltip.bottom .tooltip-arrow {
+  top: 0;
+  left: 50%;
+  margin-left: -5px;
+  border-width: 0 5px 5px;
+  border-bottom-color: #000000;
+}
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1010;
+  display: none;
+  max-width: 276px;
+  padding: 1px;
+  text-align: left;
+  background-color: #ffffff;
+  -webkit-background-clip: padding-box;
+  -moz-background-clip: padding;
+  background-clip: padding-box;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  white-space: normal;
+}
+.popover.top {
+  margin-top: -10px;
+}
+.popover.right {
+  margin-left: 10px;
+}
+.popover.bottom {
+  margin-top: 10px;
+}
+.popover.left {
+  margin-left: -10px;
+}
+.popover-title {
+  margin: 0;
+  padding: 8px 14px;
+  font-size: 14px;
+  font-weight: normal;
+  line-height: 18px;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  -webkit-border-radius: 5px 5px 0 0;
+  -moz-border-radius: 5px 5px 0 0;
+  border-radius: 5px 5px 0 0;
+}
+.popover-title:empty {
+  display: none;
+}
+.popover-content {
+  padding: 9px 14px;
+}
+.popover .arrow,
+.popover .arrow:after {
+  position: absolute;
+  display: block;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+}
+.popover .arrow {
+  border-width: 11px;
+}
+.popover .arrow:after {
+  border-width: 10px;
+  content: "";
+}
+.popover.top .arrow {
+  left: 50%;
+  margin-left: -11px;
+  border-bottom-width: 0;
+  border-top-color: #999;
+  border-top-color: rgba(0, 0, 0, 0.25);
+  bottom: -11px;
+}
+.popover.top .arrow:after {
+  bottom: 1px;
+  margin-left: -10px;
+  border-bottom-width: 0;
+  border-top-color: #ffffff;
+}
+.popover.right .arrow {
+  top: 50%;
+  left: -11px;
+  margin-top: -11px;
+  border-left-width: 0;
+  border-right-color: #999;
+  border-right-color: rgba(0, 0, 0, 0.25);
+}
+.popover.right .arrow:after {
+  left: 1px;
+  bottom: -10px;
+  border-left-width: 0;
+  border-right-color: #ffffff;
+}
+.popover.bottom .arrow {
+  left: 50%;
+  margin-left: -11px;
+  border-top-width: 0;
+  border-bottom-color: #999;
+  border-bottom-color: rgba(0, 0, 0, 0.25);
+  top: -11px;
+}
+.popover.bottom .arrow:after {
+  top: 1px;
+  margin-left: -10px;
+  border-top-width: 0;
+  border-bottom-color: #ffffff;
+}
+.popover.left .arrow {
+  top: 50%;
+  right: -11px;
+  margin-top: -11px;
+  border-right-width: 0;
+  border-left-color: #999;
+  border-left-color: rgba(0, 0, 0, 0.25);
+}
+.popover.left .arrow:after {
+  right: 1px;
+  border-right-width: 0;
+  border-left-color: #ffffff;
+  bottom: -10px;
+}
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1040;
+  background-color: #000000;
+}
+.modal-backdrop.fade {
+  opacity: 0;
+}
+.modal-backdrop,
+.modal-backdrop.fade.in {
+  opacity: 0.8;
+  filter: alpha(opacity=80);
+}
+.modal {
+  position: fixed;
+  top: 10%;
+  left: 50%;
+  z-index: 1050;
+  width: 560px;
+  margin-left: -280px;
+  background-color: #ffffff;
+  border: 1px solid #999;
+  border: 1px solid rgba(0, 0, 0, 0.3);
+  *border: 1px solid #999;
+  /* IE6-7 */
+
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+  -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
+  -webkit-background-clip: padding-box;
+  -moz-background-clip: padding-box;
+  background-clip: padding-box;
+  outline: none;
+}
+.modal.fade {
+  -webkit-transition: opacity .3s linear, top .3s ease-out;
+  -moz-transition: opacity .3s linear, top .3s ease-out;
+  -o-transition: opacity .3s linear, top .3s ease-out;
+  transition: opacity .3s linear, top .3s ease-out;
+  top: -25%;
+}
+.modal.fade.in {
+  top: 10%;
+}
+.modal-header {
+  padding: 9px 15px;
+  border-bottom: 1px solid #eee;
+}
+.modal-header .close {
+  margin-top: 2px;
+}
+.modal-header h3 {
+  margin: 0;
+  line-height: 30px;
+}
+.modal-body {
+  position: relative;
+  overflow-y: auto;
+  max-height: 400px;
+  padding: 15px;
+}
+.modal-form {
+  margin-bottom: 0;
+}
+.modal-footer {
+  padding: 14px 15px 15px;
+  margin-bottom: 0;
+  text-align: right;
+  background-color: #f5f5f5;
+  border-top: 1px solid #ddd;
+  -webkit-border-radius: 0 0 6px 6px;
+  -moz-border-radius: 0 0 6px 6px;
+  border-radius: 0 0 6px 6px;
+  -webkit-box-shadow: inset 0 1px 0 #ffffff;
+  -moz-box-shadow: inset 0 1px 0 #ffffff;
+  box-shadow: inset 0 1px 0 #ffffff;
+  *zoom: 1;
+}
+.modal-footer:before,
+.modal-footer:after {
+  display: table;
+  content: "";
+  line-height: 0;
+}
+.modal-footer:after {
+  clear: both;
+}
+.modal-footer .btn + .btn {
+  margin-left: 5px;
+  margin-bottom: 0;
+}
+.modal-footer .btn-group .btn + .btn {
+  margin-left: -1px;
+}
+.modal-footer .btn-block + .btn-block {
+  margin-left: 0;
+}
+.dropup,
+.dropdown {
+  position: relative;
+}
+.dropdown-toggle {
+  *margin-bottom: -3px;
+}
+.dropdown-toggle:active,
+.open .dropdown-toggle {
+  outline: 0;
+}
+.caret {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  vertical-align: top;
+  border-top: 4px solid #000000;
+  border-right: 4px solid transparent;
+  border-left: 4px solid transparent;
+  content: "";
+}
+.dropdown .caret {
+  margin-top: 8px;
+  margin-left: 2px;
+}
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: none;
+  float: left;
+  min-width: 160px;
+  padding: 5px 0;
+  margin: 2px 0 0;
+  list-style: none;
+  background-color: #ffffff;
+  border: 1px solid #ccc;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  *border-right-width: 2px;
+  *border-bottom-width: 2px;
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+  -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+  -webkit-background-clip: padding-box;
+  -moz-background-clip: padding;
+  background-clip: padding-box;
+}
+.dropdown-menu.pull-right {
+  right: 0;
+  left: auto;
+}
+.dropdown-menu .divider {
+  *width: 100%;
+  height: 1px;
+  margin: 9px 1px;
+  *margin: -5px 0 5px;
+  overflow: hidden;
+  background-color: #e5e5e5;
+  border-bottom: 1px solid #ffffff;
+}
+.dropdown-menu > li > a {
+  display: block;
+  padding: 3px 20px;
+  clear: both;
+  font-weight: normal;
+  line-height: 20px;
+  color: #333333;
+  white-space: nowrap;
+}
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus,
+.dropdown-submenu:hover > a,
+.dropdown-submenu:focus > a {
+  text-decoration: none;
+  color: #ffffff;
+  background-color: #0081c2;
+  background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+  background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+  color: #ffffff;
+  text-decoration: none;
+  outline: 0;
+  background-color: #0081c2;
+  background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
+  background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
+  background-image: -o-linear-gradient(top, #0088cc, #0077b3);
+  background-image: linear-gradient(to bottom, #0088cc, #0077b3);
+  background-repeat: repeat-x;
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
+}
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  color: #999999;
+}
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+  text-decoration: none;
+  background-color: transparent;
+  background-image: none;
+  filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
+  cursor: default;
+}
+.open {
+  *z-index: 1000;
+}
+.open > .dropdown-menu {
+  display: block;
+}
+.pull-right > .dropdown-menu {
+  right: 0;
+  left: auto;
+}
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+  border-top: 0;
+  border-bottom: 4px solid #000000;
+  content: "";
+}
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-bottom: 1px;
+}
+.dropdown-submenu {
+  position: relative;
+}
+.dropdown-submenu > .dropdown-menu {
+  top: 0;
+  left: 100%;
+  margin-top: -6px;
+  margin-left: -1px;
+  -webkit-border-radius: 0 6px 6px 6px;
+  -moz-border-radius: 0 6px 6px 6px;
+  border-radius: 0 6px 6px 6px;
+}
+.dropdown-submenu:hover > .dropdown-menu {
+  display: block;
+}
+.dropup .dropdown-submenu > .dropdown-menu {
+  top: auto;
+  bottom: 0;
+  margin-top: 0;
+  margin-bottom: -2px;
+  -webkit-border-radius: 5px 5px 5px 0;
+  -moz-border-radius: 5px 5px 5px 0;
+  border-radius: 5px 5px 5px 0;
+}
+.dropdown-submenu > a:after {
+  display: block;
+  content: " ";
+  float: right;
+  width: 0;
+  height: 0;
+  border-color: transparent;
+  border-style: solid;
+  border-width: 5px 0 5px 5px;
+  border-left-color: #cccccc;
+  margin-top: 5px;
+  margin-right: -10px;
+}
+.dropdown-submenu:hover > a:after {
+  border-left-color: #ffffff;
+}
+.dropdown-submenu.pull-left {
+  float: none;
+}
+.dropdown-submenu.pull-left > .dropdown-menu {
+  left: -100%;
+  margin-left: 10px;
+  -webkit-border-radius: 6px 0 6px 6px;
+  -moz-border-radius: 6px 0 6px 6px;
+  border-radius: 6px 0 6px 6px;
+}
+.dropdown .dropdown-menu .nav-header {
+  padding-left: 20px;
+  padding-right: 20px;
+}
+.typeahead {
+  z-index: 1051;
+  margin-top: 2px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.accordion {
+  margin-bottom: 20px;
+}
+.accordion-group {
+  margin-bottom: 2px;
+  border: 1px solid #e5e5e5;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.accordion-heading {
+  border-bottom: 0;
+}
+.accordion-heading .accordion-toggle {
+  display: block;
+  padding: 8px 15px;
+}
+.accordion-toggle {
+  cursor: pointer;
+}
+.accordion-inner {
+  padding: 9px 15px;
+  border-top: 1px solid #e5e5e5;
+}
+.carousel {
+  position: relative;
+  margin-bottom: 20px;
+  line-height: 1;
+}
+.carousel-inner {
+  overflow: hidden;
+  width: 100%;
+  position: relative;
+}
+.carousel-inner > .item {
+  display: none;
+  position: relative;
+  -webkit-transition: 0.6s ease-in-out left;
+  -moz-transition: 0.6s ease-in-out left;
+  -o-transition: 0.6s ease-in-out left;
+  transition: 0.6s ease-in-out left;
+}
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+  display: block;
+  line-height: 1;
+}
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  display: block;
+}
+.carousel-inner > .active {
+  left: 0;
+}
+.carousel-inner > .next,
+.carousel-inner > .prev {
+  position: absolute;
+  top: 0;
+  width: 100%;
+}
+.carousel-inner > .next {
+  left: 100%;
+}
+.carousel-inner > .prev {
+  left: -100%;
+}
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+  left: 0;
+}
+.carousel-inner > .active.left {
+  left: -100%;
+}
+.carousel-inner > .active.right {
+  left: 100%;
+}
+.carousel-control {
+  position: absolute;
+  top: 40%;
+  left: 15px;
+  width: 40px;
+  height: 40px;
+  margin-top: -20px;
+  font-size: 60px;
+  font-weight: 100;
+  line-height: 30px;
+  color: #ffffff;
+  text-align: center;
+  background: #222222;
+  border: 3px solid #ffffff;
+  -webkit-border-radius: 23px;
+  -moz-border-radius: 23px;
+  border-radius: 23px;
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
+.carousel-control.right {
+  left: auto;
+  right: 15px;
+}
+.carousel-control:hover,
+.carousel-control:focus {
+  color: #ffffff;
+  text-decoration: none;
+  opacity: 0.9;
+  filter: alpha(opacity=90);
+}
+.carousel-indicators {
+  position: absolute;
+  top: 15px;
+  right: 15px;
+  z-index: 5;
+  margin: 0;
+  list-style: none;
+}
+.carousel-indicators li {
+  display: block;
+  float: left;
+  width: 10px;
+  height: 10px;
+  margin-left: 5px;
+  text-indent: -999px;
+  background-color: #ccc;
+  background-color: rgba(255, 255, 255, 0.25);
+  border-radius: 5px;
+}
+.carousel-indicators .active {
+  background-color: #fff;
+}
+.carousel-caption {
+  position: absolute;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  padding: 15px;
+  background: #333333;
+  background: rgba(0, 0, 0, 0.75);
+}
+.carousel-caption h4,
+.carousel-caption p {
+  color: #ffffff;
+  line-height: 20px;
+}
+.carousel-caption h4 {
+  margin: 0 0 5px;
+}
+.carousel-caption p {
+  margin-bottom: 0;
+}
+.well {
+  min-height: 20px;
+  padding: 19px;
+  margin-bottom: 20px;
+  background-color: #f5f5f5;
+  border: 1px solid #e3e3e3;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+.well blockquote {
+  border-color: #ddd;
+  border-color: rgba(0, 0, 0, 0.15);
+}
+.well-large {
+  padding: 24px;
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+}
+.well-small {
+  padding: 9px;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.close {
+  float: right;
+  font-size: 20px;
+  font-weight: bold;
+  line-height: 20px;
+  color: #000000;
+  text-shadow: 0 1px 0 #ffffff;
+  opacity: 0.2;
+  filter: alpha(opacity=20);
+}
+.close:hover,
+.close:focus {
+  color: #000000;
+  text-decoration: none;
+  cursor: pointer;
+  opacity: 0.4;
+  filter: alpha(opacity=40);
+}
+button.close {
+  padding: 0;
+  cursor: pointer;
+  background: transparent;
+  border: 0;
+  -webkit-appearance: none;
+}
+.pull-right {
+  float: right;
+}
+.pull-left {
+  float: left;
+}
+.hide {
+  display: none;
+}
+.show {
+  display: block;
+}
+.invisible {
+  visibility: hidden;
+}
+.affix {
+  position: fixed;
+}
+.fade {
+  opacity: 0;
+  -webkit-transition: opacity 0.15s linear;
+  -moz-transition: opacity 0.15s linear;
+  -o-transition: opacity 0.15s linear;
+  transition: opacity 0.15s linear;
+}
+.fade.in {
+  opacity: 1;
+}
+.collapse {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition: height 0.35s ease;
+  -moz-transition: height 0.35s ease;
+  -o-transition: height 0.35s ease;
+  transition: height 0.35s ease;
+}
+.collapse.in {
+  height: auto;
+}
+@-ms-viewport {
+  width: device-width;
+}
+.hidden {
+  display: none;
+  visibility: hidden;
+}
+.visible-phone {
+  display: none !important;
+}
+.visible-tablet {
+  display: none !important;
+}
+.hidden-desktop {
+  display: none !important;
+}
+.visible-desktop {
+  display: inherit !important;
+}
+@media (min-width: 768px) and (max-width: 979px) {
+  .hidden-desktop {
+    display: inherit !important;
+  }
+  .visible-desktop {
+    display: none !important ;
+  }
+  .visible-tablet {
+    display: inherit !important;
+  }
+  .hidden-tablet {
+    display: none !important;
+  }
+}
+@media (max-width: 767px) {
+  .hidden-desktop {
+    display: inherit !important;
+  }
+  .visible-desktop {
+    display: none !important;
+  }
+  .visible-phone {
+    display: inherit !important;
+  }
+  .hidden-phone {
+    display: none !important;
+  }
+}
+.visible-print {
+  display: none !important;
+}
+@media print {
+  .visible-print {
+    display: inherit !important;
+  }
+  .hidden-print {
+    display: none !important;
+  }
+}
+@media (max-width: 767px) {
+  body {
+    padding-left: 20px;
+    padding-right: 20px;
+  }
+  .navbar-fixed-top,
+  .navbar-fixed-bottom,
+  .navbar-static-top {
+    margin-left: -20px;
+    margin-right: -20px;
+  }
+  .container-fluid {
+    padding: 0;
+  }
+  .dl-horizontal dt {
+    float: none;
+    clear: none;
+    width: auto;
+    text-align: left;
+  }
+  .dl-horizontal dd {
+    margin-left: 0;
+  }
+  .container {
+    width: auto;
+  }
+  .row-fluid {
+    width: 100%;
+  }
+  .row,
+  .thumbnails {
+    margin-left: 0;
+  }
+  .thumbnails > li {
+    float: none;
+    margin-left: 0;
+  }
+  [class*="span"],
+  .uneditable-input[class*="span"],
+  .row-fluid [class*="span"] {
+    float: none;
+    display: block;
+    width: 100%;
+    margin-left: 0;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+  }
+  .span12,
+  .row-fluid .span12 {
+    width: 100%;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+  }
+  .row-fluid [class*="offset"]:first-child {
+    margin-left: 0;
+  }
+  .input-large,
+  .input-xlarge,
+  .input-xxlarge,
+  input[class*="span"],
+  select[class*="span"],
+  textarea[class*="span"],
+  .uneditable-input {
+    display: block;
+    width: 100%;
+    min-height: 30px;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+  }
+  .input-prepend input,
+  .input-append input,
+  .input-prepend input[class*="span"],
+  .input-append input[class*="span"] {
+    display: inline-block;
+    width: auto;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 0;
+  }
+  .modal {
+    position: fixed;
+    top: 20px;
+    left: 20px;
+    right: 20px;
+    width: auto;
+    margin: 0;
+  }
+  .modal.fade {
+    top: -100px;
+  }
+  .modal.fade.in {
+    top: 20px;
+  }
+}
+@media (max-width: 480px) {
+  .nav-collapse {
+    -webkit-transform: translate3d(0, 0, 0);
+  }
+  .page-header h1 small {
+    display: block;
+    line-height: 20px;
+  }
+  input[type="checkbox"],
+  input[type="radio"] {
+    border: 1px solid #ccc;
+  }
+  .form-horizontal .control-label {
+    float: none;
+    width: auto;
+    padding-top: 0;
+    text-align: left;
+  }
+  .form-horizontal .controls {
+    margin-left: 0;
+  }
+  .form-horizontal .control-list {
+    padding-top: 0;
+  }
+  .form-horizontal .form-actions {
+    padding-left: 10px;
+    padding-right: 10px;
+  }
+  .media .pull-left,
+  .media .pull-right {
+    float: none;
+    display: block;
+    margin-bottom: 10px;
+  }
+  .media-object {
+    margin-right: 0;
+    margin-left: 0;
+  }
+  .modal {
+    top: 10px;
+    left: 10px;
+    right: 10px;
+  }
+  .modal-header .close {
+    padding: 10px;
+    margin: -10px;
+  }
+  .carousel-caption {
+    position: static;
+  }
+}
+@media (min-width: 768px) and (max-width: 979px) {
+  .row {
+    margin-left: -20px;
+    *zoom: 1;
+  }
+  .row:before,
+  .row:after {
+    display: table;
+    content: "";
+    line-height: 0;
+  }
+  .row:after {
+    clear: both;
+  }
+  [class*="span"] {
+    float: left;
+    min-height: 1px;
+    margin-left: 20px;
+  }
+  .container,
+  .navbar-static-top .container,
+  .navbar-fixed-top .container,
+  .navbar-fixed-bottom .container {
+    width: 724px;
+  }
+  .span12 {
+    width: 724px;
+  }
+  .span11 {
+    width: 662px;
+  }
+  .span10 {
+    width: 600px;
+  }
+  .span9 {
+    width: 538px;
+  }
+  .span8 {
+    width: 476px;
+  }
+  .span7 {
+    width: 414px;
+  }
+  .span6 {
+    width: 352px;
+  }
+  .span5 {
+    width: 290px;
+  }
+  .span4 {
+    width: 228px;
+  }
+  .span3 {
+    width: 166px;
+  }
+  .span2 {
+    width: 104px;
+  }
+  .span1 {
+    width: 42px;
+  }
+  .offset12 {
+    margin-left: 764px;
+  }
+  .offset11 {
+    margin-left: 702px;
+  }
+  .offset10 {
+    margin-left: 640px;
+  }
+  .offset9 {
+    margin-left: 578px;
+  }
+  .offset8 {
+    margin-left: 516px;
+  }
+  .offset7 {
+    margin-left: 454px;
+  }
+  .offset6 {
+    margin-left: 392px;
+  }
+  .offset5 {
+    margin-left: 330px;
+  }
+  .offset4 {
+    margin-left: 268px;
+  }
+  .offset3 {
+    margin-left: 206px;
+  }
+  .offset2 {
+    margin-left: 144px;
+  }
+  .offset1 {
+    margin-left: 82px;
+  }
+  .row-fluid {
+    width: 100%;
+    *zoom: 1;
+  }
+  .row-fluid:before,
+  .row-fluid:after {
+    display: table;
+    content: "";
+    line-height: 0;
+  }
+  .row-fluid:after {
+    clear: both;
+  }
+  .row-fluid [class*="span"] {
+    display: block;
+    width: 100%;
+    min-height: 30px;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    float: left;
+    margin-left: 2.7624309392265194%;
+    *margin-left: 2.709239449864817%;
+  }
+  .row-fluid [class*="span"]:first-child {
+    margin-left: 0;
+  }
+  .row-fluid .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 2.7624309392265194%;
+  }
+  .row-fluid .span12 {
+    width: 100%;
+    *width: 99.94680851063829%;
+  }
+  .row-fluid .span11 {
+    width: 91.43646408839778%;
+    *width: 91.38327259903608%;
+  }
+  .row-fluid .span10 {
+    width: 82.87292817679558%;
+    *width: 82.81973668743387%;
+  }
+  .row-fluid .span9 {
+    width: 74.30939226519337%;
+    *width: 74.25620077583166%;
+  }
+  .row-fluid .span8 {
+    width: 65.74585635359117%;
+    *width: 65.69266486422946%;
+  }
+  .row-fluid .span7 {
+    width: 57.18232044198895%;
+    *width: 57.12912895262725%;
+  }
+  .row-fluid .span6 {
+    width: 48.61878453038674%;
+    *width: 48.56559304102504%;
+  }
+  .row-fluid .span5 {
+    width: 40.05524861878453%;
+    *width: 40.00205712942283%;
+  }
+  .row-fluid .span4 {
+    width: 31.491712707182323%;
+    *width: 31.43852121782062%;
+  }
+  .row-fluid .span3 {
+    width: 22.92817679558011%;
+    *width: 22.87498530621841%;
+  }
+  .row-fluid .span2 {
+    width: 14.3646408839779%;
+    *width: 14.311449394616199%;
+  }
+  .row-fluid .span1 {
+    width: 5.801104972375691%;
+    *width: 5.747913483013988%;
+  }
+  .row-fluid .offset12 {
+    margin-left: 105.52486187845304%;
+    *margin-left: 105.41847889972962%;
+  }
+  .row-fluid .offset12:first-child {
+    margin-left: 102.76243093922652%;
+    *margin-left: 102.6560479605031%;
+  }
+  .row-fluid .offset11 {
+    margin-left: 96.96132596685082%;
+    *margin-left: 96.8549429881274%;
+  }
+  .row-fluid .offset11:first-child {
+    margin-left: 94.1988950276243%;
+    *margin-left: 94.09251204890089%;
+  }
+  .row-fluid .offset10 {
+    margin-left: 88.39779005524862%;
+    *margin-left: 88.2914070765252%;
+  }
+  .row-fluid .offset10:first-child {
+    margin-left: 85.6353591160221%;
+    *margin-left: 85.52897613729868%;
+  }
+  .row-fluid .offset9 {
+    margin-left: 79.8342541436464%;
+    *margin-left: 79.72787116492299%;
+  }
+  .row-fluid .offset9:first-child {
+    margin-left: 77.07182320441989%;
+    *margin-left: 76.96544022569647%;
+  }
+  .row-fluid .offset8 {
+    margin-left: 71.2707182320442%;
+    *margin-left: 71.16433525332079%;
+  }
+  .row-fluid .offset8:first-child {
+    margin-left: 68.50828729281768%;
+    *margin-left: 68.40190431409427%;
+  }
+  .row-fluid .offset7 {
+    margin-left: 62.70718232044199%;
+    *margin-left: 62.600799341718584%;
+  }
+  .row-fluid .offset7:first-child {
+    margin-left: 59.94475138121547%;
+    *margin-left: 59.838368402492065%;
+  }
+  .row-fluid .offset6 {
+    margin-left: 54.14364640883978%;
+    *margin-left: 54.037263430116376%;
+  }
+  .row-fluid .offset6:first-child {
+    margin-left: 51.38121546961326%;
+    *margin-left: 51.27483249088986%;
+  }
+  .row-fluid .offset5 {
+    margin-left: 45.58011049723757%;
+    *margin-left: 45.47372751851417%;
+  }
+  .row-fluid .offset5:first-child {
+    margin-left: 42.81767955801105%;
+    *margin-left: 42.71129657928765%;
+  }
+  .row-fluid .offset4 {
+    margin-left: 37.01657458563536%;
+    *margin-left: 36.91019160691196%;
+  }
+  .row-fluid .offset4:first-child {
+    margin-left: 34.25414364640884%;
+    *margin-left: 34.14776066768544%;
+  }
+  .row-fluid .offset3 {
+    margin-left: 28.45303867403315%;
+    *margin-left: 28.346655695309746%;
+  }
+  .row-fluid .offset3:first-child {
+    margin-left: 25.69060773480663%;
+    *margin-left: 25.584224756083227%;
+  }
+  .row-fluid .offset2 {
+    margin-left: 19.88950276243094%;
+    *margin-left: 19.783119783707537%;
+  }
+  .row-fluid .offset2:first-child {
+    margin-left: 17.12707182320442%;
+    *margin-left: 17.02068884448102%;
+  }
+  .row-fluid .offset1 {
+    margin-left: 11.32596685082873%;
+    *margin-left: 11.219583872105325%;
+  }
+  .row-fluid .offset1:first-child {
+    margin-left: 8.56353591160221%;
+    *margin-left: 8.457152932878806%;
+  }
+  input,
+  textarea,
+  .uneditable-input {
+    margin-left: 0;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 20px;
+  }
+  input.span12,
+  textarea.span12,
+  .uneditable-input.span12 {
+    width: 710px;
+  }
+  input.span11,
+  textarea.span11,
+  .uneditable-input.span11 {
+    width: 648px;
+  }
+  input.span10,
+  textarea.span10,
+  .uneditable-input.span10 {
+    width: 586px;
+  }
+  input.span9,
+  textarea.span9,
+  .uneditable-input.span9 {
+    width: 524px;
+  }
+  input.span8,
+  textarea.span8,
+  .uneditable-input.span8 {
+    width: 462px;
+  }
+  input.span7,
+  textarea.span7,
+  .uneditable-input.span7 {
+    width: 400px;
+  }
+  input.span6,
+  textarea.span6,
+  .uneditable-input.span6 {
+    width: 338px;
+  }
+  input.span5,
+  textarea.span5,
+  .uneditable-input.span5 {
+    width: 276px;
+  }
+  input.span4,
+  textarea.span4,
+  .uneditable-input.span4 {
+    width: 214px;
+  }
+  input.span3,
+  textarea.span3,
+  .uneditable-input.span3 {
+    width: 152px;
+  }
+  input.span2,
+  textarea.span2,
+  .uneditable-input.span2 {
+    width: 90px;
+  }
+  input.span1,
+  textarea.span1,
+  .uneditable-input.span1 {
+    width: 28px;
+  }
+}
+@media (min-width: 1200px) {
+  .row {
+    margin-left: -30px;
+    *zoom: 1;
+  }
+  .row:before,
+  .row:after {
+    display: table;
+    content: "";
+    line-height: 0;
+  }
+  .row:after {
+    clear: both;
+  }
+  [class*="span"] {
+    float: left;
+    min-height: 1px;
+    margin-left: 30px;
+  }
+  .container,
+  .navbar-static-top .container,
+  .navbar-fixed-top .container,
+  .navbar-fixed-bottom .container {
+    width: 1170px;
+  }
+  .span12 {
+    width: 1170px;
+  }
+  .span11 {
+    width: 1070px;
+  }
+  .span10 {
+    width: 970px;
+  }
+  .span9 {
+    width: 870px;
+  }
+  .span8 {
+    width: 770px;
+  }
+  .span7 {
+    width: 670px;
+  }
+  .span6 {
+    width: 570px;
+  }
+  .span5 {
+    width: 470px;
+  }
+  .span4 {
+    width: 370px;
+  }
+  .span3 {
+    width: 270px;
+  }
+  .span2 {
+    width: 170px;
+  }
+  .span1 {
+    width: 70px;
+  }
+  .offset12 {
+    margin-left: 1230px;
+  }
+  .offset11 {
+    margin-left: 1130px;
+  }
+  .offset10 {
+    margin-left: 1030px;
+  }
+  .offset9 {
+    margin-left: 930px;
+  }
+  .offset8 {
+    margin-left: 830px;
+  }
+  .offset7 {
+    margin-left: 730px;
+  }
+  .offset6 {
+    margin-left: 630px;
+  }
+  .offset5 {
+    margin-left: 530px;
+  }
+  .offset4 {
+    margin-left: 430px;
+  }
+  .offset3 {
+    margin-left: 330px;
+  }
+  .offset2 {
+    margin-left: 230px;
+  }
+  .offset1 {
+    margin-left: 130px;
+  }
+  .row-fluid {
+    width: 100%;
+    *zoom: 1;
+  }
+  .row-fluid:before,
+  .row-fluid:after {
+    display: table;
+    content: "";
+    line-height: 0;
+  }
+  .row-fluid:after {
+    clear: both;
+  }
+  .row-fluid [class*="span"] {
+    display: block;
+    width: 100%;
+    min-height: 30px;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    float: left;
+    margin-left: 2.564102564102564%;
+    *margin-left: 2.5109110747408616%;
+  }
+  .row-fluid [class*="span"]:first-child {
+    margin-left: 0;
+  }
+  .row-fluid .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 2.564102564102564%;
+  }
+  .row-fluid .span12 {
+    width: 100%;
+    *width: 99.94680851063829%;
+  }
+  .row-fluid .span11 {
+    width: 91.45299145299145%;
+    *width: 91.39979996362975%;
+  }
+  .row-fluid .span10 {
+    width: 82.90598290598291%;
+    *width: 82.8527914166212%;
+  }
+  .row-fluid .span9 {
+    width: 74.35897435897436%;
+    *width: 74.30578286961266%;
+  }
+  .row-fluid .span8 {
+    width: 65.81196581196582%;
+    *width: 65.75877432260411%;
+  }
+  .row-fluid .span7 {
+    width: 57.26495726495726%;
+    *width: 57.21176577559556%;
+  }
+  .row-fluid .span6 {
+    width: 48.717948717948715%;
+    *width: 48.664757228587014%;
+  }
+  .row-fluid .span5 {
+    width: 40.17094017094017%;
+    *width: 40.11774868157847%;
+  }
+  .row-fluid .span4 {
+    width: 31.623931623931625%;
+    *width: 31.570740134569924%;
+  }
+  .row-fluid .span3 {
+    width: 23.076923076923077%;
+    *width: 23.023731587561375%;
+  }
+  .row-fluid .span2 {
+    width: 14.52991452991453%;
+    *width: 14.476723040552828%;
+  }
+  .row-fluid .span1 {
+    width: 5.982905982905983%;
+    *width: 5.929714493544281%;
+  }
+  .row-fluid .offset12 {
+    margin-left: 105.12820512820512%;
+    *margin-left: 105.02182214948171%;
+  }
+  .row-fluid .offset12:first-child {
+    margin-left: 102.56410256410257%;
+    *margin-left: 102.45771958537915%;
+  }
+  .row-fluid .offset11 {
+    margin-left: 96.58119658119658%;
+    *margin-left: 96.47481360247316%;
+  }
+  .row-fluid .offset11:first-child {
+    margin-left: 94.01709401709402%;
+    *margin-left: 93.91071103837061%;
+  }
+  .row-fluid .offset10 {
+    margin-left: 88.03418803418803%;
+    *margin-left: 87.92780505546462%;
+  }
+  .row-fluid .offset10:first-child {
+    margin-left: 85.47008547008548%;
+    *margin-left: 85.36370249136206%;
+  }
+  .row-fluid .offset9 {
+    margin-left: 79.48717948717949%;
+    *margin-left: 79.38079650845607%;
+  }
+  .row-fluid .offset9:first-child {
+    margin-left: 76.92307692307693%;
+    *margin-left: 76.81669394435352%;
+  }
+  .row-fluid .offset8 {
+    margin-left: 70.94017094017094%;
+    *margin-left: 70.83378796144753%;
+  }
+  .row-fluid .offset8:first-child {
+    margin-left: 68.37606837606839%;
+    *margin-left: 68.26968539734497%;
+  }
+  .row-fluid .offset7 {
+    margin-left: 62.393162393162385%;
+    *margin-left: 62.28677941443899%;
+  }
+  .row-fluid .offset7:first-child {
+    margin-left: 59.82905982905982%;
+    *margin-left: 59.72267685033642%;
+  }
+  .row-fluid .offset6 {
+    margin-left: 53.84615384615384%;
+    *margin-left: 53.739770867430444%;
+  }
+  .row-fluid .offset6:first-child {
+    margin-left: 51.28205128205128%;
+    *margin-left: 51.175668303327875%;
+  }
+  .row-fluid .offset5 {
+    margin-left: 45.299145299145295%;
+    *margin-left: 45.1927623204219%;
+  }
+  .row-fluid .offset5:first-child {
+    margin-left: 42.73504273504273%;
+    *margin-left: 42.62865975631933%;
+  }
+  .row-fluid .offset4 {
+    margin-left: 36.75213675213675%;
+    *margin-left: 36.645753773413354%;
+  }
+  .row-fluid .offset4:first-child {
+    margin-left: 34.18803418803419%;
+    *margin-left: 34.081651209310785%;
+  }
+  .row-fluid .offset3 {
+    margin-left: 28.205128205128204%;
+    *margin-left: 28.0987452264048%;
+  }
+  .row-fluid .offset3:first-child {
+    margin-left: 25.641025641025642%;
+    *margin-left: 25.53464266230224%;
+  }
+  .row-fluid .offset2 {
+    margin-left: 19.65811965811966%;
+    *margin-left: 19.551736679396257%;
+  }
+  .row-fluid .offset2:first-child {
+    margin-left: 17.094017094017094%;
+    *margin-left: 16.98763411529369%;
+  }
+  .row-fluid .offset1 {
+    margin-left: 11.11111111111111%;
+    *margin-left: 11.004728132387708%;
+  }
+  .row-fluid .offset1:first-child {
+    margin-left: 8.547008547008547%;
+    *margin-left: 8.440625568285142%;
+  }
+  input,
+  textarea,
+  .uneditable-input {
+    margin-left: 0;
+  }
+  .controls-row [class*="span"] + [class*="span"] {
+    margin-left: 30px;
+  }
+  input.span12,
+  textarea.span12,
+  .uneditable-input.span12 {
+    width: 1156px;
+  }
+  input.span11,
+  textarea.span11,
+  .uneditable-input.span11 {
+    width: 1056px;
+  }
+  input.span10,
+  textarea.span10,
+  .uneditable-input.span10 {
+    width: 956px;
+  }
+  input.span9,
+  textarea.span9,
+  .uneditable-input.span9 {
+    width: 856px;
+  }
+  input.span8,
+  textarea.span8,
+  .uneditable-input.span8 {
+    width: 756px;
+  }
+  input.span7,
+  textarea.span7,
+  .uneditable-input.span7 {
+    width: 656px;
+  }
+  input.span6,
+  textarea.span6,
+  .uneditable-input.span6 {
+    width: 556px;
+  }
+  input.span5,
+  textarea.span5,
+  .uneditable-input.span5 {
+    width: 456px;
+  }
+  input.span4,
+  textarea.span4,
+  .uneditable-input.span4 {
+    width: 356px;
+  }
+  input.span3,
+  textarea.span3,
+  .uneditable-input.span3 {
+    width: 256px;
+  }
+  input.span2,
+  textarea.span2,
+  .uneditable-input.span2 {
+    width: 156px;
+  }
+  input.span1,
+  textarea.span1,
+  .uneditable-input.span1 {
+    width: 56px;
+  }
+  .thumbnails {
+    margin-left: -30px;
+  }
+  .thumbnails > li {
+    margin-left: 30px;
+  }
+  .row-fluid .thumbnails {
+    margin-left: 0;
+  }
+}
+@media (max-width: 979px) {
+  body {
+    padding-top: 0;
+  }
+  .navbar-fixed-top,
+  .navbar-fixed-bottom {
+    position: static;
+  }
+  .navbar-fixed-top {
+    margin-bottom: 20px;
+  }
+  .navbar-fixed-bottom {
+    margin-top: 20px;
+  }
+  .navbar-fixed-top .navbar-inner,
+  .navbar-fixed-bottom .navbar-inner {
+    padding: 5px;
+  }
+  .navbar .container {
+    width: auto;
+    padding: 0;
+  }
+  .navbar .brand {
+    padding-left: 10px;
+    padding-right: 10px;
+    margin: 0 0 0 -5px;
+  }
+  .nav-collapse {
+    clear: both;
+  }
+  .nav-collapse .nav {
+    float: none;
+    margin: 0 0 10px;
+  }
+  .nav-collapse .nav > li {
+    float: none;
+  }
+  .nav-collapse .nav > li > a {
+    margin-bottom: 2px;
+  }
+  .nav-collapse .nav > .divider-vertical {
+    display: none;
+  }
+  .nav-collapse .nav .nav-header {
+    color: #777777;
+    text-shadow: none;
+  }
+  .nav-collapse .nav > li > a,
+  .nav-collapse .dropdown-menu a {
+    padding: 9px 15px;
+    font-weight: bold;
+    color: #777777;
+    -webkit-border-radius: 3px;
+    -moz-border-radius: 3px;
+    border-radius: 3px;
+  }
+  .nav-collapse .btn {
+    padding: 4px 10px 4px;
+    font-weight: normal;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+  }
+  .nav-collapse .dropdown-menu li + li a {
+    margin-bottom: 2px;
+  }
+  .nav-collapse .nav > li > a:hover,
+  .nav-collapse .nav > li > a:focus,
+  .nav-collapse .dropdown-menu a:hover,
+  .nav-collapse .dropdown-menu a:focus {
+    background-color: #f2f2f2;
+  }
+  .navbar-inverse .nav-collapse .nav > li > a,
+  .navbar-inverse .nav-collapse .dropdown-menu a {
+    color: #999999;
+  }
+  .navbar-inverse .nav-collapse .nav > li > a:hover,
+  .navbar-inverse .nav-collapse .nav > li > a:focus,
+  .navbar-inverse .nav-collapse .dropdown-menu a:hover,
+  .navbar-inverse .nav-collapse .dropdown-menu a:focus {
+    background-color: #111111;
+  }
+  .nav-collapse.in .btn-group {
+    margin-top: 5px;
+    padding: 0;
+  }
+  .nav-collapse .dropdown-menu {
+    position: static;
+    top: auto;
+    left: auto;
+    float: none;
+    display: none;
+    max-width: none;
+    margin: 0 15px;
+    padding: 0;
+    background-color: transparent;
+    border: none;
+    -webkit-border-radius: 0;
+    -moz-border-radius: 0;
+    border-radius: 0;
+    -webkit-box-shadow: none;
+    -moz-box-shadow: none;
+    box-shadow: none;
+  }
+  .nav-collapse .open > .dropdown-menu {
+    display: block;
+  }
+  .nav-collapse .dropdown-menu:before,
+  .nav-collapse .dropdown-menu:after {
+    display: none;
+  }
+  .nav-collapse .dropdown-menu .divider {
+    display: none;
+  }
+  .nav-collapse .nav > li > .dropdown-menu:before,
+  .nav-collapse .nav > li > .dropdown-menu:after {
+    display: none;
+  }
+  .nav-collapse .navbar-form,
+  .nav-collapse .navbar-search {
+    float: none;
+    padding: 10px 15px;
+    margin: 10px 0;
+    border-top: 1px solid #f2f2f2;
+    border-bottom: 1px solid #f2f2f2;
+    -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);
+    -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);
+    box-shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);
+  }
+  .navbar-inverse .nav-collapse .navbar-form,
+  .navbar-inverse .nav-collapse .navbar-search {
+    border-top-color: #111111;
+    border-bottom-color: #111111;
+  }
+  .navbar .nav-collapse .nav.pull-right {
+    float: none;
+    margin-left: 0;
+  }
+  .nav-collapse,
+  .nav-collapse.collapse {
+    overflow: hidden;
+    height: 0;
+  }
+  .navbar .btn-navbar {
+    display: block;
+  }
+  .navbar-static .navbar-inner {
+    padding-left: 10px;
+    padding-right: 10px;
+  }
+}
+@media (min-width: 980px) {
+  .nav-collapse.collapse {
+    height: auto !important;
+    overflow: visible !important;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/assets/bootstrap/css/bootstrap.min.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,731 @@
+/*!
+ * Bootstrap v2.3.0
+ *
+ * Copyright 2012 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
+ */
+.clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";line-height:0;}
+.clearfix:after{clear:both;}
+.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;}
+.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
+article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
+audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
+audio:not([controls]){display:none;}
+html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
+a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+a:hover,a:active{outline:0;}
+sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
+sup{top:-0.5em;}
+sub{bottom:-0.25em;}
+img{max-width:100%;width:auto\9;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic;}
+#map_canvas img,.google-maps img{max-width:none;}
+button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}
+button,input{*overflow:visible;line-height:normal;}
+button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}
+button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}
+label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer;}
+input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield;}
+input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
+textarea{overflow:auto;vertical-align:top;}
+@media print{*{text-shadow:none !important;color:#000 !important;background:transparent !important;box-shadow:none !important;} a,a:visited{text-decoration:underline;} a[href]:after{content:" (" attr(href) ")";} abbr[title]:after{content:" (" attr(title) ")";} .ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:"";} pre,blockquote{border:1px solid #999;page-break-inside:avoid;} thead{display:table-header-group;} tr,img{page-break-inside:avoid;} img{max-width:100% !important;} @page {margin:0.5cm;}p,h2,h3{orphans:3;widows:3;} h2,h3{page-break-after:avoid;}}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333333;background-color:#ffffff;}
+a{color:#0088cc;text-decoration:none;}
+a:hover,a:focus{color:#005580;text-decoration:underline;}
+.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.1);}
+.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px;}
+.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;}
+.row:after{clear:both;}
+[class*="span"]{float:left;min-height:1px;margin-left:20px;}
+.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
+.span12{width:940px;}
+.span11{width:860px;}
+.span10{width:780px;}
+.span9{width:700px;}
+.span8{width:620px;}
+.span7{width:540px;}
+.span6{width:460px;}
+.span5{width:380px;}
+.span4{width:300px;}
+.span3{width:220px;}
+.span2{width:140px;}
+.span1{width:60px;}
+.offset12{margin-left:980px;}
+.offset11{margin-left:900px;}
+.offset10{margin-left:820px;}
+.offset9{margin-left:740px;}
+.offset8{margin-left:660px;}
+.offset7{margin-left:580px;}
+.offset6{margin-left:500px;}
+.offset5{margin-left:420px;}
+.offset4{margin-left:340px;}
+.offset3{margin-left:260px;}
+.offset2{margin-left:180px;}
+.offset1{margin-left:100px;}
+.row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;}
+.row-fluid:after{clear:both;}
+.row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;}
+.row-fluid [class*="span"]:first-child{margin-left:0;}
+.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%;}
+.row-fluid .span12{width:100%;*width:99.94680851063829%;}
+.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%;}
+.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%;}
+.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%;}
+.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%;}
+.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%;}
+.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%;}
+.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%;}
+.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%;}
+.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%;}
+.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%;}
+.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%;}
+.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%;}
+.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%;}
+.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%;}
+.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%;}
+.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%;}
+.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%;}
+.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%;}
+.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%;}
+.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%;}
+.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%;}
+.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%;}
+.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%;}
+.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%;}
+.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%;}
+.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%;}
+.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%;}
+.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%;}
+.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%;}
+.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%;}
+.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%;}
+.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%;}
+.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%;}
+.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%;}
+.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%;}
+[class*="span"].hide,.row-fluid [class*="span"].hide{display:none;}
+[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right;}
+.container{margin-right:auto;margin-left:auto;*zoom:1;}.container:before,.container:after{display:table;content:"";line-height:0;}
+.container:after{clear:both;}
+.container-fluid{padding-right:20px;padding-left:20px;*zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";line-height:0;}
+.container-fluid:after{clear:both;}
+p{margin:0 0 10px;}
+.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px;}
+small{font-size:85%;}
+strong{font-weight:bold;}
+em{font-style:italic;}
+cite{font-style:normal;}
+.muted{color:#999999;}
+a.muted:hover,a.muted:focus{color:#808080;}
+.text-warning{color:#c09853;}
+a.text-warning:hover,a.text-warning:focus{color:#a47e3c;}
+.text-error{color:#b94a48;}
+a.text-error:hover,a.text-error:focus{color:#953b39;}
+.text-info{color:#3a87ad;}
+a.text-info:hover,a.text-info:focus{color:#2d6987;}
+.text-success{color:#468847;}
+a.text-success:hover,a.text-success:focus{color:#356635;}
+.text-left{text-align:left;}
+.text-right{text-align:right;}
+.text-center{text-align:center;}
+h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999999;}
+h1,h2,h3{line-height:40px;}
+h1{font-size:38.5px;}
+h2{font-size:31.5px;}
+h3{font-size:24.5px;}
+h4{font-size:17.5px;}
+h5{font-size:14px;}
+h6{font-size:11.9px;}
+h1 small{font-size:24.5px;}
+h2 small{font-size:17.5px;}
+h3 small{font-size:14px;}
+h4 small{font-size:14px;}
+.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eeeeee;}
+ul,ol{padding:0;margin:0 0 10px 25px;}
+ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
+li{line-height:20px;}
+ul.unstyled,ol.unstyled{margin-left:0;list-style:none;}
+ul.inline,ol.inline{margin-left:0;list-style:none;}ul.inline>li,ol.inline>li{display:inline-block;*display:inline;*zoom:1;padding-left:5px;padding-right:5px;}
+dl{margin-bottom:20px;}
+dt,dd{line-height:20px;}
+dt{font-weight:bold;}
+dd{margin-left:10px;}
+.dl-horizontal{*zoom:1;}.dl-horizontal:before,.dl-horizontal:after{display:table;content:"";line-height:0;}
+.dl-horizontal:after{clear:both;}
+.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
+.dl-horizontal dd{margin-left:180px;}
+hr{margin:20px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;}
+abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999999;}
+abbr.initialism{font-size:90%;text-transform:uppercase;}
+blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25;}
+blockquote small{display:block;line-height:20px;color:#999999;}blockquote small:before{content:'\2014 \00A0';}
+blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eeeeee;border-left:0;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
+blockquote.pull-right small:before{content:'';}
+blockquote.pull-right small:after{content:'\00A0 \2014';}
+q:before,q:after,blockquote:before,blockquote:after{content:"";}
+address{display:block;margin-bottom:20px;font-style:normal;line-height:20px;}
+code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;white-space:nowrap;}
+pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}pre.prettyprint{margin-bottom:20px;}
+pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0;}
+.pre-scrollable{max-height:340px;overflow-y:scroll;}
+.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#ffffff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#999999;}
+.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+.badge{padding-left:9px;padding-right:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px;}
+.label:empty,.badge:empty{display:none;}
+a.label:hover,a.label:focus,a.badge:hover,a.badge:focus{color:#ffffff;text-decoration:none;cursor:pointer;}
+.label-important,.badge-important{background-color:#b94a48;}
+.label-important[href],.badge-important[href]{background-color:#953b39;}
+.label-warning,.badge-warning{background-color:#f89406;}
+.label-warning[href],.badge-warning[href]{background-color:#c67605;}
+.label-success,.badge-success{background-color:#468847;}
+.label-success[href],.badge-success[href]{background-color:#356635;}
+.label-info,.badge-info{background-color:#3a87ad;}
+.label-info[href],.badge-info[href]{background-color:#2d6987;}
+.label-inverse,.badge-inverse{background-color:#333333;}
+.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a;}
+.btn .label,.btn .badge{position:relative;top:-1px;}
+.btn-mini .label,.btn-mini .badge{top:0;}
+table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0;}
+.table{width:100%;margin-bottom:20px;}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #dddddd;}
+.table th{font-weight:bold;}
+.table thead th{vertical-align:bottom;}
+.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0;}
+.table tbody+tbody{border-top:2px solid #dddddd;}
+.table .table{background-color:#ffffff;}
+.table-condensed th,.table-condensed td{padding:4px 5px;}
+.table-bordered{border:1px solid #dddddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.table-bordered th,.table-bordered td{border-left:1px solid #dddddd;}
+.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0;}
+.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;}
+.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;}
+.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
+.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
+.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;-moz-border-radius-bottomleft:0;border-bottom-left-radius:0;}
+.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;-moz-border-radius-bottomright:0;border-bottom-right-radius:0;}
+.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;}
+.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;}
+.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9;}
+.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5;}
+table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0;}
+.table td.span1,.table th.span1{float:none;width:44px;margin-left:0;}
+.table td.span2,.table th.span2{float:none;width:124px;margin-left:0;}
+.table td.span3,.table th.span3{float:none;width:204px;margin-left:0;}
+.table td.span4,.table th.span4{float:none;width:284px;margin-left:0;}
+.table td.span5,.table th.span5{float:none;width:364px;margin-left:0;}
+.table td.span6,.table th.span6{float:none;width:444px;margin-left:0;}
+.table td.span7,.table th.span7{float:none;width:524px;margin-left:0;}
+.table td.span8,.table th.span8{float:none;width:604px;margin-left:0;}
+.table td.span9,.table th.span9{float:none;width:684px;margin-left:0;}
+.table td.span10,.table th.span10{float:none;width:764px;margin-left:0;}
+.table td.span11,.table th.span11{float:none;width:844px;margin-left:0;}
+.table td.span12,.table th.span12{float:none;width:924px;margin-left:0;}
+.table tbody tr.success>td{background-color:#dff0d8;}
+.table tbody tr.error>td{background-color:#f2dede;}
+.table tbody tr.warning>td{background-color:#fcf8e3;}
+.table tbody tr.info>td{background-color:#d9edf7;}
+.table-hover tbody tr.success:hover>td{background-color:#d0e9c6;}
+.table-hover tbody tr.error:hover>td{background-color:#ebcccc;}
+.table-hover tbody tr.warning:hover>td{background-color:#faf2cc;}
+.table-hover tbody tr.info:hover>td{background-color:#c4e3f3;}
+form{margin:0 0 20px;}
+fieldset{padding:0;margin:0;border:0;}
+legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333333;border:0;border-bottom:1px solid #e5e5e5;}legend small{font-size:15px;color:#999999;}
+label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px;}
+input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;}
+label{display:block;margin-bottom:5px;}
+select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555555;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;vertical-align:middle;}
+input,textarea,.uneditable-input{width:206px;}
+textarea{height:auto;}
+textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#ffffff;border:1px solid #cccccc;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-webkit-transition:border linear .2s, box-shadow linear .2s;-moz-transition:border linear .2s, box-shadow linear .2s;-o-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82, 168, 236, 0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6);}
+input[type="radio"],input[type="checkbox"]{margin:4px 0 0;*margin-top:0;margin-top:1px \9;line-height:normal;}
+input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto;}
+select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px;}
+select{width:220px;border:1px solid #cccccc;background-color:#ffffff;}
+select[multiple],select[size]{height:auto;}
+select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+.uneditable-input,.uneditable-textarea{color:#999999;background-color:#fcfcfc;border-color:#cccccc;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
+.uneditable-input{overflow:hidden;white-space:nowrap;}
+.uneditable-textarea{width:auto;height:auto;}
+input:-moz-placeholder,textarea:-moz-placeholder{color:#999999;}
+input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999999;}
+input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999999;}
+.radio,.checkbox{min-height:20px;padding-left:20px;}
+.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px;}
+.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px;}
+.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle;}
+.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px;}
+.input-mini{width:60px;}
+.input-small{width:90px;}
+.input-medium{width:150px;}
+.input-large{width:210px;}
+.input-xlarge{width:270px;}
+.input-xxlarge{width:530px;}
+input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0;}
+.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block;}
+input,textarea,.uneditable-input{margin-left:0;}
+.controls-row [class*="span"]+[class*="span"]{margin-left:20px;}
+input.span12,textarea.span12,.uneditable-input.span12{width:926px;}
+input.span11,textarea.span11,.uneditable-input.span11{width:846px;}
+input.span10,textarea.span10,.uneditable-input.span10{width:766px;}
+input.span9,textarea.span9,.uneditable-input.span9{width:686px;}
+input.span8,textarea.span8,.uneditable-input.span8{width:606px;}
+input.span7,textarea.span7,.uneditable-input.span7{width:526px;}
+input.span6,textarea.span6,.uneditable-input.span6{width:446px;}
+input.span5,textarea.span5,.uneditable-input.span5{width:366px;}
+input.span4,textarea.span4,.uneditable-input.span4{width:286px;}
+input.span3,textarea.span3,.uneditable-input.span3{width:206px;}
+input.span2,textarea.span2,.uneditable-input.span2{width:126px;}
+input.span1,textarea.span1,.uneditable-input.span1{width:46px;}
+.controls-row{*zoom:1;}.controls-row:before,.controls-row:after{display:table;content:"";line-height:0;}
+.controls-row:after{clear:both;}
+.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left;}
+.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px;}
+input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eeeeee;}
+input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent;}
+.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853;}
+.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853;}
+.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #dbc59e;}
+.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853;}
+.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48;}
+.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48;}
+.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #d59392;}
+.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48;}
+.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847;}
+.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847;}
+.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7aba7b;}
+.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847;}
+.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad;}
+.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad;}
+.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075);}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.075),0 0 6px #7ab5d3;}
+.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad;}
+input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b;}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
+.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1;}.form-actions:before,.form-actions:after{display:table;content:"";line-height:0;}
+.form-actions:after{clear:both;}
+.help-block,.help-inline{color:#595959;}
+.help-block{display:block;margin-bottom:10px;}
+.help-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle;padding-left:5px;}
+.input-append,.input-prepend{display:inline-block;margin-bottom:10px;vertical-align:middle;font-size:0;white-space:nowrap;}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu,.input-append .popover,.input-prepend .popover{font-size:14px;}
+.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2;}
+.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #ffffff;background-color:#eeeeee;border:1px solid #ccc;}
+.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546;}
+.input-prepend .add-on,.input-prepend .btn{margin-right:-1px;}
+.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
+.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
+.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px;}
+.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
+.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
+.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
+.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
+.input-prepend.input-append .btn-group:first-child{margin-left:0;}
+input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
+.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;}
+.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;}
+.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0;}
+.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px;}
+.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;*zoom:1;margin-bottom:0;vertical-align:middle;}
+.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none;}
+.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block;}
+.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0;}
+.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle;}
+.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0;}
+.control-group{margin-bottom:10px;}
+legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate;}
+.form-horizontal .control-group{margin-bottom:20px;*zoom:1;}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;content:"";line-height:0;}
+.form-horizontal .control-group:after{clear:both;}
+.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right;}
+.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0;}.form-horizontal .controls:first-child{*padding-left:180px;}
+.form-horizontal .help-block{margin-bottom:0;}
+.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px;}
+.form-horizontal .form-actions{padding-left:180px;}
+.btn{display:inline-block;*display:inline;*zoom:1;padding:4px 12px;margin-bottom:0;font-size:14px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333333;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #cccccc;*border:0;border-bottom-color:#b3b3b3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);}.btn:hover,.btn:focus,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333333;background-color:#e6e6e6;*background-color:#d9d9d9;}
+.btn:active,.btn.active{background-color:#cccccc \9;}
+.btn:first-child{*margin-left:0;}
+.btn:hover,.btn:focus{color:#333333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position 0.1s linear;-moz-transition:background-position 0.1s linear;-o-transition:background-position 0.1s linear;transition:background-position 0.1s linear;}
+.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
+.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);}
+.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:0.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px;}
+.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0;}
+.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px;}
+.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+.btn-block{display:block;width:100%;padding-left:0;padding-right:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;}
+.btn-block+.btn-block{margin-top:5px;}
+input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%;}
+.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255, 255, 255, 0.75);}
+.btn-primary{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#006dcc;background-image:-moz-linear-gradient(top, #0088cc, #0044cc);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));background-image:-webkit-linear-gradient(top, #0088cc, #0044cc);background-image:-o-linear-gradient(top, #0088cc, #0044cc);background-image:linear-gradient(to bottom, #0088cc, #0044cc);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);border-color:#0044cc #0044cc #002a80;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#0044cc;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#ffffff;background-color:#0044cc;*background-color:#003bb3;}
+.btn-primary:active,.btn-primary.active{background-color:#003399 \9;}
+.btn-warning{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);border-color:#f89406 #f89406 #ad6704;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#f89406;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#ffffff;background-color:#f89406;*background-color:#df8505;}
+.btn-warning:active,.btn-warning.active{background-color:#c67605 \9;}
+.btn-danger{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#da4f49;background-image:-moz-linear-gradient(top, #ee5f5b, #bd362f);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));background-image:-webkit-linear-gradient(top, #ee5f5b, #bd362f);background-image:-o-linear-gradient(top, #ee5f5b, #bd362f);background-image:linear-gradient(to bottom, #ee5f5b, #bd362f);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);border-color:#bd362f #bd362f #802420;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#bd362f;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#ffffff;background-color:#bd362f;*background-color:#a9302a;}
+.btn-danger:active,.btn-danger.active{background-color:#942a25 \9;}
+.btn-success{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#5bb75b;background-image:-moz-linear-gradient(top, #62c462, #51a351);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));background-image:-webkit-linear-gradient(top, #62c462, #51a351);background-image:-o-linear-gradient(top, #62c462, #51a351);background-image:linear-gradient(to bottom, #62c462, #51a351);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);border-color:#51a351 #51a351 #387038;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#51a351;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#ffffff;background-color:#51a351;*background-color:#499249;}
+.btn-success:active,.btn-success.active{background-color:#408140 \9;}
+.btn-info{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#49afcd;background-image:-moz-linear-gradient(top, #5bc0de, #2f96b4);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));background-image:-webkit-linear-gradient(top, #5bc0de, #2f96b4);background-image:-o-linear-gradient(top, #5bc0de, #2f96b4);background-image:linear-gradient(to bottom, #5bc0de, #2f96b4);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#2f96b4;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#ffffff;background-color:#2f96b4;*background-color:#2a85a0;}
+.btn-info:active,.btn-info.active{background-color:#24748c \9;}
+.btn-inverse{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#363636;background-image:-moz-linear-gradient(top, #444444, #222222);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));background-image:-webkit-linear-gradient(top, #444444, #222222);background-image:-o-linear-gradient(top, #444444, #222222);background-image:linear-gradient(to bottom, #444444, #222222);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);border-color:#222222 #222222 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#222222;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.btn-inverse:hover,.btn-inverse:focus,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#ffffff;background-color:#222222;*background-color:#151515;}
+.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9;}
+button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px;}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0;}
+button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px;}
+button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px;}
+button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px;}
+.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn-link{border-color:transparent;cursor:pointer;color:#0088cc;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.btn-link:hover,.btn-link:focus{color:#005580;text-decoration:underline;background-color:transparent;}
+.btn-link[disabled]:hover,.btn-link[disabled]:focus{color:#333333;text-decoration:none;}
+.btn-group{position:relative;display:inline-block;*display:inline;*zoom:1;font-size:0;vertical-align:middle;white-space:nowrap;*margin-left:.3em;}.btn-group:first-child{*margin-left:0;}
+.btn-group+.btn-group{margin-left:5px;}
+.btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px;}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px;}
+.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.btn-group>.btn+.btn{margin-left:-1px;}
+.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px;}
+.btn-group>.btn-mini{font-size:10.5px;}
+.btn-group>.btn-small{font-size:11.9px;}
+.btn-group>.btn-large{font-size:17.5px;}
+.btn-group>.btn:first-child{margin-left:0;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
+.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
+.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
+.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
+.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2;}
+.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0;}
+.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);*padding-top:5px;*padding-bottom:5px;}
+.btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px;*padding-top:2px;*padding-bottom:2px;}
+.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px;}
+.btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px;*padding-top:7px;*padding-bottom:7px;}
+.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);}
+.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6;}
+.btn-group.open .btn-primary.dropdown-toggle{background-color:#0044cc;}
+.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406;}
+.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f;}
+.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351;}
+.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4;}
+.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222222;}
+.btn .caret{margin-top:8px;margin-left:0;}
+.btn-large .caret{margin-top:6px;}
+.btn-large .caret{border-left-width:5px;border-right-width:5px;border-top-width:5px;}
+.btn-mini .caret,.btn-small .caret{margin-top:8px;}
+.dropup .btn-large .caret{border-bottom-width:5px;}
+.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.btn-group-vertical{display:inline-block;*display:inline;*zoom:1;}
+.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.btn-group-vertical>.btn+.btn{margin-left:0;margin-top:-1px;}
+.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}
+.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}
+.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0;}
+.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;}
+.nav{margin-left:0;margin-bottom:20px;list-style:none;}
+.nav>li>a{display:block;}
+.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eeeeee;}
+.nav>li>a>img{max-width:none;}
+.nav>.pull-right{float:right;}
+.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999999;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);text-transform:uppercase;}
+.nav li+.nav-header{margin-top:9px;}
+.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0;}
+.nav-list>li>a,.nav-list .nav-header{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
+.nav-list>li>a{padding:3px 15px;}
+.nav-list>.active>a,.nav-list>.active>a:hover,.nav-list>.active>a:focus{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.2);background-color:#0088cc;}
+.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px;}
+.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;}
+.nav-tabs,.nav-pills{*zoom:1;}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;content:"";line-height:0;}
+.nav-tabs:after,.nav-pills:after{clear:both;}
+.nav-tabs>li,.nav-pills>li{float:left;}
+.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px;}
+.nav-tabs{border-bottom:1px solid #ddd;}
+.nav-tabs>li{margin-bottom:-1px;}
+.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.nav-tabs>li>a:hover,.nav-tabs>li>a:focus{border-color:#eeeeee #eeeeee #dddddd;}
+.nav-tabs>.active>a,.nav-tabs>.active>a:hover,.nav-tabs>.active>a:focus{color:#555555;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
+.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;}
+.nav-pills>.active>a,.nav-pills>.active>a:hover,.nav-pills>.active>a:focus{color:#ffffff;background-color:#0088cc;}
+.nav-stacked>li{float:none;}
+.nav-stacked>li>a{margin-right:0;}
+.nav-tabs.nav-stacked{border-bottom:0;}
+.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;}
+.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
+.nav-tabs.nav-stacked>li>a:hover,.nav-tabs.nav-stacked>li>a:focus{border-color:#ddd;z-index:2;}
+.nav-pills.nav-stacked>li>a{margin-bottom:3px;}
+.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px;}
+.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;}
+.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.nav .dropdown-toggle .caret{border-top-color:#0088cc;border-bottom-color:#0088cc;margin-top:6px;}
+.nav .dropdown-toggle:hover .caret,.nav .dropdown-toggle:focus .caret{border-top-color:#005580;border-bottom-color:#005580;}
+.nav-tabs .dropdown-toggle .caret{margin-top:8px;}
+.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff;}
+.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555555;border-bottom-color:#555555;}
+.nav>.dropdown.active>a:hover,.nav>.dropdown.active>a:focus{cursor:pointer;}
+.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover,.nav>li.dropdown.open.active>a:focus{color:#ffffff;background-color:#999999;border-color:#999999;}
+.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open a:focus .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;opacity:1;filter:alpha(opacity=100);}
+.tabs-stacked .open>a:hover,.tabs-stacked .open>a:focus{border-color:#999999;}
+.tabbable{*zoom:1;}.tabbable:before,.tabbable:after{display:table;content:"";line-height:0;}
+.tabbable:after{clear:both;}
+.tab-content{overflow:auto;}
+.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0;}
+.tab-content>.tab-pane,.pill-content>.pill-pane{display:none;}
+.tab-content>.active,.pill-content>.active{display:block;}
+.tabs-below>.nav-tabs{border-top:1px solid #ddd;}
+.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0;}
+.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px;}.tabs-below>.nav-tabs>li>a:hover,.tabs-below>.nav-tabs>li>a:focus{border-bottom-color:transparent;border-top-color:#ddd;}
+.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover,.tabs-below>.nav-tabs>.active>a:focus{border-color:transparent #ddd #ddd #ddd;}
+.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none;}
+.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px;}
+.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd;}
+.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px;}
+.tabs-left>.nav-tabs>li>a:hover,.tabs-left>.nav-tabs>li>a:focus{border-color:#eeeeee #dddddd #eeeeee #eeeeee;}
+.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover,.tabs-left>.nav-tabs .active>a:focus{border-color:#ddd transparent #ddd #ddd;*border-right-color:#ffffff;}
+.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd;}
+.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0;}
+.tabs-right>.nav-tabs>li>a:hover,.tabs-right>.nav-tabs>li>a:focus{border-color:#eeeeee #eeeeee #eeeeee #dddddd;}
+.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover,.tabs-right>.nav-tabs .active>a:focus{border-color:#ddd #ddd #ddd transparent;*border-left-color:#ffffff;}
+.nav>.disabled>a{color:#999999;}
+.nav>.disabled>a:hover,.nav>.disabled>a:focus{text-decoration:none;background-color:transparent;cursor:default;}
+.navbar{overflow:visible;margin-bottom:20px;*position:relative;*z-index:2;}
+.navbar-inner{min-height:40px;padding-left:20px;padding-right:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top, #ffffff, #f2f2f2);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));background-image:-webkit-linear-gradient(top, #ffffff, #f2f2f2);background-image:-o-linear-gradient(top, #ffffff, #f2f2f2);background-image:linear-gradient(to bottom, #ffffff, #f2f2f2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);-moz-box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);box-shadow:0 1px 4px rgba(0, 0, 0, 0.065);*zoom:1;}.navbar-inner:before,.navbar-inner:after{display:table;content:"";line-height:0;}
+.navbar-inner:after{clear:both;}
+.navbar .container{width:auto;}
+.nav-collapse.collapse{height:auto;overflow:visible;}
+.navbar .brand{float:left;display:block;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777777;text-shadow:0 1px 0 #ffffff;}.navbar .brand:hover,.navbar .brand:focus{text-decoration:none;}
+.navbar-text{margin-bottom:0;line-height:40px;color:#777777;}
+.navbar-link{color:#777777;}.navbar-link:hover,.navbar-link:focus{color:#333333;}
+.navbar .divider-vertical{height:40px;margin:0 9px;border-left:1px solid #f2f2f2;border-right:1px solid #ffffff;}
+.navbar .btn,.navbar .btn-group{margin-top:5px;}
+.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn,.navbar .input-prepend .btn-group,.navbar .input-append .btn-group{margin-top:0;}
+.navbar-form{margin-bottom:0;*zoom:1;}.navbar-form:before,.navbar-form:after{display:table;content:"";line-height:0;}
+.navbar-form:after{clear:both;}
+.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px;}
+.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0;}
+.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px;}
+.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap;}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0;}
+.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0;}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
+.navbar-static-top{position:static;margin-bottom:0;}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0;}
+.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px;}
+.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0;}
+.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-left:0;padding-right:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;}
+.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px;}
+.navbar-fixed-top{top:0;}
+.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,.1);box-shadow:0 1px 10px rgba(0,0,0,.1);}
+.navbar-fixed-bottom{bottom:0;}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,.1);box-shadow:0 -1px 10px rgba(0,0,0,.1);}
+.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0;}
+.navbar .nav.pull-right{float:right;margin-right:0;}
+.navbar .nav>li{float:left;}
+.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777777;text-decoration:none;text-shadow:0 1px 0 #ffffff;}
+.navbar .nav .dropdown-toggle .caret{margin-top:8px;}
+.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:transparent;color:#333333;text-decoration:none;}
+.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);-moz-box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);box-shadow:inset 0 3px 8px rgba(0, 0, 0, 0.125);}
+.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#ededed;background-image:-moz-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));background-image:-webkit-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:-o-linear-gradient(top, #f2f2f2, #e5e5e5);background-image:linear-gradient(to bottom, #f2f2f2, #e5e5e5);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e5e5e5;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075);}.navbar .btn-navbar:hover,.navbar .btn-navbar:focus,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#ffffff;background-color:#e5e5e5;*background-color:#d9d9d9;}
+.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#cccccc \9;}
+.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);-moz-box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);box-shadow:0 1px 0 rgba(0, 0, 0, 0.25);}
+.btn-navbar .icon-bar+.icon-bar{margin-top:3px;}
+.navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0, 0, 0, 0.2);position:absolute;top:-7px;left:9px;}
+.navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #ffffff;position:absolute;top:-6px;left:10px;}
+.navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0, 0, 0, 0.2);border-bottom:0;bottom:-7px;top:auto;}
+.navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #ffffff;border-bottom:0;bottom:-6px;top:auto;}
+.navbar .nav li.dropdown>a:hover .caret,.navbar .nav li.dropdown>a:focus .caret{border-top-color:#333333;border-bottom-color:#333333;}
+.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{background-color:#e5e5e5;color:#555555;}
+.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777777;border-bottom-color:#777777;}
+.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555555;border-bottom-color:#555555;}
+.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{left:auto;right:0;}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{left:auto;right:12px;}
+.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{left:auto;right:13px;}
+.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;}
+.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top, #222222, #111111);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));background-image:-webkit-linear-gradient(top, #222222, #111111);background-image:-o-linear-gradient(top, #222222, #111111);background-image:linear-gradient(to bottom, #222222, #111111);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);border-color:#252525;}
+.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999999;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover,.navbar-inverse .brand:focus,.navbar-inverse .nav>li>a:focus{color:#ffffff;}
+.navbar-inverse .brand{color:#999999;}
+.navbar-inverse .navbar-text{color:#999999;}
+.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:transparent;color:#ffffff;}
+.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#ffffff;background-color:#111111;}
+.navbar-inverse .navbar-link{color:#999999;}.navbar-inverse .navbar-link:hover,.navbar-inverse .navbar-link:focus{color:#ffffff;}
+.navbar-inverse .divider-vertical{border-left-color:#111111;border-right-color:#222222;}
+.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{background-color:#111111;color:#ffffff;}
+.navbar-inverse .nav li.dropdown>a:hover .caret,.navbar-inverse .nav li.dropdown>a:focus .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999999;border-bottom-color:#999999;}
+.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#ffffff;border-bottom-color:#ffffff;}
+.navbar-inverse .navbar-search .search-query{color:#ffffff;background-color:#515151;border-color:#111111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);box-shadow:inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none;}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#cccccc;}
+.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#cccccc;}
+.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#cccccc;}
+.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333333;text-shadow:0 1px 0 #ffffff;background-color:#ffffff;border:0;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);outline:0;}
+.navbar-inverse .btn-navbar{color:#ffffff;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e0e0e;background-image:-moz-linear-gradient(top, #151515, #040404);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));background-image:-webkit-linear-gradient(top, #151515, #040404);background-image:-o-linear-gradient(top, #151515, #040404);background-image:linear-gradient(to bottom, #151515, #040404);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);border-color:#040404 #040404 #000000;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#040404;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#ffffff;background-color:#040404;*background-color:#000000;}
+.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000000 \9;}
+.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.breadcrumb>li{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 0 #ffffff;}.breadcrumb>li>.divider{padding:0 5px;color:#ccc;}
+.breadcrumb>.active{color:#999999;}
+.pagination{margin:20px 0;}
+.pagination ul{display:inline-block;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
+.pagination ul>li{display:inline;}
+.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#ffffff;border:1px solid #dddddd;border-left-width:0;}
+.pagination ul>li>a:hover,.pagination ul>li>a:focus,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5;}
+.pagination ul>.active>a,.pagination ul>.active>span{color:#999999;cursor:default;}
+.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>a:focus{color:#999999;background-color:transparent;cursor:default;}
+.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-top-left-radius:4px;-moz-border-radius-topleft:4px;border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px;border-bottom-left-radius:4px;}
+.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;-moz-border-radius-topright:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px;border-bottom-right-radius:4px;}
+.pagination-centered{text-align:center;}
+.pagination-right{text-align:right;}
+.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px;}
+.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-top-left-radius:6px;-moz-border-radius-topleft:6px;border-top-left-radius:6px;-webkit-border-bottom-left-radius:6px;-moz-border-radius-bottomleft:6px;border-bottom-left-radius:6px;}
+.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;-moz-border-radius-topright:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;-moz-border-radius-bottomright:6px;border-bottom-right-radius:6px;}
+.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-top-left-radius:3px;-moz-border-radius-topleft:3px;border-top-left-radius:3px;-webkit-border-bottom-left-radius:3px;-moz-border-radius-bottomleft:3px;border-bottom-left-radius:3px;}
+.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;-moz-border-radius-topright:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;-moz-border-radius-bottomright:3px;border-bottom-right-radius:3px;}
+.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px;}
+.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px;}
+.pager{margin:20px 0;list-style:none;text-align:center;*zoom:1;}.pager:before,.pager:after{display:table;content:"";line-height:0;}
+.pager:after{clear:both;}
+.pager li{display:inline;}
+.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}
+.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#f5f5f5;}
+.pager .next>a,.pager .next>span{float:right;}
+.pager .previous>a,.pager .previous>span{float:left;}
+.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999999;background-color:#fff;cursor:default;}
+.thumbnails{margin-left:-20px;list-style:none;*zoom:1;}.thumbnails:before,.thumbnails:after{display:table;content:"";line-height:0;}
+.thumbnails:after{clear:both;}
+.row-fluid .thumbnails{margin-left:0;}
+.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px;}
+.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);box-shadow:0 1px 3px rgba(0, 0, 0, 0.055);-webkit-transition:all 0.2s ease-in-out;-moz-transition:all 0.2s ease-in-out;-o-transition:all 0.2s ease-in-out;transition:all 0.2s ease-in-out;}
+a.thumbnail:hover,a.thumbnail:focus{border-color:#0088cc;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
+.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto;}
+.thumbnail .caption{padding:9px;color:#555555;}
+.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.alert,.alert h4{color:#c09853;}
+.alert h4{margin:0;}
+.alert .close{position:relative;top:-2px;right:-21px;line-height:20px;}
+.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847;}
+.alert-success h4{color:#468847;}
+.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48;}
+.alert-danger h4,.alert-error h4{color:#b94a48;}
+.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad;}
+.alert-info h4{color:#3a87ad;}
+.alert-block{padding-top:14px;padding-bottom:14px;}
+.alert-block>p,.alert-block>ul{margin-bottom:0;}
+.alert-block p+p{margin-top:5px;}
+@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}@-o-keyframes progress-bar-stripes{from{background-position:0 0;} to{background-position:40px 0;}}@keyframes progress-bar-stripes{from{background-position:40px 0;} to{background-position:0 0;}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:-moz-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));background-image:-webkit-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:-o-linear-gradient(top, #f5f5f5, #f9f9f9);background-image:linear-gradient(to bottom, #f5f5f5, #f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.progress .bar{width:0%;height:100%;color:#ffffff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top, #149bdf, #0480be);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));background-image:-webkit-linear-gradient(top, #149bdf, #0480be);background-image:-o-linear-gradient(top, #149bdf, #0480be);background-image:linear-gradient(to bottom, #149bdf, #0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);box-shadow:inset 0 -1px 0 rgba(0, 0, 0, 0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width 0.6s ease;-moz-transition:width 0.6s ease;-o-transition:width 0.6s ease;transition:width 0.6s ease;}
+.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15);}
+.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px;}
+.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite;}
+.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(to bottom, #ee5f5b, #c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);}
+.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(to bottom, #62c462, #57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);}
+.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(to bottom, #5bc0de, #339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);}
+.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top, #fbb450, #f89406);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));background-image:-webkit-linear-gradient(top, #fbb450, #f89406);background-image:-o-linear-gradient(top, #fbb450, #f89406);background-image:linear-gradient(to bottom, #fbb450, #f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);}
+.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));background-image:-webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);}
+.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;}
+.hero-unit li{line-height:30px;}
+.media,.media-body{overflow:hidden;*overflow:visible;zoom:1;}
+.media,.media .media{margin-top:15px;}
+.media:first-child{margin-top:0;}
+.media-object{display:block;}
+.media-heading{margin:0 0 5px;}
+.media>.pull-left{margin-right:10px;}
+.media>.pull-right{margin-left:10px;}
+.media-list{margin-left:0;list-style:none;}
+.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0);}.tooltip.in{opacity:0.8;filter:alpha(opacity=80);}
+.tooltip.top{margin-top:-3px;padding:5px 0;}
+.tooltip.right{margin-left:3px;padding:0 5px;}
+.tooltip.bottom{margin-top:3px;padding:5px 0;}
+.tooltip.left{margin-left:-3px;padding:0 5px;}
+.tooltip-inner{max-width:200px;padding:8px;color:#ffffff;text-align:center;text-decoration:none;background-color:#000000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid;}
+.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000000;}
+.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000000;}
+.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000000;}
+.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000000;}
+.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#ffffff;-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);white-space:normal;}.popover.top{margin-top:-10px;}
+.popover.right{margin-left:10px;}
+.popover.bottom{margin-top:10px;}
+.popover.left{margin-left:-10px;}
+.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;}.popover-title:empty{display:none;}
+.popover-content{padding:9px 14px;}
+.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid;}
+.popover .arrow{border-width:11px;}
+.popover .arrow:after{border-width:10px;content:"";}
+.popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0, 0, 0, 0.25);bottom:-11px;}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#ffffff;}
+.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0, 0, 0, 0.25);}.popover.right .arrow:after{left:1px;bottom:-10px;border-left-width:0;border-right-color:#ffffff;}
+.popover.bottom .arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0, 0, 0, 0.25);top:-11px;}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#ffffff;}
+.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0, 0, 0, 0.25);}.popover.left .arrow:after{right:1px;border-right-width:0;border-left-color:#ffffff;bottom:-10px;}
+.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000000;}.modal-backdrop.fade{opacity:0;}
+.modal-backdrop,.modal-backdrop.fade.in{opacity:0.8;filter:alpha(opacity=80);}
+.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;outline:none;}.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
+.modal.fade.in{top:10%;}
+.modal-header{padding:9px 15px;border-bottom:1px solid #eee;}.modal-header .close{margin-top:2px;}
+.modal-header h3{margin:0;line-height:30px;}
+.modal-body{position:relative;overflow-y:auto;max-height:400px;padding:15px;}
+.modal-form{margin-bottom:0;}
+.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;*zoom:1;}.modal-footer:before,.modal-footer:after{display:table;content:"";line-height:0;}
+.modal-footer:after{clear:both;}
+.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0;}
+.modal-footer .btn-group .btn+.btn{margin-left:-1px;}
+.modal-footer .btn-block+.btn-block{margin-left:0;}
+.dropup,.dropdown{position:relative;}
+.dropdown-toggle{*margin-bottom:-3px;}
+.dropdown-toggle:active,.open .dropdown-toggle{outline:0;}
+.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000000;border-right:4px solid transparent;border-left:4px solid transparent;content:"";}
+.dropdown .caret{margin-top:8px;margin-left:2px;}
+.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#ffffff;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);box-shadow:0 5px 10px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box;}.dropdown-menu.pull-right{right:0;left:auto;}
+.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #ffffff;}
+.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333333;white-space:nowrap;}
+.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus,.dropdown-submenu:hover>a,.dropdown-submenu:focus>a{text-decoration:none;color:#ffffff;background-color:#0081c2;background-image:-moz-linear-gradient(top, #0088cc, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));background-image:-webkit-linear-gradient(top, #0088cc, #0077b3);background-image:-o-linear-gradient(top, #0088cc, #0077b3);background-image:linear-gradient(to bottom, #0088cc, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);}
+.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#ffffff;text-decoration:none;outline:0;background-color:#0081c2;background-image:-moz-linear-gradient(top, #0088cc, #0077b3);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));background-image:-webkit-linear-gradient(top, #0088cc, #0077b3);background-image:-o-linear-gradient(top, #0088cc, #0077b3);background-image:linear-gradient(to bottom, #0088cc, #0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);}
+.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999999;}
+.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);cursor:default;}
+.open{*z-index:1000;}.open>.dropdown-menu{display:block;}
+.pull-right>.dropdown-menu{right:0;left:auto;}
+.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000000;content:"";}
+.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px;}
+.dropdown-submenu{position:relative;}
+.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
+.dropdown-submenu:hover>.dropdown-menu{display:block;}
+.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0;}
+.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#cccccc;margin-top:5px;margin-right:-10px;}
+.dropdown-submenu:hover>a:after{border-left-color:#ffffff;}
+.dropdown-submenu.pull-left{float:none;}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px;}
+.dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px;}
+.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.accordion{margin-bottom:20px;}
+.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.accordion-heading{border-bottom:0;}
+.accordion-heading .accordion-toggle{display:block;padding:8px 15px;}
+.accordion-toggle{cursor:pointer;}
+.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5;}
+.carousel{position:relative;margin-bottom:20px;line-height:1;}
+.carousel-inner{overflow:hidden;width:100%;position:relative;}
+.carousel-inner>.item{display:none;position:relative;-webkit-transition:0.6s ease-in-out left;-moz-transition:0.6s ease-in-out left;-o-transition:0.6s ease-in-out left;transition:0.6s ease-in-out left;}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;line-height:1;}
+.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block;}
+.carousel-inner>.active{left:0;}
+.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%;}
+.carousel-inner>.next{left:100%;}
+.carousel-inner>.prev{left:-100%;}
+.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0;}
+.carousel-inner>.active.left{left:-100%;}
+.carousel-inner>.active.right{left:100%;}
+.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#ffffff;text-align:center;background:#222222;border:3px solid #ffffff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:0.5;filter:alpha(opacity=50);}.carousel-control.right{left:auto;right:15px;}
+.carousel-control:hover,.carousel-control:focus{color:#ffffff;text-decoration:none;opacity:0.9;filter:alpha(opacity=90);}
+.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none;}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255, 255, 255, 0.25);border-radius:5px;}
+.carousel-indicators .active{background-color:#fff;}
+.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333333;background:rgba(0, 0, 0, 0.75);}
+.carousel-caption h4,.carousel-caption p{color:#ffffff;line-height:20px;}
+.carousel-caption h4{margin:0 0 5px;}
+.carousel-caption p{margin-bottom:0;}
+.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
+.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000000;text-shadow:0 1px 0 #ffffff;opacity:0.2;filter:alpha(opacity=20);}.close:hover,.close:focus{color:#000000;text-decoration:none;cursor:pointer;opacity:0.4;filter:alpha(opacity=40);}
+button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none;}
+.pull-right{float:right;}
+.pull-left{float:left;}
+.hide{display:none;}
+.show{display:block;}
+.invisible{visibility:hidden;}
+.affix{position:fixed;}
+.fade{opacity:0;-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;}.fade.in{opacity:1;}
+.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height 0.35s ease;-moz-transition:height 0.35s ease;-o-transition:height 0.35s ease;transition:height 0.35s ease;}.collapse.in{height:auto;}
+@-ms-viewport{width:device-width;}.hidden{display:none;visibility:hidden;}
+.visible-phone{display:none !important;}
+.visible-tablet{display:none !important;}
+.hidden-desktop{display:none !important;}
+.visible-desktop{display:inherit !important;}
+@media (min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important ;} .visible-tablet{display:inherit !important;} .hidden-tablet{display:none !important;}}@media (max-width:767px){.hidden-desktop{display:inherit !important;} .visible-desktop{display:none !important;} .visible-phone{display:inherit !important;} .hidden-phone{display:none !important;}}.visible-print{display:none !important;}
+@media print{.visible-print{display:inherit !important;} .hidden-print{display:none !important;}}@media (max-width:767px){body{padding-left:20px;padding-right:20px;} .navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-left:-20px;margin-right:-20px;} .container-fluid{padding:0;} .dl-horizontal dt{float:none;clear:none;width:auto;text-align:left;} .dl-horizontal dd{margin-left:0;} .container{width:auto;} .row-fluid{width:100%;} .row,.thumbnails{margin-left:0;} .thumbnails>li{float:none;margin-left:0;} [class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{float:none;display:block;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .row-fluid [class*="offset"]:first-child{margin-left:0;} .input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;} .input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto;} .controls-row [class*="span"]+[class*="span"]{margin-left:0;} .modal{position:fixed;top:20px;left:20px;right:20px;width:auto;margin:0;}.modal.fade{top:-100px;} .modal.fade.in{top:20px;}}@media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0, 0, 0);} .page-header h1 small{display:block;line-height:20px;} input[type="checkbox"],input[type="radio"]{border:1px solid #ccc;} .form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left;} .form-horizontal .controls{margin-left:0;} .form-horizontal .control-list{padding-top:0;} .form-horizontal .form-actions{padding-left:10px;padding-right:10px;} .media .pull-left,.media .pull-right{float:none;display:block;margin-bottom:10px;} .media-object{margin-right:0;margin-left:0;} .modal{top:10px;left:10px;right:10px;} .modal-header .close{padding:10px;margin:-10px;} .carousel-caption{position:static;}}@media (min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} .row:after{clear:both;} [class*="span"]{float:left;min-height:1px;margin-left:20px;} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px;} .span12{width:724px;} .span11{width:662px;} .span10{width:600px;} .span9{width:538px;} .span8{width:476px;} .span7{width:414px;} .span6{width:352px;} .span5{width:290px;} .span4{width:228px;} .span3{width:166px;} .span2{width:104px;} .span1{width:42px;} .offset12{margin-left:764px;} .offset11{margin-left:702px;} .offset10{margin-left:640px;} .offset9{margin-left:578px;} .offset8{margin-left:516px;} .offset7{margin-left:454px;} .offset6{margin-left:392px;} .offset5{margin-left:330px;} .offset4{margin-left:268px;} .offset3{margin-left:206px;} .offset2{margin-left:144px;} .offset1{margin-left:82px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%;} .row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%;} .row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%;} .row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%;} .row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%;} .row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%;} .row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%;} .row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%;} .row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%;} .row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%;} .row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%;} .row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%;} .row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%;} .row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%;} .row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%;} .row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%;} .row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%;} .row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%;} .row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%;} .row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%;} .row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%;} .row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%;} .row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%;} .row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%;} .row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%;} .row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%;} .row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%;} .row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%;} .row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%;} .row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%;} .row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%;} .row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%;} .row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%;} .row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%;} .row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%;} input,textarea,.uneditable-input{margin-left:0;} .controls-row [class*="span"]+[class*="span"]{margin-left:20px;} input.span12,textarea.span12,.uneditable-input.span12{width:710px;} input.span11,textarea.span11,.uneditable-input.span11{width:648px;} input.span10,textarea.span10,.uneditable-input.span10{width:586px;} input.span9,textarea.span9,.uneditable-input.span9{width:524px;} input.span8,textarea.span8,.uneditable-input.span8{width:462px;} input.span7,textarea.span7,.uneditable-input.span7{width:400px;} input.span6,textarea.span6,.uneditable-input.span6{width:338px;} input.span5,textarea.span5,.uneditable-input.span5{width:276px;} input.span4,textarea.span4,.uneditable-input.span4{width:214px;} input.span3,textarea.span3,.uneditable-input.span3{width:152px;} input.span2,textarea.span2,.uneditable-input.span2{width:90px;} input.span1,textarea.span1,.uneditable-input.span1{width:28px;}}@media (min-width:1200px){.row{margin-left:-30px;*zoom:1;}.row:before,.row:after{display:table;content:"";line-height:0;} .row:after{clear:both;} [class*="span"]{float:left;min-height:1px;margin-left:30px;} .container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px;} .span12{width:1170px;} .span11{width:1070px;} .span10{width:970px;} .span9{width:870px;} .span8{width:770px;} .span7{width:670px;} .span6{width:570px;} .span5{width:470px;} .span4{width:370px;} .span3{width:270px;} .span2{width:170px;} .span1{width:70px;} .offset12{margin-left:1230px;} .offset11{margin-left:1130px;} .offset10{margin-left:1030px;} .offset9{margin-left:930px;} .offset8{margin-left:830px;} .offset7{margin-left:730px;} .offset6{margin-left:630px;} .offset5{margin-left:530px;} .offset4{margin-left:430px;} .offset3{margin-left:330px;} .offset2{margin-left:230px;} .offset1{margin-left:130px;} .row-fluid{width:100%;*zoom:1;}.row-fluid:before,.row-fluid:after{display:table;content:"";line-height:0;} .row-fluid:after{clear:both;} .row-fluid [class*="span"]{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;float:left;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;} .row-fluid [class*="span"]:first-child{margin-left:0;} .row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%;} .row-fluid .span12{width:100%;*width:99.94680851063829%;} .row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%;} .row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%;} .row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%;} .row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%;} .row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%;} .row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%;} .row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%;} .row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%;} .row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%;} .row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%;} .row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%;} .row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%;} .row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%;} .row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%;} .row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%;} .row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%;} .row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%;} .row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%;} .row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%;} .row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%;} .row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%;} .row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%;} .row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%;} .row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%;} .row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%;} .row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%;} .row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%;} .row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%;} .row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%;} .row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%;} .row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%;} .row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%;} .row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%;} .row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%;} .row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%;} input,textarea,.uneditable-input{margin-left:0;} .controls-row [class*="span"]+[class*="span"]{margin-left:30px;} input.span12,textarea.span12,.uneditable-input.span12{width:1156px;} input.span11,textarea.span11,.uneditable-input.span11{width:1056px;} input.span10,textarea.span10,.uneditable-input.span10{width:956px;} input.span9,textarea.span9,.uneditable-input.span9{width:856px;} input.span8,textarea.span8,.uneditable-input.span8{width:756px;} input.span7,textarea.span7,.uneditable-input.span7{width:656px;} input.span6,textarea.span6,.uneditable-input.span6{width:556px;} input.span5,textarea.span5,.uneditable-input.span5{width:456px;} input.span4,textarea.span4,.uneditable-input.span4{width:356px;} input.span3,textarea.span3,.uneditable-input.span3{width:256px;} input.span2,textarea.span2,.uneditable-input.span2{width:156px;} input.span1,textarea.span1,.uneditable-input.span1{width:56px;} .thumbnails{margin-left:-30px;} .thumbnails>li{margin-left:30px;} .row-fluid .thumbnails{margin-left:0;}}@media (max-width:979px){body{padding-top:0;} .navbar-fixed-top,.navbar-fixed-bottom{position:static;} .navbar-fixed-top{margin-bottom:20px;} .navbar-fixed-bottom{margin-top:20px;} .navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px;} .navbar .container{width:auto;padding:0;} .navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px;} .nav-collapse{clear:both;} .nav-collapse .nav{float:none;margin:0 0 10px;} .nav-collapse .nav>li{float:none;} .nav-collapse .nav>li>a{margin-bottom:2px;} .nav-collapse .nav>.divider-vertical{display:none;} .nav-collapse .nav .nav-header{color:#777777;text-shadow:none;} .nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;} .nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;} .nav-collapse .dropdown-menu li+li a{margin-bottom:2px;} .nav-collapse .nav>li>a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .dropdown-menu a:focus{background-color:#f2f2f2;} .navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999999;} .navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:focus{background-color:#111111;} .nav-collapse.in .btn-group{margin-top:5px;padding:0;} .nav-collapse .dropdown-menu{position:static;top:auto;left:auto;float:none;display:none;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;} .nav-collapse .open>.dropdown-menu{display:block;} .nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none;} .nav-collapse .dropdown-menu .divider{display:none;} .nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none;} .nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);} .navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111111;border-bottom-color:#111111;} .navbar .nav-collapse .nav.pull-right{float:none;margin-left:0;} .nav-collapse,.nav-collapse.collapse{overflow:hidden;height:0;} .navbar .btn-navbar{display:block;} .navbar-static .navbar-inner{padding-left:10px;padding-right:10px;}}@media (min-width:980px){.nav-collapse.collapse{height:auto !important;overflow:visible !important;}}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/assets/bootstrap/js/bootstrap.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,2279 @@
+/* ===================================================
+ * bootstrap-transition.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
+ * ===================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+  /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
+   * ======================================================= */
+
+  $(function () {
+
+    $.support.transition = (function () {
+
+      var transitionEnd = (function () {
+
+        var el = document.createElement('bootstrap')
+          , transEndEventNames = {
+               'WebkitTransition' : 'webkitTransitionEnd'
+            ,  'MozTransition'    : 'transitionend'
+            ,  'OTransition'      : 'oTransitionEnd otransitionend'
+            ,  'transition'       : 'transitionend'
+            }
+          , name
+
+        for (name in transEndEventNames){
+          if (el.style[name] !== undefined) {
+            return transEndEventNames[name]
+          }
+        }
+
+      }())
+
+      return transitionEnd && {
+        end: transitionEnd
+      }
+
+    })()
+
+  })
+
+}(window.jQuery);
+/* =========================================================
+ * bootstrap-modal.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#modals
+ * =========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* MODAL CLASS DEFINITION
+  * ====================== */
+
+  var Modal = function (element, options) {
+    this.options = options
+    this.$element = $(element)
+      .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
+    this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
+  }
+
+  Modal.prototype = {
+
+      constructor: Modal
+
+    , toggle: function () {
+        return this[!this.isShown ? 'show' : 'hide']()
+      }
+
+    , show: function () {
+        var that = this
+          , e = $.Event('show')
+
+        this.$element.trigger(e)
+
+        if (this.isShown || e.isDefaultPrevented()) return
+
+        this.isShown = true
+
+        this.escape()
+
+        this.backdrop(function () {
+          var transition = $.support.transition && that.$element.hasClass('fade')
+
+          if (!that.$element.parent().length) {
+            that.$element.appendTo(document.body) //don't move modals dom position
+          }
+
+          that.$element.show()
+
+          if (transition) {
+            that.$element[0].offsetWidth // force reflow
+          }
+
+          that.$element
+            .addClass('in')
+            .attr('aria-hidden', false)
+
+          that.enforceFocus()
+
+          transition ?
+            that.$element.one($.support.transition.end, function () { that.$element.focus().trigger('shown') }) :
+            that.$element.focus().trigger('shown')
+
+        })
+      }
+
+    , hide: function (e) {
+        e && e.preventDefault()
+
+        var that = this
+
+        e = $.Event('hide')
+
+        this.$element.trigger(e)
+
+        if (!this.isShown || e.isDefaultPrevented()) return
+
+        this.isShown = false
+
+        this.escape()
+
+        $(document).off('focusin.modal')
+
+        this.$element
+          .removeClass('in')
+          .attr('aria-hidden', true)
+
+        $.support.transition && this.$element.hasClass('fade') ?
+          this.hideWithTransition() :
+          this.hideModal()
+      }
+
+    , enforceFocus: function () {
+        var that = this
+        $(document).on('focusin.modal', function (e) {
+          if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
+            that.$element.focus()
+          }
+        })
+      }
+
+    , escape: function () {
+        var that = this
+        if (this.isShown && this.options.keyboard) {
+          this.$element.on('keyup.dismiss.modal', function ( e ) {
+            e.which == 27 && that.hide()
+          })
+        } else if (!this.isShown) {
+          this.$element.off('keyup.dismiss.modal')
+        }
+      }
+
+    , hideWithTransition: function () {
+        var that = this
+          , timeout = setTimeout(function () {
+              that.$element.off($.support.transition.end)
+              that.hideModal()
+            }, 500)
+
+        this.$element.one($.support.transition.end, function () {
+          clearTimeout(timeout)
+          that.hideModal()
+        })
+      }
+
+    , hideModal: function () {
+        var that = this
+        this.$element.hide()
+        this.backdrop(function () {
+          that.removeBackdrop()
+          that.$element.trigger('hidden')
+        })
+      }
+
+    , removeBackdrop: function () {
+        this.$backdrop.remove()
+        this.$backdrop = null
+      }
+
+    , backdrop: function (callback) {
+        var that = this
+          , animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+        if (this.isShown && this.options.backdrop) {
+          var doAnimate = $.support.transition && animate
+
+          this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
+            .appendTo(document.body)
+
+          this.$backdrop.click(
+            this.options.backdrop == 'static' ?
+              $.proxy(this.$element[0].focus, this.$element[0])
+            : $.proxy(this.hide, this)
+          )
+
+          if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+          this.$backdrop.addClass('in')
+
+          if (!callback) return
+
+          doAnimate ?
+            this.$backdrop.one($.support.transition.end, callback) :
+            callback()
+
+        } else if (!this.isShown && this.$backdrop) {
+          this.$backdrop.removeClass('in')
+
+          $.support.transition && this.$element.hasClass('fade')?
+            this.$backdrop.one($.support.transition.end, callback) :
+            callback()
+
+        } else if (callback) {
+          callback()
+        }
+      }
+  }
+
+
+ /* MODAL PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.modal
+
+  $.fn.modal = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('modal')
+        , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
+      if (!data) $this.data('modal', (data = new Modal(this, options)))
+      if (typeof option == 'string') data[option]()
+      else if (options.show) data.show()
+    })
+  }
+
+  $.fn.modal.defaults = {
+      backdrop: true
+    , keyboard: true
+    , show: true
+  }
+
+  $.fn.modal.Constructor = Modal
+
+
+ /* MODAL NO CONFLICT
+  * ================= */
+
+  $.fn.modal.noConflict = function () {
+    $.fn.modal = old
+    return this
+  }
+
+
+ /* MODAL DATA-API
+  * ============== */
+
+  $(document).on('click.modal.data-api', '[data-toggle="modal"]', function (e) {
+    var $this = $(this)
+      , href = $this.attr('href')
+      , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+      , option = $target.data('modal') ? 'toggle' : $.extend({ remote:!/#/.test(href) && href }, $target.data(), $this.data())
+
+    e.preventDefault()
+
+    $target
+      .modal(option)
+      .one('hide', function () {
+        $this.focus()
+      })
+  })
+
+}(window.jQuery);
+
+/* ============================================================
+ * bootstrap-dropdown.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* DROPDOWN CLASS DEFINITION
+  * ========================= */
+
+  var toggle = '[data-toggle=dropdown]'
+    , Dropdown = function (element) {
+        var $el = $(element).on('click.dropdown.data-api', this.toggle)
+        $('html').on('click.dropdown.data-api', function () {
+          $el.parent().removeClass('open')
+        })
+      }
+
+  Dropdown.prototype = {
+
+    constructor: Dropdown
+
+  , toggle: function (e) {
+      var $this = $(this)
+        , $parent
+        , isActive
+
+      if ($this.is('.disabled, :disabled')) return
+
+      $parent = getParent($this)
+
+      isActive = $parent.hasClass('open')
+
+      clearMenus()
+
+      if (!isActive) {
+        $parent.toggleClass('open')
+      }
+
+      $this.focus()
+
+      return false
+    }
+
+  , keydown: function (e) {
+      var $this
+        , $items
+        , $active
+        , $parent
+        , isActive
+        , index
+
+      if (!/(38|40|27)/.test(e.keyCode)) return
+
+      $this = $(this)
+
+      e.preventDefault()
+      e.stopPropagation()
+
+      if ($this.is('.disabled, :disabled')) return
+
+      $parent = getParent($this)
+
+      isActive = $parent.hasClass('open')
+
+      if (!isActive || (isActive && e.keyCode == 27)) {
+        if (e.which == 27) $parent.find(toggle).focus()
+        return $this.click()
+      }
+
+      $items = $('[role=menu] li:not(.divider):visible a', $parent)
+
+      if (!$items.length) return
+
+      index = $items.index($items.filter(':focus'))
+
+      if (e.keyCode == 38 && index > 0) index--                                        // up
+      if (e.keyCode == 40 && index < $items.length - 1) index++                        // down
+      if (!~index) index = 0
+
+      $items
+        .eq(index)
+        .focus()
+    }
+
+  }
+
+  function clearMenus() {
+    $(toggle).each(function () {
+      getParent($(this)).removeClass('open')
+    })
+  }
+
+  function getParent($this) {
+    var selector = $this.attr('data-target')
+      , $parent
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    $parent = selector && $(selector)
+
+    if (!$parent || !$parent.length) $parent = $this.parent()
+
+    return $parent
+  }
+
+
+  /* DROPDOWN PLUGIN DEFINITION
+   * ========================== */
+
+  var old = $.fn.dropdown
+
+  $.fn.dropdown = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('dropdown')
+      if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.dropdown.Constructor = Dropdown
+
+
+ /* DROPDOWN NO CONFLICT
+  * ==================== */
+
+  $.fn.dropdown.noConflict = function () {
+    $.fn.dropdown = old
+    return this
+  }
+
+
+  /* APPLY TO STANDARD DROPDOWN ELEMENTS
+   * =================================== */
+
+  $(document)
+    .on('click.dropdown.data-api', clearMenus)
+    .on('click.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+    .on('.dropdown-menu', function (e) { e.stopPropagation() })
+    .on('click.dropdown.data-api'  , toggle, Dropdown.prototype.toggle)
+    .on('keydown.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+
+}(window.jQuery);
+
+/* =============================================================
+ * bootstrap-scrollspy.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#scrollspy
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* SCROLLSPY CLASS DEFINITION
+  * ========================== */
+
+  function ScrollSpy(element, options) {
+    var process = $.proxy(this.process, this)
+      , $element = $(element).is('body') ? $(window) : $(element)
+      , href
+    this.options = $.extend({}, $.fn.scrollspy.defaults, options)
+    this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
+    this.selector = (this.options.target
+      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+      || '') + ' .nav li > a'
+    this.$body = $('body')
+    this.refresh()
+    this.process()
+  }
+
+  ScrollSpy.prototype = {
+
+      constructor: ScrollSpy
+
+    , refresh: function () {
+        var self = this
+          , $targets
+
+        this.offsets = $([])
+        this.targets = $([])
+
+        $targets = this.$body
+          .find(this.selector)
+          .map(function () {
+            var $el = $(this)
+              , href = $el.data('target') || $el.attr('href')
+              , $href = /^#\w/.test(href) && $(href)
+            return ( $href
+              && $href.length
+              && [[ $href.position().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]] ) || null
+          })
+          .sort(function (a, b) { return a[0] - b[0] })
+          .each(function () {
+            self.offsets.push(this[0])
+            self.targets.push(this[1])
+          })
+      }
+
+    , process: function () {
+        var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+          , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+          , maxScroll = scrollHeight - this.$scrollElement.height()
+          , offsets = this.offsets
+          , targets = this.targets
+          , activeTarget = this.activeTarget
+          , i
+
+        if (scrollTop >= maxScroll) {
+          return activeTarget != (i = targets.last()[0])
+            && this.activate ( i )
+        }
+
+        for (i = offsets.length; i--;) {
+          activeTarget != targets[i]
+            && scrollTop >= offsets[i]
+            && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+            && this.activate( targets[i] )
+        }
+      }
+
+    , activate: function (target) {
+        var active
+          , selector
+
+        this.activeTarget = target
+
+        $(this.selector)
+          .parent('.active')
+          .removeClass('active')
+
+        selector = this.selector
+          + '[data-target="' + target + '"],'
+          + this.selector + '[href="' + target + '"]'
+
+        active = $(selector)
+          .parent('li')
+          .addClass('active')
+
+        if (active.parent('.dropdown-menu').length)  {
+          active = active.closest('li.dropdown').addClass('active')
+        }
+
+        active.trigger('activate')
+      }
+
+  }
+
+
+ /* SCROLLSPY PLUGIN DEFINITION
+  * =========================== */
+
+  var old = $.fn.scrollspy
+
+  $.fn.scrollspy = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('scrollspy')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.scrollspy.Constructor = ScrollSpy
+
+  $.fn.scrollspy.defaults = {
+    offset: 10
+  }
+
+
+ /* SCROLLSPY NO CONFLICT
+  * ===================== */
+
+  $.fn.scrollspy.noConflict = function () {
+    $.fn.scrollspy = old
+    return this
+  }
+
+
+ /* SCROLLSPY DATA-API
+  * ================== */
+
+  $(window).on('load', function () {
+    $('[data-spy="scroll"]').each(function () {
+      var $spy = $(this)
+      $spy.scrollspy($spy.data())
+    })
+  })
+
+}(window.jQuery);
+/* ========================================================
+ * bootstrap-tab.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#tabs
+ * ========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* TAB CLASS DEFINITION
+  * ==================== */
+
+  var Tab = function (element) {
+    this.element = $(element)
+  }
+
+  Tab.prototype = {
+
+    constructor: Tab
+
+  , show: function () {
+      var $this = this.element
+        , $ul = $this.closest('ul:not(.dropdown-menu)')
+        , selector = $this.attr('data-target')
+        , previous
+        , $target
+        , e
+
+      if (!selector) {
+        selector = $this.attr('href')
+        selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+      }
+
+      if ( $this.parent('li').hasClass('active') ) return
+
+      previous = $ul.find('.active:last a')[0]
+
+      e = $.Event('show', {
+        relatedTarget: previous
+      })
+
+      $this.trigger(e)
+
+      if (e.isDefaultPrevented()) return
+
+      $target = $(selector)
+
+      this.activate($this.parent('li'), $ul)
+      this.activate($target, $target.parent(), function () {
+        $this.trigger({
+          type: 'shown'
+        , relatedTarget: previous
+        })
+      })
+    }
+
+  , activate: function ( element, container, callback) {
+      var $active = container.find('> .active')
+        , transition = callback
+            && $.support.transition
+            && $active.hasClass('fade')
+
+      function next() {
+        $active
+          .removeClass('active')
+          .find('> .dropdown-menu > .active')
+          .removeClass('active')
+
+        element.addClass('active')
+
+        if (transition) {
+          element[0].offsetWidth // reflow for transition
+          element.addClass('in')
+        } else {
+          element.removeClass('fade')
+        }
+
+        if ( element.parent('.dropdown-menu') ) {
+          element.closest('li.dropdown').addClass('active')
+        }
+
+        callback && callback()
+      }
+
+      transition ?
+        $active.one($.support.transition.end, next) :
+        next()
+
+      $active.removeClass('in')
+    }
+  }
+
+
+ /* TAB PLUGIN DEFINITION
+  * ===================== */
+
+  var old = $.fn.tab
+
+  $.fn.tab = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('tab')
+      if (!data) $this.data('tab', (data = new Tab(this)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tab.Constructor = Tab
+
+
+ /* TAB NO CONFLICT
+  * =============== */
+
+  $.fn.tab.noConflict = function () {
+    $.fn.tab = old
+    return this
+  }
+
+
+ /* TAB DATA-API
+  * ============ */
+
+  $(document).on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+    e.preventDefault()
+    $(this).tab('show')
+  })
+
+}(window.jQuery);
+/* ===========================================================
+ * bootstrap-tooltip.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+  * =============================== */
+
+  var Tooltip = function (element, options) {
+    this.init('tooltip', element, options)
+  }
+
+  Tooltip.prototype = {
+
+    constructor: Tooltip
+
+  , init: function (type, element, options) {
+      var eventIn
+        , eventOut
+        , triggers
+        , trigger
+        , i
+
+      this.type = type
+      this.$element = $(element)
+      this.options = this.getOptions(options)
+      this.enabled = true
+
+      triggers = this.options.trigger.split(' ')
+
+      for (i = triggers.length; i--;) {
+        trigger = triggers[i]
+        if (trigger == 'click') {
+          this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+        } else if (trigger != 'manual') {
+          eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
+          eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
+          this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+          this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+        }
+      }
+
+      this.options.selector ?
+        (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+        this.fixTitle()
+    }
+
+  , getOptions: function (options) {
+      options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)
+
+      if (options.delay && typeof options.delay == 'number') {
+        options.delay = {
+          show: options.delay
+        , hide: options.delay
+        }
+      }
+
+      return options
+    }
+
+  , enter: function (e) {
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+      if (!self.options.delay || !self.options.delay.show) return self.show()
+
+      clearTimeout(this.timeout)
+      self.hoverState = 'in'
+      this.timeout = setTimeout(function() {
+        if (self.hoverState == 'in') self.show()
+      }, self.options.delay.show)
+    }
+
+  , leave: function (e) {
+      var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+      if (this.timeout) clearTimeout(this.timeout)
+      if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+      self.hoverState = 'out'
+      this.timeout = setTimeout(function() {
+        if (self.hoverState == 'out') self.hide()
+      }, self.options.delay.hide)
+    }
+
+  , show: function () {
+      var $tip
+        , pos
+        , actualWidth
+        , actualHeight
+        , placement
+        , tp
+        , e = $.Event('show')
+
+      if (this.hasContent() && this.enabled) {
+        this.$element.trigger(e)
+        if (e.isDefaultPrevented()) return
+        $tip = this.tip()
+        this.setContent()
+
+        if (this.options.animation) {
+          $tip.addClass('fade')
+        }
+
+        placement = typeof this.options.placement == 'function' ?
+          this.options.placement.call(this, $tip[0], this.$element[0]) :
+          this.options.placement
+
+        $tip
+          .detach()
+          .css({ top: 0, left: 0, display: 'block' })
+
+        this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+        pos = this.getPosition()
+
+        actualWidth = $tip[0].offsetWidth
+        actualHeight = $tip[0].offsetHeight
+
+        switch (placement) {
+          case 'bottom':
+            tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+            break
+          case 'top':
+            tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+            break
+          case 'left':
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+            break
+          case 'right':
+            tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+            break
+        }
+
+        this.applyPlacement(tp, placement)
+        this.$element.trigger('shown')
+      }
+    }
+
+  , applyPlacement: function(offset, placement){
+      var $tip = this.tip()
+        , width = $tip[0].offsetWidth
+        , height = $tip[0].offsetHeight
+        , actualWidth
+        , actualHeight
+        , delta
+        , replace
+
+      $tip
+        .offset(offset)
+        .addClass(placement)
+        .addClass('in')
+
+      actualWidth = $tip[0].offsetWidth
+      actualHeight = $tip[0].offsetHeight
+
+      if (placement == 'top' && actualHeight != height) {
+        offset.top = offset.top + height - actualHeight
+        replace = true
+      }
+
+      if (placement == 'bottom' || placement == 'top') {
+        delta = 0
+
+        if (offset.left < 0){
+          delta = offset.left * -2
+          offset.left = 0
+          $tip.offset(offset)
+          actualWidth = $tip[0].offsetWidth
+          actualHeight = $tip[0].offsetHeight
+        }
+
+        this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
+      } else {
+        this.replaceArrow(actualHeight - height, actualHeight, 'top')
+      }
+
+      if (replace) $tip.offset(offset)
+    }
+
+  , replaceArrow: function(delta, dimension, position){
+      this
+        .arrow()
+        .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
+    }
+
+  , setContent: function () {
+      var $tip = this.tip()
+        , title = this.getTitle()
+
+      $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+      $tip.removeClass('fade in top bottom left right')
+    }
+
+  , hide: function () {
+      var that = this
+        , $tip = this.tip()
+        , e = $.Event('hide')
+
+      this.$element.trigger(e)
+      if (e.isDefaultPrevented()) return
+
+      $tip.removeClass('in')
+
+      function removeWithAnimation() {
+        var timeout = setTimeout(function () {
+          $tip.off($.support.transition.end).detach()
+        }, 500)
+
+        $tip.one($.support.transition.end, function () {
+          clearTimeout(timeout)
+          $tip.detach()
+        })
+      }
+
+      $.support.transition && this.$tip.hasClass('fade') ?
+        removeWithAnimation() :
+        $tip.detach()
+
+      this.$element.trigger('hidden')
+
+      return this
+    }
+
+  , fixTitle: function () {
+      var $e = this.$element
+      if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+        $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+      }
+    }
+
+  , hasContent: function () {
+      return this.getTitle()
+    }
+
+  , getPosition: function () {
+      var el = this.$element[0]
+      return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
+        width: el.offsetWidth
+      , height: el.offsetHeight
+      }, this.$element.offset())
+    }
+
+  , getTitle: function () {
+      var title
+        , $e = this.$element
+        , o = this.options
+
+      title = $e.attr('data-original-title')
+        || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)
+
+      return title
+    }
+
+  , tip: function () {
+      return this.$tip = this.$tip || $(this.options.template)
+    }
+
+  , arrow: function(){
+      return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
+    }
+
+  , validate: function () {
+      if (!this.$element[0].parentNode) {
+        this.hide()
+        this.$element = null
+        this.options = null
+      }
+    }
+
+  , enable: function () {
+      this.enabled = true
+    }
+
+  , disable: function () {
+      this.enabled = false
+    }
+
+  , toggleEnabled: function () {
+      this.enabled = !this.enabled
+    }
+
+  , toggle: function (e) {
+      var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
+      self.tip().hasClass('in') ? self.hide() : self.show()
+    }
+
+  , destroy: function () {
+      this.hide().$element.off('.' + this.type).removeData(this.type)
+    }
+
+  }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+  * ========================= */
+
+  var old = $.fn.tooltip
+
+  $.fn.tooltip = function ( option ) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('tooltip')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.tooltip.Constructor = Tooltip
+
+  $.fn.tooltip.defaults = {
+    animation: true
+  , placement: 'top'
+  , selector: false
+  , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
+  , trigger: 'hover focus'
+  , title: ''
+  , delay: 0
+  , html: false
+  , container: false
+  }
+
+
+ /* TOOLTIP NO CONFLICT
+  * =================== */
+
+  $.fn.tooltip.noConflict = function () {
+    $.fn.tooltip = old
+    return this
+  }
+
+}(window.jQuery);
+
+/* ===========================================================
+ * bootstrap-popover.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* POPOVER PUBLIC CLASS DEFINITION
+  * =============================== */
+
+  var Popover = function (element, options) {
+    this.init('popover', element, options)
+  }
+
+
+  /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+     ========================================== */
+
+  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+    constructor: Popover
+
+  , setContent: function () {
+      var $tip = this.tip()
+        , title = this.getTitle()
+        , content = this.getContent()
+
+      $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+      $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+      $tip.removeClass('fade top bottom left right in')
+    }
+
+  , hasContent: function () {
+      return this.getTitle() || this.getContent()
+    }
+
+  , getContent: function () {
+      var content
+        , $e = this.$element
+        , o = this.options
+
+      content = (typeof o.content == 'function' ? o.content.call($e[0]) :  o.content)
+        || $e.attr('data-content')
+
+      return content
+    }
+
+  , tip: function () {
+      if (!this.$tip) {
+        this.$tip = $(this.options.template)
+      }
+      return this.$tip
+    }
+
+  , destroy: function () {
+      this.hide().$element.off('.' + this.type).removeData(this.type)
+    }
+
+  })
+
+
+ /* POPOVER PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.popover
+
+  $.fn.popover = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('popover')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('popover', (data = new Popover(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.popover.Constructor = Popover
+
+  $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+    placement: 'right'
+  , trigger: 'click'
+  , content: ''
+  , template: '<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
+  })
+
+
+ /* POPOVER NO CONFLICT
+  * =================== */
+
+  $.fn.popover.noConflict = function () {
+    $.fn.popover = old
+    return this
+  }
+
+}(window.jQuery);
+
+/* ==========================================================
+ * bootstrap-affix.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#affix
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* AFFIX CLASS DEFINITION
+  * ====================== */
+
+  var Affix = function (element, options) {
+    this.options = $.extend({}, $.fn.affix.defaults, options)
+    this.$window = $(window)
+      .on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
+      .on('click.affix.data-api',  $.proxy(function () { setTimeout($.proxy(this.checkPosition, this), 1) }, this))
+    this.$element = $(element)
+    this.checkPosition()
+  }
+
+  Affix.prototype.checkPosition = function () {
+    if (!this.$element.is(':visible')) return
+
+    var scrollHeight = $(document).height()
+      , scrollTop = this.$window.scrollTop()
+      , position = this.$element.offset()
+      , offset = this.options.offset
+      , offsetBottom = offset.bottom
+      , offsetTop = offset.top
+      , reset = 'affix affix-top affix-bottom'
+      , affix
+
+    if (typeof offset != 'object') offsetBottom = offsetTop = offset
+    if (typeof offsetTop == 'function') offsetTop = offset.top()
+    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+    affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
+      false    : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
+      'bottom' : offsetTop != null && scrollTop <= offsetTop ?
+      'top'    : false
+
+    if (this.affixed === affix) return
+
+    this.affixed = affix
+    this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+    this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
+  }
+
+
+ /* AFFIX PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.affix
+
+  $.fn.affix = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('affix')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('affix', (data = new Affix(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.affix.Constructor = Affix
+
+  $.fn.affix.defaults = {
+    offset: 0
+  }
+
+
+ /* AFFIX NO CONFLICT
+  * ================= */
+
+  $.fn.affix.noConflict = function () {
+    $.fn.affix = old
+    return this
+  }
+
+
+ /* AFFIX DATA-API
+  * ============== */
+
+  $(window).on('load', function () {
+    $('[data-spy="affix"]').each(function () {
+      var $spy = $(this)
+        , data = $spy.data()
+
+      data.offset = data.offset || {}
+
+      data.offsetBottom && (data.offset.bottom = data.offsetBottom)
+      data.offsetTop && (data.offset.top = data.offsetTop)
+
+      $spy.affix(data)
+    })
+  })
+
+
+}(window.jQuery);
+/* ==========================================================
+ * bootstrap-alert.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* ALERT CLASS DEFINITION
+  * ====================== */
+
+  var dismiss = '[data-dismiss="alert"]'
+    , Alert = function (el) {
+        $(el).on('click', dismiss, this.close)
+      }
+
+  Alert.prototype.close = function (e) {
+    var $this = $(this)
+      , selector = $this.attr('data-target')
+      , $parent
+
+    if (!selector) {
+      selector = $this.attr('href')
+      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+    }
+
+    $parent = $(selector)
+
+    e && e.preventDefault()
+
+    $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+    $parent.trigger(e = $.Event('close'))
+
+    if (e.isDefaultPrevented()) return
+
+    $parent.removeClass('in')
+
+    function removeElement() {
+      $parent
+        .trigger('closed')
+        .remove()
+    }
+
+    $.support.transition && $parent.hasClass('fade') ?
+      $parent.on($.support.transition.end, removeElement) :
+      removeElement()
+  }
+
+
+ /* ALERT PLUGIN DEFINITION
+  * ======================= */
+
+  var old = $.fn.alert
+
+  $.fn.alert = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('alert')
+      if (!data) $this.data('alert', (data = new Alert(this)))
+      if (typeof option == 'string') data[option].call($this)
+    })
+  }
+
+  $.fn.alert.Constructor = Alert
+
+
+ /* ALERT NO CONFLICT
+  * ================= */
+
+  $.fn.alert.noConflict = function () {
+    $.fn.alert = old
+    return this
+  }
+
+
+ /* ALERT DATA-API
+  * ============== */
+
+  $(document).on('click.alert.data-api', dismiss, Alert.prototype.close)
+
+}(window.jQuery);
+/* ============================================================
+ * bootstrap-button.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#buttons
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* BUTTON PUBLIC CLASS DEFINITION
+  * ============================== */
+
+  var Button = function (element, options) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.button.defaults, options)
+  }
+
+  Button.prototype.setState = function (state) {
+    var d = 'disabled'
+      , $el = this.$element
+      , data = $el.data()
+      , val = $el.is('input') ? 'val' : 'html'
+
+    state = state + 'Text'
+    data.resetText || $el.data('resetText', $el[val]())
+
+    $el[val](data[state] || this.options[state])
+
+    // push to event loop to allow forms to submit
+    setTimeout(function () {
+      state == 'loadingText' ?
+        $el.addClass(d).attr(d, d) :
+        $el.removeClass(d).removeAttr(d)
+    }, 0)
+  }
+
+  Button.prototype.toggle = function () {
+    var $parent = this.$element.closest('[data-toggle="buttons-radio"]')
+
+    $parent && $parent
+      .find('.active')
+      .removeClass('active')
+
+    this.$element.toggleClass('active')
+  }
+
+
+ /* BUTTON PLUGIN DEFINITION
+  * ======================== */
+
+  var old = $.fn.button
+
+  $.fn.button = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('button')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('button', (data = new Button(this, options)))
+      if (option == 'toggle') data.toggle()
+      else if (option) data.setState(option)
+    })
+  }
+
+  $.fn.button.defaults = {
+    loadingText: 'loading...'
+  }
+
+  $.fn.button.Constructor = Button
+
+
+ /* BUTTON NO CONFLICT
+  * ================== */
+
+  $.fn.button.noConflict = function () {
+    $.fn.button = old
+    return this
+  }
+
+
+ /* BUTTON DATA-API
+  * =============== */
+
+  $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) {
+    var $btn = $(e.target)
+    if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+    $btn.button('toggle')
+  })
+
+}(window.jQuery);
+/* =============================================================
+ * bootstrap-collapse.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#collapse
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* COLLAPSE PUBLIC CLASS DEFINITION
+  * ================================ */
+
+  var Collapse = function (element, options) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.collapse.defaults, options)
+
+    if (this.options.parent) {
+      this.$parent = $(this.options.parent)
+    }
+
+    this.options.toggle && this.toggle()
+  }
+
+  Collapse.prototype = {
+
+    constructor: Collapse
+
+  , dimension: function () {
+      var hasWidth = this.$element.hasClass('width')
+      return hasWidth ? 'width' : 'height'
+    }
+
+  , show: function () {
+      var dimension
+        , scroll
+        , actives
+        , hasData
+
+      if (this.transitioning || this.$element.hasClass('in')) return
+
+      dimension = this.dimension()
+      scroll = $.camelCase(['scroll', dimension].join('-'))
+      actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+      if (actives && actives.length) {
+        hasData = actives.data('collapse')
+        if (hasData && hasData.transitioning) return
+        actives.collapse('hide')
+        hasData || actives.data('collapse', null)
+      }
+
+      this.$element[dimension](0)
+      this.transition('addClass', $.Event('show'), 'shown')
+      $.support.transition && this.$element[dimension](this.$element[0][scroll])
+    }
+
+  , hide: function () {
+      var dimension
+      if (this.transitioning || !this.$element.hasClass('in')) return
+      dimension = this.dimension()
+      this.reset(this.$element[dimension]())
+      this.transition('removeClass', $.Event('hide'), 'hidden')
+      this.$element[dimension](0)
+    }
+
+  , reset: function (size) {
+      var dimension = this.dimension()
+
+      this.$element
+        .removeClass('collapse')
+        [dimension](size || 'auto')
+        [0].offsetWidth
+
+      this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
+
+      return this
+    }
+
+  , transition: function (method, startEvent, completeEvent) {
+      var that = this
+        , complete = function () {
+            if (startEvent.type == 'show') that.reset()
+            that.transitioning = 0
+            that.$element.trigger(completeEvent)
+          }
+
+      this.$element.trigger(startEvent)
+
+      if (startEvent.isDefaultPrevented()) return
+
+      this.transitioning = 1
+
+      this.$element[method]('in')
+
+      $.support.transition && this.$element.hasClass('collapse') ?
+        this.$element.one($.support.transition.end, complete) :
+        complete()
+    }
+
+  , toggle: function () {
+      this[this.$element.hasClass('in') ? 'hide' : 'show']()
+    }
+
+  }
+
+
+ /* COLLAPSE PLUGIN DEFINITION
+  * ========================== */
+
+  var old = $.fn.collapse
+
+  $.fn.collapse = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('collapse')
+        , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option)
+      if (!data) $this.data('collapse', (data = new Collapse(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.collapse.defaults = {
+    toggle: true
+  }
+
+  $.fn.collapse.Constructor = Collapse
+
+
+ /* COLLAPSE NO CONFLICT
+  * ==================== */
+
+  $.fn.collapse.noConflict = function () {
+    $.fn.collapse = old
+    return this
+  }
+
+
+ /* COLLAPSE DATA-API
+  * ================= */
+
+  $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
+    var $this = $(this), href
+      , target = $this.attr('data-target')
+        || e.preventDefault()
+        || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+      , option = $(target).data('collapse') ? 'toggle' : $this.data()
+    $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+    $(target).collapse(option)
+  })
+
+}(window.jQuery);
+/* ==========================================================
+ * bootstrap-carousel.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#carousel
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+  "use strict"; // jshint ;_;
+
+
+ /* CAROUSEL CLASS DEFINITION
+  * ========================= */
+
+  var Carousel = function (element, options) {
+    this.$element = $(element)
+    this.$indicators = this.$element.find('.carousel-indicators')
+    this.options = options
+    this.options.pause == 'hover' && this.$element
+      .on('mouseenter', $.proxy(this.pause, this))
+      .on('mouseleave', $.proxy(this.cycle, this))
+  }
+
+  Carousel.prototype = {
+
+    cycle: function (e) {
+      if (!e) this.paused = false
+      if (this.interval) clearInterval(this.interval);
+      this.options.interval
+        && !this.paused
+        && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+      return this
+    }
+
+  , getActiveIndex: function () {
+      this.$active = this.$element.find('.item.active')
+      this.$items = this.$active.parent().children()
+      return this.$items.index(this.$active)
+    }
+
+  , to: function (pos) {
+      var activeIndex = this.getActiveIndex()
+        , that = this
+
+      if (pos > (this.$items.length - 1) || pos < 0) return
+
+      if (this.sliding) {
+        return this.$element.one('slid', function () {
+          that.to(pos)
+        })
+      }
+
+      if (activeIndex == pos) {
+        return this.pause().cycle()
+      }
+
+      return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
+    }
+
+  , pause: function (e) {
+      if (!e) this.paused = true
+      if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+        this.$element.trigger($.support.transition.end)
+        this.cycle()
+      }
+      clearInterval(this.interval)
+      this.interval = null
+      return this
+    }
+
+  , next: function () {
+      if (this.sliding) return
+      return this.slide('next')
+    }
+
+  , prev: function () {
+      if (this.sliding) return
+      return this.slide('prev')
+    }
+
+  , slide: function (type, next) {
+      var $active = this.$element.find('.item.active')
+        , $next = next || $active[type]()
+        , isCycling = this.interval
+        , direction = type == 'next' ? 'left' : 'right'
+        , fallback  = type == 'next' ? 'first' : 'last'
+        , that = this
+        , e
+
+      this.sliding = true
+
+      isCycling && this.pause()
+
+      $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+      e = $.Event('slide', {
+        relatedTarget: $next[0]
+      , direction: direction
+      })
+
+      if ($next.hasClass('active')) return
+
+      if (this.$indicators.length) {
+        this.$indicators.find('.active').removeClass('active')
+        this.$element.one('slid', function () {
+          var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
+          $nextIndicator && $nextIndicator.addClass('active')
+        })
+      }
+
+      if ($.support.transition && this.$element.hasClass('slide')) {
+        this.$element.trigger(e)
+        if (e.isDefaultPrevented()) return
+        $next.addClass(type)
+        $next[0].offsetWidth // force reflow
+        $active.addClass(direction)
+        $next.addClass(direction)
+        this.$element.one($.support.transition.end, function () {
+          $next.removeClass([type, direction].join(' ')).addClass('active')
+          $active.removeClass(['active', direction].join(' '))
+          that.sliding = false
+          setTimeout(function () { that.$element.trigger('slid') }, 0)
+        })
+      } else {
+        this.$element.trigger(e)
+        if (e.isDefaultPrevented()) return
+        $active.removeClass('active')
+        $next.addClass('active')
+        this.sliding = false
+        this.$element.trigger('slid')
+      }
+
+      isCycling && this.cycle()
+
+      return this
+    }
+
+  }
+
+
+ /* CAROUSEL PLUGIN DEFINITION
+  * ========================== */
+
+  var old = $.fn.carousel
+
+  $.fn.carousel = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('carousel')
+        , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
+        , action = typeof option == 'string' ? option : options.slide
+      if (!data) $this.data('carousel', (data = new Carousel(this, options)))
+      if (typeof option == 'number') data.to(option)
+      else if (action) data[action]()
+      else if (options.interval) data.pause().cycle()
+    })
+  }
+
+  $.fn.carousel.defaults = {
+    interval: 5000
+  , pause: 'hover'
+  }
+
+  $.fn.carousel.Constructor = Carousel
+
+
+ /* CAROUSEL NO CONFLICT
+  * ==================== */
+
+  $.fn.carousel.noConflict = function () {
+    $.fn.carousel = old
+    return this
+  }
+
+ /* CAROUSEL DATA-API
+  * ================= */
+
+  $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
+    var $this = $(this), href
+      , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+      , options = $.extend({}, $target.data(), $this.data())
+      , slideIndex
+
+    $target.carousel(options)
+
+    if (slideIndex = $this.attr('data-slide-to')) {
+      $target.data('carousel').pause().to(slideIndex).cycle()
+    }
+
+    e.preventDefault()
+  })
+
+}(window.jQuery);
+/* =============================================================
+ * bootstrap-typeahead.js v2.3.0
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+  "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+  * ================================= */
+
+  var Typeahead = function (element, options) {
+    this.$element = $(element)
+    this.options = $.extend({}, $.fn.typeahead.defaults, options)
+    this.matcher = this.options.matcher || this.matcher
+    this.sorter = this.options.sorter || this.sorter
+    this.highlighter = this.options.highlighter || this.highlighter
+    this.updater = this.options.updater || this.updater
+    this.source = this.options.source
+    this.$menu = $(this.options.menu)
+    this.shown = false
+    this.listen()
+  }
+
+  Typeahead.prototype = {
+
+    constructor: Typeahead
+
+  , select: function () {
+      var val = this.$menu.find('.active').attr('data-value')
+      this.$element
+        .val(this.updater(val))
+        .change()
+      return this.hide()
+    }
+
+  , updater: function (item) {
+      return item
+    }
+
+  , show: function () {
+      var pos = $.extend({}, this.$element.position(), {
+        height: this.$element[0].offsetHeight
+      })
+
+      this.$menu
+        .insertAfter(this.$element)
+        .css({
+          top: pos.top + pos.height
+        , left: pos.left
+        })
+        .show()
+
+      this.shown = true
+      return this
+    }
+
+  , hide: function () {
+      this.$menu.hide()
+      this.shown = false
+      return this
+    }
+
+  , lookup: function (event) {
+      var items
+
+      this.query = this.$element.val()
+
+      if (!this.query || this.query.length < this.options.minLength) {
+        return this.shown ? this.hide() : this
+      }
+
+      items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+      return items ? this.process(items) : this
+    }
+
+  , process: function (items) {
+      var that = this
+
+      items = $.grep(items, function (item) {
+        return that.matcher(item)
+      })
+
+      items = this.sorter(items)
+
+      if (!items.length) {
+        return this.shown ? this.hide() : this
+      }
+
+      return this.render(items.slice(0, this.options.items)).show()
+    }
+
+  , matcher: function (item) {
+      return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+    }
+
+  , sorter: function (items) {
+      var beginswith = []
+        , caseSensitive = []
+        , caseInsensitive = []
+        , item
+
+      while (item = items.shift()) {
+        if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+        else if (~item.indexOf(this.query)) caseSensitive.push(item)
+        else caseInsensitive.push(item)
+      }
+
+      return beginswith.concat(caseSensitive, caseInsensitive)
+    }
+
+  , highlighter: function (item) {
+      var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+      return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+        return '<strong>' + match + '</strong>'
+      })
+    }
+
+  , render: function (items) {
+      var that = this
+
+      items = $(items).map(function (i, item) {
+        i = $(that.options.item).attr('data-value', item)
+        i.find('a').html(that.highlighter(item))
+        return i[0]
+      })
+
+      items.first().addClass('active')
+      this.$menu.html(items)
+      return this
+    }
+
+  , next: function (event) {
+      var active = this.$menu.find('.active').removeClass('active')
+        , next = active.next()
+
+      if (!next.length) {
+        next = $(this.$menu.find('li')[0])
+      }
+
+      next.addClass('active')
+    }
+
+  , prev: function (event) {
+      var active = this.$menu.find('.active').removeClass('active')
+        , prev = active.prev()
+
+      if (!prev.length) {
+        prev = this.$menu.find('li').last()
+      }
+
+      prev.addClass('active')
+    }
+
+  , listen: function () {
+      this.$element
+        .on('focus',    $.proxy(this.focus, this))
+        .on('blur',     $.proxy(this.blur, this))
+        .on('keypress', $.proxy(this.keypress, this))
+        .on('keyup',    $.proxy(this.keyup, this))
+
+      if (this.eventSupported('keydown')) {
+        this.$element.on('keydown', $.proxy(this.keydown, this))
+      }
+
+      this.$menu
+        .on('click', $.proxy(this.click, this))
+        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+        .on('mouseleave', 'li', $.proxy(this.mouseleave, this))
+    }
+
+  , eventSupported: function(eventName) {
+      var isSupported = eventName in this.$element
+      if (!isSupported) {
+        this.$element.setAttribute(eventName, 'return;')
+        isSupported = typeof this.$element[eventName] === 'function'
+      }
+      return isSupported
+    }
+
+  , move: function (e) {
+      if (!this.shown) return
+
+      switch(e.keyCode) {
+        case 9: // tab
+        case 13: // enter
+        case 27: // escape
+          e.preventDefault()
+          break
+
+        case 38: // up arrow
+          e.preventDefault()
+          this.prev()
+          break
+
+        case 40: // down arrow
+          e.preventDefault()
+          this.next()
+          break
+      }
+
+      e.stopPropagation()
+    }
+
+  , keydown: function (e) {
+      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
+      this.move(e)
+    }
+
+  , keypress: function (e) {
+      if (this.suppressKeyPressRepeat) return
+      this.move(e)
+    }
+
+  , keyup: function (e) {
+      switch(e.keyCode) {
+        case 40: // down arrow
+        case 38: // up arrow
+        case 16: // shift
+        case 17: // ctrl
+        case 18: // alt
+          break
+
+        case 9: // tab
+        case 13: // enter
+          if (!this.shown) return
+          this.select()
+          break
+
+        case 27: // escape
+          if (!this.shown) return
+          this.hide()
+          break
+
+        default:
+          this.lookup()
+      }
+
+      e.stopPropagation()
+      e.preventDefault()
+  }
+
+  , focus: function (e) {
+      this.focused = true
+    }
+
+  , blur: function (e) {
+      this.focused = false
+      if (!this.mousedover && this.shown) this.hide()
+    }
+
+  , click: function (e) {
+      e.stopPropagation()
+      e.preventDefault()
+      this.select()
+      this.$element.focus()
+    }
+
+  , mouseenter: function (e) {
+      this.mousedover = true
+      this.$menu.find('.active').removeClass('active')
+      $(e.currentTarget).addClass('active')
+    }
+
+  , mouseleave: function (e) {
+      this.mousedover = false
+      if (!this.focused && this.shown) this.hide()
+    }
+
+  }
+
+
+  /* TYPEAHEAD PLUGIN DEFINITION
+   * =========================== */
+
+  var old = $.fn.typeahead
+
+  $.fn.typeahead = function (option) {
+    return this.each(function () {
+      var $this = $(this)
+        , data = $this.data('typeahead')
+        , options = typeof option == 'object' && option
+      if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+      if (typeof option == 'string') data[option]()
+    })
+  }
+
+  $.fn.typeahead.defaults = {
+    source: []
+  , items: 8
+  , menu: '<ul class="typeahead dropdown-menu"></ul>'
+  , item: '<li><a href="#"></a></li>'
+  , minLength: 1
+  }
+
+  $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD NO CONFLICT
+  * =================== */
+
+  $.fn.typeahead.noConflict = function () {
+    $.fn.typeahead = old
+    return this
+  }
+
+
+ /* TYPEAHEAD DATA-API
+  * ================== */
+
+  $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+    var $this = $(this)
+    if ($this.data('typeahead')) return
+    $this.typeahead($this.data())
+  })
+
+}(window.jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/assets/bootstrap/js/bootstrap.min.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,7 @@
+/**
+* Bootstrap.js by @fat & @mdo
+* plugins: bootstrap-transition.js, bootstrap-modal.js, bootstrap-dropdown.js, bootstrap-scrollspy.js, bootstrap-tab.js, bootstrap-tooltip.js, bootstrap-popover.js, bootstrap-affix.js, bootstrap-alert.js, bootstrap-button.js, bootstrap-collapse.js, bootstrap-carousel.js, bootstrap-typeahead.js
+* Copyright 2012 Twitter, Inc.
+* http://www.apache.org/licenses/LICENSE-2.0.txt
+*/
+!function(a){a(function(){a.support.transition=function(){var a=function(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},c;for(c in b)if(a.style[c]!==undefined)return b[c]}();return a&&{end:a}}()})}(window.jQuery),!function(a){var b=function(b,c){this.options=c,this.$element=a(b).delegate('[data-dismiss="modal"]',"click.dismiss.modal",a.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};b.prototype={constructor:b,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var b=this,c=a.Event("show");this.$element.trigger(c);if(this.isShown||c.isDefaultPrevented())return;this.isShown=!0,this.escape(),this.backdrop(function(){var c=a.support.transition&&b.$element.hasClass("fade");b.$element.parent().length||b.$element.appendTo(document.body),b.$element.show(),c&&b.$element[0].offsetWidth,b.$element.addClass("in").attr("aria-hidden",!1),b.enforceFocus(),c?b.$element.one(a.support.transition.end,function(){b.$element.focus().trigger("shown")}):b.$element.focus().trigger("shown")})},hide:function(b){b&&b.preventDefault();var c=this;b=a.Event("hide"),this.$element.trigger(b);if(!this.isShown||b.isDefaultPrevented())return;this.isShown=!1,this.escape(),a(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),a.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var b=this;a(document).on("focusin.modal",function(a){b.$element[0]!==a.target&&!b.$element.has(a.target).length&&b.$element.focus()})},escape:function(){var a=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(b){b.which==27&&a.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var b=this,c=setTimeout(function(){b.$element.off(a.support.transition.end),b.hideModal()},500);this.$element.one(a.support.transition.end,function(){clearTimeout(c),b.hideModal()})},hideModal:function(){var a=this;this.$element.hide(),this.backdrop(function(){a.removeBackdrop(),a.$element.trigger("hidden")})},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(b){var c=this,d=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var e=a.support.transition&&d;this.$backdrop=a('<div class="modal-backdrop '+d+'" />').appendTo(document.body),this.$backdrop.click(this.options.backdrop=="static"?a.proxy(this.$element[0].focus,this.$element[0]):a.proxy(this.hide,this)),e&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in");if(!b)return;e?this.$backdrop.one(a.support.transition.end,b):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b):b()):b&&b()}};var c=a.fn.modal;a.fn.modal=function(c){return this.each(function(){var d=a(this),e=d.data("modal"),f=a.extend({},a.fn.modal.defaults,d.data(),typeof c=="object"&&c);e||d.data("modal",e=new b(this,f)),typeof c=="string"?e[c]():f.show&&e.show()})},a.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());b.preventDefault(),e.modal(f).one("hide",function(){c.focus()})})}(window.jQuery),!function(a){function d(){a(b).each(function(){e(a(this)).removeClass("open")})}function e(b){var c=b.attr("data-target"),d;c||(c=b.attr("href"),c=c&&/#/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,"")),d=c&&a(c);if(!d||!d.length)d=b.parent();return d}var b="[data-toggle=dropdown]",c=function(b){var c=a(b).on("click.dropdown.data-api",this.toggle);a("html").on("click.dropdown.data-api",function(){c.parent().removeClass("open")})};c.prototype={constructor:c,toggle:function(b){var c=a(this),f,g;if(c.is(".disabled, :disabled"))return;return f=e(c),g=f.hasClass("open"),d(),g||f.toggleClass("open"),c.focus(),!1},keydown:function(c){var d,f,g,h,i,j;if(!/(38|40|27)/.test(c.keyCode))return;d=a(this),c.preventDefault(),c.stopPropagation();if(d.is(".disabled, :disabled"))return;h=e(d),i=h.hasClass("open");if(!i||i&&c.keyCode==27)return c.which==27&&h.find(b).focus(),d.click();f=a("[role=menu] li:not(.divider):visible a",h);if(!f.length)return;j=f.index(f.filter(":focus")),c.keyCode==38&&j>0&&j--,c.keyCode==40&&j<f.length-1&&j++,~j||(j=0),f.eq(j).focus()}};var f=a.fn.dropdown;a.fn.dropdown=function(b){return this.each(function(){var d=a(this),e=d.data("dropdown");e||d.data("dropdown",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.dropdown.Constructor=c,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=f,this},a(document).on("click.dropdown.data-api",d).on("click.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on(".dropdown-menu",function(a){a.stopPropagation()}).on("click.dropdown.data-api",b,c.prototype.toggle).on("keydown.dropdown.data-api",b+", [role=menu]",c.prototype.keydown)}(window.jQuery),!function(a){function b(b,c){var d=a.proxy(this.process,this),e=a(b).is("body")?a(window):a(b),f;this.options=a.extend({},a.fn.scrollspy.defaults,c),this.$scrollElement=e.on("scroll.scroll-spy.data-api",d),this.selector=(this.options.target||(f=a(b).attr("href"))&&f.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=a("body"),this.refresh(),this.process()}b.prototype={constructor:b,refresh:function(){var b=this,c;this.offsets=a([]),this.targets=a([]),c=this.$body.find(this.selector).map(function(){var c=a(this),d=c.data("target")||c.attr("href"),e=/^#\w/.test(d)&&a(d);return e&&e.length&&[[e.position().top+(!a.isWindow(b.$scrollElement.get(0))&&b.$scrollElement.scrollTop()),d]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},process:function(){var a=this.$scrollElement.scrollTop()+this.options.offset,b=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,c=b-this.$scrollElement.height(),d=this.offsets,e=this.targets,f=this.activeTarget,g;if(a>=c)return f!=(g=e.last()[0])&&this.activate(g);for(g=d.length;g--;)f!=e[g]&&a>=d[g]&&(!d[g+1]||a<=d[g+1])&&this.activate(e[g])},activate:function(b){var c,d;this.activeTarget=b,a(this.selector).parent(".active").removeClass("active"),d=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',c=a(d).parent("li").addClass("active"),c.parent(".dropdown-menu").length&&(c=c.closest("li.dropdown").addClass("active")),c.trigger("activate")}};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("scrollspy"),f=typeof c=="object"&&c;e||d.data("scrollspy",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.defaults={offset:10},a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(window.jQuery),!function(a){var b=function(b){this.element=a(b)};b.prototype={constructor:b,show:function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.attr("data-target"),e,f,g;d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,""));if(b.parent("li").hasClass("active"))return;e=c.find(".active:last a")[0],g=a.Event("show",{relatedTarget:e}),b.trigger(g);if(g.isDefaultPrevented())return;f=a(d),this.activate(b.parent("li"),c),this.activate(f,f.parent(),function(){b.trigger({type:"shown",relatedTarget:e})})},activate:function(b,c,d){function g(){e.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),f?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var e=c.find("> .active"),f=d&&a.support.transition&&e.hasClass("fade");f?e.one(a.support.transition.end,g):g(),e.removeClass("in")}};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("tab");e||d.data("tab",e=new b(this)),typeof c=="string"&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(window.jQuery),!function(a){var b=function(a,b){this.init("tooltip",a,b)};b.prototype={constructor:b,init:function(b,c,d){var e,f,g,h,i;this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.enabled=!0,g=this.options.trigger.split(" ");for(i=g.length;i--;)h=g[i],h=="click"?this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this)):h!="manual"&&(e=h=="hover"?"mouseenter":"focus",f=h=="hover"?"mouseleave":"blur",this.$element.on(e+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(f+"."+this.type,this.options.selector,a.proxy(this.leave,this)));this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(b){return b=a.extend({},a.fn[this.type].defaults,this.$element.data(),b),b.delay&&typeof b.delay=="number"&&(b.delay={show:b.delay,hide:b.delay}),b},enter:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);if(!c.options.delay||!c.options.delay.show)return c.show();clearTimeout(this.timeout),c.hoverState="in",this.timeout=setTimeout(function(){c.hoverState=="in"&&c.show()},c.options.delay.show)},leave:function(b){var c=a(b.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!c.options.delay||!c.options.delay.hide)return c.hide();c.hoverState="out",this.timeout=setTimeout(function(){c.hoverState=="out"&&c.hide()},c.options.delay.hide)},show:function(){var b,c,d,e,f,g,h=a.Event("show");if(this.hasContent()&&this.enabled){this.$element.trigger(h);if(h.isDefaultPrevented())return;b=this.tip(),this.setContent(),this.options.animation&&b.addClass("fade"),f=typeof this.options.placement=="function"?this.options.placement.call(this,b[0],this.$element[0]):this.options.placement,b.detach().css({top:0,left:0,display:"block"}),this.options.container?b.appendTo(this.options.container):b.insertAfter(this.$element),c=this.getPosition(),d=b[0].offsetWidth,e=b[0].offsetHeight;switch(f){case"bottom":g={top:c.top+c.height,left:c.left+c.width/2-d/2};break;case"top":g={top:c.top-e,left:c.left+c.width/2-d/2};break;case"left":g={top:c.top+c.height/2-e/2,left:c.left-d};break;case"right":g={top:c.top+c.height/2-e/2,left:c.left+c.width}}this.applyPlacement(g,f),this.$element.trigger("shown")}},applyPlacement:function(a,b){var c=this.tip(),d=c[0].offsetWidth,e=c[0].offsetHeight,f,g,h,i;c.offset(a).addClass(b).addClass("in"),f=c[0].offsetWidth,g=c[0].offsetHeight,b=="top"&&g!=e&&(a.top=a.top+e-g,i=!0),b=="bottom"||b=="top"?(h=0,a.left<0&&(h=a.left*-2,a.left=0,c.offset(a),f=c[0].offsetWidth,g=c[0].offsetHeight),this.replaceArrow(h-d+f,f,"left")):this.replaceArrow(g-e,g,"top"),i&&c.offset(a)},replaceArrow:function(a,b,c){this.arrow().css(c,a?50*(1-a/b)+"%":"")},setContent:function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},hide:function(){function e(){var b=setTimeout(function(){c.off(a.support.transition.end).detach()},500);c.one(a.support.transition.end,function(){clearTimeout(b),c.detach()})}var b=this,c=this.tip(),d=a.Event("hide");this.$element.trigger(d);if(d.isDefaultPrevented())return;return c.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?e():c.detach(),this.$element.trigger("hidden"),this},fixTitle:function(){var a=this.$element;(a.attr("title")||typeof a.attr("data-original-title")!="string")&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},hasContent:function(){return this.getTitle()},getPosition:function(){var b=this.$element[0];return a.extend({},typeof b.getBoundingClientRect=="function"?b.getBoundingClientRect():{width:b.offsetWidth,height:b.offsetHeight},this.$element.offset())},getTitle:function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||(typeof c.title=="function"?c.title.call(b[0]):c.title),a},tip:function(){return this.$tip=this.$tip||a(this.options.template)},arrow:function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(b){var c=b?a(b.currentTarget)[this.type](this._options).data(this.type):this;c.tip().hasClass("in")?c.hide():c.show()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}};var c=a.fn.tooltip;a.fn.tooltip=function(c){return this.each(function(){var d=a(this),e=d.data("tooltip"),f=typeof c=="object"&&c;e||d.data("tooltip",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.tooltip.Constructor=b,a.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},a.fn.tooltip.noConflict=function(){return a.fn.tooltip=c,this}}(window.jQuery),!function(a){var b=function(a,b){this.init("popover",a,b)};b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype,{constructor:b,setContent:function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"html":"text"](c),a.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var a,b=this.$element,c=this.options;return a=(typeof c.content=="function"?c.content.call(b[0]):c.content)||b.attr("data-content"),a},tip:function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}});var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("popover"),f=typeof c=="object"&&c;e||d.data("popover",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.popover.Constructor=b,a.fn.popover.defaults=a.extend({},a.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(window.jQuery),!function(a){var b=function(b,c){this.options=a.extend({},a.fn.affix.defaults,c),this.$window=a(window).on("scroll.affix.data-api",a.proxy(this.checkPosition,this)).on("click.affix.data-api",a.proxy(function(){setTimeout(a.proxy(this.checkPosition,this),1)},this)),this.$element=a(b),this.checkPosition()};b.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var b=a(document).height(),c=this.$window.scrollTop(),d=this.$element.offset(),e=this.options.offset,f=e.bottom,g=e.top,h="affix affix-top affix-bottom",i;typeof e!="object"&&(f=g=e),typeof g=="function"&&(g=e.top()),typeof f=="function"&&(f=e.bottom()),i=this.unpin!=null&&c+this.unpin<=d.top?!1:f!=null&&d.top+this.$element.height()>=b-f?"bottom":g!=null&&c<=g?"top":!1;if(this.affixed===i)return;this.affixed=i,this.unpin=i=="bottom"?d.top-c:null,this.$element.removeClass(h).addClass("affix"+(i?"-"+i:""))};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("affix"),f=typeof c=="object"&&c;e||d.data("affix",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.defaults={offset:0},a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(window.jQuery),!function(a){var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function f(){e.trigger("closed").remove()}var c=a(this),d=c.attr("data-target"),e;d||(d=c.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),e=a(d),b&&b.preventDefault(),e.length||(e=c.hasClass("alert")?c:c.parent()),e.trigger(b=a.Event("close"));if(b.isDefaultPrevented())return;e.removeClass("in"),a.support.transition&&e.hasClass("fade")?e.on(a.support.transition.end,f):f()};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("alert");e||d.data("alert",e=new c(this)),typeof b=="string"&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.alert.data-api",b,c.prototype.close)}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.button.defaults,c)};b.prototype.setState=function(a){var b="disabled",c=this.$element,d=c.data(),e=c.is("input")?"val":"html";a+="Text",d.resetText||c.data("resetText",c[e]()),c[e](d[a]||this.options[a]),setTimeout(function(){a=="loadingText"?c.addClass(b).attr(b,b):c.removeClass(b).removeAttr(b)},0)},b.prototype.toggle=function(){var a=this.$element.closest('[data-toggle="buttons-radio"]');a&&a.find(".active").removeClass("active"),this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("button"),f=typeof c=="object"&&c;e||d.data("button",e=new b(this,f)),c=="toggle"?e.toggle():c&&e.setState(c)})},a.fn.button.defaults={loadingText:"loading..."},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle")})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.collapse.defaults,c),this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.prototype={constructor:b,dimension:function(){var a=this.$element.hasClass("width");return a?"width":"height"},show:function(){var b,c,d,e;if(this.transitioning||this.$element.hasClass("in"))return;b=this.dimension(),c=a.camelCase(["scroll",b].join("-")),d=this.$parent&&this.$parent.find("> .accordion-group > .in");if(d&&d.length){e=d.data("collapse");if(e&&e.transitioning)return;d.collapse("hide"),e||d.data("collapse",null)}this.$element[b](0),this.transition("addClass",a.Event("show"),"shown"),a.support.transition&&this.$element[b](this.$element[0][c])},hide:function(){var b;if(this.transitioning||!this.$element.hasClass("in"))return;b=this.dimension(),this.reset(this.$element[b]()),this.transition("removeClass",a.Event("hide"),"hidden"),this.$element[b](0)},reset:function(a){var b=this.dimension();return this.$element.removeClass("collapse")[b](a||"auto")[0].offsetWidth,this.$element[a!==null?"addClass":"removeClass"]("collapse"),this},transition:function(b,c,d){var e=this,f=function(){c.type=="show"&&e.reset(),e.transitioning=0,e.$element.trigger(d)};this.$element.trigger(c);if(c.isDefaultPrevented())return;this.transitioning=1,this.$element[b]("in"),a.support.transition&&this.$element.hasClass("collapse")?this.$element.one(a.support.transition.end,f):f()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("collapse"),f=a.extend({},a.fn.collapse.defaults,d.data(),typeof c=="object"&&c);e||d.data("collapse",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.collapse.defaults={toggle:!0},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.collapse.data-api","[data-toggle=collapse]",function(b){var c=a(this),d,e=c.attr("data-target")||b.preventDefault()||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),f=a(e).data("collapse")?"toggle":c.data();c[a(e).hasClass("in")?"addClass":"removeClass"]("collapsed"),a(e).collapse(f)})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.options.pause=="hover"&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.prototype={cycle:function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(b){var c=this.getActiveIndex(),d=this;if(b>this.$items.length-1||b<0)return;return this.sliding?this.$element.one("slid",function(){d.to(b)}):c==b?this.pause().cycle():this.slide(b>c?"next":"prev",a(this.$items[b]))},pause:function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition.end&&(this.$element.trigger(a.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g=b=="next"?"left":"right",h=b=="next"?"first":"last",i=this,j;this.sliding=!0,f&&this.pause(),e=e.length?e:this.$element.find(".item")[h](),j=a.Event("slide",{relatedTarget:e[0],direction:g});if(e.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")}));if(a.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(j);if(j.isDefaultPrevented())return;e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),this.$element.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid")},0)})}else{this.$element.trigger(j);if(j.isDefaultPrevented())return;d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return f&&this.cycle(),this}};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("carousel"),f=a.extend({},a.fn.carousel.defaults,typeof c=="object"&&c),g=typeof c=="string"?c:f.slide;e||d.data("carousel",e=new b(this,f)),typeof c=="number"?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.defaults={interval:5e3,pause:"hover"},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c=a(this),d,e=a(c.attr("data-target")||(d=c.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),c.data()),g;e.carousel(f),(g=c.attr("data-slide-to"))&&e.data("carousel").pause().to(g).cycle(),b.preventDefault()})}(window.jQuery),!function(a){var b=function(b,c){this.$element=a(b),this.options=a.extend({},a.fn.typeahead.defaults,c),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.source=this.options.source,this.$menu=a(this.options.menu),this.shown=!1,this.listen()};b.prototype={constructor:b,select:function(){var a=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(a)).change(),this.hide()},updater:function(a){return a},show:function(){var b=a.extend({},this.$element.position(),{height:this.$element[0].offsetHeight});return this.$menu.insertAfter(this.$element).css({top:b.top+b.height,left:b.left}).show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(b){var c;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(c=a.isFunction(this.source)?this.source(this.query,a.proxy(this.process,this)):this.source,c?this.process(c):this)},process:function(b){var c=this;return b=a.grep(b,function(a){return c.matcher(a)}),b=this.sorter(b),b.length?this.render(b.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(a){return~a.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(a){var b=[],c=[],d=[],e;while(e=a.shift())e.toLowerCase().indexOf(this.query.toLowerCase())?~e.indexOf(this.query)?c.push(e):d.push(e):b.push(e);return b.concat(c,d)},highlighter:function(a){var b=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return a.replace(new RegExp("("+b+")","ig"),function(a,b){return"<strong>"+b+"</strong>"})},render:function(b){var c=this;return b=a(b).map(function(b,d){return b=a(c.options.item).attr("data-value",d),b.find("a").html(c.highlighter(d)),b[0]}),b.first().addClass("active"),this.$menu.html(b),this},next:function(b){var c=this.$menu.find(".active").removeClass("active"),d=c.next();d.length||(d=a(this.$menu.find("li")[0])),d.addClass("active")},prev:function(a){var b=this.$menu.find(".active").removeClass("active"),c=b.prev();c.length||(c=this.$menu.find("li").last()),c.addClass("active")},listen:function(){this.$element.on("focus",a.proxy(this.focus,this)).on("blur",a.proxy(this.blur,this)).on("keypress",a.proxy(this.keypress,this)).on("keyup",a.proxy(this.keyup,this)),this.eventSupported("keydown")&&this.$element.on("keydown",a.proxy(this.keydown,this)),this.$menu.on("click",a.proxy(this.click,this)).on("mouseenter","li",a.proxy(this.mouseenter,this)).on("mouseleave","li",a.proxy(this.mouseleave,this))},eventSupported:function(a){var b=a in this.$element;return b||(this.$element.setAttribute(a,"return;"),b=typeof this.$element[a]=="function"),b},move:function(a){if(!this.shown)return;switch(a.keyCode){case 9:case 13:case 27:a.preventDefault();break;case 38:a.preventDefault(),this.prev();break;case 40:a.preventDefault(),this.next()}a.stopPropagation()},keydown:function(b){this.suppressKeyPressRepeat=~a.inArray(b.keyCode,[40,38,9,13,27]),this.move(b)},keypress:function(a){if(this.suppressKeyPressRepeat)return;this.move(a)},keyup:function(a){switch(a.keyCode){case 40:case 38:case 16:case 17:case 18:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}a.stopPropagation(),a.preventDefault()},focus:function(a){this.focused=!0},blur:function(a){this.focused=!1,!this.mousedover&&this.shown&&this.hide()},click:function(a){a.stopPropagation(),a.preventDefault(),this.select(),this.$element.focus()},mouseenter:function(b){this.mousedover=!0,this.$menu.find(".active").removeClass("active"),a(b.currentTarget).addClass("active")},mouseleave:function(a){this.mousedover=!1,!this.focused&&this.shown&&this.hide()}};var c=a.fn.typeahead;a.fn.typeahead=function(c){return this.each(function(){var d=a(this),e=d.data("typeahead"),f=typeof c=="object"&&c;e||d.data("typeahead",e=new b(this,f)),typeof c=="string"&&e[c]()})},a.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},a.fn.typeahead.Constructor=b,a.fn.typeahead.noConflict=function(){return a.fn.typeahead=c,this},a(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(b){var c=a(this);if(c.data("typeahead"))return;c.typeahead(c.data())})}(window.jQuery)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/assets/font-awesome/.gitignore	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,29 @@
+*.pyc
+*.egg-info
+*.db
+*.db.old
+*.swp
+*.db-journal
+
+.coverage
+.DS_Store
+.installed.cfg
+
+.idea/*
+.svn/*
+src/website/static/*
+src/website/media/*
+
+bin
+build
+cfcache
+develop-eggs
+dist
+downloads
+eggs
+parts
+tmp
+.sass-cache
+
+src/website/settingslocal.py
+stunnel.log
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/assets/font-awesome/css/font-awesome-ie7.min.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,22 @@
+/*!
+ *  Font Awesome 3.0.2
+ *  the iconic font designed for use with Twitter Bootstrap
+ *  -------------------------------------------------------
+ *  The full suite of pictographic icons, examples, and documentation
+ *  can be found at: http://fortawesome.github.com/Font-Awesome/
+ *
+ *  License
+ *  -------------------------------------------------------
+ *  - The Font Awesome font is licensed under the SIL Open Font License - http://scripts.sil.org/OFL
+ *  - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License -
+ *    http://opensource.org/licenses/mit-license.html
+ *  - The Font Awesome pictograms are licensed under the CC BY 3.0 License - http://creativecommons.org/licenses/by/3.0/
+ *  - Attribution is no longer required in Font Awesome 3.0, but much appreciated:
+ *    "Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome"
+
+ *  Contact
+ *  -------------------------------------------------------
+ *  Email: dave@davegandy.com
+ *  Twitter: http://twitter.com/fortaweso_me
+ *  Work: Lead Product Designer @ http://kyruus.com
+ */.icon-large{font-size:1.3333333333333333em;margin-top:-4px;padding-top:3px;margin-bottom:-4px;padding-bottom:3px;vertical-align:middle}.nav [class^="icon-"],.nav [class*=" icon-"]{vertical-align:inherit;margin-top:-4px;padding-top:3px;margin-bottom:-4px;padding-bottom:3px}.nav [class^="icon-"].icon-large,.nav [class*=" icon-"].icon-large{vertical-align:-25%}.nav-pills [class^="icon-"].icon-large,.nav-tabs [class^="icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large{line-height:.75em;margin-top:-7px;padding-top:5px;margin-bottom:-5px;padding-bottom:4px}.btn [class^="icon-"].pull-left,.btn [class*=" icon-"].pull-left,.btn [class^="icon-"].pull-right,.btn [class*=" icon-"].pull-right{vertical-align:inherit}.btn [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large{margin-top:-0.5em}a [class^="icon-"],a [class*=" icon-"]{cursor:pointer}ul.icons{text-indent:-1.5em;margin-left:3em}.icon-glass{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf000;')}.icon-music{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf001;')}.icon-search{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf002;')}.icon-envelope{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf003;')}.icon-heart{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf004;')}.icon-star{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf005;')}.icon-star-empty{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf006;')}.icon-user{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf007;')}.icon-film{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf008;')}.icon-th-large{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf009;')}.icon-th{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf00a;')}.icon-th-list{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf00b;')}.icon-ok{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf00c;')}.icon-remove{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf00d;')}.icon-zoom-in{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf00e;')}.icon-zoom-out{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf010;')}.icon-off{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf011;')}.icon-signal{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf012;')}.icon-cog{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf013;')}.icon-trash{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf014;')}.icon-home{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf015;')}.icon-file{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf016;')}.icon-time{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf017;')}.icon-road{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf018;')}.icon-download-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf019;')}.icon-download{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf01a;')}.icon-upload{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf01b;')}.icon-inbox{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf01c;')}.icon-play-circle{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf01d;')}.icon-repeat{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf01e;')}.icon-refresh{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf021;')}.icon-list-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf022;')}.icon-lock{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf023;')}.icon-flag{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf024;')}.icon-headphones{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf025;')}.icon-volume-off{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf026;')}.icon-volume-down{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf027;')}.icon-volume-up{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf028;')}.icon-qrcode{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf029;')}.icon-barcode{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf02a;')}.icon-tag{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf02b;')}.icon-tags{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf02c;')}.icon-book{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf02d;')}.icon-bookmark{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf02e;')}.icon-print{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf02f;')}.icon-camera{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf030;')}.icon-font{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf031;')}.icon-bold{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf032;')}.icon-italic{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf033;')}.icon-text-height{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf034;')}.icon-text-width{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf035;')}.icon-align-left{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf036;')}.icon-align-center{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf037;')}.icon-align-right{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf038;')}.icon-align-justify{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf039;')}.icon-list{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf03a;')}.icon-indent-left{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf03b;')}.icon-indent-right{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf03c;')}.icon-facetime-video{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf03d;')}.icon-picture{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf03e;')}.icon-pencil{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf040;')}.icon-map-marker{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf041;')}.icon-adjust{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf042;')}.icon-tint{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf043;')}.icon-edit{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf044;')}.icon-share{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf045;')}.icon-check{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf046;')}.icon-move{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf047;')}.icon-step-backward{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf048;')}.icon-fast-backward{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf049;')}.icon-backward{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf04a;')}.icon-play{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf04b;')}.icon-pause{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf04c;')}.icon-stop{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf04d;')}.icon-forward{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf04e;')}.icon-fast-forward{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf050;')}.icon-step-forward{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf051;')}.icon-eject{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf052;')}.icon-chevron-left{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf053;')}.icon-chevron-right{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf054;')}.icon-plus-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf055;')}.icon-minus-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf056;')}.icon-remove-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf057;')}.icon-ok-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf058;')}.icon-question-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf059;')}.icon-info-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf05a;')}.icon-screenshot{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf05b;')}.icon-remove-circle{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf05c;')}.icon-ok-circle{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf05d;')}.icon-ban-circle{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf05e;')}.icon-arrow-left{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf060;')}.icon-arrow-right{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf061;')}.icon-arrow-up{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf062;')}.icon-arrow-down{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf063;')}.icon-share-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf064;')}.icon-resize-full{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf065;')}.icon-resize-small{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf066;')}.icon-plus{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf067;')}.icon-minus{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf068;')}.icon-asterisk{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf069;')}.icon-exclamation-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf06a;')}.icon-gift{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf06b;')}.icon-leaf{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf06c;')}.icon-fire{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf06d;')}.icon-eye-open{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf06e;')}.icon-eye-close{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf070;')}.icon-warning-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf071;')}.icon-plane{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf072;')}.icon-calendar{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf073;')}.icon-random{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf074;')}.icon-comment{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf075;')}.icon-magnet{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf076;')}.icon-chevron-up{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf077;')}.icon-chevron-down{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf078;')}.icon-retweet{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf079;')}.icon-shopping-cart{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf07a;')}.icon-folder-close{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf07b;')}.icon-folder-open{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf07c;')}.icon-resize-vertical{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf07d;')}.icon-resize-horizontal{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf07e;')}.icon-bar-chart{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf080;')}.icon-twitter-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf081;')}.icon-facebook-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf082;')}.icon-camera-retro{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf083;')}.icon-key{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf084;')}.icon-cogs{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf085;')}.icon-comments{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf086;')}.icon-thumbs-up{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf087;')}.icon-thumbs-down{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf088;')}.icon-star-half{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf089;')}.icon-heart-empty{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf08a;')}.icon-signout{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf08b;')}.icon-linkedin-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf08c;')}.icon-pushpin{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf08d;')}.icon-external-link{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf08e;')}.icon-signin{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf090;')}.icon-trophy{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf091;')}.icon-github-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf092;')}.icon-upload-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf093;')}.icon-lemon{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf094;')}.icon-phone{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf095;')}.icon-check-empty{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf096;')}.icon-bookmark-empty{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf097;')}.icon-phone-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf098;')}.icon-twitter{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf099;')}.icon-facebook{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf09a;')}.icon-github{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf09b;')}.icon-unlock{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf09c;')}.icon-credit-card{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf09d;')}.icon-rss{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf09e;')}.icon-hdd{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0a0;')}.icon-bullhorn{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0a1;')}.icon-bell{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0a2;')}.icon-certificate{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0a3;')}.icon-hand-right{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0a4;')}.icon-hand-left{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0a5;')}.icon-hand-up{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0a6;')}.icon-hand-down{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0a7;')}.icon-circle-arrow-left{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0a8;')}.icon-circle-arrow-right{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0a9;')}.icon-circle-arrow-up{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0aa;')}.icon-circle-arrow-down{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0ab;')}.icon-globe{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0ac;')}.icon-wrench{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0ad;')}.icon-tasks{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0ae;')}.icon-filter{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0b0;')}.icon-briefcase{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0b1;')}.icon-fullscreen{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0b2;')}.icon-group{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0c0;')}.icon-link{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0c1;')}.icon-cloud{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0c2;')}.icon-beaker{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0c3;')}.icon-cut{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0c4;')}.icon-copy{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0c5;')}.icon-paper-clip{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0c6;')}.icon-save{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0c7;')}.icon-sign-blank{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0c8;')}.icon-reorder{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0c9;')}.icon-list-ul{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0ca;')}.icon-list-ol{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0cb;')}.icon-strikethrough{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0cc;')}.icon-underline{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0cd;')}.icon-table{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0ce;')}.icon-magic{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0d0;')}.icon-truck{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0d1;')}.icon-pinterest{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0d2;')}.icon-pinterest-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0d3;')}.icon-google-plus-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0d4;')}.icon-google-plus{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0d5;')}.icon-money{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0d6;')}.icon-caret-down{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0d7;')}.icon-caret-up{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0d8;')}.icon-caret-left{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0d9;')}.icon-caret-right{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0da;')}.icon-columns{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0db;')}.icon-sort{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0dc;')}.icon-sort-down{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0dd;')}.icon-sort-up{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0de;')}.icon-envelope-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0e0;')}.icon-linkedin{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0e1;')}.icon-undo{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0e2;')}.icon-legal{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0e3;')}.icon-dashboard{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0e4;')}.icon-comment-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0e5;')}.icon-comments-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0e6;')}.icon-bolt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0e7;')}.icon-sitemap{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0e8;')}.icon-umbrella{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0e9;')}.icon-paste{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0ea;')}.icon-lightbulb{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0eb;')}.icon-exchange{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0ec;')}.icon-cloud-download{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0ed;')}.icon-cloud-upload{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0ee;')}.icon-user-md{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0f0;')}.icon-stethoscope{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0f1;')}.icon-suitcase{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0f2;')}.icon-bell-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0f3;')}.icon-coffee{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0f4;')}.icon-food{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0f5;')}.icon-file-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0f6;')}.icon-building{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0f7;')}.icon-hospital{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0f8;')}.icon-ambulance{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0f9;')}.icon-medkit{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0fa;')}.icon-fighter-jet{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0fb;')}.icon-beer{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0fc;')}.icon-h-sign{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0fd;')}.icon-plus-sign-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf0fe;')}.icon-double-angle-left{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf100;')}.icon-double-angle-right{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf101;')}.icon-double-angle-up{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf102;')}.icon-double-angle-down{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf103;')}.icon-angle-left{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf104;')}.icon-angle-right{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf105;')}.icon-angle-up{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf106;')}.icon-angle-down{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf107;')}.icon-desktop{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf108;')}.icon-laptop{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf109;')}.icon-tablet{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf10a;')}.icon-mobile-phone{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf10b;')}.icon-circle-blank{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf10c;')}.icon-quote-left{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf10d;')}.icon-quote-right{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf10e;')}.icon-spinner{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf110;')}.icon-circle{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf111;')}.icon-reply{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf112;')}.icon-github-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf113;')}.icon-folder-close-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf114;')}.icon-folder-open-alt{*zoom:expression(this.runtimeStyle['zoom'] = '1',this.innerHTML = '&#xf115;')}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/assets/font-awesome/css/font-awesome.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,540 @@
+/*!
+ *  Font Awesome 3.0.2
+ *  the iconic font designed for use with Twitter Bootstrap
+ *  -------------------------------------------------------
+ *  The full suite of pictographic icons, examples, and documentation
+ *  can be found at: http://fortawesome.github.com/Font-Awesome/
+ *
+ *  License
+ *  -------------------------------------------------------
+ *  - The Font Awesome font is licensed under the SIL Open Font License - http://scripts.sil.org/OFL
+ *  - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License -
+ *    http://opensource.org/licenses/mit-license.html
+ *  - The Font Awesome pictograms are licensed under the CC BY 3.0 License - http://creativecommons.org/licenses/by/3.0/
+ *  - Attribution is no longer required in Font Awesome 3.0, but much appreciated:
+ *    "Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome"
+
+ *  Contact
+ *  -------------------------------------------------------
+ *  Email: dave@davegandy.com
+ *  Twitter: http://twitter.com/fortaweso_me
+ *  Work: Lead Product Designer @ http://kyruus.com
+ */
+@font-face {
+  font-family: 'FontAwesome';
+  src: url('../font/fontawesome-webfont.eot?v=3.0.1');
+  src: url('../font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'),
+    url('../font/fontawesome-webfont.woff?v=3.0.1') format('woff'),
+    url('../font/fontawesome-webfont.ttf?v=3.0.1') format('truetype');
+  font-weight: normal;
+  font-style: normal;
+}
+/*  Font Awesome styles
+    ------------------------------------------------------- */
+[class^="icon-"],
+[class*=" icon-"] {
+  font-family: FontAwesome;
+  font-weight: normal;
+  font-style: normal;
+  text-decoration: inherit;
+  -webkit-font-smoothing: antialiased;
+
+  /* sprites.less reset */
+  display: inline;
+  width: auto;
+  height: auto;
+  line-height: normal;
+  vertical-align: baseline;
+  background-image: none;
+  background-position: 0% 0%;
+  background-repeat: repeat;
+  margin-top: 0;
+}
+/* more sprites.less reset */
+.icon-white,
+.nav-pills > .active > a > [class^="icon-"],
+.nav-pills > .active > a > [class*=" icon-"],
+.nav-list > .active > a > [class^="icon-"],
+.nav-list > .active > a > [class*=" icon-"],
+.navbar-inverse .nav > .active > a > [class^="icon-"],
+.navbar-inverse .nav > .active > a > [class*=" icon-"],
+.dropdown-menu > li > a:hover > [class^="icon-"],
+.dropdown-menu > li > a:hover > [class*=" icon-"],
+.dropdown-menu > .active > a > [class^="icon-"],
+.dropdown-menu > .active > a > [class*=" icon-"],
+.dropdown-submenu:hover > a > [class^="icon-"],
+.dropdown-submenu:hover > a > [class*=" icon-"] {
+  background-image: none;
+}
+[class^="icon-"]:before,
+[class*=" icon-"]:before {
+  text-decoration: inherit;
+  display: inline-block;
+  speak: none;
+}
+/* makes sure icons active on rollover in links */
+a [class^="icon-"],
+a [class*=" icon-"] {
+  display: inline-block;
+}
+/* makes the font 33% larger relative to the icon container */
+.icon-large:before {
+  vertical-align: -10%;
+  font-size: 1.3333333333333333em;
+}
+.btn [class^="icon-"],
+.nav [class^="icon-"],
+.btn [class*=" icon-"],
+.nav [class*=" icon-"] {
+  display: inline;
+  /* keeps button heights with and without icons the same */
+
+}
+.btn [class^="icon-"].icon-large,
+.nav [class^="icon-"].icon-large,
+.btn [class*=" icon-"].icon-large,
+.nav [class*=" icon-"].icon-large {
+  line-height: .9em;
+}
+.btn [class^="icon-"].icon-spin,
+.nav [class^="icon-"].icon-spin,
+.btn [class*=" icon-"].icon-spin,
+.nav [class*=" icon-"].icon-spin {
+  display: inline-block;
+}
+.nav-tabs [class^="icon-"],
+.nav-pills [class^="icon-"],
+.nav-tabs [class*=" icon-"],
+.nav-pills [class*=" icon-"] {
+  /* keeps button heights with and without icons the same */
+
+}
+.nav-tabs [class^="icon-"],
+.nav-pills [class^="icon-"],
+.nav-tabs [class*=" icon-"],
+.nav-pills [class*=" icon-"],
+.nav-tabs [class^="icon-"].icon-large,
+.nav-pills [class^="icon-"].icon-large,
+.nav-tabs [class*=" icon-"].icon-large,
+.nav-pills [class*=" icon-"].icon-large {
+  line-height: .9em;
+}
+li [class^="icon-"],
+.nav li [class^="icon-"],
+li [class*=" icon-"],
+.nav li [class*=" icon-"] {
+  display: inline-block;
+  width: 1.25em;
+  text-align: center;
+}
+li [class^="icon-"].icon-large,
+.nav li [class^="icon-"].icon-large,
+li [class*=" icon-"].icon-large,
+.nav li [class*=" icon-"].icon-large {
+  /* increased font size for icon-large */
+
+  width: 1.5625em;
+}
+ul.icons {
+  list-style-type: none;
+  text-indent: -0.75em;
+}
+ul.icons li [class^="icon-"],
+ul.icons li [class*=" icon-"] {
+  width: .75em;
+}
+.icon-muted {
+  color: #eeeeee;
+}
+.icon-border {
+  border: solid 1px #eeeeee;
+  padding: .2em .25em .15em;
+  -webkit-border-radius: 3px;
+  -moz-border-radius: 3px;
+  border-radius: 3px;
+}
+.icon-2x {
+  font-size: 2em;
+}
+.icon-2x.icon-border {
+  border-width: 2px;
+  -webkit-border-radius: 4px;
+  -moz-border-radius: 4px;
+  border-radius: 4px;
+}
+.icon-3x {
+  font-size: 3em;
+}
+.icon-3x.icon-border {
+  border-width: 3px;
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+}
+.icon-4x {
+  font-size: 4em;
+}
+.icon-4x.icon-border {
+  border-width: 4px;
+  -webkit-border-radius: 6px;
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+}
+.pull-right {
+  float: right;
+}
+.pull-left {
+  float: left;
+}
+[class^="icon-"].pull-left,
+[class*=" icon-"].pull-left {
+  margin-right: .3em;
+}
+[class^="icon-"].pull-right,
+[class*=" icon-"].pull-right {
+  margin-left: .3em;
+}
+.btn [class^="icon-"].pull-left.icon-2x,
+.btn [class*=" icon-"].pull-left.icon-2x,
+.btn [class^="icon-"].pull-right.icon-2x,
+.btn [class*=" icon-"].pull-right.icon-2x {
+  margin-top: .18em;
+}
+.btn [class^="icon-"].icon-spin.icon-large,
+.btn [class*=" icon-"].icon-spin.icon-large {
+  line-height: .8em;
+}
+.btn.btn-small [class^="icon-"].pull-left.icon-2x,
+.btn.btn-small [class*=" icon-"].pull-left.icon-2x,
+.btn.btn-small [class^="icon-"].pull-right.icon-2x,
+.btn.btn-small [class*=" icon-"].pull-right.icon-2x {
+  margin-top: .25em;
+}
+.btn.btn-large [class^="icon-"],
+.btn.btn-large [class*=" icon-"] {
+  margin-top: 0;
+}
+.btn.btn-large [class^="icon-"].pull-left.icon-2x,
+.btn.btn-large [class*=" icon-"].pull-left.icon-2x,
+.btn.btn-large [class^="icon-"].pull-right.icon-2x,
+.btn.btn-large [class*=" icon-"].pull-right.icon-2x {
+  margin-top: .05em;
+}
+.btn.btn-large [class^="icon-"].pull-left.icon-2x,
+.btn.btn-large [class*=" icon-"].pull-left.icon-2x {
+  margin-right: .2em;
+}
+.btn.btn-large [class^="icon-"].pull-right.icon-2x,
+.btn.btn-large [class*=" icon-"].pull-right.icon-2x {
+  margin-left: .2em;
+}
+.icon-spin {
+  display: inline-block;
+  -moz-animation: spin 2s infinite linear;
+  -o-animation: spin 2s infinite linear;
+  -webkit-animation: spin 2s infinite linear;
+  animation: spin 2s infinite linear;
+}
+@-moz-keyframes spin {
+  0% { -moz-transform: rotate(0deg); }
+  100% { -moz-transform: rotate(359deg); }
+}
+@-webkit-keyframes spin {
+  0% { -webkit-transform: rotate(0deg); }
+  100% { -webkit-transform: rotate(359deg); }
+}
+@-o-keyframes spin {
+  0% { -o-transform: rotate(0deg); }
+  100% { -o-transform: rotate(359deg); }
+}
+@-ms-keyframes spin {
+  0% { -ms-transform: rotate(0deg); }
+  100% { -ms-transform: rotate(359deg); }
+}
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(359deg); }
+}
+@-moz-document url-prefix() {
+  .icon-spin {
+    height: .9em;
+  }
+  .btn .icon-spin {
+    height: auto;
+  }
+  .icon-spin.icon-large {
+    height: 1.25em;
+  }
+  .btn .icon-spin.icon-large {
+    height: .75em;
+  }
+}
+/*  Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
+    readers do not read off random characters that represent icons */
+.icon-glass:before                { content: "\f000"; }
+.icon-music:before                { content: "\f001"; }
+.icon-search:before               { content: "\f002"; }
+.icon-envelope:before             { content: "\f003"; }
+.icon-heart:before                { content: "\f004"; }
+.icon-star:before                 { content: "\f005"; }
+.icon-star-empty:before           { content: "\f006"; }
+.icon-user:before                 { content: "\f007"; }
+.icon-film:before                 { content: "\f008"; }
+.icon-th-large:before             { content: "\f009"; }
+.icon-th:before                   { content: "\f00a"; }
+.icon-th-list:before              { content: "\f00b"; }
+.icon-ok:before                   { content: "\f00c"; }
+.icon-remove:before               { content: "\f00d"; }
+.icon-zoom-in:before              { content: "\f00e"; }
+
+.icon-zoom-out:before             { content: "\f010"; }
+.icon-off:before                  { content: "\f011"; }
+.icon-signal:before               { content: "\f012"; }
+.icon-cog:before                  { content: "\f013"; }
+.icon-trash:before                { content: "\f014"; }
+.icon-home:before                 { content: "\f015"; }
+.icon-file:before                 { content: "\f016"; }
+.icon-time:before                 { content: "\f017"; }
+.icon-road:before                 { content: "\f018"; }
+.icon-download-alt:before         { content: "\f019"; }
+.icon-download:before             { content: "\f01a"; }
+.icon-upload:before               { content: "\f01b"; }
+.icon-inbox:before                { content: "\f01c"; }
+.icon-play-circle:before          { content: "\f01d"; }
+.icon-repeat:before               { content: "\f01e"; }
+
+/* \f020 doesn't work in Safari. all shifted one down */
+.icon-refresh:before              { content: "\f021"; }
+.icon-list-alt:before             { content: "\f022"; }
+.icon-lock:before                 { content: "\f023"; }
+.icon-flag:before                 { content: "\f024"; }
+.icon-headphones:before           { content: "\f025"; }
+.icon-volume-off:before           { content: "\f026"; }
+.icon-volume-down:before          { content: "\f027"; }
+.icon-volume-up:before            { content: "\f028"; }
+.icon-qrcode:before               { content: "\f029"; }
+.icon-barcode:before              { content: "\f02a"; }
+.icon-tag:before                  { content: "\f02b"; }
+.icon-tags:before                 { content: "\f02c"; }
+.icon-book:before                 { content: "\f02d"; }
+.icon-bookmark:before             { content: "\f02e"; }
+.icon-print:before                { content: "\f02f"; }
+
+.icon-camera:before               { content: "\f030"; }
+.icon-font:before                 { content: "\f031"; }
+.icon-bold:before                 { content: "\f032"; }
+.icon-italic:before               { content: "\f033"; }
+.icon-text-height:before          { content: "\f034"; }
+.icon-text-width:before           { content: "\f035"; }
+.icon-align-left:before           { content: "\f036"; }
+.icon-align-center:before         { content: "\f037"; }
+.icon-align-right:before          { content: "\f038"; }
+.icon-align-justify:before        { content: "\f039"; }
+.icon-list:before                 { content: "\f03a"; }
+.icon-indent-left:before          { content: "\f03b"; }
+.icon-indent-right:before         { content: "\f03c"; }
+.icon-facetime-video:before       { content: "\f03d"; }
+.icon-picture:before              { content: "\f03e"; }
+
+.icon-pencil:before               { content: "\f040"; }
+.icon-map-marker:before           { content: "\f041"; }
+.icon-adjust:before               { content: "\f042"; }
+.icon-tint:before                 { content: "\f043"; }
+.icon-edit:before                 { content: "\f044"; }
+.icon-share:before                { content: "\f045"; }
+.icon-check:before                { content: "\f046"; }
+.icon-move:before                 { content: "\f047"; }
+.icon-step-backward:before        { content: "\f048"; }
+.icon-fast-backward:before        { content: "\f049"; }
+.icon-backward:before             { content: "\f04a"; }
+.icon-play:before                 { content: "\f04b"; }
+.icon-pause:before                { content: "\f04c"; }
+.icon-stop:before                 { content: "\f04d"; }
+.icon-forward:before              { content: "\f04e"; }
+
+.icon-fast-forward:before         { content: "\f050"; }
+.icon-step-forward:before         { content: "\f051"; }
+.icon-eject:before                { content: "\f052"; }
+.icon-chevron-left:before         { content: "\f053"; }
+.icon-chevron-right:before        { content: "\f054"; }
+.icon-plus-sign:before            { content: "\f055"; }
+.icon-minus-sign:before           { content: "\f056"; }
+.icon-remove-sign:before          { content: "\f057"; }
+.icon-ok-sign:before              { content: "\f058"; }
+.icon-question-sign:before        { content: "\f059"; }
+.icon-info-sign:before            { content: "\f05a"; }
+.icon-screenshot:before           { content: "\f05b"; }
+.icon-remove-circle:before        { content: "\f05c"; }
+.icon-ok-circle:before            { content: "\f05d"; }
+.icon-ban-circle:before           { content: "\f05e"; }
+
+.icon-arrow-left:before           { content: "\f060"; }
+.icon-arrow-right:before          { content: "\f061"; }
+.icon-arrow-up:before             { content: "\f062"; }
+.icon-arrow-down:before           { content: "\f063"; }
+.icon-share-alt:before            { content: "\f064"; }
+.icon-resize-full:before          { content: "\f065"; }
+.icon-resize-small:before         { content: "\f066"; }
+.icon-plus:before                 { content: "\f067"; }
+.icon-minus:before                { content: "\f068"; }
+.icon-asterisk:before             { content: "\f069"; }
+.icon-exclamation-sign:before     { content: "\f06a"; }
+.icon-gift:before                 { content: "\f06b"; }
+.icon-leaf:before                 { content: "\f06c"; }
+.icon-fire:before                 { content: "\f06d"; }
+.icon-eye-open:before             { content: "\f06e"; }
+
+.icon-eye-close:before            { content: "\f070"; }
+.icon-warning-sign:before         { content: "\f071"; }
+.icon-plane:before                { content: "\f072"; }
+.icon-calendar:before             { content: "\f073"; }
+.icon-random:before               { content: "\f074"; }
+.icon-comment:before              { content: "\f075"; }
+.icon-magnet:before               { content: "\f076"; }
+.icon-chevron-up:before           { content: "\f077"; }
+.icon-chevron-down:before         { content: "\f078"; }
+.icon-retweet:before              { content: "\f079"; }
+.icon-shopping-cart:before        { content: "\f07a"; }
+.icon-folder-close:before         { content: "\f07b"; }
+.icon-folder-open:before          { content: "\f07c"; }
+.icon-resize-vertical:before      { content: "\f07d"; }
+.icon-resize-horizontal:before    { content: "\f07e"; }
+
+.icon-bar-chart:before            { content: "\f080"; }
+.icon-twitter-sign:before         { content: "\f081"; }
+.icon-facebook-sign:before        { content: "\f082"; }
+.icon-camera-retro:before         { content: "\f083"; }
+.icon-key:before                  { content: "\f084"; }
+.icon-cogs:before                 { content: "\f085"; }
+.icon-comments:before             { content: "\f086"; }
+.icon-thumbs-up:before            { content: "\f087"; }
+.icon-thumbs-down:before          { content: "\f088"; }
+.icon-star-half:before            { content: "\f089"; }
+.icon-heart-empty:before          { content: "\f08a"; }
+.icon-signout:before              { content: "\f08b"; }
+.icon-linkedin-sign:before        { content: "\f08c"; }
+.icon-pushpin:before              { content: "\f08d"; }
+.icon-external-link:before        { content: "\f08e"; }
+
+.icon-signin:before               { content: "\f090"; }
+.icon-trophy:before               { content: "\f091"; }
+.icon-github-sign:before          { content: "\f092"; }
+.icon-upload-alt:before           { content: "\f093"; }
+.icon-lemon:before                { content: "\f094"; }
+.icon-phone:before                { content: "\f095"; }
+.icon-check-empty:before          { content: "\f096"; }
+.icon-bookmark-empty:before       { content: "\f097"; }
+.icon-phone-sign:before           { content: "\f098"; }
+.icon-twitter:before              { content: "\f099"; }
+.icon-facebook:before             { content: "\f09a"; }
+.icon-github:before               { content: "\f09b"; }
+.icon-unlock:before               { content: "\f09c"; }
+.icon-credit-card:before          { content: "\f09d"; }
+.icon-rss:before                  { content: "\f09e"; }
+
+.icon-hdd:before                  { content: "\f0a0"; }
+.icon-bullhorn:before             { content: "\f0a1"; }
+.icon-bell:before                 { content: "\f0a2"; }
+.icon-certificate:before          { content: "\f0a3"; }
+.icon-hand-right:before           { content: "\f0a4"; }
+.icon-hand-left:before            { content: "\f0a5"; }
+.icon-hand-up:before              { content: "\f0a6"; }
+.icon-hand-down:before            { content: "\f0a7"; }
+.icon-circle-arrow-left:before    { content: "\f0a8"; }
+.icon-circle-arrow-right:before   { content: "\f0a9"; }
+.icon-circle-arrow-up:before      { content: "\f0aa"; }
+.icon-circle-arrow-down:before    { content: "\f0ab"; }
+.icon-globe:before                { content: "\f0ac"; }
+.icon-wrench:before               { content: "\f0ad"; }
+.icon-tasks:before                { content: "\f0ae"; }
+
+.icon-filter:before               { content: "\f0b0"; }
+.icon-briefcase:before            { content: "\f0b1"; }
+.icon-fullscreen:before           { content: "\f0b2"; }
+
+.icon-group:before                { content: "\f0c0"; }
+.icon-link:before                 { content: "\f0c1"; }
+.icon-cloud:before                { content: "\f0c2"; }
+.icon-beaker:before               { content: "\f0c3"; }
+.icon-cut:before                  { content: "\f0c4"; }
+.icon-copy:before                 { content: "\f0c5"; }
+.icon-paper-clip:before           { content: "\f0c6"; }
+.icon-save:before                 { content: "\f0c7"; }
+.icon-sign-blank:before           { content: "\f0c8"; }
+.icon-reorder:before              { content: "\f0c9"; }
+.icon-list-ul:before              { content: "\f0ca"; }
+.icon-list-ol:before              { content: "\f0cb"; }
+.icon-strikethrough:before        { content: "\f0cc"; }
+.icon-underline:before            { content: "\f0cd"; }
+.icon-table:before                { content: "\f0ce"; }
+
+.icon-magic:before                { content: "\f0d0"; }
+.icon-truck:before                { content: "\f0d1"; }
+.icon-pinterest:before            { content: "\f0d2"; }
+.icon-pinterest-sign:before       { content: "\f0d3"; }
+.icon-google-plus-sign:before     { content: "\f0d4"; }
+.icon-google-plus:before          { content: "\f0d5"; }
+.icon-money:before                { content: "\f0d6"; }
+.icon-caret-down:before           { content: "\f0d7"; }
+.icon-caret-up:before             { content: "\f0d8"; }
+.icon-caret-left:before           { content: "\f0d9"; }
+.icon-caret-right:before          { content: "\f0da"; }
+.icon-columns:before              { content: "\f0db"; }
+.icon-sort:before                 { content: "\f0dc"; }
+.icon-sort-down:before            { content: "\f0dd"; }
+.icon-sort-up:before              { content: "\f0de"; }
+
+.icon-envelope-alt:before         { content: "\f0e0"; }
+.icon-linkedin:before             { content: "\f0e1"; }
+.icon-undo:before                 { content: "\f0e2"; }
+.icon-legal:before                { content: "\f0e3"; }
+.icon-dashboard:before            { content: "\f0e4"; }
+.icon-comment-alt:before          { content: "\f0e5"; }
+.icon-comments-alt:before         { content: "\f0e6"; }
+.icon-bolt:before                 { content: "\f0e7"; }
+.icon-sitemap:before              { content: "\f0e8"; }
+.icon-umbrella:before             { content: "\f0e9"; }
+.icon-paste:before                { content: "\f0ea"; }
+.icon-lightbulb:before            { content: "\f0eb"; }
+.icon-exchange:before             { content: "\f0ec"; }
+.icon-cloud-download:before       { content: "\f0ed"; }
+.icon-cloud-upload:before         { content: "\f0ee"; }
+
+.icon-user-md:before              { content: "\f0f0"; }
+.icon-stethoscope:before          { content: "\f0f1"; }
+.icon-suitcase:before             { content: "\f0f2"; }
+.icon-bell-alt:before             { content: "\f0f3"; }
+.icon-coffee:before               { content: "\f0f4"; }
+.icon-food:before                 { content: "\f0f5"; }
+.icon-file-alt:before             { content: "\f0f6"; }
+.icon-building:before             { content: "\f0f7"; }
+.icon-hospital:before             { content: "\f0f8"; }
+.icon-ambulance:before            { content: "\f0f9"; }
+.icon-medkit:before               { content: "\f0fa"; }
+.icon-fighter-jet:before          { content: "\f0fb"; }
+.icon-beer:before                 { content: "\f0fc"; }
+.icon-h-sign:before               { content: "\f0fd"; }
+.icon-plus-sign-alt:before        { content: "\f0fe"; }
+
+.icon-double-angle-left:before    { content: "\f100"; }
+.icon-double-angle-right:before   { content: "\f101"; }
+.icon-double-angle-up:before      { content: "\f102"; }
+.icon-double-angle-down:before    { content: "\f103"; }
+.icon-angle-left:before           { content: "\f104"; }
+.icon-angle-right:before          { content: "\f105"; }
+.icon-angle-up:before             { content: "\f106"; }
+.icon-angle-down:before           { content: "\f107"; }
+.icon-desktop:before              { content: "\f108"; }
+.icon-laptop:before               { content: "\f109"; }
+.icon-tablet:before               { content: "\f10a"; }
+.icon-mobile-phone:before         { content: "\f10b"; }
+.icon-circle-blank:before         { content: "\f10c"; }
+.icon-quote-left:before           { content: "\f10d"; }
+.icon-quote-right:before          { content: "\f10e"; }
+
+.icon-spinner:before              { content: "\f110"; }
+.icon-circle:before               { content: "\f111"; }
+.icon-reply:before                { content: "\f112"; }
+.icon-github-alt:before           { content: "\f113"; }
+.icon-folder-close-alt:before     { content: "\f114"; }
+.icon-folder-open-alt:before      { content: "\f115"; }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/assets/font-awesome/css/font-awesome.min.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,33 @@
+/*!
+ *  Font Awesome 3.0.2
+ *  the iconic font designed for use with Twitter Bootstrap
+ *  -------------------------------------------------------
+ *  The full suite of pictographic icons, examples, and documentation
+ *  can be found at: http://fortawesome.github.com/Font-Awesome/
+ *
+ *  License
+ *  -------------------------------------------------------
+ *  - The Font Awesome font is licensed under the SIL Open Font License - http://scripts.sil.org/OFL
+ *  - Font Awesome CSS, LESS, and SASS files are licensed under the MIT License -
+ *    http://opensource.org/licenses/mit-license.html
+ *  - The Font Awesome pictograms are licensed under the CC BY 3.0 License - http://creativecommons.org/licenses/by/3.0/
+ *  - Attribution is no longer required in Font Awesome 3.0, but much appreciated:
+ *    "Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome"
+
+ *  Contact
+ *  -------------------------------------------------------
+ *  Email: dave@davegandy.com
+ *  Twitter: http://twitter.com/fortaweso_me
+ *  Work: Lead Product Designer @ http://kyruus.com
+ */
+
+@font-face{
+  font-family:'FontAwesome';
+  src:url('../font/fontawesome-webfont.eot?v=3.0.1');
+  src:url('../font/fontawesome-webfont.eot?#iefix&v=3.0.1') format('embedded-opentype'),
+  url('../font/fontawesome-webfont.woff?v=3.0.1') format('woff'),
+  url('../font/fontawesome-webfont.ttf?v=3.0.1') format('truetype');
+  font-weight:normal;
+  font-style:normal }
+
+[class^="icon-"],[class*=" icon-"]{font-family:FontAwesome;font-weight:normal;font-style:normal;text-decoration:inherit;-webkit-font-smoothing:antialiased;display:inline;width:auto;height:auto;line-height:normal;vertical-align:baseline;background-image:none;background-position:0 0;background-repeat:repeat;margin-top:0}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:none}[class^="icon-"]:before,[class*=" icon-"]:before{text-decoration:inherit;display:inline-block;speak:none}a [class^="icon-"],a [class*=" icon-"]{display:inline-block}.icon-large:before{vertical-align:-10%;font-size:1.3333333333333333em}.btn [class^="icon-"],.nav [class^="icon-"],.btn [class*=" icon-"],.nav [class*=" icon-"]{display:inline}.btn [class^="icon-"].icon-large,.nav [class^="icon-"].icon-large,.btn [class*=" icon-"].icon-large,.nav [class*=" icon-"].icon-large{line-height:.9em}.btn [class^="icon-"].icon-spin,.nav [class^="icon-"].icon-spin,.btn [class*=" icon-"].icon-spin,.nav [class*=" icon-"].icon-spin{display:inline-block}.nav-tabs [class^="icon-"],.nav-pills [class^="icon-"],.nav-tabs [class*=" icon-"],.nav-pills [class*=" icon-"],.nav-tabs [class^="icon-"].icon-large,.nav-pills [class^="icon-"].icon-large,.nav-tabs [class*=" icon-"].icon-large,.nav-pills [class*=" icon-"].icon-large{line-height:.9em}li [class^="icon-"],.nav li [class^="icon-"],li [class*=" icon-"],.nav li [class*=" icon-"]{display:inline-block;width:1.25em;text-align:center}li [class^="icon-"].icon-large,.nav li [class^="icon-"].icon-large,li [class*=" icon-"].icon-large,.nav li [class*=" icon-"].icon-large{width:1.5625em}ul.icons{list-style-type:none;text-indent:-0.75em}ul.icons li [class^="icon-"],ul.icons li [class*=" icon-"]{width:.75em}.icon-muted{color:#eee}.icon-border{border:solid 1px #eee;padding:.2em .25em .15em;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.icon-2x{font-size:2em}.icon-2x.icon-border{border-width:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.icon-3x{font-size:3em}.icon-3x.icon-border{border-width:3px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.icon-4x{font-size:4em}.icon-4x.icon-border{border-width:4px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.pull-right{float:right}.pull-left{float:left}[class^="icon-"].pull-left,[class*=" icon-"].pull-left{margin-right:.3em}[class^="icon-"].pull-right,[class*=" icon-"].pull-right{margin-left:.3em}.btn [class^="icon-"].pull-left.icon-2x,.btn [class*=" icon-"].pull-left.icon-2x,.btn [class^="icon-"].pull-right.icon-2x,.btn [class*=" icon-"].pull-right.icon-2x{margin-top:.18em}.btn [class^="icon-"].icon-spin.icon-large,.btn [class*=" icon-"].icon-spin.icon-large{line-height:.8em}.btn.btn-small [class^="icon-"].pull-left.icon-2x,.btn.btn-small [class*=" icon-"].pull-left.icon-2x,.btn.btn-small [class^="icon-"].pull-right.icon-2x,.btn.btn-small [class*=" icon-"].pull-right.icon-2x{margin-top:.25em}.btn.btn-large [class^="icon-"],.btn.btn-large [class*=" icon-"]{margin-top:0}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x,.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-top:.05em}.btn.btn-large [class^="icon-"].pull-left.icon-2x,.btn.btn-large [class*=" icon-"].pull-left.icon-2x{margin-right:.2em}.btn.btn-large [class^="icon-"].pull-right.icon-2x,.btn.btn-large [class*=" icon-"].pull-right.icon-2x{margin-left:.2em}.icon-spin{display:inline-block;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;-webkit-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)}100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}@-moz-document url-prefix(){.icon-spin{height:.9em}.btn .icon-spin{height:auto}.icon-spin.icon-large{height:1.25em}.btn .icon-spin.icon-large{height:.75em}}.icon-glass:before{content:"\f000"}.icon-music:before{content:"\f001"}.icon-search:before{content:"\f002"}.icon-envelope:before{content:"\f003"}.icon-heart:before{content:"\f004"}.icon-star:before{content:"\f005"}.icon-star-empty:before{content:"\f006"}.icon-user:before{content:"\f007"}.icon-film:before{content:"\f008"}.icon-th-large:before{content:"\f009"}.icon-th:before{content:"\f00a"}.icon-th-list:before{content:"\f00b"}.icon-ok:before{content:"\f00c"}.icon-remove:before{content:"\f00d"}.icon-zoom-in:before{content:"\f00e"}.icon-zoom-out:before{content:"\f010"}.icon-off:before{content:"\f011"}.icon-signal:before{content:"\f012"}.icon-cog:before{content:"\f013"}.icon-trash:before{content:"\f014"}.icon-home:before{content:"\f015"}.icon-file:before{content:"\f016"}.icon-time:before{content:"\f017"}.icon-road:before{content:"\f018"}.icon-download-alt:before{content:"\f019"}.icon-download:before{content:"\f01a"}.icon-upload:before{content:"\f01b"}.icon-inbox:before{content:"\f01c"}.icon-play-circle:before{content:"\f01d"}.icon-repeat:before{content:"\f01e"}.icon-refresh:before{content:"\f021"}.icon-list-alt:before{content:"\f022"}.icon-lock:before{content:"\f023"}.icon-flag:before{content:"\f024"}.icon-headphones:before{content:"\f025"}.icon-volume-off:before{content:"\f026"}.icon-volume-down:before{content:"\f027"}.icon-volume-up:before{content:"\f028"}.icon-qrcode:before{content:"\f029"}.icon-barcode:before{content:"\f02a"}.icon-tag:before{content:"\f02b"}.icon-tags:before{content:"\f02c"}.icon-book:before{content:"\f02d"}.icon-bookmark:before{content:"\f02e"}.icon-print:before{content:"\f02f"}.icon-camera:before{content:"\f030"}.icon-font:before{content:"\f031"}.icon-bold:before{content:"\f032"}.icon-italic:before{content:"\f033"}.icon-text-height:before{content:"\f034"}.icon-text-width:before{content:"\f035"}.icon-align-left:before{content:"\f036"}.icon-align-center:before{content:"\f037"}.icon-align-right:before{content:"\f038"}.icon-align-justify:before{content:"\f039"}.icon-list:before{content:"\f03a"}.icon-indent-left:before{content:"\f03b"}.icon-indent-right:before{content:"\f03c"}.icon-facetime-video:before{content:"\f03d"}.icon-picture:before{content:"\f03e"}.icon-pencil:before{content:"\f040"}.icon-map-marker:before{content:"\f041"}.icon-adjust:before{content:"\f042"}.icon-tint:before{content:"\f043"}.icon-edit:before{content:"\f044"}.icon-share:before{content:"\f045"}.icon-check:before{content:"\f046"}.icon-move:before{content:"\f047"}.icon-step-backward:before{content:"\f048"}.icon-fast-backward:before{content:"\f049"}.icon-backward:before{content:"\f04a"}.icon-play:before{content:"\f04b"}.icon-pause:before{content:"\f04c"}.icon-stop:before{content:"\f04d"}.icon-forward:before{content:"\f04e"}.icon-fast-forward:before{content:"\f050"}.icon-step-forward:before{content:"\f051"}.icon-eject:before{content:"\f052"}.icon-chevron-left:before{content:"\f053"}.icon-chevron-right:before{content:"\f054"}.icon-plus-sign:before{content:"\f055"}.icon-minus-sign:before{content:"\f056"}.icon-remove-sign:before{content:"\f057"}.icon-ok-sign:before{content:"\f058"}.icon-question-sign:before{content:"\f059"}.icon-info-sign:before{content:"\f05a"}.icon-screenshot:before{content:"\f05b"}.icon-remove-circle:before{content:"\f05c"}.icon-ok-circle:before{content:"\f05d"}.icon-ban-circle:before{content:"\f05e"}.icon-arrow-left:before{content:"\f060"}.icon-arrow-right:before{content:"\f061"}.icon-arrow-up:before{content:"\f062"}.icon-arrow-down:before{content:"\f063"}.icon-share-alt:before{content:"\f064"}.icon-resize-full:before{content:"\f065"}.icon-resize-small:before{content:"\f066"}.icon-plus:before{content:"\f067"}.icon-minus:before{content:"\f068"}.icon-asterisk:before{content:"\f069"}.icon-exclamation-sign:before{content:"\f06a"}.icon-gift:before{content:"\f06b"}.icon-leaf:before{content:"\f06c"}.icon-fire:before{content:"\f06d"}.icon-eye-open:before{content:"\f06e"}.icon-eye-close:before{content:"\f070"}.icon-warning-sign:before{content:"\f071"}.icon-plane:before{content:"\f072"}.icon-calendar:before{content:"\f073"}.icon-random:before{content:"\f074"}.icon-comment:before{content:"\f075"}.icon-magnet:before{content:"\f076"}.icon-chevron-up:before{content:"\f077"}.icon-chevron-down:before{content:"\f078"}.icon-retweet:before{content:"\f079"}.icon-shopping-cart:before{content:"\f07a"}.icon-folder-close:before{content:"\f07b"}.icon-folder-open:before{content:"\f07c"}.icon-resize-vertical:before{content:"\f07d"}.icon-resize-horizontal:before{content:"\f07e"}.icon-bar-chart:before{content:"\f080"}.icon-twitter-sign:before{content:"\f081"}.icon-facebook-sign:before{content:"\f082"}.icon-camera-retro:before{content:"\f083"}.icon-key:before{content:"\f084"}.icon-cogs:before{content:"\f085"}.icon-comments:before{content:"\f086"}.icon-thumbs-up:before{content:"\f087"}.icon-thumbs-down:before{content:"\f088"}.icon-star-half:before{content:"\f089"}.icon-heart-empty:before{content:"\f08a"}.icon-signout:before{content:"\f08b"}.icon-linkedin-sign:before{content:"\f08c"}.icon-pushpin:before{content:"\f08d"}.icon-external-link:before{content:"\f08e"}.icon-signin:before{content:"\f090"}.icon-trophy:before{content:"\f091"}.icon-github-sign:before{content:"\f092"}.icon-upload-alt:before{content:"\f093"}.icon-lemon:before{content:"\f094"}.icon-phone:before{content:"\f095"}.icon-check-empty:before{content:"\f096"}.icon-bookmark-empty:before{content:"\f097"}.icon-phone-sign:before{content:"\f098"}.icon-twitter:before{content:"\f099"}.icon-facebook:before{content:"\f09a"}.icon-github:before{content:"\f09b"}.icon-unlock:before{content:"\f09c"}.icon-credit-card:before{content:"\f09d"}.icon-rss:before{content:"\f09e"}.icon-hdd:before{content:"\f0a0"}.icon-bullhorn:before{content:"\f0a1"}.icon-bell:before{content:"\f0a2"}.icon-certificate:before{content:"\f0a3"}.icon-hand-right:before{content:"\f0a4"}.icon-hand-left:before{content:"\f0a5"}.icon-hand-up:before{content:"\f0a6"}.icon-hand-down:before{content:"\f0a7"}.icon-circle-arrow-left:before{content:"\f0a8"}.icon-circle-arrow-right:before{content:"\f0a9"}.icon-circle-arrow-up:before{content:"\f0aa"}.icon-circle-arrow-down:before{content:"\f0ab"}.icon-globe:before{content:"\f0ac"}.icon-wrench:before{content:"\f0ad"}.icon-tasks:before{content:"\f0ae"}.icon-filter:before{content:"\f0b0"}.icon-briefcase:before{content:"\f0b1"}.icon-fullscreen:before{content:"\f0b2"}.icon-group:before{content:"\f0c0"}.icon-link:before{content:"\f0c1"}.icon-cloud:before{content:"\f0c2"}.icon-beaker:before{content:"\f0c3"}.icon-cut:before{content:"\f0c4"}.icon-copy:before{content:"\f0c5"}.icon-paper-clip:before{content:"\f0c6"}.icon-save:before{content:"\f0c7"}.icon-sign-blank:before{content:"\f0c8"}.icon-reorder:before{content:"\f0c9"}.icon-list-ul:before{content:"\f0ca"}.icon-list-ol:before{content:"\f0cb"}.icon-strikethrough:before{content:"\f0cc"}.icon-underline:before{content:"\f0cd"}.icon-table:before{content:"\f0ce"}.icon-magic:before{content:"\f0d0"}.icon-truck:before{content:"\f0d1"}.icon-pinterest:before{content:"\f0d2"}.icon-pinterest-sign:before{content:"\f0d3"}.icon-google-plus-sign:before{content:"\f0d4"}.icon-google-plus:before{content:"\f0d5"}.icon-money:before{content:"\f0d6"}.icon-caret-down:before{content:"\f0d7"}.icon-caret-up:before{content:"\f0d8"}.icon-caret-left:before{content:"\f0d9"}.icon-caret-right:before{content:"\f0da"}.icon-columns:before{content:"\f0db"}.icon-sort:before{content:"\f0dc"}.icon-sort-down:before{content:"\f0dd"}.icon-sort-up:before{content:"\f0de"}.icon-envelope-alt:before{content:"\f0e0"}.icon-linkedin:before{content:"\f0e1"}.icon-undo:before{content:"\f0e2"}.icon-legal:before{content:"\f0e3"}.icon-dashboard:before{content:"\f0e4"}.icon-comment-alt:before{content:"\f0e5"}.icon-comments-alt:before{content:"\f0e6"}.icon-bolt:before{content:"\f0e7"}.icon-sitemap:before{content:"\f0e8"}.icon-umbrella:before{content:"\f0e9"}.icon-paste:before{content:"\f0ea"}.icon-lightbulb:before{content:"\f0eb"}.icon-exchange:before{content:"\f0ec"}.icon-cloud-download:before{content:"\f0ed"}.icon-cloud-upload:before{content:"\f0ee"}.icon-user-md:before{content:"\f0f0"}.icon-stethoscope:before{content:"\f0f1"}.icon-suitcase:before{content:"\f0f2"}.icon-bell-alt:before{content:"\f0f3"}.icon-coffee:before{content:"\f0f4"}.icon-food:before{content:"\f0f5"}.icon-file-alt:before{content:"\f0f6"}.icon-building:before{content:"\f0f7"}.icon-hospital:before{content:"\f0f8"}.icon-ambulance:before{content:"\f0f9"}.icon-medkit:before{content:"\f0fa"}.icon-fighter-jet:before{content:"\f0fb"}.icon-beer:before{content:"\f0fc"}.icon-h-sign:before{content:"\f0fd"}.icon-plus-sign-alt:before{content:"\f0fe"}.icon-double-angle-left:before{content:"\f100"}.icon-double-angle-right:before{content:"\f101"}.icon-double-angle-up:before{content:"\f102"}.icon-double-angle-down:before{content:"\f103"}.icon-angle-left:before{content:"\f104"}.icon-angle-right:before{content:"\f105"}.icon-angle-up:before{content:"\f106"}.icon-angle-down:before{content:"\f107"}.icon-desktop:before{content:"\f108"}.icon-laptop:before{content:"\f109"}.icon-tablet:before{content:"\f10a"}.icon-mobile-phone:before{content:"\f10b"}.icon-circle-blank:before{content:"\f10c"}.icon-quote-left:before{content:"\f10d"}.icon-quote-right:before{content:"\f10e"}.icon-spinner:before{content:"\f110"}.icon-circle:before{content:"\f111"}.icon-reply:before{content:"\f112"}.icon-github-alt:before{content:"\f113"}.icon-folder-close-alt:before{content:"\f114"}.icon-folder-open-alt:before{content:"\f115"}
\ No newline at end of file
Binary file src/nabble/view/web/assets/font-awesome/font/FontAwesome.otf has changed
Binary file src/nabble/view/web/assets/font-awesome/font/fontawesome-webfont.eot has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/assets/font-awesome/font/fontawesome-webfont.svg	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,284 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata></metadata>
+<defs>
+<font id="fontawesomeregular" horiz-adv-x="1536" >
+<font-face units-per-em="1792" ascent="1536" descent="-256" />
+<missing-glyph horiz-adv-x="448" />
+<glyph unicode=" "  horiz-adv-x="448" />
+<glyph unicode="&#x09;" horiz-adv-x="448" />
+<glyph unicode="&#xa0;" horiz-adv-x="448" />
+<glyph unicode="&#xa8;" horiz-adv-x="1792" />
+<glyph unicode="&#xa9;" horiz-adv-x="1792" />
+<glyph unicode="&#xae;" horiz-adv-x="1792" />
+<glyph unicode="&#xb4;" horiz-adv-x="1792" />
+<glyph unicode="&#xc6;" horiz-adv-x="1792" />
+<glyph unicode="&#x2000;" horiz-adv-x="768" />
+<glyph unicode="&#x2001;" />
+<glyph unicode="&#x2002;" horiz-adv-x="768" />
+<glyph unicode="&#x2003;" />
+<glyph unicode="&#x2004;" horiz-adv-x="512" />
+<glyph unicode="&#x2005;" horiz-adv-x="384" />
+<glyph unicode="&#x2006;" horiz-adv-x="256" />
+<glyph unicode="&#x2007;" horiz-adv-x="256" />
+<glyph unicode="&#x2008;" horiz-adv-x="192" />
+<glyph unicode="&#x2009;" horiz-adv-x="307" />
+<glyph unicode="&#x200a;" horiz-adv-x="85" />
+<glyph unicode="&#x202f;" horiz-adv-x="307" />
+<glyph unicode="&#x205f;" horiz-adv-x="384" />
+<glyph unicode="&#x2122;" horiz-adv-x="1792" />
+<glyph unicode="&#x221e;" horiz-adv-x="1792" />
+<glyph unicode="&#x2260;" horiz-adv-x="1792" />
+<glyph unicode="&#xe000;" horiz-adv-x="500" d="M0 0z" />
+<glyph unicode="&#xf000;" horiz-adv-x="1792" d="M1699 1350q0 -35 -43 -78l-632 -632v-768h320q26 0 45 -19t19 -45t-19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45t45 19h320v768l-632 632q-43 43 -43 78q0 23 18 36.5t38 17.5t43 4h1408q23 0 43 -4t38 -17.5t18 -36.5z" />
+<glyph unicode="&#xf001;" d="M1536 1312v-1120q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v537l-768 -237v-709q0 -50 -34 -89t-86 -60.5t-103.5 -32t-96.5 -10.5t-96.5 10.5t-103.5 32t-86 60.5t-34 89 t34 89t86 60.5t103.5 32t96.5 10.5q105 0 192 -39v967q0 31 19 56.5t49 35.5l832 256q12 4 28 4q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf002;" horiz-adv-x="1664" d="M1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -52 -38 -90t-90 -38q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5 t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
+<glyph unicode="&#xf003;" horiz-adv-x="1792" d="M1664 32v768q-32 -36 -69 -66q-268 -206 -426 -338q-51 -43 -83 -67t-86.5 -48.5t-102.5 -24.5h-1h-1q-48 0 -102.5 24.5t-86.5 48.5t-83 67q-158 132 -426 338q-37 30 -69 66v-768q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1664 1083v11v13.5t-0.5 13 t-3 12.5t-5.5 9t-9 7.5t-14 2.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5q0 -168 147 -284q193 -152 401 -317q6 -5 35 -29.5t46 -37.5t44.5 -31.5t50.5 -27.5t43 -9h1h1q20 0 43 9t50.5 27.5t44.5 31.5t46 37.5t35 29.5q208 165 401 317q54 43 100.5 115.5t46.5 131.5z M1792 1120v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf004;" horiz-adv-x="1792" d="M896 -128q-26 0 -44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5q224 0 351 -124t127 -344q0 -221 -229 -450l-623 -600 q-18 -18 -44 -18z" />
+<glyph unicode="&#xf005;" horiz-adv-x="1664" d="M1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -21 -10.5 -35.5t-30.5 -14.5q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455 l502 -73q56 -9 56 -46z" />
+<glyph unicode="&#xf006;" horiz-adv-x="1664" d="M1137 532l306 297l-422 62l-189 382l-189 -382l-422 -62l306 -297l-73 -421l378 199l377 -199zM1664 889q0 -22 -26 -48l-363 -354l86 -500q1 -7 1 -20q0 -50 -41 -50q-19 0 -40 12l-449 236l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500 l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41t49 -41l225 -455l502 -73q56 -9 56 -46z" />
+<glyph unicode="&#xf007;" horiz-adv-x="1408" d="M1408 131q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5t43 97.5t62 81t85.5 53.5t111.5 20q9 0 42 -21.5t74.5 -48t108 -48t133.5 -21.5t133.5 21.5t108 48t74.5 48t42 21.5q61 0 111.5 -20t85.5 -53.5t62 -81 t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf008;" horiz-adv-x="1920" d="M384 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 320v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM384 704v128q0 26 -19 45t-45 19h-128 q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 -64v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM384 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45 t45 -19h128q26 0 45 19t19 45zM1792 -64v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1408 704v512q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-512q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1792 320v128 q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 704v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1792 1088v128q0 26 -19 45t-45 19h-128q-26 0 -45 -19 t-19 -45v-128q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1920 1248v-1344q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1344q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf009;" horiz-adv-x="1664" d="M768 512v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM768 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 512v-384q0 -52 -38 -90t-90 -38 h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90zM1664 1280v-384q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v384q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf00a;" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 288v-192q0 -40 -28 -68t-68 -28h-320 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1152 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf00b;" horiz-adv-x="1792" d="M512 288v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM512 800v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 288v-192q0 -40 -28 -68t-68 -28h-960 q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68zM512 1312v-192q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h320q40 0 68 -28t28 -68zM1792 800v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28 h960q40 0 68 -28t28 -68zM1792 1312v-192q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h960q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf00c;" horiz-adv-x="1792" d="M1671 970q0 -40 -28 -68l-724 -724l-136 -136q-28 -28 -68 -28t-68 28l-136 136l-362 362q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -295l656 657q28 28 68 28t68 -28l136 -136q28 -28 28 -68z" />
+<glyph unicode="&#xf00d;" horiz-adv-x="1408" d="M1298 214q0 -40 -28 -68l-136 -136q-28 -28 -68 -28t-68 28l-294 294l-294 -294q-28 -28 -68 -28t-68 28l-136 136q-28 28 -28 68t28 68l294 294l-294 294q-28 28 -28 68t28 68l136 136q28 28 68 28t68 -28l294 -294l294 294q28 28 68 28t68 -28l136 -136q28 -28 28 -68 t-28 -68l-294 -294l294 -294q28 -28 28 -68z" />
+<glyph unicode="&#xf00e;" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-224q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v224h-224q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h224v224q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-224h224 q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5zM1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5 t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z" />
+<glyph unicode="&#xf010;" horiz-adv-x="1664" d="M1024 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-576q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h576q13 0 22.5 -9.5t9.5 -22.5zM1152 704q0 185 -131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5t316.5 131.5t131.5 316.5z M1664 -128q0 -53 -37.5 -90.5t-90.5 -37.5q-54 0 -90 38l-343 342q-179 -124 -399 -124q-143 0 -273.5 55.5t-225 150t-150 225t-55.5 273.5t55.5 273.5t150 225t225 150t273.5 55.5t273.5 -55.5t225 -150t150 -225t55.5 -273.5q0 -220 -124 -399l343 -343q37 -37 37 -90z " />
+<glyph unicode="&#xf011;" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61t-298 61t-245 164t-164 245t-61 298q0 182 80.5 343t226.5 270q43 32 95.5 25t83.5 -50q32 -42 24.5 -94.5t-49.5 -84.5q-98 -74 -151.5 -181t-53.5 -228q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5 t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5q0 121 -53.5 228t-151.5 181q-42 32 -49.5 84.5t24.5 94.5q31 43 84 50t95 -25q146 -109 226.5 -270t80.5 -343zM896 1408v-640q0 -52 -38 -90t-90 -38t-90 38t-38 90v640q0 52 38 90t90 38t90 -38t38 -90z" />
+<glyph unicode="&#xf012;" horiz-adv-x="1792" d="M256 96v-192q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM640 224v-320q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v320q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1024 480v-576q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23 v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1408 864v-960q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v960q0 14 9 23t23 9h192q14 0 23 -9t9 -23zM1792 1376v-1472q0 -14 -9 -23t-23 -9h-192q-14 0 -23 9t-9 23v1472q0 14 9 23t23 9h192q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf013;" d="M1024 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1536 749v-222q0 -12 -8 -23t-20 -13l-185 -28q-19 -54 -39 -91q35 -50 107 -138q10 -12 10 -25t-9 -23q-27 -37 -99 -108t-94 -71q-12 0 -26 9l-138 108q-44 -23 -91 -38 q-16 -136 -29 -186q-7 -28 -36 -28h-222q-14 0 -24.5 8.5t-11.5 21.5l-28 184q-49 16 -90 37l-141 -107q-10 -9 -25 -9q-14 0 -25 11q-126 114 -165 168q-7 10 -7 23q0 12 8 23q15 21 51 66.5t54 70.5q-27 50 -41 99l-183 27q-13 2 -21 12.5t-8 23.5v222q0 12 8 23t19 13 l186 28q14 46 39 92q-40 57 -107 138q-10 12 -10 24q0 10 9 23q26 36 98.5 107.5t94.5 71.5q13 0 26 -10l138 -107q44 23 91 38q16 136 29 186q7 28 36 28h222q14 0 24.5 -8.5t11.5 -21.5l28 -184q49 -16 90 -37l142 107q9 9 24 9q13 0 25 -10q129 -119 165 -170q7 -8 7 -22 q0 -12 -8 -23q-15 -21 -51 -66.5t-54 -70.5q26 -50 41 -98l183 -28q13 -2 21 -12.5t8 -23.5z" />
+<glyph unicode="&#xf014;" horiz-adv-x="1408" d="M512 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM768 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1024 800v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1152 76v948h-896v-948q0 -22 7 -40.5t14.5 -27t10.5 -8.5h832q3 0 10.5 8.5t14.5 27t7 40.5zM480 1152h448l-48 117q-7 9 -17 11h-317q-10 -2 -17 -11zM1408 1120v-64q0 -14 -9 -23t-23 -9h-96v-948q0 -83 -47 -143.5t-113 -60.5h-832 q-66 0 -113 58.5t-47 141.5v952h-96q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h309l70 167q15 37 54 63t79 26h320q40 0 79 -26t54 -63l70 -167h309q14 0 23 -9t9 -23z" />
+<glyph unicode="&#xf015;" horiz-adv-x="1664" d="M1408 544v-480q0 -26 -19 -45t-45 -19h-384v384h-256v-384h-384q-26 0 -45 19t-19 45v480q0 1 0.5 3t0.5 3l575 474l575 -474q1 -2 1 -6zM1631 613l-62 -74q-8 -9 -21 -11h-3q-13 0 -21 7l-692 577l-692 -577q-12 -8 -24 -7q-13 2 -21 11l-62 74q-8 10 -7 23.5t11 21.5 l719 599q32 26 76 26t76 -26l244 -204v195q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-408l219 -182q10 -8 11 -21.5t-7 -23.5z" />
+<glyph unicode="&#xf016;" horiz-adv-x="1280" d="M128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280zM768 896h299l-299 299v-299zM1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h544q40 0 88 -20t76 -48l408 -408q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf017;" d="M1088 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-384q-13 0 -22.5 9.5t-9.5 22.5v448q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5v-352h288q13 0 22.5 -9.5t9.5 -22.5zM1280 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5 t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5 t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf018;" horiz-adv-x="1920" d="M1111 540v4l-24 320q-1 13 -11 22.5t-23 9.5h-186q-13 0 -23 -9.5t-11 -22.5l-24 -320v-4q-1 -12 8 -20t21 -8h244q12 0 21 8t8 20zM1870 73q0 -73 -46 -73h-704q13 0 22 9.5t8 22.5l-20 256q-1 13 -11 22.5t-23 9.5h-272q-13 0 -23 -9.5t-11 -22.5l-20 -256 q-1 -13 8 -22.5t22 -9.5h-704q-46 0 -46 73q0 54 26 116l417 1044q8 19 26 33t38 14h339q-13 0 -23 -9.5t-11 -22.5l-15 -192q-1 -14 8 -23t22 -9h166q13 0 22 9t8 23l-15 192q-1 13 -11 22.5t-23 9.5h339q20 0 38 -14t26 -33l417 -1044q26 -62 26 -116z" />
+<glyph unicode="&#xf019;" horiz-adv-x="1664" d="M1339 729q17 -41 -14 -70l-448 -448q-18 -19 -45 -19t-45 19l-448 448q-31 29 -14 70q17 39 59 39h256v448q0 26 19 45t45 19h256q26 0 45 -19t19 -45v-448h256q42 0 59 -39zM1632 512q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-1600q-14 0 -23 9t-9 23v576q0 14 9 23 t23 9h192q14 0 23 -9t9 -23v-352h1152v352q0 14 9 23t23 9h192z" />
+<glyph unicode="&#xf01a;" d="M1120 608q0 -12 -10 -24l-319 -319q-9 -9 -23 -9t-23 9l-320 320q-9 9 -9 23q0 13 9.5 22.5t22.5 9.5h192v352q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-352h192q14 0 23 -9t9 -23zM1280 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5 t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5 t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01b;" d="M1120 672q0 -13 -9.5 -22.5t-22.5 -9.5h-192v-352q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v352h-192q-14 0 -23 9t-9 23q0 12 10 24l319 319q9 9 23 9t23 -9l320 -320q9 -9 9 -23zM1280 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5 t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5 t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01c;" d="M1023 576h316q-1 3 -2.5 8t-2.5 8l-212 496h-708l-212 -496q-1 -2 -2.5 -8t-2.5 -8h316l95 -192h320zM1536 546v-482q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v482q0 62 25 123l238 552q10 25 36.5 42t52.5 17h832q26 0 52.5 -17t36.5 -42l238 -552 q25 -61 25 -123z" />
+<glyph unicode="&#xf01d;" d="M1152 640q0 -37 -33 -56l-512 -288q-14 -8 -31 -8t-32 9q-32 18 -32 55v576q0 37 32 55q31 20 63 1l512 -288q33 -19 33 -56zM1280 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5 t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf01e;" d="M1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l138 138q-148 137 -349 137q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5q169 0 304 99.5t185 261.5q7 23 30 23h199 q16 0 25 -12q10 -13 7 -27q-39 -175 -147.5 -312t-266 -213t-336.5 -76q-156 0 -298 61t-245 164t-164 245t-61 298t61 298t164 245t245 164t298 61q147 0 284.5 -55.5t244.5 -156.5l130 129q29 31 70 14q39 -17 39 -59z" />
+<glyph unicode="&#xf021;" d="M1511 480q0 -5 -1 -7q-64 -268 -268 -434.5t-478 -166.5q-146 0 -282.5 55t-243.5 157l-129 -129q-19 -19 -45 -19t-45 19t-19 45v448q0 26 19 45t45 19h448q26 0 45 -19t19 -45t-19 -45l-137 -137q71 -66 161 -102t187 -36q134 0 250 65t186 179q11 17 53 117 q8 23 30 23h192q13 0 22.5 -9.5t9.5 -22.5zM1536 1280v-448q0 -26 -19 -45t-45 -19h-448q-26 0 -45 19t-19 45t19 45l138 138q-148 137 -349 137q-134 0 -250 -65t-186 -179q-11 -17 -53 -117q-8 -23 -30 -23h-199q-13 0 -22.5 9.5t-9.5 22.5v7q65 268 270 434.5t480 166.5 q146 0 284 -55.5t245 -156.5l130 129q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf022;" horiz-adv-x="1792" d="M384 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M384 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1536 352v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5z M1536 608v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5t9.5 -22.5zM1536 864v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h960q13 0 22.5 -9.5 t9.5 -22.5zM1664 160v832q0 13 -9.5 22.5t-22.5 9.5h-1472q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1472q13 0 22.5 9.5t9.5 22.5zM1792 1248v-1088q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1472q66 0 113 -47 t47 -113z" />
+<glyph unicode="&#xf023;" horiz-adv-x="1152" d="M704 512q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5q0 -37 19 -67t51 -47l-69 -229q-5 -15 5 -28t26 -13h192q16 0 26 13t5 28l-69 229q32 17 51 47t19 67zM320 768h512v192q0 106 -75 181t-181 75t-181 -75t-75 -181v-192zM1152 672v-576q0 -40 -28 -68 t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h32v192q0 184 132 316t316 132t316 -132t132 -316v-192h32q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf024;" horiz-adv-x="1792" d="M320 1280q0 -72 -64 -110v-1266q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v1266q-64 38 -64 110q0 53 37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1792 1216v-763q0 -25 -12.5 -38.5t-39.5 -27.5q-215 -116 -369 -116q-61 0 -123.5 22t-108.5 48 t-115.5 48t-142.5 22q-192 0 -464 -146q-17 -9 -33 -9q-26 0 -45 19t-19 45v742q0 32 31 55q21 14 79 43q236 120 421 120q107 0 200 -29t219 -88q38 -19 88 -19q54 0 117.5 21t110 47t88 47t54.5 21q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf025;" horiz-adv-x="1664" d="M1664 650q0 -166 -60 -314l-20 -49l-185 -33q-22 -83 -90.5 -136.5t-156.5 -53.5v-32q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-32q71 0 130 -35.5t93 -95.5l68 12q29 95 29 193q0 148 -88 279t-236.5 209t-315.5 78 t-315.5 -78t-236.5 -209t-88 -279q0 -98 29 -193l68 -12q34 60 93 95.5t130 35.5v32q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v32q-88 0 -156.5 53.5t-90.5 136.5l-185 33l-20 49q-60 148 -60 314q0 151 67 291t179 242.5 t266 163.5t320 61t320 -61t266 -163.5t179 -242.5t67 -291z" />
+<glyph unicode="&#xf026;" horiz-adv-x="768" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf027;" horiz-adv-x="1152" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142z" />
+<glyph unicode="&#xf028;" horiz-adv-x="1664" d="M768 1184v-1088q0 -26 -19 -45t-45 -19t-45 19l-333 333h-262q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h262l333 333q19 19 45 19t45 -19t19 -45zM1152 640q0 -76 -42.5 -141.5t-112.5 -93.5q-10 -5 -25 -5q-26 0 -45 18.5t-19 45.5q0 21 12 35.5t29 25t34 23t29 35.5 t12 57t-12 57t-29 35.5t-34 23t-29 25t-12 35.5q0 27 19 45.5t45 18.5q15 0 25 -5q70 -27 112.5 -93t42.5 -142zM1408 640q0 -153 -85 -282.5t-225 -188.5q-13 -5 -25 -5q-27 0 -46 19t-19 45q0 39 39 59q56 29 76 44q74 54 115.5 135.5t41.5 173.5t-41.5 173.5 t-115.5 135.5q-20 15 -76 44q-39 20 -39 59q0 26 19 45t45 19q13 0 26 -5q140 -59 225 -188.5t85 -282.5zM1664 640q0 -230 -127 -422.5t-338 -283.5q-13 -5 -26 -5q-26 0 -45 19t-19 45q0 36 39 59q7 4 22.5 10.5t22.5 10.5q46 25 82 51q123 91 192 227t69 289t-69 289 t-192 227q-36 26 -82 51q-7 4 -22.5 10.5t-22.5 10.5q-39 23 -39 59q0 26 19 45t45 19q13 0 26 -5q211 -91 338 -283.5t127 -422.5z" />
+<glyph unicode="&#xf029;" horiz-adv-x="1408" d="M384 384v-128h-128v128h128zM384 1152v-128h-128v128h128zM1152 1152v-128h-128v128h128zM128 129h384v383h-384v-383zM128 896h384v384h-384v-384zM896 896h384v384h-384v-384zM640 640v-640h-640v640h640zM1152 128v-128h-128v128h128zM1408 128v-128h-128v128h128z M1408 640v-384h-384v128h-128v-384h-128v640h384v-128h128v128h128zM640 1408v-640h-640v640h640zM1408 1408v-640h-640v640h640z" />
+<glyph unicode="&#xf02a;" horiz-adv-x="1792" d="M672 1408v-1536h-64v1536h64zM1408 1408v-1536h-64v1536h64zM1568 1408v-1536h-64v1536h64zM576 1408v-1536h-64v1536h64zM1280 1408v-1536h-256v1536h256zM896 1408v-1536h-128v1536h128zM448 1408v-1536h-128v1536h128zM1792 1408v-1536h-128v1536h128zM256 1408v-1536 h-256v1536h256z" />
+<glyph unicode="&#xf02b;" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91z" />
+<glyph unicode="&#xf02c;" horiz-adv-x="1920" d="M448 1088q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1515 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-53 0 -90 37l-715 716q-38 37 -64.5 101t-26.5 117v416q0 52 38 90t90 38h416q53 0 117 -26.5t102 -64.5 l715 -714q37 -39 37 -91zM1899 512q0 -53 -37 -90l-491 -492q-39 -37 -91 -37q-36 0 -59 14t-53 45l470 470q37 37 37 90q0 52 -37 91l-715 714q-38 38 -102 64.5t-117 26.5h224q53 0 117 -26.5t102 -64.5l715 -714q37 -39 37 -91z" />
+<glyph unicode="&#xf02d;" horiz-adv-x="1664" d="M1639 1058q40 -57 18 -129l-275 -906q-19 -64 -76.5 -107.5t-122.5 -43.5h-923q-77 0 -148.5 53.5t-99.5 131.5q-24 67 -2 127q0 4 3 27t4 37q1 8 -3 21.5t-3 19.5q2 11 8 21t16.5 23.5t16.5 23.5q23 38 45 91.5t30 91.5q3 10 0.5 30t-0.5 28q3 11 17 28t17 23 q21 36 42 92t25 90q1 9 -2.5 32t0.5 28q4 13 22 30.5t22 22.5q19 26 42.5 84.5t27.5 96.5q1 8 -3 25.5t-2 26.5q2 8 9 18t18 23t17 21q8 12 16.5 30.5t15 35t16 36t19.5 32t26.5 23.5t36 11.5t47.5 -5.5l-1 -3q38 9 51 9h761q74 0 114 -56t18 -130l-274 -906 q-36 -119 -71.5 -153.5t-128.5 -34.5h-869q-27 0 -38 -15q-11 -16 -1 -43q24 -70 144 -70h923q29 0 56 15.5t35 41.5l300 987q7 22 5 57q38 -15 59 -43zM575 1056q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5 t-16.5 -22.5zM492 800q-4 -13 2 -22.5t20 -9.5h608q13 0 25.5 9.5t16.5 22.5l21 64q4 13 -2 22.5t-20 9.5h-608q-13 0 -25.5 -9.5t-16.5 -22.5z" />
+<glyph unicode="&#xf02e;" horiz-adv-x="1280" d="M1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
+<glyph unicode="&#xf02f;" horiz-adv-x="1664" d="M384 0h896v256h-896v-256zM384 640h896v384h-160q-40 0 -68 28t-28 68v160h-640v-640zM1536 576q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 576v-416q0 -13 -9.5 -22.5t-22.5 -9.5h-224v-160q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68 v160h-224q-13 0 -22.5 9.5t-9.5 22.5v416q0 79 56.5 135.5t135.5 56.5h64v544q0 40 28 68t68 28h672q40 0 88 -20t76 -48l152 -152q28 -28 48 -76t20 -88v-256h64q79 0 135.5 -56.5t56.5 -135.5z" />
+<glyph unicode="&#xf030;" horiz-adv-x="1920" d="M960 864q119 0 203.5 -84.5t84.5 -203.5t-84.5 -203.5t-203.5 -84.5t-203.5 84.5t-84.5 203.5t84.5 203.5t203.5 84.5zM1664 1280q106 0 181 -75t75 -181v-896q0 -106 -75 -181t-181 -75h-1408q-106 0 -181 75t-75 181v896q0 106 75 181t181 75h224l51 136 q19 49 69.5 84.5t103.5 35.5h512q53 0 103.5 -35.5t69.5 -84.5l51 -136h224zM960 128q185 0 316.5 131.5t131.5 316.5t-131.5 316.5t-316.5 131.5t-316.5 -131.5t-131.5 -316.5t131.5 -316.5t316.5 -131.5z" />
+<glyph unicode="&#xf031;" horiz-adv-x="1664" d="M725 977l-170 -450q73 -1 153.5 -2t119 -1.5t52.5 -0.5l29 2q-32 95 -92 241q-53 132 -92 211zM21 -128h-21l2 79q22 7 80 18q89 16 110 31q20 16 48 68l237 616l280 724h75h53l11 -21l205 -480q103 -242 124 -297q39 -102 96 -235q26 -58 65 -164q24 -67 65 -149 q22 -49 35 -57q22 -19 69 -23q47 -6 103 -27q6 -39 6 -57q0 -14 -1 -26q-80 0 -192 8q-93 8 -189 8q-79 0 -135 -2l-200 -11l-58 -2q0 45 4 78l131 28q56 13 68 23q12 12 12 27t-6 32l-47 114l-92 228l-450 2q-29 -65 -104 -274q-23 -64 -23 -84q0 -31 17 -43 q26 -21 103 -32q3 0 13.5 -2t30 -5t40.5 -6q1 -28 1 -58q0 -17 -2 -27q-66 0 -349 20l-48 -8q-81 -14 -167 -14z" />
+<glyph unicode="&#xf032;" horiz-adv-x="1408" d="M555 15q76 -32 140 -32q131 0 216 41t122 113q38 70 38 181q0 114 -41 180q-58 94 -141 126q-80 32 -247 32q-74 0 -101 -10v-144l-1 -173l3 -270q0 -15 12 -44zM541 761q43 -7 109 -7q175 0 264 65t89 224q0 112 -85 187q-84 75 -255 75q-52 0 -130 -13q0 -44 2 -77 q7 -122 6 -279l-1 -98q0 -43 1 -77zM0 -128l2 94q45 9 68 12q77 12 123 31q17 27 21 51q9 66 9 194l-2 497q-5 256 -9 404q-1 87 -11 109q-1 4 -12 12q-18 12 -69 15q-30 2 -114 13l-4 83l260 6l380 13l45 1q5 0 14 0.5t14 0.5q1 0 21.5 -0.5t40.5 -0.5h74q88 0 191 -27 q43 -13 96 -39q57 -29 102 -76q44 -47 65 -104t21 -122q0 -70 -32 -128t-95 -105q-26 -20 -150 -77q177 -41 267 -146q92 -106 92 -236q0 -76 -29 -161q-21 -62 -71 -117q-66 -72 -140 -108q-73 -36 -203 -60q-82 -15 -198 -11l-197 4q-84 2 -298 -11q-33 -3 -272 -11z" />
+<glyph unicode="&#xf033;" horiz-adv-x="1024" d="M0 -126l17 85q4 1 77 20q76 19 116 39q29 37 41 101l27 139l56 268l12 64q8 44 17 84.5t16 67t12.5 46.5t9 30.5t3.5 11.5l29 157l16 63l22 135l8 50v38q-41 22 -144 28q-28 2 -38 4l19 103l317 -14q39 -2 73 -2q66 0 214 9q33 2 68 4.5t36 2.5q-2 -19 -6 -38 q-7 -29 -13 -51q-55 -19 -109 -31q-64 -16 -101 -31q-12 -31 -24 -88q-9 -44 -13 -82q-44 -199 -66 -306l-61 -311l-38 -158l-43 -235l-12 -45q-2 -7 1 -27q64 -15 119 -21q36 -5 66 -10q-1 -29 -7 -58q-7 -31 -9 -41q-18 0 -23 -1q-24 -2 -42 -2q-9 0 -28 3q-19 4 -145 17 l-198 2q-41 1 -174 -11q-74 -7 -98 -9z" />
+<glyph unicode="&#xf034;" horiz-adv-x="1792" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l215 -1h293l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -42.5 2t-103.5 -1t-111 -1 q-34 0 -67 -5q-10 -97 -8 -136l1 -152v-332l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-88 0 -233 -14q-48 -4 -70 -4q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q8 192 6 433l-5 428q-1 62 -0.5 118.5t0.5 102.5t-2 57t-6 15q-6 5 -14 6q-38 6 -148 6q-43 0 -100 -13.5t-73 -24.5q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1744 128q33 0 42 -18.5t-11 -44.5 l-126 -162q-20 -26 -49 -26t-49 26l-126 162q-20 26 -11 44.5t42 18.5h80v1024h-80q-33 0 -42 18.5t11 44.5l126 162q20 26 49 26t49 -26l126 -162q20 -26 11 -44.5t-42 -18.5h-80v-1024h80z" />
+<glyph unicode="&#xf035;" d="M81 1407l54 -27q20 -5 211 -5h130l19 3l115 1l446 -1h318l34 -2q14 -1 28 7t21 16l7 8l42 1q15 0 28 -1v-104.5t1 -131.5l1 -100l-1 -58q0 -32 -4 -51q-39 -15 -68 -18q-25 43 -54 128q-8 24 -15.5 62.5t-11.5 65.5t-6 29q-13 15 -27 19q-7 2 -58.5 2t-138.5 -1t-128 -1 q-94 0 -127 -5q-10 -97 -8 -136l1 -152v52l3 -359l-1 -147q-1 -46 11 -85q49 -25 89 -32q2 0 18 -5t44 -13t43 -12q30 -8 50 -18q5 -45 5 -50q0 -10 -3 -29q-14 -1 -34 -1q-110 0 -187 10q-72 8 -238 8q-82 0 -233 -13q-45 -5 -70 -5q-2 22 -2 26l-1 26v9q21 33 79 49 q139 38 159 50q9 21 12 56q6 137 6 433l-5 44q0 265 -2 278q-2 11 -6 15q-6 5 -14 6q-38 6 -148 6q-50 0 -168.5 -14t-132.5 -24q-13 -9 -22 -33t-22 -75t-24 -84q-6 -19 -19.5 -32t-20.5 -13q-44 27 -56 44v297v86zM1505 113q26 -20 26 -49t-26 -49l-162 -126 q-26 -20 -44.5 -11t-18.5 42v80h-1024v-80q0 -33 -18.5 -42t-44.5 11l-162 126q-26 20 -26 49t26 49l162 126q26 20 44.5 11t18.5 -42v-80h1024v80q0 33 18.5 42t44.5 -11z" />
+<glyph unicode="&#xf036;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf037;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1408 576v-128q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45 -19t19 -45zM1664 960v-128q0 -26 -19 -45t-45 -19 h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1280 1344v-128q0 -26 -19 -45t-45 -19h-640q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h640q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf038;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1280q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1536q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1536q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1152q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf039;" horiz-adv-x="1792" d="M1792 192v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 576v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 960v-128q0 -26 -19 -45 t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-128q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf03a;" horiz-adv-x="1792" d="M256 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM256 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5 t9.5 -22.5zM256 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344 q13 0 22.5 -9.5t9.5 -22.5zM256 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-192q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v192 q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03b;" horiz-adv-x="1792" d="M384 992v-576q0 -13 -9.5 -22.5t-22.5 -9.5q-14 0 -23 9l-288 288q-9 9 -9 23t9 23l288 288q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03c;" horiz-adv-x="1792" d="M352 704q0 -14 -9 -23l-288 -288q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v576q0 13 9.5 22.5t22.5 9.5q14 0 23 -9l288 -288q9 -9 9 -23zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5 t9.5 -22.5zM1792 608v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088q13 0 22.5 -9.5t9.5 -22.5zM1792 992v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1088q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1088 q13 0 22.5 -9.5t9.5 -22.5zM1792 1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1728q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1728q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf03d;" horiz-adv-x="1920" d="M1900 1278q20 -8 20 -30v-1216q0 -22 -20 -30q-8 -2 -12 -2q-12 0 -23 9l-585 586v-307q0 -119 -84.5 -203.5t-203.5 -84.5h-704q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h704q119 0 203.5 -84.5t84.5 -203.5v-307l585 586q16 15 35 7z" />
+<glyph unicode="&#xf03e;" horiz-adv-x="1920" d="M640 960q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1664 576v-448h-1408v192l320 320l160 -160l512 512zM1760 1280h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5v1216 q0 13 -9.5 22.5t-22.5 9.5zM1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf040;" d="M363 0l91 91l-235 235l-91 -91v-107h128v-128h107zM886 928q0 22 -22 22q-10 0 -17 -7l-542 -542q-7 -7 -7 -17q0 -22 22 -22q10 0 17 7l542 542q7 7 7 17zM832 1120l416 -416l-832 -832h-416v416zM1515 1024q0 -53 -37 -90l-166 -166l-416 416l166 165q36 38 90 38 q53 0 91 -38l235 -234q37 -39 37 -91z" />
+<glyph unicode="&#xf041;" horiz-adv-x="1024" d="M768 896q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1024 896q0 -109 -33 -179l-364 -774q-16 -33 -47.5 -52t-67.5 -19t-67.5 19t-46.5 52l-365 774q-33 70 -33 179q0 212 150 362t362 150t362 -150t150 -362z" />
+<glyph unicode="&#xf042;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM256 640q0 -104 40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5v1024q-104 0 -198.5 -40.5 t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5z" />
+<glyph unicode="&#xf043;" horiz-adv-x="1024" d="M512 384q0 36 -20 69q-1 1 -15.5 22.5t-25.5 38t-25 44t-21 50.5q-4 16 -21 16t-21 -16q-7 -23 -21 -50.5t-25 -44t-25.5 -38t-15.5 -22.5q-20 -33 -20 -69q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 512q0 -212 -150 -362t-362 -150t-362 150t-150 362 q0 145 81 275q6 9 62.5 90.5t101 151t99.5 178t83 201.5q9 30 34 47t51 17t51.5 -17t33.5 -47q28 -93 83 -201.5t99.5 -178t101 -151t62.5 -90.5q81 -127 81 -275z" />
+<glyph unicode="&#xf044;" horiz-adv-x="1792" d="M888 352l116 116l-152 152l-116 -116v-56h96v-96h56zM1328 1072q-16 16 -33 -1l-350 -350q-17 -17 -1 -33t33 1l350 350q17 17 1 33zM1408 478v-190q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-14 -14 -32 -8q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v126q0 13 9 22l64 64q15 15 35 7t20 -29zM1312 1216l288 -288l-672 -672h-288v288zM1756 1084l-92 -92 l-288 288l92 92q28 28 68 28t68 -28l152 -152q28 -28 28 -68t-28 -68z" />
+<glyph unicode="&#xf045;" horiz-adv-x="1664" d="M1408 547v-259q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h255v0q13 0 22.5 -9.5t9.5 -22.5q0 -27 -26 -32q-77 -26 -133 -60q-10 -4 -16 -4h-112q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832 q66 0 113 47t47 113v214q0 19 18 29q28 13 54 37q16 16 35 8q21 -9 21 -29zM1645 1043l-384 -384q-18 -19 -45 -19q-12 0 -25 5q-39 17 -39 59v192h-160q-323 0 -438 -131q-119 -137 -74 -473q3 -23 -20 -34q-8 -2 -12 -2q-16 0 -26 13q-10 14 -21 31t-39.5 68.5t-49.5 99.5 t-38.5 114t-17.5 122q0 49 3.5 91t14 90t28 88t47 81.5t68.5 74t94.5 61.5t124.5 48.5t159.5 30.5t196.5 11h160v192q0 42 39 59q13 5 25 5q26 0 45 -19l384 -384q19 -19 19 -45t-19 -45z" />
+<glyph unicode="&#xf046;" horiz-adv-x="1664" d="M1408 606v-318q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832q63 0 117 -25q15 -7 18 -23q3 -17 -9 -29l-49 -49q-10 -10 -23 -10q-3 0 -9 2q-23 6 -45 6h-832q-66 0 -113 -47t-47 -113v-832 q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v254q0 13 9 22l64 64q10 10 23 10q6 0 12 -3q20 -8 20 -29zM1639 1095l-814 -814q-24 -24 -57 -24t-57 24l-430 430q-24 24 -24 57t24 57l110 110q24 24 57 24t57 -24l263 -263l647 647q24 24 57 24t57 -24l110 -110 q24 -24 24 -57t-24 -57z" />
+<glyph unicode="&#xf047;" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-384v-384h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v384h-384v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45 t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h384v384h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45t-19 -45t-45 -19h-128v-384h384v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf048;" horiz-adv-x="1024" d="M979 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19z" />
+<glyph unicode="&#xf049;" horiz-adv-x="1792" d="M1747 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-9 9 -13 19v-678q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-678q4 11 13 19l710 710 q19 19 32 13t13 -32v-710q4 11 13 19z" />
+<glyph unicode="&#xf04a;" horiz-adv-x="1664" d="M1619 1395q19 19 32 13t13 -32v-1472q0 -26 -13 -32t-32 13l-710 710q-8 9 -13 19v-710q0 -26 -13 -32t-32 13l-710 710q-19 19 -19 45t19 45l710 710q19 19 32 13t13 -32v-710q5 11 13 19z" />
+<glyph unicode="&#xf04b;" horiz-adv-x="1408" d="M1384 609l-1328 -738q-23 -13 -39.5 -3t-16.5 36v1472q0 26 16.5 36t39.5 -3l1328 -738q23 -13 23 -31t-23 -31z" />
+<glyph unicode="&#xf04c;" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45zM640 1344v-1408q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h512q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf04d;" d="M1536 1344v-1408q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v1408q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf04e;" horiz-adv-x="1664" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q19 -19 19 -45t-19 -45l-710 -710q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
+<glyph unicode="&#xf050;" horiz-adv-x="1792" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v710q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19l-710 -710 q-19 -19 -32 -13t-13 32v710q-5 -10 -13 -19z" />
+<glyph unicode="&#xf051;" horiz-adv-x="1024" d="M45 -115q-19 -19 -32 -13t-13 32v1472q0 26 13 32t32 -13l710 -710q8 -8 13 -19v678q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-1408q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v678q-5 -10 -13 -19z" />
+<glyph unicode="&#xf052;" horiz-adv-x="1538" d="M14 557l710 710q19 19 45 19t45 -19l710 -710q19 -19 13 -32t-32 -13h-1472q-26 0 -32 13t13 32zM1473 0h-1408q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1408q26 0 45 -19t19 -45v-256q0 -26 -19 -45t-45 -19z" />
+<glyph unicode="&#xf053;" horiz-adv-x="1152" d="M742 -37l-652 651q-37 37 -37 90.5t37 90.5l652 651q37 37 90.5 37t90.5 -37l75 -75q37 -37 37 -90.5t-37 -90.5l-486 -486l486 -485q37 -38 37 -91t-37 -90l-75 -75q-37 -37 -90.5 -37t-90.5 37z" />
+<glyph unicode="&#xf054;" horiz-adv-x="1152" d="M1099 704q0 -52 -37 -91l-652 -651q-37 -37 -90 -37t-90 37l-76 75q-37 39 -37 91q0 53 37 90l486 486l-486 485q-37 39 -37 91q0 53 37 90l76 75q36 38 90 38t90 -38l652 -651q37 -37 37 -90z" />
+<glyph unicode="&#xf055;" d="M1216 576v128q0 26 -19 45t-45 19h-256v256q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-256h-256q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h256v-256q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v256h256q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf056;" d="M1216 576v128q0 26 -19 45t-45 19h-768q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h768q26 0 45 19t19 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" />
+<glyph unicode="&#xf057;" d="M1149 414q0 26 -19 45l-181 181l181 181q19 19 19 45q0 27 -19 46l-90 90q-19 19 -46 19q-26 0 -45 -19l-181 -181l-181 181q-19 19 -45 19q-27 0 -46 -19l-90 -90q-19 -19 -19 -46q0 -26 19 -45l181 -181l-181 -181q-19 -19 -19 -45q0 -27 19 -46l90 -90q19 -19 46 -19 q26 0 45 19l181 181l181 -181q19 -19 45 -19q27 0 46 19l90 90q19 19 19 46zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf058;" d="M1284 802q0 28 -18 46l-91 90q-19 19 -45 19t-45 -19l-408 -407l-226 226q-19 19 -45 19t-45 -19l-91 -90q-18 -18 -18 -46q0 -27 18 -45l362 -362q19 -19 45 -19q27 0 46 19l543 543q18 18 18 45zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103 t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf059;" d="M896 160v192q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-192q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1152 832q0 97 -58.5 172t-144.5 111.5t-181 36.5t-181 -36.5t-144.5 -111.5t-58.5 -172v-11v-13t1 -11.5t3 -11.5t5.5 -8t9 -7 t13.5 -2h192q14 0 23 9t9 23q0 12 11 27q19 31 50.5 50t66.5 19q39 0 83 -21.5t44 -57.5q0 -33 -26.5 -58t-63.5 -44t-74.5 -41.5t-64 -63.5t-26.5 -98v-11v-13t1 -11.5t3 -11.5t5.5 -8t9 -7t13.5 -2h192q17 0 24 10.5t8 24.5t13.5 33t37.5 32q60 33 70 39q62 44 98.5 108 t36.5 137zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05a;" d="M1024 160v64q0 14 -9 23t-23 9h-96v480q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h96v-384h-96q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h448q14 0 23 9t9 23zM896 928v192q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23 t23 -9h192q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05b;" d="M1197 512h-109q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h109q-32 108 -112.5 188.5t-188.5 112.5v-109q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v109q-108 -32 -188.5 -112.5t-112.5 -188.5h109q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-109 q32 -108 112.5 -188.5t188.5 -112.5v109q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-109q108 32 188.5 112.5t112.5 188.5zM1536 704v-128q0 -26 -19 -45t-45 -19h-143q-37 -161 -154.5 -278.5t-278.5 -154.5v-143q0 -26 -19 -45t-45 -19h-128q-26 0 -45 19t-19 45v143 q-161 37 -278.5 154.5t-154.5 278.5h-143q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h143q37 161 154.5 278.5t278.5 154.5v143q0 26 19 45t45 19h128q26 0 45 -19t19 -45v-143q161 -37 278.5 -154.5t154.5 -278.5h143q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf05c;" d="M1125 448q0 -27 -18 -45l-102 -102q-18 -18 -45 -18t-45 18l-147 147l-147 -147q-18 -18 -45 -18t-45 18l-102 102q-18 18 -18 45t18 45l147 147l-147 147q-18 18 -18 45t18 45l102 102q18 18 45 18t45 -18l147 -147l147 147q18 18 45 18t45 -18l102 -102q18 -18 18 -45 t-18 -45l-147 -147l147 -147q18 -18 18 -45zM1280 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5 t40.5 198.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05d;" d="M1189 768q0 -27 -18 -45l-320 -320l-102 -102q-18 -18 -45 -18t-45 18l-102 102l-192 192q-18 18 -18 45t18 45l102 102q18 18 45 18t45 -18l147 -147l275 275q18 18 45 18t45 -18l102 -102q18 -18 18 -45zM1280 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5 t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5 t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf05e;" d="M1280 640q0 139 -71 260l-701 -701q121 -71 260 -71q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM327 380l701 701q-121 71 -260 71q-104 0 -198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5q0 -139 71 -260zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf060;" d="M1536 640v-128q0 -53 -32.5 -90.5t-84.5 -37.5h-704l293 -294q38 -36 38 -90t-38 -90l-75 -76q-37 -37 -90 -37q-52 0 -91 37l-651 652q-37 37 -37 90q0 52 37 91l651 650q38 38 91 38q52 0 90 -38l75 -74q38 -38 38 -91t-38 -91l-293 -293h704q52 0 84.5 -37.5 t32.5 -90.5z" />
+<glyph unicode="&#xf061;" d="M1472 576q0 -54 -37 -91l-651 -651q-39 -37 -91 -37q-51 0 -90 37l-75 75q-38 38 -38 91t38 91l293 293h-704q-52 0 -84.5 37.5t-32.5 90.5v128q0 53 32.5 90.5t84.5 37.5h704l-293 294q-38 36 -38 90t38 90l75 75q38 38 90 38q53 0 91 -38l651 -651q37 -35 37 -90z" />
+<glyph unicode="&#xf062;" horiz-adv-x="1664" d="M1611 565q0 -51 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-294 293v-704q0 -52 -37.5 -84.5t-90.5 -32.5h-128q-53 0 -90.5 32.5t-37.5 84.5v704l-294 -293q-36 -38 -90 -38t-90 38l-75 75q-38 38 -38 90q0 53 38 91l651 651q35 37 90 37q54 0 91 -37l651 -651 q37 -39 37 -91z" />
+<glyph unicode="&#xf063;" horiz-adv-x="1664" d="M1611 704q0 -53 -37 -90l-651 -652q-39 -37 -91 -37q-53 0 -90 37l-651 652q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l294 -294v704q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-704l294 294q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
+<glyph unicode="&#xf064;" horiz-adv-x="1792" d="M1792 896q0 -26 -19 -45l-512 -512q-19 -19 -45 -19t-45 19t-19 45v256h-224q-98 0 -175.5 -6t-154 -21.5t-133 -42.5t-105.5 -69.5t-80 -101t-48.5 -138.5t-17.5 -181q0 -55 5 -123q0 -6 2.5 -23.5t2.5 -26.5q0 -15 -8.5 -25t-23.5 -10q-16 0 -28 17q-7 9 -13 22 t-13.5 30t-10.5 24q-127 285 -127 451q0 199 53 333q162 403 875 403h224v256q0 26 19 45t45 19t45 -19l512 -512q19 -19 19 -45z" />
+<glyph unicode="&#xf065;" d="M755 480q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23zM1536 1344v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332 q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf066;" d="M768 576v-448q0 -26 -19 -45t-45 -19t-45 19l-144 144l-332 -332q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l332 332l-144 144q-19 19 -19 45t19 45t45 19h448q26 0 45 -19t19 -45zM1523 1248q0 -13 -10 -23l-332 -332l144 -144q19 -19 19 -45t-19 -45 t-45 -19h-448q-26 0 -45 19t-19 45v448q0 26 19 45t45 19t45 -19l144 -144l332 332q10 10 23 10t23 -10l114 -114q10 -10 10 -23z" />
+<glyph unicode="&#xf067;" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-416v-416q0 -40 -28 -68t-68 -28h-192q-40 0 -68 28t-28 68v416h-416q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h416v416q0 40 28 68t68 28h192q40 0 68 -28t28 -68v-416h416q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf068;" horiz-adv-x="1408" d="M1408 800v-192q0 -40 -28 -68t-68 -28h-1216q-40 0 -68 28t-28 68v192q0 40 28 68t68 28h1216q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf069;" horiz-adv-x="1664" d="M1482 486q46 -26 59.5 -77.5t-12.5 -97.5l-64 -110q-26 -46 -77.5 -59.5t-97.5 12.5l-266 153v-307q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v307l-266 -153q-46 -26 -97.5 -12.5t-77.5 59.5l-64 110q-26 46 -12.5 97.5t59.5 77.5l266 154l-266 154 q-46 26 -59.5 77.5t12.5 97.5l64 110q26 46 77.5 59.5t97.5 -12.5l266 -153v307q0 52 38 90t90 38h128q52 0 90 -38t38 -90v-307l266 153q46 26 97.5 12.5t77.5 -59.5l64 -110q26 -46 12.5 -97.5t-59.5 -77.5l-266 -154z" />
+<glyph unicode="&#xf06a;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM896 161v190q0 14 -9 23.5t-22 9.5h-192q-13 0 -23 -10t-10 -23v-190q0 -13 10 -23t23 -10h192 q13 0 22 9.5t9 23.5zM894 505l18 621q0 12 -10 18q-10 8 -24 8h-220q-14 0 -24 -8q-10 -6 -10 -18l17 -621q0 -10 10 -17.5t24 -7.5h185q14 0 23.5 7.5t10.5 17.5z" />
+<glyph unicode="&#xf06b;" d="M928 180v716h-320v-716q0 -25 18.5 -38.5t45.5 -13.5h192q27 0 45.5 13.5t18.5 38.5zM472 1024h195l-126 161q-24 31 -69 31q-40 0 -68 -28t-28 -68t28 -68t68 -28zM1160 1120q0 40 -28 68t-68 28q-45 0 -69 -31l-125 -161h194q40 0 68 28t28 68zM1536 864v-320 q0 -14 -10 -22t-27 -10.5t-32 -2.5t-34.5 1.5t-24.5 1.5v-416q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v416q-5 0 -24.5 -1.5t-34.5 -1.5t-32 2.5t-27 10.5t-10 22v320q0 13 9.5 22.5t22.5 9.5h440q-93 0 -158.5 65.5t-65.5 158.5t65.5 158.5t158.5 65.5 q108 0 168 -77l128 -165l128 165q60 77 168 77q93 0 158.5 -65.5t65.5 -158.5t-65.5 -158.5t-158.5 -65.5h440q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf06c;" horiz-adv-x="1792" d="M1280 832q0 26 -19 45t-45 19q-172 0 -318 -49.5t-259.5 -134t-235.5 -219.5q-19 -21 -19 -45q0 -26 19 -45t45 -19q24 0 45 19q27 24 74 71t67 66q137 124 268.5 176t313.5 52q26 0 45 19t19 45zM1792 1030q0 -95 -20 -193q-46 -224 -184.5 -383t-357.5 -268 q-214 -108 -438 -108q-148 0 -286 47q-15 5 -88 42t-96 37q-16 0 -39.5 -32t-45 -70t-52.5 -70t-60 -32q-30 0 -51 11t-31 24t-27 42q-2 4 -6 11t-5.5 10t-3 9.5t-1.5 13.5q0 35 31 73.5t68 65.5t68 56t31 48q0 4 -14 38t-16 44q-9 51 -9 104q0 115 43.5 220t119 184.5 t170.5 139t204 95.5q55 18 145 25.5t179.5 9t178.5 6t163.5 24t113.5 56.5l29.5 29.5t29.5 28t27 20t36.5 16t43.5 4.5q39 0 70.5 -46t47.5 -112t24 -124t8 -96z" />
+<glyph unicode="&#xf06d;" horiz-adv-x="1408" d="M1408 -160v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-1344q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h1344q13 0 22.5 -9.5t9.5 -22.5zM1152 896q0 -78 -24.5 -144t-64 -112.5t-87.5 -88t-96 -77.5t-87.5 -72t-64 -81.5t-24.5 -96.5q0 -96 67 -224l-4 1l1 -1 q-90 41 -160 83t-138.5 100t-113.5 122.5t-72.5 150.5t-27.5 184q0 78 24.5 144t64 112.5t87.5 88t96 77.5t87.5 72t64 81.5t24.5 96.5q0 94 -66 224l3 -1l-1 1q90 -41 160 -83t138.5 -100t113.5 -122.5t72.5 -150.5t27.5 -184z" />
+<glyph unicode="&#xf06e;" horiz-adv-x="1792" d="M1664 576q-152 236 -381 353q61 -104 61 -225q0 -185 -131.5 -316.5t-316.5 -131.5t-316.5 131.5t-131.5 316.5q0 121 61 225q-229 -117 -381 -353q133 -205 333.5 -326.5t434.5 -121.5t434.5 121.5t333.5 326.5zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5 t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1792 576q0 -34 -20 -69q-140 -230 -376.5 -368.5t-499.5 -138.5t-499.5 139t-376.5 368q-20 35 -20 69t20 69q140 229 376.5 368t499.5 139t499.5 -139t376.5 -368q20 -35 20 -69z" />
+<glyph unicode="&#xf070;" horiz-adv-x="1792" d="M555 201l78 141q-87 63 -136 159t-49 203q0 121 61 225q-229 -117 -381 -353q167 -258 427 -375zM944 960q0 20 -14 34t-34 14q-125 0 -214.5 -89.5t-89.5 -214.5q0 -20 14 -34t34 -14t34 14t14 34q0 86 61 147t147 61q20 0 34 14t14 34zM1307 1151q0 -7 -1 -9 q-105 -188 -315 -566t-316 -567l-49 -89q-10 -16 -28 -16q-12 0 -134 70q-16 10 -16 28q0 12 44 87q-143 65 -263.5 173t-208.5 245q-20 31 -20 69t20 69q153 235 380 371t496 136q89 0 180 -17l54 97q10 16 28 16q5 0 18 -6t31 -15.5t33 -18.5t31.5 -18.5t19.5 -11.5 q16 -10 16 -27zM1344 704q0 -139 -79 -253.5t-209 -164.5l280 502q8 -45 8 -84zM1792 576q0 -35 -20 -69q-39 -64 -109 -145q-150 -172 -347.5 -267t-419.5 -95l74 132q212 18 392.5 137t301.5 307q-115 179 -282 294l63 112q95 -64 182.5 -153t144.5 -184q20 -34 20 -69z " />
+<glyph unicode="&#xf071;" horiz-adv-x="1792" d="M1024 161v190q0 14 -9.5 23.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -23.5v-190q0 -14 9.5 -23.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 23.5zM1022 535l18 459q0 12 -10 19q-13 11 -24 11h-220q-11 0 -24 -11q-10 -7 -10 -21l17 -457q0 -10 10 -16.5t24 -6.5h185 q14 0 23.5 6.5t10.5 16.5zM1008 1469l768 -1408q35 -63 -2 -126q-17 -29 -46.5 -46t-63.5 -17h-1536q-34 0 -63.5 17t-46.5 46q-37 63 -2 126l768 1408q17 31 47 49t65 18t65 -18t47 -49z" />
+<glyph unicode="&#xf072;" horiz-adv-x="1408" d="M1397 1324q0 -87 -149 -236l-240 -240l143 -746l1 -6q0 -14 -9 -23l-64 -64q-9 -9 -23 -9q-21 0 -29 18l-274 575l-245 -245q68 -238 68 -252t-9 -23l-64 -64q-9 -9 -23 -9q-18 0 -28 16l-155 280l-280 155q-17 9 -17 28q0 14 9 23l64 65q9 9 23 9t252 -68l245 245 l-575 274q-18 8 -18 29q0 14 9 23l64 64q9 9 23 9q4 0 6 -1l746 -143l240 240q149 149 236 149q32 0 52.5 -20.5t20.5 -52.5z" />
+<glyph unicode="&#xf073;" horiz-adv-x="1664" d="M128 -128h288v288h-288v-288zM480 -128h320v288h-320v-288zM128 224h288v320h-288v-320zM480 224h320v320h-320v-320zM128 608h288v288h-288v-288zM864 -128h320v288h-320v-288zM480 608h320v288h-320v-288zM1248 -128h288v288h-288v-288zM864 224h320v320h-320v-320z M512 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1248 224h288v320h-288v-320zM864 608h320v288h-320v-288zM1248 608h288v288h-288v-288zM1280 1088v288q0 13 -9.5 22.5t-22.5 9.5h-64 q-13 0 -22.5 -9.5t-9.5 -22.5v-288q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47 h64q66 0 113 -47t47 -113v-96h128q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf074;" horiz-adv-x="1792" d="M666 1055q-60 -92 -137 -273q-22 45 -37 72.5t-40.5 63.5t-51 56.5t-63 35t-81.5 14.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q250 0 410 -225zM1792 256q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5v192q-32 0 -85 -0.5t-81 -1t-73 1 t-71 5t-64 10.5t-63 18.5t-58 28.5t-59 40t-55 53.5t-56 69.5q59 93 136 273q22 -45 37 -72.5t40.5 -63.5t51 -56.5t63 -35t81.5 -14.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23zM1792 1152q0 -14 -9 -23l-320 -320q-9 -9 -23 -9q-13 0 -22.5 9.5t-9.5 22.5 v192h-256q-48 0 -87 -15t-69 -45t-51 -61.5t-45 -77.5q-32 -62 -78 -171q-29 -66 -49.5 -111t-54 -105t-64 -100t-74 -83t-90 -68.5t-106.5 -42t-128 -16.5h-224q-14 0 -23 9t-9 23v192q0 14 9 23t23 9h224q48 0 87 15t69 45t51 61.5t45 77.5q32 62 78 171q29 66 49.5 111 t54 105t64 100t74 83t90 68.5t106.5 42t128 16.5h256v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
+<glyph unicode="&#xf075;" horiz-adv-x="1792" d="M1792 640q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22q-17 -2 -30.5 9t-17.5 29v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281 q0 130 71 248.5t191 204.5t286 136.5t348 50.5q244 0 450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="&#xf076;" d="M1536 704v-128q0 -201 -98.5 -362t-274 -251.5t-395.5 -90.5t-395.5 90.5t-274 251.5t-98.5 362v128q0 26 19 45t45 19h384q26 0 45 -19t19 -45v-128q0 -52 23.5 -90t53.5 -57t71 -30t64 -13t44 -2t44 2t64 13t71 30t53.5 57t23.5 90v128q0 26 19 45t45 19h384 q26 0 45 -19t19 -45zM512 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45zM1536 1344v-384q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h384q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf077;" horiz-adv-x="1664" d="M1611 320q0 -53 -37 -90l-75 -75q-38 -38 -91 -38q-54 0 -90 38l-486 485l-486 -485q-36 -38 -90 -38t-90 38l-75 75q-38 36 -38 90q0 53 38 91l651 651q37 37 90 37q52 0 91 -37l650 -651q38 -38 38 -91z" />
+<glyph unicode="&#xf078;" horiz-adv-x="1664" d="M1611 832q0 -53 -37 -90l-651 -651q-38 -38 -91 -38q-54 0 -90 38l-651 651q-38 36 -38 90q0 53 38 91l74 75q39 37 91 37q53 0 90 -37l486 -486l486 486q37 37 90 37q52 0 91 -37l75 -75q37 -39 37 -91z" />
+<glyph unicode="&#xf079;" horiz-adv-x="1920" d="M1280 32q0 -13 -9.5 -22.5t-22.5 -9.5h-960q-8 0 -13.5 2t-9 7t-5.5 8t-3 11.5t-1 11.5v13v11v160v416h-192q-26 0 -45 19t-19 45q0 24 15 41l320 384q19 22 49 22t49 -22l320 -384q15 -17 15 -41q0 -26 -19 -45t-45 -19h-192v-384h576q16 0 25 -11l160 -192q7 -11 7 -21 zM1920 448q0 -24 -15 -41l-320 -384q-20 -23 -49 -23t-49 23l-320 384q-15 17 -15 41q0 26 19 45t45 19h192v384h-576q-16 0 -25 12l-160 192q-7 9 -7 20q0 13 9.5 22.5t22.5 9.5h960q8 0 13.5 -2t9 -7t5.5 -8t3 -11.5t1 -11.5v-13v-11v-160v-416h192q26 0 45 -19t19 -45z " />
+<glyph unicode="&#xf07a;" horiz-adv-x="1664" d="M640 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1536 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM1664 1088v-512q0 -24 -16 -42.5t-41 -21.5 l-1044 -122q1 -7 4.5 -21.5t6 -26.5t2.5 -22q0 -16 -24 -64h920q26 0 45 -19t19 -45t-19 -45t-45 -19h-1024q-26 0 -45 19t-19 45q0 14 11 39.5t29.5 59.5t20.5 38l-177 823h-204q-26 0 -45 19t-19 45t19 45t45 19h256q16 0 28.5 -6.5t20 -15.5t13 -24.5t7.5 -26.5 t5.5 -29.5t4.5 -25.5h1201q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf07b;" horiz-adv-x="1664" d="M1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf07c;" horiz-adv-x="1920" d="M1879 584q0 -31 -31 -66l-336 -396q-43 -51 -120.5 -86.5t-143.5 -35.5h-1088q-34 0 -60.5 13t-26.5 43q0 31 31 66l336 396q43 51 120.5 86.5t143.5 35.5h1088q34 0 60.5 -13t26.5 -43zM1536 928v-160h-832q-94 0 -197 -47.5t-164 -119.5l-337 -396l-5 -6q0 4 -0.5 12.5 t-0.5 12.5v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf07d;" horiz-adv-x="768" d="M704 1216q0 -26 -19 -45t-45 -19h-128v-1024h128q26 0 45 -19t19 -45t-19 -45l-256 -256q-19 -19 -45 -19t-45 19l-256 256q-19 19 -19 45t19 45t45 19h128v1024h-128q-26 0 -45 19t-19 45t19 45l256 256q19 19 45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf07e;" horiz-adv-x="1792" d="M1792 640q0 -26 -19 -45l-256 -256q-19 -19 -45 -19t-45 19t-19 45v128h-1024v-128q0 -26 -19 -45t-45 -19t-45 19l-256 256q-19 19 -19 45t19 45l256 256q19 19 45 19t45 -19t19 -45v-128h1024v128q0 26 19 45t45 19t45 -19l256 -256q19 -19 19 -45z" />
+<glyph unicode="&#xf080;" horiz-adv-x="1920" d="M512 512v-384h-256v384h256zM896 1024v-896h-256v896h256zM1280 768v-640h-256v640h256zM1664 1152v-1024h-256v1024h256zM1792 32v1216q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-1216q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5z M1920 1248v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf081;" d="M1280 958q0 13 -9.5 22.5t-22.5 9.5q-5 0 -15 -4q20 34 20 55q0 13 -9.5 22.5t-22.5 9.5q-7 0 -17 -5q-60 -34 -97 -43q-65 63 -154 63q-98 0 -164.5 -72.5t-64.5 -169.5v-12q-107 14 -187.5 64t-156.5 139q-10 12 -28 12q-26 0 -41 -50.5t-15 -86.5q0 -62 29 -117 q-13 -2 -21.5 -11.5t-8.5 -22.5q0 -112 81 -185q-12 -8 -12 -25q0 -6 1 -9q15 -51 50.5 -91.5t84.5 -60.5q-77 -43 -165 -43q-8 0 -24 1.5t-23 1.5q-13 0 -22.5 -9.5t-9.5 -22.5q0 -17 14 -26q63 -47 150 -73.5t170 -26.5q130 0 248 58q166 79 256 232.5t88 339.5v12 q27 22 62.5 63t35.5 61zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf082;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-350q-2 0 -2 1v671h177q31 0 32 23l12 164q2 15 -8 25q-10 12 -24 12h-189v72q0 44 11.5 57t54.5 13q57 0 117 -13q13 -3 26 5q11 8 13 22l23 166q2 12 -5.5 22.5t-19.5 13.5 q-93 26 -197 26q-311 0 -311 -299v-85h-95q-13 0 -23 -10.5t-10 -24.5v-172q0 -8 5.5 -12t10 -4.5t17.5 -0.5h95v-671l10 -1h-330q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" />
+<glyph unicode="&#xf083;" horiz-adv-x="1792" d="M928 704q0 14 -9 23t-23 9q-66 0 -113 -47t-47 -113q0 -14 9 -23t23 -9t23 9t9 23q0 40 28 68t68 28q14 0 23 9t9 23zM1152 574q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181zM128 0h1536v128h-1536v-128zM1280 574q0 159 -112.5 271.5 t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5t271.5 -112.5t271.5 112.5t112.5 271.5zM256 1216h384v128h-384v-128zM128 1024h1536v118v138h-828l-64 -128h-644v-128zM1792 1280v-1280q0 -53 -37.5 -90.5t-90.5 -37.5h-1536q-53 0 -90.5 37.5t-37.5 90.5v1280 q0 53 37.5 90.5t90.5 37.5h1536q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf084;" horiz-adv-x="1792" d="M832 1024q0 80 -56 136t-136 56t-136 -56t-56 -136q0 -42 19 -83q-41 19 -83 19q-80 0 -136 -56t-56 -136t56 -136t136 -56t136 56t56 136q0 42 -19 83q41 -19 83 -19q80 0 136 56t56 136zM1683 320q0 -17 -49 -66t-66 -49q-9 0 -28.5 16t-36.5 33t-38.5 40t-24.5 26 l-96 -96l220 -220q28 -28 28 -68q0 -42 -39 -81t-81 -39q-40 0 -68 28l-671 671q-176 -131 -365 -131q-163 0 -265.5 102.5t-102.5 265.5q0 160 95 313t248 248t313 95q163 0 265.5 -102.5t102.5 -265.5q0 -189 -131 -365l355 -355l96 96q-3 3 -26 24.5t-40 38.5t-33 36.5 t-16 28.5q0 17 49 66t66 49q13 0 23 -10q6 -6 46 -44.5t82 -79.5t86.5 -86t73 -78t28.5 -41z" />
+<glyph unicode="&#xf085;" horiz-adv-x="1920" d="M896 640q0 106 -75 181t-181 75t-181 -75t-75 -181t75 -181t181 -75t181 75t75 181zM1664 128q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1664 1152q0 52 -38 90t-90 38t-90 -38t-38 -90q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1280 731v-185q0 -10 -7 -19.5t-16 -10.5l-155 -24q-11 -35 -32 -76q34 -48 90 -115q7 -10 7 -20q0 -12 -7 -19q-23 -30 -82.5 -89.5t-78.5 -59.5q-11 0 -21 7l-115 90q-37 -19 -77 -31q-11 -108 -23 -155q-7 -24 -30 -24h-186q-11 0 -20 7.5t-10 17.5 l-23 153q-34 10 -75 31l-118 -89q-7 -7 -20 -7q-11 0 -21 8q-144 133 -144 160q0 9 7 19q10 14 41 53t47 61q-23 44 -35 82l-152 24q-10 1 -17 9.5t-7 19.5v185q0 10 7 19.5t16 10.5l155 24q11 35 32 76q-34 48 -90 115q-7 11 -7 20q0 12 7 20q22 30 82 89t79 59q11 0 21 -7 l115 -90q34 18 77 32q11 108 23 154q7 24 30 24h186q11 0 20 -7.5t10 -17.5l23 -153q34 -10 75 -31l118 89q8 7 20 7q11 0 21 -8q144 -133 144 -160q0 -9 -7 -19q-12 -16 -42 -54t-45 -60q23 -48 34 -82l152 -23q10 -2 17 -10.5t7 -19.5zM1920 198v-140q0 -16 -149 -31 q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20 t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31zM1920 1222v-140q0 -16 -149 -31q-12 -27 -30 -52q51 -113 51 -138q0 -4 -4 -7q-122 -71 -124 -71q-8 0 -46 47t-52 68 q-20 -2 -30 -2t-30 2q-14 -21 -52 -68t-46 -47q-2 0 -124 71q-4 3 -4 7q0 25 51 138q-18 25 -30 52q-149 15 -149 31v140q0 16 149 31q13 29 30 52q-51 113 -51 138q0 4 4 7q4 2 35 20t59 34t30 16q8 0 46 -46.5t52 -67.5q20 2 30 2t30 -2q51 71 92 112l6 2q4 0 124 -70 q4 -3 4 -7q0 -25 -51 -138q17 -23 30 -52q149 -15 149 -31z" />
+<glyph unicode="&#xf086;" horiz-adv-x="1792" d="M1408 768q0 -139 -94 -257t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224 q0 139 94 257t256.5 186.5t353.5 68.5t353.5 -68.5t256.5 -186.5t94 -257zM1792 512q0 -120 -71 -224.5t-195 -176.5q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7 q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230z" />
+<glyph unicode="&#xf087;" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 768q0 51 -39 89.5t-89 38.5h-352q0 58 48 159.5t48 160.5q0 98 -32 145t-128 47q-26 -26 -38 -85t-30.5 -125.5t-59.5 -109.5q-22 -23 -77 -91q-4 -5 -23 -30t-31.5 -41t-34.5 -42.5 t-40 -44t-38.5 -35.5t-40 -27t-35.5 -9h-32v-640h32q13 0 31.5 -3t33 -6.5t38 -11t35 -11.5t35.5 -12.5t29 -10.5q211 -73 342 -73h121q192 0 192 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5q32 1 53.5 47t21.5 81zM1536 769 q0 -89 -49 -163q9 -33 9 -69q0 -77 -38 -144q3 -21 3 -43q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5h-36h-93q-96 0 -189.5 22.5t-216.5 65.5q-116 40 -138 40h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h274q36 24 137 155q58 75 107 128 q24 25 35.5 85.5t30.5 126.5t62 108q39 37 90 37q84 0 151 -32.5t102 -101.5t35 -186q0 -93 -48 -192h176q104 0 180 -76t76 -179z" />
+<glyph unicode="&#xf088;" d="M256 1088q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 512q0 35 -21.5 81t-53.5 47q15 17 25 47.5t10 55.5q0 69 -53 119q18 32 18 69t-17.5 73.5t-47.5 52.5q5 30 5 56q0 85 -49 126t-136 41h-128q-131 0 -342 -73q-5 -2 -29 -10.5 t-35.5 -12.5t-35 -11.5t-38 -11t-33 -6.5t-31.5 -3h-32v-640h32q16 0 35.5 -9t40 -27t38.5 -35.5t40 -44t34.5 -42.5t31.5 -41t23 -30q55 -68 77 -91q41 -43 59.5 -109.5t30.5 -125.5t38 -85q96 0 128 47t32 145q0 59 -48 160.5t-48 159.5h352q50 0 89 38.5t39 89.5z M1536 511q0 -103 -76 -179t-180 -76h-176q48 -99 48 -192q0 -118 -35 -186q-35 -69 -102 -101.5t-151 -32.5q-51 0 -90 37q-34 33 -54 82t-25.5 90.5t-17.5 84.5t-31 64q-48 50 -107 127q-101 131 -137 155h-274q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5 h288q22 0 138 40q128 44 223 66t200 22h112q140 0 226.5 -79t85.5 -216v-5q60 -77 60 -178q0 -22 -3 -43q38 -67 38 -144q0 -36 -9 -69q49 -74 49 -163z" />
+<glyph unicode="&#xf089;" horiz-adv-x="896" d="M832 1504v-1339l-449 -236q-22 -12 -40 -12q-21 0 -31.5 14.5t-10.5 35.5q0 6 2 20l86 500l-364 354q-25 27 -25 48q0 37 56 46l502 73l225 455q19 41 49 41z" />
+<glyph unicode="&#xf08a;" horiz-adv-x="1792" d="M1664 940q0 81 -21.5 143t-55 98.5t-81.5 59.5t-94 31t-98 8t-112 -25.5t-110.5 -64t-86.5 -72t-60 -61.5q-18 -22 -49 -22t-49 22q-24 28 -60 61.5t-86.5 72t-110.5 64t-112 25.5t-98 -8t-94 -31t-81.5 -59.5t-55 -98.5t-21.5 -143q0 -168 187 -355l581 -560l580 559 q188 188 188 356zM1792 940q0 -221 -229 -450l-623 -600q-18 -18 -44 -18t-44 18l-624 602q-10 8 -27.5 26t-55.5 65.5t-68 97.5t-53.5 121t-23.5 138q0 220 127 344t351 124q62 0 126.5 -21.5t120 -58t95.5 -68.5t76 -68q36 36 76 68t95.5 68.5t120 58t126.5 21.5 q224 0 351 -124t127 -344z" />
+<glyph unicode="&#xf08b;" horiz-adv-x="1664" d="M640 96q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-119 0 -203.5 84.5t-84.5 203.5v704q0 119 84.5 203.5t203.5 84.5h320q13 0 22.5 -9.5t9.5 -22.5q0 -4 1 -20t0.5 -26.5t-3 -23.5t-10 -19.5t-20.5 -6.5h-320q-66 0 -113 -47t-47 -113v-704 q0 -66 47 -113t113 -47h288h11h13t11.5 -1t11.5 -3t8 -5.5t7 -9t2 -13.5zM1568 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45z" />
+<glyph unicode="&#xf08c;" d="M512 160v640q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-640q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM503 1028q0 51 -36 87.5t-88 36.5q-51 0 -87 -36.5t-36 -87.5t36 -87.5t87 -36.5q52 0 88 36.5t36 87.5zM1280 160v435 q0 127 -73.5 192.5t-202.5 65.5q-90 0 -158 -45q-12 -8 -14 -12q0 36 -35 36h-176q-14 0 -29.5 -7.5t-15.5 -20.5v-644q0 -13 15.5 -22.5t29.5 -9.5h182q12 0 20.5 9.5t8.5 22.5v349q0 140 114 140q49 0 63.5 -22.5t14.5 -73.5v-393q0 -13 12 -22.5t26 -9.5h186 q13 0 22.5 9.5t9.5 22.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf08d;" horiz-adv-x="1152" d="M480 672v448q0 14 -9 23t-23 9t-23 -9t-9 -23v-448q0 -14 9 -23t23 -9t23 9t9 23zM1152 320q0 -26 -19 -45t-45 -19h-429l-51 -483q-2 -12 -10.5 -20.5t-20.5 -8.5h-1q-27 0 -32 27l-76 485h-404q-26 0 -45 19t-19 45q0 123 78.5 221.5t177.5 98.5v512q-52 0 -90 38 t-38 90t38 90t90 38h640q52 0 90 -38t38 -90t-38 -90t-90 -38v-512q99 0 177.5 -98.5t78.5 -221.5z" />
+<glyph unicode="&#xf08e;" horiz-adv-x="1792" d="M1408 608v-320q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h704q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-704q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v320 q0 14 9 23t23 9h64q14 0 23 -9t9 -23zM1792 1472v-512q0 -26 -19 -45t-45 -19t-45 19l-176 176l-652 -652q-10 -10 -23 -10t-23 10l-114 114q-10 10 -10 23t10 23l652 652l-176 176q-19 19 -19 45t19 45t45 19h512q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf090;" d="M1184 640q0 -26 -19 -45l-544 -544q-19 -19 -45 -19t-45 19t-19 45v288h-448q-26 0 -45 19t-19 45v384q0 26 19 45t45 19h448v288q0 26 19 45t45 19t45 -19l544 -544q19 -19 19 -45zM1536 992v-704q0 -119 -84.5 -203.5t-203.5 -84.5h-320q-13 0 -22.5 9.5t-9.5 22.5 q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q66 0 113 47t47 113v704q0 66 -47 113t-113 47h-288h-11h-13t-11.5 1t-11.5 3t-8 5.5t-7 9t-2 13.5q0 4 -1 20t-0.5 26.5t3 23.5t10 19.5t20.5 6.5h320q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf091;" horiz-adv-x="1664" d="M458 653q-74 162 -74 371h-256v-96q0 -78 94.5 -162t235.5 -113zM1536 928v96h-256q0 -209 -74 -371q141 29 235.5 113t94.5 162zM1664 1056v-128q0 -71 -41.5 -143t-112 -130t-173 -97.5t-215.5 -44.5q-42 -54 -95 -95q-38 -34 -52.5 -72.5t-14.5 -89.5q0 -54 30.5 -91 t97.5 -37q75 0 133.5 -45.5t58.5 -114.5v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23v64q0 69 58.5 114.5t133.5 45.5q67 0 97.5 37t30.5 91q0 51 -14.5 89.5t-52.5 72.5q-53 41 -95 95q-113 5 -215.5 44.5t-173 97.5t-112 130t-41.5 143v128q0 40 28 68t68 28h288v96 q0 66 47 113t113 47h576q66 0 113 -47t47 -113v-96h288q40 0 68 -28t28 -68z" />
+<glyph unicode="&#xf092;" d="M582 228q0 -66 -93 -66q-107 0 -107 63q0 64 98 64q102 0 102 -61zM546 694q0 -85 -74 -85q-77 0 -77 84q0 90 77 90q36 0 55 -26t19 -63zM712 769v125q-78 -29 -135 -29q-50 29 -110 29q-86 0 -145 -57t-59 -143q0 -50 29.5 -102t73.5 -67v-3q-38 -17 -38 -85 q0 -52 41 -77v-3q-113 -37 -113 -139q0 -60 36 -98t84 -51t107 -13q224 0 224 187q0 48 -25.5 78t-62.5 42.5t-74 21.5t-62.5 23.5t-25.5 39.5q0 44 49 52q77 15 122 70t45 134q0 24 -10 52q30 7 49 13zM771 350h137q-2 20 -2 90v372q0 59 2 76h-137q3 -26 3 -79v-377 q0 -55 -3 -82zM1280 366v121q-30 -21 -68 -21q-53 0 -53 82v225h52q9 0 26.5 -1t26.5 -1v117h-105q0 82 3 102h-140q4 -24 4 -55v-47h-60v-117q36 3 37 3q4 0 11.5 -0.5t11.5 -0.5v-2h-2v-217q0 -37 2.5 -64t11.5 -56.5t24.5 -48.5t43.5 -31t66 -12q64 0 108 24zM924 1072 q0 36 -24 63.5t-60 27.5t-60.5 -27t-24.5 -64q0 -36 25 -62.5t60 -26.5t59.5 27t24.5 62zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf093;" horiz-adv-x="1664" d="M1664 480v-576q0 -13 -9.5 -22.5t-22.5 -9.5h-1600q-13 0 -22.5 9.5t-9.5 22.5v576q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5v-352h1152v352q0 13 9.5 22.5t22.5 9.5h192q13 0 22.5 -9.5t9.5 -22.5zM1344 832q0 -26 -19 -45t-45 -19h-256v-448 q0 -26 -19 -45t-45 -19h-256q-26 0 -45 19t-19 45v448h-256q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf094;" d="M1407 710q0 44 -7 113.5t-18 96.5q-12 30 -17 44t-9 36.5t-4 48.5q0 23 5 68.5t5 67.5q0 37 -10 55q-4 1 -13 1q-19 0 -58 -4.5t-59 -4.5q-60 0 -176 24t-175 24q-43 0 -94.5 -11.5t-85 -23.5t-89.5 -34q-137 -54 -202 -103q-96 -73 -159.5 -189.5t-88 -236t-24.5 -248.5 q0 -40 12.5 -120t12.5 -121q0 -23 -11 -66.5t-11 -65.5t12 -36.5t34 -14.5q24 0 72.5 11t73.5 11q57 0 169.5 -15.5t169.5 -15.5q181 0 284 36q129 45 235.5 152.5t166 245.5t59.5 275zM1535 712q0 -165 -70 -327.5t-196 -288t-281 -180.5q-124 -44 -326 -44 q-57 0 -170 14.5t-169 14.5q-24 0 -72.5 -14.5t-73.5 -14.5q-73 0 -123.5 55.5t-50.5 128.5q0 24 11 68t11 67q0 40 -12.5 120.5t-12.5 121.5q0 111 18 217.5t54.5 209.5t100.5 194t150 156q78 59 232 120q194 78 316 78q60 0 175.5 -24t173.5 -24q19 0 57 5t58 5 q81 0 118 -50.5t37 -134.5q0 -23 -5 -68t-5 -68q0 -10 1 -18.5t3 -17t4 -13.5t6.5 -16t6.5 -17q16 -40 25 -118.5t9 -136.5z" />
+<glyph unicode="&#xf095;" horiz-adv-x="1408" d="M1408 296q0 -27 -10 -70.5t-21 -68.5q-21 -50 -122 -106q-94 -51 -186 -51q-27 0 -52.5 3.5t-57.5 12.5t-47.5 14.5t-55.5 20.5t-49 18q-98 35 -175 83q-128 79 -264.5 215.5t-215.5 264.5q-48 77 -83 175q-3 9 -18 49t-20.5 55.5t-14.5 47.5t-12.5 57.5t-3.5 52.5 q0 92 51 186q56 101 106 122q25 11 68.5 21t70.5 10q14 0 21 -3q18 -6 53 -76q11 -19 30 -54t35 -63.5t31 -53.5q3 -4 17.5 -25t21.5 -35.5t7 -28.5q0 -20 -28.5 -50t-62 -55t-62 -53t-28.5 -46q0 -9 5 -22.5t8.5 -20.5t14 -24t11.5 -19q76 -137 174 -235t235 -174 q2 -1 19 -11.5t24 -14t20.5 -8.5t22.5 -5q18 0 46 28.5t53 62t55 62t50 28.5q14 0 28.5 -7t35.5 -21.5t25 -17.5q25 -15 53.5 -31t63.5 -35t54 -30q70 -35 76 -53q3 -7 3 -21z" />
+<glyph unicode="&#xf096;" horiz-adv-x="1664" d="M1120 1280h-832q-66 0 -113 -47t-47 -113v-832q0 -66 47 -113t113 -47h832q66 0 113 47t47 113v832q0 66 -47 113t-113 47zM1408 1120v-832q0 -119 -84.5 -203.5t-203.5 -84.5h-832q-119 0 -203.5 84.5t-84.5 203.5v832q0 119 84.5 203.5t203.5 84.5h832 q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf097;" horiz-adv-x="1280" d="M1152 1280h-1024v-1242l423 406l89 85l89 -85l423 -406v1242zM1164 1408q23 0 44 -9q33 -13 52.5 -41t19.5 -62v-1289q0 -34 -19.5 -62t-52.5 -41q-19 -8 -44 -8q-48 0 -83 32l-441 424l-441 -424q-36 -33 -83 -33q-23 0 -44 9q-33 13 -52.5 41t-19.5 62v1289 q0 34 19.5 62t52.5 41q21 9 44 9h1048z" />
+<glyph unicode="&#xf098;" d="M1280 343q0 11 -2 16q-3 8 -38.5 29.5t-88.5 49.5l-53 29q-5 3 -19 13t-25 15t-21 5q-18 0 -47 -32.5t-57 -65.5t-44 -33q-7 0 -16.5 3.5t-15.5 6.5t-17 9.5t-14 8.5q-99 55 -170.5 126.5t-126.5 170.5q-2 3 -8.5 14t-9.5 17t-6.5 15.5t-3.5 16.5q0 13 20.5 33.5t45 38.5 t45 39.5t20.5 36.5q0 10 -5 21t-15 25t-13 19q-3 6 -15 28.5t-25 45.5t-26.5 47.5t-25 40.5t-16.5 18t-16 2q-48 0 -101 -22q-46 -21 -80 -94.5t-34 -130.5q0 -16 2.5 -34t5 -30.5t9 -33t10 -29.5t12.5 -33t11 -30q60 -164 216.5 -320.5t320.5 -216.5q6 -2 30 -11t33 -12.5 t29.5 -10t33 -9t30.5 -5t34 -2.5q57 0 130.5 34t94.5 80q22 53 22 101zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf099;" horiz-adv-x="1920" d="M1875 1202q0 -10 -5 -18q-64 -104 -179 -190v-33q4 -227 -100 -457q-134 -297 -397.5 -464.5t-591.5 -167.5q-265 0 -500 122q-64 33 -87 50q-15 12 -15 27q0 13 9.5 22.5t22.5 9.5q14 0 44 -2.5t45 -2.5q204 0 375 106q-103 24 -181 96t-111 173q-2 8 -2 11q0 12 9 21.5 t22 9.5q5 0 14 -2t12 -2q-89 55 -142 147t-53 196q0 15 11.5 25.5t27.5 10.5q10 0 35 -11.5t30 -13.5q-92 110 -92 256q0 51 14.5 108t40.5 95q10 16 25 16q16 0 27 -12q76 -84 110 -115q123 -111 276 -177.5t317 -80.5q-4 21 -4 49q0 167 118.5 285.5t285.5 118.5 q163 0 282 -114q95 20 209 82q8 5 16 5q13 0 22.5 -9.5t9.5 -22.5q0 -24 -28 -73t-51 -76q7 2 30 10.5t43 16t24 7.5q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf09a;" horiz-adv-x="768" d="M560 1125q-49 0 -62 -15.5t-13 -66.5v-88h217q16 0 27 -12q11 -13 10 -29l-14 -200q-2 -15 -12.5 -25.5t-25.5 -10.5h-202v-768q0 -16 -11 -27t-26 -11h-250q-16 0 -27 11t-11 27v768h-122q-16 0 -27 11.5t-11 27.5v200q0 16 11 27t27 11h122v103q0 177 88 263.5 t267 86.5q120 0 225 -30q14 -4 22 -16t6 -26l-27 -195q-2 -16 -16 -26q-14 -9 -30 -6q-76 16 -135 16z" />
+<glyph unicode="&#xf09b;" d="M1408 640q0 130 -51 248.5t-136.5 204t-204 136.5t-248.5 51t-248.5 -51t-204 -136.5t-136.5 -204t-51 -248.5q0 -209 124.5 -378.5t323.5 -231.5v169q-54 -7 -69 -7q-110 0 -153 100q-15 38 -36 63q-5 6 -21 19t-28.5 24t-12.5 16q0 12 28 12q29 0 51.5 -14.5t38 -35 t31.5 -41.5t40.5 -35.5t56.5 -14.5q42 0 81 14q16 57 63 89q-166 16 -246 83.5t-80 224.5q0 118 73 198q-14 42 -14 84q0 58 27 109q57 0 101 -19.5t101 -60.5q76 18 169 18q80 0 153 -16q57 40 100.5 59t99.5 19q27 -51 27 -109q0 -43 -14 -83q73 -82 73 -199 q0 -157 -80 -225.5t-245 -83.5q69 -47 69 -131v-226q199 62 323.5 231.5t124.5 378.5zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf09c;" horiz-adv-x="1664" d="M704 160q0 6 -15 57t-35 115.5t-20 65.5q32 16 51 47t19 67q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5q0 -36 19 -66.5t51 -47.5q0 -2 -20 -66t-35 -115t-15 -57q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1664 960v-256q0 -26 -19 -45t-45 -19 h-64q-26 0 -45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-192h96q40 0 68 -28t28 -68v-576q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v576q0 40 28 68t68 28h672v192q0 185 131.5 316.5t316.5 131.5t316.5 -131.5t131.5 -316.5z" />
+<glyph unicode="&#xf09d;" horiz-adv-x="1920" d="M1760 1408q66 0 113 -47t47 -113v-1216q0 -66 -47 -113t-113 -47h-1600q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1600zM160 1280q-13 0 -22.5 -9.5t-9.5 -22.5v-224h1664v224q0 13 -9.5 22.5t-22.5 9.5h-1600zM1760 0q13 0 22.5 9.5t9.5 22.5v608h-1664v-608 q0 -13 9.5 -22.5t22.5 -9.5h1600zM256 128v128h256v-128h-256zM640 128v128h384v-128h-384z" />
+<glyph unicode="&#xf09e;" horiz-adv-x="1408" d="M384 192q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM896 69q2 -28 -17 -48q-18 -21 -47 -21h-135q-25 0 -43 16.5t-20 41.5q-22 229 -184.5 391.5t-391.5 184.5q-25 2 -41.5 20t-16.5 43v135q0 29 21 47q17 17 43 17h5q160 -13 306 -80.5 t259 -181.5q114 -113 181.5 -259t80.5 -306zM1408 67q2 -27 -18 -47q-18 -20 -46 -20h-143q-26 0 -44.5 17.5t-19.5 42.5q-12 215 -101 408.5t-231.5 336t-336 231.5t-408.5 102q-25 1 -42.5 19.5t-17.5 43.5v143q0 28 20 46q18 18 44 18h3q262 -13 501.5 -120t425.5 -294 q187 -186 294 -425.5t120 -501.5z" />
+<glyph unicode="&#xf0a0;" d="M1040 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1296 320q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5zM1408 160v320q0 13 -9.5 22.5t-22.5 9.5 h-1216q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h1216q13 0 22.5 9.5t9.5 22.5zM178 640h1180l-157 482q-4 13 -16 21.5t-26 8.5h-782q-14 0 -26 -8.5t-16 -21.5zM1536 480v-320q0 -66 -47 -113t-113 -47h-1216q-66 0 -113 47t-47 113v320q0 25 16 75 l197 606q17 53 63 86t101 33h782q55 0 101 -33t63 -86l197 -606q16 -50 16 -75z" />
+<glyph unicode="&#xf0a1;" horiz-adv-x="1792" d="M1664 896q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5v-384q0 -52 -38 -90t-90 -38q-417 347 -812 380q-58 -19 -91 -66t-31 -100.5t40 -92.5q-20 -33 -23 -65.5t6 -58t33.5 -55t48 -50t61.5 -50.5q-29 -58 -111.5 -83t-168.5 -11.5t-132 55.5q-7 23 -29.5 87.5 t-32 94.5t-23 89t-15 101t3.5 98.5t22 110.5h-122q-66 0 -113 47t-47 113v192q0 66 47 113t113 47h480q435 0 896 384q52 0 90 -38t38 -90v-384zM1536 292v954q-394 -302 -768 -343v-270q377 -42 768 -341z" />
+<glyph unicode="&#xf0a2;" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM183 128h1298q-164 181 -246.5 411.5t-82.5 484.5q0 256 -320 256t-320 -256q0 -254 -82.5 -484.5t-246.5 -411.5zM1664 128q0 -52 -38 -90t-90 -38 h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" />
+<glyph unicode="&#xf0a3;" d="M1376 640l138 -135q30 -28 20 -70q-12 -41 -52 -51l-188 -48l53 -186q12 -41 -19 -70q-29 -31 -70 -19l-186 53l-48 -188q-10 -40 -51 -52q-12 -2 -19 -2q-31 0 -51 22l-135 138l-135 -138q-28 -30 -70 -20q-41 11 -51 52l-48 188l-186 -53q-41 -12 -70 19q-31 29 -19 70 l53 186l-188 48q-40 10 -52 51q-10 42 20 70l138 135l-138 135q-30 28 -20 70q12 41 52 51l188 48l-53 186q-12 41 19 70q29 31 70 19l186 -53l48 188q10 41 51 51q41 12 70 -19l135 -139l135 139q29 30 70 19q41 -10 51 -51l48 -188l186 53q41 12 70 -19q31 -29 19 -70 l-53 -186l188 -48q40 -10 52 -51q10 -42 -20 -70z" />
+<glyph unicode="&#xf0a4;" horiz-adv-x="1792" d="M256 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1664 768q0 51 -39 89.5t-89 38.5h-576q0 20 15 48.5t33 55t33 68t15 84.5q0 67 -44.5 97.5t-115.5 30.5q-24 0 -90 -139q-24 -44 -37 -65q-40 -64 -112 -145q-71 -81 -101 -106 q-69 -57 -140 -57h-32v-640h32q72 0 167 -32t193.5 -64t179.5 -32q189 0 189 167q0 26 -5 56q30 16 47.5 52.5t17.5 73.5t-18 69q53 50 53 119q0 25 -10 55.5t-25 47.5h331q52 0 90 38t38 90zM1792 769q0 -105 -75.5 -181t-180.5 -76h-169q-4 -62 -37 -119q3 -21 3 -43 q0 -101 -60 -178q1 -139 -85 -219.5t-227 -80.5q-133 0 -322 69q-164 59 -223 59h-288q-53 0 -90.5 37.5t-37.5 90.5v640q0 53 37.5 90.5t90.5 37.5h288q10 0 21.5 4.5t23.5 14t22.5 18t24 22.5t20.5 21.5t19 21.5t14 17q65 74 100 129q13 21 33 62t37 72t40.5 63t55 49.5 t69.5 17.5q125 0 206.5 -67t81.5 -189q0 -68 -22 -128h374q104 0 180 -76t76 -179z" />
+<glyph unicode="&#xf0a5;" horiz-adv-x="1792" d="M1376 128h32v640h-32q-35 0 -67 11.5t-64 38.5t-48 44t-50 55q-2 3 -3.5 4.5t-4 4.5t-4.5 5q-72 81 -112 145q-14 22 -38 68q-1 3 -10.5 22.5t-18.5 36t-20 35.5t-21.5 30.5t-18.5 11.5q-71 0 -115.5 -30.5t-44.5 -97.5q0 -43 15 -84.5t33 -68t33 -55t15 -48.5h-576 q-50 0 -89 -38.5t-39 -89.5q0 -52 38 -90t90 -38h331q-15 -17 -25 -47.5t-10 -55.5q0 -69 53 -119q-18 -32 -18 -69t17.5 -73.5t47.5 -52.5q-4 -24 -4 -56q0 -85 48.5 -126t135.5 -41q84 0 183 32t194 64t167 32zM1664 192q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45 t45 -19t45 19t19 45zM1792 768v-640q0 -53 -37.5 -90.5t-90.5 -37.5h-288q-59 0 -223 -59q-190 -69 -317 -69q-142 0 -230 77.5t-87 217.5l1 5q-61 76 -61 178q0 22 3 43q-33 57 -37 119h-169q-105 0 -180.5 76t-75.5 181q0 103 76 179t180 76h374q-22 60 -22 128 q0 122 81.5 189t206.5 67q38 0 69.5 -17.5t55 -49.5t40.5 -63t37 -72t33 -62q35 -55 100 -129q2 -3 14 -17t19 -21.5t20.5 -21.5t24 -22.5t22.5 -18t23.5 -14t21.5 -4.5h288q53 0 90.5 -37.5t37.5 -90.5z" />
+<glyph unicode="&#xf0a6;" d="M1280 -64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 700q0 189 -167 189q-26 0 -56 -5q-16 30 -52.5 47.5t-73.5 17.5t-69 -18q-50 53 -119 53q-25 0 -55.5 -10t-47.5 -25v331q0 52 -38 90t-90 38q-51 0 -89.5 -39t-38.5 -89v-576 q-20 0 -48.5 15t-55 33t-68 33t-84.5 15q-67 0 -97.5 -44.5t-30.5 -115.5q0 -24 139 -90q44 -24 65 -37q64 -40 145 -112q81 -71 106 -101q57 -69 57 -140v-32h640v32q0 72 32 167t64 193.5t32 179.5zM1536 705q0 -133 -69 -322q-59 -164 -59 -223v-288q0 -53 -37.5 -90.5 t-90.5 -37.5h-640q-53 0 -90.5 37.5t-37.5 90.5v288q0 10 -4.5 21.5t-14 23.5t-18 22.5t-22.5 24t-21.5 20.5t-21.5 19t-17 14q-74 65 -129 100q-21 13 -62 33t-72 37t-63 40.5t-49.5 55t-17.5 69.5q0 125 67 206.5t189 81.5q68 0 128 -22v374q0 104 76 180t179 76 q105 0 181 -75.5t76 -180.5v-169q62 -4 119 -37q21 3 43 3q101 0 178 -60q139 1 219.5 -85t80.5 -227z" />
+<glyph unicode="&#xf0a7;" d="M1408 576q0 84 -32 183t-64 194t-32 167v32h-640v-32q0 -46 -25 -91t-52 -72t-72 -66q-9 -8 -14 -12q-81 -72 -145 -112q-22 -14 -68 -38q-3 -1 -22.5 -10.5t-36 -18.5t-35.5 -20t-30.5 -21.5t-11.5 -18.5q0 -71 30.5 -115.5t97.5 -44.5q43 0 84.5 15t68 33t55 33 t48.5 15v-576q0 -50 38.5 -89t89.5 -39q52 0 90 38t38 90v331q46 -35 103 -35q69 0 119 53q32 -18 69 -18t73.5 17.5t52.5 47.5q24 -4 56 -4q85 0 126 48.5t41 135.5zM1280 1344q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1536 580q0 -142 -77.5 -230 t-217.5 -87l-5 1q-76 -61 -178 -61q-22 0 -43 3q-54 -30 -119 -37v-169q0 -105 -76 -180.5t-181 -75.5q-103 0 -179 76t-76 180v374q-54 -22 -128 -22q-121 0 -188.5 81.5t-67.5 206.5q0 38 17.5 69.5t49.5 55t63 40.5t72 37t62 33q55 35 129 100q3 2 17 14t21.5 19 t21.5 20.5t22.5 24t18 22.5t14 23.5t4.5 21.5v288q0 53 37.5 90.5t90.5 37.5h640q53 0 90.5 -37.5t37.5 -90.5v-288q0 -59 59 -223q69 -190 69 -317z" />
+<glyph unicode="&#xf0a8;" d="M1280 576v128q0 26 -19 45t-45 19h-502l189 189q19 19 19 45t-19 45l-91 91q-18 18 -45 18t-45 -18l-362 -362l-91 -91q-18 -18 -18 -45t18 -45l91 -91l362 -362q18 -18 45 -18t45 18l91 91q18 18 18 45t-18 45l-189 189h502q26 0 45 19t19 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0a9;" d="M1285 640q0 27 -18 45l-91 91l-362 362q-18 18 -45 18t-45 -18l-91 -91q-18 -18 -18 -45t18 -45l189 -189h-502q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h502l-189 -189q-19 -19 -19 -45t19 -45l91 -91q18 -18 45 -18t45 18l362 362l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0aa;" d="M1284 641q0 27 -18 45l-362 362l-91 91q-18 18 -45 18t-45 -18l-91 -91l-362 -362q-18 -18 -18 -45t18 -45l91 -91q18 -18 45 -18t45 18l189 189v-502q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v502l189 -189q19 -19 45 -19t45 19l91 91q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0ab;" d="M1284 639q0 27 -18 45l-91 91q-18 18 -45 18t-45 -18l-189 -189v502q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-502l-189 189q-19 19 -45 19t-45 -19l-91 -91q-18 -18 -18 -45t18 -45l362 -362l91 -91q18 -18 45 -18t45 18l91 91l362 362q18 18 18 45zM1536 640 q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0ac;" d="M1193 993q11 7 25 22v-1q0 -2 -9.5 -10t-11.5 -12q-1 1 -4 1zM1187 992q-1 1 -2.5 3t-1.5 3q3 -2 10 -5q-6 -4 -6 -1zM728 1175q-16 2 -26 5q1 0 6.5 -1t10.5 -2t9 -2zM773 1212q7 4 13.5 2.5t7.5 -7.5q-5 3 -21 5zM765 1206l-3 2q-2 3 -5.5 5t-4.5 2q2 -1 21 -3 q-6 -4 -8 -6zM663 1290v2q1 -2 3 -5.5t3 -5.5zM558 1250q0 -2 -1 -2l-1 2h2zM933 206v-1v1zM768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM1240 162 l5 5q-7 10 -29 12q1 12 -14 26.5t-27 15.5q0 4 -10.5 11t-17.5 8q-9 2 -27 -9q-7 -3 -4 -5q-3 3 -12 11t-16 11q-2 1 -7.5 1t-8.5 2q-1 1 -6 4.5t-7 4.5t-6.5 3t-7.5 1.5t-7.5 -2.5t-8.5 -6t-4.5 -15.5t-2.5 -14.5q-8 6 -0.5 20t1.5 20q-7 7 -21 0.5t-21 -15.5 q-1 -1 -9.5 -5.5t-11.5 -7.5q-4 -6 -9 -17.5t-6 -13.5q0 2 -2.5 6.5t-2.5 6.5q-12 -2 -16 3q5 -16 8 -17l-4 2q-1 -6 3 -15t4 -11q1 -5 -1.5 -13t-2.5 -11q0 -2 5 -11q4 -19 -2 -32q0 -1 -3.5 -7t-6.5 -11l-2 -5l-2 1q-1 1 -2 0q-1 -6 -9 -13t-10 -11q-15 -23 -9 -38 q3 -8 10 -10q3 -1 3 2q1 -9 -11 -27q1 -1 4 -3q-17 0 -10 -14q202 36 352 181h-3zM680 347q16 3 30.5 -16t22.5 -23q41 -20 59 -11q0 -9 14 -28q3 -4 6.5 -11.5t5.5 -10.5q5 -7 19 -16t19 -16q6 3 9 9q13 -35 24 -34q5 0 8 8q0 -1 -0.5 -3t-1.5 -3q7 15 5 26l6 4q5 4 5 5 q-6 6 -9 -3q-30 -14 -48 22q-2 3 -4.5 8t-5 12t-1.5 11.5t6 4.5q11 0 12.5 1.5t-2.5 6t-4 7.5q-1 4 -1.5 12.5t-1.5 12.5l-5 6q-5 6 -11.5 13.5t-7.5 9.5q-4 -10 -16.5 -8.5t-18.5 9.5q1 -2 -0.5 -6.5t-1.5 -6.5q-14 0 -17 1q1 6 3 21t4 22q1 5 5.5 13.5t8 15.5t4.5 14 t-4.5 10.5t-18.5 2.5q-20 -1 -29 -22q-1 -3 -3 -11.5t-5 -12.5t-9 -7q-8 -3 -27 -2t-26 5q-14 8 -24 30.5t-11 41.5q0 10 3 27.5t3 27t-6 26.5q3 2 10 10.5t11 11.5q2 2 5 2h5t4 2t3 6q-1 1 -4 3q-3 3 -4 3q4 -3 19 -1t19 2q0 1 22 0q17 -13 24 2q0 1 -2.5 10.5t-0.5 14.5 q5 -29 32 -10q3 -4 16.5 -6t18.5 -5q3 -2 7 -5.5t6 -5t6 -0.5t9 7q11 -17 13 -25q11 -43 20 -48q8 -2 12.5 -2t5 10.5t0 15.5t-1.5 13l-2 37q-16 3 -20 12.5t1.5 20t16.5 19.5q1 1 16.5 8t21.5 12q24 19 17 39q9 -2 11 9l-5 3q-4 3 -8 5.5t-5 1.5q11 7 2 18q5 3 8 11.5 t9 11.5q9 -14 22 -3q8 9 2 18q5 8 22 11.5t20 9.5q5 -1 7 0t2 4.5v7.5t1 8.5t3 7.5q4 6 16 10.5t14 5.5l19 12q4 4 0 4q18 -2 32 11q13 12 -5 23q2 7 -4 10.5t-16 5.5q3 1 12 0.5t12 1.5q15 11 -7 17q-20 5 -47 -13q-3 -2 -13 -12t-17 -11q15 18 5 22q8 -1 22.5 9t15.5 11 q4 2 10.5 2.5t8.5 1.5q71 25 92 -1q8 11 11 15t9.5 9t15.5 8q21 7 23 9l1 23q-12 -1 -18 8t-7 22l-6 -8q0 6 -3.5 7.5t-7.5 0.5t-9.5 -2t-7.5 0q-9 2 -19.5 15.5t-14.5 16.5q9 0 9 5q-2 5 -10 8q1 6 -2 8t-9 0q-2 12 -1 13q-6 1 -11 11t-8 10q-2 0 -4.5 -2t-5 -5.5l-5 -7 t-3.5 -5.5l-2 -2q-12 6 -24 -10q-9 1 -17 -2q15 6 2 13q-11 5 -21 2q12 5 10 14t-12 16q1 0 4 -1t4 -1q-1 5 -9.5 9.5t-19.5 9t-14 6.5q-7 5 -36 10.5t-36 1.5q-5 -3 -6 -6t1.5 -8.5t3.5 -8.5q6 -23 5 -27q-1 -3 -8.5 -8t-5.5 -12q1 -4 11.5 -10t12.5 -12q5 -13 -4 -25 q-4 -5 -15 -11t-14 -10q-5 -5 -3.5 -11.5t0.5 -9.5q1 1 1 2.5t1 2.5q0 -13 11 -22q8 -6 -16 -18q-20 -11 -20 -4q1 8 -7.5 16t-10.5 12t-3.5 19t-9.5 21q-6 4 -19 4t-18 -5q0 10 -49 30q-17 8 -58 4q7 1 0 17q-8 16 -21 12q-8 25 -4 35q2 5 9 14t9 15q1 3 15.5 6t16.5 8 q1 4 -2.5 6.5t-9.5 4.5q53 -6 63 18q5 9 3 14q0 -1 2 -1t2 -1q12 3 7 17q19 8 26 8q5 -1 11 -6t10 -5q17 -3 21.5 10t-9.5 23q7 -4 7 6q-1 13 -7 19q-3 2 -6.5 2.5t-6.5 0t-7 0.5q-1 0 -8 2q-1 -1 -2 -1h-8q-4 -2 -4 -5v-1q-1 -3 4 -6l5 -1l3 -2q-1 0 -2.5 -2.5t-2.5 -2.5 q0 -3 3 -5q-2 -1 -14 -7.5t-17 -10.5q-1 -1 -4 -2.5t-4 -2.5q-2 -1 -4 2t-4 9t-4 11.5t-4.5 10t-5.5 4.5q-12 0 -18 -17q3 10 -13 17.5t-25 7.5q20 15 -9 30l-1 1q-30 -4 -45 -7q-2 -6 3 -12q-1 -7 6 -9q0 -1 0.5 -1t0.5 -1q0 1 -0.5 1t-0.5 1q3 -1 10.5 -1.5t9.5 -1.5 q3 -1 4.5 -2l7.5 -5t5.5 -6t-2.5 -5q-2 -1 -9 -4t-12.5 -5.5t-6.5 -3.5q-3 -5 0 -16t-2 -15q-5 5 -10 18.5t-8 17.5q8 -9 -30 -6l-8 1q-4 0 -15 -2t-16 -1q-7 0 -29 6q7 17 5 25q5 0 7 2l-6 3q-3 -1 -25 -9q2 -3 8 -9.5t9 -11.5q-22 6 -27 -2q0 -1 -9 0q-25 1 -24 -7 q1 -4 9 -12q0 -9 -1 -9q-27 22 -30 23q-172 -83 -276 -248q1 -2 2.5 -11t3.5 -8.5t11 4.5q9 -9 3 -21q2 2 36 -21q56 -40 22 -53v5.5t1 6.5q-9 -1 -19 5q-3 -6 0.5 -20t11.5 -14q-8 0 -10.5 -17t-2.5 -38.5t-1 -25.5l2 -1q-3 -13 6 -37.5t24 -20.5q-4 -18 5 -21q-1 -4 0 -8 t4.5 -8.5t6 -7l7.5 -7.5l6 -6q28 -11 41 -29q4 -6 10.5 -24.5t15.5 -25.5q-2 -6 10 -21.5t11 -25.5q-1 0 -2.5 -0.5t-2.5 -0.5q3 -8 16.5 -16t16.5 -14q2 -3 2.5 -10.5t3 -12t8.5 -2.5q3 24 -26 68q-16 27 -18 31q-3 5 -5.5 16.5t-4.5 15.5q27 -9 26 -13q-5 -10 26 -52 q2 -3 10 -10t11 -12q3 -4 9.5 -14.5t10.5 -15.5q-1 0 -3 -2l-3 -3q4 -2 9 -5t8 -4.5t7.5 -5t7.5 -7.5q16 -18 20 -33q1 -4 0.5 -15.5t1.5 -16.5q2 -6 6 -11t11.5 -10t11.5 -7t14.5 -6.5t11.5 -5.5q2 -1 18 -11t25 -14q10 -4 16.5 -4.5t16 2.5t15.5 4z" />
+<glyph unicode="&#xf0ad;" horiz-adv-x="1664" d="M384 64q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1028 484l-682 -682q-37 -37 -90 -37q-52 0 -91 37l-106 108q-38 36 -38 90q0 53 38 91l681 681q39 -98 114.5 -173.5t173.5 -114.5zM1662 919q0 -39 -23 -106q-47 -134 -164.5 -217.5 t-258.5 -83.5q-185 0 -316.5 131.5t-131.5 316.5t131.5 316.5t316.5 131.5q58 0 121.5 -16.5t107.5 -46.5q16 -11 16 -28t-16 -28l-293 -169v-224l193 -107q5 3 79 48.5t135.5 81t70.5 35.5q15 0 23.5 -10t8.5 -25z" />
+<glyph unicode="&#xf0ae;" horiz-adv-x="1792" d="M1024 128h640v128h-640v-128zM640 640h1024v128h-1024v-128zM1280 1152h384v128h-384v-128zM1792 320v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 832v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19 t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45zM1792 1344v-256q0 -26 -19 -45t-45 -19h-1664q-26 0 -45 19t-19 45v256q0 26 19 45t45 19h1664q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0b0;" horiz-adv-x="1408" d="M1403 1241q17 -41 -14 -70l-493 -493v-742q0 -42 -39 -59q-13 -5 -25 -5q-27 0 -45 19l-256 256q-19 19 -19 45v486l-493 493q-31 29 -14 70q17 39 59 39h1280q42 0 59 -39z" />
+<glyph unicode="&#xf0b1;" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM1792 512v-480q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v480h672v-160q0 -26 19 -45t45 -19h320q26 0 45 19t19 45v160h672zM1024 512v-128h-256v128h256zM1792 992v-384h-1792v384q0 66 47 113t113 47h352v160q0 40 28 68 t68 28h576q40 0 68 -28t28 -68v-160h352q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf0b2;" d="M1283 995l-355 -355l355 -355l144 144q29 31 70 14q39 -17 39 -59v-448q0 -26 -19 -45t-45 -19h-448q-42 0 -59 40q-17 39 14 69l144 144l-355 355l-355 -355l144 -144q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l144 -144 l355 355l-355 355l-144 -144q-19 -19 -45 -19q-12 0 -24 5q-40 17 -40 59v448q0 26 19 45t45 19h448q42 0 59 -40q17 -39 -14 -69l-144 -144l355 -355l355 355l-144 144q-31 30 -14 69q17 40 59 40h448q26 0 45 -19t19 -45v-448q0 -42 -39 -59q-13 -5 -25 -5q-26 0 -45 19z " />
+<glyph unicode="&#xf0c0;" horiz-adv-x="1920" d="M593 640q-162 -5 -265 -128h-134q-82 0 -138 40.5t-56 118.5q0 353 124 353q6 0 43.5 -21t97.5 -42.5t119 -21.5q67 0 133 23q-5 -37 -5 -66q0 -139 81 -256zM1664 3q0 -120 -73 -189.5t-194 -69.5h-874q-121 0 -194 69.5t-73 189.5q0 53 3.5 103.5t14 109t26.5 108.5 t43 97.5t62 81t85.5 53.5t111.5 20q10 0 43 -21.5t73 -48t107 -48t135 -21.5t135 21.5t107 48t73 48t43 21.5q61 0 111.5 -20t85.5 -53.5t62 -81t43 -97.5t26.5 -108.5t14 -109t3.5 -103.5zM640 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75 t75 -181zM1344 896q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5zM1920 671q0 -78 -56 -118.5t-138 -40.5h-134q-103 123 -265 128q81 117 81 256q0 29 -5 66q66 -23 133 -23q59 0 119 21.5t97.5 42.5 t43.5 21q124 0 124 -353zM1792 1280q0 -106 -75 -181t-181 -75t-181 75t-75 181t75 181t181 75t181 -75t75 -181z" />
+<glyph unicode="&#xf0c1;" horiz-adv-x="1664" d="M1456 320q0 40 -28 68l-208 208q-28 28 -68 28q-42 0 -72 -32q3 -3 19 -18.5t21.5 -21.5t15 -19t13 -25.5t3.5 -27.5q0 -40 -28 -68t-68 -28q-15 0 -27.5 3.5t-25.5 13t-19 15t-21.5 21.5t-18.5 19q-33 -31 -33 -73q0 -40 28 -68l206 -207q27 -27 68 -27q40 0 68 26 l147 146q28 28 28 67zM753 1025q0 40 -28 68l-206 207q-28 28 -68 28q-39 0 -68 -27l-147 -146q-28 -28 -28 -67q0 -40 28 -68l208 -208q27 -27 68 -27q42 0 72 31q-3 3 -19 18.5t-21.5 21.5t-15 19t-13 25.5t-3.5 27.5q0 40 28 68t68 28q15 0 27.5 -3.5t25.5 -13t19 -15 t21.5 -21.5t18.5 -19q33 31 33 73zM1648 320q0 -120 -85 -203l-147 -146q-83 -83 -203 -83q-121 0 -204 85l-206 207q-83 83 -83 203q0 123 88 209l-88 88q-86 -88 -208 -88q-120 0 -204 84l-208 208q-84 84 -84 204t85 203l147 146q83 83 203 83q121 0 204 -85l206 -207 q83 -83 83 -203q0 -123 -88 -209l88 -88q86 88 208 88q120 0 204 -84l208 -208q84 -84 84 -204z" />
+<glyph unicode="&#xf0c2;" horiz-adv-x="1920" d="M1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088q-185 0 -316.5 131.5t-131.5 316.5q0 132 71 241.5t187 163.5q-2 28 -2 43q0 212 150 362t362 150q158 0 286.5 -88t187.5 -230q70 62 166 62q106 0 181 -75t75 -181q0 -75 -41 -138q129 -30 213 -134.5t84 -239.5z " />
+<glyph unicode="&#xf0c3;" horiz-adv-x="1664" d="M1527 88q56 -89 21.5 -152.5t-140.5 -63.5h-1152q-106 0 -140.5 63.5t21.5 152.5l503 793v399h-64q-26 0 -45 19t-19 45t19 45t45 19h512q26 0 45 -19t19 -45t-19 -45t-45 -19h-64v-399zM748 813l-272 -429h712l-272 429l-20 31v37v399h-128v-399v-37z" />
+<glyph unicode="&#xf0c4;" horiz-adv-x="1792" d="M960 640q26 0 45 -19t19 -45t-19 -45t-45 -19t-45 19t-19 45t19 45t45 19zM1260 576l507 -398q28 -20 25 -56q-5 -35 -35 -51l-128 -64q-13 -7 -29 -7q-17 0 -31 8l-690 387l-110 -66q-8 -4 -12 -5q14 -49 10 -97q-7 -77 -56 -147.5t-132 -123.5q-132 -84 -277 -84 q-136 0 -222 78q-90 84 -79 207q7 76 56 147t131 124q132 84 278 84q83 0 151 -31q9 13 22 22l122 73l-122 73q-13 9 -22 22q-68 -31 -151 -31q-146 0 -278 84q-82 53 -131 124t-56 147q-5 59 15.5 113t63.5 93q85 79 222 79q145 0 277 -84q83 -52 132 -123t56 -148 q4 -48 -10 -97q4 -1 12 -5l110 -66l690 387q14 8 31 8q16 0 29 -7l128 -64q30 -16 35 -51q3 -36 -25 -56zM579 836q46 42 21 108t-106 117q-92 59 -192 59q-74 0 -113 -36q-46 -42 -21 -108t106 -117q92 -59 192 -59q74 0 113 36zM494 91q81 51 106 117t-21 108 q-39 36 -113 36q-100 0 -192 -59q-81 -51 -106 -117t21 -108q39 -36 113 -36q100 0 192 59zM672 704l96 -58v11q0 36 33 56l14 8l-79 47l-26 -26q-3 -3 -10 -11t-12 -12q-2 -2 -4 -3.5t-3 -2.5zM896 480l96 -32l736 576l-128 64l-768 -431v-113l-160 -96l9 -8q2 -2 7 -6 q4 -4 11 -12t11 -12l26 -26zM1600 64l128 64l-520 408l-177 -138q-2 -3 -13 -7z" />
+<glyph unicode="&#xf0c5;" horiz-adv-x="1792" d="M1696 1152q40 0 68 -28t28 -68v-1216q0 -40 -28 -68t-68 -28h-960q-40 0 -68 28t-28 68v288h-544q-40 0 -68 28t-28 68v672q0 40 20 88t48 76l408 408q28 28 76 48t88 20h416q40 0 68 -28t28 -68v-328q68 40 128 40h416zM1152 939l-299 -299h299v299zM512 1323l-299 -299 h299v299zM708 676l316 316v416h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h512v256q0 40 20 88t48 76zM1664 -128v1152h-384v-416q0 -40 -28 -68t-68 -28h-416v-640h896z" />
+<glyph unicode="&#xf0c6;" horiz-adv-x="1408" d="M1404 151q0 -117 -79 -196t-196 -79q-135 0 -235 100l-777 776q-113 115 -113 271q0 159 110 270t269 111q158 0 273 -113l605 -606q10 -10 10 -22q0 -16 -30.5 -46.5t-46.5 -30.5q-13 0 -23 10l-606 607q-79 77 -181 77q-106 0 -179 -75t-73 -181q0 -105 76 -181 l776 -777q63 -63 145 -63q64 0 106 42t42 106q0 82 -63 145l-581 581q-26 24 -60 24q-29 0 -48 -19t-19 -48q0 -32 25 -59l410 -410q10 -10 10 -22q0 -16 -31 -47t-47 -31q-12 0 -22 10l-410 410q-63 61 -63 149q0 82 57 139t139 57q88 0 149 -63l581 -581q100 -98 100 -235 z" />
+<glyph unicode="&#xf0c7;" d="M384 0h768v384h-768v-384zM1280 0h128v896q0 14 -10 38.5t-20 34.5l-281 281q-10 10 -34 20t-39 10v-416q0 -40 -28 -68t-68 -28h-576q-40 0 -68 28t-28 68v416h-128v-1280h128v416q0 40 28 68t68 28h832q40 0 68 -28t28 -68v-416zM896 928v320q0 13 -9.5 22.5t-22.5 9.5 h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5zM1536 896v-928q0 -40 -28 -68t-68 -28h-1344q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h928q40 0 88 -20t76 -48l280 -280q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf0c8;" d="M1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf0c9;" d="M1536 192v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 704v-128q0 -26 -19 -45t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45zM1536 1216v-128q0 -26 -19 -45 t-45 -19h-1408q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h1408q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0ca;" horiz-adv-x="1792" d="M384 128q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM384 640q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 224v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5 t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1152q0 -80 -56 -136t-136 -56t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z M1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf0cb;" horiz-adv-x="1792" d="M381 -84q0 -80 -54.5 -126t-135.5 -46q-106 0 -172 66l57 88q49 -45 106 -45q29 0 50.5 14.5t21.5 42.5q0 64 -105 56l-26 56q8 10 32.5 43.5t42.5 54t37 38.5v1q-16 0 -48.5 -1t-48.5 -1v-53h-106v152h333v-88l-95 -115q51 -12 81 -49t30 -88zM383 543v-159h-362 q-6 36 -6 54q0 51 23.5 93t56.5 68t66 47.5t56.5 43.5t23.5 45q0 25 -14.5 38.5t-39.5 13.5q-46 0 -81 -58l-85 59q24 51 71.5 79.5t105.5 28.5q73 0 123 -41.5t50 -112.5q0 -50 -34 -91.5t-75 -64.5t-75.5 -50.5t-35.5 -52.5h127v60h105zM1792 224v-192q0 -13 -9.5 -22.5 t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM384 1123v-99h-335v99h107q0 41 0.5 122t0.5 121v12h-2q-8 -17 -50 -54l-71 76l136 127h106v-404h108zM1792 736v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5 t-9.5 22.5v192q0 14 9 23t23 9h1216q13 0 22.5 -9.5t9.5 -22.5zM1792 1248v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1216q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1216q13 0 22.5 -9.5t9.5 -22.5z" />
+<glyph unicode="&#xf0cc;" horiz-adv-x="1792" d="M1760 640q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-1728q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h1728zM483 704q-28 35 -51 80q-48 97 -48 188q0 181 134 309q133 127 393 127q50 0 167 -19q66 -12 177 -48q10 -38 21 -118q14 -123 14 -183q0 -18 -5 -45l-12 -3l-84 6 l-14 2q-50 149 -103 205q-88 91 -210 91q-114 0 -182 -59q-67 -58 -67 -146q0 -73 66 -140t279 -129q69 -20 173 -66q58 -28 95 -52h-743zM990 448h411q7 -39 7 -92q0 -111 -41 -212q-23 -55 -71 -104q-37 -35 -109 -81q-80 -48 -153 -66q-80 -21 -203 -21q-114 0 -195 23 l-140 40q-57 16 -72 28q-8 8 -8 22v13q0 108 -2 156q-1 30 0 68l2 37v44l102 2q15 -34 30 -71t22.5 -56t12.5 -27q35 -57 80 -94q43 -36 105 -57q59 -22 132 -22q64 0 139 27q77 26 122 86q47 61 47 129q0 84 -81 157q-34 29 -137 71z" />
+<glyph unicode="&#xf0cd;" d="M48 1313q-37 2 -45 4l-3 88q13 1 40 1q60 0 112 -4q132 -7 166 -7q86 0 168 3q116 4 146 5q56 0 86 2l-1 -14l2 -64v-9q-60 -9 -124 -9q-60 0 -79 -25q-13 -14 -13 -132q0 -13 0.5 -32.5t0.5 -25.5l1 -229l14 -280q6 -124 51 -202q35 -59 96 -92q88 -47 177 -47 q104 0 191 28q56 18 99 51q48 36 65 64q36 56 53 114q21 73 21 229q0 79 -3.5 128t-11 122.5t-13.5 159.5l-4 59q-5 67 -24 88q-34 35 -77 34l-100 -2l-14 3l2 86h84l205 -10q76 -3 196 10l18 -2q6 -38 6 -51q0 -7 -4 -31q-45 -12 -84 -13q-73 -11 -79 -17q-15 -15 -15 -41 q0 -7 1.5 -27t1.5 -31q8 -19 22 -396q6 -195 -15 -304q-15 -76 -41 -122q-38 -65 -112 -123q-75 -57 -182 -89q-109 -33 -255 -33q-167 0 -284 46q-119 47 -179 122q-61 76 -83 195q-16 80 -16 237v333q0 188 -17 213q-25 36 -147 39zM1536 -96v64q0 14 -9 23t-23 9h-1472 q-14 0 -23 -9t-9 -23v-64q0 -14 9 -23t23 -9h1472q14 0 23 9t9 23z" />
+<glyph unicode="&#xf0ce;" horiz-adv-x="1664" d="M512 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 160v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23 v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM512 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 160v192 q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1024 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 544v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192 q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1536 928v192q0 14 -9 23t-23 9h-320q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h320q14 0 23 9t9 23zM1664 1248v-1088q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1344q66 0 113 -47t47 -113 z" />
+<glyph unicode="&#xf0d0;" horiz-adv-x="1664" d="M1190 955l293 293l-107 107l-293 -293zM1637 1248q0 -27 -18 -45l-1286 -1286q-18 -18 -45 -18t-45 18l-198 198q-18 18 -18 45t18 45l1286 1286q18 18 45 18t45 -18l198 -198q18 -18 18 -45zM286 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM636 1276 l196 -60l-196 -60l-60 -196l-60 196l-196 60l196 60l60 196zM1566 798l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98zM926 1438l98 -30l-98 -30l-30 -98l-30 98l-98 30l98 30l30 98z" />
+<glyph unicode="&#xf0d1;" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d2;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf0d3;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" />
+<glyph unicode="&#xf0d4;" d="M678 -57q0 -38 -10 -71h-380q-95 0 -171.5 56.5t-103.5 147.5q24 45 69 77.5t100 49.5t107 24t107 7q32 0 49 -2q6 -4 30.5 -21t33 -23t31 -23t32 -25.5t27.5 -25.5t26.5 -29.5t21 -30.5t17.5 -34.5t9.5 -36t4.5 -40.5zM385 294q-234 -7 -385 -85v433q103 -118 273 -118 q32 0 70 5q-21 -61 -21 -86q0 -67 63 -149zM558 805q0 -100 -43.5 -160.5t-140.5 -60.5q-51 0 -97 26t-78 67.5t-56 93.5t-35.5 104t-11.5 99q0 96 51.5 165t144.5 69q66 0 119 -41t84 -104t47 -130t16 -128zM1536 896v-736q0 -119 -84.5 -203.5t-203.5 -84.5h-468 q39 73 39 157q0 66 -22 122.5t-55.5 93t-72 71t-72 59.5t-55.5 54.5t-22 59.5q0 36 23 68t56 61.5t65.5 64.5t55.5 93t23 131t-26.5 145.5t-75.5 118.5q-6 6 -14 11t-12.5 7.5t-10 9.5t-10.5 17h135l135 64h-437q-138 0 -244.5 -38.5t-182.5 -133.5q0 126 81 213t207 87h960 q119 0 203.5 -84.5t84.5 -203.5v-96h-256v256h-128v-256h-256v-128h256v-256h128v256h256z" />
+<glyph unicode="&#xf0d5;" horiz-adv-x="1664" d="M876 71q0 21 -4.5 40.5t-9.5 36t-17.5 34.5t-21 30.5t-26.5 29.5t-27.5 25.5t-32 25.5t-31 23t-33 23t-30.5 21q-17 2 -50 2q-54 0 -106 -7t-108 -25t-98 -46t-69 -75t-27 -107q0 -68 35.5 -121.5t93 -84t120.5 -45.5t127 -15q59 0 112.5 12.5t100.5 39t74.5 73.5 t27.5 110zM756 933q0 60 -16.5 127.5t-47 130.5t-84 104t-119.5 41q-93 0 -144 -69t-51 -165q0 -47 11.5 -99t35.5 -104t56 -93.5t78 -67.5t97 -26q97 0 140.5 60.5t43.5 160.5zM625 1408h437l-135 -79h-135q71 -45 110 -126t39 -169q0 -74 -23 -131.5t-56 -92.5t-66 -64.5 t-56 -61t-23 -67.5q0 -26 16.5 -51t43 -48t58.5 -48t64 -55.5t58.5 -66t43 -85t16.5 -106.5q0 -160 -140 -282q-152 -131 -420 -131q-59 0 -119.5 10t-122 33.5t-108.5 58t-77 89t-30 121.5q0 61 37 135q32 64 96 110.5t145 71t155 36t150 13.5q-64 83 -64 149q0 12 2 23.5 t5 19.5t8 21.5t7 21.5q-40 -5 -70 -5q-149 0 -255.5 98t-106.5 246q0 140 95 250.5t234 141.5q94 20 187 20zM1664 1152v-128h-256v-256h-128v256h-256v128h256v256h128v-256h256z" />
+<glyph unicode="&#xf0d6;" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d7;" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0d8;" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0d9;" horiz-adv-x="640" d="M640 1088v-896q0 -26 -19 -45t-45 -19t-45 19l-448 448q-19 19 -19 45t19 45l448 448q19 19 45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf0da;" horiz-adv-x="640" d="M576 640q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19t-19 45v896q0 26 19 45t45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0db;" horiz-adv-x="1664" d="M160 0h608v1152h-640v-1120q0 -13 9.5 -22.5t22.5 -9.5zM1536 32v1120h-640v-1152h608q13 0 22.5 9.5t9.5 22.5zM1664 1248v-1216q0 -66 -47 -113t-113 -47h-1344q-66 0 -113 47t-47 113v1216q0 66 47 113t113 47h1344q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf0dc;" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45zM1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0dd;" horiz-adv-x="1024" d="M1024 448q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0de;" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
+<glyph unicode="&#xf0e0;" horiz-adv-x="1792" d="M1792 826v-794q0 -66 -47 -113t-113 -47h-1472q-66 0 -113 47t-47 113v794q44 -49 101 -87q362 -246 497 -345q57 -42 92.5 -65.5t94.5 -48t110 -24.5h1h1q51 0 110 24.5t94.5 48t92.5 65.5q170 123 498 345q57 39 100 87zM1792 1120q0 -79 -49 -151t-122 -123 q-376 -261 -468 -325q-10 -7 -42.5 -30.5t-54 -38t-52 -32.5t-57.5 -27t-50 -9h-1h-1q-23 0 -50 9t-57.5 27t-52 32.5t-54 38t-42.5 30.5q-91 64 -262 182.5t-205 142.5q-62 42 -117 115.5t-55 136.5q0 78 41.5 130t118.5 52h1472q65 0 112.5 -47t47.5 -113z" />
+<glyph unicode="&#xf0e1;" horiz-adv-x="1379" d="M1014 961q171 0 268 -85.5t97 -254.5v-586q0 -14 -10.5 -24.5t-24.5 -10.5h-252q-14 0 -24.5 10.5t-10.5 24.5v529q0 71 -26.5 104t-95.5 33q-88 0 -123.5 -51.5t-35.5 -143.5v-471q0 -14 -10.5 -24.5t-25.5 -10.5h-246q-14 0 -24.5 10.5t-10.5 24.5v868q0 14 10.5 24.5 t24.5 10.5h239q13 0 21 -5t10.5 -18.5t3 -18t0.5 -22.5q93 87 246 87zM290 938q14 0 24.5 -10.5t10.5 -24.5v-868q0 -14 -10.5 -24.5t-24.5 -10.5h-246q-14 0 -24.5 10.5t-10.5 24.5v868q0 14 10.5 24.5t24.5 10.5h246zM167 1371q69 0 118 -49t49 -118t-49 -118t-118 -49 t-118 49t-49 118t49 118t118 49z" />
+<glyph unicode="&#xf0e2;" d="M1536 640q0 -156 -61 -298t-164 -245t-245 -164t-298 -61q-179 0 -336.5 76t-266 213t-147.5 312q-3 14 7 27q9 12 25 12h199q23 0 30 -23q50 -162 185 -261.5t304 -99.5q104 0 198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5t-40.5 198.5t-109.5 163.5t-163.5 109.5 t-198.5 40.5q-98 0 -188 -35.5t-160 -101.5l137 -138q31 -30 14 -69q-17 -40 -59 -40h-448q-26 0 -45 19t-19 45v448q0 42 40 59q39 17 69 -14l130 -129q107 101 244.5 156.5t284.5 55.5q156 0 298 -61t245 -164t164 -245t61 -298z" />
+<glyph unicode="&#xf0e3;" horiz-adv-x="1792" d="M1771 0q0 -53 -37 -90l-107 -108q-39 -37 -91 -37q-53 0 -90 37l-363 364q-38 36 -38 90q0 53 43 96l-256 256l-126 -126q-14 -14 -34 -14t-34 14q2 -2 12.5 -12t12.5 -13t10 -11.5t10 -13.5t6 -13.5t5.5 -16.5t1.5 -18q0 -38 -28 -68q-3 -3 -16.5 -18t-19 -20.5 t-18.5 -16.5t-22 -15.5t-22 -9t-26 -4.5q-40 0 -68 28l-408 408q-28 28 -28 68q0 13 4.5 26t9 22t15.5 22t16.5 18.5t20.5 19t18 16.5q30 28 68 28q10 0 18 -1.5t16.5 -5.5t13.5 -6t13.5 -10t11.5 -10t13 -12.5t12 -12.5q-14 14 -14 34t14 34l348 348q14 14 34 14t34 -14 q-2 2 -12.5 12t-12.5 13t-10 11.5t-10 13.5t-6 13.5t-5.5 16.5t-1.5 18q0 38 28 68q3 3 16.5 18t19 20.5t18.5 16.5t22 15.5t22 9t26 4.5q40 0 68 -28l408 -408q28 -28 28 -68q0 -13 -4.5 -26t-9 -22t-15.5 -22t-16.5 -18.5t-20.5 -19t-18 -16.5q-30 -28 -68 -28 q-10 0 -18 1.5t-16.5 5.5t-13.5 6t-13.5 10t-11.5 10t-13 12.5t-12 12.5q14 -14 14 -34t-14 -34l-126 -126l256 -256q43 43 96 43q52 0 91 -37l363 -363q37 -39 37 -91z" />
+<glyph unicode="&#xf0e4;" horiz-adv-x="1792" d="M384 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM576 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1004 351l101 382q6 26 -7.5 48.5t-38.5 29.5 t-48 -6.5t-30 -39.5l-101 -382q-60 -5 -107 -43.5t-63 -98.5q-20 -77 20 -146t117 -89t146 20t89 117q16 60 -6 117t-72 91zM1664 384q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1024 1024q0 53 -37.5 90.5 t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1472 832q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM1792 384q0 -261 -141 -483q-19 -29 -54 -29h-1402q-35 0 -54 29 q-141 221 -141 483q0 182 71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
+<glyph unicode="&#xf0e5;" horiz-adv-x="1792" d="M896 1152q-204 0 -381.5 -69.5t-282 -187.5t-104.5 -255q0 -112 71.5 -213.5t201.5 -175.5l87 -50l-27 -96q-24 -91 -70 -172q152 63 275 171l43 38l57 -6q69 -8 130 -8q204 0 381.5 69.5t282 187.5t104.5 255t-104.5 255t-282 187.5t-381.5 69.5zM1792 640 q0 -174 -120 -321.5t-326 -233t-450 -85.5q-70 0 -145 8q-198 -175 -460 -242q-49 -14 -114 -22h-5q-15 0 -27 10.5t-16 27.5v1q-3 4 -0.5 12t2 10t4.5 9.5l6 9t7 8.5t8 9q7 8 31 34.5t34.5 38t31 39.5t32.5 51t27 59t26 76q-157 89 -247.5 220t-90.5 281q0 174 120 321.5 t326 233t450 85.5t450 -85.5t326 -233t120 -321.5z" />
+<glyph unicode="&#xf0e6;" horiz-adv-x="1792" d="M704 1152q-153 0 -286 -52t-211.5 -141t-78.5 -191q0 -82 53 -158t149 -132l97 -56l-35 -84q34 20 62 39l44 31l53 -10q78 -14 153 -14q153 0 286 52t211.5 141t78.5 191t-78.5 191t-211.5 141t-286 52zM704 1280q191 0 353.5 -68.5t256.5 -186.5t94 -257t-94 -257 t-256.5 -186.5t-353.5 -68.5q-86 0 -176 16q-124 -88 -278 -128q-36 -9 -86 -16h-3q-11 0 -20.5 8t-11.5 21q-1 3 -1 6.5t0.5 6.5t2 6l2.5 5t3.5 5.5t4 5t4.5 5t4 4.5q5 6 23 25t26 29.5t22.5 29t25 38.5t20.5 44q-124 72 -195 177t-71 224q0 139 94 257t256.5 186.5 t353.5 68.5zM1526 111q10 -24 20.5 -44t25 -38.5t22.5 -29t26 -29.5t23 -25q1 -1 4 -4.5t4.5 -5t4 -5t3.5 -5.5l2.5 -5t2 -6t0.5 -6.5t-1 -6.5q-3 -14 -13 -22t-22 -7q-50 7 -86 16q-154 40 -278 128q-90 -16 -176 -16q-271 0 -472 132q58 -4 88 -4q161 0 309 45t264 129 q125 92 192 212t67 254q0 77 -23 152q129 -71 204 -178t75 -230q0 -120 -71 -224.5t-195 -176.5z" />
+<glyph unicode="&#xf0e7;" horiz-adv-x="896" d="M885 970q18 -20 7 -44l-540 -1157q-13 -25 -42 -25q-4 0 -14 2q-17 5 -25.5 19t-4.5 30l197 808l-406 -101q-4 -1 -12 -1q-18 0 -31 11q-18 15 -13 39l201 825q4 14 16 23t28 9h328q19 0 32 -12.5t13 -29.5q0 -8 -5 -18l-171 -463l396 98q8 2 12 2q19 0 34 -15z" />
+<glyph unicode="&#xf0e8;" horiz-adv-x="1792" d="M1792 288v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192h-512v-192h96q40 0 68 -28t28 -68v-320 q0 -40 -28 -68t-68 -28h-320q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h96v192q0 52 38 90t90 38h512v192h-96q-40 0 -68 28t-28 68v320q0 40 28 68t68 28h320q40 0 68 -28t28 -68v-320q0 -40 -28 -68t-68 -28h-96v-192h512q52 0 90 -38t38 -90v-192h96q40 0 68 -28t28 -68 z" />
+<glyph unicode="&#xf0e9;" horiz-adv-x="1664" d="M896 708v-580q0 -104 -76 -180t-180 -76t-180 76t-76 180q0 26 19 45t45 19t45 -19t19 -45q0 -50 39 -89t89 -39t89 39t39 89v580q33 11 64 11t64 -11zM1664 681q0 -13 -9.5 -22.5t-22.5 -9.5q-11 0 -23 10q-49 46 -93 69t-102 23q-68 0 -128 -37t-103 -97 q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -28 -17q-18 0 -29 17q-4 6 -14.5 24t-17.5 28q-43 60 -102.5 97t-127.5 37t-127.5 -37t-102.5 -97q-7 -10 -17.5 -28t-14.5 -24q-11 -17 -29 -17q-17 0 -28 17q-4 6 -14.5 24t-17.5 28q-43 60 -103 97t-128 37q-58 0 -102 -23t-93 -69 q-12 -10 -23 -10q-13 0 -22.5 9.5t-9.5 22.5q0 5 1 7q45 183 172.5 319.5t298 204.5t360.5 68q140 0 274.5 -40t246.5 -113.5t194.5 -187t115.5 -251.5q1 -2 1 -7zM896 1408v-98q-42 2 -64 2t-64 -2v98q0 26 19 45t45 19t45 -19t19 -45z" />
+<glyph unicode="&#xf0ea;" horiz-adv-x="1792" d="M768 -128h896v640h-416q-40 0 -68 28t-28 68v416h-384v-1152zM1024 1312v64q0 13 -9.5 22.5t-22.5 9.5h-704q-13 0 -22.5 -9.5t-9.5 -22.5v-64q0 -13 9.5 -22.5t22.5 -9.5h704q13 0 22.5 9.5t9.5 22.5zM1280 640h299l-299 299v-299zM1792 512v-672q0 -40 -28 -68t-68 -28 h-960q-40 0 -68 28t-28 68v160h-544q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h1088q40 0 68 -28t28 -68v-328q21 -13 36 -28l408 -408q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf0eb;" horiz-adv-x="1024" d="M736 960q0 -13 -9.5 -22.5t-22.5 -9.5t-22.5 9.5t-9.5 22.5q0 46 -54 71t-106 25q-13 0 -22.5 9.5t-9.5 22.5t9.5 22.5t22.5 9.5q50 0 99.5 -16t87 -54t37.5 -90zM896 960q0 72 -34.5 134t-90 101.5t-123 62t-136.5 22.5t-136.5 -22.5t-123 -62t-90 -101.5t-34.5 -134 q0 -101 68 -180q10 -11 30.5 -33t30.5 -33q128 -153 141 -298h228q13 145 141 298q10 11 30.5 33t30.5 33q68 79 68 180zM1024 960q0 -155 -103 -268q-45 -49 -74.5 -87t-59.5 -95.5t-34 -107.5q47 -28 47 -82q0 -37 -25 -64q25 -27 25 -64q0 -52 -45 -81q13 -23 13 -47 q0 -46 -31.5 -71t-77.5 -25q-20 -44 -60 -70t-87 -26t-87 26t-60 70q-46 0 -77.5 25t-31.5 71q0 24 13 47q-45 29 -45 81q0 37 25 64q-25 27 -25 64q0 54 47 82q-4 50 -34 107.5t-59.5 95.5t-74.5 87q-103 113 -103 268q0 99 44.5 184.5t117 142t164 89t186.5 32.5 t186.5 -32.5t164 -89t117 -142t44.5 -184.5z" />
+<glyph unicode="&#xf0ec;" horiz-adv-x="1792" d="M1792 352v-192q0 -13 -9.5 -22.5t-22.5 -9.5h-1376v-192q0 -13 -9.5 -22.5t-22.5 -9.5q-12 0 -24 10l-319 320q-9 9 -9 22q0 14 9 23l320 320q9 9 23 9q13 0 22.5 -9.5t9.5 -22.5v-192h1376q13 0 22.5 -9.5t9.5 -22.5zM1792 896q0 -14 -9 -23l-320 -320q-9 -9 -23 -9 q-13 0 -22.5 9.5t-9.5 22.5v192h-1376q-13 0 -22.5 9.5t-9.5 22.5v192q0 13 9.5 22.5t22.5 9.5h1376v192q0 14 9 23t23 9q12 0 24 -10l319 -319q9 -9 9 -23z" />
+<glyph unicode="&#xf0ed;" horiz-adv-x="1920" d="M1280 608q0 14 -9 23t-23 9h-224v352q0 13 -9.5 22.5t-22.5 9.5h-192q-13 0 -22.5 -9.5t-9.5 -22.5v-352h-224q-13 0 -22.5 -9.5t-9.5 -22.5q0 -14 9 -23l352 -352q9 -9 23 -9t23 9l351 351q10 12 10 24zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
+<glyph unicode="&#xf0ee;" horiz-adv-x="1920" d="M1280 672q0 14 -9 23l-352 352q-9 9 -23 9t-23 -9l-351 -351q-10 -12 -10 -24q0 -14 9 -23t23 -9h224v-352q0 -13 9.5 -22.5t22.5 -9.5h192q13 0 22.5 9.5t9.5 22.5v352h224q13 0 22.5 9.5t9.5 22.5zM1920 384q0 -159 -112.5 -271.5t-271.5 -112.5h-1088 q-185 0 -316.5 131.5t-131.5 316.5q0 130 70 240t188 165q-2 30 -2 43q0 212 150 362t362 150q156 0 285.5 -87t188.5 -231q71 62 166 62q106 0 181 -75t75 -181q0 -76 -41 -138q130 -31 213.5 -135.5t83.5 -238.5z" />
+<glyph unicode="&#xf0f0;" horiz-adv-x="1408" d="M384 192q0 -26 -19 -45t-45 -19t-45 19t-19 45t19 45t45 19t45 -19t19 -45zM1408 131q0 -121 -73 -190t-194 -69h-874q-121 0 -194 69t-73 190q0 68 5.5 131t24 138t47.5 132.5t81 103t120 60.5q-22 -52 -22 -120v-203q-58 -20 -93 -70t-35 -111q0 -80 56 -136t136 -56 t136 56t56 136q0 61 -35.5 111t-92.5 70v203q0 62 25 93q132 -104 295 -104t295 104q25 -31 25 -93v-64q-106 0 -181 -75t-75 -181v-89q-32 -29 -32 -71q0 -40 28 -68t68 -28t68 28t28 68q0 42 -32 71v89q0 52 38 90t90 38t90 -38t38 -90v-89q-32 -29 -32 -71q0 -40 28 -68 t68 -28t68 28t28 68q0 42 -32 71v89q0 68 -34.5 127.5t-93.5 93.5q0 10 0.5 42.5t0 48t-2.5 41.5t-7 47t-13 40q68 -15 120 -60.5t81 -103t47.5 -132.5t24 -138t5.5 -131zM1088 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf0f1;" horiz-adv-x="1408" d="M1280 832q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1408 832q0 -62 -35.5 -111t-92.5 -70v-395q0 -159 -131.5 -271.5t-316.5 -112.5t-316.5 112.5t-131.5 271.5v132q-164 20 -274 128t-110 252v512q0 26 19 45t45 19q6 0 16 -2q17 30 47 48 t65 18q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5q-33 0 -64 18v-402q0 -106 94 -181t226 -75t226 75t94 181v402q-31 -18 -64 -18q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5q35 0 65 -18t47 -48q10 2 16 2q26 0 45 -19t19 -45v-512q0 -144 -110 -252 t-274 -128v-132q0 -106 94 -181t226 -75t226 75t94 181v395q-57 21 -92.5 70t-35.5 111q0 80 56 136t136 56t136 -56t56 -136z" />
+<glyph unicode="&#xf0f2;" horiz-adv-x="1792" d="M640 1152h512v128h-512v-128zM288 1152v-1280h-64q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h64zM1408 1152v-1280h-1024v1280h128v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h128zM1792 928v-832q0 -92 -66 -158t-158 -66h-64v1280h64q92 0 158 -66 t66 -158z" />
+<glyph unicode="&#xf0f3;" horiz-adv-x="1664" d="M848 -160q0 16 -16 16q-59 0 -101.5 42.5t-42.5 101.5q0 16 -16 16t-16 -16q0 -73 51.5 -124.5t124.5 -51.5q16 0 16 16zM1664 128q0 -52 -38 -90t-90 -38h-448q0 -106 -75 -181t-181 -75t-181 75t-75 181h-448q-52 0 -90 38t-38 90q190 161 287 397.5t97 498.5 q0 165 96 262t264 117q-8 18 -8 37q0 40 28 68t68 28t68 -28t28 -68q0 -19 -8 -37q168 -20 264 -117t96 -262q0 -262 97 -498.5t287 -397.5z" />
+<glyph unicode="&#xf0f4;" horiz-adv-x="1920" d="M1664 896q0 80 -56 136t-136 56h-64v-384h64q80 0 136 56t56 136zM0 128h1792q0 -106 -75 -181t-181 -75h-1280q-106 0 -181 75t-75 181zM1856 896q0 -159 -112.5 -271.5t-271.5 -112.5h-64v-32q0 -92 -66 -158t-158 -66h-704q-92 0 -158 66t-66 158v736q0 26 19 45 t45 19h1152q159 0 271.5 -112.5t112.5 -271.5z" />
+<glyph unicode="&#xf0f5;" horiz-adv-x="1408" d="M640 1472v-640q0 -61 -35.5 -111t-92.5 -70v-779q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v779q-57 20 -92.5 70t-35.5 111v640q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45v-416q0 -26 19 -45 t45 -19t45 19t19 45v416q0 26 19 45t45 19t45 -19t19 -45zM1408 1472v-1600q0 -52 -38 -90t-90 -38h-128q-52 0 -90 38t-38 90v512h-224q-13 0 -22.5 9.5t-9.5 22.5v800q0 132 94 226t226 94h256q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f6;" horiz-adv-x="1280" d="M1024 352v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM1024 608v-64q0 -14 -9 -23t-23 -9h-704q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h704q14 0 23 -9t9 -23zM128 0h1024v768h-416q-40 0 -68 28t-28 68v416h-512v-1280z M768 896h299l-299 299v-299zM1280 768v-800q0 -40 -28 -68t-68 -28h-1088q-40 0 -68 28t-28 68v1344q0 40 28 68t68 28h544q40 0 88 -20t76 -48l408 -408q28 -28 48 -76t20 -88z" />
+<glyph unicode="&#xf0f7;" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 992v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 1248v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1536h-1152v-1536h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM1408 1472v-1664q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1664q0 26 19 45t45 19h1280q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f8;" horiz-adv-x="1408" d="M384 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM384 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M1152 224v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM896 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M640 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 480v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5zM1152 736v-64q0 -13 -9.5 -22.5t-22.5 -9.5h-64q-13 0 -22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5 -9.5t9.5 -22.5z M896 -128h384v1152h-256v-32q0 -40 -28 -68t-68 -28h-448q-40 0 -68 28t-28 68v32h-256v-1152h384v224q0 13 9.5 22.5t22.5 9.5h320q13 0 22.5 -9.5t9.5 -22.5v-224zM896 1056v320q0 13 -9.5 22.5t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-96h-128v96q0 13 -9.5 22.5 t-22.5 9.5h-64q-13 0 -22.5 -9.5t-9.5 -22.5v-320q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5v96h128v-96q0 -13 9.5 -22.5t22.5 -9.5h64q13 0 22.5 9.5t9.5 22.5zM1408 1088v-1280q0 -26 -19 -45t-45 -19h-1280q-26 0 -45 19t-19 45v1280q0 26 19 45t45 19h320 v288q0 40 28 68t68 28h448q40 0 68 -28t28 -68v-288h320q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0f9;" horiz-adv-x="1920" d="M640 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5zM256 640h384v256h-158q-14 -2 -22 -9l-195 -195q-7 -12 -9 -22v-30zM1536 128q0 53 -37.5 90.5t-90.5 37.5t-90.5 -37.5t-37.5 -90.5t37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5zM1664 800v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM1920 1344v-1152 q0 -26 -19 -45t-45 -19h-192q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-128q-26 0 -45 19t-19 45t19 45t45 19v416q0 26 13 58t32 51l198 198q19 19 51 32t58 13h160v320q0 26 19 45t45 19h1152q26 0 45 -19t19 -45z" />
+<glyph unicode="&#xf0fa;" horiz-adv-x="1792" d="M1280 416v192q0 14 -9 23t-23 9h-224v224q0 14 -9 23t-23 9h-192q-14 0 -23 -9t-9 -23v-224h-224q-14 0 -23 -9t-9 -23v-192q0 -14 9 -23t23 -9h224v-224q0 -14 9 -23t23 -9h192q14 0 23 9t9 23v224h224q14 0 23 9t9 23zM640 1152h512v128h-512v-128zM256 1152v-1280h-32 q-92 0 -158 66t-66 158v832q0 92 66 158t158 66h32zM1440 1152v-1280h-1088v1280h160v160q0 40 28 68t68 28h576q40 0 68 -28t28 -68v-160h160zM1792 928v-832q0 -92 -66 -158t-158 -66h-32v1280h32q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf0fb;" horiz-adv-x="1920" d="M1632 800q261 -58 287 -93l1 -3q-1 -32 -288 -96l-352 -32l-224 -64h-64l-293 -352h69q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-96h-160h-64v32h64v416h-160l-192 -224h-96l-32 32v192h32v32h128v8l-192 24v128l192 24v8h-128v32h-32v192l32 32h96l192 -224h160v416 h-64v32h64h160h96q26 0 45 -4.5t19 -11.5t-19 -11.5t-45 -4.5h-69l293 -352h64l224 -64z" />
+<glyph unicode="&#xf0fc;" horiz-adv-x="1664" d="M640 640v384h-256v-160q0 -45 2 -76t7.5 -56.5t14.5 -40t23 -26.5t33.5 -15.5t45 -7.5t58 -2.5t72.5 0.5zM1664 192v-192h-1152v192l128 192h-97q-211 0 -313 102.5t-102 314.5v287l-64 64l32 128h480l32 128h960l32 -192l-64 -32v-800z" />
+<glyph unicode="&#xf0fd;" d="M1280 192v896q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-512v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-896q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h512v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf0fe;" d="M1280 576v128q0 26 -19 45t-45 19h-320v320q0 26 -19 45t-45 19h-128q-26 0 -45 -19t-19 -45v-320h-320q-26 0 -45 -19t-19 -45v-128q0 -26 19 -45t45 -19h320v-320q0 -26 19 -45t45 -19h128q26 0 45 19t19 45v320h320q26 0 45 19t19 45zM1536 1120v-960 q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
+<glyph unicode="&#xf100;" horiz-adv-x="1024" d="M627 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23zM1011 160q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23t-10 -23l-393 -393l393 -393q10 -10 10 -23z" />
+<glyph unicode="&#xf101;" horiz-adv-x="1024" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM979 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23 l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf102;" horiz-adv-x="1152" d="M1075 224q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23zM1075 608q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393 q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf103;" horiz-adv-x="1152" d="M1075 672q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23zM1075 1056q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23 t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf104;" horiz-adv-x="640" d="M627 992q0 -13 -10 -23l-393 -393l393 -393q10 -10 10 -23t-10 -23l-50 -50q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf105;" horiz-adv-x="640" d="M595 576q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l393 393l-393 393q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf106;" horiz-adv-x="1152" d="M1075 352q0 -13 -10 -23l-50 -50q-10 -10 -23 -10t-23 10l-393 393l-393 -393q-10 -10 -23 -10t-23 10l-50 50q-10 10 -10 23t10 23l466 466q10 10 23 10t23 -10l466 -466q10 -10 10 -23z" />
+<glyph unicode="&#xf107;" horiz-adv-x="1152" d="M1075 800q0 -13 -10 -23l-466 -466q-10 -10 -23 -10t-23 10l-466 466q-10 10 -10 23t10 23l50 50q10 10 23 10t23 -10l393 -393l393 393q10 10 23 10t23 -10l50 -50q10 -10 10 -23z" />
+<glyph unicode="&#xf108;" horiz-adv-x="1920" d="M1792 544v832q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-832q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1376v-1088q0 -66 -47 -113t-113 -47h-544q0 -37 16 -77.5t32 -71t16 -43.5q0 -26 -19 -45t-45 -19h-512q-26 0 -45 19 t-19 45q0 14 16 44t32 70t16 78h-544q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf109;" horiz-adv-x="1920" d="M416 256q-66 0 -113 47t-47 113v704q0 66 47 113t113 47h1088q66 0 113 -47t47 -113v-704q0 -66 -47 -113t-113 -47h-1088zM384 1120v-704q0 -13 9.5 -22.5t22.5 -9.5h1088q13 0 22.5 9.5t9.5 22.5v704q0 13 -9.5 22.5t-22.5 9.5h-1088q-13 0 -22.5 -9.5t-9.5 -22.5z M1760 192h160v-96q0 -40 -47 -68t-113 -28h-1600q-66 0 -113 28t-47 68v96h160h1600zM1040 96q16 0 16 16t-16 16h-160q-16 0 -16 -16t16 -16h160z" />
+<glyph unicode="&#xf10a;" horiz-adv-x="1152" d="M640 128q0 26 -19 45t-45 19t-45 -19t-19 -45t19 -45t45 -19t45 19t19 45zM1024 288v960q0 13 -9.5 22.5t-22.5 9.5h-832q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h832q13 0 22.5 9.5t9.5 22.5zM1152 1248v-1088q0 -66 -47 -113t-113 -47h-832 q-66 0 -113 47t-47 113v1088q0 66 47 113t113 47h832q66 0 113 -47t47 -113z" />
+<glyph unicode="&#xf10b;" horiz-adv-x="768" d="M464 128q0 33 -23.5 56.5t-56.5 23.5t-56.5 -23.5t-23.5 -56.5t23.5 -56.5t56.5 -23.5t56.5 23.5t23.5 56.5zM672 288v704q0 13 -9.5 22.5t-22.5 9.5h-512q-13 0 -22.5 -9.5t-9.5 -22.5v-704q0 -13 9.5 -22.5t22.5 -9.5h512q13 0 22.5 9.5t9.5 22.5zM480 1136 q0 16 -16 16h-160q-16 0 -16 -16t16 -16h160q16 0 16 16zM768 1152v-1024q0 -52 -38 -90t-90 -38h-512q-52 0 -90 38t-38 90v1024q0 52 38 90t90 38h512q52 0 90 -38t38 -90z" />
+<glyph unicode="&#xf10c;" d="M1280 640q0 104 -40.5 198.5t-109.5 163.5t-163.5 109.5t-198.5 40.5t-198.5 -40.5t-163.5 -109.5t-109.5 -163.5t-40.5 -198.5t40.5 -198.5t109.5 -163.5t163.5 -109.5t198.5 -40.5t198.5 40.5t163.5 109.5t109.5 163.5t40.5 198.5zM1536 640q0 -209 -103 -385.5 t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf10d;" horiz-adv-x="1664" d="M768 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z M1664 576v-384q0 -80 -56 -136t-136 -56h-384q-80 0 -136 56t-56 136v704q0 104 40.5 198.5t109.5 163.5t163.5 109.5t198.5 40.5h64q26 0 45 -19t19 -45v-128q0 -26 -19 -45t-45 -19h-64q-106 0 -181 -75t-75 -181v-32q0 -40 28 -68t68 -28h224q80 0 136 -56t56 -136z" />
+<glyph unicode="&#xf10e;" horiz-adv-x="1664" d="M768 1216v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136zM1664 1216 v-704q0 -104 -40.5 -198.5t-109.5 -163.5t-163.5 -109.5t-198.5 -40.5h-64q-26 0 -45 19t-19 45v128q0 26 19 45t45 19h64q106 0 181 75t75 181v32q0 40 -28 68t-68 28h-224q-80 0 -136 56t-56 136v384q0 80 56 136t136 56h384q80 0 136 -56t56 -136z" />
+<glyph unicode="&#xf110;" horiz-adv-x="1568" d="M496 192q0 -60 -42.5 -102t-101.5 -42q-60 0 -102 42t-42 102t42 102t102 42q59 0 101.5 -42t42.5 -102zM928 0q0 -53 -37.5 -90.5t-90.5 -37.5t-90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5t90.5 -37.5t37.5 -90.5zM320 640q0 -66 -47 -113t-113 -47t-113 47t-47 113 t47 113t113 47t113 -47t47 -113zM1360 192q0 -46 -33 -79t-79 -33t-79 33t-33 79t33 79t79 33t79 -33t33 -79zM528 1088q0 -73 -51.5 -124.5t-124.5 -51.5t-124.5 51.5t-51.5 124.5t51.5 124.5t124.5 51.5t124.5 -51.5t51.5 -124.5zM992 1280q0 -80 -56 -136t-136 -56 t-136 56t-56 136t56 136t136 56t136 -56t56 -136zM1536 640q0 -40 -28 -68t-68 -28t-68 28t-28 68t28 68t68 28t68 -28t28 -68zM1328 1088q0 -33 -23.5 -56.5t-56.5 -23.5t-56.5 23.5t-23.5 56.5t23.5 56.5t56.5 23.5t56.5 -23.5t23.5 -56.5z" />
+<glyph unicode="&#xf111;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
+<glyph unicode="&#xf112;" horiz-adv-x="1792" d="M1792 416q0 -166 -127 -451q-3 -7 -10.5 -24t-13.5 -30t-13 -22q-12 -17 -28 -17q-15 0 -23.5 10t-8.5 25q0 9 2.5 26.5t2.5 23.5q5 68 5 123q0 101 -17.5 181t-48.5 138.5t-80 101t-105.5 69.5t-133 42.5t-154 21.5t-175.5 6h-224v-256q0 -26 -19 -45t-45 -19t-45 19 l-512 512q-19 19 -19 45t19 45l512 512q19 19 45 19t45 -19t19 -45v-256h224q713 0 875 -403q53 -134 53 -333z" />
+<glyph unicode="&#xf113;" horiz-adv-x="1664" d="M640 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1280 320q0 -40 -12.5 -82t-43 -76t-72.5 -34t-72.5 34t-43 76t-12.5 82t12.5 82t43 76t72.5 34t72.5 -34t43 -76t12.5 -82zM1440 320 q0 120 -69 204t-187 84q-41 0 -195 -21q-71 -11 -157 -11t-157 11q-152 21 -195 21q-118 0 -187 -84t-69 -204q0 -88 32 -153.5t81 -103t122 -60t140 -29.5t149 -7h168q82 0 149 7t140 29.5t122 60t81 103t32 153.5zM1664 496q0 -207 -61 -331q-38 -77 -105.5 -133t-141 -86 t-170 -47.5t-171.5 -22t-167 -4.5q-78 0 -142 3t-147.5 12.5t-152.5 30t-137 51.5t-121 81t-86 115q-62 123 -62 331q0 237 136 396q-27 82 -27 170q0 116 51 218q108 0 190 -39.5t189 -123.5q147 35 309 35q148 0 280 -32q105 82 187 121t189 39q51 -102 51 -218 q0 -87 -27 -168q136 -160 136 -398z" />
+<glyph unicode="&#xf114;" horiz-adv-x="1664" d="M1536 224v704q0 40 -28 68t-68 28h-704q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68v-960q0 -40 28 -68t68 -28h1216q40 0 68 28t28 68zM1664 928v-704q0 -92 -66 -158t-158 -66h-1216q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320 q92 0 158 -66t66 -158v-32h672q92 0 158 -66t66 -158z" />
+<glyph unicode="&#xf115;" horiz-adv-x="1920" d="M1781 605q0 35 -53 35h-1088q-40 0 -85.5 -21.5t-71.5 -52.5l-294 -363q-18 -24 -18 -40q0 -35 53 -35h1088q40 0 86 22t71 53l294 363q18 22 18 39zM640 768h768v160q0 40 -28 68t-68 28h-576q-40 0 -68 28t-28 68v64q0 40 -28 68t-68 28h-320q-40 0 -68 -28t-28 -68 v-853l256 315q44 53 116 87.5t140 34.5zM1909 605q0 -62 -46 -120l-295 -363q-43 -53 -116 -87.5t-140 -34.5h-1088q-92 0 -158 66t-66 158v960q0 92 66 158t158 66h320q92 0 158 -66t66 -158v-32h544q92 0 158 -66t66 -158v-160h192q54 0 99 -24.5t67 -70.5q15 -32 15 -68z " />
+</font>
+</defs></svg> 
\ No newline at end of file
Binary file src/nabble/view/web/assets/font-awesome/font/fontawesome-webfont.ttf has changed
Binary file src/nabble/view/web/assets/font-awesome/font/fontawesome-webfont.woff has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/assets/jquery/jquery-1.9.1.min.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,5 @@
+/*! jQuery v1.9.1 | (c) 2005, 2012 jQuery Foundation, Inc. | jquery.org/license
+//@ sourceMappingURL=jquery.min.map
+*/(function(e,t){var n,r,i=typeof t,o=e.document,a=e.location,s=e.jQuery,u=e.$,l={},c=[],p="1.9.1",f=c.concat,d=c.push,h=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,b=function(e,t){return new b.fn.init(e,t,r)},x=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^[\],:{}\s]*$/,E=/(?:^|:|,)(?:\s*\[)+/g,S=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,A=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,j=/^-ms-/,D=/-([\da-z])/gi,L=function(e,t){return t.toUpperCase()},H=function(e){(o.addEventListener||"load"===e.type||"complete"===o.readyState)&&(q(),b.ready())},q=function(){o.addEventListener?(o.removeEventListener("DOMContentLoaded",H,!1),e.removeEventListener("load",H,!1)):(o.detachEvent("onreadystatechange",H),e.detachEvent("onload",H))};b.fn=b.prototype={jquery:p,constructor:b,init:function(e,n,r){var i,a;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 b?n[0]:n,b.merge(this,b.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:o,!0)),C.test(i[1])&&b.isPlainObject(n))for(i in n)b.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(a=o.getElementById(i[2]),a&&a.parentNode){if(a.id!==i[2])return r.find(e);this.length=1,this[0]=a}return this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):b.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),b.makeArray(e,this))},selector:"",length:0,size:function(){return this.length},toArray:function(){return h.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=b.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return b.each(this,e,t)},ready:function(e){return b.ready.promise().done(e),this},slice:function(){return this.pushStack(h.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(b.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:d,sort:[].sort,splice:[].splice},b.fn.init.prototype=b.fn,b.extend=b.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},u=1,l=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},u=2),"object"==typeof s||b.isFunction(s)||(s={}),l===u&&(s=this,--u);l>u;u++)if(null!=(o=arguments[u]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(b.isPlainObject(r)||(n=b.isArray(r)))?(n?(n=!1,a=e&&b.isArray(e)?e:[]):a=e&&b.isPlainObject(e)?e:{},s[i]=b.extend(c,a,r)):r!==t&&(s[i]=r));return s},b.extend({noConflict:function(t){return e.$===b&&(e.$=u),t&&e.jQuery===b&&(e.jQuery=s),b},isReady:!1,readyWait:1,holdReady:function(e){e?b.readyWait++:b.ready(!0)},ready:function(e){if(e===!0?!--b.readyWait:!b.isReady){if(!o.body)return setTimeout(b.ready);b.isReady=!0,e!==!0&&--b.readyWait>0||(n.resolveWith(o,[b]),b.fn.trigger&&b(o).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===b.type(e)},isArray:Array.isArray||function(e){return"array"===b.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?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if(!e||"object"!==b.type(e)||e.nodeType||b.isWindow(e))return!1;try{if(e.constructor&&!y.call(e,"constructor")&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||y.call(e,r)},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||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=b.buildFragment([e],t,i),i&&b(i).remove(),b.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=b.trim(n),n&&k.test(n.replace(S,"@").replace(A,"]").replace(E,"")))?Function("return "+n)():(b.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||b.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&b.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(j,"ms-").replace(D,L)},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:v&&!v.call("\ufeff\u00a0")?function(e){return null==e?"":v.call(e)}:function(e){return null==e?"":(e+"").replace(T,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?b.merge(n,"string"==typeof e?[e]:e):d.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(g)return g.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 f.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),b.isFunction(e)?(r=h.call(arguments,2),i=function(){return e.apply(n||this,r.concat(h.call(arguments)))},i.guid=e.guid=e.guid||b.guid++,i):t},access:function(e,n,r,i,o,a,s){var u=0,l=e.length,c=null==r;if("object"===b.type(r)){o=!0;for(u in r)b.access(e,n,u,r[u],!0,a,s)}else if(i!==t&&(o=!0,b.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(b(e),n)})),n))for(;l>u;u++)n(e[u],r,s?i:i.call(e[u],u,n(e[u],r)));return o?e:c?n.call(e):l?n(e[0],r):a},now:function(){return(new Date).getTime()}}),b.ready.promise=function(t){if(!n)if(n=b.Deferred(),"complete"===o.readyState)setTimeout(b.ready);else if(o.addEventListener)o.addEventListener("DOMContentLoaded",H,!1),e.addEventListener("load",H,!1);else{o.attachEvent("onreadystatechange",H),e.attachEvent("onload",H);var r=!1;try{r=null==e.frameElement&&o.documentElement}catch(i){}r&&r.doScroll&&function a(){if(!b.isReady){try{r.doScroll("left")}catch(e){return setTimeout(a,50)}q(),b.ready()}}()}return n.promise(t)},b.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=b.type(e);return b.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=b(o);var _={};function F(e){var t=_[e]={};return b.each(e.match(w)||[],function(e,n){t[n]=!0}),t}b.Callbacks=function(e){e="string"==typeof e?_[e]||F(e):b.extend({},e);var n,r,i,o,a,s,u=[],l=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=u.length,n=!0;u&&o>a;a++)if(u[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,u&&(l?l.length&&c(l.shift()):r?u=[]:p.disable())},p={add:function(){if(u){var t=u.length;(function i(t){b.each(t,function(t,n){var r=b.type(n);"function"===r?e.unique&&p.has(n)||u.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=u.length:r&&(s=t,c(r))}return this},remove:function(){return u&&b.each(arguments,function(e,t){var r;while((r=b.inArray(t,u,r))>-1)u.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?b.inArray(e,u)>-1:!(!u||!u.length)},empty:function(){return u=[],this},disable:function(){return u=l=r=t,this},disabled:function(){return!u},lock:function(){return l=t,r||p.disable(),this},locked:function(){return!l},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!u||i&&!l||(n?l.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},b.extend({Deferred:function(e){var t=[["resolve","done",b.Callbacks("once memory"),"resolved"],["reject","fail",b.Callbacks("once memory"),"rejected"],["notify","progress",b.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return b.Deferred(function(n){b.each(t,function(t,o){var a=o[0],s=b.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&b.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?b.extend(e,r):r}},i={};return r.pipe=r.then,b.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=h.call(arguments),r=n.length,i=1!==r||e&&b.isFunction(e.promise)?r:0,o=1===i?e:b.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?h.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,u,l;if(r>1)for(s=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&b.isFunction(n[t].promise)?n[t].promise().done(a(t,l,n)).fail(o.reject).progress(a(t,u,s)):--i;return i||o.resolveWith(l,n),o.promise()}}),b.support=function(){var t,n,r,a,s,u,l,c,p,f,d=o.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],!n||!r||!n.length)return{};s=o.createElement("select"),l=s.appendChild(o.createElement("option")),a=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={getSetAttribute:"t"!==d.className,leadingWhitespace:3===d.firstChild.nodeType,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:"/a"===r.getAttribute("href"),opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:!!a.value,optSelected:l.selected,enctype:!!o.createElement("form").enctype,html5Clone:"<:nav></:nav>"!==o.createElement("nav").cloneNode(!0).outerHTML,boxModel:"CSS1Compat"===o.compatMode,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},a.checked=!0,t.noCloneChecked=a.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!l.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}a=o.createElement("input"),a.setAttribute("value",""),t.input=""===a.getAttribute("value"),a.value="t",a.setAttribute("type","radio"),t.radioValue="t"===a.value,a.setAttribute("checked","t"),a.setAttribute("name","t"),u=o.createDocumentFragment(),u.appendChild(a),t.appendChecked=a.checked,t.checkClone=u.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;return d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip,b(function(){var n,r,a,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",u=o.getElementsByTagName("body")[0];u&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",u.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",a=d.getElementsByTagName("td"),a[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===a[0].offsetHeight,a[0].style.display="",a[1].style.display="none",t.reliableHiddenOffsets=p&&0===a[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%;",t.boxSizing=4===d.offsetWidth,t.doesNotIncludeMarginInBodyOffset=1!==u.offsetTop,e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(o.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&&(u.style.zoom=1)),u.removeChild(n),n=d=a=r=null)}),n=s=u=l=r=a=null,t}();var O=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,B=/([A-Z])/g;function P(e,n,r,i){if(b.acceptData(e)){var o,a,s=b.expando,u="string"==typeof n,l=e.nodeType,p=l?b.cache:e,f=l?e[s]:e[s]&&s;if(f&&p[f]&&(i||p[f].data)||!u||r!==t)return f||(l?e[s]=f=c.pop()||b.guid++:f=s),p[f]||(p[f]={},l||(p[f].toJSON=b.noop)),("object"==typeof n||"function"==typeof n)&&(i?p[f]=b.extend(p[f],n):p[f].data=b.extend(p[f].data,n)),o=p[f],i||(o.data||(o.data={}),o=o.data),r!==t&&(o[b.camelCase(n)]=r),u?(a=o[n],null==a&&(a=o[b.camelCase(n)])):a=o,a}}function R(e,t,n){if(b.acceptData(e)){var r,i,o,a=e.nodeType,s=a?b.cache:e,u=a?e[b.expando]:b.expando;if(s[u]){if(t&&(o=n?s[u]:s[u].data)){b.isArray(t)?t=t.concat(b.map(t,b.camelCase)):t in o?t=[t]:(t=b.camelCase(t),t=t in o?[t]:t.split(" "));for(r=0,i=t.length;i>r;r++)delete o[t[r]];if(!(n?$:b.isEmptyObject)(o))return}(n||(delete s[u].data,$(s[u])))&&(a?b.cleanData([e],!0):b.support.deleteExpando||s!=s.window?delete s[u]:s[u]=null)}}}b.extend({cache:{},expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?b.cache[e[b.expando]]:e[b.expando],!!e&&!$(e)},data:function(e,t,n){return P(e,t,n)},removeData:function(e,t){return R(e,t)},_data:function(e,t,n){return P(e,t,n,!0)},_removeData:function(e,t){return R(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&b.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),b.fn.extend({data:function(e,n){var r,i,o=this[0],a=0,s=null;if(e===t){if(this.length&&(s=b.data(o),1===o.nodeType&&!b._data(o,"parsedAttrs"))){for(r=o.attributes;r.length>a;a++)i=r[a].name,i.indexOf("data-")||(i=b.camelCase(i.slice(5)),W(o,i,s[i]));b._data(o,"parsedAttrs",!0)}return s}return"object"==typeof e?this.each(function(){b.data(this,e)}):b.access(this,function(n){return n===t?o?W(o,e,b.data(o,e)):null:(this.each(function(){b.data(this,e,n)}),t)},null,n,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function W(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(B,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:O.test(r)?b.parseJSON(r):r}catch(o){}b.data(e,n,r)}else r=t}return r}function $(e){var t;for(t in e)if(("data"!==t||!b.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}b.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=b._data(e,n),r&&(!i||b.isArray(r)?i=b._data(e,n,b.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=b.queue(e,t),r=n.length,i=n.shift(),o=b._queueHooks(e,t),a=function(){b.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),o.cur=i,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 b._data(e,n)||b._data(e,n,{empty:b.Callbacks("once memory").add(function(){b._removeData(e,t+"queue"),b._removeData(e,n)})})}}),b.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?b.queue(this[0],e):n===t?this:this.each(function(){var t=b.queue(this,e,n);b._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&b.dequeue(this,e)})},dequeue:function(e){return this.each(function(){b.dequeue(this,e)})},delay:function(e,t){return e=b.fx?b.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=b.Deferred(),a=this,s=this.length,u=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=b._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(u));return u(),o.promise(n)}});var I,z,X=/[\t\r\n]/g,U=/\r/g,V=/^(?:input|select|textarea|button|object)$/i,Y=/^(?:a|area)$/i,J=/^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,G=/^(?:checked|selected)$/i,Q=b.support.getSetAttribute,K=b.support.input;b.fn.extend({attr:function(e,t){return b.access(this,b.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,t){return b.access(this,b.prop,e,t,arguments.length>1)},removeProp:function(e){return e=b.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,u="string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=b.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,u=0===arguments.length||"string"==typeof e&&e;if(b.isFunction(e))return this.each(function(t){b(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(X," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?b.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return b.isFunction(e)?this.each(function(n){b(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=b(this),u=t,l=e.match(w)||[];while(o=l[a++])u=r?u:!s.hasClass(o),s[u?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&b._data(this,"__className__",this.className),this.className=this.className||e===!1?"":b._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(X," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=b.isFunction(e),this.each(function(n){var o,a=b(this);1===this.nodeType&&(o=i?e.call(this,n,a.val()):e,null==o?o="":"number"==typeof o?o+="":b.isArray(o)&&(o=b.map(o,function(e){return null==e?"":e+""})),r=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=b.valHooks[o.type]||b.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(U,""):null==n?"":n)}}}),b.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value: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,u=0>i?s:o?i:0;for(;s>u;u++)if(n=r[u],!(!n.selected&&u!==i||(b.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&b.nodeName(n.parentNode,"optgroup"))){if(t=b(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n=b.makeArray(t);return b(e).find("option").each(function(){this.selected=b.inArray(b(this).val(),n)>=0}),n.length||(e.selectedIndex=-1),n}}},attr:function(e,n,r){var o,a,s,u=e.nodeType;if(e&&3!==u&&8!==u&&2!==u)return typeof e.getAttribute===i?b.prop(e,n,r):(a=1!==u||!b.isXMLDoc(e),a&&(n=n.toLowerCase(),o=b.attrHooks[n]||(J.test(n)?z:I)),r===t?o&&a&&"get"in o&&null!==(s=o.get(e,n))?s:(typeof e.getAttribute!==i&&(s=e.getAttribute(n)),null==s?t:s):null!==r?o&&a&&"set"in o&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r):(b.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=b.propFix[n]||n,J.test(n)?!Q&&G.test(n)?e[b.camelCase("default-"+n)]=e[r]=!1:e[r]=!1:b.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!b.support.radioValue&&"radio"===t&&b.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!b.isXMLDoc(e),a&&(n=b.propFix[n]||n,o=b.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 n=e.getAttributeNode("tabindex");return n&&n.specified?parseInt(n.value,10):V.test(e.nodeName)||Y.test(e.nodeName)&&e.href?0:t}}}}),z={get:function(e,n){var r=b.prop(e,n),i="boolean"==typeof r&&e.getAttribute(n),o="boolean"==typeof r?K&&Q?null!=i:G.test(n)?e[b.camelCase("default-"+n)]:!!i:e.getAttributeNode(n);return o&&o.value!==!1?n.toLowerCase():t},set:function(e,t,n){return t===!1?b.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&b.propFix[n]||n,n):e[b.camelCase("default-"+n)]=e[n]=!0,n}},K&&Q||(b.attrHooks.value={get:function(e,n){var r=e.getAttributeNode(n);return b.nodeName(e,"input")?e.defaultValue:r&&r.specified?r.value:t},set:function(e,n,r){return b.nodeName(e,"input")?(e.defaultValue=n,t):I&&I.set(e,n,r)}}),Q||(I=b.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&("id"===n||"name"===n||"coords"===n?""!==r.value:r.specified)?r.value:t},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}},b.attrHooks.contenteditable={get:I.get,set:function(e,t,n){I.set(e,""===t?!1:t,n)}},b.each(["width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}})})),b.support.hrefNormalized||(b.each(["href","src","width","height"],function(e,n){b.attrHooks[n]=b.extend(b.attrHooks[n],{get:function(e){var r=e.getAttribute(n,2);return null==r?t:r}})}),b.each(["href","src"],function(e,t){b.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}})),b.support.style||(b.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),b.support.optSelected||(b.propHooks.selected=b.extend(b.propHooks.selected,{get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}})),b.support.enctype||(b.propFix.enctype="encoding"),b.support.checkOn||b.each(["radio","checkbox"],function(){b.valHooks[this]={get:function(e){return null===e.getAttribute("value")?"on":e.value}}}),b.each(["radio","checkbox"],function(){b.valHooks[this]=b.extend(b.valHooks[this],{set:function(e,n){return b.isArray(n)?e.checked=b.inArray(b(e).val(),n)>=0:t}})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}b.event={global:{},add:function(e,n,r,o,a){var s,u,l,c,p,f,d,h,g,m,y,v=b._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=b.guid++),(u=v.events)||(u=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof b===i||e&&b.event.triggered===e.type?t:b.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(w)||[""],l=n.length;while(l--)s=rt.exec(n[l])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),p=b.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=b.event.special[g]||{},d=b.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&b.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=u[g])||(h=u[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),b.event.global[g]=!0;e=null}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,p,f,d,h,g,m=b.hasData(e)&&b._data(e);if(m&&(c=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(s=rt.exec(t[l])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=b.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),u=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));u&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||b.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)b.event.remove(e,d+t[l],n,r,!0);b.isEmptyObject(c)&&(delete m.handle,b._removeData(e,"events"))}},trigger:function(n,r,i,a){var s,u,l,c,p,f,d,h=[i||o],g=y.call(n,"type")?n.type:n,m=y.call(n,"namespace")?n.namespace.split("."):[];if(l=f=i=i||o,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+b.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),u=0>g.indexOf(":")&&"on"+g,n=n[b.expando]?n:new b.Event(g,"object"==typeof n&&n),n.isTrigger=!0,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]:b.makeArray(r,[n]),p=b.event.special[g]||{},a||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!a&&!p.noBubble&&!b.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(l=l.parentNode);l;l=l.parentNode)h.push(l),f=l;f===(i.ownerDocument||o)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((l=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(b._data(l,"events")||{})[n.type]&&b._data(l,"handle"),s&&s.apply(l,r),s=u&&l[u],s&&b.acceptData(l)&&s.apply&&s.apply(l,r)===!1&&n.preventDefault();if(n.type=g,!(a||n.isDefaultPrevented()||p._default&&p._default.apply(i.ownerDocument,r)!==!1||"click"===g&&b.nodeName(i,"a")||!b.acceptData(i)||!u||!i[g]||b.isWindow(i))){f=i[u],f&&(i[u]=null),b.event.triggered=g;try{i[g]()}catch(v){}b.event.triggered=t,f&&(i[u]=f)}return n.result}},dispatch:function(e){e=b.event.fix(e);var n,r,i,o,a,s=[],u=h.call(arguments),l=(b._data(this,"events")||{})[e.type]||[],c=b.event.special[e.type]||{};if(u[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=b.event.handlers.call(this,e,l),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=((b.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,u),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=[],u=n.delegateCount,l=e.target;if(u&&l.nodeType&&(!e.button||"click"!==e.type))for(;l!=this;l=l.parentNode||this)if(1===l.nodeType&&(l.disabled!==!0||"click"!==e.type)){for(o=[],a=0;u>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?b(r,this).index(l)>=0:b.find(r,this,null,[l]).length),o[r]&&o.push(i);o.length&&s.push({elem:l,handlers:o})}return n.length>u&&s.push({elem:this,handlers:n.slice(u)}),s},fix:function(e){if(e[b.expando])return e;var t,n,r,i=e.type,a=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 b.Event(a),t=r.length;while(t--)n=r[t],e[n]=a[n];return e.target||(e.target=a.srcElement||o),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,a):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,a,s=n.button,u=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||o,a=i.documentElement,r=i.body,e.pageX=n.clientX+(a&&a.scrollLeft||r&&r.scrollLeft||0)-(a&&a.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(a&&a.scrollTop||r&&r.scrollTop||0)-(a&&a.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&u&&(e.relatedTarget=u===e.target?n.toElement:u),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},click:{trigger:function(){return b.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t}},focus:{trigger:function(){if(this!==o.activeElement&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===o.activeElement&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=b.extend(new b.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?b.event.trigger(i,null,t):b.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},b.removeEvent=o.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))},b.Event=function(e,n){return this instanceof b.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&&b.extend(this,n),this.timeStamp=e&&e.timeStamp||b.now(),this[b.expando]=!0,t):new b.Event(e,n)},b.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()}},b.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){b.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;
+return(!i||i!==r&&!b.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),b.support.submitBubbles||(b.event.special.submit={setup:function(){return b.nodeName(this,"form")?!1:(b.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=b.nodeName(n,"input")||b.nodeName(n,"button")?n.form:t;r&&!b._data(r,"submitBubbles")&&(b.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),b._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&b.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return b.nodeName(this,"form")?!1:(b.event.remove(this,"._submit"),t)}}),b.support.changeBubbles||(b.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(b.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),b.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),b.event.simulate("change",this,e,!0)})),!1):(b.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!b._data(t,"changeBubbles")&&(b.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||b.event.simulate("change",this.parentNode,e,!0)}),b._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 b.event.remove(this,"._change"),!Z.test(this.nodeName)}}),b.support.focusinBubbles||b.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){b.event.simulate(t,e.target,b.event.fix(e),!0)};b.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),b.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 b().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=b.guid++)),this.each(function(){b.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,b(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(){b.event.remove(this,e,r,n)})},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)},trigger:function(e,t){return this.each(function(){b.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?b.event.trigger(e,n,r,!0):t}}),function(e,t){var n,r,i,o,a,s,u,l,c,p,f,d,h,g,m,y,v,x="sizzle"+-new Date,w=e.document,T={},N=0,C=0,k=it(),E=it(),S=it(),A=typeof t,j=1<<31,D=[],L=D.pop,H=D.push,q=D.slice,M=D.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},_="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=F.replace("w","w#"),B="([*^$|!~]?=)",P="\\["+_+"*("+F+")"+_+"*(?:"+B+_+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+O+")|)|)"+_+"*\\]",R=":("+F+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+P.replace(3,8)+")*)|.*)\\)|)",W=RegExp("^"+_+"+|((?:^|[^\\\\])(?:\\\\.)*)"+_+"+$","g"),$=RegExp("^"+_+"*,"+_+"*"),I=RegExp("^"+_+"*([\\x20\\t\\r\\n\\f>+~])"+_+"*"),z=RegExp(R),X=RegExp("^"+O+"$"),U={ID:RegExp("^#("+F+")"),CLASS:RegExp("^\\.("+F+")"),NAME:RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:RegExp("^("+F.replace("w","w*")+")"),ATTR:RegExp("^"+P),PSEUDO:RegExp("^"+R),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+_+"*(even|odd|(([+-]|)(\\d*)n|)"+_+"*(?:([+-]|)"+_+"*(\\d+)|))"+_+"*\\)|)","i"),needsContext:RegExp("^"+_+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+_+"*((?:-\\d)?\\d*)"+_+"*\\)|)(?=[^-]|$)","i")},V=/[\x20\t\r\n\f]*[+~]/,Y=/^[^{]+\{\s*\[native code/,J=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,G=/^(?:input|select|textarea|button)$/i,Q=/^h\d$/i,K=/'|\\/g,Z=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,et=/\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g,tt=function(e,t){var n="0x"+t-65536;return n!==n?t:0>n?String.fromCharCode(n+65536):String.fromCharCode(55296|n>>10,56320|1023&n)};try{q.call(w.documentElement.childNodes,0)[0].nodeType}catch(nt){q=function(e){var t,n=[];while(t=this[e++])n.push(t);return n}}function rt(e){return Y.test(e+"")}function it(){var e,t=[];return e=function(n,r){return t.push(n+=" ")>i.cacheLength&&delete e[t.shift()],e[n]=r}}function ot(e){return e[x]=!0,e}function at(e){var t=p.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}}function st(e,t,n,r){var i,o,a,s,u,l,f,g,m,v;if((t?t.ownerDocument||t:w)!==p&&c(t),t=t||p,n=n||[],!e||"string"!=typeof e)return n;if(1!==(s=t.nodeType)&&9!==s)return[];if(!d&&!r){if(i=J.exec(e))if(a=i[1]){if(9===s){if(o=t.getElementById(a),!o||!o.parentNode)return n;if(o.id===a)return n.push(o),n}else if(t.ownerDocument&&(o=t.ownerDocument.getElementById(a))&&y(t,o)&&o.id===a)return n.push(o),n}else{if(i[2])return H.apply(n,q.call(t.getElementsByTagName(e),0)),n;if((a=i[3])&&T.getByClassName&&t.getElementsByClassName)return H.apply(n,q.call(t.getElementsByClassName(a),0)),n}if(T.qsa&&!h.test(e)){if(f=!0,g=x,m=t,v=9===s&&e,1===s&&"object"!==t.nodeName.toLowerCase()){l=ft(e),(f=t.getAttribute("id"))?g=f.replace(K,"\\$&"):t.setAttribute("id",g),g="[id='"+g+"'] ",u=l.length;while(u--)l[u]=g+dt(l[u]);m=V.test(e)&&t.parentNode||t,v=l.join(",")}if(v)try{return H.apply(n,q.call(m.querySelectorAll(v),0)),n}catch(b){}finally{f||t.removeAttribute("id")}}}return wt(e.replace(W,"$1"),t,n,r)}a=st.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},c=st.setDocument=function(e){var n=e?e.ownerDocument||e:w;return n!==p&&9===n.nodeType&&n.documentElement?(p=n,f=n.documentElement,d=a(n),T.tagNameNoComments=at(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),T.attributes=at(function(e){e.innerHTML="<select></select>";var t=typeof e.lastChild.getAttribute("multiple");return"boolean"!==t&&"string"!==t}),T.getByClassName=at(function(e){return e.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",e.getElementsByClassName&&e.getElementsByClassName("e").length?(e.lastChild.className="e",2===e.getElementsByClassName("e").length):!1}),T.getByName=at(function(e){e.id=x+0,e.innerHTML="<a name='"+x+"'></a><div name='"+x+"'></div>",f.insertBefore(e,f.firstChild);var t=n.getElementsByName&&n.getElementsByName(x).length===2+n.getElementsByName(x+0).length;return T.getIdNotName=!n.getElementById(x),f.removeChild(e),t}),i.attrHandle=at(function(e){return e.innerHTML="<a href='#'></a>",e.firstChild&&typeof e.firstChild.getAttribute!==A&&"#"===e.firstChild.getAttribute("href")})?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},T.getIdNotName?(i.find.ID=function(e,t){if(typeof t.getElementById!==A&&!d){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){return e.getAttribute("id")===t}}):(i.find.ID=function(e,n){if(typeof n.getElementById!==A&&!d){var r=n.getElementById(e);return r?r.id===e||typeof r.getAttributeNode!==A&&r.getAttributeNode("id").value===e?[r]:t:[]}},i.filter.ID=function(e){var t=e.replace(et,tt);return function(e){var n=typeof e.getAttributeNode!==A&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=T.tagNameNoComments?function(e,n){return typeof n.getElementsByTagName!==A?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},i.find.NAME=T.getByName&&function(e,n){return typeof n.getElementsByName!==A?n.getElementsByName(name):t},i.find.CLASS=T.getByClassName&&function(e,n){return typeof n.getElementsByClassName===A||d?t:n.getElementsByClassName(e)},g=[],h=[":focus"],(T.qsa=rt(n.querySelectorAll))&&(at(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||h.push("\\["+_+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||h.push(":checked")}),at(function(e){e.innerHTML="<input type='hidden' i=''/>",e.querySelectorAll("[i^='']").length&&h.push("[*^$]="+_+"*(?:\"\"|'')"),e.querySelectorAll(":enabled").length||h.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),h.push(",.*:")})),(T.matchesSelector=rt(m=f.matchesSelector||f.mozMatchesSelector||f.webkitMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&at(function(e){T.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",R)}),h=RegExp(h.join("|")),g=RegExp(g.join("|")),y=rt(f.contains)||f.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},v=f.compareDocumentPosition?function(e,t){var r;return e===t?(u=!0,0):(r=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t))?1&r||e.parentNode&&11===e.parentNode.nodeType?e===n||y(w,e)?-1:t===n||y(w,t)?1:0:4&r?-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 u=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:0;if(o===a)return ut(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?ut(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},u=!1,[0,0].sort(v),T.detectDuplicates=u,p):p},st.matches=function(e,t){return st(e,null,null,t)},st.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Z,"='$1']"),!(!T.matchesSelector||d||g&&g.test(t)||h.test(t)))try{var n=m.call(e,t);if(n||T.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(r){}return st(t,p,null,[e]).length>0},st.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},st.attr=function(e,t){var n;return(e.ownerDocument||e)!==p&&c(e),d||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):d||T.attributes?e.getAttribute(t):((n=e.getAttributeNode(t))||e.getAttribute(t))&&e[t]===!0?t:n&&n.specified?n.value:null},st.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},st.uniqueSort=function(e){var t,n=[],r=1,i=0;if(u=!T.detectDuplicates,e.sort(v),u){for(;t=e[r];r++)t===e[r-1]&&(i=n.push(r));while(i--)e.splice(n[i],1)}return e};function ut(e,t){var n=t&&e,r=n&&(~t.sourceIndex||j)-(~e.sourceIndex||j);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function lt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ct(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function pt(e){return ot(function(t){return t=+t,ot(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]))})})}o=st.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+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=st.selectors={cacheLength:50,createPseudo:ot,match:U,find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(et,tt),e[3]=(e[4]||e[5]||"").replace(et,tt),"~="===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]||st.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]&&st.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return U.CHILD.test(e[0])?null:(e[4]?e[2]=e[4]:n&&z.test(n)&&(t=ft(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){return"*"===e?function(){return!0}:(e=e.replace(et,tt).toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[e+" "];return t||(t=RegExp("(^|"+_+")"+e+"("+_+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==A&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=st.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,u){var l,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!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[x]||(m[x]={}),l=c[e]||[],d=l[0]===N&&l[1],f=l[0]===N&&l[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]=[N,d,f];break}}else if(v&&(l=(t[x]||(t[x]={}))[e])&&l[0]===N)f=l[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[x]||(p[x]={}))[e]=[N,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||st.error("unsupported pseudo: "+e);return r[x]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?ot(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=M.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ot(function(e){var t=[],n=[],r=s(e.replace(W,"$1"));return r[x]?ot(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:ot(function(e){return function(t){return st(e,t).length>0}}),contains:ot(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:ot(function(e){return X.test(e||"")||st.error("unsupported lang: "+e),e=e.replace(et,tt).toLowerCase(),function(t){var n;do if(n=d?t.getAttribute("xml:lang")||t.getAttribute("lang"):t.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===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.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!i.pseudos.empty(e)},header:function(e){return Q.test(e.nodeName)},input:function(e){return G.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:pt(function(){return[0]}),last:pt(function(e,t){return[t-1]}),eq:pt(function(e,t,n){return[0>n?n+t:n]}),even:pt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:pt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:pt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:pt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[n]=lt(n);for(n in{submit:!0,reset:!0})i.pseudos[n]=ct(n);function ft(e,t){var n,r,o,a,s,u,l,c=E[e+" "];if(c)return t?0:c.slice(0);s=e,u=[],l=i.preFilter;while(s){(!n||(r=$.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),u.push(o=[])),n=!1,(r=I.exec(s))&&(n=r.shift(),o.push({value:n,type:r[0].replace(W," ")}),s=s.slice(n.length));for(a in i.filter)!(r=U[a].exec(s))||l[a]&&!(r=l[a](r))||(n=r.shift(),o.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?st.error(e):E(e,u).slice(0)}function dt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function ht(e,t,n){var i=t.dir,o=n&&"parentNode"===i,a=C++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,s){var u,l,c,p=N+" "+a;if(s){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[x]||(t[x]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,s)||r,l[1]===!0)return!0}}function gt(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 mt(e,t,n,r,i){var o,a=[],s=0,u=e.length,l=null!=t;for(;u>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),l&&t.push(s));return a}function yt(e,t,n,r,i,o){return r&&!r[x]&&(r=yt(r)),i&&!i[x]&&(i=yt(i,o)),ot(function(o,a,s,u){var l,c,p,f=[],d=[],h=a.length,g=o||xt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:mt(g,f,e,s,u),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,u),r){l=mt(y,d),r(l,[],s,u),c=l.length;while(c--)(p=l[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?M.call(o,p):f[c])>-1&&(o[l]=!(a[l]=p))}}else y=mt(y===a?y.splice(h,y.length):y),i?i(null,a,y,u):H.apply(a,y)})}function vt(e){var t,n,r,o=e.length,a=i.relative[e[0].type],s=a||i.relative[" "],u=a?1:0,c=ht(function(e){return e===t},s,!0),p=ht(function(e){return M.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==l)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>u;u++)if(n=i.relative[e[u].type])f=[ht(gt(f),n)];else{if(n=i.filter[e[u].type].apply(null,e[u].matches),n[x]){for(r=++u;o>r;r++)if(i.relative[e[r].type])break;return yt(u>1&&gt(f),u>1&&dt(e.slice(0,u-1)).replace(W,"$1"),n,r>u&&vt(e.slice(u,r)),o>r&&vt(e=e.slice(r)),o>r&&dt(e))}f.push(n)}return gt(f)}function bt(e,t){var n=0,o=t.length>0,a=e.length>0,s=function(s,u,c,f,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,T=l,C=s||a&&i.find.TAG("*",d&&u.parentNode||u),k=N+=null==T?1:Math.random()||.1;for(w&&(l=u!==p&&u,r=n);null!=(h=C[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,u,c)){f.push(h);break}w&&(N=k,r=++n)}o&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,o&&b!==v){g=0;while(m=t[g++])m(x,y,u,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=L.call(f));y=mt(y)}H.apply(f,y),w&&!s&&y.length>0&&v+t.length>1&&st.uniqueSort(f)}return w&&(N=k,l=T),x};return o?ot(s):s}s=st.compile=function(e,t){var n,r=[],i=[],o=S[e+" "];if(!o){t||(t=ft(e)),n=t.length;while(n--)o=vt(t[n]),o[x]?r.push(o):i.push(o);o=S(e,bt(i,r))}return o};function xt(e,t,n){var r=0,i=t.length;for(;i>r;r++)st(e,t[r],n);return n}function wt(e,t,n,r){var o,a,u,l,c,p=ft(e);if(!r&&1===p.length){if(a=p[0]=p[0].slice(0),a.length>2&&"ID"===(u=a[0]).type&&9===t.nodeType&&!d&&i.relative[a[1].type]){if(t=i.find.ID(u.matches[0].replace(et,tt),t)[0],!t)return n;e=e.slice(a.shift().value.length)}o=U.needsContext.test(e)?0:a.length;while(o--){if(u=a[o],i.relative[l=u.type])break;if((c=i.find[l])&&(r=c(u.matches[0].replace(et,tt),V.test(a[0].type)&&t.parentNode||t))){if(a.splice(o,1),e=r.length&&dt(a),!e)return H.apply(n,q.call(r,0)),n;break}}}return s(e,p)(r,t,d,n,V.test(e)),n}i.pseudos.nth=i.pseudos.eq;function Tt(){}i.filters=Tt.prototype=i.pseudos,i.setFilters=new Tt,c(),st.attr=b.attr,b.find=st,b.expr=st.selectors,b.expr[":"]=b.expr.pseudos,b.unique=st.uniqueSort,b.text=st.getText,b.isXMLDoc=st.isXML,b.contains=st.contains}(e);var at=/Until$/,st=/^(?:parents|prev(?:Until|All))/,ut=/^.[^:#\[\.,]*$/,lt=b.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};b.fn.extend({find:function(e){var t,n,r,i=this.length;if("string"!=typeof e)return r=this,this.pushStack(b(e).filter(function(){for(t=0;i>t;t++)if(b.contains(r[t],this))return!0}));for(n=[],t=0;i>t;t++)b.find(e,this[t],n);return n=this.pushStack(i>1?b.unique(n):n),n.selector=(this.selector?this.selector+" ":"")+e,n},has:function(e){var t,n=b(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(b.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e,!1))},filter:function(e){return this.pushStack(ft(this,e,!0))},is:function(e){return!!e&&("string"==typeof e?lt.test(e)?b(e,this.context).index(this[0])>=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,o=[],a=lt.test(e)||"string"!=typeof e?b(e,t||this.context):0;for(;i>r;r++){n=this[r];while(n&&n.ownerDocument&&n!==t&&11!==n.nodeType){if(a?a.index(n)>-1:b.find.matchesSelector(n,e)){o.push(n);break}n=n.parentNode}}return this.pushStack(o.length>1?b.unique(o):o)},index:function(e){return e?"string"==typeof e?b.inArray(this[0],b(e)):b.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?b(e,t):b.makeArray(e&&e.nodeType?[e]:e),r=b.merge(this.get(),n);return this.pushStack(b.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),b.fn.andSelf=b.fn.addBack;function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}b.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(e,t,n){return b.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(e,t,n){return b.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return b.dir(e,"previousSibling",n)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.merge([],e.childNodes)}},function(e,t){b.fn[e]=function(n,r){var i=b.map(this,t,n);return at.test(e)||(r=n),r&&"string"==typeof r&&(i=b.filter(r,i)),i=this.length>1&&!ct[e]?b.unique(i):i,this.length>1&&st.test(e)&&(i=i.reverse()),this.pushStack(i)}}),b.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),1===t.length?b.find.matchesSelector(t[0],e)?[t[0]]:[]:b.find.matches(e,t)},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!b(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(t=t||0,b.isFunction(t))return b.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return b.grep(e,function(e){return e===t===n});if("string"==typeof t){var r=b.grep(e,function(e){return 1===e.nodeType});if(ut.test(t))return b.filter(t,r,!n);t=b.filter(t,r)}return b.grep(e,function(e){return b.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,Nt=/^(?:checkbox|radio)$/i,Ct=/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:b.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(o),Dt=jt.appendChild(o.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,b.fn.extend({text:function(e){return b.access(this,function(e){return e===t?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e))return this.each(function(t){b(this).wrapAll(e.call(this,t))});if(this[0]){var t=b(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 b.isFunction(e)?this.each(function(t){b(this).wrapInner(e.call(this,t))}):this.each(function(){var t=b(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=b.isFunction(e);return this.each(function(n){b(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){b.nodeName(this,"body")||b(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&this.insertBefore(e,this.firstChild)})},before:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,!1,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=0;for(;null!=(n=this[r]);r++)(!e||b.filter(e,[n]).length>0)&&(t||1!==n.nodeType||b.cleanData(Ot(n)),n.parentNode&&(t&&b.contains(n.ownerDocument,n)&&Mt(Ot(n,"script")),n.parentNode.removeChild(n)));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&b.cleanData(Ot(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&b.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 b.clone(this,e,t)})},html:function(e){return b.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)||!b.support.htmlSerialize&&mt.test(e)||!b.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&&(b.cleanData(Ot(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(e){var t=b.isFunction(e);return t||"string"==typeof e||(e=b(e).not(this).detach()),this.domManip([e],!0,function(e){var t=this.nextSibling,n=this.parentNode;n&&(b(this).remove(),n.insertBefore(e,t))})},detach:function(e){return this.remove(e,!0)},domManip:function(e,n,r){e=f.apply([],e);var i,o,a,s,u,l,c=0,p=this.length,d=this,h=p-1,g=e[0],m=b.isFunction(g);if(m||!(1>=p||"string"!=typeof g||b.support.checkClone)&&Ct.test(g))return this.each(function(i){var o=d.eq(i);m&&(e[0]=g.call(this,i,n?o.html():t)),o.domManip(e,n,r)});if(p&&(l=b.buildFragment(e,this[0].ownerDocument,!1,this),i=l.firstChild,1===l.childNodes.length&&(l=i),i)){for(n=n&&b.nodeName(i,"tr"),s=b.map(Ot(l,"script"),Ht),a=s.length;p>c;c++)o=l,c!==h&&(o=b.clone(o,!0,!0),a&&b.merge(s,Ot(o,"script"))),r.call(n&&b.nodeName(this[c],"table")?Lt(this[c],"tbody"):this[c],o,c);if(a)for(u=s[s.length-1].ownerDocument,b.map(s,qt),c=0;a>c;c++)o=s[c],kt.test(o.type||"")&&!b._data(o,"globalEval")&&b.contains(u,o)&&(o.src?b.ajax({url:o.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):b.globalEval((o.text||o.textContent||o.innerHTML||"").replace(St,"")));l=i=null}return this}});function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function Ht(e){var t=e.getAttributeNode("type");return e.type=(t&&t.specified)+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function Mt(e,t){var n,r=0;for(;null!=(n=e[r]);r++)b._data(n,"globalEval",!t||b._data(t[r],"globalEval"))}function _t(e,t){if(1===t.nodeType&&b.hasData(e)){var n,r,i,o=b._data(e),a=b._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++)b.event.add(t,n,s[n][r])}a.data&&(a.data=b.extend({},a.data))}}function Ft(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!b.support.noCloneEvent&&t[b.expando]){i=b._data(t);for(r in i.events)b.removeEvent(t,r,i.handle);t.removeAttribute(b.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),b.support.html5Clone&&e.innerHTML&&!b.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Nt.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)}}b.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){b.fn[e]=function(e){var n,r=0,i=[],o=b(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),b(o[r])[t](n),d.apply(i,n.get());return this.pushStack(i)}});function Ot(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||b.nodeName(o,n)?s.push(o):b.merge(s,Ot(o,n));return n===t||n&&b.nodeName(e,n)?b.merge([e],s):s}function Bt(e){Nt.test(e.type)&&(e.defaultChecked=e.checked)}b.extend({clone:function(e,t,n){var r,i,o,a,s,u=b.contains(e.ownerDocument,e);if(b.support.html5Clone||b.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(b.support.noCloneEvent&&b.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||b.isXMLDoc(e)))for(r=Ot(o),s=Ot(e),a=0;null!=(i=s[a]);++a)r[a]&&Ft(i,r[a]);if(t)if(n)for(s=s||Ot(e),r=r||Ot(o),a=0;null!=(i=s[a]);a++)_t(i,r[a]);else _t(e,o);return r=Ot(o,"script"),r.length>0&&Mt(r,!u&&Ot(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,u,l,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===b.type(o))b.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),u=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[u]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!b.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!b.support.tbody){o="table"!==u||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)b.nodeName(l=o.childNodes[i],"tbody")&&!l.childNodes.length&&o.removeChild(l)
+}b.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),b.support.appendChecked||b.grep(Ot(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===b.inArray(o,r))&&(a=b.contains(o.ownerDocument,o),s=Ot(f.appendChild(o),"script"),a&&Mt(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,u=b.expando,l=b.cache,p=b.support.deleteExpando,f=b.event.special;for(;null!=(n=e[s]);s++)if((t||b.acceptData(n))&&(o=n[u],a=o&&l[o])){if(a.events)for(r in a.events)f[r]?b.event.remove(n,r):b.removeEvent(n,r,a.handle);l[o]&&(delete l[o],p?delete n[u]:typeof n.removeAttribute!==i?n.removeAttribute(u):n[u]=null,c.push(o))}}});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("^("+x+")(.*)$","i"),Yt=RegExp("^("+x+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+x+")","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"===b.css(e,"display")||!b.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]=b._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=b._data(r,"olddisplay",un(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&b._data(r,"olddisplay",i?n:b.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}b.fn.extend({css:function(e,n){return b.access(this,function(e,n,r){var i,o,a={},s=0;if(b.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=b.css(e,n[s],!1,o);return a}return r!==t?b.style(e,n,r):b.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?b(this).show():b(this).hide()})}}),b.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,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,u=b.camelCase(n),l=e.style;if(n=b.cssProps[u]||(b.cssProps[u]=tn(l,u)),s=b.cssHooks[n]||b.cssHooks[u],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:l[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(b.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||b.cssNumber[u]||(r+="px"),b.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(l[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{l[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,u=b.camelCase(n);return n=b.cssProps[u]||(b.cssProps[u]=tn(e.style,u)),s=b.cssHooks[n]||b.cssHooks[u],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||b.isNumeric(o)?o||0:a):a},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}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s.getPropertyValue(n)||s[n]:t,l=e.style;return s&&(""!==u||b.contains(e.ownerDocument,e)||(u=b.style(e,n)),Yt.test(u)&&Ut.test(n)&&(i=l.width,o=l.minWidth,a=l.maxWidth,l.minWidth=l.maxWidth=l.width=u,u=s.width,l.width=i,l.minWidth=o,l.maxWidth=a)),u}):o.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),u=s?s[n]:t,l=e.style;return null==u&&l&&l[n]&&(u=l[n]),Yt.test(u)&&!zt.test(n)&&(i=l.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),l.left="fontSize"===n?"1em":u,u=l.pixelLeft+"px",l.left=i,a&&(o.left=a)),""===u?"auto":u});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+=b.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=b.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=b.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=b.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=b.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=b.support.boxSizing&&"border-box"===b.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&&(b.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function un(e){var t=o,n=Gt[e];return n||(n=ln(e,t),"none"!==n&&n||(Pt=(Pt||b("<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=ln(e,t),Pt.detach()),Gt[e]=n),n}function ln(e,t){var n=b(t.createElement(e)).appendTo(t.body),r=b.css(n[0],"display");return n.remove(),r}b.each(["height","width"],function(e,n){b.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(b.css(e,"display"))?b.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,b.support.boxSizing&&"border-box"===b.css(e,"boxSizing",!1,i),i):0)}}}),b.support.opacity||(b.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=b.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===b.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),b(function(){b.support.reliableMarginRight||(b.cssHooks.marginRight={get:function(e,n){return n?b.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!b.support.pixelPosition&&b.fn.position&&b.each(["top","left"],function(e,n){b.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?b(e).position()[n]+"px":r):t}}})}),b.expr&&b.expr.filters&&(b.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!b.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||b.css(e,"display"))},b.expr.filters.visible=function(e){return!b.expr.filters.hidden(e)}),b.each({margin:"",padding:"",border:"Width"},function(e,t){b.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)||(b.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;b.fn.extend({serialize:function(){return b.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=b.prop(this,"elements");return e?b.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!b(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Nt.test(e))}).map(function(e,t){var n=b(this).val();return null==n?null:b.isArray(n)?b.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),b.param=function(e,n){var r,i=[],o=function(e,t){t=b.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=b.ajaxSettings&&b.ajaxSettings.traditional),b.isArray(e)||e.jquery&&!b.isPlainObject(e))b.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(b.isArray(t))b.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"!==b.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}b.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){b.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),b.fn.hover=function(e,t){return this.mouseenter(e).mouseleave(t||e)};var mn,yn,vn=b.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Nn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Cn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=b.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=a.href}catch(Ln){yn=o.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(w)||[];if(b.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(u){var l;return o[u]=!0,b.each(e[u]||[],function(e,u){var c=u(n,r,i);return"string"!=typeof c||a||o[c]?a?!(l=c):t:(n.dataTypes.unshift(c),s(c),!1)}),l}return s(n.dataTypes[0])||!o["*"]&&s("*")}function Mn(e,n){var r,i,o=b.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&b.extend(!0,e,r),e}b.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,u=e.indexOf(" ");return u>=0&&(i=e.slice(u,e.length),e=e.slice(0,u)),b.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&b.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?b("<div>").append(b.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},b.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){b.fn[t]=function(e){return this.on(t,e)}}),b.each(["get","post"],function(e,n){b[n]=function(e,r,i,o){return b.isFunction(r)&&(o=o||i,i=r,r=t),b.ajax({url:e,type:n,dataType:o,data:r,success:i})}}),b.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Nn.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"},converters:{"* text":e.String,"text html":!0,"text json":b.parseJSON,"text xml":b.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Mn(Mn(e,b.ajaxSettings),t):Mn(b.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,u,l,c,p=b.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?b(f):b.event,h=b.Deferred(),g=b.Callbacks("once memory"),m=p.statusCode||{},y={},v={},x=0,T="canceled",N={readyState:0,getResponseHeader:function(e){var t;if(2===x){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===x?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return x||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return x||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>x)for(t in e)m[t]=[m[t],e[t]];else N.always(e[N.status]);return this},abort:function(e){var t=e||T;return l&&l.abort(t),k(0,t),this}};if(h.promise(N).complete=g.add,N.success=N.done,N.error=N.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=b.trim(p.dataType||"*").toLowerCase().match(w)||[""],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=b.param(p.data,p.traditional)),qn(An,p,n,N),2===x)return N;u=p.global,u&&0===b.active++&&b.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Cn.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&&(b.lastModified[o]&&N.setRequestHeader("If-Modified-Since",b.lastModified[o]),b.etag[o]&&N.setRequestHeader("If-None-Match",b.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&N.setRequestHeader("Content-Type",p.contentType),N.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)N.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,N,p)===!1||2===x))return N.abort();T="abort";for(i in{success:1,error:1,complete:1})N[i](p[i]);if(l=qn(jn,p,n,N)){N.readyState=1,u&&d.trigger("ajaxSend",[N,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){N.abort("timeout")},p.timeout));try{x=1,l.send(y,k)}catch(C){if(!(2>x))throw C;k(-1,C)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,C=n;2!==x&&(x=2,s&&clearTimeout(s),l=t,a=i||"",N.readyState=e>0?4:0,r&&(w=_n(p,N,r)),e>=200&&300>e||304===e?(p.ifModified&&(T=N.getResponseHeader("Last-Modified"),T&&(b.lastModified[o]=T),T=N.getResponseHeader("etag"),T&&(b.etag[o]=T)),204===e?(c=!0,C="nocontent"):304===e?(c=!0,C="notmodified"):(c=Fn(p,w),C=c.state,y=c.data,v=c.error,c=!v)):(v=C,(e||!C)&&(C="error",0>e&&(e=0))),N.status=e,N.statusText=(n||C)+"",c?h.resolveWith(f,[y,C,N]):h.rejectWith(f,[N,C,v]),N.statusCode(m),m=t,u&&d.trigger(c?"ajaxSuccess":"ajaxError",[N,p,c?y:v]),g.fireWith(f,[N,C]),u&&(d.trigger("ajaxComplete",[N,p]),--b.active||b.event.trigger("ajaxStop")))}return N},getScript:function(e,n){return b.get(e,t,n,"script")},getJSON:function(e,t,n){return b.get(e,t,n,"json")}});function _n(e,n,r){var i,o,a,s,u=e.contents,l=e.dataTypes,c=e.responseFields;for(s in c)s in r&&(n[c[s]]=r[s]);while("*"===l[0])l.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in u)if(u[s]&&u[s].test(o)){l.unshift(s);break}if(l[0]in r)a=l[0];else{for(s in r){if(!l[0]||e.converters[s+" "+l[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==l[0]&&l.unshift(a),r[a]):t}function Fn(e,t){var n,r,i,o,a={},s=0,u=e.dataTypes.slice(),l=u[0];if(e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u[1])for(i in e.converters)a[i.toLowerCase()]=e.converters[i];for(;r=u[++s];)if("*"!==r){if("*"!==l&&l!==r){if(i=a[l+" "+r]||a["* "+r],!i)for(n in a)if(o=n.split(" "),o[1]===r&&(i=a[l+" "+o[0]]||a["* "+o[0]])){i===!0?i=a[n]:a[n]!==!0&&(r=o[0],u.splice(s--,0,r));break}if(i!==!0)if(i&&e["throws"])t=i(t);else try{t=i(t)}catch(c){return{state:"parsererror",error:i?c:"No conversion from "+l+" to "+r}}}l=r}return{state:"success",data:t}}b.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return b.globalEval(e),e}}}),b.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),b.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=o.head||b("head")[0]||o.documentElement;return{send:function(t,i){n=o.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 On=[],Bn=/(=)\?(?=&|$)|\?\?/;b.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=On.pop()||b.expando+"_"+vn++;return this[e]=!0,e}}),b.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,u=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 u||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=b.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,u?n[u]=n[u].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||b.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,On.push(o)),s&&b.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){}}b.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=b.ajaxSettings.xhr(),b.support.cors=!!Rn&&"withCredentials"in Rn,Rn=b.support.ajax=!!Rn,Rn&&b.ajaxTransport(function(n){if(!n.crossDomain||b.support.cors){var r;return{send:function(i,o){var a,s,u=n.xhr();if(n.username?u.open(n.type,n.url,n.async,n.username,n.password):u.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)u[s]=n.xhrFields[s];n.mimeType&&u.overrideMimeType&&u.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)u.setRequestHeader(s,i[s])}catch(l){}u.send(n.hasContent&&n.data||null),r=function(e,i){var s,l,c,p;try{if(r&&(i||4===u.readyState))if(r=t,a&&(u.onreadystatechange=b.noop,$n&&delete Pn[a]),i)4!==u.readyState&&u.abort();else{p={},s=u.status,l=u.getAllResponseHeaders(),"string"==typeof u.responseText&&(p.text=u.responseText);try{c=u.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,l)},n.async?4===u.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},b(e).unload($n)),Pn[a]=r),u.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+x+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n,r,i=this.createTween(e,t),o=Yn.exec(t),a=i.cur(),s=+a||0,u=1,l=20;if(o){if(n=+o[2],r=o[3]||(b.cssNumber[e]?"":"px"),"px"!==r&&s){s=b.css(i.elem,e,!0)||n||1;do u=u||".5",s/=u,b.style(i.elem,e,s+r);while(u!==(u=i.cur()/a)&&1!==u&&--l)}i.unit=r,i.start=s,i.end=o[1]?s+(o[1]+1)*n:n}return i}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=b.now()}function Zn(e,t){b.each(t,function(t,n){var r=(Qn[t]||[]).concat(Qn["*"]),i=0,o=r.length;for(;o>i;i++)if(r[i].call(e,t,n))return})}function er(e,t,n){var r,i,o=0,a=Gn.length,s=b.Deferred().always(function(){delete u.elem}),u=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,l.startTime+l.duration-t),r=n/l.duration||0,o=1-r,a=0,u=l.tweens.length;for(;u>a;a++)l.tweens[a].run(o);return s.notifyWith(e,[l,o,n]),1>o&&u?n:(s.resolveWith(e,[l]),!1)},l=s.promise({elem:e,props:b.extend({},t),opts:b.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=b.Tween(e,l.opts,t,n,l.opts.specialEasing[t]||l.opts.easing);return l.tweens.push(r),r},stop:function(t){var n=0,r=t?l.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)l.tweens[n].run(1);return t?s.resolveWith(e,[l,t]):s.rejectWith(e,[l,t]),this}}),c=l.props;for(tr(c,l.opts.specialEasing);a>o;o++)if(r=Gn[o].call(l,e,c,l.opts))return r;return Zn(l,c),b.isFunction(l.opts.start)&&l.opts.start.call(e,l),b.fx.timer(b.extend(u,{elem:e,anim:l,queue:l.opts.queue})),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always)}function tr(e,t){var n,r,i,o,a;for(i in e)if(r=b.camelCase(i),o=t[r],n=e[i],b.isArray(n)&&(o=n[1],n=e[i]=n[0]),i!==r&&(e[r]=n,delete e[i]),a=b.cssHooks[r],a&&"expand"in a){n=a.expand(n),delete e[r];for(i in n)i in e||(e[i]=n[i],t[i]=o)}else t[r]=o}b.Animation=b.extend(er,{tweener:function(e,t){b.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,u,l,c,p,f=this,d=e.style,h={},g=[],m=e.nodeType&&nn(e);n.queue||(c=b._queueHooks(e,"fx"),null==c.unqueued&&(c.unqueued=0,p=c.empty.fire,c.empty.fire=function(){c.unqueued||p()}),c.unqueued++,f.always(function(){f.always(function(){c.unqueued--,b.queue(e,"fx").length||c.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[d.overflow,d.overflowX,d.overflowY],"inline"===b.css(e,"display")&&"none"===b.css(e,"float")&&(b.support.inlineBlockNeedsLayout&&"inline"!==un(e.nodeName)?d.zoom=1:d.display="inline-block")),n.overflow&&(d.overflow="hidden",b.support.shrinkWrapBlocks||f.always(function(){d.overflow=n.overflow[0],d.overflowX=n.overflow[1],d.overflowY=n.overflow[2]}));for(i in t)if(a=t[i],Vn.exec(a)){if(delete t[i],u=u||"toggle"===a,a===(m?"hide":"show"))continue;g.push(i)}if(o=g.length){s=b._data(e,"fxshow")||b._data(e,"fxshow",{}),"hidden"in s&&(m=s.hidden),u&&(s.hidden=!m),m?b(e).show():f.done(function(){b(e).hide()}),f.done(function(){var t;b._removeData(e,"fxshow");for(t in h)b.style(e,t,h[t])});for(i=0;o>i;i++)r=g[i],l=f.createTween(r,m?s[r]:0),h[r]=s[r]||b.style(e,r),r in s||(s[r]=l.start,m&&(l.end=l.start,l.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}b.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||(b.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?b.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=b.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){b.fx.step[e.prop]?b.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[b.cssProps[e.prop]]||b.cssHooks[e.prop])?b.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)}},b.each(["toggle","show","hide"],function(e,t){var n=b.fn[t];b.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),b.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=b.isEmptyObject(e),o=b.speed(t,n,r),a=function(){var t=er(this,b.extend({},e),o);a.finish=function(){t.stop(!0)},(i||b._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=b.timers,a=b._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)&&b.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=b._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=b.timers,a=r?r.length:0;for(n.finish=!0,b.queue(this,e,[]),i&&i.cur&&i.cur.finish&&i.cur.finish.call(this),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}b.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){b.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),b.speed=function(e,t,n){var r=e&&"object"==typeof e?b.extend({},e):{complete:n||!n&&t||b.isFunction(e)&&e,duration:e,easing:n&&t||t&&!b.isFunction(t)&&t};return r.duration=b.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in b.fx.speeds?b.fx.speeds[r.duration]:b.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){b.isFunction(r.old)&&r.old.call(this),r.queue&&b.dequeue(this,r.queue)},r},b.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},b.timers=[],b.fx=rr.prototype.init,b.fx.tick=function(){var e,n=b.timers,r=0;for(Xn=b.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||b.fx.stop(),Xn=t},b.fx.timer=function(e){e()&&b.timers.push(e)&&b.fx.start()},b.fx.interval=13,b.fx.start=function(){Un||(Un=setInterval(b.fx.tick,b.fx.interval))},b.fx.stop=function(){clearInterval(Un),Un=null},b.fx.speeds={slow:600,fast:200,_default:400},b.fx.step={},b.expr&&b.expr.filters&&(b.expr.filters.animated=function(e){return b.grep(b.timers,function(t){return e===t.elem}).length}),b.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){b.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,b.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},b.offset={setOffset:function(e,t,n){var r=b.css(e,"position");"static"===r&&(e.style.position="relative");var i=b(e),o=i.offset(),a=b.css(e,"top"),s=b.css(e,"left"),u=("absolute"===r||"fixed"===r)&&b.inArray("auto",[a,s])>-1,l={},c={},p,f;u?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),b.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(l.top=t.top-o.top+p),null!=t.left&&(l.left=t.left-o.left+f),"using"in t?t.using.call(e,l):i.css(l)}},b.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===b.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),b.nodeName(e[0],"html")||(n=e.offset()),n.top+=b.css(e[0],"borderTopWidth",!0),n.left+=b.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-b.css(r,"marginTop",!0),left:t.left-n.left-b.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||o.documentElement;while(e&&!b.nodeName(e,"html")&&"static"===b.css(e,"position"))e=e.offsetParent;return e||o.documentElement})}}),b.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);b.fn[e]=function(i){return b.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?b(a).scrollLeft():o,r?o:b(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return b.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}b.each({Height:"height",Width:"width"},function(e,n){b.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){b.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return b.access(this,function(n,r,i){var o;return b.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?b.css(n,r,s):b.style(n,r,i,s)},n,a?i:t,a,null)}})}),e.jQuery=e.$=b,"function"==typeof define&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return b})})(window);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/catalog/ChangeParent.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,284 @@
+
+package nabble.view.web.catalog;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.model.export.Export;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import nabble.view.web.template.UrlMapperNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class ChangeParent extends HttpServlet implements AuthorizingServlet {
+	private static final Logger logger = LoggerFactory.getLogger(ChangeParent.class);
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"forum")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response);
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String forumId = request.getParameter("forum");
+		Node forum = forumId == null? null : Jtp.getSiteNotNull(request).getNode(Long.valueOf(forumId));
+
+		if (forum == null)
+			return;
+
+		Person visitor = Jtp.getVisitor(request, response);
+		boolean isSiteAdmin = visitor instanceof User && Permissions.isInGroup((User) visitor, Permissions.ADMINISTRATORS_GROUP);
+
+		boolean allowed = Jtp.canBeRemovedBy(forum,visitor);
+		if (!allowed) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		String errorMsg = null;
+		String action = request.getParameter("action");
+		String option = request.getParameter("option");
+		String url = request.getParameter("url");
+		if ("set".equals(action) && "POST".equals(request.getMethod())) {
+			if ("provide-url".equals(option))
+				errorMsg = setParent(forum, url, request, response);
+			else if ("merge".equals(option))
+				errorMsg = merge(forum, visitor, isSiteAdmin, response);
+			else if ("delete-root".equals(option))
+				errorMsg = deleteRoot(forum, visitor, response);
+			else if ("create-parent".equals(option))
+				errorMsg = createParent(forum, (User)visitor, request, response);
+			if (errorMsg == null)
+				return;
+		}
+
+		Node currentParent = forum.getParent();
+		
+		out.print( "\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request, response, "Parent Options"); 
+		out.print( "\r\n		<script type=\"text/javascript\">\r\n			$(document).ready(function() {\r\n				function handleFocus() {\r\n					$('#url,#name,#description,#type').attr('disabled','y');\r\n					var id = $(':radio').filter('[checked]').attr('id');\r\n					if (id == 'choose-parent') {\r\n						$('#url').removeAttr('disabled').focus();\r\n					} else if (id == 'create-parent') {\r\n						$('#name,#description,#type').removeAttr('disabled').focus();\r\n						$('#name').focus();\r\n					}\r\n				};\r\n				handleFocus();\r\n				$(':radio').click(handleFocus);\r\n			});\r\n		</script>\r\n		<style type=\"text/css\">\r\n			div.field-title {\r\n				font-weight:bold;\r\n				font-size:110%;\r\n				padding-bottom:.1em;\r\n				margin-top: 1.7em;\r\n			}\r\n			span.disabled {\r\n				font-size:90%;\r\n				font-weight:normal;\r\n				margin-left:1em\r\n			}\r\n		</style>\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.minHeader(request,response, forum); 
+		out.print( "\r\n		" );
+ Shared.editHeader(forum.getSubjectHtml(), "Parent Options", out); 
+		out.print( "\r\n		" );
+ Shared.errorMessage(request, response, errorMsg, null); 
+		out.print( "\r\n		<form id=\"parent-form\" method=\"post\" action=\"/catalog/ChangeParent.jtp\" accept-charset=\"UTF-8\">\r\n			<input type=\"hidden\" name=\"action\" value=\"set\" />\r\n			<input type=\"hidden\" name=\"forum\" value=\"" );
+		out.print( (forum.getId()) );
+		out.print( "\" />\r\n\r\n			<div class=\"second-font field-title\">\r\n				<input type=\"radio\" id=\"choose-parent\" name=\"option\" value=\"provide-url\" " );
+		out.print( (option == null || "provide-url".equals(option)? "checked='y'" : "") );
+		out.print( "/>\r\n				<label for=\"choose-parent\">Set a New Parent</label>\r\n			</div>\r\n			<div class=\"weak-color\" style=\"margin-left:1.9em\">\r\n				Enter the permalink of the new parent:<br/>\r\n				<input id=\"url\" name=\"url\" size=\"60\" value=\"" );
+		out.print( (Jtp.hideNull(url)) );
+		out.print( "\"/>\r\n			</div>\r\n\r\n			" );
+ boolean isDisabled = !isSiteAdmin || currentParent == null || !Jtp.canBeDeletedBy(forum,visitor); 
+		out.print( "\r\n			<div class=\"second-font field-title\">\r\n				<input type=\"radio\" " );
+		out.print( (isDisabled?"disabled":"") );
+		out.print( " id=\"merge\" name=\"option\" value=\"merge\" " );
+		out.print( ("merge".equals(option)? "checked='y'" : "") );
+		out.print( "/>\r\n				<label for=\"merge\" " );
+		out.print( (isDisabled?"class='weak-color'":"") );
+		out.print( ">Merge into " );
+		out.print( (Jtp.parentName(forum)) );
+		out.print( "</label>\r\n				" );
+		out.print( (isDisabled? "<span class='disabled important'>/ Not Applicable</span>":"") );
+		out.print( "\r\n			</div>\r\n			<div class=\"weak-color\" style=\"margin-left:1.9em\">\r\n				Delete this " );
+		out.print( (Jtp.viewName(forum).toLowerCase()) );
+		out.print( " and move its contents to the " );
+		out.print( (Jtp.parentName(forum).toLowerCase()) );
+		out.print( ". <br/>\r\n			</div>\r\n\r\n			" );
+ isDisabled = !forum.isRoot() || forum.getChildCount() != 1; 
+		out.print( "\r\n			" );
+ Node child = isDisabled? null : forum.getChildren().get(0, 1).get(0); 
+		out.print( "\r\n			" );
+ isDisabled = child != null && child.getKind() == Node.Kind.POST? true : isDisabled; 
+		out.print( "\r\n			<div class=\"second-font field-title\">\r\n				<input type=\"radio\" " );
+		out.print( (isDisabled?"disabled":"") );
+		out.print( " id=\"delete-root\" name=\"option\" value=\"delete-root\" " );
+		out.print( ("delete-root".equals(option)? "checked='y'" : "") );
+		out.print( "/>\r\n				<label for=\"delete-root\" " );
+		out.print( (isDisabled?"class='weak-color'":"") );
+		out.print( ">Make Child the New Root</label>\r\n				" );
+		out.print( (isDisabled? "<span class='disabled important'>/ Not Applicable</span>":"") );
+		out.print( "\r\n			</div>\r\n			<div class=\"weak-color\" style=\"margin-left:1.9em\">\r\n				Delete this " );
+		out.print( (Jtp.viewName(forum).toLowerCase()) );
+		out.print( " and make " );
+		out.print( (child == null? "the single child" : child.getSubjectHtml() ) );
+		out.print( " the new root. <br/>\r\n			</div>\r\n\r\n			" );
+ isDisabled = forum.getParent() != null; 
+		out.print( "\r\n			<div class=\"second-font field-title\">\r\n				<input type=\"radio\" id=\"create-parent\" name=\"option\" value=\"create-parent\" " );
+		out.print( ("create-parent".equals(option)? "checked='y'" : "") );
+		out.print( " " );
+		out.print( (isDisabled?"disabled":"") );
+		out.print( "/>\r\n				<label " );
+		out.print( (isDisabled?"class='weak-color'":"") );
+		out.print( " for=\"create-parent\">Create a New Parent</label>\r\n				" );
+		out.print( (isDisabled? "<span class='disabled important'>/ Not Applicable</span>":"") );
+		out.print( "\r\n			</div>\r\n			<div class=\"weak-color\" style=\"margin-left:1.9em\">\r\n				<table>\r\n					<tr>\r\n						<td>Name:</td>\r\n						<td><input id=\"name\" " );
+		out.print( (isDisabled?"disabled":"") );
+		out.print( " name=\"name\" size=\"30\" value=\"" );
+		out.print( (Jtp.hideNull(request.getParameter("name"))) );
+		out.print( "\" /></td>\r\n					</tr>\r\n					<tr>\r\n						<td>Type:</td>\r\n						<td>\r\n							<select id=\"type\" name=\"type\">\r\n									<option value=\"" );
+		out.print( (Node.Type.FORUM) );
+		out.print( "\">Forum</option>\r\n									<option value=\"" );
+		out.print( (Node.Type.CATEGORY) );
+		out.print( "\">Category</option>\r\n									<option value=\"" );
+		out.print( (Node.Type.BOARD) );
+		out.print( "\">Board</option>\r\n									<option value=\"" );
+		out.print( (Node.Type.MIXED) );
+		out.print( "\">Mixed</option>\r\n									<option value=\"" );
+		out.print( (Node.Type.GALLERY) );
+		out.print( "\">Gallery</option>\r\n									<option value=\"" );
+		out.print( (Node.Type.BLOG) );
+		out.print( "\">Blog</option>\r\n									<option value=\"" );
+		out.print( (Node.Type.NEWS) );
+		out.print( "\">Newspaper</option>\r\n							</select>\r\n						</td>\r\n					</tr>\r\n				</table>\r\n				<div style=\"margin: .5em 0\">\r\n					Description:<br/>\r\n					<textarea id=\"description\" " );
+		out.print( (isDisabled?"disabled":"") );
+		out.print( " name=\"description\" style=\"width:30em;height:10em\">" );
+		out.print( (Jtp.hideNull(request.getParameter("description"))) );
+		out.print( "</textarea>\r\n				</div>\r\n			</div>\r\n\r\n			<div style=\"margin-top:1.4em\">\r\n				<input type=\"submit\" name=\"save\" value=\"Save Changes\" /> or <a href=\"" );
+		out.print( (Jtp.path(forum)) );
+		out.print( "\">Cancel</a>\r\n			</div>\r\n		</form>\r\n\r\n		" );
+ Shared.footer(request, response); 
+		out.print( "\r\n		" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+
+	private static String setParent(Node app, String url, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		if (url == null || url.trim().length() == 0 || !url.startsWith("http://")) {
+			return "You must provide a valid link.";
+		} else {
+			url = Jtp.noCid(url);
+			Node parent = UrlMapperNamespace.getNodeFromUrl(url);
+			if (parent == null || parent.getKind()!=Node.Kind.APP || !parent.getSite().equals(app.getSite())) {
+				if (Export.isValidExportServer(url)) {
+					// Send to export confirmation page
+					response.sendRedirect("/catalog/ExportConfirmation.jtp?node="+app.getId()+"&url="+HtmlUtils.urlEncode(url));
+					return null;
+				} else {
+					return "The link you provided is not a valid Nabble application.";
+				}
+			} else if (parent.getSite().equals(app.getSite()) && parent.getAncestors().contains(app)) {
+				return "Circular relationship is not allowed.";
+			} else {
+				DbDatabase db = app.getSite().getDb();
+				db.beginTransaction();
+				try {
+					Node forumCopy = app.getGoodCopy();
+					forumCopy.changeParent(parent);
+					forumCopy.update();
+					db.commitTransaction();
+
+					forumCopy = forumCopy.getGoodCopy();
+					Shared.javascriptRedirect(request, response, Jtp.url(forumCopy), null, true);
+					return null;
+				} catch(ModelException.NodeLoop e) {
+					if (parent.equals(app))
+						return "The new parent cannot be the forum itself.";
+					else
+						return "The new parent cannot be a descendant of the current forum (circular relationship).";
+				} catch (ModelException e) {
+					logger.error("",e);
+					return "You cannot move this forum here because: "+e.getMessage();
+				} finally {
+					db.endTransaction();
+				}
+			}
+		}
+	}
+
+	private static String merge(Node app, Person visitor, boolean isSiteAdmin, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		if (isSiteAdmin || Jtp.canBeDeletedBy(app,visitor)) {
+			DbDatabase db = app.getSite().getDb();
+			db.beginTransaction();
+			try {
+				app = app.getGoodCopy();
+				Node parent = app.getParent();
+				for (Node child : app.getChildren()) {
+					child.changeParent(parent);
+				}
+				app.getGoodCopy().deleteMessageOrNode();
+				db.commitTransaction();
+				response.sendRedirect(Jtp.path(parent));
+				return null;
+			} catch (ModelException e) {
+				return e.getMessage();
+			} finally {
+				db.endTransaction();
+			}
+		} else {
+			return "You don't have privileges to delete and merge this sub-forum";
+		}
+	}
+
+	private static String deleteRoot(Node app, Person visitor, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		if (Jtp.canBeDeletedBy(app,visitor)) {
+			DbDatabase db = app.getSite().getDb();
+			db.beginTransaction();
+			try {
+				Site site = app.getSite();
+				site.deleteRootNode();
+				db.commitTransaction();
+				response.sendRedirect(site.getBaseUrl());
+				return null;
+			} catch (ModelException e) {
+				return e.getMessage();
+			} finally {
+				db.endTransaction();
+			}
+		} else {
+			return "You don't have privileges to make the child a new root";
+		}
+	}
+
+	private static String createParent(Node app, User user, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		DbDatabase db = app.getSite().getDb();
+		db.beginTransaction();
+		try {
+			String name = request.getParameter("name");
+			if (name == null || name.trim().length() == 0)
+				return "Please enter a valid name";
+			String description = request.getParameter("description");
+			String type = request.getParameter("type");
+			Node parent = user.newRootNode(Node.Kind.APP, name, description, Message.Format.TEXT, app.getSite(),type);
+			Jtp.addPinnedChild(parent, app.getGoodCopy());
+			db.commitTransaction();
+			response.sendRedirect(Jtp.path(parent));
+			return null;
+		} catch (ModelException e) {
+			return e.getMessage();
+		} finally {
+			db.endTransaction();
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/catalog/ChangeParent.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,312 @@
+<%
+package nabble.view.web.catalog;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.model.export.Export;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import nabble.view.web.template.UrlMapperNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class ChangeParent extends HttpServlet implements AuthorizingServlet {
+	private static final Logger logger = LoggerFactory.getLogger(ChangeParent.class);
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"forum")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response);
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String forumId = request.getParameter("forum");
+		Node forum = forumId == null? null : Jtp.getSiteNotNull(request).getNode(Long.valueOf(forumId));
+
+		if (forum == null)
+			return;
+
+		Person visitor = Jtp.getVisitor(request, response);
+		boolean isSiteAdmin = visitor instanceof User && Permissions.isInGroup((User) visitor, Permissions.ADMINISTRATORS_GROUP);
+
+		boolean allowed = Jtp.canBeRemovedBy(forum,visitor);
+		if (!allowed) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		String errorMsg = null;
+		String action = request.getParameter("action");
+		String option = request.getParameter("option");
+		String url = request.getParameter("url");
+		if ("set".equals(action) && "POST".equals(request.getMethod())) {
+			if ("provide-url".equals(option))
+				errorMsg = setParent(forum, url, request, response);
+			else if ("merge".equals(option))
+				errorMsg = merge(forum, visitor, isSiteAdmin, response);
+			else if ("delete-root".equals(option))
+				errorMsg = deleteRoot(forum, visitor, response);
+			else if ("create-parent".equals(option))
+				errorMsg = createParent(forum, (User)visitor, request, response);
+			if (errorMsg == null)
+				return;
+		}
+
+		Node currentParent = forum.getParent();
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<% Shared.title(request, response, "Parent Options"); %>
+				<script type="text/javascript">
+					$(document).ready(function() {
+						function handleFocus() {
+							$('#url,#name,#description,#type').attr('disabled','y');
+							var id = $(':radio').filter('[checked]').attr('id');
+							if (id == 'choose-parent') {
+								$('#url').removeAttr('disabled').focus();
+							} else if (id == 'create-parent') {
+								$('#name,#description,#type').removeAttr('disabled').focus();
+								$('#name').focus();
+							}
+						};
+						handleFocus();
+						$(':radio').click(handleFocus);
+					});
+				</script>
+				<style type="text/css">
+					div.field-title {
+						font-weight:bold;
+						font-size:110%;
+						padding-bottom:.1em;
+						margin-top: 1.7em;
+					}
+					span.disabled {
+						font-size:90%;
+						font-weight:normal;
+						margin-left:1em
+					}
+				</style>
+			</head>
+			<body>
+				<% Shared.minHeader(request,response, forum); %>
+				<% Shared.editHeader(forum.getSubjectHtml(), "Parent Options", out); %>
+				<% Shared.errorMessage(request, response, errorMsg, null); %>
+				<form id="parent-form" method="post" action="/catalog/ChangeParent.jtp" accept-charset="UTF-8">
+					<input type="hidden" name="action" value="set" />
+					<input type="hidden" name="forum" value="<%=forum.getId()%>" />
+
+					<div class="second-font field-title">
+						<input type="radio" id="choose-parent" name="option" value="provide-url" <%=option == null || "provide-url".equals(option)? "checked='y'" : ""%>/>
+						<label for="choose-parent">Set a New Parent</label>
+					</div>
+					<div class="weak-color" style="margin-left:1.9em">
+						Enter the permalink of the new parent:<br/>
+						<input id="url" name="url" size="60" value="<%=Jtp.hideNull(url)%>"/>
+					</div>
+
+					<% boolean isDisabled = !isSiteAdmin || currentParent == null || !Jtp.canBeDeletedBy(forum,visitor); %>
+					<div class="second-font field-title">
+						<input type="radio" <%=isDisabled?"disabled":""%> id="merge" name="option" value="merge" <%="merge".equals(option)? "checked='y'" : ""%>/>
+						<label for="merge" <%=isDisabled?"class='weak-color'":""%>>Merge into <%=Jtp.parentName(forum)%></label>
+						<%=isDisabled? "<span class='disabled important'>/ Not Applicable</span>":""%>
+					</div>
+					<div class="weak-color" style="margin-left:1.9em">
+						Delete this <%=Jtp.viewName(forum).toLowerCase()%> and move its contents to the <%=Jtp.parentName(forum).toLowerCase()%>. <br/>
+					</div>
+
+					<% isDisabled = !forum.isRoot() || forum.getChildCount() != 1; %>
+					<% Node child = isDisabled? null : forum.getChildren().get(0, 1).get(0); %>
+					<% isDisabled = child != null && child.getKind() == Node.Kind.POST? true : isDisabled; %>
+					<div class="second-font field-title">
+						<input type="radio" <%=isDisabled?"disabled":""%> id="delete-root" name="option" value="delete-root" <%="delete-root".equals(option)? "checked='y'" : ""%>/>
+						<label for="delete-root" <%=isDisabled?"class='weak-color'":""%>>Make Child the New Root</label>
+						<%=isDisabled? "<span class='disabled important'>/ Not Applicable</span>":""%>
+					</div>
+					<div class="weak-color" style="margin-left:1.9em">
+						Delete this <%=Jtp.viewName(forum).toLowerCase()%> and make <%=child == null? "the single child" : child.getSubjectHtml() %> the new root. <br/>
+					</div>
+
+					<% isDisabled = forum.getParent() != null; %>
+					<div class="second-font field-title">
+						<input type="radio" id="create-parent" name="option" value="create-parent" <%="create-parent".equals(option)? "checked='y'" : ""%> <%=isDisabled?"disabled":""%>/>
+						<label <%=isDisabled?"class='weak-color'":""%> for="create-parent">Create a New Parent</label>
+						<%=isDisabled? "<span class='disabled important'>/ Not Applicable</span>":""%>
+					</div>
+					<div class="weak-color" style="margin-left:1.9em">
+						<table>
+							<tr>
+								<td>Name:</td>
+								<td><input id="name" <%=isDisabled?"disabled":""%> name="name" size="30" value="<%=Jtp.hideNull(request.getParameter("name"))%>" /></td>
+							</tr>
+							<tr>
+								<td>Type:</td>
+								<td>
+									<select id="type" name="type">
+											<option value="<%=Node.Type.FORUM%>">Forum</option>
+											<option value="<%=Node.Type.CATEGORY%>">Category</option>
+											<option value="<%=Node.Type.BOARD%>">Board</option>
+											<option value="<%=Node.Type.MIXED%>">Mixed</option>
+											<option value="<%=Node.Type.GALLERY%>">Gallery</option>
+											<option value="<%=Node.Type.BLOG%>">Blog</option>
+											<option value="<%=Node.Type.NEWS%>">Newspaper</option>
+									</select>
+								</td>
+							</tr>
+						</table>
+						<div style="margin: .5em 0">
+							Description:<br/>
+							<textarea id="description" <%=isDisabled?"disabled":""%> name="description" style="width:30em;height:10em"><%=Jtp.hideNull(request.getParameter("description"))%></textarea>
+						</div>
+					</div>
+
+					<div style="margin-top:1.4em">
+						<input type="submit" name="save" value="Save Changes" /> or <a href="<%=Jtp.path(forum)%>">Cancel</a>
+					</div>
+				</form>
+
+				<% Shared.footer(request, response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+
+	private static String setParent(Node app, String url, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		if (url == null || url.trim().length() == 0 || !url.startsWith("http://")) {
+			return "You must provide a valid link.";
+		} else {
+			url = Jtp.noCid(url);
+			Node parent = UrlMapperNamespace.getNodeFromUrl(url);
+			if (parent == null || parent.getKind()!=Node.Kind.APP || !parent.getSite().equals(app.getSite())) {
+				if (Export.isValidExportServer(url)) {
+					// Send to export confirmation page
+					response.sendRedirect("/catalog/ExportConfirmation.jtp?node="+app.getId()+"&url="+HtmlUtils.urlEncode(url));
+					return null;
+				} else {
+					return "The link you provided is not a valid Nabble application.";
+				}
+			} else if (parent.getSite().equals(app.getSite()) && parent.getAncestors().contains(app)) {
+				return "Circular relationship is not allowed.";
+			} else {
+				DbDatabase db = app.getSite().getDb();
+				db.beginTransaction();
+				try {
+					Node forumCopy = app.getGoodCopy();
+					forumCopy.changeParent(parent);
+					forumCopy.update();
+					db.commitTransaction();
+
+					forumCopy = forumCopy.getGoodCopy();
+					Shared.javascriptRedirect(request, response, Jtp.url(forumCopy), null, true);
+					return null;
+				} catch(ModelException.NodeLoop e) {
+					if (parent.equals(app))
+						return "The new parent cannot be the forum itself.";
+					else
+						return "The new parent cannot be a descendant of the current forum (circular relationship).";
+				} catch (ModelException e) {
+					logger.error("",e);
+					return "You cannot move this forum here because: "+e.getMessage();
+				} finally {
+					db.endTransaction();
+				}
+			}
+		}
+	}
+
+	private static String merge(Node app, Person visitor, boolean isSiteAdmin, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		if (isSiteAdmin || Jtp.canBeDeletedBy(app,visitor)) {
+			DbDatabase db = app.getSite().getDb();
+			db.beginTransaction();
+			try {
+				app = app.getGoodCopy();
+				Node parent = app.getParent();
+				for (Node child : app.getChildren()) {
+					child.changeParent(parent);
+				}
+				app.getGoodCopy().deleteMessageOrNode();
+				db.commitTransaction();
+				response.sendRedirect(Jtp.path(parent));
+				return null;
+			} catch (ModelException e) {
+				return e.getMessage();
+			} finally {
+				db.endTransaction();
+			}
+		} else {
+			return "You don't have privileges to delete and merge this sub-forum";
+		}
+	}
+
+	private static String deleteRoot(Node app, Person visitor, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		if (Jtp.canBeDeletedBy(app,visitor)) {
+			DbDatabase db = app.getSite().getDb();
+			db.beginTransaction();
+			try {
+				Site site = app.getSite();
+				site.deleteRootNode();
+				db.commitTransaction();
+				response.sendRedirect(site.getBaseUrl());
+				return null;
+			} catch (ModelException e) {
+				return e.getMessage();
+			} finally {
+				db.endTransaction();
+			}
+		} else {
+			return "You don't have privileges to make the child a new root";
+		}
+	}
+
+	private static String createParent(Node app, User user, HttpServletRequest request, HttpServletResponse response)
+		throws IOException, ServletException
+	{
+		DbDatabase db = app.getSite().getDb();
+		db.beginTransaction();
+		try {
+			String name = request.getParameter("name");
+			if (name == null || name.trim().length() == 0)
+				return "Please enter a valid name";
+			String description = request.getParameter("description");
+			String type = request.getParameter("type");
+			Node parent = user.newRootNode(Node.Kind.APP, name, description, Message.Format.TEXT, app.getSite(),type);
+			Jtp.addPinnedChild(parent, app.getGoodCopy());
+			db.commitTransaction();
+			response.sendRedirect(Jtp.path(parent));
+			return null;
+		} catch (ModelException e) {
+			return e.getMessage();
+		} finally {
+			db.endTransaction();
+		}
+	}
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/catalog/ChangePinOrder.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,229 @@
+
+package nabble.view.web.catalog;
+
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.Node;
+import nabble.model.NodeIterator;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class ChangePinOrder extends HttpServlet implements AuthorizingServlet {
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"forum")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response); 
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		Site site = Jtp.getSiteNotNull(request);
+		Node forum = site.getNode(Jtp.getLong(request,"forum"));
+		if (forum == null) {
+			response.sendError(HttpServletResponse.SC_GONE, "This app has been deleted.");
+			return;
+		}
+
+		String what = request.getParameter("what");
+
+		boolean pinnedThreads = "threads".equals(what);
+		String title = pinnedThreads? "Pinned Threads" : "Manage " + Jtp.childName(forum, true);
+
+		Person visitor = Jtp.getVisitor(request, response);
+
+		if (!Jtp.canBeEditedBy(forum,visitor)) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		List<Node> others = new ArrayList<Node>();
+		List<Node> pinned = new ArrayList<Node>();
+
+		NodeIterator<? extends Node> children = forum.getChildren();
+		for (Node node : children) {
+			if( !node.isPinned() )
+				break;
+
+			if (node.getKind()==Node.Kind.POST && pinnedThreads)
+				pinned.add(node);
+			else if (node.getKind()==Node.Kind.APP && !pinnedThreads)
+				pinned.add(node);
+			else
+				others.add(node);
+		}
+		children.close();
+
+		String action = request.getParameter("action");
+		boolean up = "up".equals(action);
+		boolean down = "down".equals(action);
+		if (up || down) {
+			long id = Long.parseLong(request.getParameter("id"));
+			Node node = site.getNode(id);
+			if (pinned.contains(node)) {
+				int index = pinned.indexOf(node);
+				pinned.remove(node);
+				index = up? index-1 : index+1;
+				if (index < 0)
+					index = 0;
+				else if (index > pinned.size())
+					index = pinned.size();
+				pinned.add(index, node);
+
+				List<Node> all = new ArrayList<Node>();
+				// Ensure that forums come first
+				if (pinnedThreads) {
+					all.addAll(others);					
+					all.addAll(pinned);
+				} else {
+					all.addAll(pinned);
+					all.addAll(others);
+				}
+				forum.pin(all.toArray(new Node[0]));
+
+				Jtp.sendRedirect(request,response,"/catalog/ChangePinOrder.jtp?forum=" + forum.getId() + "&what=" + what);
+				return;				
+			}
+		}
+
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request, response, "Manage " + title); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.minHeader(request,response, forum); 
+		out.print( "\r\n		" );
+ Shared.editHeader(forum.getSubjectHtml(), title, out); 
+		out.print( "\r\n		<style>\r\n			table.pin {\r\n				width: 40em;\r\n				border-collapse:collapse;\r\n			}\r\n\r\n			table.pin td {\r\n				padding: .4em;\r\n			}\r\n			\r\n			.title-row {\r\n				margin-top:2em;\r\n				padding:.2em;\r\n				border-bottom-width:2px;\r\n				border-bottom-style:solid;\r\n				font-weight:bold;\r\n			}\r\n		</style>\r\n\r\n		<div class=\"title-row light-border-color\">\r\n			" );
+		out.print( (pinnedThreads? "Pinned Topics" : "Pinned " + Jtp.childName(forum, true)) );
+		out.print( "\r\n		</div>\r\n\r\n		" );
+ if (!pinnedThreads) { 
+		out.print( "\r\n		<div class=\"weak-color\" style=\"margin:0 0 .3em .2em\">Pinned " );
+		out.print( (Jtp.childName(forum, true).toLowerCase()) );
+		out.print( " are created or approved by a forum owner to always appear under a forum.</div>\r\n		" );
+ } 
+		out.print( "\r\n		<div style=\"padding-left:2em\">\r\n			" );
+
+					if (pinned.size() > 1) {
+						
+		out.print( "\r\n<table id=\"table-pin\" class=\"pin\">\r\n	" );
+
+								for (int i = 0; i < pinned.size(); i++) {
+									Node node = pinned.get(i);
+									boolean isFirst = i == 0;
+									boolean isLast = i == pinned.size() - 1;
+									
+		out.print( "\r\n<tr>\r\n	<td style=\"width:24px\"><img src=\"/images/" );
+		out.print( (pinnedThreads? "pin.png" : "forum_pin.png") );
+		out.print( "\"></td>\r\n	<td style=\"padding-left: .4em;width:100%\"><a href=\"" );
+		out.print( (Jtp.path(node)) );
+		out.print( "\">" );
+		out.print( (node.getSubjectHtml()) );
+		out.print( "</a></td>\r\n	<td style=\"width:24px\">\r\n		<a class=\"up\" style=\"" );
+		out.print( (isFirst? "display:none" : "") );
+		out.print( "\" href=\"/catalog/ChangePinOrder.jtp?forum=" );
+		out.print( (forum.getId()) );
+		out.print( "&what=" );
+		out.print( (what) );
+		out.print( "&action=up&id=" );
+		out.print( (node.getId()) );
+		out.print( "\"><img src=\"" );
+		out.print( (context) );
+		out.print( "/images/icon_up.png\" style=\"border:none;margin:5px\" title=\"Move up\"/></a>\r\n	</td>\r\n	<td style=\"width:24px\">\r\n		<a class=\"down\" style=\"" );
+		out.print( (isLast? "display:none" : "") );
+		out.print( "\" href=\"/catalog/ChangePinOrder.jtp?forum=" );
+		out.print( (forum.getId()) );
+		out.print( "&what=" );
+		out.print( (what) );
+		out.print( "&action=down&id=" );
+		out.print( (node.getId()) );
+		out.print( "\"><img src=\"" );
+		out.print( (context) );
+		out.print( "/images/icon_down.png\" style=\"border:none;margin:5px\" title=\"Move down\"/></a>\r\n	</td>\r\n	<td style=\"width:5em\"><a href=\"/catalog/SetPin.jtp?node=" );
+		out.print( (node.getId()) );
+		out.print( "&value=unpin\">Unpin</a></td>\r\n</tr>\r\n" );
+
+								}
+							
+		out.print( "\r\n</table>\r\n" );
+
+					} else if (pinned.size() == 1) {
+						Node node = pinned.get(0);
+						
+		out.print( "\r\n<table class=\"pin\">\r\n	<tr>\r\n		<td style=\"width:24px\"><img src=\"/images/" );
+		out.print( (pinnedThreads? "pin.png" : "forum_pin.png") );
+		out.print( "\"></td>\r\n		<td><a href=\"" );
+		out.print( (Jtp.path(node)) );
+		out.print( "\">" );
+		out.print( (node.getSubjectHtml()) );
+		out.print( "</a></td>\r\n		<td style=\"width:5em\"><a href=\"/catalog/SetPin.jtp?node=" );
+		out.print( (node.getId()) );
+		out.print( "&value=unpin\">Unpin</a></td>\r\n	</tr>\r\n</table>\r\n" );
+
+					} else {
+						
+		out.print( "\r\n<div style=\"margin-top:1em\">\r\n	No " );
+		out.print( (pinnedThreads? "pinned topics" : "pinned " + Jtp.childName(forum, true).toLowerCase()) );
+		out.print( ".\r\n</div>\r\n" );
+
+					}
+					
+		out.print( "\r\n</div>\r\n\r\n" );
+
+				if (!pinnedThreads) {
+					// Search for unpinned child forums
+					List<Node> childForums = forum.getChildApps().get(0, 1000);
+					List<Node> unpinned = new ArrayList<Node>();
+					for (Node child : childForums) {
+						if (!child.isPinned())
+							unpinned.add(child);
+					}
+
+					if (!unpinned.isEmpty()) {
+						
+		out.print( "\r\n<div class=\"title-row light-border-color\">Unpinned Sub-forums</div>\r\n<div class=\"weak-color\" style=\"margin:0 0 .3em .2em\">Unpinned sub-forums are user-created categories which float under a forum like a thread.</div>\r\n<div style=\"padding-left:2em\">\r\n	<table class=\"pin\">\r\n		" );
+
+								for (Node node : unpinned) {
+									
+		out.print( "\r\n<tr>\r\n	<td style=\"width:30px\"><img src=\"/images/forum.png\"></td>										\r\n	<td style=\"width:100%\"><a href=\"" );
+		out.print( (Jtp.path(node)) );
+		out.print( "\">" );
+		out.print( (node.getSubjectHtml()) );
+		out.print( "</a></td>\r\n	<td style=\"width:24px\"><img src=\"/images/unpin.png\"/></td>\r\n	<td style=\"width:5em\"><a href=\"/catalog/SetPin.jtp?node=" );
+		out.print( (node.getId()) );
+		out.print( "&value=pin\">Pin</a></td>\r\n</tr>\r\n" );
+
+								}
+								
+		out.print( "\r\n</table>\r\n</div>\r\n" );
+
+					}
+				}
+				
+		out.print( "\r\n\r\n<div class=\"light-bg-color\" style=\"padding: .5em;margin:1.5em 0 0\">\r\n	<div class=\"second-font field-title\" style=\"margin-top:0\">Related Help Article</div>\r\n	" );
+		out.print( (Help.pinned_subapps.link()) );
+		out.print( "\r\n</div>\r\n\r\n" );
+ Shared.footer(request, response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/catalog/ChangePinOrder.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,235 @@
+<%
+package nabble.view.web.catalog;
+
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.Node;
+import nabble.model.NodeIterator;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class ChangePinOrder extends HttpServlet implements AuthorizingServlet {
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"forum")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response); 
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		Site site = Jtp.getSiteNotNull(request);
+		Node forum = site.getNode(Jtp.getLong(request,"forum"));
+		if (forum == null) {
+			response.sendError(HttpServletResponse.SC_GONE, "This app has been deleted.");
+			return;
+		}
+
+		String what = request.getParameter("what");
+
+		boolean pinnedThreads = "threads".equals(what);
+		String title = pinnedThreads? "Pinned Threads" : "Manage " + Jtp.childName(forum, true);
+
+		Person visitor = Jtp.getVisitor(request, response);
+
+		if (!Jtp.canBeEditedBy(forum,visitor)) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		List<Node> others = new ArrayList<Node>();
+		List<Node> pinned = new ArrayList<Node>();
+
+		NodeIterator<? extends Node> children = forum.getChildren();
+		for (Node node : children) {
+			if( !node.isPinned() )
+				break;
+
+			if (node.getKind()==Node.Kind.POST && pinnedThreads)
+				pinned.add(node);
+			else if (node.getKind()==Node.Kind.APP && !pinnedThreads)
+				pinned.add(node);
+			else
+				others.add(node);
+		}
+		children.close();
+
+		String action = request.getParameter("action");
+		boolean up = "up".equals(action);
+		boolean down = "down".equals(action);
+		if (up || down) {
+			long id = Long.parseLong(request.getParameter("id"));
+			Node node = site.getNode(id);
+			if (pinned.contains(node)) {
+				int index = pinned.indexOf(node);
+				pinned.remove(node);
+				index = up? index-1 : index+1;
+				if (index < 0)
+					index = 0;
+				else if (index > pinned.size())
+					index = pinned.size();
+				pinned.add(index, node);
+
+				List<Node> all = new ArrayList<Node>();
+				// Ensure that forums come first
+				if (pinnedThreads) {
+					all.addAll(others);					
+					all.addAll(pinned);
+				} else {
+					all.addAll(pinned);
+					all.addAll(others);
+				}
+				forum.pin(all.toArray(new Node[0]));
+
+				Jtp.sendRedirect(request,response,"/catalog/ChangePinOrder.jtp?forum=" + forum.getId() + "&what=" + what);
+				return;				
+			}
+		}
+
+		%>
+		<html>
+			<head>
+				<% Shared.title(request, response, "Manage " + title); %>
+			</head>
+			<body>
+				<% Shared.minHeader(request,response, forum); %>
+				<% Shared.editHeader(forum.getSubjectHtml(), title, out); %>
+				<style>
+					table.pin {
+						width: 40em;
+						border-collapse:collapse;
+					}
+
+					table.pin td {
+						padding: .4em;
+					}
+					
+					.title-row {
+						margin-top:2em;
+						padding:.2em;
+						border-bottom-width:2px;
+						border-bottom-style:solid;
+						font-weight:bold;
+					}
+				</style>
+
+				<div class="title-row light-border-color">
+					<%=pinnedThreads? "Pinned Topics" : "Pinned " + Jtp.childName(forum, true)%>
+				</div>
+
+				<% if (!pinnedThreads) { %>
+				<div class="weak-color" style="margin:0 0 .3em .2em">Pinned <%=Jtp.childName(forum, true).toLowerCase()%> are created or approved by a forum owner to always appear under a forum.</div>
+				<% } %>
+				<div style="padding-left:2em">
+					<%
+					if (pinned.size() > 1) {
+						%>
+						<table id="table-pin" class="pin">
+							<%
+								for (int i = 0; i < pinned.size(); i++) {
+									Node node = pinned.get(i);
+									boolean isFirst = i == 0;
+									boolean isLast = i == pinned.size() - 1;
+									%>
+									<tr>
+										<td style="width:24px"><img src="/images/<%=pinnedThreads? "pin.png" : "forum_pin.png"%>"></td>
+										<td style="padding-left: .4em;width:100%"><a href="<%=Jtp.path(node)%>"><%=node.getSubjectHtml()%></a></td>
+										<td style="width:24px">
+											<a class="up" style="<%=isFirst? "display:none" : ""%>" href="/catalog/ChangePinOrder.jtp?forum=<%=forum.getId()%>&what=<%=what%>&action=up&id=<%=node.getId()%>"><img src="<%=context%>/images/icon_up.png" style="border:none;margin:5px" title="Move up"/></a>
+										</td>
+										<td style="width:24px">
+											<a class="down" style="<%=isLast? "display:none" : ""%>" href="/catalog/ChangePinOrder.jtp?forum=<%=forum.getId()%>&what=<%=what%>&action=down&id=<%=node.getId()%>"><img src="<%=context%>/images/icon_down.png" style="border:none;margin:5px" title="Move down"/></a>
+										</td>
+										<td style="width:5em"><a href="/catalog/SetPin.jtp?node=<%=node.getId()%>&value=unpin">Unpin</a></td>
+									</tr>
+									<%
+								}
+							%>
+						</table>
+						<%
+					} else if (pinned.size() == 1) {
+						Node node = pinned.get(0);
+						%>
+						<table class="pin">
+							<tr>
+								<td style="width:24px"><img src="/images/<%=pinnedThreads? "pin.png" : "forum_pin.png"%>"></td>
+								<td><a href="<%=Jtp.path(node)%>"><%=node.getSubjectHtml()%></a></td>
+								<td style="width:5em"><a href="/catalog/SetPin.jtp?node=<%=node.getId()%>&value=unpin">Unpin</a></td>
+							</tr>
+						</table>
+						<%
+					} else {
+						%>
+						<div style="margin-top:1em">
+							No <%=pinnedThreads? "pinned topics" : "pinned " + Jtp.childName(forum, true).toLowerCase()%>.
+						</div>
+						<%
+					}
+					%>
+				</div>
+
+				<%
+				if (!pinnedThreads) {
+					// Search for unpinned child forums
+					List<Node> childForums = forum.getChildApps().get(0, 1000);
+					List<Node> unpinned = new ArrayList<Node>();
+					for (Node child : childForums) {
+						if (!child.isPinned())
+							unpinned.add(child);
+					}
+
+					if (!unpinned.isEmpty()) {
+						%>
+						<div class="title-row light-border-color">Unpinned Sub-forums</div>
+						<div class="weak-color" style="margin:0 0 .3em .2em">Unpinned sub-forums are user-created categories which float under a forum like a thread.</div>
+						<div style="padding-left:2em">
+							<table class="pin">
+								<%
+								for (Node node : unpinned) {
+									%>
+									<tr>
+										<td style="width:30px"><img src="/images/forum.png"></td>										
+										<td style="width:100%"><a href="<%=Jtp.path(node)%>"><%=node.getSubjectHtml()%></a></td>
+										<td style="width:24px"><img src="/images/unpin.png"/></td>
+										<td style="width:5em"><a href="/catalog/SetPin.jtp?node=<%=node.getId()%>&value=pin">Pin</a></td>
+									</tr>
+									<%
+								}
+								%>
+							</table>
+						</div>
+						<%
+					}
+				}
+				%>
+
+				<div class="light-bg-color" style="padding: .5em;margin:1.5em 0 0">
+					<div class="second-font field-title" style="margin-top:0">Related Help Article</div>
+					<%=Help.pinned_subapps.link()%>
+				</div>
+
+				<% Shared.footer(request, response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/catalog/ExportConfirmation.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,95 @@
+
+package nabble.view.web.catalog;
+
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.model.export.Export;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class ExportConfirmation extends HttpServlet implements AuthorizingServlet {
+	private static final Logger logger = LoggerFactory.getLogger(ExportConfirmation.class);
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"node")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response);
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String nodeId = request.getParameter("node");
+		Node node = Jtp.getSiteNotNull(request).getNode(Long.valueOf(nodeId));
+		if (node == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND);
+			return;
+		}
+
+		User user = Jtp.getUser(request, response);
+		boolean allowed = Jtp.canBeRemovedBy(node,user);
+		if (!allowed) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		String url = request.getParameter("url");
+		if (url == null || !Export.isValidExportServer(url)) {
+			logger.error("Invalid export URL: ["+url+"] referer="+request.getHeader("referer")+" user="+Jtp.getUser(request,response)+" user-agent="+request.getHeader("user-agent"));
+			return;
+		}
+
+		String action = request.getParameter("action");
+		if ("export".equals(action) && "POST".equals(request.getMethod())) {
+			logger.info("Starting export of node ID=" + node.getId() + " to [" + url + ']');
+			node.export(url, user.getEmail());
+			response.sendRedirect(url);
+			return;
+		}
+		
+		out.print( "\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n	<head>\n		" );
+ Shared.title(request, response, "Parent Options / Export Confirmation"); 
+		out.print( "\n	</head>\n	<body>\n		" );
+ Shared.minHeader(request,response, node); 
+		out.print( "\n		" );
+ Shared.editHeader(node.getSubjectHtml(), "Parent Options", out); 
+		out.print( "\n\n		<h2 style=\"margin-bottom:.6em\">Export Confirmation</h2>\n		You have requested the following URL to be the new parent of <em>\"" );
+		out.print( (node.getSubjectHtml()) );
+		out.print( "\"</em>:\n		<div class=\"highlight rounded\" style=\"padding:.5em;margin: .5em 0 1em;font-weight:bold\">\n			" );
+		out.print( (url) );
+		out.print( "\n		</div>\n		This new parent is not related to <em>" );
+		out.print( (node.getSubjectHtml()) );
+		out.print( "</em>,\n		so Nabble must migrate the contents of your " );
+		out.print( (Jtp.viewName(node).toLowerCase()) );
+		out.print( " to that new location.\n		You should understand that:\n		<ul>\n			<li style=\"padding-bottom:1em\">\n				<strong>This change may not happen immediately</strong>.\n				<div>\n					The system will have to move post by post to the new location and this may\n					take hours (or even days) to finish depending on the size of the moved elements.\n					You will be notified by email when the process is complete.\n				</div>\n			</li>\n			<li style=\"padding-bottom:1em\">\n				<strong>All links to the moved elements will change (including links to posts and replies).</strong>\n				<div>\n					<span class=\"important\" style=\"font-weight:bold\">Previous links will NOT work anymore.</span>\n					Users should update their links if they want to visit those pages again in the future.\n				</div>\n			</li>\n			<li style=\"padding-bottom:1em\">\n				<strong>Some user accounts may not be moved.</strong>\n				<div>\n					The system will move posts and all associated user accounts to the new destination.\n					Non-related users accounts will not be moved. \n				</div>\n			</li>\n			<li>\n				<strong>Customizations will not be moved.</strong>\n				<div>\n					Custom NAML code and other changes (e.g., font size, CSS and other settings from the <i>Change Appearance</i> page) will not be moved.\n				</div>\n			</li>\n		</ul>\n		<div style=\"margin-top:1.5em\">\n		Do you really want to start the export process for <em>" );
+		out.print( (node.getSubjectHtml()) );
+		out.print( "</em>?\n		</div>\n		\n		<form method=\"post\" action=\"/catalog/ExportConfirmation.jtp\" accept-charset=\"UTF-8\">\n			<input type=\"hidden\" name=\"action\" value=\"export\" />\n			<input type=\"hidden\" name=\"node\" value=\"" );
+		out.print( (node.getId()) );
+		out.print( "\" />\n			<input type=\"hidden\" name=\"url\" value=\"" );
+		out.print( (url) );
+		out.print( "\" />\n\n			<div style=\"margin-top:1.4em\">\n				<input type=\"submit\" name=\"save\" value=\"Yes, Export it\" /> or <a href=\"" );
+		out.print( (Jtp.path(node)) );
+		out.print( "\">Cancel</a>\n			</div>\n		</form>\n\n		" );
+ Shared.footer(request, response); 
+		out.print( "\n		" );
+ Shared.analytics(request,response); 
+		out.print( "\n	</body>\n</html>\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/catalog/ExportConfirmation.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,133 @@
+<%
+package nabble.view.web.catalog;
+
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.model.export.Export;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class ExportConfirmation extends HttpServlet implements AuthorizingServlet {
+	private static final Logger logger = LoggerFactory.getLogger(ExportConfirmation.class);
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"node")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response);
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String nodeId = request.getParameter("node");
+		Node node = Jtp.getSiteNotNull(request).getNode(Long.valueOf(nodeId));
+		if (node == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND);
+			return;
+		}
+
+		User user = Jtp.getUser(request, response);
+		boolean allowed = Jtp.canBeRemovedBy(node,user);
+		if (!allowed) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		String url = request.getParameter("url");
+		if (url == null || !Export.isValidExportServer(url)) {
+			logger.error("Invalid export URL: ["+url+"] referer="+request.getHeader("referer")+" user="+Jtp.getUser(request,response)+" user-agent="+request.getHeader("user-agent"));
+			return;
+		}
+
+		String action = request.getParameter("action");
+		if ("export".equals(action) && "POST".equals(request.getMethod())) {
+			logger.info("Starting export of node ID=" + node.getId() + " to [" + url + ']');
+			node.export(url, user.getEmail());
+			response.sendRedirect(url);
+			return;
+		}
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<% Shared.title(request, response, "Parent Options / Export Confirmation"); %>
+			</head>
+			<body>
+				<% Shared.minHeader(request,response, node); %>
+				<% Shared.editHeader(node.getSubjectHtml(), "Parent Options", out); %>
+
+				<h2 style="margin-bottom:.6em">Export Confirmation</h2>
+				You have requested the following URL to be the new parent of <em>"<%=node.getSubjectHtml()%>"</em>:
+				<div class="highlight rounded" style="padding:.5em;margin: .5em 0 1em;font-weight:bold">
+					<%=url%>
+				</div>
+				This new parent is not related to <em><%=node.getSubjectHtml()%></em>,
+				so Nabble must migrate the contents of your <%=Jtp.viewName(node).toLowerCase()%> to that new location.
+				You should understand that:
+				<ul>
+					<li style="padding-bottom:1em">
+						<strong>This change may not happen immediately</strong>.
+						<div>
+							The system will have to move post by post to the new location and this may
+							take hours (or even days) to finish depending on the size of the moved elements.
+							You will be notified by email when the process is complete.
+						</div>
+					</li>
+					<li style="padding-bottom:1em">
+						<strong>All links to the moved elements will change (including links to posts and replies).</strong>
+						<div>
+							<span class="important" style="font-weight:bold">Previous links will NOT work anymore.</span>
+							Users should update their links if they want to visit those pages again in the future.
+						</div>
+					</li>
+					<li style="padding-bottom:1em">
+						<strong>Some user accounts may not be moved.</strong>
+						<div>
+							The system will move posts and all associated user accounts to the new destination.
+							Non-related users accounts will not be moved. 
+						</div>
+					</li>
+					<li>
+						<strong>Customizations will not be moved.</strong>
+						<div>
+							Custom NAML code and other changes (e.g., font size, CSS and other settings from the <i>Change Appearance</i> page) will not be moved.
+						</div>
+					</li>
+				</ul>
+				<div style="margin-top:1.5em">
+				Do you really want to start the export process for <em><%=node.getSubjectHtml()%></em>?
+				</div>
+				
+				<form method="post" action="/catalog/ExportConfirmation.jtp" accept-charset="UTF-8">
+					<input type="hidden" name="action" value="export" />
+					<input type="hidden" name="node" value="<%=node.getId()%>" />
+					<input type="hidden" name="url" value="<%=url%>" />
+
+					<div style="margin-top:1.4em">
+						<input type="submit" name="save" value="Yes, Export it" /> or <a href="<%=Jtp.path(node)%>">Cancel</a>
+					</div>
+				</form>
+
+				<% Shared.footer(request, response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/catalog/SetPin.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,58 @@
+package nabble.view.web.catalog;
+
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.view.lib.Jtp;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+public class SetPin extends HttpServlet implements AuthorizingServlet {
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"node")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response);
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Person visitor = Jtp.getVisitor(request, response);
+
+		Node node = Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request, "node"));
+		if (!Jtp.canBeRemovedBy(node,visitor)) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		} else if (node.getParent() == null) {
+			// Parent was removed (e.g., user clicked on the back button and
+			// tried to pin/unpin a node on a dead parent)
+			response.sendError(HttpServletResponse.SC_GONE, "The parent of this node has been deleted. You cannot pin/unpin a node under a dead parent.");
+			return;
+		}
+		String value = request.getParameter("value");
+		boolean pin = "pin".equals(value);
+		if (pin)
+			Jtp.addPinnedChild(node.getParent(), node);
+		else {
+			Jtp.unpinChild(node.getParent(), node);
+		}
+
+		boolean isScript = "y".equals(request.getParameter("script"));
+		if (isScript) {
+			response.setHeader("Content-Type","application/x-javascript");
+		} else {
+			String what = node.getKind() == Node.Kind.APP? "forums" : "threads";
+			Jtp.sendRedirect(request,response,"/catalog/ChangePinOrder.jtp?forum=" + node.getParent().getId() + "&what=" + what);
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/embed/EmbedInfo.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,54 @@
+
+package nabble.view.web.embed;
+
+import nabble.model.Site;
+import nabble.model.Node;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class EmbedInfo extends HttpServlet {
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		Jtp.dontCache(response);
+
+		PrintWriter out = response.getWriter();
+		String nodeID = request.getParameter("node");
+		if (nodeID == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND);
+			return;
+		}
+
+		String clientID = request.getParameter("cid");
+		String hash = request.getParameter("hash");
+		String conf = request.getParameter("conf");
+
+		Site site = Jtp.getSite(request);
+		Node embeddedNode = site==null ? null : site.getNode(Long.parseLong(nodeID));
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<script src=\"" );
+		out.print( (Shared.getJQueryPath()) );
+		out.print( "\"></script>\r\n		<script type=\"text/javascript\">\r\n			var hash = \"" );
+		out.print( (hash) );
+		out.print( "\";\r\n			var what = '" );
+		out.print( (embeddedNode == null? "" : embeddedNode.getKind() == Node.Kind.APP? Jtp.viewName(embeddedNode).toLowerCase():"topic") );
+		out.print( "';\r\n			function canScroll() { return " );
+		out.print( (conf == null || conf.indexOf("noscroll") == -1) );
+		out.print( "; }\r\n\r\n			var clientID = '" );
+		out.print( (clientID) );
+		out.print( "';\r\n\r\n			" );
+/* Runs when everything here is ready */
+		out.print( "\r\n			function start() {\r\n				try {\r\n					parent.nabbleready.location = \"/util/Empty.jtp\";\r\n				} catch(err) {\r\n					setTimeout(start, 100);\r\n				}\r\n			}\r\n			start();\r\n		</script>\r\n	</head>\r\n	<body>\r\n	</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/embed/EmbedInfo.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,65 @@
+<%
+package nabble.view.web.embed;
+
+import nabble.model.Site;
+import nabble.model.Node;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class EmbedInfo extends HttpServlet {
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		Jtp.dontCache(response);
+
+		PrintWriter out = response.getWriter();
+		String nodeID = request.getParameter("node");
+		if (nodeID == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND);
+			return;
+		}
+
+		String clientID = request.getParameter("cid");
+		String hash = request.getParameter("hash");
+		String conf = request.getParameter("conf");
+
+		Site site = Jtp.getSite(request);
+		Node embeddedNode = site==null ? null : site.getNode(Long.parseLong(nodeID));
+		%>
+		<html>
+			<head>
+				<script src="<%=Shared.getJQueryPath()%>"></script>
+				<script type="text/javascript">
+					var hash = "<%=hash%>";
+					var what = '<%=embeddedNode == null? "" : embeddedNode.getKind() == Node.Kind.APP? Jtp.viewName(embeddedNode).toLowerCase():"topic"%>';
+					function canScroll() { return <%=conf == null || conf.indexOf("noscroll") == -1%>; }
+	
+					var clientID = '<%=clientID%>';
+	
+					<%/* Runs when everything here is ready */%>
+					function start() {
+						try {
+							parent.nabbleready.location = "/util/Empty.jtp";
+						} catch(err) {
+							setTimeout(start, 100);
+						}
+					}
+					start();
+				</script>
+			</head>
+			<body>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/embed/EmbedOptions.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,115 @@
+
+package nabble.view.web.embed;
+
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.view.lib.EmbedUtils;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+	
+public class EmbedOptions extends HttpServlet implements AuthorizingServlet {
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		Site site = Jtp.getSite(request);
+		return site==null ? null : Jtp.getReadAuthorizationKey( site.getNode(Jtp.getLong(request,"node")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response); 
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		Site site = Jtp.getSite(request);
+		if( site == null )
+			return;
+		String nodeId = request.getParameter("node");
+		if( nodeId == null )
+			return;
+		Node node = site.getNode(Long.valueOf(nodeId));
+		if (node == null)
+			return;
+
+		boolean isForum = node.getKind() == Node.Kind.APP;
+
+		Person visitor = Jtp.getVisitor(request, response);
+
+		boolean allowed = Jtp.canBeEditedBy(node,visitor);
+		if (!allowed && isForum) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		String action = request.getParameter("action");
+		if ("save".equals(action) && "POST".equals(request.getMethod())) {
+			String url = request.getParameter("url");
+			String option = request.getParameter("option");
+			String value = "this".equals(option)? url : null;
+			node.setEmbeddingUrl(value);
+			if (value != null)
+				response.sendRedirect(Jtp.path(node));
+			else
+				Shared.javascriptRedirect(request, response, Jtp.path(node), null, true);
+		}
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<meta name=\"robots\" content=\"noindex,nofollow\"/>\r\n		" );
+ Shared.title(request, response, "Embedding Options"); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.minHeader(request,response, node); 
+		out.print( "\r\n		" );
+ Shared.editHeader(node.getSubjectHtml(), "Embedding Options", out); 
+		out.print( "\r\n\r\n		<div class=\"second-font field-title\">Javascript Code</div>\r\n		<div class=\"weak-color\" style=\"margin-left:1.5em\">\r\n			To add this " );
+		out.print( (isForum? Jtp.viewName(node).toLowerCase() : "topic") );
+		out.print( " to your website, copy and paste the following code on your HTML page:<br/>\r\n			<textarea style=\"height:3.5em;width:85%;margin:.3em .3em .3em 0;font-size:80%\" readonly=\"true\" onClick=\"this.focus();this.select();\">" );
+		out.print( (isForum? EmbedUtils.getForumSnippet(request, node) : EmbedUtils.getTopicSnippet(request, node)) );
+		out.print( "</textarea>\r\n			<br>You can embed this " );
+		out.print( (isForum? Jtp.viewName(node).toLowerCase():"topic") );
+		out.print( " in more than one website.\r\n			<br>We suggest that you use a custom domain name to avoid third-party cookie problems.\r\n			<br>Please check the <a href=\"" );
+		out.print( (context) );
+		out.print( "/help/Answer.jtp?id=36\">Nabble Help</a> for more information.\r\n		</div>\r\n\r\n		" );
+ if (isForum) { 
+		out.print( "\r\n		<div class=\"second-font field-title\">Redirect Users</div>\r\n		<div id=\"more\" class=\"weak-color\" style=\"margin-left:1.5em\">\r\n			Redirect options are available only when the " );
+		out.print( (Jtp.viewName(node).toLowerCase()) );
+		out.print( " is embedded.\r\n		</div>\r\n\r\n		<form id=\"settings\" method=\"post\" action=\"/embed/EmbedOptions.jtp\" accept-charset=\"UTF-8\" style=\"display:none\">\r\n			<input type=\"hidden\" name=\"action\" value=\"save\" />\r\n			<input type=\"hidden\" name=\"node\" value=\"" );
+		out.print( (node.getId()) );
+		out.print( "\" />\r\n			<input type=\"hidden\" id=\"url\" name=\"url\" value=\"\" />\r\n\r\n			<div class=\"weak-color\" style=\"margin:0 0 1em 1.5em\">\r\n				Your embedded " );
+		out.print( (Jtp.viewName(node).toLowerCase()) );
+		out.print( " is hosted on Nabble at this URL: <b>" );
+		out.print( (Jtp.url(node)) );
+		out.print( "</b><br>\r\n\r\n				<input type=\"radio\" id=\"o1\" name=\"option\" value=\"nabble\"></input>\r\n				<label for=\"o1\">Allow users to view this " );
+		out.print( (Jtp.viewName(node).toLowerCase()) );
+		out.print( " without embedding.</label><br/>\r\n\r\n				<input type=\"radio\" id=\"o2\" name=\"option\" value=\"this\"></input>\r\n				<label for=\"o2\">Redirect them to: <span id=\"embedding-url\" style=\"font-weight:bold\"></span>.</label><br/>\r\n\r\n				<span id=\"radio3\" style=\"display:none\">\r\n					<input type=\"radio\" id=\"o3\" name=\"option\" value=\"that\"></input>\r\n					<label for=\"o3\" title=\"\">Redirect them to: <span id=\"default-url\" style=\"font-weight:bold\"></span>.</label><br/>\r\n				</span>\r\n			</div>\r\n			<a href=\"" );
+		out.print( (context) );
+		out.print( "/help/Answer.jtp?id=40\">Learn More</a>\r\n			<div style=\"margin-top:1.4em\">\r\n				<input type=\"submit\" value=\"Save Changes\" /> or <a href=\"" );
+		out.print( (Jtp.path(node)) );
+		out.print( "\">Go back to the " );
+		out.print( (Jtp.viewName(node).toLowerCase()) );
+		out.print( "</a>\r\n			</div>\r\n		</form>\r\n		" );
+ } 
+		out.print( "\r\n		\r\n		" );
+ String embedDefaultUrl = node.getEmbeddingUrl(); 
+		out.print( "\r\n		<script type=\"text/javascript\">\r\n			var defaultUrl = \"" );
+		out.print( (Jtp.hideNull(embedDefaultUrl)) );
+		out.print( "\";\r\n			$(document).ready(function() {\r\n				if (Nabble.isEmbedded) {\r\n					$('#settings').show();\r\n					$('#more').hide();\r\n					\r\n					$('#url').val(Nabble.embeddingUrl);\r\n					$('#embedding-url').html(Nabble.embeddingUrl);\r\n\r\n					if (defaultUrl == \"\") {\r\n						$('#o1').attr('checked', 'y');\r\n					} else if (Nabble.embeddingUrl == defaultUrl) {\r\n						$('#o2').attr('checked', 'y');\r\n					} else {\r\n						$('#radio3').show();\r\n						$('#default-url').html(defaultUrl);\r\n						$('#o3').attr('checked', 'y');								\r\n					}\r\n				}\r\n			});\r\n		</script>\r\n\r\n		" );
+ Shared.footer(request, response); 
+		out.print( "\r\n		" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/embed/EmbedOptions.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,150 @@
+<%
+package nabble.view.web.embed;
+
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.view.lib.EmbedUtils;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+	
+public class EmbedOptions extends HttpServlet implements AuthorizingServlet {
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		Site site = Jtp.getSite(request);
+		return site==null ? null : Jtp.getReadAuthorizationKey( site.getNode(Jtp.getLong(request,"node")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response); 
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		Site site = Jtp.getSite(request);
+		if( site == null )
+			return;
+		String nodeId = request.getParameter("node");
+		if( nodeId == null )
+			return;
+		Node node = site.getNode(Long.valueOf(nodeId));
+		if (node == null)
+			return;
+
+		boolean isForum = node.getKind() == Node.Kind.APP;
+
+		Person visitor = Jtp.getVisitor(request, response);
+
+		boolean allowed = Jtp.canBeEditedBy(node,visitor);
+		if (!allowed && isForum) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		String action = request.getParameter("action");
+		if ("save".equals(action) && "POST".equals(request.getMethod())) {
+			String url = request.getParameter("url");
+			String option = request.getParameter("option");
+			String value = "this".equals(option)? url : null;
+			node.setEmbeddingUrl(value);
+			if (value != null)
+				response.sendRedirect(Jtp.path(node));
+			else
+				Shared.javascriptRedirect(request, response, Jtp.path(node), null, true);
+		}
+		%>
+		<html>
+			<head>
+				<meta name="robots" content="noindex,nofollow"/>
+				<% Shared.title(request, response, "Embedding Options"); %>
+			</head>
+			<body>
+				<% Shared.minHeader(request,response, node); %>
+				<% Shared.editHeader(node.getSubjectHtml(), "Embedding Options", out); %>
+
+				<div class="second-font field-title">Javascript Code</div>
+				<div class="weak-color" style="margin-left:1.5em">
+					To add this <%=isForum? Jtp.viewName(node).toLowerCase() : "topic"%> to your website, copy and paste the following code on your HTML page:<br/>
+					<textarea style="height:3.5em;width:85%;margin:.3em .3em .3em 0;font-size:80%" readonly="true" onClick="this.focus();this.select();"><%=isForum? EmbedUtils.getForumSnippet(request, node) : EmbedUtils.getTopicSnippet(request, node)%></textarea>
+					<br>You can embed this <%=isForum? Jtp.viewName(node).toLowerCase():"topic"%> in more than one website.
+					<br>We suggest that you use a custom domain name to avoid third-party cookie problems.
+					<br>Please check the <a href="<%=context%>/help/Answer.jtp?id=36">Nabble Help</a> for more information.
+				</div>
+
+				<% if (isForum) { %>
+				<div class="second-font field-title">Redirect Users</div>
+				<div id="more" class="weak-color" style="margin-left:1.5em">
+					Redirect options are available only when the <%=Jtp.viewName(node).toLowerCase()%> is embedded.
+				</div>
+
+				<form id="settings" method="post" action="/embed/EmbedOptions.jtp" accept-charset="UTF-8" style="display:none">
+					<input type="hidden" name="action" value="save" />
+					<input type="hidden" name="node" value="<%=node.getId()%>" />
+					<input type="hidden" id="url" name="url" value="" />
+
+					<div class="weak-color" style="margin:0 0 1em 1.5em">
+						Your embedded <%=Jtp.viewName(node).toLowerCase()%> is hosted on Nabble at this URL: <b><%=Jtp.url(node)%></b><br>
+
+						<input type="radio" id="o1" name="option" value="nabble"></input>
+						<label for="o1">Allow users to view this <%=Jtp.viewName(node).toLowerCase()%> without embedding.</label><br/>
+
+						<input type="radio" id="o2" name="option" value="this"></input>
+						<label for="o2">Redirect them to: <span id="embedding-url" style="font-weight:bold"></span>.</label><br/>
+
+						<span id="radio3" style="display:none">
+							<input type="radio" id="o3" name="option" value="that"></input>
+							<label for="o3" title="">Redirect them to: <span id="default-url" style="font-weight:bold"></span>.</label><br/>
+						</span>
+					</div>
+					<a href="<%=context%>/help/Answer.jtp?id=40">Learn More</a>
+					<div style="margin-top:1.4em">
+						<input type="submit" value="Save Changes" /> or <a href="<%=Jtp.path(node)%>">Go back to the <%=Jtp.viewName(node).toLowerCase()%></a>
+					</div>
+				</form>
+				<% } %>
+				
+				<% String embedDefaultUrl = node.getEmbeddingUrl(); %>
+				<script type="text/javascript">
+					var defaultUrl = "<%=Jtp.hideNull(embedDefaultUrl)%>";
+					$(document).ready(function() {
+						if (Nabble.isEmbedded) {
+							$('#settings').show();
+							$('#more').hide();
+							
+							$('#url').val(Nabble.embeddingUrl);
+							$('#embedding-url').html(Nabble.embeddingUrl);
+
+							if (defaultUrl == "") {
+								$('#o1').attr('checked', 'y');
+							} else if (Nabble.embeddingUrl == defaultUrl) {
+								$('#o2').attr('checked', 'y');
+							} else {
+								$('#radio3').show();
+								$('#default-url').html(defaultUrl);
+								$('#o3').attr('checked', 'y');								
+							}
+						}
+					});
+				</script>
+
+				<% Shared.footer(request, response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/embed/JsEmbed.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,107 @@
+
+package nabble.view.web.embed;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public final class JsEmbed extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(JsEmbed.class);
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		response.setHeader("Content-Type","application/x-javascript");
+		PrintWriter out = response.getWriter();
+
+		long siteId = Jtp.getLong(request, "site");
+		Site site = ModelHome.getSite(siteId);
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Site not found");
+			return;
+		}
+
+		long nodeId = Jtp.getLong(request, "node");
+		Node node = site.getNode(nodeId);
+
+		// cache the page
+		List<String> events = new ArrayList<String>();
+		Jtp.addBreadCrumbEvents( events, site.getRootNode() );
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response, events.toArray( new String[events.size()] ) );
+
+		String baseUrl = Jtp.getBaseUrl(request);
+
+		String embedUrl = request.getParameter("url");
+		logger.info("URL=" + embedUrl);
+
+		embedUrl = HtmlUtils.urlDecode(embedUrl);
+		int pos = embedUrl.indexOf("#nabble-");
+		String defaultUrl = node == null ? null : baseUrl+Jtp.path(node);
+		if (pos > 0) {
+			String sufix = embedUrl.substring(pos+7);
+			// if sufix contains #a (with post id)
+			int posHash = sufix.indexOf('|');
+			if (posHash > 0) {
+				sufix = sufix.substring(0, posHash);
+			}
+			defaultUrl = baseUrl + '/' + sufix + ".html";
+		}
+		
+		out.print( "\r\nvar Nabble = new Object();\r\nNabble.defaultHeight = 700;\r\nNabble.currentHeight = 0;\r\nNabble.counter = 0;\r\nNabble.title = document.title == \"\"? \"\" : document.title + \" - \";\r\nNabble.resizeTimeoutID;\r\nNabble.context = '" );
+		out.print( (baseUrl) );
+		out.print( "';\r\nNabble.defaultUrl = '" );
+		out.print( (defaultUrl) );
+		out.print( "';\r\n\r\nNabble.get = function(id) { return document.getElementById(id); };\r\n\r\nNabble.resizeTimeout = function() {\r\n	Nabble.resizeTimeoutID = setTimeout(Nabble.showMoreLink, 6000);\r\n};\r\n\r\nNabble.cancelTimeout = function() {\r\n	if (Nabble.resizeTimeoutID) {\r\n		clearInterval(Nabble.resizeTimeoutID);\r\n		Nabble.get('nabblemore').style.display = 'none';\r\n		Nabble.resizeTimeoutID = null;\r\n	}\r\n};\r\n\r\nNabble.showMoreLink = function() {\r\n	if (Nabble.resizeTimeoutID) {\r\n		Nabble.get('nabblemore').style.display = 'block';\r\n	}\r\n};\r\n\r\n        Nabble.showMore = function() {\r\n	if (Nabble.currentHeight == 0)\r\n		Nabble.currentHeight = Nabble.defaultHeight;\r\n	Nabble.currentHeight += 300;\r\n	Nabble.get('nabbleiframe').style.height = Nabble.currentHeight + 'px';\r\n};\r\n\r\nNabble.escape = function(value) {\r\n	if (typeof value == 'string') {\r\n		var hasSpace = value.indexOf(' ') >= 0;\r\n		var hasQuote = value.indexOf('\"') >= 0;\r\n\r\n		value = value.replace(/\\;/g, '%3B');\r\n		value = value.replace(/\"/g, '\\\\\"');\r\n\r\n		if (hasSpace || hasQuote)\r\n			value = '\"' + value + '\"';\r\n	}\r\n	return value;\r\n};\r\n\r\nNabble.unescape = function(value) {\r\n	if (value.charAt(0) == '\"' && value.charAt(value.length-1) == '\"')\r\n		value = value.substring(1, value.length-1);\r\n\r\n	value = value.replace(/\\\\\"/g, '\"');\r\n	value = value.replace(/%3B/g, ';');\r\n	return value;\r\n};\r\n\r\n        Nabble.setCookie = function(name, value) {\r\n	name = name+'" );
+		out.print( (siteId) );
+		out.print( "';\r\n	document.cookie = name + \"=\" + Nabble.escape(value) + \"; path=/\";\r\n};\r\n\r\nNabble.getCookie = function(name) {\r\n	name = name+'" );
+		out.print( (siteId) );
+		out.print( "';\r\n	var dc = document.cookie;\r\n	var prefix = name + \"=\";\r\n	var begin = dc.indexOf(\"; \" + prefix);\r\n	if (begin == -1) {\r\n		begin = dc.indexOf(prefix);\r\n		if (begin != 0) return null;\r\n	} else\r\n		begin += 2;\r\n	var end = document.cookie.indexOf(\";\", begin);\r\n	if (end == -1)\r\n		end = dc.length;\r\n	return Nabble.unescape(dc.substring(begin + prefix.length, end));\r\n};\r\n\r\nNabble.setPersistentCookie = function(name, value) {\r\n	name = name+'" );
+		out.print( (siteId) );
+		out.print( "';\r\n	var expires = new Date();\r\n	expires.setFullYear(expires.getFullYear()+10);\r\n	var curCookie = name + \"=\" + Nabble.escape(value) + \"; expires=\" + expires.toGMTString() + \"; path=/\";\r\n	document.cookie = curCookie;\r\n};\r\n\r\n        Nabble.deleteCookie = function(name) {\r\n	name = name+'" );
+		out.print( (siteId) );
+		out.print( "';\r\n	document.cookie = name + \"=\" +\r\n	\"; path=/\"  +\r\n	\"; expires=Thu, 01-Jan-1970 00:00:01 GMT\";\r\n};\r\n\r\nNabble.noHash = function(url) {\r\n	var pos = url.indexOf('#');\r\n	return (pos>-1)?url.substring(0, pos):url;\r\n};\r\n\r\nNabble.debug = function(s) {\r\n	if (Nabble.debugElement == 0)\r\n		return;\r\n	if (!Nabble.debugElement) {\r\n		Nabble.debugElement = Nabble.get('debug');\r\n		if (!Nabble.debugElement) {\r\n			Nabble.debugElement = 0;\r\n			return;\r\n		}\r\n	}\r\n	Nabble.debugElement.innerHTML = Nabble.debugElement.innerHTML+s+'<br/>';\r\n};\r\n\r\nNabble.loadScript = function(url) {\r\n	Nabble.debug('Loading script: ' + url);\r\n	var e = document.createElement(\"script\");\r\n	e.src = url;\r\n	e.type=\"text/javascript\";\r\n	document.getElementsByTagName(\"head\")[0].appendChild(e);\r\n};\r\n\r\nNabble.getJs = function(keys) {\r\n	if (!window.clientID)\r\n		return;\r\n	var p = '';\r\n	for (var i=0;i<keys.length;i++) {\r\n		p += '&key=' + keys[i];\r\n	}\r\n	var url = Nabble.context+\"/util/SessionService.jtp?action=get\" + p + \"&cid=\" + window.clientID + \"&_=\" + new Date().getTime();\r\n	Nabble.loadScript(url);\r\n};\r\n\r\nNabble.scroll = function(y) {\r\n	Nabble.debug('[scroll] y=' + y);\r\n	if (y == 1 && window.nabble_scroll_top) {\r\n		scrollTo(0, 0);\r\n	} else if (y > 0 && !window.nabble_ignore_scroll) {\r\n		var obj = Nabble.get('nabbleiframe');\r\n		do {\r\n			y += obj.offsetTop;\r\n		} while (obj = obj.offsetParent);\r\n		scrollTo(0, y);\r\n	}\r\n};\r\n\r\nNabble.resizeFrames = function(height,title,validHeight) {\r\n	if (document.title != title && !window.nabble_ignore_title)\r\n		document.title = title;\r\n	Nabble.debug('[resizeFrames] Counter = ' + (Nabble.counter++) + ' Height = ' + height + ' Title=[' + title + '] History=' + history.length + ' -- cid=' + window.clientID);\r\n\r\n	if (height != Nabble.currentHeight) {\r\n		Nabble.currentHeight = height;\r\n       			var f = Nabble.get('nabbleiframe');\r\n		if (f) {\r\n			f.scrolling = validHeight? 'no' : 'auto';\r\n			Nabble.debug('Scrolling=' + f.scrolling);\r\n			f.style.height = height + 'px';\r\n			Nabble.cancelTimeout();\r\n		}\r\n	}\r\n};\r\n\r\nNabble.getCurrentUrl = function() {\r\n	var currentUrl = Nabble.defaultUrl;\r\n	if (Nabble.hash.indexOf('#nabble+') == 0) {\r\n		var path = Nabble.hash.substring(8);\r\n		path = decodeURIComponent(path);\r\n		path = path\r\n			.replace(/</g,'%3C')\r\n			.replace(/>/g,'%3E')\r\n			.replace(/\"/g,'%22')\r\n			.replace(/'/g,'%27');\r\n		currentUrl = Nabble.context+\"/\" + path;\r\n	}\r\n	currentUrl += Nabble.realHash == ''? '' : '#' + Nabble.realHash;\r\n	return currentUrl;\r\n};\r\n\r\nNabble.getClientID = function() {\r\n	var clientID = Nabble.getCookie('clientID');\r\n	if (!clientID) {\r\n            	clientID = new Date().getTime() + '-' + Math.ceil(Math.random() * 1000);\r\n		Nabble.setCookie('clientID', clientID);\r\n	}\r\n	return clientID;\r\n};\r\n\r\nNabble.restart = function(nodeId, baseUrl) {\r\n	Nabble.debug('Restart -- baseUrl=' + baseUrl);\r\n	Nabble.context = baseUrl;\r\n	Nabble.defaultUrl = baseUrl+'/';\r\n	Nabble.start();\r\n};\r\n\r\nNabble.getConf = function() {\r\n	return window.nabble_ignore_scroll? 'noscroll;':'';\r\n};\r\n\r\nNabble.start = function() {\r\n	Nabble.infoLoaded = false;\r\n	window.clientID = Nabble.getClientID();\r\n\r\n	" );
+ /* Hash processing */ 
+		out.print( "\r\n	var hash = location.hash;\r\n	var pipe = hash.indexOf('|');\r\n	var realHash = '';\r\n	if (pipe > 0) {\r\n		" );
+/*
+					If there is a pipe in the hash, the value after it is the
+					hash of the original URL, which must be restored.
+					For Example:
+					#nabble-t100|a123
+				*/
+		out.print( "\r\n		realHash = hash.substring(pipe);\r\n		hash = hash.replace(realHash, '');\r\n		realHash = realHash.substring(1);\r\n	}\r\n	" );
+/* save the hash and realHash variables because the getCurrentUrl() method will need them.. */
+		out.print( "\r\n	Nabble.hash = hash;\r\n	Nabble.realHash = realHash;\r\n	Nabble.infoUrl = Nabble.context+\"/embed/EmbedInfo.jtp?node=" );
+		out.print( (nodeId) );
+		out.print( "&cid=\" + window.clientID + \"&hash=\" + realHash + '&conf=' + Nabble.getConf() + \"&_=\" + new Date().getTime() + \"#\" + Nabble.noHash(location.href);\r\n	var emptyUrl = Nabble.context+\"/util/Empty.jtp\";\r\n	var html = \"<div id='nabblemain'><div style='height:700px'><img src='" );
+		out.print( (baseUrl) );
+		out.print( "/images/loading.png' width=94 height=33 alt='Loading...'></div></div>\";\r\n	html += \"<div id='nabblemore' style='display:none'><a href=\\\"javascript: void Nabble.showMore()\\\">view more</a></div>\";\r\n	html += \"<iframe name='nabbleinfo' id='nabbleinfo' width='1' height='1' style='display:none' src=''></iframe>\";\r\n	html += \"<iframe name='nabbleresize' onload='Nabble.getJs([\\\"resizejs\\\", \\\"scrolljs\\\", \\\"others\\\"])' width='1' height='1' style='display:none' src='\" + emptyUrl + \"'></iframe>\";\r\n	html += \"<iframe name='nabbleready' onload='Nabble.loadMain()' width='1' height='1' style='display:none' src='\" + emptyUrl + \"'></iframe>\";\r\n	var div = Nabble.get('nabbleforum');\r\n	div.innerHTML = html;\r\n};\r\n\r\n" );
+/*
+			1 - Loads the main page as the last page in order to allow the
+				embedding code to run before it.
+			2 - We must load the iframe using a string because IE (and some
+				other browsers) have problems to load an iframe through the DOM.
+			3 - The loadMain() function is called when the 'nabbleready' iframe is loaded.
+				Note that this iframe doesn't have an initial URL. The first URL is provided in EmbedInfo.jtp.
+		*/
+		out.print( "\r\nNabble.loadMain = function() {\r\n	Nabble.debug('Loading main page...infoLoaded='+Nabble.infoLoaded);\r\n	if (!Nabble.infoLoaded) {\r\n		Nabble.debug('InfoUrl='+Nabble.infoUrl);\r\n		Nabble.get('nabbleinfo').setAttribute('src',Nabble.infoUrl);\r\n		Nabble.infoLoaded = true;\r\n		return;\r\n	}\r\n	var width = window.nabble_width? window.nabble_width : '100%';\r\n	var currentUrl = Nabble.getCurrentUrl();\r\n	Nabble.debug('CurrentUrl='+currentUrl);\r\n	var m = '<iframe name=\"nabbleiframe\" id=\"nabbleiframe\" src=\"' + currentUrl + '\" width=\"' + width + '\" height=\"' + Nabble.defaultHeight + '\" frameBorder=\"0\" scrolling=\"no\" allowtransparency=\"true\"></iframe>';\r\n	Nabble.get('nabblemain').innerHTML = m;\r\n	Nabble.resizeTimeout();\r\n}\r\n\r\nNabble.start();\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/embed/JsEmbed.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,326 @@
+<%
+package nabble.view.web.embed;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public final class JsEmbed extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(JsEmbed.class);
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		response.setHeader("Content-Type","application/x-javascript");
+		PrintWriter out = response.getWriter();
+
+		long siteId = Jtp.getLong(request, "site");
+		Site site = ModelHome.getSite(siteId);
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Site not found");
+			return;
+		}
+
+		long nodeId = Jtp.getLong(request, "node");
+		Node node = site.getNode(nodeId);
+
+		// cache the page
+		List<String> events = new ArrayList<String>();
+		Jtp.addBreadCrumbEvents( events, site.getRootNode() );
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response, events.toArray( new String[events.size()] ) );
+
+		String baseUrl = Jtp.getBaseUrl(request);
+
+		String embedUrl = request.getParameter("url");
+		logger.info("URL=" + embedUrl);
+
+		embedUrl = HtmlUtils.urlDecode(embedUrl);
+		int pos = embedUrl.indexOf("#nabble-");
+		String defaultUrl = node == null ? null : baseUrl+Jtp.path(node);
+		if (pos > 0) {
+			String sufix = embedUrl.substring(pos+7);
+			// if sufix contains #a (with post id)
+			int posHash = sufix.indexOf('|');
+			if (posHash > 0) {
+				sufix = sufix.substring(0, posHash);
+			}
+			defaultUrl = baseUrl + '/' + sufix + ".html";
+		}
+		%>
+		var Nabble = new Object();
+		Nabble.defaultHeight = 700;
+		Nabble.currentHeight = 0;
+		Nabble.counter = 0;
+		Nabble.title = document.title == ""? "" : document.title + " - ";
+		Nabble.resizeTimeoutID;
+		Nabble.context = '<%=baseUrl%>';
+		Nabble.defaultUrl = '<%=defaultUrl%>';
+
+		Nabble.get = function(id) { return document.getElementById(id); };
+
+		Nabble.resizeTimeout = function() {
+			Nabble.resizeTimeoutID = setTimeout(Nabble.showMoreLink, 6000);
+		};
+
+		Nabble.cancelTimeout = function() {
+			if (Nabble.resizeTimeoutID) {
+				clearInterval(Nabble.resizeTimeoutID);
+				Nabble.get('nabblemore').style.display = 'none';
+				Nabble.resizeTimeoutID = null;
+			}
+		};
+
+		Nabble.showMoreLink = function() {
+			if (Nabble.resizeTimeoutID) {
+				Nabble.get('nabblemore').style.display = 'block';
+			}
+		};
+
+        Nabble.showMore = function() {
+			if (Nabble.currentHeight == 0)
+				Nabble.currentHeight = Nabble.defaultHeight;
+			Nabble.currentHeight += 300;
+			Nabble.get('nabbleiframe').style.height = Nabble.currentHeight + 'px';
+		};
+
+		Nabble.escape = function(value) {
+			if (typeof value == 'string') {
+				var hasSpace = value.indexOf(' ') >= 0;
+				var hasQuote = value.indexOf('"') >= 0;
+
+				value = value.replace(/\;/g, '%3B');
+				value = value.replace(/"/g, '\\"');
+
+				if (hasSpace || hasQuote)
+					value = '"' + value + '"';
+			}
+			return value;
+		};
+
+		Nabble.unescape = function(value) {
+			if (value.charAt(0) == '"' && value.charAt(value.length-1) == '"')
+				value = value.substring(1, value.length-1);
+
+			value = value.replace(/\\"/g, '"');
+			value = value.replace(/%3B/g, ';');
+			return value;
+		};
+
+        Nabble.setCookie = function(name, value) {
+			name = name+'<%=siteId%>';
+			document.cookie = name + "=" + Nabble.escape(value) + "; path=/";
+		};
+
+		Nabble.getCookie = function(name) {
+			name = name+'<%=siteId%>';
+			var dc = document.cookie;
+			var prefix = name + "=";
+			var begin = dc.indexOf("; " + prefix);
+			if (begin == -1) {
+				begin = dc.indexOf(prefix);
+				if (begin != 0) return null;
+			} else
+				begin += 2;
+			var end = document.cookie.indexOf(";", begin);
+			if (end == -1)
+				end = dc.length;
+			return Nabble.unescape(dc.substring(begin + prefix.length, end));
+		};
+
+		Nabble.setPersistentCookie = function(name, value) {
+			name = name+'<%=siteId%>';
+			var expires = new Date();
+			expires.setFullYear(expires.getFullYear()+10);
+			var curCookie = name + "=" + Nabble.escape(value) + "; expires=" + expires.toGMTString() + "; path=/";
+			document.cookie = curCookie;
+		};
+
+        Nabble.deleteCookie = function(name) {
+			name = name+'<%=siteId%>';
+			document.cookie = name + "=" +
+			"; path=/"  +
+			"; expires=Thu, 01-Jan-1970 00:00:01 GMT";
+		};
+
+		Nabble.noHash = function(url) {
+			var pos = url.indexOf('#');
+			return (pos>-1)?url.substring(0, pos):url;
+		};
+
+		Nabble.debug = function(s) {
+			if (Nabble.debugElement == 0)
+				return;
+			if (!Nabble.debugElement) {
+				Nabble.debugElement = Nabble.get('debug');
+				if (!Nabble.debugElement) {
+					Nabble.debugElement = 0;
+					return;
+				}
+			}
+			Nabble.debugElement.innerHTML = Nabble.debugElement.innerHTML+s+'<br/>';
+		};
+
+		Nabble.loadScript = function(url) {
+			Nabble.debug('Loading script: ' + url);
+			var e = document.createElement("script");
+			e.src = url;
+			e.type="text/javascript";
+			document.getElementsByTagName("head")[0].appendChild(e);
+		};
+
+		Nabble.getJs = function(keys) {
+			if (!window.clientID)
+				return;
+			var p = '';
+			for (var i=0;i<keys.length;i++) {
+				p += '&key=' + keys[i];
+			}
+			var url = Nabble.context+"/util/SessionService.jtp?action=get" + p + "&cid=" + window.clientID + "&_=" + new Date().getTime();
+			Nabble.loadScript(url);
+		};
+
+		Nabble.scroll = function(y) {
+			Nabble.debug('[scroll] y=' + y);
+			if (y == 1 && window.nabble_scroll_top) {
+				scrollTo(0, 0);
+			} else if (y > 0 && !window.nabble_ignore_scroll) {
+				var obj = Nabble.get('nabbleiframe');
+				do {
+					y += obj.offsetTop;
+				} while (obj = obj.offsetParent);
+				scrollTo(0, y);
+			}
+		};
+
+		Nabble.resizeFrames = function(height,title,validHeight) {
+			if (document.title != title && !window.nabble_ignore_title)
+				document.title = title;
+			Nabble.debug('[resizeFrames] Counter = ' + (Nabble.counter++) + ' Height = ' + height + ' Title=[' + title + '] History=' + history.length + ' -- cid=' + window.clientID);
+
+			if (height != Nabble.currentHeight) {
+				Nabble.currentHeight = height;
+       			var f = Nabble.get('nabbleiframe');
+				if (f) {
+					f.scrolling = validHeight? 'no' : 'auto';
+					Nabble.debug('Scrolling=' + f.scrolling);
+					f.style.height = height + 'px';
+					Nabble.cancelTimeout();
+				}
+			}
+		};
+
+		Nabble.getCurrentUrl = function() {
+			var currentUrl = Nabble.defaultUrl;
+			if (Nabble.hash.indexOf('#nabble+') == 0) {
+				var path = Nabble.hash.substring(8);
+				path = decodeURIComponent(path);
+				path = path
+					.replace(/</g,'%3C')
+					.replace(/>/g,'%3E')
+					.replace(/"/g,'%22')
+					.replace(/'/g,'%27');
+				currentUrl = Nabble.context+"/" + path;
+			}
+			currentUrl += Nabble.realHash == ''? '' : '#' + Nabble.realHash;
+			return currentUrl;
+		};
+
+		Nabble.getClientID = function() {
+			var clientID = Nabble.getCookie('clientID');
+			if (!clientID) {
+            	clientID = new Date().getTime() + '-' + Math.ceil(Math.random() * 1000);
+				Nabble.setCookie('clientID', clientID);
+			}
+			return clientID;
+		};
+
+		Nabble.restart = function(nodeId, baseUrl) {
+			Nabble.debug('Restart -- baseUrl=' + baseUrl);
+			Nabble.context = baseUrl;
+			Nabble.defaultUrl = baseUrl+'/';
+			Nabble.start();
+		};
+
+		Nabble.getConf = function() {
+			return window.nabble_ignore_scroll? 'noscroll;':'';
+		};
+
+		Nabble.start = function() {
+			Nabble.infoLoaded = false;
+			window.clientID = Nabble.getClientID();
+
+			<% /* Hash processing */ %>
+			var hash = location.hash;
+			var pipe = hash.indexOf('|');
+			var realHash = '';
+			if (pipe > 0) {
+				<%/*
+					If there is a pipe in the hash, the value after it is the
+					hash of the original URL, which must be restored.
+					For Example:
+					#nabble-t100|a123
+				*/%>
+				realHash = hash.substring(pipe);
+				hash = hash.replace(realHash, '');
+				realHash = realHash.substring(1);
+			}
+			<%/* save the hash and realHash variables because the getCurrentUrl() method will need them.. */%>
+			Nabble.hash = hash;
+			Nabble.realHash = realHash;
+			Nabble.infoUrl = Nabble.context+"/embed/EmbedInfo.jtp?node=<%=nodeId%>&cid=" + window.clientID + "&hash=" + realHash + '&conf=' + Nabble.getConf() + "&_=" + new Date().getTime() + "#" + Nabble.noHash(location.href);
+			var emptyUrl = Nabble.context+"/util/Empty.jtp";
+			var html = "<div id='nabblemain'><div style='height:700px'><img src='<%=baseUrl%>/images/loading.png' width=94 height=33 alt='Loading...'></div></div>";
+			html += "<div id='nabblemore' style='display:none'><a href=\"javascript: void Nabble.showMore()\">view more</a></div>";
+			html += "<iframe name='nabbleinfo' id='nabbleinfo' width='1' height='1' style='display:none' src=''></iframe>";
+			html += "<iframe name='nabbleresize' onload='Nabble.getJs([\"resizejs\", \"scrolljs\", \"others\"])' width='1' height='1' style='display:none' src='" + emptyUrl + "'></iframe>";
+			html += "<iframe name='nabbleready' onload='Nabble.loadMain()' width='1' height='1' style='display:none' src='" + emptyUrl + "'></iframe>";
+			var div = Nabble.get('nabbleforum');
+			div.innerHTML = html;
+		};
+
+		<%/*
+			1 - Loads the main page as the last page in order to allow the
+				embedding code to run before it.
+			2 - We must load the iframe using a string because IE (and some
+				other browsers) have problems to load an iframe through the DOM.
+			3 - The loadMain() function is called when the 'nabbleready' iframe is loaded.
+				Note that this iframe doesn't have an initial URL. The first URL is provided in EmbedInfo.jtp.
+		*/%>
+		Nabble.loadMain = function() {
+			Nabble.debug('Loading main page...infoLoaded='+Nabble.infoLoaded);
+			if (!Nabble.infoLoaded) {
+				Nabble.debug('InfoUrl='+Nabble.infoUrl);
+				Nabble.get('nabbleinfo').setAttribute('src',Nabble.infoUrl);
+				Nabble.infoLoaded = true;
+				return;
+			}
+			var width = window.nabble_width? window.nabble_width : '100%';
+			var currentUrl = Nabble.getCurrentUrl();
+			Nabble.debug('CurrentUrl='+currentUrl);
+			var m = '<iframe name="nabbleiframe" id="nabbleiframe" src="' + currentUrl + '" width="' + width + '" height="' + Nabble.defaultHeight + '" frameBorder="0" scrolling="no" allowtransparency="true"></iframe>';
+			Nabble.get('nabblemain').innerHTML = m;
+			Nabble.resizeTimeout();
+		}
+
+		Nabble.start();
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/embed/NabbleEmbed.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,95 @@
+
+package nabble.view.web.embed;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.UrlMappable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class NabbleEmbed extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/embed/(f|p)?(\\d+)?$");
+
+	public String path(String type, String nodeId) {
+		return "/embed/" + type + nodeId;
+	}
+
+	public Map<String, String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		String type = m.group(1);
+		String nodeId = m.group(2);
+		params.put("type",new String[]{type});
+		params.put("node",new String[]{nodeId});
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		response.setHeader("Content-Type","application/x-javascript");
+		PrintWriter out = response.getWriter();
+
+		long nodeId = Long.valueOf(request.getParameter("node"));
+		Site site = Jtp.getSite(request);
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Node not found");
+				return;
+			}
+		Node node = site.getNode(nodeId);
+		if (node == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Node not found");
+			return;
+		}
+		jsLoadNode(site.getId(), nodeId, request, out);
+	}
+
+	static void invalidApp(PrintWriter out) {
+		
+		out.print( "\r\n	document.write(\"<div style='margin:.5em 0'>\");\r\n	document.write(\"<span style='color:#000;background:#FFF;padding:.5em'>\");\r\n	document.write(\"<b>This forum doesn't exist.</b><br>\");\r\n	" );
+ if (Jtp.supportUrl() != null) { 
+		out.print( "\r\n	document.write(\"Please contact <a href='" );
+		out.print( (Jtp.supportUrl()) );
+		out.print( "'>Nabble Support</a> if you need help.\");\r\n	" );
+ } 
+		out.print( "\r\n	document.write(\"</span>\");\r\n	document.write(\"</div>\");\r\n" );
+
+	}
+
+	static void jsLoadNode(long siteId, long nodeId, HttpServletRequest request, PrintWriter out) {
+		String base = Jtp.getBaseUrl(request);
+		
+		out.print( "\r\n        var link=document.getElementById(\"nabblelink\");\r\nif (link != null) {\r\n	link.style.display=\"none\";\r\n	document.write(\"<div id='nabbleforum' style='width:100%'><div style='height:700px'><img src='" );
+		out.print( (base) );
+		out.print( "/images/loading.png' width='94' height='33' alt='Loading...'></div></div>\");\r\n	var e = document.createElement(\"script\");\r\n	e.src = '" );
+		out.print( (base) );
+		out.print( "/embed/JsEmbed.jtp?site=" );
+		out.print( (siteId) );
+		out.print( "&node=" );
+		out.print( (nodeId) );
+		out.print( "&url=' + encodeURIComponent(location.href);\r\n            e.type=\"text/javascript\";\r\n	document.getElementsByTagName(\"head\")[0].appendChild(e);\r\n}\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/embed/NabbleEmbed.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,96 @@
+<%
+package nabble.view.web.embed;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.UrlMappable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class NabbleEmbed extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/embed/(f|p)?(\\d+)?$");
+
+	public String path(String type, String nodeId) {
+		return "/embed/" + type + nodeId;
+	}
+
+	public Map<String, String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		String type = m.group(1);
+		String nodeId = m.group(2);
+		params.put("type",new String[]{type});
+		params.put("node",new String[]{nodeId});
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		response.setHeader("Content-Type","application/x-javascript");
+		PrintWriter out = response.getWriter();
+
+		long nodeId = Long.valueOf(request.getParameter("node"));
+		Site site = Jtp.getSite(request);
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Node not found");
+				return;
+			}
+		Node node = site.getNode(nodeId);
+		if (node == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Node not found");
+			return;
+		}
+		jsLoadNode(site.getId(), nodeId, request, out);
+	}
+
+	static void invalidApp(PrintWriter out) {
+		%>
+			document.write("<div style='margin:.5em 0'>");
+			document.write("<span style='color:#000;background:#FFF;padding:.5em'>");
+			document.write("<b>This forum doesn't exist.</b><br>");
+			<% if (Jtp.supportUrl() != null) { %>
+			document.write("Please contact <a href='<%=Jtp.supportUrl()%>'>Nabble Support</a> if you need help.");
+			<% } %>
+			document.write("</span>");
+			document.write("</div>");
+		<%
+	}
+
+	static void jsLoadNode(long siteId, long nodeId, HttpServletRequest request, PrintWriter out) {
+		String base = Jtp.getBaseUrl(request);
+		%>
+        var link=document.getElementById("nabblelink");
+		if (link != null) {
+			link.style.display="none";
+			document.write("<div id='nabbleforum' style='width:100%'><div style='height:700px'><img src='<%=base%>/images/loading.png' width='94' height='33' alt='Loading...'></div></div>");
+			var e = document.createElement("script");
+			e.src = '<%=base%>/embed/JsEmbed.jtp?site=<%=siteId%>&node=<%=nodeId%>&url=' + encodeURIComponent(location.href);
+            e.type="text/javascript";
+			document.getElementsByTagName("head")[0].appendChild(e);
+		}
+		<%
+	}
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/end.luan	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,33 @@
+java()
+local Io = require "luan:Io.luan"
+local Http = require "luan:http/Http.luan"
+local HtmlViewUtils = require "java:nabble.view.lib.HtmlViewUtils"
+
+
+return function()
+	local p = Http.request.parameters.p or "0"
+	Io.stdout = Http.response.text_writer()
+%>
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta name="robots" content="noindex,nofollow">
+		<title>New Tab</title>
+<%		HtmlViewUtils.googleAnalytics(Http.response.java.getWriter()) %>
+		<script>
+			var p = <%=p%>;
+			function trk_fn() {
+				setTimeout(function(){
+					top.location = '/end.luan?p=' + (p+1) + '#__';
+				},60000);
+			}
+			if( p >= 10 )
+				trk_fn = null;
+		</script>
+        <script src="/trk5.js"></script>
+	</head>
+	<body>
+	</body>
+</html>
+<%
+end
Binary file src/nabble/view/web/favicon.ico has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/AttachmentDownload.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,120 @@
+package nabble.view.web.forum;
+
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.servlet.AuthorizingServlet;
+import fschmidt.util.servlet.JtpContext;
+import fschmidt.util.servlet.CanonicalUrl;
+import nabble.model.MailMessageFormat;
+import nabble.model.Message;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.UrlMappable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class AttachmentDownload extends HttpServlet implements UrlMappable, AuthorizingServlet, CanonicalUrl {
+	private static final Pattern urlPtn = Pattern.compile("/attachment/(\\d+)/(\\d+)/(.*)$");
+
+	private static String url(Site site,String postId,String attachmentId,String name) {
+		return site.getBaseUrl()
+				+ path(postId,attachmentId,name);
+	}
+
+	private static String path(String postId,String attachmentId,String name) {
+		StringBuilder buf = new StringBuilder();
+		buf.append( "/attachment/" );
+		buf.append( postId );
+		buf.append( "/" );
+		buf.append( attachmentId );
+		buf.append( "/" );
+		buf.append( name );
+		return buf.toString();
+	}
+
+	public String getCanonicalUrl(HttpServletRequest request) {
+		Site site = Jtp.getSite(request);
+		if( site == null )
+			return null;
+		String postId = request.getParameter("post");
+		String attachmentId = request.getParameter("attachment");
+		String name = request.getParameter("name");
+		return url(site,postId,attachmentId,name);
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = urlPtn.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		String postId = m.group(1);
+		params.put("post",new String[]{postId});
+		String attachmentId = m.group(2);
+		params.put("attachment",new String[]{attachmentId});
+		String name = m.group(3);
+		params.put("name",new String[]{name});
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return urlPtn;
+	}
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"post")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response); 
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		OutputStream out = response.getOutputStream();
+		long postId = Long.parseLong( request.getParameter("post") );
+		int attachmentId = Integer.parseInt( request.getParameter("attachment") );
+		String name = request.getParameter("name");
+		String contentType = FileDownload.getMimeByExtension(name);
+		if (contentType==null /*|| !contentType.startsWith("image")*/) {
+			contentType = "application/x-download";
+			response.setHeader("content-disposition", "attachment; filename=\""+name+"\"");
+		}
+		response.setContentType(contentType);
+		Node post = Jtp.getSiteNotNull(request).getNode(postId);
+		if( post==null ) {
+			response.sendError(HttpServletResponse.SC_GONE,"This post has been deleted.");
+			return;
+		}
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response, Cache.nodeChangeEvent(post) );
+		// If the user edits a mail message, we convert it into a HTML/TEXT format.
+		// After this change all attachments are lost because we lose the RAW email where
+		// the file was encoded. So we should return "404 Not Found" in such cases.
+		Message.Format fmt = post.getMessage().getFormat();
+		if (fmt instanceof MailMessageFormat) {
+			InputStream in = ((MailMessageFormat) fmt).getAttachment(post,attachmentId);
+			try {
+				IoUtils.copyAll(in,out);
+			} finally {
+				in.close();
+			}
+		} else {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "File not found");
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/ChangeDomainName.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,172 @@
+
+package nabble.view.web.forum;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+
+public final class ChangeDomainName extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = ModelHome.getSite(Jtp.getLong(request, "site"));
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "No application match the \"<i>" + request.getHeader("host") + "</i>\" domain.");
+			return;
+		}
+
+		User user = Jtp.getUser(request, response);
+		if (user == null) {
+			response.sendRedirect("http://" + Jtp.getDefaultBaseUrl(site) + Jtp.loginPath(site,null,response.encodeURL("/forum/ChangeDomainName.jtp?site="+site.getId())) );
+			return;
+		}
+
+		boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+		boolean isSysAdmin = Permissions.isSysAdmin(user);
+		if (!isSiteAdmin && !isSysAdmin) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		Node rootNode = site.getRootNode();
+		boolean isSave = "save".equals(request.getParameter("action"));
+
+		String domainName = site.getCustomDomain();
+		String domainOption = domainName == null? "default-domain" : "own-domain";
+		if (isSave) {
+			domainName = request.getParameter("domain-name");
+			domainOption = request.getParameter("domain-option");
+		}
+
+		String errorMsg = null;
+		if (isSave && "POST".equals(request.getMethod())) {
+			try {
+				boolean isDefault = "default-domain".equals(domainOption);
+				if (isDefault)
+					site.setCustomDomain(null);
+				else {
+					domainName = domainName.trim();
+					if (domainName.length() == 0 || domainName.endsWith(".nabble.com") || domainName.equals(InetAddress.getLocalHost().getHostAddress()))
+						throw ModelException.newInstance("invalid_domain", "Please enter a valid domain name for your application.");
+					Long currentSiteId = ModelHome.getSiteIdFromDomain(domainName);
+					if (currentSiteId != null && !currentSiteId.equals(site.getId()))
+						throw ModelException.newInstance("domain_already_in_use", "This domain is already in use.");
+					String domainIP = getDomainIP(domainName);
+					String nabbleIP = getDomainIP(Jtp.getDefaultHost());
+					if (!nabbleIP.equals(domainIP))
+						throw ModelException.newInstance(
+							"domain_bad_resolution",
+							"Your domain name currently resolves to an IP address that doesn't belong to Nabble. " +
+							"Please follow the instructions (see link below) in order to update your DNS. " +
+							"If you have just updated your DNS, please wait 24 hours for the changes to take affect.");
+
+					site.setCustomDomain(domainName);
+				}
+				sendEmail(user, site);
+				response.sendRedirect(Jtp.url(rootNode));
+				return;
+			} catch (TemplateException e) {
+				errorMsg = e.getMessage();
+			}
+		}
+
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n	<head>\n		<META NAME=\"robots\" CONTENT=\"noindex,nofollow\">\n		" );
+ Shared.title(request, response, "Change Domain Name"); 
+		out.print( "\n		<style type=\"text/css\">\n			label { font-weight:bold; }\n			.step {\n				font-weight: bold;\n				font-size: 120%;\n				padding: .5em .3em;\n				white-space:nowrap;\n			}\n		</style>\n		<script type=\"text/javascript\">\n			$(document).ready(function() {\n				function enableControls() {\n					if ($('#default-domain').attr('checked')) {\n						$('#domain-name').attr('disabled','y');\n					} else {\n						$('#domain-name').removeAttr('disabled').focus();\n					}\n				};\n				$('#default-domain,#own-domain').change(enableControls);\n				enableControls();\n			});\n		</script>\n	</head>\n	<body>\n		" );
+ Shared.minHeader(request,response, rootNode); 
+		out.print( "\n		" );
+ Shared.editHeader(rootNode.getSubjectHtml(), "Change Domain Name", out); 
+		out.print( "\n		" );
+ Shared.errorMessage(request, response, errorMsg, null); 
+		out.print( "\n\n		" );
+ if (site.getCustomDomain() != null) { 
+		out.print( "\n			<div class=\"info-message rounded\" style=\"padding: .5em;margin:.4em 0\">\n				<b>You already have a domain set for \"" );
+		out.print( (rootNode.getSubject()) );
+		out.print( "\".</b><br/>\n				If you change this domain configuration, links to the old domain will probably stop working.\n			</div>\n		" );
+ } 
+		out.print( "\n\n		<div class=\"field weak-color\" style=\"margin-left:1.5em\">\n			<div style=\"margin:1em 0 .5em\">\n				Access your application with a custom domain name.\n			</div>\n\n			<form method=\"post\" action=\"/forum/ChangeDomainName.jtp\" accept-charset=\"UTF-8\">\n				<input type=\"hidden\" name=\"action\" value=\"save\"/>\n				<input type=\"hidden\" name=\"site\" value=\"" );
+		out.print( (site.getId()) );
+		out.print( "\"/>\n				<input type=\"radio\" name=\"domain-option\" id=\"default-domain\" value=\"default-domain\" " );
+		out.print( ("default-domain".equals(domainOption)?"checked='y'":"") );
+		out.print( "/>\n				<label for=\"default-domain\">Nabble Default URL</label>\n				<div style=\"margin:.5em 0 1em 3em\">\n					http://" );
+		out.print( (Jtp.getDefaultBaseUrl(site)) );
+		out.print( "/\n				</div>\n\n				<input type=\"radio\" name=\"domain-option\" id=\"own-domain\" value=\"own-domain\" " );
+		out.print( ("own-domain".equals(domainOption)?"checked='y'":"") );
+		out.print( "/>\n				<label for=\"own-domain\">Your Own Domain</label>\n				<div style=\"margin:.5em 0 1em 3em\">\n					<table class=\"weak-color\">\n						<tr style=\"vertical-align:top\">\n							<td style=\"padding-right:.8em\"><div class=\"second-font shaded-bg-color rounded step border1 medium-border-color\">Step 1</div></td>\n							<td>\n								Go to your Domain Registrar and set the CNAME-record for your custom domain to <b>" );
+		out.print( (Jtp.getDefaultHost()) );
+		out.print( "</b>.\n								(<a href=\"/help/DNSConfiguration.jtp\" target=\"_new\">show me more details</a>)\n								<div style=\"margin-top:.7em;font-size:80%\">\n									(After this change, wait a little bit for the DNS to propagate.\n									Then open your custom domain url in the browser to see if the change is propagated &ndash; if successful, you should get redirected to the Nabble home page)\n								</div>\n							</td>\n						</tr>\n						<tr style=\"vertical-align:top\">\n							<td style=\"padding:1.7em .5em 0 0\"><div class=\"second-font shaded-bg-color rounded step border1 medium-border-color\">Step 2</div></td>\n							<td style=\"padding-top:1.5em\">\n								<div style=\"margin-bottom:.3em\">Enter your custom domain name below:</div>\n								http://<input id=\"domain-name\" name=\"domain-name\" size=\"40\" value=\"" );
+		out.print( (Jtp.hideNull(domainName)) );
+		out.print( "\"/>\n								<div style=\"font-size:80%;margin:.3em 0\">\n									Example: mydomain.com\n								</div>\n							</td>\n						</tr>\n						<tr style=\"vertical-align:top\">\n							<td style=\"padding:1.3em .5em 0 0\"><div class=\"second-font shaded-bg-color rounded step border1 medium-border-color\">Step 3</div></td>\n							<td style=\"padding-top:1.1em\">\n								After saving your changes, your old URLs will continue to work and will automatically redirect to your custom domain URL.\n								You will also receive an email with a link to revert this domain change.\n							</td>\n						</tr>\n					</table>\n				</div>\n\n				<input type=\"submit\" value=\"Save Changes\" />\n				or <a href=\"" );
+		out.print( (Jtp.path(rootNode)) );
+		out.print( "\">Cancel</a>\n			</form>\n		</div>\n		" );
+ Shared.footer(request, response); 
+		out.print( "\n		" );
+ Shared.analytics(request, response); 
+		out.print( "\n	</body>\n</html>\n" );
+
+	}
+
+	private static String getDomainIP(String domain) {
+		String ip = domain;
+		int i = ip.indexOf(":");
+		if( i > 0 )
+			ip = ip.substring(0,i);
+		try {
+			InetAddress add = InetAddress.getByName(ip);
+			if (add != null)
+				ip = add.getHostAddress();
+		} catch(UnknownHostException e) {}
+		return ip;
+	}
+
+	private static void sendEmail(User user, Site site)
+		throws IOException, ServletException
+	{
+		StringBuilder builder = new StringBuilder();
+		builder.append("Dear ").append(user.getName()).append(",\n\n");
+		builder.append("You have changed your domain name configuration to:\n");
+		builder.append(site.getBaseUrl());
+		builder.append("\n\n");
+		builder.append("If you have problems with your domain name or simply want to change this configuration again, you can use this link:\n");
+		builder.append("http://").append(Jtp.getDefaultHost()).append("/forum/ChangeDomainName.jtp?site=").append(site.getId());
+		builder.append("\n\n");
+		builder.append("Regards,\n");
+		builder.append("The Nabble team");
+
+		try {
+			Mail mail = MailHome.newMail();
+			mail.setFrom(new MailAddress(ModelHome.noReply, "Nabble"));
+			mail.setTo(new MailAddress(user.getEmail()));
+			mail.setSubject("Domain name configuration changed");
+			mail.setContent(new PlainTextContent(builder.toString()));
+			ModelHome.send(mail);
+		} catch(MailException e) {
+			throw new RuntimeException(e);
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/ChangeDomainName.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,236 @@
+<%
+package nabble.view.web.forum;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+
+public final class ChangeDomainName extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = ModelHome.getSite(Jtp.getLong(request, "site"));
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "No application match the \"<i>" + request.getHeader("host") + "</i>\" domain.");
+			return;
+		}
+
+		User user = Jtp.getUser(request, response);
+		if (user == null) {
+			response.sendRedirect("http://" + Jtp.getDefaultBaseUrl(site) + Jtp.loginPath(site,null,response.encodeURL("/forum/ChangeDomainName.jtp?site="+site.getId())) );
+			return;
+		}
+
+		boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+		boolean isSysAdmin = Permissions.isSysAdmin(user);
+		if (!isSiteAdmin && !isSysAdmin) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		Node rootNode = site.getRootNode();
+		boolean isSave = "save".equals(request.getParameter("action"));
+
+		String domainName = site.getCustomDomain();
+		String domainOption = domainName == null? "default-domain" : "own-domain";
+		if (isSave) {
+			domainName = request.getParameter("domain-name");
+			domainOption = request.getParameter("domain-option");
+		}
+
+		String errorMsg = null;
+		if (isSave && "POST".equals(request.getMethod())) {
+			try {
+				boolean isDefault = "default-domain".equals(domainOption);
+				if (isDefault)
+					site.setCustomDomain(null);
+				else {
+					domainName = domainName.trim();
+					if (domainName.length() == 0 || domainName.endsWith(".nabble.com") || domainName.equals(InetAddress.getLocalHost().getHostAddress()))
+						throw ModelException.newInstance("invalid_domain", "Please enter a valid domain name for your application.");
+					Long currentSiteId = ModelHome.getSiteIdFromDomain(domainName);
+					if (currentSiteId != null && !currentSiteId.equals(site.getId()))
+						throw ModelException.newInstance("domain_already_in_use", "This domain is already in use.");
+					String domainIP = getDomainIP(domainName);
+					String nabbleIP = getDomainIP(Jtp.getDefaultHost());
+					if (!nabbleIP.equals(domainIP))
+						throw ModelException.newInstance(
+							"domain_bad_resolution",
+							"Your domain name currently resolves to an IP address that doesn't belong to Nabble. " +
+							"Please follow the instructions (see link below) in order to update your DNS. " +
+							"If you have just updated your DNS, please wait 24 hours for the changes to take affect.");
+
+					site.setCustomDomain(domainName);
+				}
+				sendEmail(user, site);
+				response.sendRedirect(Jtp.url(rootNode));
+				return;
+			} catch (TemplateException e) {
+				errorMsg = e.getMessage();
+			}
+		}
+
+		PrintWriter out = response.getWriter();
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow">
+				<% Shared.title(request, response, "Change Domain Name"); %>
+				<style type="text/css">
+					label { font-weight:bold; }
+					.step {
+						font-weight: bold;
+						font-size: 120%;
+						padding: .5em .3em;
+						white-space:nowrap;
+					}
+				</style>
+				<script type="text/javascript">
+					$(document).ready(function() {
+						function enableControls() {
+							if ($('#default-domain').attr('checked')) {
+								$('#domain-name').attr('disabled','y');
+							} else {
+								$('#domain-name').removeAttr('disabled').focus();
+							}
+						};
+						$('#default-domain,#own-domain').change(enableControls);
+						enableControls();
+					});
+				</script>
+			</head>
+			<body>
+				<% Shared.minHeader(request,response, rootNode); %>
+				<% Shared.editHeader(rootNode.getSubjectHtml(), "Change Domain Name", out); %>
+				<% Shared.errorMessage(request, response, errorMsg, null); %>
+
+				<% if (site.getCustomDomain() != null) { %>
+					<div class="info-message rounded" style="padding: .5em;margin:.4em 0">
+						<b>You already have a domain set for "<%=rootNode.getSubject()%>".</b><br/>
+						If you change this domain configuration, links to the old domain will probably stop working.
+					</div>
+				<% } %>
+
+				<div class="field weak-color" style="margin-left:1.5em">
+					<div style="margin:1em 0 .5em">
+						Access your application with a custom domain name.
+					</div>
+
+					<form method="post" action="/forum/ChangeDomainName.jtp" accept-charset="UTF-8">
+						<input type="hidden" name="action" value="save"/>
+						<input type="hidden" name="site" value="<%=site.getId()%>"/>
+						<input type="radio" name="domain-option" id="default-domain" value="default-domain" <%="default-domain".equals(domainOption)?"checked='y'":""%>/>
+						<label for="default-domain">Nabble Default URL</label>
+						<div style="margin:.5em 0 1em 3em">
+							http://<%=Jtp.getDefaultBaseUrl(site)%>/
+						</div>
+
+						<input type="radio" name="domain-option" id="own-domain" value="own-domain" <%="own-domain".equals(domainOption)?"checked='y'":""%>/>
+						<label for="own-domain">Your Own Domain</label>
+						<div style="margin:.5em 0 1em 3em">
+							<table class="weak-color">
+								<tr style="vertical-align:top">
+									<td style="padding-right:.8em"><div class="second-font shaded-bg-color rounded step border1 medium-border-color">Step 1</div></td>
+									<td>
+										Go to your Domain Registrar and set the CNAME-record for your custom domain to <b><%=Jtp.getDefaultHost()%></b>.
+										(<a href="/help/DNSConfiguration.jtp" target="_new">show me more details</a>)
+										<div style="margin-top:.7em;font-size:80%">
+											(After this change, wait a little bit for the DNS to propagate.
+											Then open your custom domain url in the browser to see if the change is propagated &ndash; if successful, you should get redirected to the Nabble home page)
+										</div>
+									</td>
+								</tr>
+								<tr style="vertical-align:top">
+									<td style="padding:1.7em .5em 0 0"><div class="second-font shaded-bg-color rounded step border1 medium-border-color">Step 2</div></td>
+									<td style="padding-top:1.5em">
+										<div style="margin-bottom:.3em">Enter your custom domain name below:</div>
+										http://<input id="domain-name" name="domain-name" size="40" value="<%=Jtp.hideNull(domainName)%>"/>
+										<div style="font-size:80%;margin:.3em 0">
+											Example: mydomain.com
+										</div>
+									</td>
+								</tr>
+								<tr style="vertical-align:top">
+									<td style="padding:1.3em .5em 0 0"><div class="second-font shaded-bg-color rounded step border1 medium-border-color">Step 3</div></td>
+									<td style="padding-top:1.1em">
+										After saving your changes, your old URLs will continue to work and will automatically redirect to your custom domain URL.
+										You will also receive an email with a link to revert this domain change.
+									</td>
+								</tr>
+							</table>
+						</div>
+
+						<input type="submit" value="Save Changes" />
+						or <a href="<%=Jtp.path(rootNode)%>">Cancel</a>
+					</form>
+				</div>
+				<% Shared.footer(request, response); %>
+				<% Shared.analytics(request, response); %>
+			</body>
+		</html>
+		<%
+	}
+
+	private static String getDomainIP(String domain) {
+		String ip = domain;
+		int i = ip.indexOf(":");
+		if( i > 0 )
+			ip = ip.substring(0,i);
+		try {
+			InetAddress add = InetAddress.getByName(ip);
+			if (add != null)
+				ip = add.getHostAddress();
+		} catch(UnknownHostException e) {}
+		return ip;
+	}
+
+	private static void sendEmail(User user, Site site)
+		throws IOException, ServletException
+	{
+		StringBuilder builder = new StringBuilder();
+		builder.append("Dear ").append(user.getName()).append(",\n\n");
+		builder.append("You have changed your domain name configuration to:\n");
+		builder.append(site.getBaseUrl());
+		builder.append("\n\n");
+		builder.append("If you have problems with your domain name or simply want to change this configuration again, you can use this link:\n");
+		builder.append("http://").append(Jtp.getDefaultHost()).append("/forum/ChangeDomainName.jtp?site=").append(site.getId());
+		builder.append("\n\n");
+		builder.append("Regards,\n");
+		builder.append("The Nabble team");
+
+		try {
+			Mail mail = MailHome.newMail();
+			mail.setFrom(new MailAddress(ModelHome.noReply, "Nabble"));
+			mail.setTo(new MailAddress(user.getEmail()));
+			mail.setSubject("Domain name configuration changed");
+			mail.setContent(new PlainTextContent(builder.toString()));
+			ModelHome.send(mail);
+		} catch(MailException e) {
+			throw new RuntimeException(e);
+		}
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/ClearDeleteDate.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,34 @@
+package nabble.view.web.forum;
+
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class ClearDeleteDate extends HttpServlet {
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_GONE, "This node has been deleted.");
+			return;
+		}
+		site.clearDeleteDate();
+		Node node = site.getRootNode();
+		String what = node.getKind().equals(Node.Kind.POST)? "topic" : Jtp.viewName(node).toLowerCase();
+		StringBuilder msg = new StringBuilder();
+		msg.append("<div class=\"big-title second-font\">Success!</div>");
+		msg.append("This ").append(what).append(" is not scheduled for deletion anymore.");
+
+		response.setHeader("Content-Type","application/x-javascript");
+		response.getWriter().print("Nabble.get('inactive-delete').innerHTML ='" + msg.toString() + "';");
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/FileDownload.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,179 @@
+package nabble.view.web.forum;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.servlet.AuthorizingServlet;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.FileUpload;
+import nabble.model.Message;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.UrlMappable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class FileDownload extends HttpServlet implements UrlMappable, AuthorizingServlet {
+
+	private static final Pattern urlPtn = Pattern.compile("/file/(([a-z])(\\d+)/)?([^/]*)$");
+
+	public static String url(String filename,Message.Source src) {
+		return baseUrl(src) + path(filename,src);
+	}
+
+	public static String path(String filename,Message.Source src) {
+		StringBuilder buf = new StringBuilder();
+		buf.append( "/file/" );
+		if (!src.getMessageSourceType().equals(Message.SourceType.SITE)) {
+			buf.append( src.getMessageSourceType().getCode() );
+			buf.append( src.getSourceId() );
+			buf.append( "/" );
+		}
+		buf.append( HtmlUtils.urlEncode(filename) );
+		return buf.toString();
+	}
+
+	private static String baseUrl(Message.Source src) {
+		return src.getSite().getBaseUrl();
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = urlPtn.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		if (m.group(1) == null)
+			params.put("type",new String[]{String.valueOf(Message.SourceType.SITE.getCode())});
+		else {
+			String type = m.group(2);
+			params.put("type",new String[]{type});
+			String fileId = m.group(3);
+			params.put("id",new String[]{fileId});
+		}
+		String name = m.group(4);
+		params.put("name",new String[]{name});
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return urlPtn;
+	}
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		Message.Source src = getSource(request);
+		return src instanceof Node ? Jtp.getReadAuthorizationKey((Node)src) : null;
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response);
+	}
+
+	private static final Map<String,String> mimeMap = new HashMap<String,String>();
+	static {
+		ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime");
+		Enumeration<String> i = mime.getKeys();
+		while( i.hasMoreElements() ) {
+			String ext = i.nextElement();
+			String type = mime.getString(ext);
+			mimeMap.put(ext.toLowerCase(),type);
+		}
+	}
+
+	static String getMimeByExtension(String fileName) {
+		return mimeMap.get( fileName.substring(fileName.lastIndexOf('.')+1).toLowerCase() );
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		char type = request.getParameter("type").charAt(0);
+		Message.Source src = getSource(request);
+		if (src == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND,"File not found");
+			return;
+		}
+
+		switch(type) {
+		case 't':
+			break;
+		case 's':
+			jtpContext.setEtag(request,response, Cache.siteChangeEvent(src.getSite()) );
+			break;
+		case 'n':
+			{
+				Set<String> events = new LinkedHashSet<String>();
+				if( src instanceof Node ) {
+					Node node = (Node)src;
+					events.add( Cache.nodeFileChangeEvent(node) );
+					Jtp.addBreadCrumbEvents(events, node);
+				}
+				jtpContext.setEtag(request,response, events.toArray(new String[events.size()]) );
+			}
+			break;
+		case 'a':
+			jtpContext.setEtag(request,response, Cache.userFileChangeEvent(((Message.AvatarSource)src).getUser()) );
+			break;
+		default:
+			if( Jtp.invalidReferer(request) ) {
+				throw new ServletException("type = '"+type+"'");
+			} else {
+				throw new RuntimeException("type = '"+type+"'");
+			}
+		}
+
+		response.setHeader("Cache-Control",null);
+		String name = request.getParameter("name");
+
+		InputStream in = FileUpload.getFileContent(src,name);
+		if (in == null) {
+			name = HtmlUtils.urlDecode(name);
+			in = FileUpload.getFileContent(src, name);
+			if (in == null) {
+				// We have to find a better way to track this down -- the log is too full of missing images [Hugo/Jun-2010]
+				response.sendError(HttpServletResponse.SC_NOT_FOUND,"File not found");
+				return;
+			}
+		}
+		OutputStream out = response.getOutputStream();
+		response.setContentType(getMimeByExtension(name));
+		try {
+			IoUtils.copyAll(in,out);
+		} finally {
+			in.close();
+		}
+	}
+
+	private static Message.Source getSource(HttpServletRequest request)
+		throws ServletException
+	{
+		char type = request.getParameter("type").charAt(0);
+		Message.SourceType sourceType = Message.SourceType.getType(type);
+		if( sourceType==null )
+			return null;
+		Site site = Jtp.getSite(request);
+		if (site == null)
+			return null;
+		if (sourceType.equals(Message.SourceType.SITE))
+			return site;
+		long fileId = Jtp.getLong(request,"id");
+		return sourceType.getSource(site,fileId);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/NodeEditorNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,189 @@
+package nabble.view.web.forum;
+
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.Node;
+import nabble.model.export.Export;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Permissions;
+import nabble.view.web.template.NodeNamespace;
+import nabble.view.web.template.ServletNamespace;
+import nabble.view.web.template.UrlMapperNamespace;
+
+import java.util.Calendar;
+
+
+@Namespace (
+	name = "node_editor",
+	global = true
+)
+public final class NodeEditorNamespace {
+	private final Node node;
+	private final ServletNamespace servletNs;
+
+	public NodeEditorNamespace(Node node,ServletNamespace servletNs) {
+		this.node = node;
+		this.servletNs = servletNs;
+	}
+
+	public Node node() {
+		return node;
+	}
+
+	private Node getLocalNode(String url) throws ModelException.InvalidPermalink {
+		url = url.trim();
+		if (url.length() == 0)
+			return null;
+		Node n = UrlMapperNamespace.getNodeFromUrl(url);
+		if (n != null) {
+			return n.getSite().equals(node.getSite())? n : null;
+		}
+		// No app found. Let's check if this is a permalink url...
+		n = Permalink.getPostFromUrl(url);
+		if (n == null)
+			throw new ModelException.InvalidPermalink();
+		return n.getSite().equals(node.getSite())? n : null;
+	}
+
+	public static final CommandSpec edited_node = CommandSpec.DO;
+
+	@Command public void edited_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		NodeNamespace ns = new NodeNamespace(node);
+		out.print( interp.getArg(ns,"do") );
+	}
+
+	public static final CommandSpec save_node = CommandSpec.NO_OUTPUT;
+
+	@Command public void save_node(IPrintWriter out,Interpreter interp)
+		throws ModelException
+	{
+		if( node.isInDb() )
+			node.update();
+		else
+			node.insert(true);
+	}
+
+	public static final CommandSpec set_parent_url = new CommandSpec.Builder()
+		.parameters("parent_url")
+		.build()
+	;
+
+	@Command public void set_parent_url(IPrintWriter out,Interpreter interp)
+		throws TemplateException
+	{
+		String url = interp.getArgString("parent_url");
+		Node parent = getLocalNode(url);
+		if( node.equals(parent) )
+			throw TemplateException.newInstance("same_node_loop");
+		node.changeParent(parent);
+	}
+
+	public static final CommandSpec set_subject = new CommandSpec.Builder()
+		.parameters("subject")
+		.build()
+	;
+
+	@Command public void set_subject(IPrintWriter out,Interpreter interp)
+		throws ModelException
+	{
+		String subject = interp.getArgString("subject");
+		node.setSubject(subject);
+	}
+
+	public static final CommandSpec set_message = new CommandSpec.Builder()
+		.parameters("message","is_html")
+		.build()
+	;
+
+	@Command public void set_message(IPrintWriter out,Interpreter interp)
+		throws ModelException
+	{
+		Message.Format msgFmt = interp.getArgAsBoolean("is_html") ? Message.Format.HTML : Message.Format.TEXT;
+		String message = interp.getArgString("message");
+		node.setMessage(message,msgFmt);
+	}
+
+	public static final CommandSpec set_type = new CommandSpec.Builder()
+		.parameters("type")
+		.build()
+	;
+
+	@Command public void set_type(IPrintWriter out,Interpreter interp)
+		throws ModelException
+	{
+		String type = interp.getArgString("type");
+		node.setType(type);
+	}
+
+	public static final CommandSpec set_when_created = new CommandSpec.Builder()
+		.parameters("date")
+		.build()
+	;
+
+	@Command public void set_when_created(IPrintWriter out,Interpreter interp)
+		throws ModelException
+	{
+		String date = interp.getArgString("date");
+		try {
+			Calendar cal = Calendar.getInstance();
+			cal.setTimeInMillis(Long.valueOf(date));
+			node.setWhenCreated(cal.getTime());
+		} catch (NumberFormatException e) {
+			throw ModelException.newInstance("invalid_date", "Invalid date in milliseconds");
+		}
+	}
+
+
+	public static final CommandSpec remove_permissions = CommandSpec.NO_OUTPUT;
+
+	@Command public void remove_permissions(IPrintWriter out,Interpreter interp) {
+		Permissions.removePermissions(node);
+	}
+
+	public static final CommandSpec add_permission = CommandSpec.NO_OUTPUT()
+		.parameters("permission")
+		.optionalParameters("group")
+		.build()
+	;
+
+	@Command public void add_permission(IPrintWriter out,Interpreter interp) {
+		String perm = interp.getArgString("permission");
+		String group = interp.getArgString("group");
+		if( group == null )
+			Permissions.addPermission(node,perm);
+		else
+			Permissions.addPermission(node,perm,group);
+	}
+
+	public static final CommandSpec remove_permission = CommandSpec.NO_OUTPUT()
+		.parameters("permission")
+		.optionalParameters("group")
+		.build()
+	;
+
+	@Command public void remove_permission(IPrintWriter out,Interpreter interp) {
+		String group = interp.getArgString("group");
+		String perm = interp.getArgString("permission");
+		if (group == null)
+			Permissions.removePermission(node,perm);
+		else
+			Permissions.removePermission(node,perm,group);
+	}
+
+	public static final CommandSpec is_valid_export_permalink = new CommandSpec.Builder()
+		.parameters("permalink")
+		.build()
+	;
+
+	@Command public void is_valid_export_permalink(IPrintWriter out,Interpreter interp)
+		throws ModelException
+	{
+		out.print(Export.isValidExportServer(interp.getArgString("permalink")));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/Permalink.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,125 @@
+package nabble.view.web.forum;
+
+import fschmidt.util.servlet.CanonicalUrl;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.TopicView;
+import nabble.view.lib.UrlMappable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class Permalink extends HttpServlet implements UrlMappable, CanonicalUrl {
+
+	public static String url(Node topic,Node post) {
+		if( topic==null )
+			topic = post.getTopic();
+		return topic.getSite().getBaseUrl() + path(topic,post);
+	}
+
+	public static String path(Node topic,Node post) {
+		if( topic==null )
+			topic = post.getTopic();
+		StringBuilder buf = new StringBuilder();
+		buf.append( '/' );
+		buf.append( Jtp.subjectEncode(topic.getSubject()) );
+		buf.append( "-tp" );
+		buf.append( topic.getId() );
+		if( post != null && post != topic ) {
+		    buf.append("p");
+	    	buf.append(post.getId());
+		}
+		buf.append( ".html" );
+		return buf.toString();
+	}
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/([^/]*)-tp(\\d+)(p(\\d+))?(ef(\\d+))?\\.html$");
+
+	public String getCanonicalUrl(HttpServletRequest request) {
+		Site site = Jtp.getSite(request);
+		if( site == null )
+			return null;
+		Node topic = site.getNode( Long.parseLong( request.getParameter("root") ) );
+		String postS = request.getParameter("post");
+		Node post = postS==null ? null : site.getNode( Long.parseLong(postS) );
+		return url(topic,post);
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		String subject = m.group(1);
+		params.put("subject",new String[]{subject});
+		String rootId = m.group(2);
+		params.put("root",new String[]{rootId});
+		String postId = m.group(4);
+		if( postId != null )
+			params.put( "post", new String[]{postId} );
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		String type = ServletUtils.getCookieValue(request,"tview");
+		TopicView topicView = "threaded".equals(type)? TopicView.THREADED
+			: "list".equals(type)? TopicView.LIST : TopicView.CLASSIC;
+
+		long rootId = Long.parseLong( request.getParameter("root") );
+		Site site = Jtp.getSite(request);
+		if (site == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Page Not Found");
+			return;
+		}
+		Node root = site.getNode(rootId);
+		if (root == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Node Not Found");
+			return;
+		}
+		Person visitor = Jtp.getVisitor(request, response);
+
+		String postIdS = request.getParameter("post");
+		Node post = postIdS == null? root : site.getNode(Long.parseLong(postIdS));
+		if (Jtp.canBeViewedBy(root, visitor))
+			sendToTopicPage(root, post, topicView, response);
+		else if (post.getOwner().equals(visitor))
+			response.sendRedirect("/template/NamlServlet.jtp?macro=print_post&node="+post.getId());
+		else
+			sendToTopicPage(root, post, topicView, response);
+	}
+
+	private void sendToTopicPage(Node root, Node post, TopicView topicView, HttpServletResponse response) throws IOException {
+		response.setHeader("Location", Jtp.topicViewPath(root, post.getId(), topicView));
+		response.sendError(HttpServletResponse.SC_MOVED_PERMANENTLY);
+	}
+
+	public static Node getPostFromUrl(String url) {
+		Site site = Jtp.getSiteFromUrl(url);
+		if( site == null )
+			return null;
+		Matcher m = URL_PATTERN.matcher(url);
+		if( !m.find() )
+			return null;
+		String post = m.group(4) == null? m.group(2) : m.group(4);
+		return site.getNode(Long.parseLong(post));
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/SearchNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,225 @@
+package nabble.view.web.forum;
+
+import fschmidt.util.java.DateUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Lucene;
+import nabble.model.Node;
+import nabble.model.NodeSearcher;
+import nabble.model.Person;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.NodeList;
+import nabble.view.web.template.ServletNamespace;
+import nabble.view.web.template.UserNamespace;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.lucene.search.Filter;
+
+import javax.servlet.ServletException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+
+@Namespace (
+	name = "search",
+	global = true
+)
+public final class SearchNamespace {
+	private final Node node;
+	private String query;
+	private Person user;
+	private int days;
+	private int searchSize = 0;
+	private List<Node> posts = Collections.emptyList();
+	public final NodeSearcher.Builder postQuery;
+	private NodeSearcher postSearcher;
+	private boolean hasQuery;
+	private boolean sortByDate;
+	private boolean didSearch = false;
+	private final ServletNamespace servletNs;
+	private boolean hasMore;
+
+	public SearchNamespace(Node node,ServletNamespace servletNs)
+		throws ServletException
+	{
+		this.node = node;
+		this.servletNs = servletNs;
+		postQuery = new NodeSearcher.Builder(node);
+		User currentUser = Jtp.getUser(servletNs.request,servletNs.response);
+		postQuery.setCurrentUser(currentUser);
+	}
+
+	public static final CommandSpec query_author = CommandSpec.NO_OUTPUT()
+		.dotParameter("author")
+		.build()
+	;
+
+	@Command public void query_author(IPrintWriter out,Interpreter interp) {
+		if( user != null )
+			throw new RuntimeException("user already set");
+		String userId = interp.getArgString("author");
+		user = NabbleNamespace.current().site().getPerson(userId);
+		postQuery.addUser(user);
+	}
+
+	public static final CommandSpec do_search = new CommandSpec.Builder()
+		.parameters("length")
+		.optionalParameters("query","days","start","sort")
+		.build()
+	;
+
+	@Command public void do_search(IPrintWriter out,Interpreter interp)
+		throws TemplateException, ServletException
+	{
+		query = interp.getArgString("query");
+		days = interp.getArgAsInt("days",0);
+
+		int start = interp.getArgAsInt("start",0);
+		int length = interp.getArgAsInt("length");
+		Date startDate = days==0 ? null : DateUtils.addDays(new Date(), -days);
+
+		hasQuery = query != null && query.length() > 0;
+		sortByDate = hasQuery ? "date".equals(interp.getArgString("sort")) : true;
+
+		try {
+			postQuery.addLine(query);
+		} catch(ParseException e) {
+			throw TemplateException.newInstance("search_query_parse_error",e);
+		}
+		postQuery.addNodeKind(Node.Kind.POST);
+		if (days != 0) {
+			Filter filter = Lucene.getCachedFilter(Lucene.getRangeFilter(startDate, null));
+			postQuery.setFilter(filter);
+		}
+		if (sortByDate) {
+			postQuery.setSort(NodeSearcher.SORT_BY_DATE);
+			postQuery.setDateRange(startDate, null);
+		}
+		postSearcher = postQuery.build();
+		try {
+			posts = postSearcher.getNodes(start, length);
+			searchSize = postSearcher.getTotalHits();
+		} catch(NodeSearcher.TooManyClauses e) {
+			throw TemplateException.newInstance("too_many_search_clauses",e);
+		}
+		hasMore = start + posts.size() < searchSize;
+		didSearch = true;
+	}
+
+	public static final CommandSpec _user = CommandSpec.DO;
+
+	@Command("user") public void _user(IPrintWriter out,ScopedInterpreter<UserNamespace> interp)
+	{
+		UserNamespace userNs = new UserNamespace(user);
+		out.print( interp.getArg(userNs,"do") );
+	}
+
+	@Command("days") public void _days(IPrintWriter out,Interpreter interp) {
+		out.print(days);
+	}
+
+	@Command public void has_days(IPrintWriter out,Interpreter interp) {
+		out.print( days > 0 );
+	}
+
+	@Command public void total_posts(IPrintWriter out,Interpreter interp) {
+		out.print(searchSize);
+	}
+
+	@Command public void has_results(IPrintWriter out,Interpreter interp) {
+		out.print( searchSize > 0 );
+	}
+
+	public static final CommandSpec highlight = CommandSpec.TEXT;
+
+	@Command public void highlight(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		out.print( postSearcher.highlight(text,"<b>","</b>") );
+	}
+
+	public static final CommandSpec results = CommandSpec.DO;
+
+	@Command public void results(IPrintWriter out,ScopedInterpreter<NodeList> interp) {
+		NodeList seq = new NodeList(posts,node,hasMore);
+		out.print( interp.getArg(seq,"do") );
+	}
+
+	@Command public void has_resort(IPrintWriter out,Interpreter interp) {
+		out.print( hasQuery && searchSize > 1 );
+	}
+
+	@Command public void has_query(IPrintWriter out,Interpreter interp) {
+		out.print( hasQuery );
+	}
+
+	@Command public void sorted_by_date(IPrintWriter out,Interpreter interp) {
+		out.print( sortByDate );
+	}
+
+	@Command public void search_terms(IPrintWriter out,Interpreter interp) {
+		out.print( concatTerms(postSearcher.getSearchTerms()) );
+	}
+
+	private static String concatTerms(Set<String> searchTerms) {
+		StringBuilder buf = new StringBuilder();
+		for( String s : searchTerms ) {
+			if (buf.length() > 0)  buf.append('|');
+			buf.append(s.replaceAll("\\p{Punct}", "\\\\$0"));
+		}
+		return buf.toString();
+	}
+
+	public static final CommandSpec fragment = new CommandSpec.Builder()
+		.dotParameter("text")
+		.parameters("size")
+		.build()
+	;
+
+	@Command public void fragment(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		String size = interp.getArgString("size");
+		int n;
+		try {
+			n = Integer.valueOf(size);
+		} catch (NumberFormatException e) {
+			throw new RuntimeException("Invalid \"size\" attribute: " + size);
+		}
+		out.print( Jtp.hideNull(postSearcher.getFragment(text, n, "...")) );
+	}
+
+	@Command public void descendants_ids_matching_search_query(IPrintWriter out, Interpreter interp)
+		throws TemplateException
+	{
+		String query = ServletUtils.getCookieValue(servletNs.request,"query");
+		String searchUserId = ServletUtils.getCookieValue(servletNs.request, "searchuser");
+		if ((query != null && !"".equals(query)) || searchUserId != null) {
+			try {
+				NodeSearcher.Builder luceneQuery = new NodeSearcher.Builder(NabbleNamespace.current().site(),node.getId());
+				luceneQuery.addLine(query);
+				luceneQuery.addNodeKind(Node.Kind.POST);
+				if( searchUserId != null && !"".equals(searchUserId) )
+					luceneQuery.addUser(searchUserId);
+
+				final StringBuilder builder = new StringBuilder();
+				luceneQuery.build().forEach(new NodeSearcher.Handler(){
+					public void handle(long nodeId) {
+						if (builder.length() > 0)
+							builder.append(',');
+						builder.append(nodeId);
+					}
+				});
+				out.print(builder.toString());
+			} catch (ParseException e) {} // ignore bad queries
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/Thumbnail.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,167 @@
+package nabble.view.web.forum;
+
+import fschmidt.util.java.ImageUtils;
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.servlet.AuthorizingServlet;
+import fschmidt.util.servlet.CanonicalUrl;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.FileUpload;
+import nabble.model.Message;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.SystemProperties;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.UrlMappable;
+import org.apache.commons.fileupload.FileItem;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public class Thumbnail extends HttpServlet implements UrlMappable, AuthorizingServlet, CanonicalUrl {
+
+	private static final String NABBLE_IMAGE_CODE = "<nabble_img src=\"";
+	private static final Pattern YOUTUBE_PATTERN = Pattern.compile("[ ]src[ ]*=[ ]*\"(https?:)?//www\\.youtube(-nocookie)?\\.com/(v|embed)/([^&?\"]*)(.*)\"");
+	private static final Pattern EXTERNAL_IMG_PATTERN = Pattern.compile("<img(\\s|\\w|=|\"|')+src[ ]*=[ ]*[\"|'](\\S+)[\"|']");
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/file/thumb/n(\\d+)(/(.+))?$");
+
+	public static String url(Node node, String fileName) {
+		return node.getSite().getBaseUrl() + path(node,fileName);
+	}
+
+	public static String path(Node node, String fileName) {
+		return "/file/thumb/n" + node.getId() + (fileName == null? "" : '/' + fileName);
+	}
+
+	public String getCanonicalUrl(HttpServletRequest request) {
+		Site site = Jtp.getSite(request);
+		if( site == null )
+			return null;
+		return url(site.getNode(Long.parseLong(request.getParameter("node"))), request.getParameter("filename"));
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if (!m.find())
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		String nodeIdS = m.group(1);
+		params.put("node", new String[]{nodeIdS});
+		String fileName = m.group(2);
+		if (fileName != null && fileName.length() > 1)
+			params.put("filename", new String[]{fileName.substring(1)});
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"node")));
+	}
+
+	public boolean authorize(String key,HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response);
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+		Node node = Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request, "node"));
+		if (node == null)
+			return;
+
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response,Cache.nodeChangeEvent(node));
+
+		String fileName = request.getParameter("filename");
+		BufferedImage img  = getImage(node, fileName, response);
+		if (img == null) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND);
+			return;
+		}
+
+		InputStream in = null;
+		try {
+			img = ImageUtils.getThumbnail(img, 140, 100);
+			in = ImageUtils.asInputStream(img, "jpg");
+			IoUtils.copyAll(in, response.getOutputStream());
+		} finally {
+			if (in != null)
+				in.close();
+		}
+	}
+
+	private BufferedImage getImage(Node node, String fileName, HttpServletResponse response)
+		throws ServletException
+	{
+		if (fileName == null) {
+			try {
+				String message = node.getMessage().getRaw();
+				Matcher m = EXTERNAL_IMG_PATTERN.matcher(message);
+				if (m.find()) {
+					String imgUrl = m.group(2);
+					FileItem fi = new FileUpload.UrlFileItem(new URL(imgUrl));
+					response.setContentType(FileDownload.getMimeByExtension(imgUrl));
+					return ImageUtils.getImage(fi.getInputStream());
+				}
+			} catch(IOException e) {
+				throw new ServletException("getImage failed",e);
+			}
+		} else {
+			try {
+				Message.Source src = Message.SourceType.getType('n').getSource(node.getSite(),node.getId());
+				InputStream in = FileUpload.getFileContent(src,fileName);
+				if (in != null) {
+					response.setContentType(FileDownload.getMimeByExtension(fileName));
+					try {
+						return ImageUtils.getImage(in);
+					} finally {
+						in.close();
+					}
+				}
+			} catch(IOException e) {
+				throw new RuntimeException("getImage failed",e);
+			}
+		}
+		return null;
+	}
+
+	public static String getThumbnailFile(Node node) {
+		String src = null;
+
+		String message = node.getMessage().getRaw();
+		int posStart = message.indexOf(NABBLE_IMAGE_CODE);
+		if (posStart >= 0) {
+			int srcStart = posStart + NABBLE_IMAGE_CODE.length();
+			int posEnd = message.indexOf("\"", srcStart);
+			src = "/file/thumb/n" + node.getId() + '/' + message.substring(srcStart, posEnd);
+		}
+		if (src == null) {
+			// Let's check for youtube videos
+			Matcher m = YOUTUBE_PATTERN.matcher(message);
+			if(m.find()) {
+				String code = m.group(4);
+				src = "http://i1.ytimg.com/vi/" + code + "/default.jpg";
+			}
+		}
+		if (src == null) {
+			Matcher m = EXTERNAL_IMG_PATTERN.matcher(message);
+			if(m.find()) {
+				return "/file/thumb/n" + node.getId();
+			}
+		}
+		return src;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/UploadFile.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,52 @@
+
+package nabble.view.web.forum;
+
+import nabble.model.Message;
+import nabble.model.ModelHome;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class UploadFile extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to upload a file.",request,response);
+			return;
+		}
+		PrintWriter out = response.getWriter();
+		String nodeId = request.getParameter("node");
+		Message.Source src = nodeId == null || nodeId.length() == 0? new Message.TempSource(user) : user.getSite().getNode(Long.valueOf(nodeId));
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<script type=\"text/javascript\">\r\n			// Fixes the parent variable to consider the nested frames.\r\n			var customParent = function() {\r\n				return parent.parent;\r\n			};\r\n		</script>\r\n		" );
+ Shared.title(request,response,"Upload File"); 
+		out.print( "\r\n		<script type=\"text/javascript\">\r\n			// Can only be called after the Shared.title() because of jquery\r\n			$(document).ready(function() {\r\n				var $table = $('table', document.body);\r\n				var h = $table.outerHeight() + 15;\r\n				var w = $table.outerWidth() + 15;\r\n				var f = parent.Nabble.get('fileuploader');\r\n				$(f).height(h).width(w);\r\n				$(document.body).css('background-color', $('form').parent().css('background-color'));\r\n			});\r\n		</script>\r\n	</head>\r\n	<body style=\"background:transparent;margin:0\">\r\n		" );
+ Shared.noHeader(request,response); 
+		out.print( "\r\n		<div class=\"shaded-bg-color\" style=\"padding:.5em\">\r\n			<form id=\"file-upload-form\" action=\"/forum/UploadFile2.jtp\" method=\"POST\" enctype=\"multipart/form-data\">\r\n				<input type=\"hidden\" name=\"srcType\" value=\"" );
+		out.print( (src.getMessageSourceType().getCode()) );
+		out.print( "\" />\r\n				<input type=\"hidden\" name=\"srcId\" value=\"" );
+		out.print( (src.getSourceId()) );
+		out.print( "\" />\r\n				" );
+ if (request.getParameter("textAreaId") != null) { 
+		out.print( "\r\n					<input type=\"hidden\" name=\"textAreaId\" value=\"" );
+		out.print( (request.getParameter("textAreaId")) );
+		out.print( "\" />\r\n				" );
+ } 
+		out.print( "\r\n				<table>\r\n					<tr>\r\n						<td>\r\n							<div class=\"form-label\" style=\"text-align:left\">File to Upload:</div>\r\n							<input name=\"file\" id=\"nabble.file\" type=\"file\" size=\"30\" />\r\n						</td>\r\n					</tr>\r\n					<tr>\r\n						<td>\r\n							<input type=\"submit\" value=\"Upload File\">\r\n							<input type=\"button\" value=\"Close\" onclick=\"parent.Nabble.closeWindows()\">\r\n						</td>\r\n					</tr>\r\n				</table>\r\n			</form>\r\n		</div>\r\n		" );
+ Shared.noFooter(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/UploadFile.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,84 @@
+<%
+package nabble.view.web.forum;
+
+import nabble.model.Message;
+import nabble.model.ModelHome;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class UploadFile extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to upload a file.",request,response);
+			return;
+		}
+		PrintWriter out = response.getWriter();
+		String nodeId = request.getParameter("node");
+		Message.Source src = nodeId == null || nodeId.length() == 0? new Message.TempSource(user) : user.getSite().getNode(Long.valueOf(nodeId));
+		%>
+		<html>
+			<head>
+				<script type="text/javascript">
+					// Fixes the parent variable to consider the nested frames.
+					var customParent = function() {
+						return parent.parent;
+					};
+				</script>
+				<% Shared.title(request,response,"Upload File"); %>
+				<script type="text/javascript">
+					// Can only be called after the Shared.title() because of jquery
+					$(document).ready(function() {
+						var $table = $('table', document.body);
+						var h = $table.outerHeight() + 15;
+						var w = $table.outerWidth() + 15;
+						var f = parent.Nabble.get('fileuploader');
+						$(f).height(h).width(w);
+						$(document.body).css('background-color', $('form').parent().css('background-color'));
+					});
+				</script>
+			</head>
+			<body style="background:transparent;margin:0">
+				<% Shared.noHeader(request,response); %>
+				<div class="shaded-bg-color" style="padding:.5em">
+					<form id="file-upload-form" action="/forum/UploadFile2.jtp" method="POST" enctype="multipart/form-data">
+						<input type="hidden" name="srcType" value="<%=src.getMessageSourceType().getCode()%>" />
+						<input type="hidden" name="srcId" value="<%=src.getSourceId()%>" />
+						<% if (request.getParameter("textAreaId") != null) { %>
+							<input type="hidden" name="textAreaId" value="<%=request.getParameter("textAreaId")%>" />
+						<% } %>
+						<table>
+							<tr>
+								<td>
+									<div class="form-label" style="text-align:left">File to Upload:</div>
+									<input name="file" id="nabble.file" type="file" size="30" />
+								</td>
+							</tr>
+							<tr>
+								<td>
+									<input type="submit" value="Upload File">
+									<input type="button" value="Close" onclick="parent.Nabble.closeWindows()">
+								</td>
+							</tr>
+						</table>
+					</form>
+				</div>
+				<% Shared.noFooter(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/UploadFile2.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,81 @@
+
+package nabble.view.web.forum;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import fschmidt.util.servlet.JtpContext;
+import fschmidt.util.java.HtmlUtils;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.model.FileUpload;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.User;
+
+public final class UploadFile2 extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(UploadFile2.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to upload a file.",request,response);
+			return;
+		}
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n	<body>\r\n		<script type=\"text/javascript\">\r\n		" );
+
+				try {
+					final Map<String,FileItem> map;
+					try {
+						map = Jtp.getFileItems(request);
+					} catch(FileUploadException e) {
+						logger.warn("",e);
+						throw ModelException.newInstance( "upload_filed", "upload failed - " + e.getMessage() );
+					}
+					FileItem fi = map.get("file");
+					char type = map.get("srcType").getString().charAt(0);
+					long id = Long.parseLong(map.get("srcId").getString());
+					Message.Source src = Message.SourceType.getType(type).getSource(user.getSite(),id);
+					String name = FileUpload.uploadFile(fi,src);
+					JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+					jtpContext.setTimeLimit(request,0L);
+
+					if (map.containsKey("textAreaId")) { 
+		out.print( "\r\n				parent.Nabble.uploadedFile(\"" );
+		out.print( (name) );
+		out.print( "\", \"" );
+		out.print( (map.get("textAreaId").getString()) );
+		out.print( "\");\r\n			" );
+ } else { 
+		out.print( "\r\n				parent.Nabble.uploadedFile(\"" );
+		out.print( (name) );
+		out.print( "\");\r\n			" );
+ } 
+		out.print( "\r\n			parent.Nabble.closeWindows();\r\n			" );
+
+				} catch(ModelException e) {
+					logger.warn("file upload failed",e);
+					
+		out.print( "\r\nalert(\"" );
+		out.print( (HtmlUtils.javascriptStringEncode(e.getMessage())) );
+		out.print( "\");\r\nhistory.back();\r\n" );
+
+				}
+				
+		out.print( "\r\n</script>\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/UploadFile2.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,78 @@
+<%
+package nabble.view.web.forum;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import fschmidt.util.servlet.JtpContext;
+import fschmidt.util.java.HtmlUtils;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.model.FileUpload;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.User;
+
+public final class UploadFile2 extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(UploadFile2.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to upload a file.",request,response);
+			return;
+		}
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<body>
+				<script type="text/javascript">
+				<%
+				try {
+					final Map<String,FileItem> map;
+					try {
+						map = Jtp.getFileItems(request);
+					} catch(FileUploadException e) {
+						logger.warn("",e);
+						throw ModelException.newInstance( "upload_filed", "upload failed - " + e.getMessage() );
+					}
+					FileItem fi = map.get("file");
+					char type = map.get("srcType").getString().charAt(0);
+					long id = Long.parseLong(map.get("srcId").getString());
+					Message.Source src = Message.SourceType.getType(type).getSource(user.getSite(),id);
+					String name = FileUpload.uploadFile(fi,src);
+					JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+					jtpContext.setTimeLimit(request,0L);
+
+					if (map.containsKey("textAreaId")) { %>
+						parent.Nabble.uploadedFile("<%=name%>", "<%=map.get("textAreaId").getString()%>");
+					<% } else { %>
+						parent.Nabble.uploadedFile("<%=name%>");
+					<% } %>
+					parent.Nabble.closeWindows();
+					<%
+				} catch(ModelException e) {
+					logger.warn("file upload failed",e);
+					%>
+					alert("<%=HtmlUtils.javascriptStringEncode(e.getMessage())%>");
+					history.back();
+					<%
+				}
+				%>
+				</script>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/UploadImage.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,49 @@
+
+package nabble.view.web.forum;
+
+import nabble.model.Message;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class UploadImage extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to upload a file.",request,response);
+			return;
+		}
+		PrintWriter out = response.getWriter();
+
+		String nodeId = request.getParameter("node");
+		Site site = user.getSite();
+		Message.Source src = nodeId == null || nodeId.length() == 0? new Message.TempSource(user) : site.getNode(Long.valueOf(nodeId));
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<script type=\"text/javascript\">\r\n			// Fixes the parent variable to consider the nested frames.\r\n			var customParent = function() {\r\n				return parent.parent;\r\n			};\r\n		</script>\r\n		" );
+ Shared.title(request,response,"Upload Image"); 
+		out.print( "\r\n		<script type=\"text/javascript\">\r\n			// Can only be called after the Shared.title() because of jquery\r\n			$(document).ready(function() {\r\n				var $table = $('table', document.body);\r\n				var h = $table.outerHeight() + 15;\r\n				var w = $table.outerWidth() + 15;\r\n				var f = parent.Nabble.get('imageuploader');\r\n				$(f).height(h).width(w);\r\n				$(document.body).css('background-color', $('form').parent().css('background-color'));\r\n			});\r\n		</script>\r\n	</head>\r\n	<body style=\"background:transparent;margin:0\">\r\n		" );
+ Shared.noHeader(request,response); 
+		out.print( "\r\n		<div class=\"shaded-bg-color\" style=\"padding:.5em\">\r\n			<form id=\"image-upload-form\" action=\"/forum/UploadImage2.jtp\" method=\"POST\" enctype=\"multipart/form-data\">\r\n				<input type=\"hidden\" name=\"srcType\" value=\"" );
+		out.print( (src.getMessageSourceType().getCode()) );
+		out.print( "\" />\r\n				<input type=\"hidden\" name=\"srcId\" value=\"" );
+		out.print( (src.getSourceId()) );
+		out.print( "\" />\r\n				<table>\r\n					<tr>\r\n						<td colspan=2>\r\n							<span class=\"form-label\" style=\"display:block;margin-bottom:.3em;text-align:left\">Upload image from my computer:</span>\r\n							<input name=\"image\" id=\"nabble.image\" type=\"file\" size=\"35\" />\r\n						</td>\r\n					</tr>\r\n					<tr>\r\n						<td colspan=2>\r\n							<span class=\"form-label\" style=\"display:block;margin-bottom:.3em;text-align:left\">Or copy an image from the Internet:</span>\r\n							<input name=\"imageUrl\" id=\"nabble.imageUrl\" type=\"text\" size=\"40\" value=\"http://\" />\r\n						</td>\r\n					</tr>\r\n					<tr>\r\n						<td style=\"padding:.5em 0 0\" nowrap>\r\n							<table style=\"float:left;margin-right:2em\">\r\n								<tr>\r\n									<td colspan=2><span class=\"form-label\" style=\"display:block;text-align:left\">Float</span></td>\r\n								</tr>\r\n								<tr>\r\n									<td><input id=\"float-none\" type=\"radio\" name=\"float-position\" value=\"none\" checked=\"y\"/></td>\r\n									<td><label for=\"float-none\">None</label></td>\r\n								</tr>\r\n								<tr>\r\n									<td><input id=\"float-left\" type=\"radio\" name=\"float-position\" value=\"left\"/></td>\r\n									<td><label for=\"float-left\">Left</label></td>\r\n								</tr>\r\n								<tr>\r\n									<td><input id=\"float-center\" type=\"radio\" name=\"float-position\" value=\"center\"/></td>\r\n									<td><label for=\"float-center\">Center</label></td>\r\n								</tr>\r\n								<tr>\r\n									<td><input id=\"float-right\" type=\"radio\" name=\"float-position\" value=\"right\"/></td>\r\n									<td><label for=\"float-right\">Right</label></td>\r\n								</tr>\r\n							</table>\r\n							<table style=\"float:left\">\r\n								<tr>\r\n									<td colspan=2><span class=\"form-label\" style=\"display:block;text-align:left\">Resize</span></td>\r\n								</tr>\r\n								<tr>\r\n									<td><input id=\"resize-none\" type=\"radio\" name=\"resize-size\" value=\"none\" checked=\"y\"/></td>\r\n									<td><label for=\"resize-none\">None</label></td>\r\n								</tr>\r\n								<tr>\r\n									<td><input id=\"resize-small\" type=\"radio\" name=\"resize-size\" value=\"250\"/></td>\r\n									<td><label for=\"resize-small\">Small Size (250 x 250px)</label></td>\r\n								</tr>\r\n								<tr>\r\n									<td><input id=\"resize-medium\" type=\"radio\" name=\"resize-size\" value=\"500\"/></td>\r\n									<td><label for=\"resize-medium\">Medium Size (500 x 500px)</label></td>\r\n								</tr>\r\n								<tr>\r\n									<td><input id=\"resize-big\" type=\"radio\" name=\"resize-size\" value=\"750\"/></td>\r\n									<td><label for=\"resize-big\">Big Size (750 x 750px)</label></td>\r\n								</tr>\r\n							</table>\r\n						</td>\r\n					</tr>\r\n					<tr>\r\n						<td style=\"padding:.7em 0 0\" colspan=2>\r\n							<div class=\"form-label\" style=\"display:block;text-align:left\">Description</div>\r\n							<input type=\"text\" name=\"description\" size=40/>\r\n						</td>\r\n					</tr>\r\n					<tr>\r\n						<td style=\"padding:.5em 0 0\" colspan=2>\r\n							<input type=\"submit\" value=\"Insert Image\">\r\n							<input type=\"button\" value=\"Close\" onclick=\"parent.Nabble.closeWindows()\">\r\n						</td>\r\n					</tr>\r\n				</table>\r\n			</form>\r\n		</div>\r\n		" );
+ Shared.noFooter(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/UploadImage.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,142 @@
+<%
+package nabble.view.web.forum;
+
+import nabble.model.Message;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class UploadImage extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to upload a file.",request,response);
+			return;
+		}
+		PrintWriter out = response.getWriter();
+
+		String nodeId = request.getParameter("node");
+		Site site = user.getSite();
+		Message.Source src = nodeId == null || nodeId.length() == 0? new Message.TempSource(user) : site.getNode(Long.valueOf(nodeId));
+		%>
+		<html>
+			<head>
+				<script type="text/javascript">
+					// Fixes the parent variable to consider the nested frames.
+					var customParent = function() {
+						return parent.parent;
+					};
+				</script>
+				<% Shared.title(request,response,"Upload Image"); %>
+				<script type="text/javascript">
+					// Can only be called after the Shared.title() because of jquery
+					$(document).ready(function() {
+						var $table = $('table', document.body);
+						var h = $table.outerHeight() + 15;
+						var w = $table.outerWidth() + 15;
+						var f = parent.Nabble.get('imageuploader');
+						$(f).height(h).width(w);
+						$(document.body).css('background-color', $('form').parent().css('background-color'));
+					});
+				</script>
+			</head>
+			<body style="background:transparent;margin:0">
+				<% Shared.noHeader(request,response); %>
+				<div class="shaded-bg-color" style="padding:.5em">
+					<form id="image-upload-form" action="/forum/UploadImage2.jtp" method="POST" enctype="multipart/form-data">
+						<input type="hidden" name="srcType" value="<%=src.getMessageSourceType().getCode()%>" />
+						<input type="hidden" name="srcId" value="<%=src.getSourceId()%>" />
+						<table>
+							<tr>
+								<td colspan=2>
+									<span class="form-label" style="display:block;margin-bottom:.3em;text-align:left">Upload image from my computer:</span>
+									<input name="image" id="nabble.image" type="file" size="35" />
+								</td>
+							</tr>
+							<tr>
+								<td colspan=2>
+									<span class="form-label" style="display:block;margin-bottom:.3em;text-align:left">Or copy an image from the Internet:</span>
+									<input name="imageUrl" id="nabble.imageUrl" type="text" size="40" value="http://" />
+								</td>
+							</tr>
+							<tr>
+								<td style="padding:.5em 0 0" nowrap>
+									<table style="float:left;margin-right:2em">
+										<tr>
+											<td colspan=2><span class="form-label" style="display:block;text-align:left">Float</span></td>
+										</tr>
+										<tr>
+											<td><input id="float-none" type="radio" name="float-position" value="none" checked="y"/></td>
+											<td><label for="float-none">None</label></td>
+										</tr>
+										<tr>
+											<td><input id="float-left" type="radio" name="float-position" value="left"/></td>
+											<td><label for="float-left">Left</label></td>
+										</tr>
+										<tr>
+											<td><input id="float-center" type="radio" name="float-position" value="center"/></td>
+											<td><label for="float-center">Center</label></td>
+										</tr>
+										<tr>
+											<td><input id="float-right" type="radio" name="float-position" value="right"/></td>
+											<td><label for="float-right">Right</label></td>
+										</tr>
+									</table>
+									<table style="float:left">
+										<tr>
+											<td colspan=2><span class="form-label" style="display:block;text-align:left">Resize</span></td>
+										</tr>
+										<tr>
+											<td><input id="resize-none" type="radio" name="resize-size" value="none" checked="y"/></td>
+											<td><label for="resize-none">None</label></td>
+										</tr>
+										<tr>
+											<td><input id="resize-small" type="radio" name="resize-size" value="250"/></td>
+											<td><label for="resize-small">Small Size (250 x 250px)</label></td>
+										</tr>
+										<tr>
+											<td><input id="resize-medium" type="radio" name="resize-size" value="500"/></td>
+											<td><label for="resize-medium">Medium Size (500 x 500px)</label></td>
+										</tr>
+										<tr>
+											<td><input id="resize-big" type="radio" name="resize-size" value="750"/></td>
+											<td><label for="resize-big">Big Size (750 x 750px)</label></td>
+										</tr>
+									</table>
+								</td>
+							</tr>
+							<tr>
+								<td style="padding:.7em 0 0" colspan=2>
+									<div class="form-label" style="display:block;text-align:left">Description</div>
+									<input type="text" name="description" size=40/>
+								</td>
+							</tr>
+							<tr>
+								<td style="padding:.5em 0 0" colspan=2>
+									<input type="submit" value="Insert Image">
+									<input type="button" value="Close" onclick="parent.Nabble.closeWindows()">
+								</td>
+							</tr>
+						</table>
+					</form>
+				</div>
+				<% Shared.noFooter(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/UploadImage2.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,113 @@
+
+package nabble.view.web.forum;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.FileUpload;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+
+
+public final class UploadImage2 extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(UploadImage2.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setTimeLimit(request,0L);
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to upload an image.",request,response);
+			return;
+		}
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n<body>\r\n<script type=\"text/javascript\">\r\n" );
+
+		try {
+			final Map<String,FileItem> map;
+			try {
+				map = Jtp.getFileItems(request);
+			} catch(FileUploadException e) {
+				logger.warn("",e);
+				throw ModelException.newInstance( "upload_image_failed", "upload failed - " + e.getMessage() );
+			}
+			FileItem fi = map.get("image");
+
+			if (fi == null || fi.getName() == null || fi.getName().length() == 0) {
+				String urlS = map.get("imageUrl").getString();
+				URL url;
+				try {
+					url = new URL(urlS);
+				} catch(MalformedURLException e) {
+					throw ModelException.newInstance("invalid_url", "Invalid URL: "+urlS, e);
+				}
+			    fi = new FileUpload.UrlFileItem(url);
+				try {
+					fi.getInputStream();
+				} catch (IOException e) {
+					throw ModelException.newInstance("invalid_url", "Invalid URL: "+urlS, e);
+				}
+			}
+
+			char type = map.get("srcType").getString().charAt(0);
+			long id = Long.parseLong(map.get("srcId").getString());
+			Site site = Jtp.getSiteNotNull(request);
+			Message.Source src = Message.SourceType.getType(type).getSource(site,id);
+
+			FileItem resizeSizeFI = map.get("resize-size");
+			int resize = "none".equals(resizeSizeFI.getString())? 0 : Integer.valueOf(resizeSizeFI.getString());
+			String name;
+			try {
+				name = FileUpload.uploadImage(fi,src,resize);
+			} catch (TemplateException e) {
+				throw ModelException.newInstance( "upload_image_failed", "Upload failed: " + e.getMessage());
+			}
+
+			FileItem floatPositionFI = map.get("float-position");
+			String floatPosition = floatPositionFI == null? "none" : floatPositionFI.getString();
+
+			FileItem descriptionFI = map.get("description");
+			String description = descriptionFI == null? "null" : '\'' + HtmlUtils.javascriptStringEncode(HtmlUtils.htmlEncode(descriptionFI.getString())) + '\'';
+			
+		out.print( "\r\nparent.Nabble.uploadedImage('" );
+		out.print( (name) );
+		out.print( "', '" );
+		out.print( (floatPosition) );
+		out.print( "', " );
+		out.print( (description) );
+		out.print( ");\r\nparent.Nabble.closeWindows();\r\n" );
+
+		} catch(ModelException e) {
+			logger.warn("Image upload failed",e);
+			
+		out.print( "\r\nalert(\"" );
+		out.print( (HtmlUtils.javascriptStringEncode(e.getMessage())) );
+		out.print( "\");\r\nhistory.back();\r\n" );
+
+		}
+		
+		out.print( "\r\n</script>\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/forum/UploadImage2.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,111 @@
+<%
+package nabble.view.web.forum;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.FileUpload;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+
+
+public final class UploadImage2 extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(UploadImage2.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setTimeLimit(request,0L);
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to upload an image.",request,response);
+			return;
+		}
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+		<body>
+		<script type="text/javascript">
+		<%
+		try {
+			final Map<String,FileItem> map;
+			try {
+				map = Jtp.getFileItems(request);
+			} catch(FileUploadException e) {
+				logger.warn("",e);
+				throw ModelException.newInstance( "upload_image_failed", "upload failed - " + e.getMessage() );
+			}
+			FileItem fi = map.get("image");
+
+			if (fi == null || fi.getName() == null || fi.getName().length() == 0) {
+				String urlS = map.get("imageUrl").getString();
+				URL url;
+				try {
+					url = new URL(urlS);
+				} catch(MalformedURLException e) {
+					throw ModelException.newInstance("invalid_url", "Invalid URL: "+urlS, e);
+				}
+			    fi = new FileUpload.UrlFileItem(url);
+				try {
+					fi.getInputStream();
+				} catch (IOException e) {
+					throw ModelException.newInstance("invalid_url", "Invalid URL: "+urlS, e);
+				}
+			}
+
+			char type = map.get("srcType").getString().charAt(0);
+			long id = Long.parseLong(map.get("srcId").getString());
+			Site site = Jtp.getSiteNotNull(request);
+			Message.Source src = Message.SourceType.getType(type).getSource(site,id);
+
+			FileItem resizeSizeFI = map.get("resize-size");
+			int resize = "none".equals(resizeSizeFI.getString())? 0 : Integer.valueOf(resizeSizeFI.getString());
+			String name;
+			try {
+				name = FileUpload.uploadImage(fi,src,resize);
+			} catch (TemplateException e) {
+				throw ModelException.newInstance( "upload_image_failed", "Upload failed: " + e.getMessage());
+			}
+
+			FileItem floatPositionFI = map.get("float-position");
+			String floatPosition = floatPositionFI == null? "none" : floatPositionFI.getString();
+
+			FileItem descriptionFI = map.get("description");
+			String description = descriptionFI == null? "null" : '\'' + HtmlUtils.javascriptStringEncode(HtmlUtils.htmlEncode(descriptionFI.getString())) + '\'';
+			%>
+			parent.Nabble.uploadedImage('<%=name%>', '<%=floatPosition%>', <%=description%>);
+			parent.Nabble.closeWindows();
+			<%
+		} catch(ModelException e) {
+			logger.warn("Image upload failed",e);
+			%>
+			alert("<%=HtmlUtils.javascriptStringEncode(e.getMessage())%>");
+			history.back();
+			<%
+		}
+		%>
+		</script>
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/google5f301677badb6983.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,14 @@
+
+package nabble.view.web;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+// Google needs that url for sitemap verification
+public final class google5f301677badb6983 extends HttpServlet {
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+	{
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/google5f301677badb6983.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,14 @@
+<%
+package nabble.view.web;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+// Google needs that url for sitemap verification
+public final class google5f301677badb6983 extends HttpServlet {
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+	{
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/help/Answer.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,68 @@
+
+package nabble.view.web.help;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class Answer extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+		Help help = Help.getHelp(Jtp.getInt(request,"id"));
+		if (help == null) {
+			Shared.simplePage("Invalid Help Article", null, "Invalid Help Article", "The help article you are trying to open doesn't exist or was removed.<br>Please contact Nabble Support if you need help.", request, response);
+			return;
+		}
+		String metaDescription = help.getMetaDescription();
+		String metaKeywords = help.getMetaKeywords();
+		
+		out.print( "\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n	" );
+ if (metaDescription != null) { 
+		out.print( "<META NAME=\"description\" CONTENT=\"" );
+		out.print( (metaDescription) );
+		out.print( "\">" );
+ } 
+		out.print( "\r\n	" );
+ if (metaKeywords != null) { 
+		out.print( "<META NAME=\"keywords\" CONTENT=\"" );
+		out.print( (metaKeywords) );
+		out.print( "\">" );
+ } 
+		out.print( "\r\n	" );
+ Shared.canonical(request, response); 
+		out.print( "\r\n	" );
+ Shared.title(request,response,"Help - "+help.question); 
+		out.print( "\r\n</head>\r\n<body>\r\n	" );
+ Shared.helpHeader(request,response); 
+		out.print( "\r\n	<div class=\"content-description\">\r\n	<h1>" );
+		out.print( (help.question) );
+		out.print( "</h1>\r\n	" );
+		out.print( (help.answer()) );
+		out.print( "\r\n	<p>If you still have questions, visit <a href=\"" );
+		out.print( (Jtp.helpIndexUrl(request,response)) );
+		out.print( "\">Nabble Help</a> or the " );
+		out.print( (Jtp.supportLink()) );
+		out.print( " forum.</p>\r\n\r\n	<p><br />&#171; <a href=\"javascript:history.back();\">Go Back</a></p>\r\n	</div>\r\n	" );
+ Shared.footer(request,response); 
+		out.print( "\r\n	" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/help/Answer.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,58 @@
+<%
+package nabble.view.web.help;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class Answer extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+		Help help = Help.getHelp(Jtp.getInt(request,"id"));
+		if (help == null) {
+			Shared.simplePage("Invalid Help Article", null, "Invalid Help Article", "The help article you are trying to open doesn't exist or was removed.<br>Please contact Nabble Support if you need help.", request, response);
+			return;
+		}
+		String metaDescription = help.getMetaDescription();
+		String metaKeywords = help.getMetaKeywords();
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+		<head>
+			<% if (metaDescription != null) { %><META NAME="description" CONTENT="<%=metaDescription%>"><% } %>
+			<% if (metaKeywords != null) { %><META NAME="keywords" CONTENT="<%=metaKeywords%>"><% } %>
+			<% Shared.canonical(request, response); %>
+			<% Shared.title(request,response,"Help - "+help.question); %>
+		</head>
+		<body>
+			<% Shared.helpHeader(request,response); %>
+			<div class="content-description">
+			<h1><%=help.question%></h1>
+			<%=help.answer()%>
+			<p>If you still have questions, visit <a href="<%=Jtp.helpIndexUrl(request,response)%>">Nabble Help</a> or the <%=Jtp.supportLink()%> forum.</p>
+
+			<p><br />&#171; <a href="javascript:history.back();">Go Back</a></p>
+			</div>
+			<% Shared.footer(request,response); %>
+			<% Shared.analytics(request,response); %>
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/help/DNSConfiguration.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,52 @@
+
+package nabble.view.web.help;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+
+
+public final class DNSConfiguration extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+
+		String nabbleHost = Jtp.getDefaultHost();
+		
+		out.print( "\n<html>\n	<head>\n		" );
+ Shared.head(request,response); 
+		out.print( "\n		<title>Configure a Domain Already Registered</title>\n		<style type=\"text/css\">\n			div.field-title {\n				margin: 0 0 .5em 0;\n			}\n			td.number {\n				width: 3em;\n				padding-bottom: .2em;\n			}\n			span.number {\n				font-size: 200%;\n				padding: 0 .3em .03em;\n				border-width:1px;\n				border-style:solid;\n			}\n			table.dns-table {\n				margin:.5em 0 1em;\n				border-width:1px;\n				border-style:solid;\n				width:80%;\n			}\n			td.dns-addr {\n				width:20%;\n			}\n		</style>\n	</head>\n	<body>\n		" );
+ Shared.minHeaderGlobal(request,response); 
+		out.print( "\n\n		<h1>Configure a Domain Already Registered</h1>\n\n		<p>\n			The custom domain name feature allows you to setup your Nabble application with a domain name\n			you've already registered through another registrar, such as GoDaddy.\n		</p>\n\n		<h2>Follow these steps to setup a custom domain name</h2>\n\n		<table style=\"margin-left:1.3em\">\n			<tr valign=\"top\">\n				<td class=\"number\"><span class=\"number shaded-bg-color medium-border-color\">1</span></td>\n				<td>\n					<div class=\"second-font field-title\">Login to your Domain Registrar account</div>\n					Please go to your registrar's website (e.g., GoDaddy, Register.com, 1and1.com, etc.) and login.\n				</td>\n			</tr>\n		</table>\n\n		<table style=\"margin:1em 0 0 1.3em\">\n			<tr valign=\"top\">\n				<td class=\"number\"><span class=\"number shaded-bg-color medium-border-color\">2</span></td>\n				<td>\n					<div class=\"second-font field-title\">Navigate to the domain's DNS Management page</div>\n					You are looking to modify the CNAME-Record of your domain, NOT the name servers -- You need to leave your name servers configured with your existing registrar.\n				</td>\n			</tr>\n		</table>\n\n		<table style=\"margin:1em 0 0 1.3em\">\n			<tr valign=\"top\">\n				<td class=\"number\"><span class=\"number shaded-bg-color medium-border-color\">3</span></td>\n				<td>\n					<div class=\"second-font field-title\">Setup CNAME-Record</div>\n					If you are trying to setup a domain like www.mydomain.com, then you should set the CNAME-Record as follows:\n					<table class=\"medium-border-color dns-table\">\n						<tr class=\"shaded-bg-color\" style=\"font-weight:bold\">\n							<td>HOST</td>\n							<td class=\"dns-addr\">POINTS TO</td>\n						</tr>\n						<tr>\n							<td>www</td>\n							<td>" );
+		out.print( (nabbleHost) );
+		out.print( "</td>\n						</tr>								\n					</table>\n					If you are trying to setup just a sub-domain of your domain (e.g., myforum.mydomain.com), then you should set the CNAME-Record just for this sub-domain.\n					<table class=\"medium-border-color dns-table\">\n						<tr class=\"shaded-bg-color\" style=\"font-weight:bold\">\n							<td>HOST</td>\n							<td class=\"dns-addr\">POINTS TO</td>\n						</tr>\n						<tr>\n							<td>myforum</td>\n							<td>" );
+		out.print( (nabbleHost) );
+		out.print( "</td>\n						</tr>\n					</table>\n" );
+ /* 
+		out.print( "\n					If you are trying to setup a domain without host (e.g., http://mydomain.com), then you should create the A-Record for the domain root.\n					<table class=\"medium-border-color dns-table\">\n						<tr class=\"shaded-bg-color\" style=\"font-weight:bold\">\n							<td>HOST</td>\n							<td class=\"dns-addr\">POINTS TO</td>\n						</tr>\n						<tr>\n							<td>domain root (usually identified by a * or @ symbol)</td>\n							<td>" );
+		out.print( (nabbleIP) );
+		out.print( "</td>\n						</tr>\n					</table>\n" );
+ */ 
+		out.print( "\n				</td>\n			</tr>\n		</table>\n\n		<p>\n			<b>Note</b>: It can take up to 48 hours (although usually less) for these DNS changes propagate through the internet.\n		</p>\n\n		" );
+ Shared.footer(request,response); 
+		out.print( "\n		" );
+ Shared.analytics(request,response); 
+		out.print( "\n	</body>\n</html>\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/help/DNSConfiguration.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,146 @@
+<%
+package nabble.view.web.help;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+
+
+public final class DNSConfiguration extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+
+		String nabbleHost = Jtp.getDefaultHost();
+		%>
+		<html>
+			<head>
+				<% Shared.head(request,response); %>
+				<title>Configure a Domain Already Registered</title>
+				<style type="text/css">
+					div.field-title {
+						margin: 0 0 .5em 0;
+					}
+					td.number {
+						width: 3em;
+						padding-bottom: .2em;
+					}
+					span.number {
+						font-size: 200%;
+						padding: 0 .3em .03em;
+						border-width:1px;
+						border-style:solid;
+					}
+					table.dns-table {
+						margin:.5em 0 1em;
+						border-width:1px;
+						border-style:solid;
+						width:80%;
+					}
+					td.dns-addr {
+						width:20%;
+					}
+				</style>
+			</head>
+			<body>
+				<% Shared.minHeaderGlobal(request,response); %>
+
+				<h1>Configure a Domain Already Registered</h1>
+
+				<p>
+					The custom domain name feature allows you to setup your Nabble application with a domain name
+					you've already registered through another registrar, such as GoDaddy.
+				</p>
+
+				<h2>Follow these steps to setup a custom domain name</h2>
+
+				<table style="margin-left:1.3em">
+					<tr valign="top">
+						<td class="number"><span class="number shaded-bg-color medium-border-color">1</span></td>
+						<td>
+							<div class="second-font field-title">Login to your Domain Registrar account</div>
+							Please go to your registrar's website (e.g., GoDaddy, Register.com, 1and1.com, etc.) and login.
+						</td>
+					</tr>
+				</table>
+
+				<table style="margin:1em 0 0 1.3em">
+					<tr valign="top">
+						<td class="number"><span class="number shaded-bg-color medium-border-color">2</span></td>
+						<td>
+							<div class="second-font field-title">Navigate to the domain's DNS Management page</div>
+							You are looking to modify the CNAME-Record of your domain, NOT the name servers -- You need to leave your name servers configured with your existing registrar.
+						</td>
+					</tr>
+				</table>
+
+				<table style="margin:1em 0 0 1.3em">
+					<tr valign="top">
+						<td class="number"><span class="number shaded-bg-color medium-border-color">3</span></td>
+						<td>
+							<div class="second-font field-title">Setup CNAME-Record</div>
+							If you are trying to setup a domain like www.mydomain.com, then you should set the CNAME-Record as follows:
+							<table class="medium-border-color dns-table">
+								<tr class="shaded-bg-color" style="font-weight:bold">
+									<td>HOST</td>
+									<td class="dns-addr">POINTS TO</td>
+								</tr>
+								<tr>
+									<td>www</td>
+									<td><%=nabbleHost%></td>
+								</tr>								
+							</table>
+							If you are trying to setup just a sub-domain of your domain (e.g., myforum.mydomain.com), then you should set the CNAME-Record just for this sub-domain.
+							<table class="medium-border-color dns-table">
+								<tr class="shaded-bg-color" style="font-weight:bold">
+									<td>HOST</td>
+									<td class="dns-addr">POINTS TO</td>
+								</tr>
+								<tr>
+									<td>myforum</td>
+									<td><%=nabbleHost%></td>
+								</tr>
+							</table>
+<% /* %>
+							If you are trying to setup a domain without host (e.g., http://mydomain.com), then you should create the A-Record for the domain root.
+							<table class="medium-border-color dns-table">
+								<tr class="shaded-bg-color" style="font-weight:bold">
+									<td>HOST</td>
+									<td class="dns-addr">POINTS TO</td>
+								</tr>
+								<tr>
+									<td>domain root (usually identified by a * or @ symbol)</td>
+									<td><%=nabbleIP%></td>
+								</tr>
+							</table>
+<% */ %>
+						</td>
+					</tr>
+				</table>
+
+				<p>
+					<b>Note</b>: It can take up to 48 hours (although usually less) for these DNS changes propagate through the internet.
+				</p>
+
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/help/Index.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,121 @@
+
+package nabble.view.web.help;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class Index extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request,response,"Help"); 
+		out.print( "\r\n		" );
+ Shared.canonical(request, response); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.helpHeader(request,response); 
+		out.print( "\r\n		<div class=\"content-description\">\r\n\r\n		<h2>Search Help</h2>\r\n		<form action=\"" );
+		out.print( (response.encodeURL("SearchHelp.jtp")) );
+		out.print( "\" name=\"searchform\">\r\n			<input name=\"query\" size=\"40\" value=\"\" />\r\n			<input type=\"submit\" value=\"Search\" />\r\n		</form>\r\n\r\n		<p>Also see the " );
+		out.print( (Jtp.supportLink()) );
+		out.print( " forum.</p>\r\n\r\n		<h2>General</h2>\r\n		<ul>\r\n			<li>" );
+		out.print( (Help.what.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.free.link(request)) );
+		out.print( "</li>\r\n		</ul>\r\n		<h2>Embeddable Applications</h2>\r\n		<ul>\r\n			<li>" );
+		out.print( (Help.embed_what_how.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.embed_skin.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.online.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.subscriptions.link(request)) );
+		out.print( "</li>					\r\n			<li>" );
+		out.print( (Help.search.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.seo.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.embed_permalinks.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.embed_redirect.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.embed_js_options.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.domain_names.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.export.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.pinned_subapps.link(request)) );
+		out.print( "</li>\r\n		</ul>\r\n		<h2>General Users</h2>\r\n		<ul>\r\n			<li>" );
+		out.print( (Help.formatting.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.password.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.anonymous.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.userid.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.delpost.link(request)) );
+		out.print( "</li>\r\n		</ul>\r\n		<h2>Forum Users</h2>\r\n		<ul>\r\n			<li>" );
+		out.print( (Help.mailingListIntro.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.pending.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.moderation.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.stopEmails.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.restrictions.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.owner.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.cataloging.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.inactivity_deletion.link(request)) );
+		out.print( "</li>\r\n		</ul>\r\n		<h2>Mailing List Owners</h2>\r\n		<ul>\r\n			<li>" );
+		out.print( (Help.add.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.find.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.why.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.copy.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.sell.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.post.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.listSpam.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.security.link(request)) );
+		out.print( "</li>\r\n		</ul>\r\n		<h2>Forum Starters</h2>\r\n		<ul>\r\n			<li>" );
+		out.print( (Help.mirror.link(request)) );
+		out.print( "</li>\r\n			<li>" );
+		out.print( (Help.mixed.link(request)) );
+		out.print( "</li>\r\n		</ul>\r\n		<p><br />If you still have questions, visit the " );
+		out.print( (Jtp.supportLink()) );
+		out.print( " forum.</p>\r\n		</div>\r\n\r\n		" );
+ Shared.footer(request,response); 
+		out.print( "\r\n		" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/help/Index.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,110 @@
+<%
+package nabble.view.web.help;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class Index extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<% Shared.title(request,response,"Help"); %>
+				<% Shared.canonical(request, response); %>
+			</head>
+			<body>
+				<% Shared.helpHeader(request,response); %>
+				<div class="content-description">
+
+				<h2>Search Help</h2>
+				<form action="<%=response.encodeURL("SearchHelp.jtp")%>" name="searchform">
+					<input name="query" size="40" value="" />
+					<input type="submit" value="Search" />
+				</form>
+
+				<p>Also see the <%=Jtp.supportLink()%> forum.</p>
+
+				<h2>General</h2>
+				<ul>
+					<li><%=Help.what.link(request)%></li>
+					<li><%=Help.free.link(request)%></li>
+				</ul>
+				<h2>Embeddable Applications</h2>
+				<ul>
+					<li><%=Help.embed_what_how.link(request)%></li>
+					<li><%=Help.embed_skin.link(request)%></li>
+					<li><%=Help.online.link(request)%></li>
+					<li><%=Help.subscriptions.link(request)%></li>					
+					<li><%=Help.search.link(request)%></li>
+					<li><%=Help.seo.link(request)%></li>
+					<li><%=Help.embed_permalinks.link(request)%></li>
+					<li><%=Help.embed_redirect.link(request)%></li>
+					<li><%=Help.embed_js_options.link(request)%></li>
+					<li><%=Help.domain_names.link(request)%></li>
+					<li><%=Help.export.link(request)%></li>
+					<li><%=Help.pinned_subapps.link(request)%></li>
+				</ul>
+				<h2>General Users</h2>
+				<ul>
+					<li><%=Help.formatting.link(request)%></li>
+					<li><%=Help.password.link(request)%></li>
+					<li><%=Help.anonymous.link(request)%></li>
+					<li><%=Help.userid.link(request)%></li>
+					<li><%=Help.delpost.link(request)%></li>
+				</ul>
+				<h2>Forum Users</h2>
+				<ul>
+					<li><%=Help.mailingListIntro.link(request)%></li>
+					<li><%=Help.pending.link(request)%></li>
+					<li><%=Help.moderation.link(request)%></li>
+					<li><%=Help.stopEmails.link(request)%></li>
+					<li><%=Help.restrictions.link(request)%></li>
+					<li><%=Help.owner.link(request)%></li>
+					<li><%=Help.cataloging.link(request)%></li>
+					<li><%=Help.inactivity_deletion.link(request)%></li>
+				</ul>
+				<h2>Mailing List Owners</h2>
+				<ul>
+					<li><%=Help.add.link(request)%></li>
+					<li><%=Help.find.link(request)%></li>
+					<li><%=Help.why.link(request)%></li>
+					<li><%=Help.copy.link(request)%></li>
+					<li><%=Help.sell.link(request)%></li>
+					<li><%=Help.post.link(request)%></li>
+					<li><%=Help.listSpam.link(request)%></li>
+					<li><%=Help.security.link(request)%></li>
+				</ul>
+				<h2>Forum Starters</h2>
+				<ul>
+					<li><%=Help.mirror.link(request)%></li>
+					<li><%=Help.mixed.link(request)%></li>
+				</ul>
+				<p><br />If you still have questions, visit the <%=Jtp.supportLink()%> forum.</p>
+				</div>
+
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/help/SearchHelp.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,95 @@
+
+package nabble.view.web.help;
+
+import fschmidt.util.java.HtmlUtils;
+import nabble.model.Lucene;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+import org.apache.lucene.queryParser.ParseException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class SearchHelp extends HttpServlet {
+
+	static {
+		Help.index();
+	}
+	
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String errorMessage = null;
+		String query = request.getParameter("query");
+		if (query != null) {
+			query = query.trim().replaceAll("\\band\\b","AND").replaceAll("\\bor\\b","OR").replaceAll("\\bnot\\b","NOT");
+		}
+		if (query == null || "".equals(query)) {
+			response.sendRedirect("Index.jtp");
+			return;
+		}
+
+		Help[] help = null;
+		try {
+			help = Lucene.searchHelp(query);
+		} catch(ParseException e) {
+			errorMessage = "This query is invalid.\nFor more information, read [link]http://lucene.apache.org/java/2_4_1/queryparsersyntax.html[/link]";
+		}
+		
+		out.print( "\n<html>\n	<head>\n		" );
+ Shared.title(request,response,"Help"); 
+		out.print( "\n	</head>\n	<body>\n		" );
+ Shared.helpHeader(request,response); 
+		out.print( "\n		<div class=\"content-description\">\n			<h1>Search Results</h1>\n			<form action=\"" );
+		out.print( (response.encodeURL("SearchHelp.jtp")) );
+		out.print( "\" name=\"searchform\">\n				<input name=\"query\" size=\"40\" value=\"" );
+		out.print( (HtmlUtils.htmlEncode(Jtp.hideNull(query))) );
+		out.print( "\" />\n				<input type=\"submit\" value=\"Search\" />\n				<span class=\"small\" style=\"margin-left:2em;\"><a href=\"" );
+		out.print( (response.encodeURL("Index.jtp")) );
+		out.print( "\">Return to Help Topics</a> &#187;</span>\n			</form>\n			<p>\n			" );
+ if (errorMessage != null) {
+						Shared.errorMessage(request, response, errorMessage, "Please check our <a href=\""+Help.search.url()+"\">search tips</a> page.");
+					} else if (help != null && help.length==0) {
+						
+		out.print( "\nNo help topics found for <strong>" );
+		out.print( (HtmlUtils.htmlEncode(query)) );
+		out.print( "</strong>\n" );
+
+					} else {
+						
+		out.print( "\n<strong>" );
+		out.print( (help.length) );
+		out.print( "</strong> help topics found for <strong>" );
+		out.print( (HtmlUtils.htmlEncode(query)) );
+		out.print( "</strong>\n</p>\n<ul>\n" );
+
+					for (int i=0; i<help.length; i++) {
+						
+		out.print( "\n<li>" );
+		out.print( (help[i].link(request)) );
+		out.print( "</li>\n" );
+
+					}
+					
+		out.print( "\n</ul>\n" );
+
+					}
+					
+		out.print( "\n<p><br />If you still have questions, visit the " );
+		out.print( (Jtp.supportLink()) );
+		out.print( " forum.</p>\n</div>\n" );
+ Shared.footer(request,response); 
+		out.print( "\n" );
+ Shared.analytics(request,response); 
+		out.print( "\n</body>\n</html>\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/help/SearchHelp.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,91 @@
+<%
+package nabble.view.web.help;
+
+import fschmidt.util.java.HtmlUtils;
+import nabble.model.Lucene;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+import org.apache.lucene.queryParser.ParseException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public class SearchHelp extends HttpServlet {
+
+	static {
+		Help.index();
+	}
+	
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String errorMessage = null;
+		String query = request.getParameter("query");
+		if (query != null) {
+			query = query.trim().replaceAll("\\band\\b","AND").replaceAll("\\bor\\b","OR").replaceAll("\\bnot\\b","NOT");
+		}
+		if (query == null || "".equals(query)) {
+			response.sendRedirect("Index.jtp");
+			return;
+		}
+
+		Help[] help = null;
+		try {
+			help = Lucene.searchHelp(query);
+		} catch(ParseException e) {
+			errorMessage = "This query is invalid.\nFor more information, read [link]http://lucene.apache.org/java/2_4_1/queryparsersyntax.html[/link]";
+		}
+		%>
+		<html>
+			<head>
+				<% Shared.title(request,response,"Help"); %>
+			</head>
+			<body>
+				<% Shared.helpHeader(request,response); %>
+				<div class="content-description">
+					<h1>Search Results</h1>
+					<form action="<%=response.encodeURL("SearchHelp.jtp")%>" name="searchform">
+						<input name="query" size="40" value="<%=HtmlUtils.htmlEncode(Jtp.hideNull(query))%>" />
+						<input type="submit" value="Search" />
+						<span class="small" style="margin-left:2em;"><a href="<%=response.encodeURL("Index.jtp")%>">Return to Help Topics</a> &#187;</span>
+					</form>
+					<p>
+					<% if (errorMessage != null) {
+						Shared.errorMessage(request, response, errorMessage, "Please check our <a href=\""+Help.search.url()+"\">search tips</a> page.");
+					} else if (help != null && help.length==0) {
+						%>
+						No help topics found for <strong><%=HtmlUtils.htmlEncode(query)%></strong>
+						<%
+					} else {
+						%>
+						<strong><%=help.length%></strong> help topics found for <strong><%=HtmlUtils.htmlEncode(query)%></strong>
+					</p>
+					<ul>
+					<%
+					for (int i=0; i<help.length; i++) {
+						%>
+						<li><%=help[i].link(request)%></li>
+						<%
+					}
+					%>
+					</ul>
+					<%
+					}
+					%>
+					<p><br />If you still have questions, visit the <%=Jtp.supportLink()%> forum.</p>
+				</div>
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
Binary file src/nabble/view/web/images/abuse_email.png has changed
Binary file src/nabble/view/web/images/account.png has changed
Binary file src/nabble/view/web/images/add.png has changed
Binary file src/nabble/view/web/images/arrow.png has changed
Binary file src/nabble/view/web/images/arrowdown.png has changed
Binary file src/nabble/view/web/images/arrowleft.png has changed
Binary file src/nabble/view/web/images/arrowup.png has changed
Binary file src/nabble/view/web/images/avatar100.png has changed
Binary file src/nabble/view/web/images/avatar24.png has changed
Binary file src/nabble/view/web/images/avatar_no.png has changed
Binary file src/nabble/view/web/images/avatar_yes.png has changed
Binary file src/nabble/view/web/images/bold.png has changed
Binary file src/nabble/view/web/images/box.png has changed
Binary file src/nabble/view/web/images/btn_bg.gif has changed
Binary file src/nabble/view/web/images/check.png has changed
Binary file src/nabble/view/web/images/close.png has changed
Binary file src/nabble/view/web/images/collapse.gif has changed
Binary file src/nabble/view/web/images/colors.png has changed
Binary file src/nabble/view/web/images/connect-end.gif has changed
Binary file src/nabble/view/web/images/connect-line.gif has changed
Binary file src/nabble/view/web/images/connect-node-closed.gif has changed
Binary file src/nabble/view/web/images/connect-node.gif has changed
Binary file src/nabble/view/web/images/credits.png has changed
Binary file src/nabble/view/web/images/debian.png has changed
Binary file src/nabble/view/web/images/edit_sm.png has changed
Binary file src/nabble/view/web/images/embed.png has changed
Binary file src/nabble/view/web/images/embedded/atwtmb.png has changed
Binary file src/nabble/view/web/images/embedded/carrot2.png has changed
Binary file src/nabble/view/web/images/embedded/netty.png has changed
Binary file src/nabble/view/web/images/embedded/plone.png has changed
Binary file src/nabble/view/web/images/embedded/ruralmailtalk.png has changed
Binary file src/nabble/view/web/images/exim.png has changed
Binary file src/nabble/view/web/images/eye.png has changed
Binary file src/nabble/view/web/images/feeds.png has changed
Binary file src/nabble/view/web/images/file.png has changed
Binary file src/nabble/view/web/images/file_modified.png has changed
Binary file src/nabble/view/web/images/flag_gray.png has changed
Binary file src/nabble/view/web/images/forum.png has changed
Binary file src/nabble/view/web/images/forum_pin.png has changed
Binary file src/nabble/view/web/images/forum_sm.png has changed
Binary file src/nabble/view/web/images/forum_star_sm.png has changed
Binary file src/nabble/view/web/images/gear.png has changed
Binary file src/nabble/view/web/images/green.gif has changed
Binary file src/nabble/view/web/images/grey.gif has changed
Binary file src/nabble/view/web/images/grip.png has changed
Binary file src/nabble/view/web/images/hand.png has changed
Binary file src/nabble/view/web/images/help/application_nodes.png has changed
Binary file src/nabble/view/web/images/help/distributed.png has changed
Binary file src/nabble/view/web/images/help/help_embed.png has changed
Binary file src/nabble/view/web/images/help/help_embed_default.png has changed
Binary file src/nabble/view/web/images/help/help_embed_example.png has changed
Binary file src/nabble/view/web/images/help/help_embed_permalink.png has changed
Binary file src/nabble/view/web/images/help/help_embed_permalink2.png has changed
Binary file src/nabble/view/web/images/help/help_node_structure.png has changed
Binary file src/nabble/view/web/images/help/help_style_easy.png has changed
Binary file src/nabble/view/web/images/help/help_style_tab.png has changed
Binary file src/nabble/view/web/images/help/help_sub_forums.png has changed
Binary file src/nabble/view/web/images/help/members_only.png has changed
Binary file src/nabble/view/web/images/help/mixed_lengths.png has changed
Binary file src/nabble/view/web/images/help/registered_only.png has changed
Binary file src/nabble/view/web/images/help/subscribe.png has changed
Binary file src/nabble/view/web/images/help/subscribe2.png has changed
Binary file src/nabble/view/web/images/hide.gif has changed
Binary file src/nabble/view/web/images/homepage/archive.png has changed
Binary file src/nabble/view/web/images/homepage/blog.png has changed
Binary file src/nabble/view/web/images/homepage/blog_md.png has changed
Binary file src/nabble/view/web/images/homepage/blog_sm.png has changed
Binary file src/nabble/view/web/images/homepage/forum.png has changed
Binary file src/nabble/view/web/images/homepage/forum_md.png has changed
Binary file src/nabble/view/web/images/homepage/gallery.png has changed
Binary file src/nabble/view/web/images/homepage/gallery_md.png has changed
Binary file src/nabble/view/web/images/homepage/gallery_sm.png has changed
Binary file src/nabble/view/web/images/homepage/gray.png has changed
Binary file src/nabble/view/web/images/homepage/mailing-list.png has changed
Binary file src/nabble/view/web/images/homepage/news_sm.png has changed
Binary file src/nabble/view/web/images/homepage/newspaper.png has changed
Binary file src/nabble/view/web/images/homepage/newspaper_md.png has changed
Binary file src/nabble/view/web/images/homepage/separator.png has changed
Binary file src/nabble/view/web/images/icon_alert.png has changed
Binary file src/nabble/view/web/images/icon_alert_sm.png has changed
Binary file src/nabble/view/web/images/icon_attachment.gif has changed
Binary file src/nabble/view/web/images/icon_blocked.png has changed
Binary file src/nabble/view/web/images/icon_blocked_blue.png has changed
Binary file src/nabble/view/web/images/icon_blocked_gray.png has changed
Binary file src/nabble/view/web/images/icon_blocked_red.png has changed
Binary file src/nabble/view/web/images/icon_down.png has changed
Binary file src/nabble/view/web/images/icon_first.png has changed
Binary file src/nabble/view/web/images/icon_first_disabled.png has changed
Binary file src/nabble/view/web/images/icon_happy.png has changed
Binary file src/nabble/view/web/images/icon_info.png has changed
Binary file src/nabble/view/web/images/icon_last.png has changed
Binary file src/nabble/view/web/images/icon_last_disabled.png has changed
Binary file src/nabble/view/web/images/icon_message.png has changed
Binary file src/nabble/view/web/images/icon_minus.png has changed
Binary file src/nabble/view/web/images/icon_next.png has changed
Binary file src/nabble/view/web/images/icon_next_disabled.png has changed
Binary file src/nabble/view/web/images/icon_orphan.png has changed
Binary file src/nabble/view/web/images/icon_pending.png has changed
Binary file src/nabble/view/web/images/icon_plus.png has changed
Binary file src/nabble/view/web/images/icon_post_message.png has changed
Binary file src/nabble/view/web/images/icon_prev.png has changed
Binary file src/nabble/view/web/images/icon_prev_disabled.png has changed
Binary file src/nabble/view/web/images/icon_read_only_forum.png has changed
Binary file src/nabble/view/web/images/icon_star_blue.png has changed
Binary file src/nabble/view/web/images/icon_star_grey.png has changed
Binary file src/nabble/view/web/images/icon_star_red.png has changed
Binary file src/nabble/view/web/images/icon_starred.png has changed
Binary file src/nabble/view/web/images/icon_tree_forum.png has changed
Binary file src/nabble/view/web/images/icon_tree_post.png has changed
Binary file src/nabble/view/web/images/icon_tri.png has changed
Binary file src/nabble/view/web/images/icon_unstarred.png has changed
Binary file src/nabble/view/web/images/icon_up.png has changed
Binary file src/nabble/view/web/images/image.png has changed
Binary file src/nabble/view/web/images/info.png has changed
Binary file src/nabble/view/web/images/italic.png has changed
Binary file src/nabble/view/web/images/java.gif has changed
Binary file src/nabble/view/web/images/jetty.gif has changed
Binary file src/nabble/view/web/images/jscolor/arrow.gif has changed
Binary file src/nabble/view/web/images/jscolor/cross.gif has changed
Binary file src/nabble/view/web/images/jscolor/hs.png has changed
Binary file src/nabble/view/web/images/jscolor/hv.png has changed
Binary file src/nabble/view/web/images/left.png has changed
Binary file src/nabble/view/web/images/link.png has changed
Binary file src/nabble/view/web/images/loading.png has changed
Binary file src/nabble/view/web/images/lock.png has changed
Binary file src/nabble/view/web/images/lock_sm.png has changed
Binary file src/nabble/view/web/images/logo.gif has changed
Binary file src/nabble/view/web/images/logo2.gif has changed
Binary file src/nabble/view/web/images/logo_blogs.png has changed
Binary file src/nabble/view/web/images/logo_forums.png has changed
Binary file src/nabble/view/web/images/logo_gallery.png has changed
Binary file src/nabble/view/web/images/logo_nabble_forums.png has changed
Binary file src/nabble/view/web/images/logo_nabble_home.png has changed
Binary file src/nabble/view/web/images/logo_news.png has changed
Binary file src/nabble/view/web/images/mail.png has changed
Binary file src/nabble/view/web/images/maintenance_anim.gif has changed
Binary file src/nabble/view/web/images/more.gif has changed
Binary file src/nabble/view/web/images/more.png has changed
Binary file src/nabble/view/web/images/naml.png has changed
Binary file src/nabble/view/web/images/nop.gif has changed
Binary file src/nabble/view/web/images/offline.png has changed
Binary file src/nabble/view/web/images/online.png has changed
Binary file src/nabble/view/web/images/paperclip.png has changed
Binary file src/nabble/view/web/images/people.png has changed
Binary file src/nabble/view/web/images/people_sm.png has changed
Binary file src/nabble/view/web/images/pin.png has changed
Binary file src/nabble/view/web/images/plus.png has changed
Binary file src/nabble/view/web/images/postgres.gif has changed
Binary file src/nabble/view/web/images/promo/anonymous.png has changed
Binary file src/nabble/view/web/images/promo/ban.png has changed
Binary file src/nabble/view/web/images/promo/embed.png has changed
Binary file src/nabble/view/web/images/promo/format.png has changed
Binary file src/nabble/view/web/images/promo/pin.png has changed
Binary file src/nabble/view/web/images/promo/private.png has changed
Binary file src/nabble/view/web/images/promo/simple.png has changed
Binary file src/nabble/view/web/images/promo/thread.png has changed
Binary file src/nabble/view/web/images/promo/views.png has changed
Binary file src/nabble/view/web/images/quote.png has changed
Binary file src/nabble/view/web/images/remove_sm.png has changed
Binary file src/nabble/view/web/images/revert.png has changed
Binary file src/nabble/view/web/images/right.png has changed
Binary file src/nabble/view/web/images/search.png has changed
Binary file src/nabble/view/web/images/shadow.png has changed
Binary file src/nabble/view/web/images/show.gif has changed
Binary file src/nabble/view/web/images/small_square.gif has changed
Binary file src/nabble/view/web/images/smiley/anim_blbl.gif has changed
Binary file src/nabble/view/web/images/smiley/anim_claps.gif has changed
Binary file src/nabble/view/web/images/smiley/anim_confused.gif has changed
Binary file src/nabble/view/web/images/smiley/anim_crazy.gif has changed
Binary file src/nabble/view/web/images/smiley/anim_drunk.gif has changed
Binary file src/nabble/view/web/images/smiley/anim_handshake.gif has changed
Binary file src/nabble/view/web/images/smiley/anim_jump.gif has changed
Binary file src/nabble/view/web/images/smiley/anim_rules.gif has changed
Binary file src/nabble/view/web/images/smiley/anim_working.gif has changed
Binary file src/nabble/view/web/images/smiley/argue.gif has changed
Binary file src/nabble/view/web/images/smiley/bear_hug.gif has changed
Binary file src/nabble/view/web/images/smiley/big_grin.png has changed
Binary file src/nabble/view/web/images/smiley/birthday.gif has changed
Binary file src/nabble/view/web/images/smiley/blbl.gif has changed
Binary file src/nabble/view/web/images/smiley/bouquet.gif has changed
Binary file src/nabble/view/web/images/smiley/clap.gif has changed
Binary file src/nabble/view/web/images/smiley/compute.gif has changed
Binary file src/nabble/view/web/images/smiley/confused.png has changed
Binary file src/nabble/view/web/images/smiley/cry.png has changed
Binary file src/nabble/view/web/images/smiley/dont_know.gif has changed
Binary file src/nabble/view/web/images/smiley/drink.gif has changed
Binary file src/nabble/view/web/images/smiley/grin.gif has changed
Binary file src/nabble/view/web/images/smiley/heart.gif has changed
Binary file src/nabble/view/web/images/smiley/joy.gif has changed
Binary file src/nabble/view/web/images/smiley/laugh.gif has changed
Binary file src/nabble/view/web/images/smiley/oh.png has changed
Binary file src/nabble/view/web/images/smiley/pig.png has changed
Binary file src/nabble/view/web/images/smiley/respect.gif has changed
Binary file src/nabble/view/web/images/smiley/rules.gif has changed
Binary file src/nabble/view/web/images/smiley/smile.png has changed
Binary file src/nabble/view/web/images/smiley/smiley_angry.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_angry2.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_argh.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_beam.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_blush.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_cool.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_cry.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_evil.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_good.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_grin.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_happy.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_hurt.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_music.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_mustach.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_ninja.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_oh.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_oh_no.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_pirate.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_sad.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_scared.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_sleep.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_super.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_teeth.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_thinking.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_tongue.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_uh.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_unhappy.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_what.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_whistling.gif has changed
Binary file src/nabble/view/web/images/smiley/smiley_wink.gif has changed
Binary file src/nabble/view/web/images/smiley/sunglasses.png has changed
Binary file src/nabble/view/web/images/smiley/tongue.png has changed
Binary file src/nabble/view/web/images/smiley/unhappy.png has changed
Binary file src/nabble/view/web/images/smiley/wink.png has changed
Binary file src/nabble/view/web/images/social/delicious.png has changed
Binary file src/nabble/view/web/images/social/digg.png has changed
Binary file src/nabble/view/web/images/social/facebook.png has changed
Binary file src/nabble/view/web/images/social/google.png has changed
Binary file src/nabble/view/web/images/social/linkedin.png has changed
Binary file src/nabble/view/web/images/social/stumbleupon.png has changed
Binary file src/nabble/view/web/images/social/twitter.png has changed
Binary file src/nabble/view/web/images/styles/connect-end.gif has changed
Binary file src/nabble/view/web/images/styles/connect-line.gif has changed
Binary file src/nabble/view/web/images/styles/connect-node.gif has changed
Binary file src/nabble/view/web/images/styles/glossy-dark-highlight.gif has changed
Binary file src/nabble/view/web/images/styles/glossy-dark.gif has changed
Binary file src/nabble/view/web/images/styles/glossy-highlight.gif has changed
Binary file src/nabble/view/web/images/styles/glossy-light-highlight.gif has changed
Binary file src/nabble/view/web/images/styles/glossy-light.gif has changed
Binary file src/nabble/view/web/images/styles/pink-shaded.png has changed
Binary file src/nabble/view/web/images/styles/pink.png has changed
Binary file src/nabble/view/web/images/styles/preview_bluesky.png has changed
Binary file src/nabble/view/web/images/styles/preview_darkness.png has changed
Binary file src/nabble/view/web/images/styles/preview_desert.png has changed
Binary file src/nabble/view/web/images/styles/preview_glossy.png has changed
Binary file src/nabble/view/web/images/styles/preview_moonlight.png has changed
Binary file src/nabble/view/web/images/styles/preview_pink.png has changed
Binary file src/nabble/view/web/images/styles/preview_rainy.png has changed
Binary file src/nabble/view/web/images/styles/rainy-shaded.png has changed
Binary file src/nabble/view/web/images/styles/rainy.png has changed
Binary file src/nabble/view/web/images/submenu.gif has changed
Binary file src/nabble/view/web/images/success.png has changed
Binary file src/nabble/view/web/images/support.png has changed
Binary file src/nabble/view/web/images/terms_email.png has changed
Binary file src/nabble/view/web/images/thread.png has changed
Binary file src/nabble/view/web/images/thread_sm.png has changed
Binary file src/nabble/view/web/images/tool.png has changed
Binary file src/nabble/view/web/images/tool_big.png has changed
Binary file src/nabble/view/web/images/top-dropdown.png has changed
Binary file src/nabble/view/web/images/topShadow.png has changed
Binary file src/nabble/view/web/images/unpin.png has changed
Binary file src/nabble/view/web/images/user_group.png has changed
Binary file src/nabble/view/web/images/view-classic.gif has changed
Binary file src/nabble/view/web/images/view-list.gif has changed
Binary file src/nabble/view/web/images/view-threaded.gif has changed
Binary file src/nabble/view/web/images/views_category.png has changed
Binary file src/nabble/view/web/images/views_children.png has changed
Binary file src/nabble/view/web/images/views_nabble.png has changed
Binary file src/nabble/view/web/images/views_standard.png has changed
Binary file src/nabble/view/web/images/world.png has changed
Binary file src/nabble/view/web/images/zip.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/MailingListNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,200 @@
+package nabble.view.web.mailing_list;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.mail.MailAddress;
+import nabble.model.Db;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelException;
+import nabble.model.Node;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.ListSequence;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.web.template.NodeNamespace;
+import nabble.view.web.template.ServletNamespace;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.List;
+
+
+@Namespace (
+	name = "mailing_list",
+	global = true
+)
+public class MailingListNamespace {
+	private Node node;
+	private MailingList mailingList;
+
+	public MailingListNamespace(Node node) {
+		this.node = node;
+		this.mailingList = node.getMailingList();
+	}
+
+	public MailingListNamespace(MailingList mailingList) {
+		this.node = mailingList.getForum();
+		this.mailingList = mailingList;
+	}
+
+	private DbDatabase db() {
+		return node.getSite().getDb();
+	}
+
+	public static final CommandSpec mailing_list_node = CommandSpec.DO;
+
+	@Command public void mailing_list_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		NodeNamespace ns = new NodeNamespace(node);
+		out.print( interp.getArg(ns,"do") );
+	}
+
+	@Command public void mailing_list_address(IPrintWriter out,Interpreter interp) {
+		out.print( mailingList.getListAddress() );
+	}
+
+	@Command public void mailing_list_url(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( mailingList.getUrl() ) );
+	}
+
+	@Command public void mailing_list_server(IPrintWriter out,Interpreter interp) {
+		out.print( mailingList.getListServer().getType() );
+	}
+
+	@Command public void mailing_list_prefix(IPrintWriter out,Interpreter interp) {
+		out.print( mailingList.getListName() );
+	}
+
+	@Command public void plain_text_only(IPrintWriter out,Interpreter interp) {
+		out.print( mailingList.plainTextOnly() );
+	}
+
+	@Command public void ignore_xnoarchive(IPrintWriter out,Interpreter interp) {
+		out.print( mailingList.ignoreNoArchive() );
+	}
+
+	@Command public void mailing_list_options_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/mailing_list/MailingListOptions.jtp?forum=" + node.getId() ) );
+	}
+
+	@Command public void can_subscribe(IPrintWriter out,Interpreter interp) {
+		out.print( mailingList.getListServer().canSubscribe() );
+	}
+
+	@Command public void subscriber_address(IPrintWriter out,Interpreter interp) {
+		out.print( mailingList.getSubscriberAddress().getAddrSpec() );
+	}
+
+	public static final CommandSpec available_servers = CommandSpec.DO;
+
+	@Command public void available_servers(IPrintWriter out,ScopedInterpreter<Servers> interp)
+		throws IOException, ServletException
+	{
+		String[] serverTypes = ListServer.getAllServerTypes();
+		Object block = interp.getArg(new Servers(Arrays.asList(serverTypes)),"do");
+		out.print(block);
+	}
+
+	@Namespace (
+		name = "server_list",
+		global = false
+	)
+	public static final class Servers extends ListSequence<String> {
+
+		Servers(List<String> serverTypes) {
+			super(serverTypes);
+		}
+
+		@Command public void current_server(IPrintWriter out,Interpreter interp) {
+			out.print(ListServer.getServer(get()).getType());
+		}
+
+		@Command public void current_server_name(IPrintWriter out,Interpreter interp) {
+			out.print(ListServer.getServer(get()).getViewName());
+		}
+	}
+
+	public static final CommandSpec save = new CommandSpec.Builder()
+		.dotParameter("do")
+		.parameters("address","url","server","plain_text_only","ignore_xnoarchive")
+		.optionalParameters("prefix")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void save(IPrintWriter out,Interpreter interp)
+		throws TemplateException
+	{
+		ServletNamespace servletNs = interp.getFromStack(ServletNamespace.class);
+		HttpServletRequest request = servletNs.request;
+		if( !"POST".equals(request.getMethod()) )
+			return;
+
+		String address = interp.getArgString("address");
+		if (address == null)
+			throw ModelException.newInstance("invalid_list_address");
+		address = address.trim().toLowerCase();
+		if (!new MailAddress(address).isValid())
+			throw ModelException.newInstance("invalid_list_address");
+
+		String url = interp.getArgString("url");
+		if (url == null)
+			throw ModelException.newInstance("invalid_list_url");
+		try {
+			new URL(url);
+		} catch (MalformedURLException e) {
+			throw ModelException.newInstance("invalid_list_url");
+		}
+
+		String server = interp.getArgString("server");
+		ListServer listServerType = ListServer.getServer(server);
+
+		String prefix = interp.getArgString("prefix");
+		if (prefix != null && prefix.trim().length() == 0)
+			prefix = null;
+
+		boolean isPlainTextOnly = interp.getArgAsBoolean("plain_text_only",false);
+		boolean isIgnoreXNoArchive = interp.getArgAsBoolean("ignore_xnoarchive",false);
+
+		db().beginTransaction();
+		try {
+			node = node.getGoodCopy(); // In case the node needs to be updated in the "do" block.
+			mailingList = node.getMailingList();
+			if (mailingList != null) {
+				mailingList.setListAddress(address);
+				mailingList.setUrl(url);
+				mailingList.setListServer(listServerType);
+			} else {
+				mailingList = node.newMailingList(listServerType, address, url);
+			}
+			mailingList.setListName(prefix);
+			mailingList.setPlainTextOnly(isPlainTextOnly);
+			mailingList.setIgnoreNoArchive(isIgnoreXNoArchive);
+
+			interp.getArgString("do");
+			mailingList.update();
+			db().commitTransaction();
+		} finally {
+			db().endTransaction();
+		}
+	}
+
+	@Command public void remove(IPrintWriter out,Interpreter interp)
+		throws TemplateException
+	{
+		db().beginTransaction();
+		try {
+			node.getGoodCopy().deleteMailingList();
+			db().commitTransaction();
+		} finally {
+			db().endTransaction();
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/MailingListOptions.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,119 @@
+
+package nabble.view.web.mailing_list;
+
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class MailingListOptions extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		Node forum = Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"forum"));
+		if (forum == null) {
+			response.sendError(HttpServletResponse.SC_GONE, "This app has been deleted.");
+			return;
+		}
+
+		MailingList mailingList = forum.getAssociatedMailingList();
+		ListServer listServer = mailingList.getListServer();
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request,response,"Mailing List Options"); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n	" );
+ Shared.minHeader(request, response, forum);
+		out.print( "\r\n	" );
+ Shared.editHeader(forum.getSubjectHtml(), "Mailing List Options", out); 
+		out.print( "\r\n	\r\n	" );
+
+			if( listServer.canSubscribe() ) {
+				
+		out.print( "\r\n<p>\r\nNabble provides a web archive and gateway to the mailing list: <strong>" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "</strong> (<a href=\"" );
+		out.print( (Help.mailingListIntro.url(request)) );
+		out.print( "\">What is a mailing list?</a>).\r\n</p>\r\n<p>Below are a set of mailing list actions you can take through Nabble. (You may also visit the <a href=\"" );
+		out.print( (mailingList.getUrl()) );
+		out.print( "\" target=\"_top\">" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( " website</a> for additional information and options.)\r\n</p>\r\n\r\n<h3>Subscribe to " );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "</h3>\r\n<p>\r\n	Most mailing lists require subscription before you can post messages. Click <strong>Subscribe</strong> below to send a subscription request to the mailing list: " );
+		out.print( (mailingList.getListAddress()) );
+		out.print( ".\r\n</p>\r\n\r\n<p>\r\n	<strong>REMEMBER</strong>: Clicking \"Subscribe\" only sends the request to the mailing list. You have the following additional step to complete the subscription process.</p>\r\n\r\n<p>\r\n	After clicking Subscribe, wait for a confirmation email which will provide instructions on how to confirm your subscription. Follow the instructions in the confirmation email to confirm your subscription. This usually requires replying to an email or clicking the confirmation link.\r\n	(<strong>NOTE: </strong>Most mailing lists send the confirmation email almost instantaneously. However, some may require that you wait for a few hours.)\r\n</p>\r\n\r\n<p>\r\n<form action=\"Subscribe.jtp\">\r\n<input type=\"hidden\" name=\"forum\" value=\"" );
+		out.print( (forum.getId()) );
+		out.print( "\" />\r\n<input type=\"submit\" value=\"Subscribe\" />\r\n</form>\r\n</p>\r\n" );
+
+				if( !listServer.supportsDeliveryOff() ) {
+					
+		out.print( "\r\n<p>\r\n	Becoming a subscriber allows you to post to a mailing list, but at the same time, you will also receive all the emails posted to the mailing list. If you prefer not to receive the emails, you can unsubscribe after posting to the list.\r\n</p>\r\n\r\n<h3>Unsubscribe</h3>\r\n<p>\r\n	This mailing list does not support the option to turn off email delivery, so you may want to consider unsubscribing; it's acceptable to subscribe to a mailing list to post a message, but then unsubscribe after the message is sent.\r\n</p>\r\n" );
+
+				} else if( listServer.needsDefaults() ) {
+					
+		out.print( "\r\n<p>\r\n	Becoming a subscriber allows you to post messages to a mailing list. However, you also have to receive all the emails posted to the mailing list. If you prefer not to receive the emails, you have the option to turn off mail delivery.\r\n</p>\r\n\r\n<h3>Turn off mail delivery</h3>\r\n<p>This mailing list may allow you to turn off email delivery but remain a subscriber. This means you can post messages without having to receive the emails. This option works only after you have become a subscriber.</p>\r\n" );
+
+					if ( listServer == ListServer.google ) {
+						
+		out.print( "\r\n<p>To turn off email delivery, please visit <a href=\"" );
+		out.print( (mailingList.getUrl()) );
+		out.print( "\">" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "'s website</a>.</p>\r\n" );
+
+					} else {
+						
+		out.print( "\r\n<p><b>Please Note</b>: This option may not work for some early versions of Mailman. In that case, use the Unsubscribe option below.</p>\r\n<p>\r\n<form action=\"SubscribeDefaults.jtp\">\r\n<input type=\"hidden\" name=\"Action\" value=\"set\" />\r\n<input type=\"hidden\" name=\"forum\" value=\"" );
+		out.print( (forum.getId()) );
+		out.print( "\" />\r\n<input type=\"submit\" value=\"Turn off mail delivery\" />\r\n</form>\r\n</p>\r\n" );
+
+					}
+					
+		out.print( "\r\n<h3>Unsubscribe</h3>\r\n<p>You may unsubscribe from this mailing list.</p>\r\n" );
+
+				} else {
+					
+		out.print( "\r\n<p>Your subscription will be with email delivery turned off, so you can post messages without having to receive all list emails.</p>\r\n<h3>Unsubscribe</h3>\r\n<p>You may unsubscribe from this mailing list.</p>\r\n" );
+
+				}
+				
+		out.print( "\r\n<form action=\"Unsubscribe.jtp\">\r\n<input type=\"hidden\" name=\"forum\" value=\"" );
+		out.print( (forum.getId()) );
+		out.print( "\" />\r\n<input type=\"submit\" value=\"Unsubscribe\" />\r\n</form>\r\n" );
+
+			} else {
+				
+		out.print( "\r\n<p>\r\n	Nabble provides a web archive and gateway to the mailing list: <strong>" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "</strong> (<a href=\"" );
+		out.print( (Help.mailingListIntro.url(request)) );
+		out.print( "\">?</a>).\r\n</p>\r\n<p>\r\n	To subscribe or unsubscribe, please visit <a href=\"" );
+		out.print( (mailingList.getUrl()) );
+		out.print( "\">" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "'s website</a>.\r\n</p>\r\n<p>\r\n	<strong>NOTE:</strong> Please complete the subscription process before posting any messages to this mailing list to ensure that your messages are accepted by the mailing list.\r\n</p>\r\n" );
+
+			}
+			
+		out.print( "\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/MailingListOptions.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,146 @@
+<%
+package nabble.view.web.mailing_list;
+
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class MailingListOptions extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		Node forum = Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"forum"));
+		if (forum == null) {
+			response.sendError(HttpServletResponse.SC_GONE, "This app has been deleted.");
+			return;
+		}
+
+		MailingList mailingList = forum.getAssociatedMailingList();
+		ListServer listServer = mailingList.getListServer();
+		%>
+		<html>
+			<head>
+				<% Shared.title(request,response,"Mailing List Options"); %>
+			</head>
+			<body>
+			<% Shared.minHeader(request, response, forum);%>
+			<% Shared.editHeader(forum.getSubjectHtml(), "Mailing List Options", out); %>
+			
+			<%
+			if( listServer.canSubscribe() ) {
+				%>
+				<p>
+				Nabble provides a web archive and gateway to the mailing list: <strong><%=mailingList.getListAddress()%></strong> (<a href="<%=Help.mailingListIntro.url(request)%>">What is a mailing list?</a>).
+				</p>
+				<p>Below are a set of mailing list actions you can take through Nabble. (You may also visit the <a href="<%=mailingList.getUrl()%>" target="_top"><%=mailingList.getListAddress()%> website</a> for additional information and options.)
+				</p>
+
+				<h3>Subscribe to <%=mailingList.getListAddress()%></h3>
+				<p>
+					Most mailing lists require subscription before you can post messages. Click <strong>Subscribe</strong> below to send a subscription request to the mailing list: <%=mailingList.getListAddress()%>.
+				</p>
+
+				<p>
+					<strong>REMEMBER</strong>: Clicking "Subscribe" only sends the request to the mailing list. You have the following additional step to complete the subscription process.</p>
+
+				<p>
+					After clicking Subscribe, wait for a confirmation email which will provide instructions on how to confirm your subscription. Follow the instructions in the confirmation email to confirm your subscription. This usually requires replying to an email or clicking the confirmation link.
+					(<strong>NOTE: </strong>Most mailing lists send the confirmation email almost instantaneously. However, some may require that you wait for a few hours.)
+				</p>
+
+				<p>
+				<form action="Subscribe.jtp">
+				<input type="hidden" name="forum" value="<%=forum.getId()%>" />
+				<input type="submit" value="Subscribe" />
+				</form>
+				</p>
+				<%
+				if( !listServer.supportsDeliveryOff() ) {
+					%>
+					<p>
+						Becoming a subscriber allows you to post to a mailing list, but at the same time, you will also receive all the emails posted to the mailing list. If you prefer not to receive the emails, you can unsubscribe after posting to the list.
+					</p>
+
+					<h3>Unsubscribe</h3>
+					<p>
+						This mailing list does not support the option to turn off email delivery, so you may want to consider unsubscribing; it's acceptable to subscribe to a mailing list to post a message, but then unsubscribe after the message is sent.
+					</p>
+					<%
+				} else if( listServer.needsDefaults() ) {
+					%>
+					<p>
+						Becoming a subscriber allows you to post messages to a mailing list. However, you also have to receive all the emails posted to the mailing list. If you prefer not to receive the emails, you have the option to turn off mail delivery.
+					</p>
+
+					<h3>Turn off mail delivery</h3>
+					<p>This mailing list may allow you to turn off email delivery but remain a subscriber. This means you can post messages without having to receive the emails. This option works only after you have become a subscriber.</p>
+					<%
+					if ( listServer == ListServer.google ) {
+						%>
+						<p>To turn off email delivery, please visit <a href="<%=mailingList.getUrl()%>"><%=mailingList.getListAddress()%>'s website</a>.</p>
+						<%
+					} else {
+						%>
+					<p><b>Please Note</b>: This option may not work for some early versions of Mailman. In that case, use the Unsubscribe option below.</p>
+					<p>
+					<form action="SubscribeDefaults.jtp">
+					<input type="hidden" name="Action" value="set" />
+					<input type="hidden" name="forum" value="<%=forum.getId()%>" />
+					<input type="submit" value="Turn off mail delivery" />
+					</form>
+					</p>
+						<%
+					}
+					%>
+					<h3>Unsubscribe</h3>
+					<p>You may unsubscribe from this mailing list.</p>
+					<%
+				} else {
+					%>
+					<p>Your subscription will be with email delivery turned off, so you can post messages without having to receive all list emails.</p>
+					<h3>Unsubscribe</h3>
+					<p>You may unsubscribe from this mailing list.</p>
+					<%
+				}
+				%>
+				<form action="Unsubscribe.jtp">
+				<input type="hidden" name="forum" value="<%=forum.getId()%>" />
+				<input type="submit" value="Unsubscribe" />
+				</form>
+				<%
+			} else {
+				%>
+				<p>
+					Nabble provides a web archive and gateway to the mailing list: <strong><%=mailingList.getListAddress()%></strong> (<a href="<%=Help.mailingListIntro.url(request)%>">?</a>).
+				</p>
+				<p>
+					To subscribe or unsubscribe, please visit <a href="<%=mailingList.getUrl()%>"><%=mailingList.getListAddress()%>'s website</a>.
+				</p>
+				<p>
+					<strong>NOTE:</strong> Please complete the subscription process before posting any messages to this mailing list to ensure that your messages are accepted by the mailing list.
+				</p>
+				<%
+			}
+			%>
+			<% Shared.footer(request,response); %>
+			<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/Subscribe.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,58 @@
+
+package nabble.view.web.mailing_list;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Init;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Executors;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.SubscribeDefaultsMail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+
+public final class Subscribe extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(Subscribe.class);
+
+	private final static long secondsUntilDeliveryOffMail = Init.get("secondsUntilDeliveryOffMail",20*60);  // why so long?
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+		if (user == null) {
+			Jtp.login("You must login to be able to subscribe to a list.",request,response);
+			return;
+		}
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+		MailingList mailingList = forum.getAssociatedMailingList();
+		Mail userSubscribeMail = mailingList.subscribeMail(user);
+		ModelHome.send(userSubscribeMail);
+		logger.info("subscription request from "+user.getEmail()+" to "+mailingList.getListAddress());
+
+		if (mailingList.getListServer().needsDefaults() && mailingList.getListServer() != ListServer.google) {
+			String url = ServletUtils.getContextURL(request) + "/mailing_list/SubscribeDefaults.jtp?forum="+forum.getId()+"&k="+mailingList.getPassword(user);
+			final Mail mail = SubscribeDefaultsMail.newMail(user.getEmail(), mailingList, url);
+			Executors.schedule(
+				new Runnable(){public void run(){
+					ModelHome.send(mail);
+				}}, secondsUntilDeliveryOffMail, TimeUnit.SECONDS
+			);
+		}
+		response.sendRedirect("Subscribe2.jtp?forum="+forum.getId());
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/Subscribe.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,58 @@
+<%
+package nabble.view.web.mailing_list;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Init;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Executors;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.SubscribeDefaultsMail;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+
+public final class Subscribe extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(Subscribe.class);
+
+	private final static long secondsUntilDeliveryOffMail = Init.get("secondsUntilDeliveryOffMail",20*60);  // why so long?
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+		if (user == null) {
+			Jtp.login("You must login to be able to subscribe to a list.",request,response);
+			return;
+		}
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+		MailingList mailingList = forum.getAssociatedMailingList();
+		Mail userSubscribeMail = mailingList.subscribeMail(user);
+		ModelHome.send(userSubscribeMail);
+		logger.info("subscription request from "+user.getEmail()+" to "+mailingList.getListAddress());
+
+		if (mailingList.getListServer().needsDefaults() && mailingList.getListServer() != ListServer.google) {
+			String url = ServletUtils.getContextURL(request) + "/mailing_list/SubscribeDefaults.jtp?forum="+forum.getId()+"&k="+mailingList.getPassword(user);
+			final Mail mail = SubscribeDefaultsMail.newMail(user.getEmail(), mailingList, url);
+			Executors.schedule(
+				new Runnable(){public void run(){
+					ModelHome.send(mail);
+				}}, secondsUntilDeliveryOffMail, TimeUnit.SECONDS
+			);
+		}
+		response.sendRedirect("Subscribe2.jtp?forum="+forum.getId());
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/Subscribe2.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,72 @@
+
+package nabble.view.web.mailing_list;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import fschmidt.util.mail.MailAddress;
+import nabble.model.Node;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+
+public final class Subscribe2 extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to be able to subscribe to a list.",request,response);
+			return;
+		}
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+		MailingList mailingList = forum.getAssociatedMailingList();
+		ListServer listServer = mailingList.getListServer();
+		MailAddress emailAddress = new MailAddress(user.getEmail(), user.getName());
+		
+		out.print( "\n<html>\n<head>\n" );
+
+		Shared.title(request,response,"Subscribe to mailing list");
+		
+		out.print( "\n</head>\n<body>\n" );
+ Shared.minHeader(request, response, forum); 
+		out.print( "\n<h1>Subscription Request Was Sent</h1>\n<p>\nA subscription request was sent on your behalf to the mailing list: <strong>" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "</strong>.\nYou will soon receive a confirmation request by email at <strong>" );
+		out.print( (user.getEmail()) );
+		out.print( "</strong>. Please follow the\ninstructions in the email to complete your subscription.\n</p>\n\n<p>\nTo ensure that your posts are accepted by the mailing list, complete the subscription process before posting any messages to this forum.  If you have already posted messages that have not been accepted by the mailing list, you will need to resend them after you have finished subscribing.\n</p>\n\n<p><strong>Remember:</strong> You are not subscribed to the <strong>" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "</strong> list until you have followed the instructions in the email.</p>\n\n" );
+
+		if( listServer.needsDefaults() && listServer!=ListServer.google ) {
+			
+		out.print( "\n<p>\nAlso, we have sent you an email to help you turn off list mail delivery to your email address (" );
+		out.print( (user.getEmail()) );
+		out.print( "). After your subscription has been approved, please click the corresponding link in the email to turn off list mail delivery.\n</p>\n" );
+
+		}
+		
+		out.print( "\n<br /><br />\n<p>\n&#171; <a href=\"" );
+		out.print( (Jtp.path(forum)) );
+		out.print( "\">Return to forum</a><br /><br />\n</p>\n" );
+
+		Shared.footer(request,response);
+		
+		out.print( "\n" );
+ Shared.analytics(request,response); 
+		out.print( "\n</body>\n</html>\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/Subscribe2.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,81 @@
+<%
+package nabble.view.web.mailing_list;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import fschmidt.util.mail.MailAddress;
+import nabble.model.Node;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+
+public final class Subscribe2 extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to be able to subscribe to a list.",request,response);
+			return;
+		}
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+		MailingList mailingList = forum.getAssociatedMailingList();
+		ListServer listServer = mailingList.getListServer();
+		MailAddress emailAddress = new MailAddress(user.getEmail(), user.getName());
+		%>
+		<html>
+		<head>
+		<%
+		Shared.title(request,response,"Subscribe to mailing list");
+		%>
+		</head>
+		<body>
+		<% Shared.minHeader(request, response, forum); %>
+		<h1>Subscription Request Was Sent</h1>
+		<p>
+		A subscription request was sent on your behalf to the mailing list: <strong><%=mailingList.getListAddress()%></strong>.
+		You will soon receive a confirmation request by email at <strong><%=user.getEmail()%></strong>. Please follow the
+		instructions in the email to complete your subscription.
+		</p>
+		
+		<p>
+		To ensure that your posts are accepted by the mailing list, complete the subscription process before posting any messages to this forum.  If you have already posted messages that have not been accepted by the mailing list, you will need to resend them after you have finished subscribing.
+		</p>
+		
+		<p><strong>Remember:</strong> You are not subscribed to the <strong><%=mailingList.getListAddress()%></strong> list until you have followed the instructions in the email.</p>
+
+		<%
+		if( listServer.needsDefaults() && listServer!=ListServer.google ) {
+			%>
+			<p>
+			Also, we have sent you an email to help you turn off list mail delivery to your email address (<%=user.getEmail()%>). After your subscription has been approved, please click the corresponding link in the email to turn off list mail delivery.
+			</p>
+			<%
+		}
+		%>
+		<br /><br />
+		<p>
+		&#171; <a href="<%=Jtp.path(forum)%>">Return to forum</a><br /><br />
+		</p>
+		<%
+		Shared.footer(request,response);
+		%>
+		<% Shared.analytics(request,response); %>
+		</body>
+		</html>
+		<%
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/SubscribeDefaults.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,93 @@
+
+package nabble.view.web.mailing_list;
+
+import fschmidt.util.mail.Mail;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class SubscribeDefaults extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(SubscribeDefaults.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to be able to subscribe to a list.",request,response);
+			return;
+		}
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+		MailingList mailingList = forum.getAssociatedMailingList();
+		ListServer listServer = mailingList.getListServer();
+		if( "set".equals( request.getParameter("Action") ) ) {
+			Mail defaultsMail = mailingList.defaultsMail(user, request.getParameter("k"));
+			ModelHome.send(defaultsMail);
+			logger.info("set nomail request from "+user.getEmail()
+					+" to "+mailingList.getListAddress());
+			response.sendRedirect("SubscribeDefaults.jtp?forum="+forum.getId()+"&done=y");
+			return;
+		}
+		boolean done = "y".equals(request.getParameter("done"));
+		
+		out.print( "\n<html>\n<head>\n	" );
+ Shared.title(request,response,"Subscribe to mailing list"); 
+		out.print( "\n</head>\n<body>\n	" );
+ Shared.minHeader(request, response, forum); 
+		out.print( "\n	<h1>Mailing list subscription</h1>\n	" );
+
+			if (done) {
+				
+		out.print( "\n<p>\n	The request to turn off list mail delivery from <strong>" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "</strong> to your email address (" );
+		out.print( (user.getEmail()) );
+		out.print( ") has been submitted. You should not receive any more messages from this mailing list at this email address.\n</p>\n<br /><br />\n<p>\n	&#171; <a href=\"" );
+		out.print( (Jtp.path(forum)) );
+		out.print( "\">Return to forum</a>\n</p>\n" );
+
+			} else if(listServer.supportsDeliveryOff()){
+				
+		out.print( "\n<form method=post action=\"" );
+		out.print( (response.encodeURL("SubscribeDefaults.jtp")) );
+		out.print( "\">\n<input type=hidden name=\"Action\" value=\"set\">\n<input type=hidden name=\"forum\" value=\"" );
+		out.print( (forum.getId()) );
+		out.print( "\">\n<input type=hidden name=\"k\" value=\"" );
+		out.print( (Jtp.hideNull(request.getParameter("k"))) );
+		out.print( "\">\n<p>\n	This will submit a request to turn off mail delivery from the mailing list <strong>" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "</strong>.\n</p>\n\n<p>\n	<strong>NOTE: </strong> Submitting this request will only work after your subscription to the mailing list\nhas been approved. If your subscription is not yet approved, you will receive an error message via email. In this case, please submit the request again after your subscription is approved.\n</p>\n<br /><br />\n<input type=submit value=\"Turn Off Email Delivery\">\n</form>\n" );
+
+			} else {
+				
+		out.print( "\n	<strong>We're sorry!</strong> Turning off email delivery is not supported by <strong>" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "</strong>. However, you can use the Unsubscribe option offered by the mailing list to stop receiving emails from them.\n" );
+
+			}
+			
+		out.print( "\n" );
+ Shared.footer(request,response); 
+		out.print( "\n" );
+ Shared.analytics(request,response); 
+		out.print( "\n</body>\n</html>\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/SubscribeDefaults.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,98 @@
+<%
+package nabble.view.web.mailing_list;
+
+import fschmidt.util.mail.Mail;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class SubscribeDefaults extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(SubscribeDefaults.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to be able to subscribe to a list.",request,response);
+			return;
+		}
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+		MailingList mailingList = forum.getAssociatedMailingList();
+		ListServer listServer = mailingList.getListServer();
+		if( "set".equals( request.getParameter("Action") ) ) {
+			Mail defaultsMail = mailingList.defaultsMail(user, request.getParameter("k"));
+			ModelHome.send(defaultsMail);
+			logger.info("set nomail request from "+user.getEmail()
+					+" to "+mailingList.getListAddress());
+			response.sendRedirect("SubscribeDefaults.jtp?forum="+forum.getId()+"&done=y");
+			return;
+		}
+		boolean done = "y".equals(request.getParameter("done"));
+		%>
+		<html>
+		<head>
+			<% Shared.title(request,response,"Subscribe to mailing list"); %>
+		</head>
+		<body>
+			<% Shared.minHeader(request, response, forum); %>
+			<h1>Mailing list subscription</h1>
+			<%
+			if (done) {
+				%>
+				<p>
+					The request to turn off list mail delivery from <strong><%=mailingList.getListAddress()%></strong> to your email address (<%=user.getEmail()%>) has been submitted. You should not receive any more messages from this mailing list at this email address.
+				</p>
+				<br /><br />
+				<p>
+					&#171; <a href="<%=Jtp.path(forum)%>">Return to forum</a>
+				</p>
+				<%
+			} else if(listServer.supportsDeliveryOff()){
+				%>
+				<form method=post action="<%=response.encodeURL("SubscribeDefaults.jtp")%>">
+				<input type=hidden name="Action" value="set">
+				<input type=hidden name="forum" value="<%=forum.getId()%>">
+				<input type=hidden name="k" value="<%=Jtp.hideNull(request.getParameter("k"))%>">
+				<p>
+					This will submit a request to turn off mail delivery from the mailing list <strong><%=mailingList.getListAddress()%></strong>.
+				</p>
+
+				<p>
+					<strong>NOTE: </strong> Submitting this request will only work after your subscription to the mailing list
+				has been approved. If your subscription is not yet approved, you will receive an error message via email. In this case, please submit the request again after your subscription is approved.
+				</p>
+				<br /><br />
+				<input type=submit value="Turn Off Email Delivery">
+				</form>
+				<%
+			} else {
+				%>
+					<strong>We're sorry!</strong> Turning off email delivery is not supported by <strong><%=mailingList.getListAddress()%></strong>. However, you can use the Unsubscribe option offered by the mailing list to stop receiving emails from them.
+				<%
+			}
+			%>
+			<% Shared.footer(request,response); %>
+			<% Shared.analytics(request,response); %>
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/SubscribeToMailingList.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,222 @@
+
+package nabble.view.web.mailing_list;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import fschmidt.util.mail.TextContent;
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class SubscribeToMailingList extends HttpServlet implements AuthorizingServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(SubscribeToMailingList.class);
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"node")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response); 
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+
+		if (user == null) {
+			Jtp.login("You must login to subscribe to mailing list.", request, response);
+			return;
+		}
+
+		Node node = Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request, "node"));
+		boolean allowed = Jtp.canBeEditedBy(node,user);
+		if (!allowed) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		PrintWriter out = response.getWriter();
+		boolean isSendEmail = "SendEmail".equals(request.getParameter("action"));
+
+		MailingList mailingList = node.getMailingList();
+		String subscriptionAddress = mailingList.getSubscriberAddress().getAddrSpec();
+		char c = 'A';
+		
+		out.print( "\n<html>\n	<head>\n		<META NAME=\"robots\" CONTENT=\"noindex,nofollow\">\n		" );
+ Shared.title(request,response,"Subscription Instructions"); 
+		out.print( "\n		<style type=\"text/css\">\n			div.field-title {\n				margin-top: 0;\n			}\n			td.number {\n				width: 3em;\n				padding-bottom: .2em;\n			}\n			span.number {\n				font-size: 200%;\n				padding: 0 .3em .03em;\n				border-width:1px;\n				border-style:solid;\n			}\n		</style>\n	</head>\n	<body>\n		" );
+ Shared.minHeader(request, response, node);
+		out.print( "\n		" );
+ Shared.editHeader(node.getSubjectHtml(), "Subscription Instructions", out); 
+		out.print( "\n		" );
+
+				if (isSendEmail && "POST".equals(request.getMethod())) {
+					String errorMsg = sendRequest(node, request, out);
+					Shared.errorMessage(request, response, errorMsg, "Failed to send the request." );
+				}
+				
+		out.print( "\n<div class=\"field-box light-border-color\">\n	<div class=\"second-font field-title\">General Instructions</div>\n	<div style=\"margin-left:1.5em\">\n		Your mailing list archive will <b>ONLY</b> work properly if you have the following email address subscribed to the mailing list:\n		<div class=\"info-message\" style=\"margin:.2em;padding:.2em;font-weight:bold\">" );
+		out.print( (subscriptionAddress) );
+		out.print( "</div>\n		This can be done in several ways. Below you can find the most common ones.\n	</div>\n</div>\n\n<div class=\"second-font field-title\" style=\"margin-top:1em\">Subscription Options</div>\n<div class=\"weak-color\">Choose the best option for this mailing list.</div>\n\n<div class=\"field-box light-border-color\">\n	<table style=\"margin-left:1.3em\">\n		<tr valign=\"top\">\n			<td class=\"number\"><span class=\"number shaded-bg-color medium-border-color\">" );
+		out.print( (c++) );
+		out.print( "</span></td>\n			<td>\n				<div class=\"second-font field-title\">Add to subscriber's list</div>\n				Mailing list administrators usually can add email addresses to the subscribers' list directly.\n				If you can do this, add <b>" );
+		out.print( (subscriptionAddress) );
+		out.print( "</b> to that list.\n			</td>\n		</tr>\n	</table>\n</div>\n\n" );
+ if (mailingList.getListServer() == ListServer.google) { 
+		out.print( "\n<div class=\"field-box light-border-color\">\n	<table style=\"margin-left:1.3em\">\n		<tr valign=\"top\">\n			<td class=\"number\"><span class=\"number shaded-bg-color medium-border-color\">" );
+		out.print( (c++) );
+		out.print( "</span></td>\n			<td>\n				<div class=\"second-font field-title\">Use the \"Invite\" feature of GoogleGroups </div>\n				You can go to the <a href=\"http://groups.google.com/\" rel=\"nofollow\">Google Groups website</a> and\n				invite the following address to be a member:\n				<b>" );
+		out.print( (subscriptionAddress) );
+		out.print( "</b>.<br>\n				The confirmation email will be forwarded to you so that you can complete this step.\n			</td>\n		</tr>\n	</table>\n</div>\n<div class=\"field-box light-border-color\">\n	<table style=\"margin-left:1.3em\">\n		<tr valign=\"top\">\n			<td class=\"number\"><span class=\"number shaded-bg-color medium-border-color\">" );
+		out.print( (c++) );
+		out.print( "</span></td>\n			<td>\n				<div class=\"second-font field-title\">Create an account in GoogleGroups for the subscription email</div>\n				You can go to the <a href=\"http://groups.google.com/\" rel=\"nofollow\">Google Groups website</a> and\n				create an account for this email:\n				<b>" );
+		out.print( (subscriptionAddress) );
+		out.print( "</b>.<br>\n				(Google will probably send a confirmation number to your cell phone so that you can finish the registration)<br>\n				When you have finished the account set up, you can go to the mailing list page and click on \"Join this group\".\n			</td>\n		</tr>\n	</table>\n</div>\n" );
+ } else { 
+		out.print( "\n	<div class=\"field-box light-border-color\">\n		<table style=\"margin-left:1.3em\">\n			<tr valign=\"top\">\n				<td class=\"number\"><span class=\"number shaded-bg-color medium-border-color\">" );
+		out.print( (c++) );
+		out.print( "</span></td>\n				<td>\n					<div class=\"second-font field-title\">Go to mailing list website</div>\n					You can go to the mailing list homepage (<a href=\"" );
+		out.print( (mailingList.getUrl()) );
+		out.print( "\">" );
+		out.print( (mailingList.getUrl()) );
+		out.print( "</a>)\n					and subscribe <b>" );
+		out.print( (subscriptionAddress) );
+		out.print( "</b> to the list.\n					The confirmation email will be forwarded to your email address as soon as it is received by Nabble.com.\n					You will have to follow the instructions in that email to confirm this request.\n				</td>\n			</tr>\n		</table>\n	</div>\n" );
+ } 
+		out.print( "\n" );
+ emailForm(node, true, "Subscribe by email", "Send Subscription Request", c, request, out); 
+		out.print( "\n\n" );
+ Shared.footer(request,response); 
+		out.print( "\n" );
+ Shared.analytics(request,response); 
+		out.print( "\n</body>\n</html>\n" );
+
+	}
+
+	static String sendRequest(Node node, HttpServletRequest request, PrintWriter out)
+		throws ServletException, IOException
+	{
+		String emailFrom = request.getParameter("emailFrom");
+		String emailToUser = request.getParameter("emailToUser");
+		String emailToDomain = request.getParameter("emailToDomain");
+		String emailSubject = request.getParameter("emailSubject");
+		String emailBody = request.getParameter("emailBody");
+
+		if (emailFrom == null || emailToUser == null || emailToDomain == null) {
+			return "Invalid message parameters";
+		} else {
+			MailAddress toAddress = new MailAddress(emailToUser + emailToDomain);
+			try {
+				Mail mail = MailHome.newMail();
+				mail.setFrom(new MailAddress(emailFrom, "Nabble Forums"));
+				mail.setTo(toAddress);
+				mail.setSubject(emailSubject);
+				mail.setContent(new PlainTextContent(emailBody));
+				ModelHome.send(mail);
+			} catch(MailException e) {
+				logger.warn("",e);
+				return e.getMessage();
+			}
+			
+		out.print( "\n<div class=\"info-message\" style=\"padding:1em\">\n	<b>Email Sent</b><br/>\n	The request has been successfully sent. The confirmation email will be forwarded to your email\n	address as soon as it is received by Nabble. You will have to follow the instructions in\n	that email to finish the process.<br/>\n	<a href=\"" );
+		out.print( (Jtp.path(node)) );
+		out.print( "\">Return to forum</a>\n</div>\n" );
+
+		}
+		return null;
+	}	
+
+	static void emailForm(Node node, boolean isSubscription, String title, String button, char c, HttpServletRequest request, PrintWriter out) {
+		MailingList mailingList = node.getMailingList();
+		if (!mailingList.getListServer().canSubscribe())
+			return;
+		
+		out.print( "\n<div class=\"field-box light-border-color\">\n	<table style=\"margin-left:1.3em\">\n		<tr valign=\"top\">\n			<td class=\"number\"><span class=\"number shaded-bg-color medium-border-color\">" );
+		out.print( (c) );
+		out.print( "</span></td>\n			<td>\n				<div class=\"second-font field-title\">" );
+		out.print( (title) );
+		out.print( "</div>\n				You can send the request by email. Default content of the email is\n				provided according to the mailing list server type (<b>" );
+		out.print( (mailingList.getListServer().getViewName()) );
+		out.print( "</b>)\n				that you have specified during the mailing list setup. You can modify it if necessary.\n			</td>\n		</tr>\n	</table>\n</div>\n" );
+
+		String emailFrom = request.getParameter("emailFrom");
+		String emailToUser = request.getParameter("emailToUser");
+		String emailToDomain = request.getParameter("emailToDomain");
+		String emailSubject = request.getParameter("emailSubject");
+		String emailBody = request.getParameter("emailBody");
+		Mail mail = isSubscription? mailingList.subscribeMail(): mailingList.unsubscribeMail();
+
+		if (emailFrom == null) {
+			emailFrom = mail.getFrom().getAddrSpec();
+		}
+
+		if (emailToUser == null || emailToDomain == null) {
+			String emailTo = mail.getTo()[0].getAddrSpec();
+			int index = emailTo.indexOf("@");
+			if(index < 0) {
+				throw new RuntimeException("Invalid emailTo: " + emailTo);
+			}
+			emailToUser = emailTo.substring(0, index);
+			emailToDomain = emailTo.substring(index);
+		}
+
+		if (emailSubject == null) {
+			emailSubject = Jtp.hideNull(mail.getSubject());
+		}
+
+		if (emailBody == null) {
+			emailBody = ((TextContent) mail.getContent()).getText();
+		}
+		
+		out.print( "\n<form method=\"post\" action=\"/mailing_list/" );
+		out.print( (isSubscription? "SubscribeToMailingList.jtp":"UnsubscribeFromMailingList.jtp") );
+		out.print( "\" accept-charset=\"UTF-8\">\n	<input type=\"hidden\" name=\"node\" value=\"" );
+		out.print( (node.getId()) );
+		out.print( "\">\n	<input type=\"hidden\" name=\"action\" value=\"SendEmail\">\n	<input type=\"hidden\" name=\"emailToDomain\" value=\"" );
+		out.print( (emailToDomain) );
+		out.print( "\">\n	<input type=\"hidden\" name=\"emailToUser\" value=\"" );
+		out.print( (emailToUser) );
+		out.print( "\">\n	<input type=\"hidden\" name=\"emailSubject\" value=\"" );
+		out.print( (emailSubject) );
+		out.print( "\">\n	<input type=\"hidden\" name=\"emailBody\" value=\"" );
+		out.print( (emailBody) );
+		out.print( "\">\n	<input type=\"hidden\" name=\"emailFrom\" value=\"" );
+		out.print( (emailFrom) );
+		out.print( "\"/>\n	<table style=\"margin-left:2em\">\n		<tr>\n			<td align=right>From:&nbsp;</td>\n			<td><input type=\"text\" name=\"emailFrom2\" value=\"" );
+		out.print( (emailFrom) );
+		out.print( "\" size=\"50\" disabled/></td>\n		</tr>\n		<tr>\n			<td align=right>To:&nbsp;</td>\n			<td valign=\"middle\"><input type=\"text\" name=\"emailToUser2\" value=\"" );
+		out.print( (emailToUser) );
+		out.print( "\" size=\"50\" disabled/>" );
+		out.print( (emailToDomain) );
+		out.print( "</td>\n		</tr>\n		<tr>\n			<td align=right>Subject:&nbsp;</td>\n			<td><input type=\"text\" name=\"emailSubject2\" value=\"" );
+		out.print( (emailSubject) );
+		out.print( "\" size=\"50\" disabled/></td>\n		</tr>\n		<tr>\n			<td align=right valign=top>Message:&nbsp;</td>\n			<td><textarea cols=\"50\" rows=\"5\" name=\"emailBody2\" wrap=\"SOFT\" disabled>" );
+		out.print( (emailBody) );
+		out.print( "</textarea></td>\n		</tr>\n		<tr>\n			<td><br></td>\n			<td>\n				<input type=\"submit\" value=\"" );
+		out.print( (button) );
+		out.print( "\" />\n			</td>\n		</tr>\n	</table>\n</form>\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/SubscribeToMailingList.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,296 @@
+<%
+package nabble.view.web.mailing_list;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import fschmidt.util.mail.TextContent;
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class SubscribeToMailingList extends HttpServlet implements AuthorizingServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(SubscribeToMailingList.class);
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"node")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response); 
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+
+		if (user == null) {
+			Jtp.login("You must login to subscribe to mailing list.", request, response);
+			return;
+		}
+
+		Node node = Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request, "node"));
+		boolean allowed = Jtp.canBeEditedBy(node,user);
+		if (!allowed) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		PrintWriter out = response.getWriter();
+		boolean isSendEmail = "SendEmail".equals(request.getParameter("action"));
+
+		MailingList mailingList = node.getMailingList();
+		String subscriptionAddress = mailingList.getSubscriberAddress().getAddrSpec();
+		char c = 'A';
+		%>
+		<html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow">
+				<% Shared.title(request,response,"Subscription Instructions"); %>
+				<style type="text/css">
+					div.field-title {
+						margin-top: 0;
+					}
+					td.number {
+						width: 3em;
+						padding-bottom: .2em;
+					}
+					span.number {
+						font-size: 200%;
+						padding: 0 .3em .03em;
+						border-width:1px;
+						border-style:solid;
+					}
+				</style>
+			</head>
+			<body>
+				<% Shared.minHeader(request, response, node);%>
+				<% Shared.editHeader(node.getSubjectHtml(), "Subscription Instructions", out); %>
+				<%
+				if (isSendEmail && "POST".equals(request.getMethod())) {
+					String errorMsg = sendRequest(node, request, out);
+					Shared.errorMessage(request, response, errorMsg, "Failed to send the request." );
+				}
+				%>
+				<div class="field-box light-border-color">
+					<div class="second-font field-title">General Instructions</div>
+					<div style="margin-left:1.5em">
+						Your mailing list archive will <b>ONLY</b> work properly if you have the following email address subscribed to the mailing list:
+						<div class="info-message" style="margin:.2em;padding:.2em;font-weight:bold"><%=subscriptionAddress%></div>
+						This can be done in several ways. Below you can find the most common ones.
+					</div>
+				</div>
+
+				<div class="second-font field-title" style="margin-top:1em">Subscription Options</div>
+				<div class="weak-color">Choose the best option for this mailing list.</div>
+
+				<div class="field-box light-border-color">
+					<table style="margin-left:1.3em">
+						<tr valign="top">
+							<td class="number"><span class="number shaded-bg-color medium-border-color"><%=c++%></span></td>
+							<td>
+								<div class="second-font field-title">Add to subscriber's list</div>
+								Mailing list administrators usually can add email addresses to the subscribers' list directly.
+								If you can do this, add <b><%=subscriptionAddress%></b> to that list.
+							</td>
+						</tr>
+					</table>
+				</div>
+
+				<% if (mailingList.getListServer() == ListServer.google) { %>
+				<div class="field-box light-border-color">
+					<table style="margin-left:1.3em">
+						<tr valign="top">
+							<td class="number"><span class="number shaded-bg-color medium-border-color"><%=c++%></span></td>
+							<td>
+								<div class="second-font field-title">Use the "Invite" feature of GoogleGroups </div>
+								You can go to the <a href="http://groups.google.com/" rel="nofollow">Google Groups website</a> and
+								invite the following address to be a member:
+								<b><%=subscriptionAddress%></b>.<br>
+								The confirmation email will be forwarded to you so that you can complete this step.
+							</td>
+						</tr>
+					</table>
+				</div>
+				<div class="field-box light-border-color">
+					<table style="margin-left:1.3em">
+						<tr valign="top">
+							<td class="number"><span class="number shaded-bg-color medium-border-color"><%=c++%></span></td>
+							<td>
+								<div class="second-font field-title">Create an account in GoogleGroups for the subscription email</div>
+								You can go to the <a href="http://groups.google.com/" rel="nofollow">Google Groups website</a> and
+								create an account for this email:
+								<b><%=subscriptionAddress%></b>.<br>
+								(Google will probably send a confirmation number to your cell phone so that you can finish the registration)<br>
+								When you have finished the account set up, you can go to the mailing list page and click on "Join this group".
+							</td>
+						</tr>
+					</table>
+				</div>
+				<% } else { %>
+					<div class="field-box light-border-color">
+						<table style="margin-left:1.3em">
+							<tr valign="top">
+								<td class="number"><span class="number shaded-bg-color medium-border-color"><%=c++%></span></td>
+								<td>
+									<div class="second-font field-title">Go to mailing list website</div>
+									You can go to the mailing list homepage (<a href="<%=mailingList.getUrl()%>"><%=mailingList.getUrl()%></a>)
+									and subscribe <b><%=subscriptionAddress%></b> to the list.
+									The confirmation email will be forwarded to your email address as soon as it is received by Nabble.com.
+									You will have to follow the instructions in that email to confirm this request.
+								</td>
+							</tr>
+						</table>
+					</div>
+				<% } %>
+				<% emailForm(node, true, "Subscribe by email", "Send Subscription Request", c, request, out); %>
+
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+
+	static String sendRequest(Node node, HttpServletRequest request, PrintWriter out)
+		throws ServletException, IOException
+	{
+		String emailFrom = request.getParameter("emailFrom");
+		String emailToUser = request.getParameter("emailToUser");
+		String emailToDomain = request.getParameter("emailToDomain");
+		String emailSubject = request.getParameter("emailSubject");
+		String emailBody = request.getParameter("emailBody");
+
+		if (emailFrom == null || emailToUser == null || emailToDomain == null) {
+			return "Invalid message parameters";
+		} else {
+			MailAddress toAddress = new MailAddress(emailToUser + emailToDomain);
+			try {
+				Mail mail = MailHome.newMail();
+				mail.setFrom(new MailAddress(emailFrom, "Nabble Forums"));
+				mail.setTo(toAddress);
+				mail.setSubject(emailSubject);
+				mail.setContent(new PlainTextContent(emailBody));
+				ModelHome.send(mail);
+			} catch(MailException e) {
+				logger.warn("",e);
+				return e.getMessage();
+			}
+			%>
+			<div class="info-message" style="padding:1em">
+				<b>Email Sent</b><br/>
+				The request has been successfully sent. The confirmation email will be forwarded to your email
+				address as soon as it is received by Nabble. You will have to follow the instructions in
+				that email to finish the process.<br/>
+				<a href="<%=Jtp.path(node)%>">Return to forum</a>
+			</div>
+			<%
+		}
+		return null;
+	}	
+
+	static void emailForm(Node node, boolean isSubscription, String title, String button, char c, HttpServletRequest request, PrintWriter out) {
+		MailingList mailingList = node.getMailingList();
+		if (!mailingList.getListServer().canSubscribe())
+			return;
+		%>
+		<div class="field-box light-border-color">
+			<table style="margin-left:1.3em">
+				<tr valign="top">
+					<td class="number"><span class="number shaded-bg-color medium-border-color"><%=c%></span></td>
+					<td>
+						<div class="second-font field-title"><%=title%></div>
+						You can send the request by email. Default content of the email is
+						provided according to the mailing list server type (<b><%=mailingList.getListServer().getViewName()%></b>)
+						that you have specified during the mailing list setup. You can modify it if necessary.
+					</td>
+				</tr>
+			</table>
+		</div>
+		<%
+		String emailFrom = request.getParameter("emailFrom");
+		String emailToUser = request.getParameter("emailToUser");
+		String emailToDomain = request.getParameter("emailToDomain");
+		String emailSubject = request.getParameter("emailSubject");
+		String emailBody = request.getParameter("emailBody");
+		Mail mail = isSubscription? mailingList.subscribeMail(): mailingList.unsubscribeMail();
+
+		if (emailFrom == null) {
+			emailFrom = mail.getFrom().getAddrSpec();
+		}
+
+		if (emailToUser == null || emailToDomain == null) {
+			String emailTo = mail.getTo()[0].getAddrSpec();
+			int index = emailTo.indexOf("@");
+			if(index < 0) {
+				throw new RuntimeException("Invalid emailTo: " + emailTo);
+			}
+			emailToUser = emailTo.substring(0, index);
+			emailToDomain = emailTo.substring(index);
+		}
+
+		if (emailSubject == null) {
+			emailSubject = Jtp.hideNull(mail.getSubject());
+		}
+
+		if (emailBody == null) {
+			emailBody = ((TextContent) mail.getContent()).getText();
+		}
+		%>
+		<form method="post" action="/mailing_list/<%=isSubscription? "SubscribeToMailingList.jtp":"UnsubscribeFromMailingList.jtp"%>" accept-charset="UTF-8">
+			<input type="hidden" name="node" value="<%=node.getId()%>">
+			<input type="hidden" name="action" value="SendEmail">
+			<input type="hidden" name="emailToDomain" value="<%=emailToDomain%>">
+			<input type="hidden" name="emailToUser" value="<%=emailToUser%>">
+			<input type="hidden" name="emailSubject" value="<%=emailSubject%>">
+			<input type="hidden" name="emailBody" value="<%=emailBody%>">
+			<input type="hidden" name="emailFrom" value="<%=emailFrom%>"/>
+			<table style="margin-left:2em">
+				<tr>
+					<td align=right>From:&nbsp;</td>
+					<td><input type="text" name="emailFrom2" value="<%=emailFrom%>" size="50" disabled/></td>
+				</tr>
+				<tr>
+					<td align=right>To:&nbsp;</td>
+					<td valign="middle"><input type="text" name="emailToUser2" value="<%=emailToUser%>" size="50" disabled/><%=emailToDomain%></td>
+				</tr>
+				<tr>
+					<td align=right>Subject:&nbsp;</td>
+					<td><input type="text" name="emailSubject2" value="<%=emailSubject%>" size="50" disabled/></td>
+				</tr>
+				<tr>
+					<td align=right valign=top>Message:&nbsp;</td>
+					<td><textarea cols="50" rows="5" name="emailBody2" wrap="SOFT" disabled><%=emailBody%></textarea></td>
+				</tr>
+				<tr>
+					<td><br></td>
+					<td>
+						<input type="submit" value="<%=button%>" />
+					</td>
+				</tr>
+			</table>
+		</form>
+		<%
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/Unsubscribe.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,40 @@
+
+package nabble.view.web.mailing_list;
+
+import fschmidt.util.mail.Mail;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+public final class Unsubscribe extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(Unsubscribe.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to be able to unsubscribe from a list.",request,response);
+			return;
+		}
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+		MailingList mailingList = forum.getAssociatedMailingList();
+		Mail unsubscribeMail = mailingList.unsubscribeMail(user);
+		ModelHome.send(unsubscribeMail);
+		logger.info("unsubscription request from "+user.getEmail()+" to "+mailingList.getListAddress());
+		response.sendRedirect("Unsubscribe2.jtp?forum="+forum.getId());
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/Unsubscribe.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,40 @@
+<%
+package nabble.view.web.mailing_list;
+
+import fschmidt.util.mail.Mail;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+public final class Unsubscribe extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(Unsubscribe.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to be able to unsubscribe from a list.",request,response);
+			return;
+		}
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+		MailingList mailingList = forum.getAssociatedMailingList();
+		Mail unsubscribeMail = mailingList.unsubscribeMail(user);
+		ModelHome.send(unsubscribeMail);
+		logger.info("unsubscription request from "+user.getEmail()+" to "+mailingList.getListAddress());
+		response.sendRedirect("Unsubscribe2.jtp?forum="+forum.getId());
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/Unsubscribe2.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,62 @@
+
+package nabble.view.web.mailing_list;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import nabble.model.Node;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import fschmidt.util.mail.MailAddress;
+
+
+public final class Unsubscribe2 extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to be able to subscribe to a list.",request,response);
+			return;
+		}
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+		MailingList mailingList = forum.getAssociatedMailingList();
+		ListServer listServer = mailingList.getListServer();
+		MailAddress emailAddress = new MailAddress(user.getEmail(), user.getName());
+		
+		out.print( "\r\n<html>\r\n<head>\r\n" );
+
+		Shared.title(request,response,"Subscribe to mailing list");
+		
+		out.print( "\r\n</head>\r\n<body>\r\n" );
+ Shared.minHeader(request, response, forum); 
+		out.print( "\r\n<h1>Unsubscription Request Was Sent</h1>\r\n<p>\r\n	An unsubscription request was sent on your behalf to the mailing list: <strong>" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "</strong>.\r\n</p>\r\n<p>\r\n	You will soon receive a confirmation request by email at <strong>" );
+		out.print( (user.getEmail()) );
+		out.print( "</strong>. Please follow the\r\ninstructions in this request to unsubscribe from the mailing list.\r\n</p>\r\n<p>\r\n	<strong>Remember:</strong> You are not unsubscribed from the <strong>" );
+		out.print( (mailingList.getListAddress()) );
+		out.print( "</strong> list until you have followed the instructions in the email.\r\n</p>\r\n\r\n<br /><br />\r\n<p>&#171; <a href=\"" );
+		out.print( (Jtp.path(forum)) );
+		out.print( "\">Return to forum</a></p>\r\n" );
+
+		Shared.footer(request,response);
+		Shared.analytics(request,response);
+		
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/Unsubscribe2.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,70 @@
+<%
+package nabble.view.web.mailing_list;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import nabble.model.Node;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import fschmidt.util.mail.MailAddress;
+
+
+public final class Unsubscribe2 extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to be able to subscribe to a list.",request,response);
+			return;
+		}
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+		MailingList mailingList = forum.getAssociatedMailingList();
+		ListServer listServer = mailingList.getListServer();
+		MailAddress emailAddress = new MailAddress(user.getEmail(), user.getName());
+		%>
+		<html>
+		<head>
+		<%
+		Shared.title(request,response,"Subscribe to mailing list");
+		%>
+		</head>
+		<body>
+		<% Shared.minHeader(request, response, forum); %>
+		<h1>Unsubscription Request Was Sent</h1>
+		<p>
+			An unsubscription request was sent on your behalf to the mailing list: <strong><%=mailingList.getListAddress()%></strong>.
+		</p>
+		<p>
+			You will soon receive a confirmation request by email at <strong><%=user.getEmail()%></strong>. Please follow the
+		instructions in this request to unsubscribe from the mailing list.
+		</p>
+		<p>
+			<strong>Remember:</strong> You are not unsubscribed from the <strong><%=mailingList.getListAddress()%></strong> list until you have followed the instructions in the email.
+		</p>
+		
+		<br /><br />
+		<p>&#171; <a href="<%=Jtp.path(forum)%>">Return to forum</a></p>
+		<%
+		Shared.footer(request,response);
+		Shared.analytics(request,response);
+		%>
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/UnsubscribeFromMailingList.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,95 @@
+
+package nabble.view.web.mailing_list;
+
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class UnsubscribeFromMailingList extends HttpServlet implements AuthorizingServlet {
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"node")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response);
+	}
+
+    protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+
+		if (user == null) {
+			Jtp.login("You must login to unsubscribe from a mailing list.", request, response);
+			return;
+		}
+
+		Node forum = Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request, "node"));
+		boolean allowed = Jtp.canBeEditedBy(forum,user);
+		if (!allowed) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		String errorMsg = null;
+		boolean isSendEmail = "SendEmail".equals(request.getParameter("action"));
+
+		MailingList mailingList = forum.getMailingList();
+		String subscriptionAddress = mailingList.getSubscriberAddress().getAddrSpec();
+		PrintWriter out = response.getWriter();
+		char c = 'A';
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<META NAME=\"robots\" CONTENT=\"noindex,nofollow\">\r\n		" );
+ Shared.title(request,response,"How to Unsubscribe this Forum"); 
+		out.print( "\r\n		<style type=\"text/css\">\r\n			div.field-title {\r\n				margin-top: 0;\r\n			}\r\n			td.number {\r\n				width: 3em;\r\n				padding-bottom: .2em;\r\n			}\r\n			span.number {\r\n				font-size: 200%;\r\n				padding: 0 .3em .03em;\r\n				border-width:1px;\r\n				border-style:solid;\r\n			}\r\n		</style>\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.minHeader(request, response, forum);
+		out.print( "\r\n		" );
+ Shared.editHeader(forum.getSubjectHtml(), "How to Unsubscribe this Forum", out); 
+		out.print( "\r\n		" );
+ Shared.errorMessage(request,response,errorMsg, "Please fix the error and try again." ); 
+		out.print( "\r\n		" );
+
+				if (isSendEmail && "POST".equals(request.getMethod())) {
+					errorMsg = SubscribeToMailingList.sendRequest(forum, request, out);
+					Shared.errorMessage(request, response, errorMsg, "Failed to send the request." );
+				}
+				
+		out.print( "\r\n\r\n<div class=\"field-box light-border-color\">\r\n	<div class=\"second-font field-title\">General Instructions</div>\r\n	<div style=\"margin-left:1.5em\">\r\n		If you want to unsubscribe this forum from a mailing list, you have to remove the following email address from the mailing list subscriber's list:\r\n		<div class=\"info-message\" style=\"margin:.2em;padding:.2em;font-weight:bold\">" );
+		out.print( (subscriptionAddress) );
+		out.print( "</div>\r\n		This can be done in several ways. Below you can find the most common ones.\r\n	</div>\r\n</div>\r\n\r\n<div class=\"second-font field-title\" style=\"margin-top:1em\">Unsubscription Options</div>\r\n<div class=\"weak-color\">Choose the best option for this mailing list.</div>\r\n\r\n<div class=\"field-box light-border-color\">\r\n	<table style=\"margin-left:1.3em\">\r\n		<tr valign=\"top\">\r\n			<td class=\"number\"><span class=\"number shaded-bg-color medium-border-color\">" );
+		out.print( (c++) );
+		out.print( "</span></td>\r\n			<td>\r\n				<div class=\"second-font field-title\">Remove from subscriber's list</div>\r\n				Mailing list administrators usually can remove email addresses from the subscribers' list directly.\r\n				If you can do this, remove <b>" );
+		out.print( (subscriptionAddress) );
+		out.print( "</b> from that list.\r\n			</td>\r\n		</tr>\r\n	</table>\r\n</div>\r\n\r\n<div class=\"field-box light-border-color\">\r\n	<table style=\"margin-left:1.3em\">\r\n		<tr valign=\"top\">\r\n			<td class=\"number\"><span class=\"number shaded-bg-color medium-border-color\">" );
+		out.print( (c++) );
+		out.print( "</span></td>\r\n			<td>\r\n				<div class=\"second-font field-title\">Go to mailing list website</div>\r\n				You can go to the mailing list homepage (<a href=\"" );
+		out.print( (mailingList.getUrl()) );
+		out.print( "\">" );
+		out.print( (mailingList.getUrl()) );
+		out.print( "</a>)\r\n				and unsubscribe <b>" );
+		out.print( (subscriptionAddress) );
+		out.print( "</b> from the list.\r\n				The confirmation email will be forwarded to your email address as soon as it is received by Nabble.com.\r\n				You will have to follow the instructions in that email to confirm this request.\r\n			</td>\r\n		</tr>\r\n	</table>\r\n</div>\r\n" );
+ SubscribeToMailingList.emailForm(forum, false, "Unsubscribe by email", "Send Unsubscription Request", c, request, out); 
+		out.print( "\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/mailing_list/UnsubscribeFromMailingList.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,134 @@
+<%
+package nabble.view.web.mailing_list;
+
+import fschmidt.util.servlet.AuthorizingServlet;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class UnsubscribeFromMailingList extends HttpServlet implements AuthorizingServlet {
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		return Jtp.getReadAuthorizationKey( Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request,"node")) );
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		return Jtp.authorizeForRead(key,request,response);
+	}
+
+    protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request,response);
+
+		if (user == null) {
+			Jtp.login("You must login to unsubscribe from a mailing list.", request, response);
+			return;
+		}
+
+		Node forum = Jtp.getSiteNotNull(request).getNode(Jtp.getLong(request, "node"));
+		boolean allowed = Jtp.canBeEditedBy(forum,user);
+		if (!allowed) {
+			Jtp.login("Only administrators can proceed in this area.", request, response);
+			return;
+		}
+
+		String errorMsg = null;
+		boolean isSendEmail = "SendEmail".equals(request.getParameter("action"));
+
+		MailingList mailingList = forum.getMailingList();
+		String subscriptionAddress = mailingList.getSubscriberAddress().getAddrSpec();
+		PrintWriter out = response.getWriter();
+		char c = 'A';
+		%>
+		<html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow">
+				<% Shared.title(request,response,"How to Unsubscribe this Forum"); %>
+				<style type="text/css">
+					div.field-title {
+						margin-top: 0;
+					}
+					td.number {
+						width: 3em;
+						padding-bottom: .2em;
+					}
+					span.number {
+						font-size: 200%;
+						padding: 0 .3em .03em;
+						border-width:1px;
+						border-style:solid;
+					}
+				</style>
+			</head>
+			<body>
+				<% Shared.minHeader(request, response, forum);%>
+				<% Shared.editHeader(forum.getSubjectHtml(), "How to Unsubscribe this Forum", out); %>
+				<% Shared.errorMessage(request,response,errorMsg, "Please fix the error and try again." ); %>
+				<%
+				if (isSendEmail && "POST".equals(request.getMethod())) {
+					errorMsg = SubscribeToMailingList.sendRequest(forum, request, out);
+					Shared.errorMessage(request, response, errorMsg, "Failed to send the request." );
+				}
+				%>
+
+				<div class="field-box light-border-color">
+					<div class="second-font field-title">General Instructions</div>
+					<div style="margin-left:1.5em">
+						If you want to unsubscribe this forum from a mailing list, you have to remove the following email address from the mailing list subscriber's list:
+						<div class="info-message" style="margin:.2em;padding:.2em;font-weight:bold"><%=subscriptionAddress%></div>
+						This can be done in several ways. Below you can find the most common ones.
+					</div>
+				</div>
+
+				<div class="second-font field-title" style="margin-top:1em">Unsubscription Options</div>
+				<div class="weak-color">Choose the best option for this mailing list.</div>
+
+				<div class="field-box light-border-color">
+					<table style="margin-left:1.3em">
+						<tr valign="top">
+							<td class="number"><span class="number shaded-bg-color medium-border-color"><%=c++%></span></td>
+							<td>
+								<div class="second-font field-title">Remove from subscriber's list</div>
+								Mailing list administrators usually can remove email addresses from the subscribers' list directly.
+								If you can do this, remove <b><%=subscriptionAddress%></b> from that list.
+							</td>
+						</tr>
+					</table>
+				</div>
+
+				<div class="field-box light-border-color">
+					<table style="margin-left:1.3em">
+						<tr valign="top">
+							<td class="number"><span class="number shaded-bg-color medium-border-color"><%=c++%></span></td>
+							<td>
+								<div class="second-font field-title">Go to mailing list website</div>
+								You can go to the mailing list homepage (<a href="<%=mailingList.getUrl()%>"><%=mailingList.getUrl()%></a>)
+								and unsubscribe <b><%=subscriptionAddress%></b> from the list.
+								The confirmation email will be forwarded to your email address as soon as it is received by Nabble.com.
+								You will have to follow the instructions in that email to confirm this request.
+							</td>
+						</tr>
+					</table>
+				</div>
+				<% SubscribeToMailingList.emailForm(forum, false, "Unsubscribe by email", "Send Unsubscription Request", c, request, out); %>
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/more/Forum.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,71 @@
+
+package nabble.view.web.more;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.UrlMappable;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+
+public final class Forum extends HttpServlet implements UrlMappable {
+
+	private static final Pattern urlPtn = Pattern.compile("/why-nabble\\.html$");
+
+	public static String path() {
+		return "/why-nabble.html";
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		return Collections.emptyMap();
+	}
+
+	public Pattern getUrlPattern() {
+		return urlPtn;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		
+		out.print( "\r\n<html>\r\n<head>\r\n" );
+
+		Shared.title(request,response,"Start a forum with us");
+		
+		out.print( "\r\n</head>\r\n<body>\r\n" );
+
+		Shared.minHeader(request,response);
+		
+		out.print( "\r\n\r\n<h1>Why Start Your Forum at Nabble?</h1>\r\n\r\n<style>\r\n	table.category {\r\n		margin-bottom: .5em;\r\n		width: 48em;\r\n	}\r\n	td.number { width: 3em; }\r\n	span.number {\r\n		font-size: 200%;\r\n		padding: 0 .3em .03em;\r\n		border-width:1px;\r\n		border-style:solid;			\r\n	}\r\n	span.title {\r\n		display:block;\r\n		font-size: 150%;\r\n		width: 100%;\r\n		margin-bottom: .5em;\r\n		border-bottom-width:1px;\r\n		border-bottom-style:solid;\r\n	}\r\n	a.forum-link {\r\n		font-size: 130%;\r\n	}\r\n	a.more {\r\n		font-size:80%;\r\n	}\r\n</style>\r\n\r\n<table class=\"category\">\r\n	<tr valign=\"top\">\r\n		<td class=\"number\">\r\n			<span class=\"number shaded-bg-color medium-border-color\">1</span>\r\n		</td>\r\n		<td>\r\n			<span class=\"title medium-border-color\">Free</span>\r\n			No fees whatsoever.<br>\r\n			No forced banner ads.<br>\r\n			No limit on how big or how busy your forum is.<br>\r\n			Free upload of pictures and files.<br>\r\n			Free XML feed.<br>\r\n			Free full export if you want to leave.<br>\r\n		</td>\r\n	</tr>\r\n</table>\r\n\r\n<table class=\"category\">\r\n	<tr valign=\"top\">\r\n		<td class=\"number\">\r\n			<span class=\"number shaded-bg-color medium-border-color\">2</span>\r\n		</td>\r\n		<td>\r\n			<span class=\"title medium-border-color\">Simple</span>\r\n			Nothing to install, instant setup.<br>\r\n			Clean minimalist UI.<br>\r\n		</td>\r\n	</tr>\r\n</table>\r\n\r\n<table class=\"category\">\r\n	<tr valign=\"top\">\r\n		<td class=\"number\">\r\n			<span class=\"number shaded-bg-color medium-border-color\">3</span>\r\n		</td>\r\n		<td>\r\n			<span class=\"title medium-border-color\">Embeddable</span>\r\n			Embed your forum in any website, just like Youtube videos.<br>\r\n			Change the appearance of your forum to match the look-and-feel of your pages.<br>\r\n		</td>\r\n	</tr>\r\n</table>\r\n\r\n<table class=\"category\">\r\n	<tr valign=\"top\">\r\n		<td class=\"number\">\r\n			<span class=\"number shaded-bg-color medium-border-color\">4</span>\r\n		</td>\r\n		<td>\r\n			<span class=\"title medium-border-color\">Fast</span>\r\n			Powerful search (<a class=\"more\" href=\"" );
+		out.print( (Help.search.url(request)) );
+		out.print( "\">more</a>).<br>\r\n			Fast loading of pages.<br>\r\n			Fast support.<br>\r\n		</td>\r\n	</tr>\r\n</table>\r\n\r\n<table class=\"category\">\r\n	<tr valign=\"top\">\r\n		<td class=\"number\">\r\n			<span class=\"number shaded-bg-color medium-border-color\">5</span>\r\n		</td>\r\n		<td>\r\n			<span class=\"title medium-border-color\">Advanced</span>\r\n			SEO (<a class=\"more\" href=\"" );
+		out.print( (Help.seo.url(request)) );
+		out.print( "\">more</a>).<br>\r\n			Mirror your group (<a class=\"more\" href=\"" );
+		out.print( (Help.mirror.url(request)) );
+		out.print( "\">more</a>).\r\n		</td>\r\n	</tr>\r\n</table>\r\n<br/>\r\n<a class=\"forum-link\" href=\"" );
+		out.print( (ForumStart.path("forum")) );
+		out.print( "\">Start a free forum now</a> &raquo;\r\n\r\n" );
+
+		Shared.footer(request,response);
+		
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/more/Forum.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,167 @@
+<%
+package nabble.view.web.more;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.UrlMappable;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+
+public final class Forum extends HttpServlet implements UrlMappable {
+
+	private static final Pattern urlPtn = Pattern.compile("/why-nabble\\.html$");
+
+	public static String path() {
+		return "/why-nabble.html";
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		return Collections.emptyMap();
+	}
+
+	public Pattern getUrlPattern() {
+		return urlPtn;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		%>
+		<html>
+		<head>
+		<%
+		Shared.title(request,response,"Start a forum with us");
+		%>
+		</head>
+		<body>
+		<%
+		Shared.minHeader(request,response);
+		%>
+
+		<h1>Why Start Your Forum at Nabble?</h1>
+
+		<style>
+			table.category {
+				margin-bottom: .5em;
+				width: 48em;
+			}
+			td.number { width: 3em; }
+			span.number {
+				font-size: 200%;
+				padding: 0 .3em .03em;
+				border-width:1px;
+				border-style:solid;			
+			}
+			span.title {
+				display:block;
+				font-size: 150%;
+				width: 100%;
+				margin-bottom: .5em;
+				border-bottom-width:1px;
+				border-bottom-style:solid;
+			}
+			a.forum-link {
+				font-size: 130%;
+			}
+			a.more {
+				font-size:80%;
+			}
+		</style>
+		
+		<table class="category">
+			<tr valign="top">
+				<td class="number">
+					<span class="number shaded-bg-color medium-border-color">1</span>
+				</td>
+				<td>
+					<span class="title medium-border-color">Free</span>
+					No fees whatsoever.<br>
+					No forced banner ads.<br>
+					No limit on how big or how busy your forum is.<br>
+					Free upload of pictures and files.<br>
+					Free XML feed.<br>
+					Free full export if you want to leave.<br>
+				</td>
+			</tr>
+		</table>
+
+		<table class="category">
+			<tr valign="top">
+				<td class="number">
+					<span class="number shaded-bg-color medium-border-color">2</span>
+				</td>
+				<td>
+					<span class="title medium-border-color">Simple</span>
+					Nothing to install, instant setup.<br>
+					Clean minimalist UI.<br>
+				</td>
+			</tr>
+		</table>
+
+		<table class="category">
+			<tr valign="top">
+				<td class="number">
+					<span class="number shaded-bg-color medium-border-color">3</span>
+				</td>
+				<td>
+					<span class="title medium-border-color">Embeddable</span>
+					Embed your forum in any website, just like Youtube videos.<br>
+					Change the appearance of your forum to match the look-and-feel of your pages.<br>
+				</td>
+			</tr>
+		</table>
+
+		<table class="category">
+			<tr valign="top">
+				<td class="number">
+					<span class="number shaded-bg-color medium-border-color">4</span>
+				</td>
+				<td>
+					<span class="title medium-border-color">Fast</span>
+					Powerful search (<a class="more" href="<%=Help.search.url(request)%>">more</a>).<br>
+					Fast loading of pages.<br>
+					Fast support.<br>
+				</td>
+			</tr>
+		</table>
+
+		<table class="category">
+			<tr valign="top">
+				<td class="number">
+					<span class="number shaded-bg-color medium-border-color">5</span>
+				</td>
+				<td>
+					<span class="title medium-border-color">Advanced</span>
+					SEO (<a class="more" href="<%=Help.seo.url(request)%>">more</a>).<br>
+					Mirror your group (<a class="more" href="<%=Help.mirror.url(request)%>">more</a>).
+				</td>
+			</tr>
+		</table>
+		<br/>
+		<a class="forum-link" href="<%=ForumStart.path("forum")%>">Start a free forum now</a> &raquo;
+
+		<%
+		Shared.footer(request,response);
+		%>
+		<% Shared.analytics(request,response); %>
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/more/ForumStart.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,297 @@
+
+package nabble.view.web.more;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.servlet.CanonicalUrl;
+import nabble.model.Db;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.NewSiteMail;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import nabble.view.lib.UrlMappable;
+import nabble.view.lib.Recaptcha;
+import nabble.view.web.app.Languages;
+import nabble.view.web.template.NabbleNamespace;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class ForumStart extends HttpServlet implements UrlMappable, CanonicalUrl {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/free-(forum|gallery|newspaper|blog|mailing-list)\\.html$");
+
+	public static String url(String what) {
+		return Jtp.defaultContextUrl() + path(what);
+	}
+
+	public static String path(String what) {
+		return "/free-" + what + ".html";
+	}
+
+	public String getCanonicalUrl(HttpServletRequest request) {
+		return url( request.getParameter("what") );
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		String what = m.group(1);
+		params.put("what",new String[]{what});
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		build(request, response, Collections.<String,String>emptyMap(), Collections.<String,String>emptyMap());
+	}
+
+	private static void build(HttpServletRequest request,HttpServletResponse response, Map<String,String> values, Map<String,String> errors)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String what = request.getParameter("what");
+		if (what == null)
+			what = "Forum";
+		else if ("mailing-list".equals(what))
+			what = "Mailing List";
+		else
+			what = Jtp.capitalize(what);
+
+		String imgName = what.toLowerCase();
+		if ("Mailing List".equals(what))
+			imgName = "mailing-list";
+
+		
+		out.print( "\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n	<head>\r\n		" );
+ Shared.head(request,response); 
+		out.print( "\r\n		<title>Nabble - Free " );
+		out.print( (what) );
+		out.print( " Setup</title>\r\n		<meta name=\"robots\" content=\"noindex\"/>\r\n		<META NAME=\"description\" CONTENT=\"Setting up a free " );
+		out.print( (what.toLowerCase()) );
+		out.print( " on Nabble is quick and easy. Fill in one simple form and you are done.\">\r\n		<META NAME=\"keywords\" CONTENT=\"free " );
+		out.print( (what.toLowerCase()) );
+		out.print( ", hosted " );
+		out.print( (what.toLowerCase()) );
+		out.print( ", simple, embeddable " );
+		out.print( (what.toLowerCase()) );
+		out.print( ", customizable\">\r\n		" );
+ /*Shared.canonical(request, response);*/ 
+		out.print( "\r\n		<style type=\"text/css\">\r\n			div.center-content {\r\n				margin:0px auto;\r\n				margin-bottom: 3em;\r\n			}\r\n			td.column1 {\r\n				text-align:right;\r\n				width:7em;\r\n				white-space:nowrap;\r\n			}\r\n			input[type=text],input[type=password] {\r\n				padding:.4em 0;\r\n			}\r\n			div.field-title {\r\n				margin-top:.3em;\r\n			}\r\n			.important { font-weight:bold }\r\n			label { vertical-align:-15%; }\r\n		</style>\r\n		<script type=\"text/javascript\">\r\n			$(document).ready(function() {\r\n				$('#username').focus();\r\n			});\r\n\r\n			function singleFormSubmit(f) {\r\n				if (f.done)\r\n					return false;\r\n				f.done = true;\r\n				$('#submit-btn').hide();\r\n				var $div = $('#wait-message');\r\n				function loading1() { $div.fadeTo(300,0.3,loading2);  };\r\n				function loading2() { $div.fadeTo(300,1,loading1);  };\r\n				loading1();\r\n				return true;\r\n			};\r\n		</script>\r\n		" );
+		out.print( ( Recaptcha.JS ) );
+		out.print( "\r\n	</head>\r\n	<body style=\"text-align:center\">\r\n		" );
+ Shared.minHeaderGlobal(request,response); 
+		out.print( "\r\n\r\n		<div class=\"center-content\">\r\n			<img src=\"/images/logo_nabble_home.png\" border=\"0\" alt=\"Nabble - free forums for everyone\"/><br />\r\n			<h1 style=\"color:#979797\">Start Your " );
+		out.print( (what) );
+		out.print( "</h1>\r\n\r\n			" );
+ if (errors.size() > 0) { 
+		out.print( "\r\n				<div class=\"error-message important\" style=\"margin:1em;padding:.5em 0\">\r\n					" );
+ String generic = errors.get("generic"); 
+		out.print( "\r\n					" );
+		out.print( (generic != null? generic : errors.size() > 0? "Please check the errors below" : "") );
+		out.print( "\r\n				</div>\r\n			" );
+ } 
+		out.print( "\r\n\r\n			<form action=\"/more/ForumStart$Save.jtp\" method=\"post\" accept-charset=\"UTF-8\" onsubmit=\"return singleFormSubmit(this)\">\r\n				<input type=\"hidden\" name=\"type\" value=\"" );
+		out.print( (what.toLowerCase().replace(" ","")) );
+		out.print( "\" />\r\n				<input type=\"hidden\" name=\"what\" value=\"" );
+		out.print( (what) );
+		out.print( "\" />\r\n\r\n				<div style=\"text-align:left;width:50em;margin:0 auto\">\r\n					<div style=\"border-bottom:2px solid #eeeeee;padding:1em\">\r\n						<div class=\"weak-color\" style=\"width:12em;text-align:center;float:left\">\r\n							<div style=\"font-weight:bold\">Account</div>\r\n							<img src=\"/images/account.png\" width=\"84\" height=\"45\"/>\r\n							<div style=\"margin-top:1em;font-size:80%\">\r\n								You will receive an email with a link to activate your account\r\n							</div>\r\n						</div>\r\n						<table>\r\n							<tr>\r\n								<td class=\"column1\"><div class=\"second-font field-title\">User Name</div></td>\r\n								<td><input type=\"text\" id=\"username\" size=\"35\" maxlength=\"30\" name=\"username\" value=\"" );
+		out.print( (Jtp.hideNull(values.get("username"))) );
+		out.print( "\" /></td>\r\n								<td class=\"important\">" );
+		out.print( (errors.containsKey("username")? errors.get("username"):"") );
+		out.print( "</td>\r\n							</tr>\r\n							<tr>\r\n								<td class=\"column1\"><div class=\"second-font field-title\">Email</div></td>\r\n								<td><input type=\"text\" size=\"35\" maxlength=\"60\" name=\"email\" value=\"" );
+		out.print( (Jtp.hideNull(values.get("email"))) );
+		out.print( "\"/></td>\r\n								<td class=\"important\">" );
+		out.print( (errors.containsKey("email")? errors.get("email"):"") );
+		out.print( "</td>\r\n							</tr>\r\n							<tr>\r\n								<td class=\"column1\"><div class=\"second-font field-title\">Password</div></td>\r\n								<td><input type=\"password\" size=\"35\" maxlength=\"15\" name=\"password\" value=\"" );
+		out.print( (Jtp.hideNull(values.get("password"))) );
+		out.print( "\"/></td>\r\n								<td class=\"important\">" );
+		out.print( (errors.containsKey("password")? errors.get("password"):"") );
+		out.print( "</td>\r\n							</tr>\r\n							<tr>\r\n								<td class=\"column1\"><input type=\"checkbox\" id=\"terms\" name=\"terms\" value=\"y\" " );
+		out.print( ("y".equals(values.get("terms"))?"checked":"") );
+		out.print( " /></td>\r\n								<td colspan=2><label for=\"terms\">I have read and I agree to Nabble's <a href=\"" );
+		out.print( (Jtp.termsUrl(true)) );
+		out.print( "\">Terms of Use</a>.</label></td>\r\n							</tr>\r\n						</table>\r\n					</div>\r\n\r\n					<div style=\"padding:1em;overflow:hidden\">\r\n						<div class=\"weak-color\" style=\"width:12em;text-align:center;float:left;height:15em\">\r\n							<div style=\"font-weight:bold\">" );
+		out.print( (what) );
+		out.print( "</div>\r\n							<img src=\"/images/homepage/" );
+		out.print( (imgName) );
+		out.print( ".png\" alt=\"Free " );
+		out.print( (what.toLowerCase()) );
+		out.print( "\">\r\n						</div>\r\n						<table>\r\n							<tr>\r\n								<td class=\"column1\"><div class=\"second-font field-title\">Language</div></td>\r\n								<td>\r\n									<select name=\"lang\">\r\n										" );
+ for( Map.Entry<String,String> entry : Languages.languages.entrySet() ) { 
+		out.print( "\r\n										" );
+ String lang = request.getParameter("lang"); 
+		out.print( "\r\n										" );
+ boolean isEnglish = entry.getKey().equals("none"); 
+		out.print( "\r\n										" );
+ boolean isSelected = (lang == null && isEnglish) || entry.getKey().equals(lang); 
+		out.print( "\r\n										<option value=\"" );
+		out.print( (entry.getKey()) );
+		out.print( "\" " );
+		out.print( (isSelected?"selected=\"true\"":"") );
+		out.print( ">" );
+		out.print( (entry.getValue()) );
+		out.print( "</option>\r\n										" );
+ } 
+		out.print( "\r\n									</select>\r\n								</td>\r\n								<td></td>\r\n							</tr>\r\n							<tr>\r\n								<td colspan=\"3\" style=\"height:.6em\"></td>\r\n							</tr>\r\n							<tr>\r\n								<td class=\"column1\"><div class=\"second-font field-title\">" );
+		out.print( (what) );
+		out.print( " Name</div></td>\r\n								<td><input type=\"text\" name=\"subject\" size=\"30\" maxlength=\"80\" value=\"" );
+		out.print( (Jtp.hideNull(values.get("subject"))) );
+		out.print( "\"/></td>\r\n								<td class=\"important\">" );
+		out.print( (errors.containsKey("subject")? errors.get("subject"):"") );
+		out.print( "</td>\r\n							</tr>\r\n							<tr>\r\n								<td colspan=3 style=\"padding:.4em 0 0 .6em\">\r\n									<div class=\"second-font field-title\">Description &nbsp;<span class=\"weak-color\" style=\"font-weight:normal;\">(optional)</span></div>\r\n									<textarea rows=\"7\" name=\"message\" wrap=\"SOFT\" style=\"width:28em\">" );
+		out.print( (Jtp.hideNull(values.get("message"))) );
+		out.print( "</textarea>\r\n									<br>" );
+		out.print( ( Recaptcha.DIV ) );
+		out.print( "\r\n								</td>\r\n							</tr>\r\n						</table>\r\n					</div>\r\n					<div class=\"weak-color\" style=\"margin-top:.5em;text-align:center;\">\r\n						<input id=\"submit-btn\" type=\"submit\" name=\"save\" value=\"Create " );
+		out.print( (what) );
+		out.print( "\" style=\"padding:.5em .8em;font-size:110%;font-weight:bold\"/>\r\n						<div id=\"wait-message\" class=\"important invisible\" style=\"margin:.1em 0\">Creating " );
+		out.print( (what) );
+		out.print( "... Please wait</div>\r\n					</div>\r\n				</div>\r\n                    </form>\r\n		</div>\r\n\r\n		" );
+ Shared.footer(request,response); 
+		out.print( "\r\n		" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+
+	public static class Save extends HttpServlet {
+
+		private static String get(String name, HttpServletRequest request) {
+			String s = request.getParameter(name);
+			return s == null? null : s.trim();
+		}
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			String username = get("username", request);
+			String email = get("email", request);
+			String password = get("password", request);
+			boolean agreed = "y".equals(get("terms", request));
+			String subject = get("subject", request);
+			String message = get("message", request);
+
+			Map<String,String> errors = new HashMap<String,String>();
+			if (username == null || username.trim().length() == 0)
+				errors.put("username", "required");
+			if (email == null || email.length() == 0)
+				errors.put("email", "required");
+			else if (!new MailAddress(email).isValid())
+				errors.put("email", "invalid email");
+			if (password == null || password.length() < 4)
+				errors.put("password", "too short");
+			if (!agreed)
+				errors.put("generic", "You must agree to the Terms and Conditions");
+			if (subject == null || subject.length() == 0)
+				errors.put("subject", "required");
+
+			String type = get("type", request);
+			type = "newspaper".equals(type)? "news" : type;
+
+			String extraMessage = "";
+			if ("mailinglist".equals(type)) {
+				type = "forum";
+				StringBuilder m = new StringBuilder();
+				m.append("\n\nMailing List Options\n");
+				m.append("Click \"Options > Subscribe via email\" to subscribe to this mailing list;\n");
+				m.append("Click \"Options > Post by email...\" to get the email address of this mailing list;\n");
+				m.append("You can post messages via email or through the forum interface below;\n");
+				m.append("All web posts and emails are archived here.");
+				extraMessage = m.toString();
+			}
+
+			if (errors.isEmpty()) {
+				DbDatabase db = Db.dbGlobal();
+				db.beginTransaction();
+				try {
+					Recaptcha.check(request);
+					Site site = ModelHome.newSite(type,subject, message + extraMessage, Message.Format.TEXT, email, username);
+					Permissions.addToGroup( (User)site.getRootNode().getOwner(), Permissions.ADMINISTRATORS_GROUP );
+					String key = site.newRegistration(email,password,username,"/");
+					db.commitTransaction();
+
+					// Track spam activities by IP
+					ModelHome.setRemoteAddr(site, Jtp.getClientIpAddr(request));
+
+					site = site.getGoodCopy();
+
+					String lang = request.getParameter("lang");
+					if (!"none".equals(lang)) {
+						site.setModuleEnabled(lang, true);
+						site = site.getGoodCopy();
+					}
+
+					sendRegisterMail(site, email, key);
+					NewSiteMail.send(site, request, response);
+					response.sendRedirect(site.getBaseUrl()+"/more/ForumStart$Redirection.jtp");
+					return;
+				} catch(ModelException e) {
+					errors.put("generic", e.getMessage());
+				} finally {
+					db.endTransaction();
+				}
+			}
+
+			Map<String,String> values = new HashMap<String,String>();
+			values.put("username", username);
+			values.put("email", email);
+			values.put("password", password);
+			values.put("terms", agreed?"y":"");
+			values.put("subject", subject);
+			values.put("message", message);
+			build(request, response, values, errors);
+		}
+	}
+
+	/** Sets cookies in the site domain  */
+	public static class Redirection extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			Site site = Jtp.getSite(request);
+			Shared.javascriptRedirect(request, response, Jtp.url(site.getRootNode()), "Nabble.setVar('appnotice','true');");
+		}
+	}
+
+	public static void sendRegisterMail(Site site, String email, String key) {
+		Map<String,Object> args = new HashMap<String,Object>();
+		args.put("email",email);
+		args.put("next_url","/");
+		args.put("key",key);
+		Template template = site.getTemplate( "send_registration_email",
+			BasicNamespace.class, NabbleNamespace.class
+		);
+		template.run( TemplatePrintWriter.NULL, args,
+			new BasicNamespace(template), new NabbleNamespace(site)
+		);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/more/ForumStart.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,354 @@
+<%
+package nabble.view.web.more;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.servlet.CanonicalUrl;
+import nabble.model.Db;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.NewSiteMail;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import nabble.view.lib.UrlMappable;
+import nabble.view.lib.Recaptcha;
+import nabble.view.web.app.Languages;
+import nabble.view.web.template.NabbleNamespace;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class ForumStart extends HttpServlet implements UrlMappable, CanonicalUrl {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/free-(forum|gallery|newspaper|blog|mailing-list)\\.html$");
+
+	public static String url(String what) {
+		return Jtp.defaultContextUrl() + path(what);
+	}
+
+	public static String path(String what) {
+		return "/free-" + what + ".html";
+	}
+
+	public String getCanonicalUrl(HttpServletRequest request) {
+		return url( request.getParameter("what") );
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		String what = m.group(1);
+		params.put("what",new String[]{what});
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		build(request, response, Collections.<String,String>emptyMap(), Collections.<String,String>emptyMap());
+	}
+
+	private static void build(HttpServletRequest request,HttpServletResponse response, Map<String,String> values, Map<String,String> errors)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String what = request.getParameter("what");
+		if (what == null)
+			what = "Forum";
+		else if ("mailing-list".equals(what))
+			what = "Mailing List";
+		else
+			what = Jtp.capitalize(what);
+
+		String imgName = what.toLowerCase();
+		if ("Mailing List".equals(what))
+			imgName = "mailing-list";
+
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<% Shared.head(request,response); %>
+				<title>Nabble - Free <%=what%> Setup</title>
+				<meta name="robots" content="noindex"/>
+				<META NAME="description" CONTENT="Setting up a free <%=what.toLowerCase()%> on Nabble is quick and easy. Fill in one simple form and you are done.">
+				<META NAME="keywords" CONTENT="free <%=what.toLowerCase()%>, hosted <%=what.toLowerCase()%>, simple, embeddable <%=what.toLowerCase()%>, customizable">
+				<% /*Shared.canonical(request, response);*/ %>
+				<style type="text/css">
+					div.center-content {
+						margin:0px auto;
+						margin-bottom: 3em;
+					}
+					td.column1 {
+						text-align:right;
+						width:7em;
+						white-space:nowrap;
+					}
+					input[type=text],input[type=password] {
+						padding:.4em 0;
+					}
+					div.field-title {
+						margin-top:.3em;
+					}
+					.important { font-weight:bold }
+					label { vertical-align:-15%; }
+				</style>
+				<script type="text/javascript">
+					$(document).ready(function() {
+						$('#username').focus();
+					});
+
+					function singleFormSubmit(f) {
+						if (f.done)
+							return false;
+						f.done = true;
+						$('#submit-btn').hide();
+						var $div = $('#wait-message');
+						function loading1() { $div.fadeTo(300,0.3,loading2);  };
+						function loading2() { $div.fadeTo(300,1,loading1);  };
+						loading1();
+						return true;
+					};
+				</script>
+				<%= Recaptcha.JS %>
+			</head>
+			<body style="text-align:center">
+				<% Shared.minHeaderGlobal(request,response); %>
+
+				<div class="center-content">
+					<img src="/images/logo_nabble_home.png" border="0" alt="Nabble - free forums for everyone"/><br />
+					<h1 style="color:#979797">Start Your <%=what%></h1>
+
+					<% if (errors.size() > 0) { %>
+						<div class="error-message important" style="margin:1em;padding:.5em 0">
+							<% String generic = errors.get("generic"); %>
+							<%=generic != null? generic : errors.size() > 0? "Please check the errors below" : ""%>
+						</div>
+					<% } %>
+
+					<form action="/more/ForumStart$Save.jtp" method="post" accept-charset="UTF-8" onsubmit="return singleFormSubmit(this)">
+						<input type="hidden" name="type" value="<%=what.toLowerCase().replace(" ","")%>" />
+						<input type="hidden" name="what" value="<%=what%>" />
+
+						<div style="text-align:left;width:50em;margin:0 auto">
+							<div style="border-bottom:2px solid #eeeeee;padding:1em">
+								<div class="weak-color" style="width:12em;text-align:center;float:left">
+									<div style="font-weight:bold">Account</div>
+									<img src="/images/account.png" width="84" height="45"/>
+									<div style="margin-top:1em;font-size:80%">
+										You will receive an email with a link to activate your account
+									</div>
+								</div>
+								<table>
+									<tr>
+										<td class="column1"><div class="second-font field-title">User Name</div></td>
+										<td><input type="text" id="username" size="35" maxlength="30" name="username" value="<%=Jtp.hideNull(values.get("username"))%>" /></td>
+										<td class="important"><%=errors.containsKey("username")? errors.get("username"):""%></td>
+									</tr>
+									<tr>
+										<td class="column1"><div class="second-font field-title">Email</div></td>
+										<td><input type="text" size="35" maxlength="60" name="email" value="<%=Jtp.hideNull(values.get("email"))%>"/></td>
+										<td class="important"><%=errors.containsKey("email")? errors.get("email"):""%></td>
+									</tr>
+									<tr>
+										<td class="column1"><div class="second-font field-title">Password</div></td>
+										<td><input type="password" size="35" maxlength="15" name="password" value="<%=Jtp.hideNull(values.get("password"))%>"/></td>
+										<td class="important"><%=errors.containsKey("password")? errors.get("password"):""%></td>
+									</tr>
+									<tr>
+										<td class="column1"><input type="checkbox" id="terms" name="terms" value="y" <%="y".equals(values.get("terms"))?"checked":""%> /></td>
+										<td colspan=2><label for="terms">I have read and I agree to Nabble's <a href="<%=Jtp.termsUrl(true)%>">Terms of Use</a>.</label></td>
+									</tr>
+								</table>
+							</div>
+
+							<div style="padding:1em;overflow:hidden">
+								<div class="weak-color" style="width:12em;text-align:center;float:left;height:15em">
+									<div style="font-weight:bold"><%=what%></div>
+									<img src="/images/homepage/<%=imgName%>.png" alt="Free <%=what.toLowerCase()%>">
+								</div>
+								<table>
+									<tr>
+										<td class="column1"><div class="second-font field-title">Language</div></td>
+										<td>
+											<select name="lang">
+												<% for( Map.Entry<String,String> entry : Languages.languages.entrySet() ) { %>
+												<% String lang = request.getParameter("lang"); %>
+												<% boolean isEnglish = entry.getKey().equals("none"); %>
+												<% boolean isSelected = (lang == null && isEnglish) || entry.getKey().equals(lang); %>
+												<option value="<%=entry.getKey()%>" <%=isSelected?"selected=\"true\"":""%>><%=entry.getValue()%></option>
+												<% } %>
+											</select>
+										</td>
+										<td></td>
+									</tr>
+									<tr>
+										<td colspan="3" style="height:.6em"></td>
+									</tr>
+									<tr>
+										<td class="column1"><div class="second-font field-title"><%=what%> Name</div></td>
+										<td><input type="text" name="subject" size="30" maxlength="80" value="<%=Jtp.hideNull(values.get("subject"))%>"/></td>
+										<td class="important"><%=errors.containsKey("subject")? errors.get("subject"):""%></td>
+									</tr>
+									<tr>
+										<td colspan=3 style="padding:.4em 0 0 .6em">
+											<div class="second-font field-title">Description &nbsp;<span class="weak-color" style="font-weight:normal;">(optional)</span></div>
+											<textarea rows="7" name="message" wrap="SOFT" style="width:28em"><%=Jtp.hideNull(values.get("message"))%></textarea>
+											<br><%= Recaptcha.DIV %>
+										</td>
+									</tr>
+								</table>
+							</div>
+							<div class="weak-color" style="margin-top:.5em;text-align:center;">
+								<input id="submit-btn" type="submit" name="save" value="Create <%=what%>" style="padding:.5em .8em;font-size:110%;font-weight:bold"/>
+								<div id="wait-message" class="important invisible" style="margin:.1em 0">Creating <%=what%>... Please wait</div>
+							</div>
+						</div>
+                    </form>
+				</div>
+
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+
+	public static class Save extends HttpServlet {
+
+		private static String get(String name, HttpServletRequest request) {
+			String s = request.getParameter(name);
+			return s == null? null : s.trim();
+		}
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			String username = get("username", request);
+			String email = get("email", request);
+			String password = get("password", request);
+			boolean agreed = "y".equals(get("terms", request));
+			String subject = get("subject", request);
+			String message = get("message", request);
+
+			Map<String,String> errors = new HashMap<String,String>();
+			if (username == null || username.trim().length() == 0)
+				errors.put("username", "required");
+			if (email == null || email.length() == 0)
+				errors.put("email", "required");
+			else if (!new MailAddress(email).isValid())
+				errors.put("email", "invalid email");
+			if (password == null || password.length() < 4)
+				errors.put("password", "too short");
+			if (!agreed)
+				errors.put("generic", "You must agree to the Terms and Conditions");
+			if (subject == null || subject.length() == 0)
+				errors.put("subject", "required");
+
+			String type = get("type", request);
+			type = "newspaper".equals(type)? "news" : type;
+
+			String extraMessage = "";
+			if ("mailinglist".equals(type)) {
+				type = "forum";
+				StringBuilder m = new StringBuilder();
+				m.append("\n\nMailing List Options\n");
+				m.append("Click \"Options > Subscribe via email\" to subscribe to this mailing list;\n");
+				m.append("Click \"Options > Post by email...\" to get the email address of this mailing list;\n");
+				m.append("You can post messages via email or through the forum interface below;\n");
+				m.append("All web posts and emails are archived here.");
+				extraMessage = m.toString();
+			}
+
+			if (errors.isEmpty()) {
+				DbDatabase db = Db.dbGlobal();
+				db.beginTransaction();
+				try {
+					Recaptcha.check(request);
+					Site site = ModelHome.newSite(type,subject, message + extraMessage, Message.Format.TEXT, email, username);
+					Permissions.addToGroup( (User)site.getRootNode().getOwner(), Permissions.ADMINISTRATORS_GROUP );
+					String key = site.newRegistration(email,password,username,"/");
+					db.commitTransaction();
+
+					// Track spam activities by IP
+					ModelHome.setRemoteAddr(site, Jtp.getClientIpAddr(request));
+
+					site = site.getGoodCopy();
+
+					String lang = request.getParameter("lang");
+					if (!"none".equals(lang)) {
+						site.setModuleEnabled(lang, true);
+						site = site.getGoodCopy();
+					}
+
+					sendRegisterMail(site, email, key);
+					NewSiteMail.send(site, request, response);
+					response.sendRedirect(site.getBaseUrl()+"/more/ForumStart$Redirection.jtp");
+					return;
+				} catch(ModelException e) {
+					errors.put("generic", e.getMessage());
+				} finally {
+					db.endTransaction();
+				}
+			}
+
+			Map<String,String> values = new HashMap<String,String>();
+			values.put("username", username);
+			values.put("email", email);
+			values.put("password", password);
+			values.put("terms", agreed?"y":"");
+			values.put("subject", subject);
+			values.put("message", message);
+			build(request, response, values, errors);
+		}
+	}
+
+	/** Sets cookies in the site domain  */
+	public static class Redirection extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			Site site = Jtp.getSite(request);
+			Shared.javascriptRedirect(request, response, Jtp.url(site.getRootNode()), "Nabble.setVar('appnotice','true');");
+		}
+	}
+
+	public static void sendRegisterMail(Site site, String email, String key) {
+		Map<String,Object> args = new HashMap<String,Object>();
+		args.put("email",email);
+		args.put("next_url","/");
+		args.put("key",key);
+		Template template = site.getTemplate( "send_registration_email",
+			BasicNamespace.class, NabbleNamespace.class
+		);
+		template.run( TemplatePrintWriter.NULL, args,
+			new BasicNamespace(template), new NabbleNamespace(site)
+		);
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/more/MailingListRequest.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,270 @@
+
+package nabble.view.web.more;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.servlet.CanonicalUrl;
+import nabble.model.Db;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.NewSiteMail;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import nabble.view.lib.UrlMappable;
+import nabble.view.lib.Recaptcha;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class MailingListRequest extends HttpServlet implements UrlMappable, CanonicalUrl {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/archive-your-mailing-list\\.html$");
+
+	public static String url() {
+		return Jtp.defaultContextUrl() + path();
+	}
+
+	public static String path() {
+		return "/archive-your-mailing-list.html";
+	}
+
+	public String getCanonicalUrl(HttpServletRequest request) {
+		return url();
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		return new HashMap<String,String[]>();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		build(request, response, Collections.<String,String>emptyMap(), Collections.<String,String>emptyMap());
+	}
+
+	private static void build(HttpServletRequest request,HttpServletResponse response, Map<String,String> values, Map<String,String> errors)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request,response,"Archive Your Mailing List"); 
+		out.print( "\r\n		<META NAME=\"description\" CONTENT=\"Archiving a mailing list at Nabble is quick and easy. Fill in one simple form and you're done.\">\r\n		<META NAME=\"keywords\" CONTENT=\"free, mailing list archive, forum interface, gateway, customizable, easy, quick setup\">\r\n		<style type=\"text/css\">\r\n			div.field-title {\r\n				margin-top: 0;\r\n			}\r\n			td.column1 {\r\n				text-align:right;\r\n				width:7em;\r\n				white-space:nowrap;\r\n			}\r\n			div.field-box {\r\n				border:none;\r\n				margin:0;\r\n			}\r\n			input[type=text],input[type=password],select {\r\n                        padding:.3em 0;\r\n                    }\r\n			.important { font-weight:bold }\r\n		</style>\r\n		<script type=\"text/javascript\">\r\n			$(document).ready(function() {\r\n				$('#username').focus();\r\n			});\r\n		</script>\r\n		" );
+		out.print( ( Recaptcha.JS ) );
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.minHeaderGlobal(request,response); 
+		out.print( "\r\n\r\n		<h1>Archive Your Mailing List</h1>\r\n		<p>\r\n			<img src=\"/images/homepage/archive.png\" style=\"float:left;margin: 0 1em 1em 1em\"/>\r\n			You can archive your mailing list to a fully functional forum at Nabble by filling out\r\n			the form below.  This makes it easy for users to browse and search archived emails.\r\n			Users can even post to the Nabble forum and we will forward these posts to your mailing list.\r\n		</p>\r\n\r\n		" );
+ if (errors.size() > 0) { 
+		out.print( "\r\n			<div class=\"error-message important\" style=\"margin:1em;padding:.5em 0 .5em 12em\">\r\n				" );
+ String generic = errors.get("generic"); 
+		out.print( "\r\n				" );
+		out.print( (generic != null? generic : errors.size() > 0? "Please check the errors below" : "") );
+		out.print( "\r\n			</div>\r\n		" );
+ } 
+		out.print( "\r\n\r\n		<form method=\"post\" action=\"/more/MailingListRequest$Save.jtp\" accept-charset=\"UTF-8\">\r\n			<input type=\"hidden\" name=\"Action\" value=\"save\">\r\n\r\n			<div style=\"border-bottom:2px solid #eeeeee;padding:1em;width:50em\">\r\n				<div class=\"weak-color\" style=\"width:11.5em;text-align:center;float:left\">\r\n					<div style=\"font-weight:bold\">Account</div>\r\n					<img src=\"/images/account.png\"/>\r\n					<div style=\"margin-top:1em;font-size:80%\">\r\n						You will receive an email with a link to activate your account\r\n					</div>\r\n				</div>\r\n				<table>\r\n					<tr>\r\n						<td><div class=\"second-font field-title\">User Name</div></td>\r\n						<td><input type=\"text\" id=\"username\" size=\"35\" maxlength=\"30\" name=\"username\" value=\"" );
+		out.print( (Jtp.hideNull(values.get("username"))) );
+		out.print( "\" /></td>\r\n						<td class=\"important\">" );
+		out.print( (errors.containsKey("username")? errors.get("username"):"") );
+		out.print( "</td>\r\n					</tr>\r\n					<tr>\r\n						<td><div class=\"second-font field-title\">Email</div></td>\r\n						<td><input type=\"text\" size=\"35\" maxlength=\"60\" name=\"email\" value=\"" );
+		out.print( (Jtp.hideNull(values.get("email"))) );
+		out.print( "\"/></td>\r\n						<td class=\"important\">" );
+		out.print( (errors.containsKey("email")? errors.get("email"):"") );
+		out.print( "</td>\r\n					</tr>\r\n					<tr>\r\n						<td><div class=\"second-font field-title\">Password</div></td>\r\n						<td><input type=\"password\" size=\"35\" maxlength=\"15\" name=\"password\" value=\"" );
+		out.print( (Jtp.hideNull(values.get("password"))) );
+		out.print( "\"/></td>\r\n						<td class=\"important\">" );
+		out.print( (errors.containsKey("password")? errors.get("password"):"") );
+		out.print( "</td>\r\n					</tr>\r\n					<tr>\r\n						<td class=\"column1\"><input type=\"checkbox\" id=\"terms\" name=\"terms\" value=\"y\" " );
+		out.print( ("y".equals(values.get("terms"))?"checked":"") );
+		out.print( " /></td>\r\n						<td colspan=2><label for=\"terms\">I have read and I agree to Nabble's <a href=\"" );
+		out.print( (Jtp.termsUrl(true)) );
+		out.print( "\">Terms of Use</a>.</label></td>\r\n					</tr>\r\n				</table>\r\n			</div>\r\n\r\n			<div style=\"padding:.5em;width:50em\">\r\n				<div class=\"weak-color\" style=\"width:12em;text-align:center;float:left;height:50em;padding-top:1em\">\r\n					<div style=\"font-weight:bold\">Mailing List</div>\r\n					<img src=\"/images/homepage/mailing-list.png\" alt=\"Free Mailing List Archive\">\r\n				</div>\r\n\r\n				<div class=\"field-box\">\r\n					<div class=\"second-font field-title\">Mailing List Address</div>\r\n					<div class=\"weak-color\">\r\n						<input id=\"mailingList\" type=\"text\" name=\"ml-address\" value=\"" );
+		out.print( (Jtp.hideNull(values.get("ml-address"))) );
+		out.print( "\" size=\"46\" />\r\n						<span class=\"important\">" );
+		out.print( (errors.containsKey("ml-address")? errors.get("ml-address"):"") );
+		out.print( "</span>\r\n						<div class=\"weak-color\">e.g., mygroup@yahoogroups.com</div>\r\n					</div>\r\n				</div>\r\n\r\n				<div class=\"field-box light-border-color\">\r\n					<div class=\"second-font field-title\">Mailing List URL</div>\r\n					<div class=\"weak-color\">\r\n						Enter the homepage of this mailing list, where other users can find more information.<br/>\r\n						<input type=\"text\" name=\"ml-url\" size=\"55\" value=\"" );
+		out.print( (Jtp.hideNull(values.get("ml-url"))) );
+		out.print( "\" />\r\n						<span class=\"important\">" );
+		out.print( (errors.containsKey("ml-url")? errors.get("ml-url"):"") );
+		out.print( "</span>\r\n						<div class=\"weak-color\">e.g., http://www.mailinglist.com/list</div>\r\n					</div>\r\n				</div>\r\n\r\n				<div class=\"field-box light-border-color\">\r\n					<div class=\"second-font field-title\">Forum Name</div>\r\n					<div class=\"weak-color\">\r\n						Enter the name of the forum for this mailing list.<br/>\r\n						<input type=\"text\" name=\"subject\" size=\"46\" value=\"" );
+		out.print( (Jtp.hideNull(values.get("subject"))) );
+		out.print( "\" />\r\n						<span class=\"important\">" );
+		out.print( (errors.containsKey("subject")? errors.get("subject"):"") );
+		out.print( "</span>\r\n					</div>\r\n				</div>\r\n\r\n				<div class=\"field-box light-border-color\">\r\n					<div class=\"second-font field-title\">Forum Description</div>\r\n					<div class=\"weak-color\">\r\n						<textarea cols=46 rows=5 name=\"message\" id=\"nabble.desc\" wrap=\"SOFT\">" );
+		out.print( (Jtp.hideNull(values.get("message"))) );
+		out.print( "</textarea>\r\n					</div>\r\n				</div>\r\n\r\n				<div class=\"field-box light-border-color\">\r\n					<div class=\"second-font field-title\">List Server and Version</div>\r\n					<div class=\"weak-color\">\r\n						If you know, please select the mailing list server application.<br/>\r\n						<select name=\"server-type\">\r\n							" );
+
+									String[] serverTypes = ListServer.getAllServerTypes();
+									ListServer selectedServer = ListServer.getServer(values.get("server-type"));
+									for (String s : serverTypes) {
+										ListServer currentServer = ListServer.getServer(s);
+										if(currentServer.showInInitialSetup()) {
+											
+		out.print( "<option value=\"" );
+		out.print( (currentServer.getType()) );
+		out.print( "\" " );
+		out.print( (currentServer == selectedServer?"selected":"") );
+		out.print( ">" );
+		out.print( (currentServer.getViewName()) );
+		out.print( "</option>" );
+
+										}
+									}
+									
+		out.print( "\r\n</select>\r\n<div class=\"weak-color\">If you are not absolutely sure about the version of the list server, it is better if you leave it as Unknown</div>\r\n</div>\r\n</div>\r\n\r\n<div class=\"field-box light-border-color\">\r\n<div class=\"second-font field-title\">Other Settings</div>\r\n<table>\r\n<tr>\r\n<td><input id=\"plain-text\" name=\"plain-text\" type=\"checkbox\" value=\"y\" " );
+		out.print( ("y".equals(values.get("plain-text"))?"checked":"") );
+		out.print( " /></td>\r\n<td><label for=\"plain-text\">This list accepts only plain-text emails</label>.</td>\r\n</tr>\r\n<tr>\r\n<td><input id=\"ignore-x-noarchive\" name=\"ignore-x-noarchive\" type=\"checkbox\" value=\"y\" " );
+		out.print( ("y".equals(values.get("ignore-x-noarchive"))?"checked":"") );
+		out.print( " /></td>\r\n<td><label for=\"ignore-x-noarchive\">Ignore X-No-Archive Header</label>.</td>\r\n</tr>\r\n<tr>\r\n<td colspan=2 style=\"padding-top:1em\">\r\n	" );
+		out.print( ( Recaptcha.DIV ) );
+		out.print( "\r\n</td>\r\n</tr>\r\n</table>\r\n</div>\r\n\r\n<input type=\"submit\" value=\"Create Mailing List Archive\" style=\"padding:.5em .8em;font-size:110%;font-weight:bold\"/>\r\n</div>\r\n</form>\r\n\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+
+	public static class Save extends HttpServlet {
+
+		private static String get(String name, HttpServletRequest request) {
+			String s = request.getParameter(name);
+			return s == null? null : s.trim();
+		}
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			String username = get("username", request);
+			String email = get("email", request);
+			String password = get("password", request);
+			boolean agreed = "y".equals(get("terms", request));
+
+			String mlAddress = get("ml-address", request);
+			String mlUrl = get("ml-url", request);
+			String subject = get("subject", request);
+			String message = get("message", request);
+			String serverType = get("server-type", request);
+			boolean isPlainText = "y".equals(get("plain-text", request));
+			boolean isIgnoreXNoArchive = "y".equals(get("ignore-x-noarchive", request));
+
+			Map<String,String> errors = new HashMap<String,String>();
+			if (username == null || username.trim().length() == 0)
+				errors.put("username", "required");
+			if (email == null || email.length() == 0)
+				errors.put("email", "required");
+			else if (!new MailAddress(email).isValid())
+				errors.put("email", "invalid email");
+			if (password == null || password.length() < 4)
+				errors.put("password", "too short");
+			if (!agreed)
+				errors.put("generic", "You must agree to the Terms and Conditions");
+			if (subject == null || subject.length() == 0)
+				errors.put("subject", "required");
+			if (mlAddress == null || mlAddress.length() == 0)
+				errors.put("ml-address", "required");
+			else if (!new MailAddress(mlAddress).isValid())
+				errors.put("ml-address", "invalid email");
+			if (mlUrl == null || mlUrl.length() == 0)
+				errors.put("ml-url", "required");
+			else {
+				try {
+					new URL(mlUrl);
+				} catch (MalformedURLException e) {
+					errors.put("ml-url", "invalid URL");
+				}
+			}
+
+			if (errors.isEmpty()) {
+				DbDatabase db = Db.dbGlobal();
+				db.beginTransaction();
+				try {
+					Recaptcha.check(request);
+					Site site = ModelHome.newSite(Node.Type.FORUM,subject, message, Message.Format.TEXT, email, username);
+					Permissions.addToGroup( (User)site.getRootNode().getOwner(), Permissions.ADMINISTRATORS_GROUP );
+					String key = site.newRegistration(email,password,username,"/mailing_list/SubscribeToMailingList.jtp?node="+site.getRootNode().getId());
+
+					MailingList ml = site.getRootNode().newMailingList(ListServer.getServer(serverType), mlAddress, mlUrl);
+					ml.setPlainTextOnly(isPlainText);
+					ml.setIgnoreNoArchive(isIgnoreXNoArchive);
+					ml.update();
+
+					db.commitTransaction();
+
+					site = site.getGoodCopy();
+					ForumStart.sendRegisterMail(site, email, key);
+					NewSiteMail.send(site, request, response);
+					response.sendRedirect(site.getBaseUrl()+"/more/MailingListRequest$FinalSteps.jtp");
+					return;
+				} catch(ModelException e) {
+					errors.put("generic", e.getMessage());
+				} finally {
+					db.endTransaction();
+				}
+			}
+
+			Map<String,String> values = new HashMap<String,String>();
+			values.put("username", username);
+			values.put("email", email);
+			values.put("password", password);
+			values.put("terms", agreed?"y":"");
+
+			values.put("ml-address", mlAddress);
+			values.put("ml-url", mlUrl);
+			values.put("subject", subject);
+			values.put("message", message);
+			values.put("server-type", serverType);
+			values.put("plain-text", isPlainText?"y":"");
+			values.put("ignore-x-noarchive", isIgnoreXNoArchive?"y":"");
+			build(request, response, values, errors);
+		}
+	}
+
+	public static class FinalSteps extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			PrintWriter out = response.getWriter();
+			
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request,response,"Archive Created, But Subscription is Needed"); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.minHeaderGlobal(request,response); 
+		out.print( "\r\n		<h1>Success</h1>\r\n		<p>Your mailing list archive has been successfully created.</p>\r\n		<h2>What should I do now?</h2>\r\n		<p>\r\n			You should now check your email and click on the activation link sent to you.\r\n			That link will complete your account registration and take you to the page where you can subscribe the Nabble archive to the mailing list.\r\n			<b>If you don't subscribe the archive to the mailing list, it will NOT work properly!</b>\r\n		</p>\r\n		<p>You can contact <a href=\"" );
+		out.print( (Jtp.supportUrl()) );
+		out.print( "\">Nabble Support</a> if you have questions.</p>\r\n		" );
+ Shared.footer(request,response); 
+		out.print( "\r\n		" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/more/MailingListRequest.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,374 @@
+<%
+package nabble.view.web.more;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.servlet.CanonicalUrl;
+import nabble.model.Db;
+import nabble.model.ListServer;
+import nabble.model.MailingList;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.NewSiteMail;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import nabble.view.lib.UrlMappable;
+import nabble.view.lib.Recaptcha;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class MailingListRequest extends HttpServlet implements UrlMappable, CanonicalUrl {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/archive-your-mailing-list\\.html$");
+
+	public static String url() {
+		return Jtp.defaultContextUrl() + path();
+	}
+
+	public static String path() {
+		return "/archive-your-mailing-list.html";
+	}
+
+	public String getCanonicalUrl(HttpServletRequest request) {
+		return url();
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		return new HashMap<String,String[]>();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		build(request, response, Collections.<String,String>emptyMap(), Collections.<String,String>emptyMap());
+	}
+
+	private static void build(HttpServletRequest request,HttpServletResponse response, Map<String,String> values, Map<String,String> errors)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<% Shared.title(request,response,"Archive Your Mailing List"); %>
+				<META NAME="description" CONTENT="Archiving a mailing list at Nabble is quick and easy. Fill in one simple form and you're done.">
+				<META NAME="keywords" CONTENT="free, mailing list archive, forum interface, gateway, customizable, easy, quick setup">
+				<style type="text/css">
+					div.field-title {
+						margin-top: 0;
+					}
+					td.column1 {
+						text-align:right;
+						width:7em;
+						white-space:nowrap;
+					}
+					div.field-box {
+						border:none;
+						margin:0;
+					}
+					input[type=text],input[type=password],select {
+                        padding:.3em 0;
+                    }
+					.important { font-weight:bold }
+				</style>
+				<script type="text/javascript">
+					$(document).ready(function() {
+						$('#username').focus();
+					});
+				</script>
+				<%= Recaptcha.JS %>
+			</head>
+			<body>
+				<% Shared.minHeaderGlobal(request,response); %>
+
+				<h1>Archive Your Mailing List</h1>
+				<p>
+					<img src="/images/homepage/archive.png" style="float:left;margin: 0 1em 1em 1em"/>
+					You can archive your mailing list to a fully functional forum at Nabble by filling out
+					the form below.  This makes it easy for users to browse and search archived emails.
+					Users can even post to the Nabble forum and we will forward these posts to your mailing list.
+				</p>
+
+				<% if (errors.size() > 0) { %>
+					<div class="error-message important" style="margin:1em;padding:.5em 0 .5em 12em">
+						<% String generic = errors.get("generic"); %>
+						<%=generic != null? generic : errors.size() > 0? "Please check the errors below" : ""%>
+					</div>
+				<% } %>
+
+				<form method="post" action="/more/MailingListRequest$Save.jtp" accept-charset="UTF-8">
+					<input type="hidden" name="Action" value="save">
+
+					<div style="border-bottom:2px solid #eeeeee;padding:1em;width:50em">
+						<div class="weak-color" style="width:11.5em;text-align:center;float:left">
+							<div style="font-weight:bold">Account</div>
+							<img src="/images/account.png"/>
+							<div style="margin-top:1em;font-size:80%">
+								You will receive an email with a link to activate your account
+							</div>
+						</div>
+						<table>
+							<tr>
+								<td><div class="second-font field-title">User Name</div></td>
+								<td><input type="text" id="username" size="35" maxlength="30" name="username" value="<%=Jtp.hideNull(values.get("username"))%>" /></td>
+								<td class="important"><%=errors.containsKey("username")? errors.get("username"):""%></td>
+							</tr>
+							<tr>
+								<td><div class="second-font field-title">Email</div></td>
+								<td><input type="text" size="35" maxlength="60" name="email" value="<%=Jtp.hideNull(values.get("email"))%>"/></td>
+								<td class="important"><%=errors.containsKey("email")? errors.get("email"):""%></td>
+							</tr>
+							<tr>
+								<td><div class="second-font field-title">Password</div></td>
+								<td><input type="password" size="35" maxlength="15" name="password" value="<%=Jtp.hideNull(values.get("password"))%>"/></td>
+								<td class="important"><%=errors.containsKey("password")? errors.get("password"):""%></td>
+							</tr>
+							<tr>
+								<td class="column1"><input type="checkbox" id="terms" name="terms" value="y" <%="y".equals(values.get("terms"))?"checked":""%> /></td>
+								<td colspan=2><label for="terms">I have read and I agree to Nabble's <a href="<%=Jtp.termsUrl(true)%>">Terms of Use</a>.</label></td>
+							</tr>
+						</table>
+					</div>
+
+					<div style="padding:.5em;width:50em">
+						<div class="weak-color" style="width:12em;text-align:center;float:left;height:50em;padding-top:1em">
+							<div style="font-weight:bold">Mailing List</div>
+							<img src="/images/homepage/mailing-list.png" alt="Free Mailing List Archive">
+						</div>
+
+						<div class="field-box">
+							<div class="second-font field-title">Mailing List Address</div>
+							<div class="weak-color">
+								<input id="mailingList" type="text" name="ml-address" value="<%=Jtp.hideNull(values.get("ml-address"))%>" size="46" />
+								<span class="important"><%=errors.containsKey("ml-address")? errors.get("ml-address"):""%></span>
+								<div class="weak-color">e.g., mygroup@yahoogroups.com</div>
+							</div>
+						</div>
+
+						<div class="field-box light-border-color">
+							<div class="second-font field-title">Mailing List URL</div>
+							<div class="weak-color">
+								Enter the homepage of this mailing list, where other users can find more information.<br/>
+								<input type="text" name="ml-url" size="55" value="<%=Jtp.hideNull(values.get("ml-url"))%>" />
+								<span class="important"><%=errors.containsKey("ml-url")? errors.get("ml-url"):""%></span>
+								<div class="weak-color">e.g., http://www.mailinglist.com/list</div>
+							</div>
+						</div>
+
+						<div class="field-box light-border-color">
+							<div class="second-font field-title">Forum Name</div>
+							<div class="weak-color">
+								Enter the name of the forum for this mailing list.<br/>
+								<input type="text" name="subject" size="46" value="<%=Jtp.hideNull(values.get("subject"))%>" />
+								<span class="important"><%=errors.containsKey("subject")? errors.get("subject"):""%></span>
+							</div>
+						</div>
+
+						<div class="field-box light-border-color">
+							<div class="second-font field-title">Forum Description</div>
+							<div class="weak-color">
+								<textarea cols=46 rows=5 name="message" id="nabble.desc" wrap="SOFT"><%=Jtp.hideNull(values.get("message"))%></textarea>
+							</div>
+						</div>
+
+						<div class="field-box light-border-color">
+							<div class="second-font field-title">List Server and Version</div>
+							<div class="weak-color">
+								If you know, please select the mailing list server application.<br/>
+								<select name="server-type">
+									<%
+									String[] serverTypes = ListServer.getAllServerTypes();
+									ListServer selectedServer = ListServer.getServer(values.get("server-type"));
+									for (String s : serverTypes) {
+										ListServer currentServer = ListServer.getServer(s);
+										if(currentServer.showInInitialSetup()) {
+											%><option value="<%=currentServer.getType()%>" <%=currentServer == selectedServer?"selected":""%>><%=currentServer.getViewName()%></option><%
+										}
+									}
+									%>
+								</select>
+								<div class="weak-color">If you are not absolutely sure about the version of the list server, it is better if you leave it as Unknown</div>
+							</div>
+						</div>
+
+						<div class="field-box light-border-color">
+							<div class="second-font field-title">Other Settings</div>
+							<table>
+								<tr>
+									<td><input id="plain-text" name="plain-text" type="checkbox" value="y" <%="y".equals(values.get("plain-text"))?"checked":""%> /></td>
+									<td><label for="plain-text">This list accepts only plain-text emails</label>.</td>
+								</tr>
+								<tr>
+									<td><input id="ignore-x-noarchive" name="ignore-x-noarchive" type="checkbox" value="y" <%="y".equals(values.get("ignore-x-noarchive"))?"checked":""%> /></td>
+									<td><label for="ignore-x-noarchive">Ignore X-No-Archive Header</label>.</td>
+								</tr>
+								<tr>
+									<td colspan=2 style="padding-top:1em">
+										<%= Recaptcha.DIV %>
+									</td>
+								</tr>
+							</table>
+						</div>
+
+						<input type="submit" value="Create Mailing List Archive" style="padding:.5em .8em;font-size:110%;font-weight:bold"/>
+					</div>
+				</form>
+
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+
+	public static class Save extends HttpServlet {
+
+		private static String get(String name, HttpServletRequest request) {
+			String s = request.getParameter(name);
+			return s == null? null : s.trim();
+		}
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			String username = get("username", request);
+			String email = get("email", request);
+			String password = get("password", request);
+			boolean agreed = "y".equals(get("terms", request));
+
+			String mlAddress = get("ml-address", request);
+			String mlUrl = get("ml-url", request);
+			String subject = get("subject", request);
+			String message = get("message", request);
+			String serverType = get("server-type", request);
+			boolean isPlainText = "y".equals(get("plain-text", request));
+			boolean isIgnoreXNoArchive = "y".equals(get("ignore-x-noarchive", request));
+
+			Map<String,String> errors = new HashMap<String,String>();
+			if (username == null || username.trim().length() == 0)
+				errors.put("username", "required");
+			if (email == null || email.length() == 0)
+				errors.put("email", "required");
+			else if (!new MailAddress(email).isValid())
+				errors.put("email", "invalid email");
+			if (password == null || password.length() < 4)
+				errors.put("password", "too short");
+			if (!agreed)
+				errors.put("generic", "You must agree to the Terms and Conditions");
+			if (subject == null || subject.length() == 0)
+				errors.put("subject", "required");
+			if (mlAddress == null || mlAddress.length() == 0)
+				errors.put("ml-address", "required");
+			else if (!new MailAddress(mlAddress).isValid())
+				errors.put("ml-address", "invalid email");
+			if (mlUrl == null || mlUrl.length() == 0)
+				errors.put("ml-url", "required");
+			else {
+				try {
+					new URL(mlUrl);
+				} catch (MalformedURLException e) {
+					errors.put("ml-url", "invalid URL");
+				}
+			}
+
+			if (errors.isEmpty()) {
+				DbDatabase db = Db.dbGlobal();
+				db.beginTransaction();
+				try {
+					Recaptcha.check(request);
+					Site site = ModelHome.newSite(Node.Type.FORUM,subject, message, Message.Format.TEXT, email, username);
+					Permissions.addToGroup( (User)site.getRootNode().getOwner(), Permissions.ADMINISTRATORS_GROUP );
+					String key = site.newRegistration(email,password,username,"/mailing_list/SubscribeToMailingList.jtp?node="+site.getRootNode().getId());
+
+					MailingList ml = site.getRootNode().newMailingList(ListServer.getServer(serverType), mlAddress, mlUrl);
+					ml.setPlainTextOnly(isPlainText);
+					ml.setIgnoreNoArchive(isIgnoreXNoArchive);
+					ml.update();
+
+					db.commitTransaction();
+
+					site = site.getGoodCopy();
+					ForumStart.sendRegisterMail(site, email, key);
+					NewSiteMail.send(site, request, response);
+					response.sendRedirect(site.getBaseUrl()+"/more/MailingListRequest$FinalSteps.jtp");
+					return;
+				} catch(ModelException e) {
+					errors.put("generic", e.getMessage());
+				} finally {
+					db.endTransaction();
+				}
+			}
+
+			Map<String,String> values = new HashMap<String,String>();
+			values.put("username", username);
+			values.put("email", email);
+			values.put("password", password);
+			values.put("terms", agreed?"y":"");
+
+			values.put("ml-address", mlAddress);
+			values.put("ml-url", mlUrl);
+			values.put("subject", subject);
+			values.put("message", message);
+			values.put("server-type", serverType);
+			values.put("plain-text", isPlainText?"y":"");
+			values.put("ignore-x-noarchive", isIgnoreXNoArchive?"y":"");
+			build(request, response, values, errors);
+		}
+	}
+
+	public static class FinalSteps extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			PrintWriter out = response.getWriter();
+			%>
+			<html>
+				<head>
+					<% Shared.title(request,response,"Archive Created, But Subscription is Needed"); %>
+				</head>
+				<body>
+					<% Shared.minHeaderGlobal(request,response); %>
+					<h1>Success</h1>
+					<p>Your mailing list archive has been successfully created.</p>
+					<h2>What should I do now?</h2>
+					<p>
+						You should now check your email and click on the activation link sent to you.
+						That link will complete your account registration and take you to the page where you can subscribe the Nabble archive to the mailing list.
+						<b>If you don't subscribe the archive to the mailing list, it will NOT work properly!</b>
+					</p>
+					<p>You can contact <a href="<%=Jtp.supportUrl()%>">Nabble Support</a> if you have questions.</p>
+					<% Shared.footer(request,response); %>
+					<% Shared.analytics(request,response); %>
+				</body>
+			</html>
+			<%
+		}
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/nabble.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,685 @@
+/* font and global ---------------------------------------------------*/
+body, input, button, textarea, select {
+	font-family: Verdana,Geneva,Helvetica,Arial,sans-serif;
+}
+.second-font, h1, h2, h3, h4, h5, h6 {
+	font-family: Arial, sans-serif;
+}
+body, table .nabble {
+	font-size: .84em;
+	margin: .8em;
+}
+code {
+	font-size: 1.1em;
+}
+.small {
+	font-size: 0.9em;
+}
+
+#forum-header {
+	padding-top:.5em;
+	text-align:center;
+}
+
+#forum-footer {
+	margin-top:.3em;
+	padding-left:.5em;
+}
+
+#forum-title {
+	font-weight:bolder;
+	font-size:190%;
+	margin:0;
+}
+
+#description-box {
+	clear:both;
+	font-size:90%;
+	padding: 1em 1em 0 1em;
+}
+
+#search-box {
+	text-align: right;
+}
+
+img.star {
+	width:18px;
+	height:18px;
+	vertical-align:middle;
+	border:none;
+}
+
+div.notice {
+	position: fixed;
+	top: 0;
+	left:50%;
+	padding: .2em .4em;
+	background-color: #FAE900;
+	font-weight: bold;
+	font-size: 120%;
+	display: none;
+}
+/* color scheme ------------------------------------------------------*/
+
+/* text and link (foreground) colors -----------------*/
+.nabble,
+.nabble table {
+	color: #000000; /* black */
+}
+.nabble h1, .nabble h2, .nabble h3, .nabble h4, .nabble h5, .nabble h6 {
+	color: #333333; /* black (light) */
+}
+.nabble .important {
+	color: #cc0000; /* red (dark) */
+}
+
+.nabble .form-label,
+.nabble .weak-color {
+	color: #666666; /* gray */
+}
+
+.nabble a * { color:inherit; }
+
+.nabble a:link,
+.nabble a.not-visited-link {
+	color: #0000ee;
+}
+
+.nabble a:visited,
+.nabble a.visited-link {
+	color: #551a8b;
+}
+
+/* background colors --------------------*/
+.nabble,
+.nabble .no-bg-color {
+	background: #ffffff; /* white */
+}
+
+.nabble .light-bg-color {
+	background: #f2f2f2; /* ultra light gray */
+}
+
+.nabble .shaded-bg-color {
+	background: #eeeeee; /* gray (light) */
+}
+
+.nabble .dark-bg-color {
+	background: #dddddd; /* gray (medium) */
+}
+.nabble .highlight {
+	background: #ffff99; /* yellow */
+}
+.nabble .error-message,
+.nabble .info-message {
+	background: #ffffcc; /* yellow (light) */
+}
+
+/* border colors ------------------------*/
+.nabble .medium-border-color  {
+	border-color: #cccccc; /* gray (medium) */
+}
+.nabble .light-border-color {
+	border-color: #eeeeee; /* gray (light) */
+}
+.nabble .dark-border-color {
+	border-color: #666666; /* gray */
+}
+
+/* generic -----------------------------------------------------------*/
+.nabble a,
+.nabble table,
+.nabble input,
+.nabble textarea,
+.nabble select {
+	font-size: 1em;
+}
+.nabble option {
+	white-space: pre;
+}
+.nabble h1 {
+	font-size: 1.8em;
+	font-weight: bold;
+	margin-top: .4em;
+	margin-bottom: 0.5em;
+}
+.nabble h1 a:link, .nabble h1 a:visited {
+	text-decoration: none;
+	font-weight: bold;
+}
+.nabble h2 {
+	font-size: 1.3em;
+	padding: .3em 0;
+	margin:0;
+}
+.nabble .smaller {
+	font-size: .9em;
+	font-weight: normal;
+}
+.nabble h3 {
+	font-size: 1.1em;
+	font-style:italic;
+	padding:.3em 0;
+	margin:0;
+}
+div.message-text h2, div.root-text h2,
+div.message-text h3, div.root-text h3,
+div.message-text h4, div.root-text h4,
+div.message-text h5, div.root-text h5,
+div.message-text h6, div.root-text h6
+{
+	padding: .6em 0 .4em;
+	margin:0;
+}
+/*  HEADER ------------------------------------------------------------------- */
+.nabble .top-bar {
+	vertical-align: top;
+	padding-bottom: .3em;
+	height:1.6em;
+	clear:both;
+}
+
+.search-box input {
+	padding:2px 0 2px 25px;
+	background: url('images/search.png') 5px 50% no-repeat;
+	border-width:2px;
+	border-style: solid;
+	-moz-border-radius: 10px;
+	-webkit-border-radius: 10px;
+	border-radius: 10px;
+}
+
+/*  FOOTER  */
+.nabble .footer-table {
+	width: 100%;
+	border-collapse: collapse;
+	margin-top: 2.4em;
+	-moz-border-radius: 6px;
+	-webkit-border-radius: 6px;
+	clear:both;
+}
+.nabble .footer-table td {
+	font-size: .9em;
+	line-height: 2.0em;
+	padding-left: 0.5em;
+	padding-right: 0.5em;
+}
+.nabble .footer-left {
+	text-align: left;
+}
+.nabble .footer-right {
+	text-align: right;
+}
+
+/* EDITOR TABLE ------------------------------------------------------*/
+.nabble .editor-table {
+	border-width: 1px;
+	border-style: solid;
+	border-spacing: 0;
+}
+.nabble .editor-table td {
+	border-bottom-width: 1px;
+	border-bottom-style: solid;
+	padding: .42em;
+}
+
+/*  FORMS ---------------------------------------------------------------------- */
+.nabble form {
+	margin: 0;
+}
+.nabble .form-label {
+	font-weight: bold;
+	text-align: right;
+	font-size: .9em;
+}
+
+div.field-title, td.field-title {
+	font-weight:bold;
+	font-size:110%;
+	padding-bottom:.1em;
+	margin-top: 1.7em;
+}
+
+div.field-box {
+	width:100%;
+	padding: .5em 0;
+	margin:.3em 0 .1em;
+	border-bottom-width: 1px;
+	border-bottom-style:solid;
+}
+/*  DESCRIPTIVE PAGES  */
+.nabble .content-description {
+	margin: 1em;
+}
+.nabble .content-description p,
+.nabble .content-description li {
+	line-height: 1.67em;
+}
+.nabble .content-description h2 {
+	font-weight: bold;
+	font-size: 1.17em;
+	margin: 1.1em 0 0 0;
+}
+
+/* message quoting -------------------------------------------------------------------------- */
+.nabble blockquote.quote {
+	border-left-width: 2px;
+	border-left-style: solid;
+	margin: 1.4em 1.8em;
+	font-size: .9em;
+}
+.nabble blockquote.quote blockquote.quote {
+	font-size: 1em;
+}
+.nabble div.quote {
+	border-top-width: 1px;
+	border-top-style: solid;
+	display: inline-block;
+	padding: 0 1em;
+}
+.nabble blockquote.quote div.quote-author {
+	padding: .5em 0;
+}
+.nabble blockquote.quote div.quote-message {
+	margin: .5em 0;
+}
+
+/*Opera fix*/
+div.shrinkable-quote {
+	overflow: hidden;
+}
+
+div.shrink-quote a {
+	font-size: 80%;
+	text-decoration: none;
+}
+
+div.quote div.shrink-quote a {
+	cursor: pointer;
+	font-size: 90%;
+}
+
+/* Others */
+input[type='radio'], input[type='checkbox'] { vertical-align:-15%; }
+label { cursor: pointer; }
+.inline {display:inline}
+.invisible {display:none;}
+.nabble .ad {text-align:center;clear:both;margin:2em 0 1em;}
+.nowrap {white-space:nowrap;}
+.float-left{float:left;}
+.float-right{float:right;}
+.no-decoration {text-decoration:none;}
+.bold { font-weight:bold; }
+.image16 { width:16px;height:16px;vertical-align:middle; border:none;}
+.image24 { width:24px;height:24px;vertical-align:middle; border:none;}
+.image32 { width:32px;height:32px;vertical-align:middle; border:none;}
+
+.nabble .border1 {
+	border-width: 1px;
+	border-style: solid;
+}
+.nabble .border2 {
+	border-width: 2px;
+	border-style: solid;
+}
+
+.nabble .signature {
+	clear:both;
+	border-top: 1px solid #ddd;
+	margin-top: 2em;
+}
+
+.black-overlay{
+	display: none;
+	position: absolute;
+	top:0;
+	left:0;
+	width: 100%;
+	height: 200%;
+	background-color: black;
+	z-index:1001;
+	opacity:.80;
+	filter: Alpha(opacity=80);
+}
+
+.window-content {
+	display: none;
+	position: absolute;
+	top: 25%;
+	left: 25%;
+	width: 50%;
+	padding: 16px;
+	border-width: 5px;
+	border-style:solid;
+	background-color: white;
+	z-index:1002;
+	overflow: auto;
+	-moz-border-radius: 6px;
+	-webkit-border-radius: 6px;
+}
+/*------------------- drop-down -----------------------*/
+span.dropdown {
+	padding-right:.5em;
+	line-height:normal;
+}
+
+span.dropdown table,
+span.dropdown ul,
+ul.dropdown-submenu {
+	position:absolute;
+	display:none;
+	text-align:left;
+	border-width:1px;
+	border-style:solid;
+	padding:.1em .2em;
+	z-index:10000;
+	border-collapse:collapse;
+}
+
+span.dropdown ul,
+ul.dropdown-submenu {
+	list-style:none;
+	padding:0;
+}
+
+span.dropdown table td,
+span.dropdown ul li,
+ul.dropdown-submenu li {
+	font-size:90%;
+	white-space: nowrap;
+	padding:.2em .5em;
+}
+
+span.dropdown table a,
+span.dropdown ul li a,
+ul.dropdown-submenu li a {
+	color: inherit;
+	text-decoration:none;
+	padding:.25em;
+}
+
+td.dropdown-simple-row {
+	padding:.45em .75em !important;
+}
+
+td.action-separator{
+	border-bottom-width: 1px;
+	border-bottom-style: solid;
+}
+
+tr.dropdown-separator {
+	font-size:1px;
+	line-height: 1px;
+	display:none;
+}
+tr.dropdown-separator > * {
+	padding:0;line-height:0;font-size:1px;
+}
+/*------------------- threads & posts ---------------------*/
+.post-hover {
+	background: #eeeeee;
+}
+
+span.post-date {
+	color:#6a6a6a;
+	cursor:default;
+}
+
+span.post-author {
+	color: #116611;
+	white-space: nowrap;
+	margin:0 .2em 0 .3em;
+	font-size:100%;
+}
+
+h2.post-subject {
+	color: #777777;
+	overflow: hidden;
+	white-space: nowrap;
+	padding: 0 .5em 0 0;
+	display:inline;
+	margin:0 0 0 1em;
+	cursor:default;
+	font-size:100%;
+}
+
+span.post-snippet, td.post-unindent {
+	color:#909090;
+}
+
+div.post-border {
+	border: 2px solid #D9D9D9;
+	width:99.9%;
+}
+
+span.connect-line {
+	background-image:url("images/connect-line.gif");
+	background-repeat: repeat-y;
+}
+
+span.connect-end {
+	background-image:url("images/connect-line.gif");
+	background-repeat: no-repeat;
+}
+
+td.connect-end {
+	background-image:url("images/connect-end.gif");
+	background-repeat: no-repeat;
+}
+
+td.connect-node {
+	cursor:pointer;
+	background-image:url("images/connect-node.gif");
+	background-repeat: no-repeat;
+}
+
+td.connect-node-closed {
+	background-image:url("images/connect-node-closed.gif");
+	background-repeat: no-repeat;
+}
+
+/*----- MISC ----*/
+.rounded {
+	-moz-border-radius: 5px;
+	-webkit-border-radius: 5px;
+	border-radius: 5px;
+}
+
+.rounded-top {
+	-moz-border-radius-topleft: 5px;
+	-moz-border-radius-topright: 5px;
+	-webkit-border-top-right-radius: 5px;
+	-webkit-border-top-left-radius: 5px;
+	border-top-left-radius:5px;
+	border-top-right-radius:5px;
+}
+
+.rounded-bottom {
+	-moz-border-radius-bottomleft: 5px;
+	-moz-border-radius-bottomright: 5px;
+	-webkit-border-bottom-left-radius: 5px;
+	-webkit-border-bottom-right-radius: 5px;
+	border-left-radius:5px;
+	border-bottom-right-radius:5px;
+}
+
+.drop-shadow {
+	-webkit-box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 4px 0px;
+	-moz-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+	box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 4px 0px;
+}
+
+h2.category-subject {
+	padding:0;
+	margin:0;
+	display:inline;
+	font-size:110%;
+}
+
+.nabble .app-notice {
+	border:3px solid #EDD9B7;
+	background: url('gradients/v60_FFFFF6_FFFFCC') #FFFFCC repeat-x;
+	padding: .45em;
+	text-align: center;
+}
+
+img.avatar {
+	line-height:2em;
+	vertical-align:-8px;
+	border-width:2px;
+	border-style:solid;
+	-webkit-border-radius:5px;
+	-moz-border-radius:5px;
+}
+
+img.online {
+	position:relative;
+	z-index:999;
+	width:8px;
+	height:8px;
+	border:none;
+	right:6px;
+	top:10px;
+	margin-right:-.5em;
+}
+
+img.left {
+	float:left;
+	margin:.4em;
+}
+img.right {
+	float:right;
+	margin:.4em;
+}
+img.center {
+	display: block;
+	margin-left:auto;
+	margin-right:auto;
+}
+
+table.number {
+	margin-bottom: .5em;
+}
+td.number {
+	width: 2em;
+}
+span.number {
+	font-size: 150%;
+	padding: 0 .3em .03em;
+	border-width:1px;
+	border-style:solid;
+}
+span.box, span.box-text {
+	display:inline-block;
+	background: url('images/shadow.png') no-repeat bottom right;
+	padding:5px 12px 12px 5px;
+	position:relative;
+}
+span.box-text {
+	overflow:hidden;
+	text-align:center;
+	width:140px;
+	height:110px;
+}
+div.pinned-box {
+	position:relative;
+	z-index:999;
+	background-image:url('images/pin.png');
+	width:20px;
+	height:21px;
+	top:10px;
+	margin-top:-21px;
+}
+
+/* work group */
+div.priority {
+	color: white;
+	text-align:center;
+	font-weight:bolder;
+	padding:.1em .2em;
+	display:inline;
+	-moz-border-radius: 4px;
+	-webkit-border-radius: 4px;
+	border-radius: 4px;
+}
+div.priority-1 { background:url('/gradients/v25_FC5858_E01B1E') #E94747 repeat-x; border: 1px solid #D13838; text-shadow:1px 1px 0 #D13838;}
+div.priority-2 { background:url('/gradients/v25_F8D14A_D7BB4B') #D8B23D repeat-x; border: 1px solid #C9A92A; text-shadow:1px 1px 0 #a58803;}
+div.priority-3 { background:url('/gradients/v25_D0D0D0_A0A0A0') #BCBCBC repeat-x; border: 1px solid #909090; text-shadow:1px 1px 0 #777777;}
+div.priority-4 { background:url('/gradients/v25_72DFF8_39B5D8') #81C7DE repeat-x; border: 1px solid #25a2a8; text-shadow:1px 1px 0 #25a2a8;}
+div.priority-5 { background:url('/gradients/v25_4378e8_1e3d80') #4A7BD5 repeat-x; border: 1px solid #26478d; text-shadow:1px 1px 0 #333333;}
+
+/* News view */
+div.news-title {
+	float:left;
+}
+h3.news-title {
+	display:inline;
+}
+.big-title {
+	font-size: 120%;
+	font-weight:bold;
+}
+div.sidebar-section,
+h2.sidebar-section {
+	width:100%;
+	margin: 0 0 .3em 0;
+	padding: .2em;
+	-moz-border-radius: 4px;
+	-webkit-border-radius: 4px;
+	white-space:nowrap;
+}
+ul.sidebar-section {
+	list-style-type: none;
+	margin:0;
+	padding:0;
+}
+ul.sidebar-section li {
+	padding:.4em 0 .4em .5em;
+	border-bottom-style: dotted;
+	border-bottom-width:1px;
+	white-space:nowrap;
+}
+
+#captcha { text-transform:uppercase; }
+
+button.toolbar,
+input[type=submit].toolbar,
+input[type=button].toolbar
+{
+	white-space:nowrap;
+	text-shadow:1px 1px 0 white;
+	padding:.15em .25em;
+	margin:0;
+	font:100%/1.4 Arial,Sans-serif;
+	color:#333 !important;
+	cursor:pointer;
+	background:#ddd url(images/btn_bg.gif) repeat-x 0 0 !important;
+	-moz-border-radius: 3px;
+	-webkit-border-radius: 3px;
+	border-radius: 3px;
+	border-left:1px solid #bbb;
+	border-right:1px solid #aaa;
+	border-top:1px solid #bbb;
+	border-bottom:1px solid #aaa;
+	outline:0;
+}
+button.toolbar:active,
+input[type=submit].toolbar:active,
+input[type=button].toolbar:active
+{
+	background-position:0 -500px;
+	outline:0;
+}
+button.toolbar-disabled,
+input[type=submit].toolbar-disabled,
+input[type=button].toolbar-disabled
+{ color: #777; }
+button.toolbar-disabled:active,
+input[type=submit].toolbar-disabled,
+input[type=button].toolbar-disabled {
+	background-position:0 0;
+}
+button.action-button,
+input[type=submit].action-button,
+input[type=button].action-button
+{ padding:.25em .5em; font-weight: bold; }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/seo/WidgetRedir.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,44 @@
+
+package nabble.view.web.seo;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.UrlMappable;
+import nabble.model.Init;
+import nabble.view.web.more.ForumStart;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+
+public final class WidgetRedir extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/free-forum-widget\\.html$");
+
+	private static final String WIDGET_REDIR = Init.get("widgetRedir", ForumStart.path("forum"));
+
+	private static String path() {
+		return "/free-forum-widget.html";
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		return Collections.emptyMap();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		response.setHeader("Location", WIDGET_REDIR);
+		response.sendError(HttpServletResponse.SC_MOVED_PERMANENTLY);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/seo/WidgetRedir.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,44 @@
+<%
+package nabble.view.web.seo;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.UrlMappable;
+import nabble.model.Init;
+import nabble.view.web.more.ForumStart;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+
+public final class WidgetRedir extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/free-forum-widget\\.html$");
+
+	private static final String WIDGET_REDIR = Init.get("widgetRedir", ForumStart.path("forum"));
+
+	private static String path() {
+		return "/free-forum-widget.html";
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		return Collections.emptyMap();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		response.setHeader("Location", WIDGET_REDIR);
+		response.sendError(HttpServletResponse.SC_MOVED_PERMANENTLY);
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/CacheNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,79 @@
+package nabble.view.web.template;
+
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.TemplateRuntimeException;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+
+@Namespace (
+	name = "cache",
+	global = false
+)
+public final class CacheNamespace {
+	static final class DontCache extends TemplateRuntimeException {}
+
+	final Set<String> events = new LinkedHashSet<String>();
+	private final Site site;
+
+	CacheNamespace() {
+		this.site = NabbleNamespace.current().site();
+		events.add( Cache.siteChangeEvent(site) );
+	}
+
+	public static final CommandSpec descendant_changes = CommandSpec.NO_OUTPUT()
+		.dotParameter("node")
+		.build()
+	;
+
+	@Command public void descendant_changes(IPrintWriter out,Interpreter interp) {
+		Node node = site.getNode(interp.getArgAsLong("node"));
+		if (node != null)
+			events.add( Cache.descendantChangeEvent(node) );
+	}
+
+	public static final CommandSpec node_changes = CommandSpec.NO_OUTPUT()
+		.dotParameter("node")
+		.build()
+	;
+
+	@Command public void node_changes(IPrintWriter out,Interpreter interp) {
+		Node node = site.getNode(interp.getArgAsLong("node"));
+		if (node != null)
+			events.add( Cache.nodeChangeEvent(node) );
+	}
+
+	public static final CommandSpec bread_crumb_changes = CommandSpec.NO_OUTPUT()
+		.dotParameter("node")
+		.build()
+	;
+
+	@Command public void bread_crumb_changes(IPrintWriter out,Interpreter interp) {
+		Node node = site.getNode(interp.getArgAsLong("node"));
+		if (node != null)
+			Jtp.addBreadCrumbEvents(events, node);
+	}
+
+	public static final CommandSpec new_user = CommandSpec.NO_OUTPUT;
+
+	@Command public void new_user(IPrintWriter out,Interpreter interp) {
+		events.add( Cache.newUserEvent(site) );
+	}
+
+	public static final CommandSpec user_group_change = CommandSpec.NO_OUTPUT;
+
+	@Command public void user_group_change(IPrintWriter out,Interpreter interp) {
+		events.add( Cache.groupChangeEvent(site) );
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/CalendarWidget.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,147 @@
+
+package nabble.view.web.template;
+
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.CommandSpec;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import nabble.naml.compiler.PrintWriter;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+
+final class CalendarWidget {
+
+	public static final CommandSpec _calendar = new CommandSpec.Builder()
+		.parameters("date_time","months","week_days","min_year")
+		.build()
+	;
+
+	public static void _calendar(IPrintWriter out,Interpreter interp) {
+		String dateTime = interp.getArgString("date_time");
+		String months = interp.getArgString("months");
+		String weekDays = interp.getArgString("week_days");
+		String minYear = interp.getArgString("min_year");
+		Calendar cal = Calendar.getInstance();
+		cal.setLenient(false);
+		cal.setTimeInMillis(Long.valueOf(dateTime));
+		printCalendar(cal, Integer.valueOf(minYear), months, weekDays, out.getPrintWriter());
+	}
+
+	private static void printCalendar(Calendar cal, int minYear, String months, String weekDays, PrintWriter out) {
+		int day = cal.get(Calendar.DAY_OF_MONTH);
+		String[] monthsArray = months.split("\\|");
+		int month = cal.get(Calendar.MONTH);
+		int year = cal.get(Calendar.YEAR);
+		int currentYear = Calendar.getInstance().get(Calendar.YEAR);
+		
+		out.print( "\n<script type=\"text/javascript\">\n	function NabbleCalendar(d,m,y) {\n		this.day = d;\n		this.month = m;\n		this.year = y;\n	};\n	NabbleCalendar.prototype.setDay = function(d) {\n		this.day = parseInt(d);\n	};\n	NabbleCalendar.prototype.getDay = function() {\n		return this.day;\n	};\n	NabbleCalendar.prototype.getMonth = function() {\n		return this.month;\n	};\n	NabbleCalendar.prototype.getYear = function() {\n		return this.year;\n	};\n	NabbleCalendar.prototype.updateCalendar = function() {\n		var $m = $('#month');\n		var $y = $('#year');\n		this.month = parseInt($m.val());\n		this.year = parseInt($y.val());\n		$m.attr('disabled','y');\n		$y.attr('disabled','y');\n		$.get('/template/CalendarWidget$Update.jtp?day='+this.day+'&month='+this.month+'&year='+this.year+'&weekdays=" );
+		out.print( (weekDays) );
+		out.print( "', function(data) {\n			$('#calendar-wrapper').html(data);\n			$m.removeAttr('disabled');\n			$y.removeAttr('disabled');\n			Nabble.addEventHandlers();\n		});\n	};\n	var nabbleCalendar = new NabbleCalendar(" );
+		out.print( (day) );
+		out.print( ", " );
+		out.print( (month) );
+		out.print( ", " );
+		out.print( (year) );
+		out.print( ");\n	Nabble.addEventHandlers = function() {\n		$('#calendar-wrapper td.day').css('cursor','pointer').click(function(){\n			$('#day'+nabbleCalendar.day).removeClass('current');\n			var day = $(this).html();\n			nabbleCalendar.setDay(day);\n			$(this).addClass('current');\n		});\n	};\n	$(document).ready(function() {\n		Nabble.addEventHandlers();\n	});\n</script>\n<select id=\"month\" onchange=\"nabbleCalendar.updateCalendar()\">\n	" );
+ for (int m = 0;m < 12; m++) { 
+		out.print( "\n	<option value=\"" );
+		out.print( (m) );
+		out.print( "\" " );
+		out.print( (month == m? "selected" : "") );
+		out.print( ">" );
+		out.print( (monthsArray[m]) );
+		out.print( "</option>\n	" );
+ } 
+		out.print( "\n</select>\n<select id=\"year\" onchange=\"nabbleCalendar.updateCalendar()\">\n	" );
+ for (int y = minYear;y <= currentYear; y++) { 
+		out.print( "\n	<option value=\"" );
+		out.print( (y) );
+		out.print( "\" " );
+		out.print( (year == y? "selected" : "") );
+		out.print( ">" );
+		out.print( (y) );
+		out.print( "</option>\n	" );
+ } 
+		out.print( "\n</select>\n<div id=\"calendar-wrapper\">\n	" );
+ printCalendarTable(cal, weekDays, out); 
+		out.print( "\n</div>\n" );
+
+	}
+
+	private static void printCalendarTable(Calendar cal, String weekDays, PrintWriter out) {
+		String[] weekDaysArray = weekDays.split("\\|");
+		int day = cal.get(Calendar.DAY_OF_MONTH);
+		int nDays = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
+		cal.set(Calendar.DAY_OF_MONTH, 1);
+		int firstDay = cal.get(Calendar.DAY_OF_WEEK);
+		int i;
+		
+		out.print( "\n<table class=\"calendar\">\n	<tr>\n		<td class=\"week-header\">" );
+		out.print( (weekDaysArray[0]) );
+		out.print( "</td>\n		<td class=\"week-header\">" );
+		out.print( (weekDaysArray[1]) );
+		out.print( "</td>\n		<td class=\"week-header\">" );
+		out.print( (weekDaysArray[2]) );
+		out.print( "</td>\n		<td class=\"week-header\">" );
+		out.print( (weekDaysArray[3]) );
+		out.print( "</td>\n		<td class=\"week-header\">" );
+		out.print( (weekDaysArray[4]) );
+		out.print( "</td>\n		<td class=\"week-header\">" );
+		out.print( (weekDaysArray[5]) );
+		out.print( "</td>\n		<td class=\"week-header\">" );
+		out.print( (weekDaysArray[6]) );
+		out.print( "</td>\n	</tr>\n	<tr>\n		" );
+ for (i = 1; i < firstDay; i++) {
+		out.print( "\n			<td class=\"empty\"></td>\n		" );
+ } 
+		out.print( "\n		" );
+ int d = 1; 
+		out.print( "\n		" );
+ for (i = firstDay; i <= 7; i++) printDay(d++, day, out); 
+		out.print( "\n	</tr>\n	" );
+ while (d <= nDays) { 
+		out.print( "\n		" );
+ int nextStop = d + 7; 
+		out.print( "\n		<tr>\n			" );
+ while (d < nextStop && d <= nDays) printDay(d++, day, out); 
+		out.print( "\n		</tr>\n	" );
+ } 
+		out.print( "\n</table>\n" );
+
+	}
+
+	private static void printDay(int day, int currentDay, PrintWriter out) {
+		
+		out.print( "<td id=\"day" );
+		out.print( (day) );
+		out.print( "\" class=\"day" );
+		out.print( (day == currentDay?" current":"") );
+		out.print( "\">" );
+		out.print( (day) );
+		out.print( "</td>" );
+
+	}
+
+	public static class Update extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			PrintWriter out = new PrintWriter(response.getWriter());
+			String weekDays = request.getParameter("weekdays");
+			String day = request.getParameter("day");
+			String month = request.getParameter("month");
+			String year = request.getParameter("year");
+			Calendar cal = new GregorianCalendar(Integer.valueOf(year), Integer.valueOf(month), Integer.valueOf(day));
+			printCalendarTable(cal, weekDays, out);
+		}
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/CalendarWidget.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,159 @@
+<%
+package nabble.view.web.template;
+
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.CommandSpec;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import nabble.naml.compiler.PrintWriter;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+
+final class CalendarWidget {
+
+	public static final CommandSpec _calendar = new CommandSpec.Builder()
+		.parameters("date_time","months","week_days","min_year")
+		.build()
+	;
+
+	public static void _calendar(IPrintWriter out,Interpreter interp) {
+		String dateTime = interp.getArgString("date_time");
+		String months = interp.getArgString("months");
+		String weekDays = interp.getArgString("week_days");
+		String minYear = interp.getArgString("min_year");
+		Calendar cal = Calendar.getInstance();
+		cal.setLenient(false);
+		cal.setTimeInMillis(Long.valueOf(dateTime));
+		printCalendar(cal, Integer.valueOf(minYear), months, weekDays, out.getPrintWriter());
+	}
+
+	private static void printCalendar(Calendar cal, int minYear, String months, String weekDays, PrintWriter out) {
+		int day = cal.get(Calendar.DAY_OF_MONTH);
+		String[] monthsArray = months.split("\\|");
+		int month = cal.get(Calendar.MONTH);
+		int year = cal.get(Calendar.YEAR);
+		int currentYear = Calendar.getInstance().get(Calendar.YEAR);
+		%>
+		<script type="text/javascript">
+			function NabbleCalendar(d,m,y) {
+				this.day = d;
+				this.month = m;
+				this.year = y;
+			};
+			NabbleCalendar.prototype.setDay = function(d) {
+				this.day = parseInt(d);
+			};
+			NabbleCalendar.prototype.getDay = function() {
+				return this.day;
+			};
+			NabbleCalendar.prototype.getMonth = function() {
+				return this.month;
+			};
+			NabbleCalendar.prototype.getYear = function() {
+				return this.year;
+			};
+			NabbleCalendar.prototype.updateCalendar = function() {
+				var $m = $('#month');
+				var $y = $('#year');
+				this.month = parseInt($m.val());
+				this.year = parseInt($y.val());
+				$m.attr('disabled','y');
+				$y.attr('disabled','y');
+				$.get('/template/CalendarWidget$Update.jtp?day='+this.day+'&month='+this.month+'&year='+this.year+'&weekdays=<%=weekDays%>', function(data) {
+					$('#calendar-wrapper').html(data);
+					$m.removeAttr('disabled');
+					$y.removeAttr('disabled');
+					Nabble.addEventHandlers();
+				});
+			};
+			var nabbleCalendar = new NabbleCalendar(<%=day%>, <%=month%>, <%=year%>);
+			Nabble.addEventHandlers = function() {
+				$('#calendar-wrapper td.day').css('cursor','pointer').click(function(){
+					$('#day'+nabbleCalendar.day).removeClass('current');
+					var day = $(this).html();
+					nabbleCalendar.setDay(day);
+					$(this).addClass('current');
+				});
+			};
+			$(document).ready(function() {
+				Nabble.addEventHandlers();
+			});
+		</script>
+		<select id="month" onchange="nabbleCalendar.updateCalendar()">
+			<% for (int m = 0;m < 12; m++) { %>
+			<option value="<%=m%>" <%=month == m? "selected" : ""%>><%=monthsArray[m]%></option>
+			<% } %>
+		</select>
+		<select id="year" onchange="nabbleCalendar.updateCalendar()">
+			<% for (int y = minYear;y <= currentYear; y++) { %>
+			<option value="<%=y%>" <%=year == y? "selected" : ""%>><%=y%></option>
+			<% } %>
+		</select>
+		<div id="calendar-wrapper">
+			<% printCalendarTable(cal, weekDays, out); %>
+		</div>
+		<%
+	}
+
+	private static void printCalendarTable(Calendar cal, String weekDays, PrintWriter out) {
+		String[] weekDaysArray = weekDays.split("\\|");
+		int day = cal.get(Calendar.DAY_OF_MONTH);
+		int nDays = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
+		cal.set(Calendar.DAY_OF_MONTH, 1);
+		int firstDay = cal.get(Calendar.DAY_OF_WEEK);
+		int i;
+		%>
+		<table class="calendar">
+			<tr>
+				<td class="week-header"><%=weekDaysArray[0]%></td>
+				<td class="week-header"><%=weekDaysArray[1]%></td>
+				<td class="week-header"><%=weekDaysArray[2]%></td>
+				<td class="week-header"><%=weekDaysArray[3]%></td>
+				<td class="week-header"><%=weekDaysArray[4]%></td>
+				<td class="week-header"><%=weekDaysArray[5]%></td>
+				<td class="week-header"><%=weekDaysArray[6]%></td>
+			</tr>
+			<tr>
+				<% for (i = 1; i < firstDay; i++) {%>
+					<td class="empty"></td>
+				<% } %>
+				<% int d = 1; %>
+				<% for (i = firstDay; i <= 7; i++) printDay(d++, day, out); %>
+			</tr>
+			<% while (d <= nDays) { %>
+				<% int nextStop = d + 7; %>
+				<tr>
+					<% while (d < nextStop && d <= nDays) printDay(d++, day, out); %>
+				</tr>
+			<% } %>
+		</table>
+		<%
+	}
+
+	private static void printDay(int day, int currentDay, PrintWriter out) {
+		%><td id="day<%=day%>" class="day<%=day == currentDay?" current":""%>"><%=day%></td><%
+	}
+
+	public static class Update extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			PrintWriter out = new PrintWriter(response.getWriter());
+			String weekDays = request.getParameter("weekdays");
+			String day = request.getParameter("day");
+			String month = request.getParameter("month");
+			String year = request.getParameter("year");
+			Calendar cal = new GregorianCalendar(Integer.valueOf(year), Integer.valueOf(month), Integer.valueOf(day));
+			printCalendarTable(cal, weekDays, out);
+		}
+
+	}
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/CompileTest.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,239 @@
+package nabble.view.web.template;
+
+import nabble.model.Init;
+import nabble.modules.ModuleManager;
+import nabble.modules.hacks.HacksModule;
+import nabble.modules.workgroup.WorkgroupModule;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Module;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.utils.Log4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+
+public final class CompileTest {
+	private static final Logger logger = LoggerFactory.getLogger(CompileTest.class);
+
+	private static final List<Program> keepInMemory = new ArrayList<Program>();
+
+	private static void test0(Program program,String macroName,Class<?>... classes) throws CompileException {
+		if( program.getTemplate( macroName, classes ) == null )
+			throw new RuntimeException("macro '"+macroName+"' not found");
+	}
+
+	public static void test(Program program,String macroName,Class<?>... classes) throws CompileException {
+		Class<?>[] a = new Class<?>[classes.length+2];
+		a[0] = BasicNamespace.class;
+		a[1] = NabbleNamespace.class;
+		System.arraycopy(classes,0,a,2,classes.length);
+		test0(program,macroName,a);
+	}
+
+	static void compileTopic(Program program) throws CompileException {
+		CompileTest.test( program, "topic", ServletNamespace.class );
+		CompileTest.test( program, "classic_blog_topic", ServletNamespace.class );
+		CompileTest.test( program, "list_blog_topic", ServletNamespace.class );
+		CompileTest.test( program, "threaded_blog_topic", ServletNamespace.class );
+		CompileTest.test( program, "classic_forum_topic", ServletNamespace.class );
+		CompileTest.test( program, "list_forum_topic", ServletNamespace.class );
+		CompileTest.test( program, "threaded_forum_topic", ServletNamespace.class );
+	}
+
+	static void compileAll(Program program) throws CompileException {
+		runCompileAllMacro(program);
+	}
+
+	static void compileAll() throws CompileException {
+		Program program;
+
+		program = Program.getInstance(ModuleManager.getGenericModules());
+		compileAll(program);
+
+		logger.info("----- Tweak: more_ads -----");
+		testModule("more_ads", new String[] { "topic", "view_standard", "view_topics", "view_mixed", "view_board", "view_category" });
+
+		logger.info("----- Tweak: PPC -----");
+		testModule("ppc", new String[] { "manage_subscribers" });
+
+		logger.info("----- Tweak: Вайнах -----");
+		testModule("banhax", new String[] { "view_news", "view_gallery" });
+
+		logger.info("----- Tweak: PedXing -----");
+		testModule("pedxing", new String[] { "atom_topics_by_popularity", "topic" });
+
+		logger.info("----- Tweak: Jonaspm -----");
+		testModule("jonaspm", new String[] { "view_news" });
+
+		logger.info("----- Tweak: Animeron -----");
+		testModule("animeron", new String[] { "user_profile", "topic" });
+
+		logger.info("----- Tweak: Blackcow -----");
+		testModule("blackcow", HacksModule.INSTANCE, new String[] { "topic" });
+
+		logger.info("----- Languages -----");
+		String[] langMacros = new String[] { "topic", "view_app", "user_nodes", "send bookmark email", "post_by_email_page" };
+		String[] langModules = new String[] { "lang_pt_br", "lang_ch_si", "lang_ch_tr", "lang_fr_fr", "lang_sv", "lang_es", "lang_cs_cz", "lang_ell", "lang_tu", "lang_pl", "lang_rus_ru" };
+		for (String lang : langModules) {
+			testModule(lang, langMacros);
+			testEmails(lang);
+		}
+
+		logger.info("----- Social -----");
+		testModule("social_dropdown_links", new String[] { "topic" });
+		testModule("social_google_plus_one", new String[] { "topic" });
+		testModule("social_facebook_like", new String[] { "topic" });
+		testModule("social_tweet", new String[] { "topic" });
+
+		logger.info("----- Content -----");
+		testModule("content_noindex", new String[] { "topic", "view_standard", "view_topics", "view_mixed" });
+		testModule("content_open_links_in_new_window", new String[] { "topic" });
+		testModule("content_smart_cache", new String[] { "view_app" });
+		testModule("content_news_summary", new String[] { "view_app", "view_news" });
+
+		logger.info("----- Email -----");
+		testModule("email_registration_notification", new String[] { "finish_registration_page" });
+
+		logger.info("----- Workgroup -----");
+		testModule("workgroup", new String[] { "view_app", "topic", "new_topic", "reply" });
+/*
+		logger.info("----- Poll -----");
+		testModule("poll", new String[] { "new_topic", "reply", "edit_post", "topic", "change_permissions" });
+		testEmails("poll");
+*/
+	}
+
+	private static void testModule(String moduleName, String[] macros) throws CompileException {
+		testModule(moduleName, null, macros);
+	}
+
+	private static void testModule(String moduleName, Module module, String[] macros) throws CompileException {
+		List<Module> modules = ModuleManager.getGenericModules();
+		modules.add( ModuleManager.getModule(moduleName) );
+		if (module != null)
+			modules.add(module);
+		Program program = Program.getInstance(modules);
+		for (String macro: macros) {
+			if ("topic".equals(macro))
+				compileTopic(program);
+			else
+				CompileTest.test( program, macro, ServletNamespace.class );
+		}
+	}
+
+	private static void testEmails(String moduleName) throws CompileException {
+		List<Module> modules = ModuleManager.getGenericModules();
+		modules.add( ModuleManager.getModule(moduleName) );
+		Program program = Program.getInstance(modules);
+		CompileTest.test( program, "notify_subscribers", NodeNamespace.class );
+		CompileTest.test( program, "digest email", NodeList.class, SubscriptionNamespace.class );
+	}
+
+	static void memTest() throws Exception {
+		logger.info("memTest");
+System.in.read();
+
+		Program program;
+
+		program = Program.getInstance(ModuleManager.getGenericModules());
+		keepInMemory.add(program);
+		compileAll(program);
+
+		List<Module> modules = ModuleManager.getGenericModules();
+
+		logger.info("----- Tweak: PPC -----");
+		modules = ModuleManager.getGenericModules();
+		modules.add( ModuleManager.getModule("ppc") );
+		program = Program.getInstance(modules);
+		keepInMemory.add(program);
+		compileAll(program);
+
+		logger.info("----- Tweak: Вайнах -----");
+		modules = ModuleManager.getGenericModules();
+		modules.add( ModuleManager.getModule("banhax") );
+		program = Program.getInstance(modules);
+		keepInMemory.add(program);
+		compileAll(program);
+
+		logger.info("----- Tweak: PedXing -----");
+		modules = ModuleManager.getGenericModules();
+		modules.add( ModuleManager.getModule("pedxing") );
+		program = Program.getInstance(modules);
+		keepInMemory.add(program);
+		compileAll(program);
+
+		logger.info("----- Tweak: Jonaspm -----");
+		modules = ModuleManager.getGenericModules();
+		modules.add( ModuleManager.getModule("jonaspm") );
+		program = Program.getInstance(modules);
+		keepInMemory.add(program);
+		compileAll(program);
+
+		logger.info("----- Tweak: Animeron -----");
+		modules = ModuleManager.getGenericModules();
+		modules.add( ModuleManager.getModule("animeron") );
+		program = Program.getInstance(modules);
+		keepInMemory.add(program);
+		compileAll(program);
+
+		logger.info("----- Tweak: Blackcow -----");
+		modules = ModuleManager.getGenericModules();
+		modules.add(HacksModule.INSTANCE);
+		modules.add( ModuleManager.getModule("blackcow") );
+		program = Program.getInstance(modules);
+		keepInMemory.add(program);
+		compileAll(program);
+
+		System.out.println("done");
+		System.in.read();
+	}
+
+	static void runCompileAllMacro(Program program) throws CompileException {
+		Template template = program.getTemplate( "compile_all", BasicNamespace.class );
+		template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(),
+			new BasicNamespace(template)
+		);
+	}
+
+	static void runCompileAllMacro() throws CompileException {
+		List<Module> modules = ModuleManager.getGenericModules();
+		Program program = Program.getInstance(modules);
+		runCompileAllMacro(program);
+	}
+
+	static void workgroupTest() throws CompileException {
+		List<Module> modules = ModuleManager.getGenericModules();
+		compileAll(Program.getInstance(modules));
+		logger.info("----- workgroup -----");
+		modules.add(WorkgroupModule.INSTANCE);
+		compileAll(Program.getInstance(modules));
+	}
+
+	public static void main(String[] args) {
+		Log4j.initForConsole();
+		Init.nop();
+		try {
+			if( args.length==1 && args[0].equals("mem") ) {
+				memTest();
+			} else {
+//System.in.read();
+				compileAll();
+//				workgroupTest();
+//				runCompileAllMacro();
+//System.out.println("done");
+//System.in.read();
+			}
+		} catch(Exception e) {
+			e.printStackTrace();
+		}
+		System.exit(0);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/DateNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,50 @@
+package nabble.view.web.template;
+
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+
+@Namespace (
+	name = "date",
+	global = false
+)
+public final class DateNamespace {
+	private final Date date;
+
+	public DateNamespace(Date date) {
+		this.date = date;
+	}
+
+	@Command public void raw_time(IPrintWriter out,Interpreter interp) {
+		out.print( date.getTime() );
+	}
+
+	public static final CommandSpec custom_format = new CommandSpec.Builder()
+		.parameters("format")
+		.build()
+	;
+
+	@Command public void custom_format(IPrintWriter out, Interpreter interp) {
+		String format = interp.getArgString("format");
+		out.print(new SimpleDateFormat(format).format(date));
+	}
+
+	public static final CommandSpec is_older_than = new CommandSpec.Builder()
+		.parameters("days")
+		.build()
+	;
+
+	@Command public void is_older_than(IPrintWriter out, Interpreter interp) {
+		int days = interp.getArgAsInt("days");
+		long diffFromNow = System.currentTimeMillis() - date.getTime();
+		float daysFromNow = diffFromNow / (24 * 60 * 60 * 1000);
+		out.print(daysFromNow > days);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/DocNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,213 @@
+package nabble.view.web.template;
+
+import nabble.model.Site;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.JavaCommand;
+import nabble.naml.compiler.Meaning;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.ListSequence;
+import nabble.naml.namespaces.StringList;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+@Namespace (
+	name = "doc",
+	global = true
+)
+public class DocNamespace {
+
+	static String getDocName(String macroName,Collection<String> requiredNamespaces) {
+		StringBuilder buf = new StringBuilder();
+		buf.append( "doc for " ).append( macroName );
+		if( !requiredNamespaces.isEmpty() ) {
+			Iterator<String> iter = requiredNamespaces.iterator();
+			buf.append( " requiring " ).append( iter.next() );
+			while( iter.hasNext() ) {
+				buf.append( "," ).append( iter.next() );
+			}
+		}
+		return buf.toString();
+	}
+
+
+	@Namespace (
+		name = "binary_doc",
+		global = true
+	)
+	public static final class BinaryDocNamespace extends DocNamespace {
+
+		private final Site site;
+		private final JavaCommand meaning;
+
+		private final String value;
+		private final String[] params;
+		private final String[] seeAlso;
+
+		BinaryDocNamespace(Site site, JavaCommand meaning,String value, String[] params, String[] seeAlso) {
+			this.site = site;
+			this.meaning = meaning;
+			this.value = value;
+			this.params = params;
+			this.seeAlso = seeAlso;
+		}
+
+		@Command("value") public void _value(IPrintWriter out,Interpreter interp) {
+			out.print(value);
+		}
+
+		@Command public void has_parameters(IPrintWriter out,Interpreter interp) {
+			out.print(params.length > 0 || meaning.getParameterNames().length > 0);
+		}
+
+		public static final CommandSpec parameter_list = CommandSpec.DO;
+
+		@Command public void parameter_list(IPrintWriter out, ScopedInterpreter<ParameterList> interp) {
+			String[] parameters = meaning.getParameterNames();
+			String[] requiredParameters = meaning.getRequiredParameterNames();
+			String dotParameter = meaning.getDotParameterName();
+
+			HashMap<String,String> all = new HashMap<String,String>();
+			for (String s : params) {
+				String[] keyValue = s.split("=");
+				String name = keyValue[0];
+				String description = keyValue[1];
+				all.put(name, description);
+			}
+			for (String s : parameters) {
+				if (!all.containsKey(s))
+					all.put(s, "");
+			}
+			List<ParameterInfo> infos = new ArrayList<ParameterInfo>();
+			for( Map.Entry<String,String> entry : all.entrySet() ) {
+				String name = entry.getKey();
+				String description = entry.getValue();
+				boolean isOptional = !contains(name, requiredParameters);
+				boolean isDotParameter = name.equals(dotParameter);
+				infos.add(new ParameterInfo(name, description, isOptional, isDotParameter));
+			}
+			out.print(interp.getArg(new ParameterList(infos),"do"));
+		}
+
+		private boolean contains(String name, String[] names) {
+			for (String s : names)
+				if (s.equals(name))
+					return true;
+			return false;
+		}
+
+		public static final CommandSpec see_also = CommandSpec.DO;
+
+		@Command public void see_also(IPrintWriter out, ScopedInterpreter<StringList> interp) {
+			out.print(interp.getArg(new StringList(Arrays.asList(seeAlso)),"do"));
+		}
+
+		@Command public void requires(IPrintWriter out, Interpreter interp) {
+			out.print( meaning.getRequiredNamespaces().iterator().next() );
+		}
+
+		@Command public void adds_namespace(IPrintWriter out, Interpreter interp) {
+			out.print( meaning.addsNamespace() != null );
+		}
+
+		@Command public void added_namespace(IPrintWriter out, Interpreter interp) {
+			out.print( meaning.addsNamespace() );
+		}
+
+		@Command public void binary_details(IPrintWriter out,Interpreter interp) {
+			out.print(meaning.getMethod());
+		}
+
+		public static final CommandSpec related_commands = CommandSpec.DO;
+
+		@Command public void related_commands(IPrintWriter out,ScopedInterpreter<MacroSourceNamespace.Commands> interp)
+			throws IOException, ServletException, CompileException
+		{
+			List<MacroSourceNamespace.CommandInfo> commands = relatedCommands();
+			Collections.sort(commands, MacroSourceNamespace.CommandInfo.MACRO_NAME_COMPARATOR);
+			Object block = interp.getArg(new MacroSourceNamespace.Commands(commands),"do");
+			out.print(block);
+		}
+
+		private List<MacroSourceNamespace.CommandInfo> relatedCommands() {
+			List<MacroSourceNamespace.CommandInfo> commands = new ArrayList<MacroSourceNamespace.CommandInfo>();
+			Class c = meaning.getMethod().getDeclaringClass();
+			for (Method method : c.getMethods()) {
+				if (method.getAnnotation(Command.class) != null) {
+					Meaning m = site.getProgram().getMeaning(c.getName() + '.' + method.getName());
+					if (m != null)
+						commands.add(new MacroSourceNamespace.CommandInfo(m, null, null));
+				}
+			}
+			return commands;
+		}
+	}
+
+	@Namespace (
+		name = "parameter_list",
+		global = true
+	)
+	public static final class ParameterList extends ListSequence<ParameterInfo> {
+		
+		public ParameterList(List<ParameterInfo> elements) {
+			super(elements);
+		}
+
+		public static final CommandSpec current_parameter = CommandSpec.DO;
+
+		@Command public void current_parameter(IPrintWriter out,ScopedInterpreter<ParameterInfo> interp) {
+			out.print(interp.getArg(get(),"do"));
+		}
+	}
+
+	@Namespace (
+		name = "parameter_info",
+		global = true
+	)
+	public static final class ParameterInfo {
+
+		private final String name;
+		private final String description;
+		private final boolean isOptional;
+		private final boolean isDotParameter;
+
+		public ParameterInfo(String name, String description, boolean isOptional, boolean isDotParameter) {
+			this.name = name;
+			this.description = description;
+			this.isOptional = isOptional;
+			this.isDotParameter = isDotParameter;
+		}
+
+		@Command("name") public void _name(IPrintWriter out,Interpreter interp) {
+			out.print(name);
+		}
+
+		@Command("description") public void _description(IPrintWriter out,Interpreter interp) {
+			out.print(description);
+		}
+
+		@Command public void is_optional(IPrintWriter out,Interpreter interp) {
+			out.print(isOptional);
+		}
+		
+		@Command public void is_dot_parameter(IPrintWriter out,Interpreter interp) {
+			out.print(isDotParameter);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/EmailNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,217 @@
+package nabble.view.web.template;
+
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.mail.AlternativeMultipartContent;
+import fschmidt.util.mail.Content;
+import fschmidt.util.mail.FileAttachmentContent;
+import fschmidt.util.mail.HtmlTextContent;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.MixedMultipartContent;
+import fschmidt.util.mail.PlainTextContent;
+import fschmidt.util.mail.TextAttachmentContent;
+import fschmidt.util.mail.TextContent;
+import nabble.model.FileUpload;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.Encoder;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+
+@Namespace(
+		name = "email",
+		global = true,
+		transparent = true
+)
+public final class EmailNamespace {
+	private static final Logger logger = LoggerFactory.getLogger(EmailNamespace.class);
+
+	private Mail mail = MailHome.newMail();
+	List<Content> attachments = new ArrayList<Content>();
+
+	public static final CommandSpec set_header = CommandSpec.NO_OUTPUT()
+		.parameters("name", "value")
+		.build();
+
+	@Command public void set_header(IPrintWriter out, Interpreter interp) {
+		String name = interp.getArgString("name");
+		String value = interp.getArgString("value");
+		mail.setHeader(name, value);
+	}
+
+	public static final CommandSpec add_text_attachment = CommandSpec.DO()
+		.parameters("subtype", "text", "filename")
+		.build();
+
+	@Command public void add_text_attachment(IPrintWriter out, Interpreter interp) {
+		interp.setEncoder(Encoder.TEXT);
+		String subtype = interp.getArgString("subtype");
+		String text = interp.getArgString("text");
+		String filename = interp.getArgString("filename");
+		attachments.add(new TextAttachmentContent(subtype, text, filename));
+	}
+
+	public static final CommandSpec add_node_as_zip_attachment = CommandSpec.DO()
+		.parameters("node_attr")
+		.dotParameter("node_attr")
+		.build();
+
+	@Command public void add_node_as_zip_attachment(IPrintWriter out, Interpreter interp) {
+		NodeNamespace nn = interp.getArgAsNamespace(NodeNamespace.class, "node_attr");
+		final String filename = nn.node().getSubject() + ".zip";
+		final InputStream zippedNode = buildZip(nn.node());
+		FileAttachmentContent content = new FileAttachmentContent("application", "octet-stream") {
+			public String getFileName() { return filename; }
+			public int getSize() { return -1; }
+			public InputStream getInputStream() { return zippedNode; }
+			public String getContentID() { return null; }
+		};
+		attachments.add(content);
+	}
+
+	public static final CommandSpec send = new CommandSpec.Builder()
+		.parameters("to", "subject", "text_part")
+		.optionalParameters("cc", "bcc", "to_name", "from", "from_name", "html_part", "aol_part", "set_headers_for", "bounce_to", "attachments")
+		.build();
+
+	@Command public void send(IPrintWriter out, Interpreter interp)
+			throws ModelException.EmailFormat
+	{
+		interp.setEncoder(Encoder.TEXT);
+		String to = interp.getArgString("to");
+		if (to == null || !new MailAddress(to).isValid())
+			throw new ModelException.EmailFormat(to);
+
+		String cc = interp.getArgString("cc");
+		if (cc != null && !new MailAddress(cc).isValid())
+			throw new ModelException.EmailFormat(cc);
+
+		String bcc = interp.getArgString("bcc");
+		if (bcc != null && !new MailAddress(bcc).isValid())
+			throw new ModelException.EmailFormat(bcc);
+
+		String fromName = interp.getArgString("from_name");
+		String from = interp.getArgString("from");
+
+		MailAddress fromAddress = from == null ?
+				new MailAddress(ModelHome.noReply, fromName == null ? "Nabble" : fromName) :
+				fromName == null ? new MailAddress(from) : new MailAddress(from, fromName);
+
+		String subject = interp.getArgString("subject");
+		String text = interp.getArgString("text_part");
+		String aol = interp.getArgString("aol_part");
+		interp.setEncoder(Encoder.HTML);
+		String html = interp.getArgString("html_part");
+		interp.setEncoder(Encoder.TEXT);
+
+		String toName = interp.getArgString("to_name");
+		MailAddress toAddress = toName == null ? new MailAddress(to) : new MailAddress(to, toName);
+
+		NodeNamespace nodeNs = interp.getArgAsNamespace(NodeNamespace.class, "set_headers_for");
+		if (nodeNs != null)
+			ModelHome.setNodeHeaders(mail, nodeNs.node());
+
+		mail.setFrom(fromAddress);
+		mail.setTo(toAddress);
+		if (cc != null)
+			mail.setCc(new MailAddress(cc));
+		if (bcc != null)
+			mail.setBcc(new MailAddress(bcc));
+		mail.setSubject(subject);
+		mail.setSentDate(new Date());
+
+		List<Content> alternatives = new ArrayList<Content>();
+		alternatives.add( new PlainTextContent(text) );
+		if( aol != null )
+			alternatives.add( new TextContent("x-aol",aol) );
+		if( html != null )
+			alternatives.add( new HtmlTextContent(html) );
+
+		setMailContents(alternatives);
+
+		String bounceTo = interp.getArgString("bounce_to");
+		if (bounceTo == null)
+			ModelHome.send(mail);
+		else
+			ModelHome.send(mail, bounceTo);
+	}
+
+	private void setMailContents(List<Content> alternatives) {
+		Content body = alternatives.size()==1 ? alternatives.get(0)
+				: new AlternativeMultipartContent(alternatives.toArray(new Content[alternatives.size()]));
+		boolean hasAttachments = attachments != null && attachments.size() > 0;
+		if( !hasAttachments ) {
+			mail.setContent(body);
+		} else {
+			List<Content> contents = new ArrayList<Content>();
+			contents.add(body);
+			for (Content c : attachments) {
+				contents.add(c);
+			}
+			Content[] contentArray = contents.toArray(new Content[contents.size()]);
+			mail.setContent(new MixedMultipartContent(contentArray));
+		}
+	}
+
+	private static InputStream buildZip(final Node node) {
+		return new FilterInputStream(null) {
+
+			@Override public int read(byte b[], int off, int len) throws IOException {
+				checkIn();
+				return super.read(b,off,len);
+			}
+
+			@Override public void close() throws IOException {
+				checkIn();
+				super.close();
+			}
+
+			private void checkIn() {
+				if( in == null ) {
+					ByteArrayOutputStream baos = new ByteArrayOutputStream();
+					ZipOutputStream zout = new ZipOutputStream(baos);
+					try {
+						zout.putNextEntry(new ZipEntry(node.getId() + ".txt"));
+						zout.write(node.getMessage().getRaw().getBytes());
+
+						FileUpload.FileDetails[] details = FileUpload.getFiles(node);
+						for (FileUpload.FileDetails d : details) {
+							String filename = d.getName();
+							InputStream is = FileUpload.getFileContent(node, filename);
+							try {
+								byte[] contents = IoUtils.readAll(is);
+								zout.putNextEntry(new ZipEntry(filename));
+								zout.write(contents);
+							} finally {
+								is.close();
+							}
+						}
+						zout.close();
+					} catch (IOException e) {
+						throw new RuntimeException(e);
+					}
+					in = new ByteArrayInputStream(baos.toByteArray());
+				}
+			}
+		};
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/ErrorNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,98 @@
+package nabble.view.web.template;
+
+import nabble.model.ModelException;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.naml.namespaces.TemplateException;
+
+
+@Namespace (
+	name = "error",
+	global = false
+)
+public final class ErrorNamespace extends BasicNamespace.ExceptionNamespace {
+
+	public ErrorNamespace(TemplateException ex) {
+		super(ex);
+	}
+
+
+	@Namespace (
+		name = "invalid_email_error",
+		global = false
+	)
+	public class InvalidEmail {
+
+		@Command public void email(IPrintWriter out,Interpreter interp) {
+			out.print(((ModelException.EmailFormat)ex).badEmail);
+		}
+	}
+
+	public static final CommandSpec invalid_email_exception = CommandSpec.DO;
+
+	@Command public void invalid_email_exception(IPrintWriter out,ScopedInterpreter<InvalidEmail> interp) {
+		if( ex instanceof ModelException.EmailFormat )
+			unnamedScopedException(out,interp,new InvalidEmail());
+	}
+
+
+	@Namespace (
+		name = "bad_text_spam_error",
+		global = false
+	)
+	public class BadSpamText {
+
+		@Command public void text(IPrintWriter out,Interpreter interp) {
+			out.print(((ModelException.SpamException)ex).badText);
+		}
+	}
+
+	public static final CommandSpec spam_exception = scopedCommandSpec;
+
+	@Command public void spam_exception(IPrintWriter out,ScopedInterpreter<BadSpamText> interp) {
+		if( ex instanceof ModelException.SpamException )
+			scopedException(out,interp,new BadSpamText());
+	}
+
+	@Namespace (
+		name = "invalid_file_error",
+		global = false
+	)
+	public class InvalidFile {
+
+		@Command public void filename(IPrintWriter out,Interpreter interp) {
+			out.print(((ModelException.InvalidFile)ex).fileName);
+		}
+	}
+
+	public static final CommandSpec invalid_file_exception = CommandSpec.DO;
+
+	@Command public void invalid_file_exception(IPrintWriter out,ScopedInterpreter<InvalidFile> interp) {
+		if( ex instanceof ModelException.InvalidFile )
+			unnamedScopedException(out,interp,new InvalidFile());
+	}
+
+	@Namespace (
+		name = "invalid_image_error",
+		global = false
+	)
+	public class InvalidImage {
+
+		@Command public void filename(IPrintWriter out,Interpreter interp) {
+			out.print(((ModelException.InvalidFile)ex).fileName);
+		}
+	}
+
+	public static final CommandSpec invalid_image_exception = CommandSpec.DO;
+
+	@Command public void invalid_image_exception(IPrintWriter out,ScopedInterpreter<InvalidImage> interp) {
+		if( ex instanceof ModelException.InvalidImage )
+			unnamedScopedException(out,interp,new InvalidImage());
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/FieldNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,57 @@
+package nabble.view.web.template;
+
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+@Namespace (
+	name = "field",
+	global = false
+)
+public class FieldNamespace {
+	private final String name;
+	private String value = null;
+
+	public FieldNamespace(String name) {
+		this.name = name;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setValue(String value) {
+		this.value = value;
+	}
+
+	public void setValue(HttpServletRequest request) {
+		this.value = request.getParameter(name);
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	@Command("name") public void _name(IPrintWriter out,Interpreter interp) {
+		out.print(name);
+	}
+
+	@Command("value") public void _value(IPrintWriter out,Interpreter interp) {
+		out.print(interp.encode(value));
+	}
+
+	public static final CommandSpec set_value = CommandSpec.NO_OUTPUT()
+		.dotParameter("value")
+		.build()
+	;
+
+	@Command public void set_value(IPrintWriter out,Interpreter interp) {
+		setValue( interp.getArgString("value") );
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/HtmlListNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,920 @@
+package nabble.view.web.template;
+
+import fschmidt.html.Html;
+import fschmidt.html.HtmlCdata;
+import fschmidt.html.HtmlScript;
+import fschmidt.html.HtmlStyle;
+import fschmidt.html.HtmlTag;
+import fschmidt.html.HtmlTextContainer;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.java.ObjectUtils;
+import nabble.model.FileUpload;
+import nabble.model.Message;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.ListSequence;
+import nabble.view.lib.Jtp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+
+@Namespace (
+	name = "html_list",
+	global = true
+)
+public final class HtmlListNamespace extends ListSequence<Object> {
+	private static final Logger logger = LoggerFactory.getLogger(HtmlListNamespace.class);
+
+	private final Message.Source source;
+	private final Message.Format format;
+
+	public HtmlListNamespace(Html list,Message.Source source, Message.Format  format) {
+		super(list);
+		this.source = source;
+		this.format = format;
+	}
+
+	public String toString() {
+		return ObjectUtils.join(elements);
+	}
+
+	public String toMailText() {
+		return htmlToTextMail2(elements);
+	}
+
+	public static final CommandSpec process_raw_tags = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_raw_tags(IPrintWriter out,Interpreter interp) {
+		processRaw(elements);
+	}
+
+	public static final CommandSpec process_cdata_tags = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_cdata_tags(IPrintWriter out,Interpreter interp) {
+		processCDATA(elements);
+	}
+
+	public static final CommandSpec process_quotes = CommandSpec.NO_OUTPUT()
+		.scopedParameters("wrote")
+		.optionalParameters("max_quoted_lines")
+		.build()
+	;
+
+	@Command public void process_quotes(IPrintWriter out,ScopedInterpreter<AuthorWroteNamespace> interp) {
+		int maxQuotedLines = interp.getArgAsInt("max_quoted_lines",10);
+		processQuotes(elements,maxQuotedLines, interp);
+	}
+
+	public static final CommandSpec process_quotes_as_text = CommandSpec.NO_OUTPUT()
+		.scopedParameters("wrote")
+		.build()
+	;
+
+	@Command public void process_quotes_as_text(IPrintWriter out,ScopedInterpreter<AuthorWroteNamespace> interp) {
+		// In order to avoid complexity, the algorithm should process
+		// just one type of line breaks. Since the desired output is TEXT,
+		// the chosen line break is CRLF. Thus we have to convert <br>
+		// into CRLF below.
+		if (format == Message.Format.HTML) {
+			convertBRIntoCRLF(elements);
+		}
+		// All lines breaks are CRLF now...
+		processQuotesText(elements, 0, interp);
+	}
+
+	private void convertBRIntoCRLF(List<Object> elements) {
+		for (int i = 0; i < elements.size(); i++) {
+			Object o = elements.get(i);
+			if (o instanceof HtmlTag && ((HtmlTag) o).getName().equals("br"))
+				elements.set(i, "\n");
+			else if (o instanceof String) {
+				elements.set(i, ((String)o).replaceAll("\r?\n", ""));
+			}
+		}
+	}
+
+	public static final CommandSpec process_email = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_email(IPrintWriter out,Interpreter interp) {
+		processEmail(elements, source);
+	}
+
+	public static final CommandSpec set_target_to_top = CommandSpec.NO_OUTPUT;
+
+	@Command public void set_target_to_top(IPrintWriter out,Interpreter interp) {
+		setTargetToTop(elements);
+	}
+
+	public static final CommandSpec add_nofollow = CommandSpec.NO_OUTPUT()
+		.optionalParameters("accept_rel_follow")
+		.build()
+	;
+
+	@Command public void add_nofollow(IPrintWriter out,Interpreter interp) {
+		boolean acceptRelFollow = interp.getArgAsBoolean("accept_rel_follow", false);
+		addNofollow(elements, acceptRelFollow);
+	}
+
+	public static final CommandSpec process_smilies = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_smilies(IPrintWriter out,Interpreter interp) {
+		processSmilies(elements,true);
+	}
+
+	public static final CommandSpec process_smilies_as_text = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_smilies_as_text(IPrintWriter out,Interpreter interp) {
+		processSmilies(elements,false);
+	}
+
+	public static final CommandSpec process_file_tags = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_file_tags(IPrintWriter out,Interpreter interp) {
+		FileUpload.processFileTags(elements,source);
+	}
+
+
+
+	// security
+
+	public static final CommandSpec disable_banned_tags = CommandSpec.NO_OUTPUT()
+		.dotParameter("banned_tags")
+		.optionalParameters("remove")
+		.build()
+	;
+
+	@Command public void disable_banned_tags(IPrintWriter out,Interpreter interp) {
+		String[] tags = splitAndTrim(interp.getArgString("banned_tags"));
+		boolean remove = interp.getArgAsBoolean("remove", format.isMail());
+		disableBannedTags(elements,remove,tags);
+	}
+
+	public static final CommandSpec disable_invalid_urls = CommandSpec.NO_OUTPUT()
+		.dotParameter("url_attributes")
+		.build()
+	;
+
+	@Command public void disable_invalid_urls(IPrintWriter out,Interpreter interp) {
+		String[] attrs = splitAndTrim(interp.getArgString("url_attributes"));
+		disableInvalidUrls(elements,false,attrs);
+	}
+
+	public static final CommandSpec disable_javascript_urls = CommandSpec.NO_OUTPUT()
+		.dotParameter("url_attributes")
+		.build()
+	;
+
+	@Command public void disable_javascript_urls(IPrintWriter out,Interpreter interp) {
+		String[] attrs = splitAndTrim(interp.getArgString("url_attributes"));
+		disableJavascriptUrls(elements,false,attrs);
+	}
+
+	public static final CommandSpec disable_on_event = CommandSpec.NO_OUTPUT;
+
+	@Command public void disable_on_event(IPrintWriter out,Interpreter interp) {
+		disableOnEvent(elements,false);
+	}
+
+	public static final CommandSpec disable_scripts = CommandSpec.NO_OUTPUT;
+
+	@Command public void disable_scripts(IPrintWriter out,Interpreter interp) {
+		disableScripts(elements,false);
+	}
+
+	public static final CommandSpec disable_style_blocks = CommandSpec.NO_OUTPUT()
+		.optionalParameters("remove")
+		.build()
+	;
+
+	@Command public void disable_style_blocks(IPrintWriter out,Interpreter interp) {
+		boolean remove = interp.getArgAsBoolean("remove", format.isMail());
+		disableStyleBlocks(elements,remove);
+	}
+
+
+	static String[] splitAndTrim(String s) {
+		final String[] a = s.split(",");
+		for( int i=0; i<a.length; i++ ) {
+			a[i] = a[i].trim();
+		}
+		return a;
+	}
+
+
+
+
+	public static final CommandSpec process_tag = CommandSpec.NO_OUTPUT()
+		.parameters("tag")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.build()
+	;
+
+	@Command public void process_tag(IPrintWriter out,ScopedInterpreter<TagNamespace> interp) {
+		String tagName = interp.getArgString("tag");
+		for( ListIterator<Object> i = elements.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				if( tag.getName().equals(tagName) ) {
+					TagNamespace ns = new TagNamespace(tag);
+					String replacement = interp.getArgString(ns,"do");
+					i.set(replacement);
+				}
+			}
+		}
+	}
+
+	public static final CommandSpec process_text = CommandSpec.NO_OUTPUT()
+		.parameters("text")
+		.dotParameter("replacement")
+		.build()
+	;
+
+	@Command public void process_text(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		String replacement = interp.getArgString("replacement");
+		for( ListIterator<Object> i = elements.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof String ) {
+				String s = (String) curr;
+				if (text.equals(s)) {
+					i.set(replacement);
+				}
+			}
+		}
+	}
+
+	@Namespace (
+		name = "html_tag",
+		global = false,
+		transparent = true
+	)
+	public static final class TagNamespace {
+		private final HtmlTag tag;
+
+		private TagNamespace(HtmlTag tag) {
+			this.tag = tag;
+		}
+
+		@Command public void tag_as_string(IPrintWriter out,Interpreter interp) {
+			out.print(tag);
+		}
+
+		public static final CommandSpec tag_attribute = new CommandSpec.Builder()
+			.dotParameter("name")
+			.build()
+		;
+
+		@Command public void tag_attribute(IPrintWriter out,Interpreter interp) {
+			out.print( HtmlTag.unquote(tag.getAttributeValue(interp.getArgString("name"))) );
+		}
+
+
+		public static final CommandSpec tag_has_attribute = new CommandSpec.Builder()
+			.dotParameter("name")
+			.build()
+		;
+
+		@Command public void tag_has_attribute(IPrintWriter out,Interpreter interp) {
+			out.print( tag.getAttributeValue(interp.getArgString("name")) != null );
+		}
+
+	}
+
+
+	public static final CommandSpec process_embed = new CommandSpec.Builder()
+		.dotParameter("regex")
+		.build()
+	;
+
+	@Command public void process_embed(IPrintWriter out,Interpreter interp) {
+		Pattern ptn = Pattern.compile( interp.getArgString("regex") );
+		for( ListIterator<Object> i = elements.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				if( tag.getName().equals("nabble_embed") ) {
+					if( tag.isEmpty() ) {  // no good
+						i.set( new Embedded(HtmlUtils.htmlEncode(tag.toString())) );
+					} else {
+						List<Object> list = new ArrayList<Object>();
+						while(true) {
+							i.remove();
+							if( !i.hasNext() ) {  // no closing tag
+								list.add(0,tag);
+								elements.add( new Embedded(HtmlUtils.htmlEncode(ObjectUtils.join(list))) );
+								return;
+							}
+							curr = i.next();
+							if( curr instanceof HtmlTag ) {
+								HtmlTag tag2 = (HtmlTag)curr;
+								if( tag2.getName().equals("/nabble_embed") )  // done
+									break;
+							}
+							list.add(curr);
+						}
+						String text = ObjectUtils.join(list);
+						if( ptn.matcher(text).matches() ) {  // ok
+							i.set( new Embedded(text) );
+						} else {  // not ok
+							list.add(0,tag);
+							list.add(curr);
+							i.set( new Embedded(HtmlUtils.htmlEncode(ObjectUtils.join(list))) );
+						}
+					}
+				}
+			}
+		}
+	}
+
+	private static class Embedded {
+		private final String text;
+
+		Embedded(String text) {
+			this.text = text;
+		}
+
+		public String toString() {
+			return text;
+		}
+
+	}
+
+
+
+	// from MessageFormatImpls
+
+
+	private static void processRaw(List<Object> list) {
+		for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) {
+			Object o = i.next();
+			if( o instanceof HtmlTextContainer ) {
+				HtmlTextContainer container = (HtmlTextContainer)o;
+				if( container.startTag.getName().equalsIgnoreCase("raw") ) {
+					HtmlTag preTag = new HtmlTag(container.startTag);
+					preTag.setName("pre");
+					i.set( new Embedded(
+						preTag.toString() + HtmlUtils.htmlEncode(container.text) + "</pre>"
+					) );
+				}
+			}
+		}
+	}
+
+	private static void processCDATA(List<Object> list) {
+		for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) {
+			Object o = i.next();
+			if (o instanceof HtmlCdata) {
+				HtmlCdata container = (HtmlCdata) o;
+				container.toString();
+				i.set("<pre>" + HtmlUtils.htmlEncode("<![CDATA[" + container.text + "]]>") + "</pre>");
+			}
+		}
+	}
+
+
+	private static final HtmlTag blockquote = new HtmlTag("blockquote");
+	private static final HtmlTag _blockquote = new HtmlTag("/blockquote");
+	private static final HtmlTag divQuote = new HtmlTag("div");
+	private static final HtmlTag divQuoteAuthor = new HtmlTag("div");
+	private static final HtmlTag divQuoteMessage = new HtmlTag("div");
+	private static final HtmlTag divQuoteMessageHidden = new HtmlTag("div");
+	private static final HtmlTag _div = new HtmlTag("/div");
+	static {
+		blockquote.setAttribute("class",HtmlTag.quote("quote dark-border-color"));
+		divQuote.setAttribute("class",HtmlTag.quote("quote light-border-color"));
+		divQuoteAuthor.setAttribute("class",HtmlTag.quote("quote-author"));
+		divQuoteAuthor.setAttribute("style",HtmlTag.quote("font-weight: bold;"));
+		divQuoteMessage.setAttribute("class",HtmlTag.quote("quote-message"));
+		divQuoteMessageHidden.setAttribute("class",HtmlTag.quote("quote-message shrinkable-quote"));
+	}
+
+
+	@Namespace (
+		name = "html_list_author_wrote",
+		global = true
+	)
+	public static class AuthorWroteNamespace {
+		private final String authorStr;
+
+		private AuthorWroteNamespace(String authorStr) {
+			this.authorStr = authorStr;
+		}
+
+		@Command public void author(IPrintWriter out,Interpreter interp) {
+			out.print(authorStr);
+		}
+	}
+
+	private static class QuoteInfo {
+		final int i;
+		final int nBreaks;
+
+		QuoteInfo(int i,int nBreaks) {
+			this.i = i;
+			this.nBreaks = nBreaks;
+		}
+	}
+
+	private static final Set<String> breakingTags = new HashSet<String>(Arrays.asList(
+		"br",
+		"div",
+		"p"
+	));
+
+	private static void processQuotes(List<Object> list, int maxQuotedLines, ScopedInterpreter<AuthorWroteNamespace> interp) {
+		processQuotes(list,maxQuotedLines,0, interp);
+	}
+
+	private static QuoteInfo processQuotes(List<Object> list,int maxQuotedLines,int start, ScopedInterpreter<AuthorWroteNamespace> interp) {
+		int nBreaks = 0;
+		int n = list.size();
+		for( int i=start; i<n; i++ ) {
+			Object o = list.get(i);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)o;
+			String tagName = tag.getName().toLowerCase();
+			if( tagName.equals("/quote") )
+				return new QuoteInfo(i,nBreaks);
+			if( tagName.equals("quote") ) {
+				QuoteInfo qi = processQuotes(list,maxQuotedLines,i+1, interp);
+				if( qi==null ) {
+					list.set(i,HtmlUtils.htmlEncode(tag.toString()));
+					return null;
+				}
+				int closeQuote = qi.i;
+				nBreaks += qi.nBreaks + 1;
+				int fromEnd = list.size() - closeQuote;
+
+				list.remove(i);
+				nBreaks -= removeBr(list,i);
+				list.add(i++,blockquote);
+				list.add(i++,divQuote);
+				list.add(i++,"\n");
+				String author = HtmlTag.unquote(tag.getAttributeValue("author"));
+				if( author != null ) {
+					list.add(i++,divQuoteAuthor);
+					list.add(i++,interp.getArgString(new AuthorWroteNamespace(author),"wrote"));
+					list.add(i++,_div);
+					list.add(i++,"\n");
+				}
+				list.add(i++, qi.nBreaks < maxQuotedLines ? divQuoteMessage : divQuoteMessageHidden );
+//				list.add(i++, divQuoteMessage );
+
+				i = list.size() - fromEnd;
+				int brs = removeBrUp(list,i-1);
+				nBreaks -= brs;
+				i -= brs;
+				list.remove(i);
+				nBreaks -= removeBr(list,i);
+				list.add(i++,_div);
+				list.add(i++,"\n");
+				list.add(i++,_div);
+				list.add(i++,_blockquote);
+				list.add(i++,"\n");
+				i--;
+
+				n = list.size();
+			}
+			else if( breakingTags.contains(tagName) ) {
+				nBreaks++;
+			}
+		}
+		return null;
+	}
+
+
+	private static final HtmlTag _a = new HtmlTag("/a");
+
+	private static void processEmail(List<Object> list,Message.Source src) {
+		int count = 0;
+		int n = list.size() - 3 + 1;
+		for( int i=0; i<n; i++ ) {
+			Object o = list.get(i);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)o;
+			if( !tag.getName().toLowerCase().equals("email") )
+				continue;
+			o = list.get(i+1);
+			if( !(o instanceof String) )
+				continue;
+			String email = (String)o;
+			o = list.get(i+2);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag endTag = (HtmlTag)o;
+			if( !endTag.getName().toLowerCase().equals("/email") )
+				continue;
+			list.remove(i);
+			list.remove(i);
+			list.remove(i);
+			HtmlTag a = new HtmlTag("a");
+			StringBuilder href = new StringBuilder();
+			href.append( "/user/SendEmail.jtp" );
+			if( src==null || src instanceof Message.TempSource) {
+				href.append( "?type=email&email=" ).append( HtmlUtils.urlEncode(email) );
+			} else {
+				if( src instanceof Node ) {
+					Node node = (Node)src;
+					href.append( "?type=node&node=" ).append( node.getId() );
+				} else if( src instanceof User ) {
+					User user = (User)src;
+					href.append( "?type=sig&user=" ).append( user.getId() );
+				} else {
+					throw new RuntimeException("src="+src);
+				}
+				href.append( "&i=" ).append( count++ );
+			}
+			a.setAttribute("href",HtmlTag.quote(href.toString()));
+			a.setAttribute("target","\"_top\"");
+			a.setAttribute("rel","\"nofollow\"");
+			list.add(i++,a);
+			list.add(i++,ModelHome.hideEmail(email));
+			list.add(i,_a);
+		}
+	}
+
+
+	private static void setTargetToTop(List<Object> list) {
+		for( Object o : list ) {
+			if( o instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)o;
+				if( tag.getName().toLowerCase().equals("a") ) {
+					if( tag.getAttributeValue("target") == null ) {
+						tag.setAttribute("target","\"_top\"");
+					}
+				}
+			}
+		}
+	}
+
+	private static void addNofollow(List<Object> list, boolean acceptRelFollow) {
+		for( Object o : list ) {
+			if( o instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)o;
+				String tagName = tag.getName().toLowerCase();
+				if (tagName.equals("a")) {
+					if (acceptRelFollow && "\"follow\"".equals(tag.getAttributeValue("rel")))
+						continue;
+					tag.setAttribute("rel","\"nofollow\"");
+					tag.setAttribute("link","\"external\"");
+				}
+			}
+		}
+	}
+
+
+
+	// from HtmlSecurity
+
+	private static void disable(ListIterator<Object> i,Object curr,boolean removeViolation) {
+		if( removeViolation ) {
+			i.remove();
+		} else {
+			i.set(HtmlUtils.htmlEncode(curr.toString()));
+		}
+	}
+
+	public static void disableBannedTags(List<Object> html,boolean removeViolation,String... tagNames) {
+		Set<String> tagNameSet = new HashSet<String>();
+		for( String tagName : tagNames ) {
+			tagNameSet.add(tagName);
+			tagNameSet.add("/"+tagName);
+		}
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				if( tagNameSet.contains(tag.getName().toLowerCase()) ) {
+					disable(i,tag,removeViolation);
+				}
+			}
+		}
+	}
+
+	private static final URL baseUrl;
+	static {
+		try {
+			baseUrl = new URL("https://www.nabble.com/");  // any valid URL is fine here
+		} catch(MalformedURLException e) {
+			logger.error("",e);
+			System.exit(-1);
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static void disableInvalidUrls(List<Object> html,boolean removeViolation,String... parameters) {
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				for (String attrName: parameters) {
+					String val = HtmlTag.unquote(tag.getAttributeValue(attrName));
+					if (val != null) {
+						try {
+							new URL(baseUrl,val).toURI();
+						} catch(MalformedURLException e) {
+							disable(i,tag,removeViolation);
+							break;
+						} catch(URISyntaxException e) {
+							disable(i,tag,removeViolation);
+							break;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	private static void disableJavascriptUrls(List<Object> html,boolean removeViolation,String... parameters) {
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				for (String attrName: parameters) {
+					String val = HtmlTag.unquote(tag.getAttributeValue(attrName));
+					if (val != null && val.toLowerCase().startsWith("javascript:")) {
+						disable(i,tag,removeViolation);
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	private static void disableOnEvent(List<Object> html,boolean removeViolation) {
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				for( String attrName : tag.getAttributeNames() ) {
+					if (HtmlTag.unquote(attrName).toLowerCase().startsWith("on")) {
+						disable(i,tag,removeViolation);
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	private static final Pattern urchin = Pattern.compile("\\s*_uacct\\s*=\\s*[\"'][^\"']+[\"']\\s*;\\s*urchinTracker\\(\\);\\s*",Pattern.MULTILINE);
+
+	private static void disableScripts(List<Object> html,boolean removeViolation) {
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlScript ) {
+				HtmlScript script = (HtmlScript)curr;
+				String src = HtmlTag.unquote(script.startTag.getAttributeValue("src"));
+				if( src != null ) {
+					if( script.text.trim().length()==0 ) {
+						if( src.equals("http://www.google-analytics.com/urchin.js") )
+							continue;
+					}
+				} else {
+					if( urchin.matcher(script.text).matches() )
+						continue;
+				}
+				disable(i,script,removeViolation);
+			}
+		}
+	}
+
+	private static void disableStyleBlocks(List<Object> html,boolean removeViolation) {
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlStyle ) {
+				disable(i,curr,removeViolation);
+			}
+		}
+	}
+
+
+	private static String htmlToTextMail2(List<Object> html) {
+	    StringBuilder buf = new StringBuilder();
+	    for (int i = 0; i < html.size(); i++) {
+			Object next = html.get(i);
+	        if( next instanceof String ) {
+	            buf.append(next);
+	        } else if( next instanceof HtmlTag) {
+	        	HtmlTag tag = (HtmlTag)next;
+				String tagName = tag.getName().toLowerCase();
+	        	String separator = Message.htmlSeparators.get(tagName);
+	        	if (separator!=null && buf.length()>0 && buf.charAt(buf.length()-1)!='\n')
+	        		buf.append(separator);
+	        	if (tagName.equals("img")) {
+	        		String src = tag.getAttributeValue("src");
+	        		if (src!=null) {
+	        			buf.append('<');
+	        			buf.append(HtmlTag.unquote(src));
+	        			buf.append("> ");
+	        		}
+	        	}
+	        	else if (tagName.equals("a")) {
+	        		String src = tag.getAttributeValue("href");
+	        		if (src!=null) {
+						String anchorText = html.get(i+1).toString();
+						buf.append(anchorText.trim());
+						buf.append(" <");
+	        			buf.append(HtmlTag.unquote(src));
+	        			buf.append("> ");
+						i++;
+	        		}
+	        	}
+	        }
+	    }
+	    Message.wrapQuoteText(buf);
+	    return buf.toString();
+	}
+
+
+	private static int processQuotesText(List<Object> list, int start, ScopedInterpreter<AuthorWroteNamespace> interp) {
+		int n = list.size();
+		for( int i=start; i<n; i++ ) {
+			Object o = list.get(i);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)o;
+			String tagName = tag.getName().toLowerCase();
+			if( tagName.equals("/quote") )
+				return i;
+			if( tagName.equals("quote") ) {
+				int closeQuote = processQuotesText(list, i+1, interp);
+				if( closeQuote == -1 )
+					return -1;
+				int fromEnd = list.size() - closeQuote;
+
+				list.remove(i);
+				removeCRLF(list, i, 0);
+				if (i > 0)
+					list.add(i++,"\n");
+
+				String author = HtmlTag.unquote(tag.getAttributeValue("author"));
+				if( author != null ) {
+					list.add(i++,interp.getArgString(new AuthorWroteNamespace(author),"wrote"));
+				}
+				int begin = i;
+				i = list.size() - fromEnd;
+				removeCRLFUp(list, i-1, -1);
+				list.remove(i);
+				removeCRLF(list, i, 0);
+				for (int j=begin;j<i;j++)
+					quoteText(list,j);
+
+				list.add(i, "\n");
+				n = list.size();
+			}
+		}
+		return -1;
+	}
+
+	private static void quoteText(List<Object> list,int i) {
+		Object obj = list.get(i);
+		if( !(obj instanceof String) )
+			return;
+		String s = (String) obj;
+		if (s.length() == 0)
+			return;
+
+		removeCRLFUp(list, i-1, -1);
+		s = CRLF_UP_PTN.matcher(s).replaceAll("");
+
+		if (!s.startsWith("\n") && !s.startsWith("\r\n") )
+			s = "\n" + s;
+
+		s = s.replaceAll("\n", "\n> ");
+		s = s.replaceAll("\n> >", "\n>>");
+
+		list.set(i, s + '\n');
+		removeCRLF(list, i+1, +1);
+	}
+
+	private static final Pattern CRLF_PTN = Pattern.compile("^(\\r?\\n){1}");
+	private static final Pattern CRLF_UP_PTN = Pattern.compile("(\\r?\\n){1}$");
+	private static final Pattern BR_PTN = Pattern.compile("^(\\s*<br/?>){1,2}");
+	private static final Pattern BR_UP_PTN = Pattern.compile("(<br/?>\\s*){1,2}$");
+
+	private static void removeCRLF(List<Object> list, int i, int increment) {
+		Object obj = i >= 0 && i < list.size()? list.get(i) : null;
+		if (obj instanceof String) {
+			String s = (String) obj;
+			while (s.length() == 0 && increment != 0) {
+				i += increment;
+				obj = i >= 0 && i < list.size()? list.get(i) : null;
+				if (obj == null || !(obj instanceof String))
+					return;
+				s = (String) obj;
+			}
+			s = CRLF_PTN.matcher(s).replaceFirst("");
+			list.set(i, s);
+		}
+	}
+
+	private static void removeCRLFUp(List<Object> list, int i, int increment) {
+		Object obj = i >= 0 && i < list.size()? list.get(i) : null;
+		if (obj instanceof String) {
+			String s = (String) obj;
+			while (s.length() == 0 && increment != 0) {
+				i += increment;
+				obj = i >= 0 && i < list.size()? list.get(i) : null;
+				if (obj == null || !(obj instanceof String))
+					return;
+				s = (String) obj;
+			}
+			s = CRLF_UP_PTN.matcher(s).replaceFirst("");
+			list.set(i, s);
+		}
+	}
+
+	private static int removeBr(List<Object> list,int i) {
+		return removeBr(list,i,0);
+	}
+
+	private static int removeBr(List<Object> list,int i,int count) {
+		if( count==2 )
+			return count;
+		if( i >= list.size() )
+			return count;
+		Object obj = list.get(i);
+		if( obj instanceof String ) {
+			String s = (String)obj;
+			s = BR_PTN.matcher(s).replaceAll("");
+			list.set(i,s);
+		} else if( obj instanceof HtmlTag ) {
+			HtmlTag tag = (HtmlTag)obj;
+			if( tag.getName().equalsIgnoreCase("br") ) {
+				list.remove(i);
+				return removeBr(list,i,count+1);
+			}
+		}
+		return count;
+	}
+
+	private static int removeBrUp(List<Object> list,int i) {
+		return removeBrUp(list,i,0);
+	}
+
+	private static int removeBrUp(List<Object> list,int i,int count) {
+		if( count==2 )
+			return count;
+		if( i < 0 )
+			return count;
+		Object obj = list.get(i);
+		if( obj instanceof String ) {
+			String s = (String)obj;
+			s = BR_UP_PTN.matcher(s).replaceAll("");
+			list.set(i,s);
+		} else if( obj instanceof HtmlTag ) {
+			HtmlTag tag = (HtmlTag)obj;
+			if( tag.getName().equalsIgnoreCase("br") ) {
+				list.remove(i);
+				return removeBrUp(list,i-1,count+1);
+			}
+		}
+		return count;
+	}
+
+
+//	private static final String smiliesDir = "http://" + Jtp.getDefaultHost() + "/images/smiley/";
+	private static final String smiliesDir = "/images/smiley/";
+
+	private static void processSmilies(List<Object> list,boolean isHtml) {
+		for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) {
+			Object o = i.next();
+			if( o instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)o;
+				if( tag.getName().toLowerCase().equals("smiley") ) {
+					if( isHtml ) {
+						String s = HtmlTag.unquote(tag.getAttributeValue("image"));
+						if( s != null ) {
+							HtmlTag img = new HtmlTag("img class='smiley' src='"+smiliesDir+s+"' /");
+							i.set(img);
+						}
+					}
+				}
+			}
+		}
+	}
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/HtmlNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,94 @@
+package nabble.view.web.template;
+
+import fschmidt.util.java.ObjectUtils;
+import nabble.view.lib.Recaptcha;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.Encoder;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+
+@Namespace (
+	name = "html",
+	global = true
+)
+public final class HtmlNamespace {
+	private static final Logger logger = LoggerFactory.getLogger(HtmlNamespace.class);
+
+	private final Set<String> headSet = new LinkedHashSet<String>();
+	private final String body;
+
+	HtmlNamespace(ScopedInterpreter<HtmlNamespace> interp) {
+		interp.setEncoder(Encoder.HTML);
+		this.body = interp.getArgString(this,"do");
+	}
+
+	@Command public void html_head_content(IPrintWriter out,Interpreter interp) {
+		String head = ObjectUtils.join(headSet);
+		out.print(head);
+	}
+
+	@Command public void html_body_content(IPrintWriter out,Interpreter interp) {
+		out.print(body);
+	}
+
+	public static final CommandSpec put_in_head = CommandSpec.NO_OUTPUT()
+		.dotParameter("in_head")
+		.build()
+	;
+
+	@Command public void put_in_head(IPrintWriter out,Interpreter interp) {
+		String s = interp.getArgString("in_head");
+		if( s == null )
+			throw new NullPointerException();
+		headSet.add(s);
+	}
+
+	public static final CommandSpec style = new CommandSpec.Builder()
+		.parameters("property")
+		.dotParameter("value")
+		.optionalParameters("value")
+		.build()
+	;
+
+	@Command public static void style(IPrintWriter out,Interpreter interp) {
+		String property = interp.getArgString("property");
+		String value = interp.getArgString("value");
+		if( value != null ) {
+			out.print( property + ':' + value );
+		}
+	}
+
+	public static final CommandSpec calendar = CalendarWidget._calendar;
+
+	@Command public void calendar(IPrintWriter out,Interpreter interp) {
+		CalendarWidget._calendar(out, interp);
+	}
+
+	@Command public void page_template(IPrintWriter out,Interpreter interp) {
+		out.print(interp.template().name());
+	}
+
+	@Command public void page_template_command_id(IPrintWriter out,Interpreter interp) {
+		out.print(interp.template().macro.getId());
+	}
+
+	@Command public void page_base_classes(IPrintWriter out,Interpreter interp) {
+		out.print(ServletNamespace.class.getName());
+	}
+
+	@Command public void captcha_div(IPrintWriter out, Interpreter interp) {
+		headSet.add( Recaptcha.JS );
+		out.print( Recaptcha.DIV );
+	}
+
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/InstantMailNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,54 @@
+package nabble.view.web.template;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import nabble.model.Node;
+import nabble.model.Subscription;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+
+
+@Namespace (
+	name = "instant_mail",
+	global = true
+)
+public final class InstantMailNamespace {
+	private final Node node;
+	private final Map<User,Subscription> subscriptionMap;
+
+	public InstantMailNamespace(Node node,Map<User,Subscription> subscriptionMap) {
+		this.node = node;
+		this.subscriptionMap = subscriptionMap;
+	}
+
+	public static final CommandSpec new_node = CommandSpec.DO;
+
+	@Command public void new_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		NodeNamespace nodeNs = new NodeNamespace(node);
+		out.print( interp.getArg(nodeNs,"do") );
+	}
+
+	public static final CommandSpec remove_from_instant_mail = CommandSpec.NO_OUTPUT()
+		.dotParameter("user")
+		.build()
+	;
+
+	@Command public void remove_from_instant_mail(IPrintWriter out,Interpreter interp) {
+		UserNamespace ns = interp.getArgAsNamespace(UserNamespace.class,"user");
+		subscriptionMap.remove(ns.user());
+	}
+
+	public static final CommandSpec subscription_list = CommandSpec.DO;
+
+	@Command public void subscription_list(IPrintWriter out,ScopedInterpreter<SubscriptionNamespace.SubscriptionList> interp) {
+		List<Subscription> list = new ArrayList<Subscription>(subscriptionMap.values());
+		out.print( interp.getArg(new SubscriptionNamespace.SubscriptionList(list),"do") );
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/MacroEditorNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,240 @@
+package nabble.view.web.template;
+
+import fschmidt.util.java.HtmlUtils;
+import nabble.model.Site;
+import nabble.modules.ModuleManager;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Macro;
+import nabble.naml.compiler.Meaning;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.Source;
+import nabble.naml.compiler.Usage;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+@Namespace(
+	name = "macro_editor",
+	global = false
+)
+public class MacroEditorNamespace {
+
+	private Site site;
+	private String rootMacroName;
+	private String[] baseClassArray;
+	private final Macro macro;
+
+	private static class SaveResults {
+		String error;
+		String macroId;
+		String base;
+		String breadcrumbs;
+	}
+
+	public MacroEditorNamespace(Site site, String meaningId, String base, String breadcrumbs) {
+		this.site = site;
+		this.baseClassArray = MacroSourceNamespace.buildBaseClassArray(base);
+		this.rootMacroName = getRootMacroName(site.getProgram(), meaningId, base, breadcrumbs);
+		if (meaningId == null) {
+			macro = null;
+		} else {
+			Meaning meaning = site.getProgram().getMeaning(meaningId);
+			if (meaning == null)
+				throw new NullPointerException("Meaning is null for: " + meaningId);
+			else if (!(meaning instanceof Macro))
+				throw new NullPointerException("Meaning is not a macro: " + meaningId);
+			macro = (Macro) meaning;
+		}
+	}
+
+	private String getRootMacroName(Program program, String meaningId, String base, String breadcrumbs) {
+		if (breadcrumbs != null) {
+			String firstMeaningId = breadcrumbs.split("-")[0];
+			Meaning meaning = program.getMeaning(firstMeaningId);
+			return meaning.getName();
+		} else if (base != null && meaningId != null) {
+			return program.getMeaning(meaningId).getName();
+		}
+		return null;
+	}
+
+	public static final CommandSpec CONTENTS = new CommandSpec.Builder()
+		.parameters("contents")
+		.build();
+
+	public static final CommandSpec save = CONTENTS;
+
+	@Command public void save(IPrintWriter out, Interpreter interp) {
+		String tweak = getContents(interp);
+
+		Map<String, String> originalTweaks = site.getCustomTweaks();
+		Map<String, String> newTweaks = new HashMap<String, String>(originalTweaks);
+		if (isEditingCustomTweak()) {
+			String macroBody = macro.element.toString();
+			for( Map.Entry<String,String> entry : originalTweaks.entrySet() ) {
+				String filename = entry.getKey();
+				String content = entry.getValue();
+				int posStart = content.indexOf(macroBody);
+				if (posStart >= 0) {
+					SaveResults saveResults = saveTweak(tweak, originalTweaks, newTweaks, filename);
+					out.print(HtmlUtils.toJson(saveResults));
+					return;
+				}
+			}
+		} else {
+			String macroName = getMacroName(macro, tweak);
+			if (macroName == null) {
+				out.print("Error: You must specify the name of the macro.");
+				return;
+			}
+			// tries to find a file with the name of the macro
+			String file = newTweaks.get(macroName);
+			String newFileContents = file == null? tweak : file + "\n\n" + tweak;
+
+			SaveResults saveResults = saveTweak(newFileContents, originalTweaks, newTweaks, macroName);
+			out.print(HtmlUtils.toJson(saveResults));
+		}
+	}
+
+	private SaveResults saveTweak(String tweak, Map<String, String> originalTweaks, Map<String, String> newTweaks, String filename) {
+		SaveResults saveResults =  new SaveResults();
+		try {
+			Usage usage = saveAndCheck(newTweaks, filename, tweak);
+			saveResults.macroId = getMacroId(filename, tweak);
+			// If usage is not null, then it was found while saving the tweak.
+			// We send this information back to the page so that the javascript
+			// can send the user to a page with navigation links.
+			if (usage != null) {
+				saveResults.base = MacroSourceNamespace.asBaseParam(usage.baseIds());
+				saveResults.breadcrumbs = MacroSourceNamespace.asBreadcrumbsParam(usage.macroPath()) ;
+			}
+		} catch(CompileException e) {
+			site.setCustomTweaks(originalTweaks);
+			saveResults.error = "Error: " + e.getMessage();
+		}
+		return saveResults;
+	}
+
+	private String getMacroName(Macro macro, String tweak) {
+		if (macro != null)
+			return macro.getName();
+		else {
+			Pattern pattern = Pattern.compile("name=\"([^\"]+)\"");
+			Matcher matcher = pattern.matcher(tweak);
+			if (matcher.find())
+				return matcher.group(1);
+			else
+				return null;
+		}
+	}
+
+	private Usage saveAndCheck(Map<String, String> newTweaks, String filename, String newFileContents)
+		throws CompileException
+	{
+		newFileContents = trim(newFileContents);
+		newTweaks.put(filename, newFileContents);
+
+		boolean isCompiledMacro = macro != null && site.getProgram().isCompiled(macro);
+
+		site.setCustomTweaks(newTweaks);
+		if (rootMacroName != null) {
+			try {
+				// Tries to compile the macro
+				site.getProgram().getTemplate(rootMacroName, this.baseClassArray);
+			} catch (CompileException e) {
+				if (isCompiledMacro)
+					throw e;
+				else {
+					Program program = site.getProgram();
+					// Let's try to find a usage for this macro for a better check
+					if (!NabbleNamespace.isCompiledAll(program))
+						CompileTest.compileAll(program);
+
+					Meaning m = program.getMeaning(macro.getId());
+					Set<Usage> usages = program.getUsages(m);
+					if (usages == null || usages.size() == 0)
+						throw e;
+					else {
+						Usage usage = usages.iterator().next();
+						program.getTemplate(usage.macroPath().get(0).getName(), usage.baseIds());
+						return usage;
+					}
+				}
+			}
+		} else {
+			// Simple XML validation
+			site.getProgram().getTemplate("do_nothing");
+		}
+		return null;
+	}
+
+	private String getMacroId(String filename, String macroBody) throws CompileException {
+		Program program = site.getProgram();
+		if (macro != null && program.getMeaning(macro.getId()) != null)
+			return macro.getId();
+		Macro m = program.getMacroWhichOverrides(macro);
+		if (m != null)
+			return m.getId();
+		List<Source> sources = site.getProgram().getSources();
+		for (Source s : sources) {
+			if (s.id.contains(':'+filename)) {
+				for (Macro mac : s.getMacros()) {
+					if (macroBody.contains(mac.element.toString()))
+						return mac.getId();
+				}
+			}
+		}
+		throw new RuntimeException("macroId not found");
+	}
+
+	public static final CommandSpec revert = CommandSpec.NO_OUTPUT;
+
+	@Command public void revert(IPrintWriter out,Interpreter interp) {
+		if (isEditingCustomTweak()) {
+			String macroBody = macro.element.toString();
+			Map<String, String> tweaks = site.getCustomTweaks();
+			Map<String, String> reverted = new HashMap<String, String>(tweaks);
+			for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
+				String name = entry.getKey();
+				String content = entry.getValue();
+				int posStart = content.indexOf(macroBody);
+				if (posStart >= 0) {
+					int posEnd = posStart + macroBody.length();
+					String revertedContents = content.substring(0, posStart) + "\n" + content.substring(posEnd);
+					if (trim(revertedContents).length() == 0)
+						reverted.remove(name);
+					else
+						reverted.put(name, revertedContents);
+					site.setCustomTweaks(reverted);
+					break;
+				}
+			}
+		} else
+			throw new RuntimeException("Not a tweaked macro");
+	}
+
+	private String getContents(Interpreter interp) {
+		String c = interp.getArgString("contents");
+		c = c.replaceAll("\\u2002", " "); // Converts any &ensp; into simple space (copy and paste issue)
+		return c;
+	}
+
+	private String trim(String s) {
+		return s.replaceAll("(^\\s+)|(\\s+$)","");
+	}
+
+	private boolean isEditingCustomTweak() {
+		return macro != null && ModuleManager.isCustomTweak(macro.source);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/MacroSourceNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,703 @@
+package nabble.view.web.template;
+
+import nabble.model.Site;
+import nabble.modules.ModuleManager;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.JavaCommand;
+import nabble.naml.compiler.Macro;
+import nabble.naml.compiler.Meaning;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ParamMeaning;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.compiler.Source;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.Usage;
+import nabble.naml.dom.Attribute;
+import nabble.naml.dom.Container;
+import nabble.naml.dom.Element;
+import nabble.naml.dom.ElementName;
+import nabble.naml.dom.EmptyElement;
+import nabble.naml.dom.Naml;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.naml.namespaces.CommandDoc;
+import nabble.naml.namespaces.ListSequence;
+import nabble.naml.namespaces.StringList;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+
+@Namespace(
+	name = "macro_source",
+	global = true
+)
+public class MacroSourceNamespace extends ServletNamespaceUtils {
+
+	private Site site;
+	private String[] baseClassArray;
+	private String baseClasses;
+	private String nextBreadcrumbs;
+	private String[] navigationBreadcrumbs;
+	private Meaning meaning;
+	private String macroOverriddenId;
+	private String macroWhichOverridesId;
+
+	MacroSourceNamespace(Site site, String id, String base, String breadcrumbs) {
+		this.site = site;
+		Program program = site.getProgram();
+		meaning = program.getMeaning(id);
+		if( meaning == null )
+			throw new NullPointerException("meaning is null for: "+id);
+
+		this.baseClasses = base;
+		this.baseClassArray = buildBaseClassArray(base);
+		this.nextBreadcrumbs = buildNextBreadcrumbs(id, breadcrumbs);
+
+		boolean hasBaseClasses = base != null && base.length() > 0;
+		if (hasBaseClasses) {
+			try {
+				Meaning root = program.getMeaning(navigationBreadcrumbs[0]);
+				program.getTemplate(root.getName(), this.baseClassArray);
+			} catch (CompileException e) {
+				throw new RuntimeException(e);
+			}
+		}
+
+		if (meaning instanceof Macro) {
+			Macro macro = (Macro) meaning;
+			Macro overridden = program.getMacroOverriddenBy(macro);
+			this.macroOverriddenId = overridden != null? overridden.getId() : null;
+			Macro overrides = program.getMacroWhichOverrides(macro);
+			this.macroWhichOverridesId = overrides != null? overrides.getId() : null;
+		}
+	}
+
+	static String[] buildBaseClassArray(String base) {
+		boolean hasBaseClasses = base != null && base.length() > 0;
+		String[] baseArray = hasBaseClasses? base.split("-") : new String[0];
+
+		String basicNamespace = nabble.naml.namespaces.BasicNamespace.class.getName();
+		String nabbleNamespace = nabble.view.web.template.NabbleNamespace.class.getName();
+
+		if (baseArray.length >=2 && baseArray[0].equals(basicNamespace) && baseArray[1].equals(nabbleNamespace)) {
+			return baseArray;
+		} else {
+			String[] array = new String[baseArray.length+2];
+			array[0] = basicNamespace;
+			array[1] = nabbleNamespace;
+
+			int i = 2;
+			for (String className : baseArray) {
+				array[i++] = className;
+			}
+			return array;
+		}
+	}
+
+	private String buildNextBreadcrumbs(String id, String breadcrumbs) {
+		if (breadcrumbs == null) {
+			navigationBreadcrumbs = new String[] { id };
+			return id;
+		} else {
+			String[] breadcrumbParts = breadcrumbs.split("-");
+			navigationBreadcrumbs = new String[breadcrumbParts.length+1];
+			int i = 0;
+			for (String s : breadcrumbParts) {
+				navigationBreadcrumbs[i++] = s;
+			}
+			navigationBreadcrumbs[i] = meaning.getId();
+			return breadcrumbs + '-' + navigationBreadcrumbs[i];
+		}
+	}
+
+	@Command public void id(IPrintWriter out,Interpreter interp) {
+		out.print(meaning.getId());
+	}
+
+	@Command public void name(IPrintWriter out,Interpreter interp) {
+		out.print(meaning.getName());
+	}
+
+	@Command public void macro_overridden_id(IPrintWriter out,Interpreter interp) {
+		out.print(macroOverriddenId);
+	}
+
+	@Command public void macro_which_overrides_id(IPrintWriter out,Interpreter interp) {
+		out.print(macroWhichOverridesId);
+	}
+
+	@Command public void is_compiled(IPrintWriter out,Interpreter interp) {
+		out.print(meaning instanceof Macro && site.getProgram().isCompiled(meaning));
+	}
+
+	public static final CommandSpec documentation = requiresServletNamespace;
+
+	@Command public void documentation(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		String name = meaning.getName();
+		Collection<String> namespaces = meaning.getRequiredNamespaces();
+		Site site = NabbleNamespace.current().site();
+		Template template = site.getTemplate(DocNamespace.getDocName(name,namespaces), BasicNamespace.class, NabbleNamespace.class, DocNamespace.class);
+		if( template == null && !namespaces.isEmpty() )
+			template = site.getTemplate(DocNamespace.getDocName(name,Collections.<String>emptySet()), BasicNamespace.class, NabbleNamespace.class, DocNamespace.class);
+		if( template == null ) {
+			if( meaning instanceof JavaCommand ) {
+				JavaCommand jc = (JavaCommand)meaning;
+				CommandDoc doc = jc.getMethod().getAnnotation(CommandDoc.class);
+
+				String value = doc == null? "" : doc.value();
+				String[] params = doc == null? new String[0] : doc.params();
+				String[] seeAlso = doc == null? new String[0] : doc.seeAlso();
+
+				template = site.getTemplate("binary doc", BasicNamespace.class, NabbleNamespace.class, DocNamespace.BinaryDocNamespace.class);
+				if( template != null ) {
+					DocNamespace.BinaryDocNamespace ns = new DocNamespace.BinaryDocNamespace(site, jc, value, params, seeAlso);
+					template.run(out,Collections.<String,Object>emptyMap(),new BasicNamespace(template), new NabbleNamespace(site), ns);
+					return;
+				}
+			}
+			template = site.getTemplate("doc not found",BasicNamespace.class,NabbleNamespace.class,DocNamespace.class);
+		}
+		DocNamespace ns = new DocNamespace();
+		template.run(out,Collections.<String,Object>emptyMap(), new BasicNamespace(template), new NabbleNamespace(site), ns);
+	}
+
+	public static final CommandSpec navigation_breadcrumbs = CommandSpec.DO;
+
+	@Command public void navigation_breadcrumbs(IPrintWriter out, ScopedInterpreter<Parts> interp) {
+		List<CommandInfo> infos = new ArrayList<CommandInfo>();
+		Program program = site.getProgram();
+		StringBuilder breadCrumbs = new StringBuilder();
+		for (String id : navigationBreadcrumbs) {
+			Meaning m = program.getMeaning(id);
+			// Skip broken meanings in the breadcrumbs
+			if (m != null)
+				infos.add(new CommandInfo(m,baseClasses,breadCrumbs.length() == 0? null : breadCrumbs.toString()));
+			if( breadCrumbs.length() > 0 )
+				breadCrumbs.append('-');
+			breadCrumbs.append(id);
+		}
+		out.print(interp.getArg(new Parts(infos),"do"));
+	}
+
+	@Command public void source(IPrintWriter out, Interpreter interp) {
+		out.print(getSource(meaning));
+	}
+
+	@Command public void source_path(IPrintWriter out, Interpreter interp) {
+		String path = null;
+		if (meaning instanceof Macro) {
+			path = "/template/NamlEditor$ViewFile.jtp?file="+ ((Macro) meaning).source.toString();
+		}
+		out.print(path);
+	}
+
+	private static String getSource(Meaning m) {
+		if (m instanceof Macro) {
+			String source = ((Macro) m).source.toString();
+			return source.endsWith(".naml")? source : source + ".naml";
+		}
+		return null;
+	}
+
+	@Command public void has_macro_overridden(IPrintWriter out,Interpreter interp) {
+		out.print(macroOverriddenId != null);
+	}
+
+	@Command public void has_macro_which_overrides(IPrintWriter out,Interpreter interp) {
+		out.print(macroWhichOverridesId != null);
+	}
+
+	@Command public void is_override(IPrintWriter out,Interpreter interp) {
+		out.print(isOverride(meaning));
+	}
+
+	private static boolean isOverride(Meaning meaning) {
+		if( !(meaning instanceof Macro) )
+			return false;
+		return ((Macro) meaning).isOverride();
+	}
+
+	@Command public void is_custom_tweak(IPrintWriter out,Interpreter interp) {
+		out.print(isCustomTweak(meaning));
+	}
+
+	@Command public void is_configuration_tweak(IPrintWriter out,Interpreter interp) {
+		out.print(isConfigurationTweak(meaning));
+	}
+
+	private static boolean isCustomTweak(Meaning meaning) {
+		if( !(meaning instanceof Macro) )
+			return false;
+		Source source = ((Macro) meaning).source;
+		return ModuleManager.isCustomTweak(source);
+	}
+
+	private static boolean isConfigurationTweak(Meaning meaning) {
+		if( !(meaning instanceof Macro) )
+			return false;
+		Source source = ((Macro) meaning).source;
+		return ModuleManager.isConfigurationTweak(source);
+	}
+
+	@Command public void is_binary(IPrintWriter out,Interpreter interp) {
+		out.print(!(meaning instanceof Macro));
+	}
+
+	@Command public void tweak_file_contents(IPrintWriter out,Interpreter interp) {
+		String file = null;
+		if (meaning instanceof Macro) {
+			Macro macro = (Macro) meaning;
+			if (isCustomTweak(meaning)) {
+				file = macro.source.content;
+			} else {
+				file = macro.element.toString();
+				file = file.replace("<macro ", "<override_macro ");
+				file = file.replace("</macro", "</override_macro");
+				file = file.replace("<subroutine ", "<override_subroutine ");
+				file = file.replace("</subroutine", "</override_subroutine");
+				file = file.replace("<translation ", "<override_translation ");
+				file = file.replace("</translation", "</override_translation");
+			}
+		}
+		out.print(file);
+	}
+
+	@Command public void macro_opening_tag(IPrintWriter out,Interpreter interp) {
+		String tag = null;
+		if (meaning instanceof Macro) {
+			Macro macro = (Macro) meaning;
+			tag = macro.element.openingTag();
+			if (!isCustomTweak(meaning)) {
+				tag = tag.replace("<macro ", "<override_macro ");
+				tag = tag.replace("<subroutine ", "<override_subroutine ");
+				tag = tag.replace("<translation ", "<override_translation ");
+			}
+		}
+		out.print(tag);
+	}
+
+	public static final CommandSpec rows = CommandSpec.DO;
+
+	@Command public void rows(IPrintWriter out,ScopedInterpreter<Rows> interp)
+		throws IOException, ServletException
+	{
+		if (meaning instanceof Macro) {
+			Macro m = (Macro) meaning;
+			List<CommandInfo> infos = new ArrayList<CommandInfo>();
+			List<Parts> rows = new ArrayList<Parts>();
+			buildElement(m.element, infos, rows, false);
+			if (infos.size() > 0)
+				rows.add(new Parts(infos));
+
+			Object block = interp.getArg(new Rows(rows, m),"do");
+			out.print(block);
+		}
+	}
+
+	private void traverse(Naml naml, List<CommandInfo> parts, List<Parts> rows, boolean isAttribute) {
+		for (Object o : naml) {
+			if (o instanceof EmptyElement) {
+				buildEmptyElement((EmptyElement) o, parts, rows, isAttribute);
+			} else if (o instanceof Container) {
+				buildElement((Container) o, parts, rows, isAttribute);
+			} else {
+				processString(o.toString(), parts, rows);
+			}
+		}
+	}
+
+	private void buildEmptyElement(EmptyElement e, List<CommandInfo> parts, List<Parts> rows, boolean isAttribute) {
+		startTag(parts, isAttribute);
+		extractParts(e, parts);
+		extractAttributes(e, parts, rows);
+		finishTag(e, parts, rows, isAttribute);
+	}
+
+	private void buildElement(Element e, List<CommandInfo> parts, List<Parts> rows, boolean isAttribute) {
+		startTag(parts, isAttribute);
+		extractParts(e, parts);
+
+		if (e.name().endsWithDot())
+			parts.add(new CommandInfo("."));
+
+		extractAttributes(e, parts, rows);
+		finishTag(e, parts, rows, isAttribute);
+		if (e instanceof Container) {
+			Container container = (Container) e;
+			traverse(container.contents(), parts, rows, isAttribute);
+			closingTag(container, isAttribute, parts);
+		}
+	}
+
+	private void closingTag(Container e, boolean isAttribute, List<CommandInfo> parts) {
+		String closingTag = e
+			.closingTag()
+			.replace("<",isAttribute?"[":"&lt;")
+			.replace(">",isAttribute?"]":"&gt;");
+		parts.add(new CommandInfo(closingTag));
+	}
+
+	private void finishTag(Element e, List<CommandInfo> infos, List<Parts> rows, boolean isAttribute) {
+		processString(e.spaceAtEndOfOpeningTag(), infos, rows);
+		infos.add(new CommandInfo(
+			(e instanceof EmptyElement? '/': "") +
+	        (isAttribute? "]":"&gt;")
+		));
+	}
+
+	private void startTag(List<CommandInfo> parts, boolean isAttribute) {
+		parts.add(new CommandInfo(isAttribute?"[":"&lt;"));
+	}
+
+	private void extractAttributes(Element e, List<CommandInfo> parts, List<Parts> rows) {
+		for (Attribute attribute : e.attributes()) {
+			processString(attribute.spaceBeforeName(), parts, rows);
+			parts.add(new CommandInfo(
+				attribute.name() +
+				attribute.spaceAfterName() +
+				'=' +
+				attribute.quote())
+			);
+			traverse(attribute.value(), parts, rows, true);
+			parts.add(new CommandInfo(String.valueOf(attribute.quote())));
+		}
+	}
+
+	private void extractParts(Element e, List<CommandInfo> parts) {
+		int i = 0;
+		List<Macro> currentMacroPath = getCurrentMacroPath();
+		Usage currentUsage = site.getProgram().getUsage(baseClassArray, currentMacroPath);
+		for (ElementName.Part p : e.name().parts()) {
+			if (i++ > 0)
+				parts.add(new CommandInfo("."));
+			Meaning meaning = site.getProgram().getMeaning(p, currentUsage);
+			if (meaning != null) {
+				boolean isArgument = meaning instanceof ParamMeaning;
+				boolean isOverridden = "overridden".equals(p.text());
+				if (isArgument || isOverridden) {
+					parts.add(new CommandInfo(p.text()));
+				} else {
+					parts.add(new CommandInfo(meaning, baseClasses, nextBreadcrumbs));
+				}
+			} else
+				parts.add(new CommandInfo(p.text()));
+		}
+	}
+
+	private List<Macro> getCurrentMacroPath() {
+		List<Macro> macroPath = new ArrayList<Macro>();
+		Program program = site.getProgram();
+		for (String id : navigationBreadcrumbs) {
+			macroPath.add((Macro) program.getMeaning(id));
+		}
+		return macroPath;
+	}
+
+	private void processString(String e, List<CommandInfo> infos, List<Parts> rows) {
+		StringBuilder builder = new StringBuilder();
+		int i = 0;
+		while (i < e.length()) {
+			char c = e.charAt(i);
+			if (c == ' ')
+				builder.append("&ensp;");
+			else if (c == '\t')
+				builder.append("&nbsp;&nbsp;&nbsp;&nbsp;");
+			else if ((c == '\r' && e.charAt(i+1) == '\n') || c == '\n') {
+				if (builder.length() > 0) {
+					infos.add(new CommandInfo(builder.toString()));
+					builder.setLength(0);
+				}
+				rows.add(new Parts(new ArrayList<CommandInfo>(infos)));
+				infos.clear();
+				if (c == '\r')
+					i++; // CRLF
+			} else if (c == '<') {
+				builder.append("&lt;");
+			} else if (c == '>') {
+				builder.append("&gt;");
+			} else if (c == '&') {
+				builder.append("&amp;");
+			} else
+				builder.append(c);
+			i++;
+		}
+		if (builder.length() > 0)
+			infos.add(new CommandInfo(builder.toString()));
+	}
+
+	static String csv(Collection<String> strings) {
+		StringBuilder b = new StringBuilder();
+		for (String s : strings) {
+			if (b.length() > 0)
+				b.append(", ");
+			b.append(s);
+		}
+		return b.toString();
+	}
+
+	@Namespace (
+		name = "macro_row_list",
+		global = true
+	)
+	public static final class Rows extends ListSequence<Parts>
+	{
+
+		private final Macro macro;
+
+		Rows(List<Parts> rows, Macro macro) {
+			super(rows);
+			this.macro = macro;
+		}
+
+		@Command public void line_number(IPrintWriter out,Interpreter interp) {
+			out.print(index + 1);
+		}
+
+		@Command public void file_line_number(IPrintWriter out,Interpreter interp) {
+			out.print(macro.element.lineNumber() + index + 1);
+		}
+
+		public static final CommandSpec current_row = CommandSpec.DO;
+
+		@Command public void current_row(IPrintWriter out,ScopedInterpreter<Parts> interp) {
+			out.print(interp.getArg(get(),"do"));
+		}
+
+		@Command public void next_row(IPrintWriter out,Interpreter interp) {
+			next_element(out,interp);
+		}
+	}
+
+	@Namespace (
+		name = "macro_parts_list",
+		global = true
+	)
+	public static final class Parts extends ListSequence<CommandInfo> {
+
+		Parts(List<CommandInfo> infos) {
+			super(infos);
+		}
+
+		public static final CommandSpec parts = CommandSpec.DO;
+
+		@Command public void parts(IPrintWriter out,ScopedInterpreter<CommandInfo> interp) {
+			out.print( interp.getArg(get(),"do") );
+		}
+
+		@Command public void is_blank(IPrintWriter out, Interpreter interp) {
+			out.print(elements.size() == 0);
+		}
+	}
+
+	public static final CommandSpec macro_usages = CommandSpec.DO;
+
+	@Command public void macro_usages(IPrintWriter out,ScopedInterpreter<MacroUsages> interp)
+		throws IOException, ServletException, CompileException
+	{
+		List<Commands> macros = new ArrayList<Commands>();
+		Set<Usage> usages = site.getProgram().getUsages(meaning);
+		if (usages == null)
+			usages = new HashSet<Usage>();
+		for (Usage u : usages) {
+			List<CommandInfo> infos = new ArrayList<CommandInfo>();
+			StringBuilder breadcrumbs = new StringBuilder();
+			String usageBase = asBaseParam(u.baseIds());
+			for (Macro m : u.macroPath()) {
+				infos.add(new CommandInfo(m, usageBase, breadcrumbs.length() == 0? null : breadcrumbs.toString()));
+
+				if (breadcrumbs.length() > 0)
+					breadcrumbs.append('-');
+				breadcrumbs.append(m.getId());
+			}
+			infos.add(new CommandInfo(meaning, usageBase, breadcrumbs.toString()));
+			macros.add(new Commands(infos));
+		}
+		Object block = interp.getArg(new MacroUsages(macros),"do");
+		out.print(block);
+	}
+
+	static String asBaseParam(String[] base) {
+		StringBuilder b = new StringBuilder();
+		for (String c : base) {
+			if (b.length() > 0)
+				b.append('-');
+			b.append(c);
+		}
+		return b.toString();
+	}
+
+	static String asBreadcrumbsParam(List<Macro> macroPath) {
+		StringBuilder breadcrumbs = new StringBuilder();
+		for (Macro p : macroPath) {
+			if (breadcrumbs.length() > 0)
+				breadcrumbs.append('-');
+			breadcrumbs.append(p.getId());
+		}
+		return breadcrumbs.toString();
+	}
+
+	@Namespace (
+		name = "macro_usages",
+		global = true
+	)
+	public static final class MacroUsages extends ListSequence<Commands>
+	{
+		public MacroUsages(List<Commands> elements) {
+			super(elements);
+		}
+
+		public static final CommandSpec current_usage = CommandSpec.DO;
+
+		@Command public void current_usage(IPrintWriter out,ScopedInterpreter<Commands> interp) {
+			out.print(interp.getArg(get(),"do"));
+		}
+	}
+
+	@Namespace (
+		name = "command_list",
+		global = true
+	)
+	public static final class Commands extends ListSequence<CommandInfo>
+	{
+		public Commands(List<CommandInfo> elements) {
+			super(elements);
+		}
+
+		public static final CommandSpec current_command = CommandSpec.DO;
+
+		@Command public void current_command(IPrintWriter out,ScopedInterpreter<CommandInfo> interp) {
+			out.print(interp.getArg(get(),"do"));
+		}
+	}
+
+	@Namespace (
+		name = "command_info",
+		global = false
+	)
+	public static final class CommandInfo {
+		private final String id;
+		private final String name;
+		private final String tag;
+		private final String source;
+		private final int fileLineNumber;
+		private final String baseClasses;
+		private final String breadcrumbs;
+		private final String requiredNamespaces;
+		private final boolean isCustomTweak;
+		private final boolean isMacro;
+		private final boolean isBinary;
+		private final String namespaceClass;
+		private final String[] parameterNames;
+
+		static final Comparator<? super CommandInfo> MACRO_NAME_COMPARATOR = new Comparator<MacroSourceNamespace.CommandInfo>() {
+			public int compare(MacroSourceNamespace.CommandInfo o1, MacroSourceNamespace.CommandInfo o2) {
+				return o1.name.compareTo(o2.name);
+			}
+		};
+
+		public CommandInfo(String name) {
+			this.id = null;
+			this.tag = name;
+			this.name = name;
+			this.isMacro = false;
+			this.isBinary = false;
+			this.source = null;
+			this.fileLineNumber = 0;
+			this.baseClasses = null;
+			this.breadcrumbs = null;
+			this.requiredNamespaces = null;
+			this.isCustomTweak = false;
+			this.namespaceClass = null;
+			this.parameterNames = null;
+		}
+
+		public CommandInfo(Meaning meaning, String baseClasses, String breadcrumbs) {
+			this.id = meaning.getId();
+			this.name = meaning.getName();
+			this.tag = this.name.startsWith("translation:")? "t" : this.name;
+			this.isMacro = meaning instanceof Macro;
+			this.isBinary = meaning instanceof JavaCommand;
+			this.source = getSource(meaning);
+			this.fileLineNumber = isMacro? ((Macro) meaning).element.lineNumber() + 1 : 0;
+			this.baseClasses = baseClasses;
+			this.breadcrumbs = breadcrumbs;
+			this.requiredNamespaces = csv(meaning.getRequiredNamespaces());
+			this.isCustomTweak = isCustomTweak(meaning);
+			this.namespaceClass = meaning instanceof JavaCommand? ((JavaCommand) meaning).getMethod().getDeclaringClass().getSimpleName() : null;
+			this.parameterNames = isMacro? ((Macro) meaning).getParameterNames() : meaning instanceof JavaCommand? ((JavaCommand) meaning).getParameterNames() : null;
+		}
+
+		@Command("id") public void _id(IPrintWriter out,Interpreter interp) {
+			out.print(id);
+		}
+
+		@Command("name") public void _name(IPrintWriter out,Interpreter interp) {
+			out.print(name);
+		}
+
+		@Command("tag") public void _tag(IPrintWriter out,Interpreter interp) {
+			out.print(tag);
+		}
+
+		@Command("source") public void _source(IPrintWriter out,Interpreter interp) {
+			out.print(source);
+		}
+
+		@Command public void naml_breadcrumbs(IPrintWriter out,Interpreter interp) {
+			out.print(breadcrumbs);
+		}
+
+		@Command public void base(IPrintWriter out,Interpreter interp) {
+			out.print(baseClasses);
+		}
+
+		@Command public void file_line_number(IPrintWriter out,Interpreter interp) {
+			out.print(fileLineNumber);
+		}
+
+		@Command public void required_namespaces(IPrintWriter out,Interpreter interp) {
+			out.print(requiredNamespaces);
+		}
+
+		@Command public void is_custom_tweak(IPrintWriter out,Interpreter interp) {
+			out.print(isCustomTweak);
+		}
+
+		@Command public void is_macro(IPrintWriter out,Interpreter interp) {
+			out.print(isMacro);
+		}
+
+		@Command public void namespace_class(IPrintWriter out,Interpreter interp) {
+			out.print(namespaceClass);
+		}
+
+		@Command public void is_binary(IPrintWriter out,Interpreter interp) {
+			out.print(isBinary);
+		}
+
+		@Command public void has_parameters(IPrintWriter out,Interpreter interp) {
+			out.print(parameterNames != null && parameterNames.length > 0);
+		}
+
+		public static final CommandSpec parameter_names = CommandSpec.DO;
+
+		@Command public void parameter_names(IPrintWriter out, ScopedInterpreter<StringList> interp) {
+			out.print(interp.getArg(new StringList(Arrays.asList(parameterNames)),"do"));
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/MessageNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,114 @@
+package nabble.view.web.template;
+
+import fschmidt.html.Html;
+import nabble.model.FileUpload;
+import nabble.model.MailMessageFormat;
+import nabble.model.Message;
+import nabble.model.Node;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+
+
+@Namespace (
+	name = "message",
+	global = false
+)
+public final class MessageNamespace {
+	private final Message message;
+
+	public MessageNamespace(Message message) {
+		this.message = message;
+	}
+
+	@Command public void as_text(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode(message.getText()) );
+	}
+
+	@Command public void as_quoted_text(IPrintWriter out,Interpreter interp) {
+		boolean isMail = message.getFormat() instanceof MailMessageFormat;
+		if( isMail ) {
+			Html html = new Html(message.getText());
+			MailMessageFormat.tagEmails(html,(Node)message.getSource());
+			out.print(html);
+		} else {
+			Html html = new Html(message.getRaw());
+			FileUpload.processFileTags(html,message.getSource());
+			out.print(html);
+		}
+	}
+
+	@Command public void as_text_without_quotes(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode(message.getTextWithoutQuotes()) );
+	}
+
+	@Command public void as_raw(IPrintWriter out,Interpreter interp) {
+		out.print( message.getRaw() );
+	}
+
+	@Command public void as_editable(IPrintWriter out,Interpreter interp) {
+		boolean isMail = message.getFormat() instanceof MailMessageFormat;
+		out.print( isMail ? message.getText() : message.getRaw() );
+	}
+
+	@Command public void is_imported_mail(IPrintWriter out,Interpreter interp) {
+		out.print( message.getFormat().isMail() );
+	}
+
+	@Command public void is_html_format(IPrintWriter out,Interpreter interp) {
+		out.print( message.getFormat() == Message.Format.HTML );
+	}
+
+	@Command public void is_text_format(IPrintWriter out,Interpreter interp) {
+		out.print( message.getFormat() == Message.Format.TEXT );
+	}
+
+	@Command public void is_deleted(IPrintWriter out,Interpreter interp) {
+		out.print( message.isDeleted() );
+	}
+
+	@Command public void is_deactivated(IPrintWriter out,Interpreter interp) {
+		out.print( message.isDeactivated() );
+	}
+
+	@Command public void format(IPrintWriter out,Interpreter interp) {
+		String s = message.getFormat().getName();
+		if( s.equals("subscription") )
+			s = "mail";  // hack for now
+		out.print(s);
+	}
+
+
+	public static final CommandSpec as_html_list = CommandSpec.DO;
+
+	@Command public void as_html_list(IPrintWriter out,ScopedInterpreter<HtmlListNamespace> interp) {
+		HtmlListNamespace html = new HtmlListNamespace( message.parse(), message.getSource(), message.getFormat());
+		interp.getArgString(html,"do");
+		out.print(html);
+	}
+
+	public static final CommandSpec as_raw_html_list = CommandSpec.DO;
+
+	@Command public void as_raw_html_list(IPrintWriter out,ScopedInterpreter<HtmlListNamespace> interp) {
+		HtmlListNamespace html = new HtmlListNamespace( new Html(message.getRaw()), message.getSource(), message.getFormat());
+		interp.getArgString(html,"do");
+		out.print(html);
+	}
+
+	public static final CommandSpec as_text_list = CommandSpec.DO;
+
+	@Command public void as_text_list(IPrintWriter out,ScopedInterpreter<HtmlListNamespace> interp) {
+		boolean isMail = message.getFormat() instanceof MailMessageFormat;
+		if( isMail ) {
+			out.print( message.getText() );
+		} else {
+			HtmlListNamespace html = new HtmlListNamespace( message.parseForMailText(), message.getSource(), message.getFormat());
+			interp.getArgString(html,"do");
+			out.print( html.toMailText() );
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/MonthlyArchivesNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,161 @@
+package nabble.view.web.template;
+
+import nabble.model.Node;
+import nabble.model.NodeIterator;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.ListSequence;
+import nabble.view.lib.Permissions;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+
+@Namespace (
+	name = "monthly_archives",
+	global = false
+)
+public class MonthlyArchivesNamespace {
+
+	private int total = 0;
+	private Map<Short, Map<Byte, Integer>> years = new TreeMap<Short, Map<Byte, Integer>>(Collections.reverseOrder());
+
+	public MonthlyArchivesNamespace(Node node) {
+		NodeIterator<? extends Node> it = node.getTopicsByLastNodeDate(null,Permissions.canBeViewedByParentViewersFilter);
+		Node[] nodes = it.get(0, 5000).toArray(new Node[0]);
+		for (int i = 0; i < nodes.length; i++) {
+			Calendar cal = Calendar.getInstance();
+			cal.setTime(nodes[i].getWhenCreated());
+			nodes[i] = null;
+			short year = (short) cal.get(Calendar.YEAR);
+
+			Map<Byte, Integer> months = years.get(year);
+			if (months == null) {
+				months = new TreeMap<Byte, Integer>();
+				years.put(year, months);
+			}
+			byte month = (byte) (cal.get(Calendar.MONTH) + 1);
+			Integer count = months.get(month);
+			count = count == null? 1 : count+1;
+			months.put(month, count);
+			total++;
+		}
+	}
+
+	public static final CommandSpec year_list = CommandSpec.DO;
+
+	@Command public void year_list(IPrintWriter out, ScopedInterpreter<Years> interp)
+		throws IOException, ServletException
+	{
+		List<YearRow> yearRows = new ArrayList<YearRow>();
+		for (short year : years.keySet()) {
+			yearRows.add(new YearRow(year, years.get(year)));
+		}
+		out.print(interp.getArg(new Years(yearRows),"do"));
+	}
+
+	@Command("total") public void _total(IPrintWriter out, Interpreter interp) {
+		out.print(total);
+	}
+
+	@Namespace (
+		name = "year_list",
+		global = true
+	)
+	public static final class Years extends ListSequence<YearRow>
+	{
+		public Years(List<YearRow> elements) {
+			super(elements);
+		}
+
+		public static final CommandSpec current_year = CommandSpec.DO;
+
+		@Command public void current_year(IPrintWriter out,ScopedInterpreter<YearRow> interp) {
+			out.print(interp.getArg(get(),"do"));
+		}
+	}
+
+	@Namespace (
+		name = "year_row",
+		global = false
+	)
+	public static class YearRow {
+		private final short year;
+		private final Map<Byte, Integer> monthMap;
+
+		public YearRow(short year, Map<Byte, Integer> monthMap) {
+			this.year = year;
+			this.monthMap = monthMap;
+		}
+
+		@Command("year") public void _year(IPrintWriter out, Interpreter interp) {
+			out.print(year);
+		}
+
+		public static final CommandSpec month_list = CommandSpec.DO;
+
+		@Command public void month_list(IPrintWriter out,ScopedInterpreter<Months> interp) {
+			List<MonthRow> monthRows = new ArrayList<MonthRow>();
+			for (Map.Entry<Byte, Integer> entry : monthMap.entrySet()) {
+				monthRows.add(new MonthRow(year, entry.getKey(), entry.getValue()));
+			}
+			out.print( interp.getArg(new Months(monthRows),"do") );
+		}
+	}
+
+	@Namespace (
+		name = "month_list",
+		global = true
+	)
+	public static final class Months extends ListSequence<MonthRow>
+	{
+		public Months(List<MonthRow> elements) {
+			super(elements);
+		}
+
+		public static final CommandSpec current_month = CommandSpec.DO;
+
+		@Command public void current_month(IPrintWriter out,ScopedInterpreter<MonthRow> interp) {
+			out.print(interp.getArg(get(),"do"));
+		}
+	}
+
+	@Namespace (
+		name = "month_row",
+		global = false
+	)
+	public static class MonthRow {
+		private final short year;
+		private final byte month;
+		private final int count;
+
+		public MonthRow(short year, byte month, int count) {
+			this.year = year;
+			this.month = month;
+			this.count = count;
+		}
+
+		@Command("month") public void _month(IPrintWriter out, Interpreter interp) {
+			String m = String.valueOf(month);
+			out.print(m.length() ==1? '0'+m : m);
+		}
+
+		@Command("year") public void _year(IPrintWriter out, Interpreter interp) {
+			out.print(year);
+		}
+
+		@Command("count") public void _count(IPrintWriter out, Interpreter interp) {
+			out.print(count);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NabbleNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1368 @@
+package nabble.view.web.template;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.html.Html;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.mail.AlternativeMultipartContent;
+import fschmidt.util.mail.Content;
+import fschmidt.util.mail.HtmlTextContent;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.Executors;
+import nabble.model.Init;
+import nabble.model.Lucene;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.model.SystemProperties;
+import nabble.model.User;
+import nabble.modules.ModuleManager;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Encoder;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Macro;
+import nabble.naml.compiler.Meaning;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.compiler.Source;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.compiler.Usage;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.naml.namespaces.StringList;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+import nabble.view.web.Javascript;
+import nabble.view.web.user.OnlineStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.regex.Pattern;
+
+
+@Namespace (
+	name = "nabble",
+	global = true
+)
+public final class NabbleNamespace {
+	private static final Logger logger = LoggerFactory.getLogger(NabbleNamespace.class);
+
+	public static final String newsflash = (String)Init.get("newsflash");
+
+	private static ThreadLocal<NabbleNamespace> tl = new ThreadLocal<NabbleNamespace>();
+
+	public static NabbleNamespace current() {
+		return tl.get();
+	}
+
+	private Site site;
+
+	public NabbleNamespace(Site site) {
+		if( site == null )
+			throw new NullPointerException("site is null");
+		this.site = site;
+		tl.set(this);
+	}
+
+	public Site site() {
+		return site;
+	}
+
+	public DbDatabase db() {
+		return site.getDb();
+	}
+
+	public static final CommandSpec root_node = CommandSpec.DO;
+
+	@Command public void root_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		NodeNamespace ns = new NodeNamespace(site().getRootNode());
+ 		out.print( interp.getArg(ns,"do") );
+	}
+
+	@Command public void site_id(IPrintWriter out,Interpreter interp) {
+		out.print( site.getId() );
+	}
+
+	@Command public void base_url(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( site.getBaseUrl() ) );
+	}
+
+	public static final CommandSpec terms_of_use_url = CommandSpec.DO()
+		.optionalParameters("is_registration_form")
+		.build()
+	;
+
+	@Command public static void terms_of_use_url(IPrintWriter out,Interpreter interp) {
+		boolean isRegistrationForm = interp.getArgAsBoolean("is_registration_form", false);
+		out.print( interp.encode(Jtp.termsUrl(isRegistrationForm)));
+	}
+
+
+	public static final CommandSpec javascript_string_encode = CommandSpec.TEXT;
+
+	@Command public void javascript_string_encode(IPrintWriter out,Interpreter interp) {
+		out.print( HtmlUtils.javascriptStringEncode(interp.getArgString("text")) );
+	}
+
+	public static final CommandSpec break_up = CommandSpec.TEXT;
+
+	@Command public void break_up(IPrintWriter out,Interpreter interp) {
+		out.print( Jtp.breakUp(interp.getArgString("text")) );
+	}
+
+	public static final CommandSpec hide_emails = CommandSpec.TEXT;
+
+	@Command public void hide_emails(IPrintWriter out,Interpreter interp) {
+		Encoder encoder = interp.getEncoder();
+		interp.setEncoder(Encoder.TEXT);
+		String text = interp.getArgString("text");
+		interp.setEncoder(encoder);
+		out.print( interp.encode( ModelHome.hideAllEmails(text) ) );
+	}
+
+
+
+	@Namespace (
+		name = "help_instance",
+		global = false
+	)
+	public static class HelpNs {
+		private final Help help;
+
+		private HelpNs(Help help) {
+			this.help = help;
+		}
+
+		@Command public void url(IPrintWriter out,Interpreter interp) {
+			out.print( help.url() );
+		}
+
+		@Command public void link(IPrintWriter out,Interpreter interp) {
+			out.print( help.link() );
+		}
+	}
+
+	@Namespace (
+		name = "help",
+		global = false
+	)
+	public static class HelpsNs {
+
+		private static void print(IPrintWriter out,ScopedInterpreter<HelpNs> interp,Help help) {
+			out.print( interp.getArg(new HelpNs(help),"do") );
+		}
+
+		@Command public void index_path(IPrintWriter out,Interpreter interp) {
+			out.print( interp.encode( "/help/Index.jtp" ) );
+		}
+
+		public static final CommandSpec mailing_list_intro = CommandSpec.DO;
+
+		@Command public void mailing_list_intro(IPrintWriter out,ScopedInterpreter<HelpNs> interp) {
+			print(out,interp,Help.mailingListIntro);
+		}
+
+		public static final CommandSpec search = CommandSpec.DO;
+
+		@Command public void search(IPrintWriter out,ScopedInterpreter<HelpNs> interp) {
+			print(out,interp,Help.search);
+		}
+
+		public static final CommandSpec cataloging = CommandSpec.DO;
+
+		@Command public void cataloging(IPrintWriter out,ScopedInterpreter<HelpNs> interp) {
+			print(out,interp,Help.cataloging);
+		}
+
+		public static final CommandSpec embed_what_how = CommandSpec.DO;
+
+		@Command public void embed_what_how(IPrintWriter out,ScopedInterpreter<HelpNs> interp) {
+			print(out,interp,Help.embed_what_how);
+		}
+
+		public static final CommandSpec mixed_lengths = CommandSpec.DO;
+
+		@Command public void mixed_lengths(IPrintWriter out,ScopedInterpreter<HelpNs> interp) {
+			print(out,interp,Help.mixed_lengths);
+		}
+
+		public static final CommandSpec password = CommandSpec.DO;
+
+		@Command public void password(IPrintWriter out,ScopedInterpreter<HelpNs> interp) {
+			print(out,interp,Help.password);
+		}
+
+		public static final CommandSpec userid = CommandSpec.DO;
+
+		@Command public void userid(IPrintWriter out,ScopedInterpreter<HelpNs> interp) {
+			print(out,interp,Help.userid);
+		}
+	}
+	private static final HelpsNs helpsNs = new HelpsNs();
+
+	public static final CommandSpec help = CommandSpec.DO;
+
+	@Command public void help(IPrintWriter out,ScopedInterpreter<HelpsNs> interp) {
+		out.print( interp.getArg(helpsNs,"do") );
+	}
+
+
+
+
+
+	public static final CommandSpec truncate = new CommandSpec.Builder()
+		.dotParameter("text")
+		.parameters("size")
+		.optionalParameters("if_truncated")
+		.build()
+	;
+
+	@Command public void truncate(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		String size = interp.getArgString("size");
+		int n;
+		try {
+			n = Integer.valueOf(size);
+		} catch (NumberFormatException e) {
+			throw new RuntimeException("Invalid \"size\" attribute: " + size);
+		}
+		if (text.length() > n) {
+			text = text.substring(0, n-4) + "...";
+			out.print(text);
+			Object ifTruncated = interp.getArg("if_truncated");
+			if (ifTruncated != null) {
+				out.print(ifTruncated);
+			}
+		} else
+			out.print(text);
+	}
+
+
+
+	private static final BasicNamespace.ExceptionNamespaceFactory<ErrorNamespace> myExceptionNamespaceFactory =
+		new BasicNamespace.ExceptionNamespaceFactory<ErrorNamespace>() {
+			public ErrorNamespace newExceptionNamespace(TemplateException ex) {
+				return new ErrorNamespace(ex);
+			}
+		}
+	;
+
+	public static final CommandSpec handle_exception = BasicNamespace.handle_exception;
+
+	@Command public void handle_exception(IPrintWriter out,ScopedInterpreter<ErrorNamespace> interp) {
+		interp.getFromStack(BasicNamespace.class).handleException(out,interp,myExceptionNamespaceFactory);
+	}
+
+
+	@Command public static void tabs_library_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( Shared.getTabsPath() ) );
+	}
+
+	@Command public static void nabble_global_apps_url(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( Jtp.homeContextUrl() + "/UserSites.jtp" ) );
+	}
+
+	@Command public void get_newsflash(IPrintWriter out,Interpreter interp) {
+		out.print( newsflash );
+	}
+
+
+	/** Number of pages to be displayed before/after the current page*/
+	private static final int NEIGHBOR_PAGES = 3;
+
+	public static final CommandSpec paging = new CommandSpec.Builder()
+		.parameters("total_rows","current_row","rows_per_page")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.build()
+	;
+
+	@Command public void paging(IPrintWriter out,ScopedInterpreter<PagingNamespace> interp) {
+		int totalRows = interp.getArgAsInt("total_rows");
+		int currentRow = interp.getArgAsInt("current_row",0);
+		int rowsPerPage = interp.getArgAsInt("rows_per_page");
+		out.print( interp.getArg( new PagingNamespace(totalRows,currentRow,rowsPerPage,NEIGHBOR_PAGES), "do" ) );
+	}
+
+
+
+	// Basic Nabble Urls
+
+	@Command public void nabble_homepage(IPrintWriter out,Interpreter interp) {
+		out.print(Jtp.homePage());
+	}
+
+	public static final CommandSpec get_int = new CommandSpec.Builder()
+		.parameters("exception")
+		.optionalParameters("default")
+		.dotParameter("int")
+		.build()
+	;
+
+	@Command public void get_int(IPrintWriter out,Interpreter interp)
+		throws TemplateException
+	{
+		String ex = interp.getArgString("exception");
+		String s = interp.getArgString("int");
+		if( s != null ) {
+			s = s.trim();
+			if( s.length() > 0 ) {
+				try {
+					out.print( Integer.parseInt(s) );
+					return;
+				} catch(NumberFormatException e) {
+					throw TemplateException.newInstance( ex );
+				}
+			}
+		}
+		String def = interp.getArgString("default");
+		if( def == null )
+			throw TemplateException.newInstance( ex );
+		out.print( Integer.parseInt(def) );
+	}
+
+	@Command public void site_activity(IPrintWriter out,Interpreter interp) {
+		out.print( site().getActivity() );
+	}
+
+	@Command public void site_has_delete_date(IPrintWriter out,Interpreter interp) {
+		out.print( site().getDeleteDate() != null );
+	}
+
+	public static final CommandSpec site_delete_date = CommandSpec.DO;
+
+	@Command public void site_delete_date(IPrintWriter out,ScopedInterpreter<DateNamespace> interp) {
+		out.print( interp.getArg( new DateNamespace(site().getDeleteDate()), "do" ) );
+	}
+
+	public static final CommandSpec new_email = CommandSpec.DO;
+
+	@Command public void new_email(IPrintWriter out,ScopedInterpreter<EmailNamespace> interp) {
+		out.print( interp.getArg( new EmailNamespace(), "do" ) );
+	}
+
+	public static final CommandSpec parse_email = new CommandSpec.Builder()
+		.dotParameter("text")
+		.build()
+	;
+
+	@Command public void parse_email(IPrintWriter out,Interpreter interp)
+		throws ModelException.EmailFormat
+	{
+		String email = null;
+		String text = interp.getArgString("text");
+		if( text != null ) {
+			text = text.trim();
+			if( text.length() > 0 ) {
+				int posOpen = text.indexOf('<');
+				int posClose = text.indexOf('>');
+				if (posOpen >= 0 && posClose > posOpen)
+					text = text.substring(posOpen+1, posClose).trim();
+				if (!new MailAddress(text).isValid())
+					throw new ModelException.EmailFormat(text);
+				email = text;
+			}
+		}
+		out.print(email);
+	}
+
+	public static final CommandSpec is_valid_node= CommandSpec.DO()
+		.parameters("node_id")
+		.build()
+	;
+
+	@Command public void is_valid_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		long nodeId = interp.getArgAsLong("node_id");
+		Node node = site.getNode(nodeId);
+		out.print(node != null);
+	}
+
+	public static final CommandSpec get_node_from_id = CommandSpec.DO()
+		.parameters("node_id")
+		.build()
+	;
+
+	@Command public void get_node_from_id(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		long nodeId = interp.getArgAsLong("node_id");
+		Node node = site.getNode(nodeId);
+		out.print( interp.getArg(new NodeNamespace(node),"do") );
+	}
+
+	public static final CommandSpec get_user_from_id = CommandSpec.DO()
+		.parameters("user_id")
+		.build();
+
+	@Command public void get_user_from_id(IPrintWriter out,ScopedInterpreter<UserNamespace> interp)
+		throws IOException, ServletException
+	{
+		String userId = interp.getArgString("user_id");
+		Person person = site().getPerson(userId);
+		out.print( interp.getArg(new UserNamespace(person),"do") );
+	}
+
+	public static final CommandSpec user_groups = CommandSpec.DO;
+
+	@Command public void user_groups(IPrintWriter out,ScopedInterpreter<GroupList> interp) {
+		List<String> groups = Permissions.getGroups(site());
+		Object block = interp.getArg(new GroupList(groups),"do");
+		out.print(block);
+	}
+
+	@Namespace (
+		name = "group_list",
+		global = true
+	)
+	public static final class GroupList extends StringList {
+
+		GroupList(List<String> groups) {
+			super(groups);
+		}
+
+		@Command public void current_group(IPrintWriter out,Interpreter interp) {
+			out.print(get());
+		}
+	}
+
+	public static final CommandSpec permissions = CommandSpec.DO()
+		.optionalParameters("values")
+		.build()
+	;
+
+	@Command public void permissions(IPrintWriter out,ScopedInterpreter<PermissionList> interp)
+		throws IOException, ServletException
+	{
+		List<String> list = new ArrayList<String>();
+		String values = interp.getArgString("values");
+		for( String s : values.split(",") ) {
+			list.add( s.trim() );
+		}
+		Object block = interp.getArg(new PermissionList(list),"do");
+		out.print(block);
+	}
+
+	@Namespace (
+		name = "permission_list",
+		global = true
+	)
+	public static final class PermissionList extends StringList {
+
+		PermissionList(List<String> list) {
+			super(list);
+		}
+
+		@Command public void current_permission(IPrintWriter out,Interpreter interp) {
+			out.print(get());
+		}
+	}
+
+
+	public static final CommandSpec get_node = CommandSpec.DO()
+		.parameters("node")
+		.build()
+	;
+
+	@Command public void get_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		NodeNamespace nodeNs = interp.getArgAsNamespace(NodeNamespace.class,"node");
+		if( nodeNs == null )
+			throw new RuntimeException("node is null");
+		out.print( interp.getArg(nodeNs,"do") );
+	}
+
+	public static final CommandSpec get_user = CommandSpec.DO()
+		.parameters("user")
+		.build()
+	;
+
+	@Command public void get_user(IPrintWriter out,ScopedInterpreter<UserNamespace> interp) {
+		UserNamespace userNs = interp.getArgAsNamespace(UserNamespace.class,"user");
+		if( userNs == null ) {
+			out.print( (String)null );
+			return;
+		}
+		out.print( interp.getArg(userNs,"do") );
+	}
+
+	public static final CommandSpec get_subscription = CommandSpec.DO()
+		.parameters("subscription")
+		.build()
+	;
+
+	@Command public void get_subscription(IPrintWriter out,ScopedInterpreter<SubscriptionNamespace> interp) {
+		SubscriptionNamespace subscriptionNs = interp.getArgAsNamespace(SubscriptionNamespace.class,"subscription");
+		out.print( interp.getArg(subscriptionNs,"do") );
+	}
+/*
+not needed yet
+	public static final CommandSpec get_mailing_list = CommandSpec.DO()
+		.parameters("mailing_list")
+		.build()
+	;
+
+	@Command public void get_mailing_list(IPrintWriter out,ScopedInterpreter<MailingListNamespace> interp) {
+		MailingListNamespace ns = interp.getArgAsNamespace(MailingListNamespace.class,"mailing_list");
+		out.print( interp.getArg(ns,"do") );
+	}
+*/
+
+
+	@Command public void anyone_group(IPrintWriter out,Interpreter interp) {
+		out.print( Permissions.ANYONE_GROUP );
+	}
+
+	@Command public void registered_group(IPrintWriter out,Interpreter interp) {
+		out.print( Permissions.REGISTERED_GROUP );
+	}
+
+	@Command public void authors_group(IPrintWriter out,Interpreter interp) {
+		out.print( Permissions.AUTHOR_GROUP );
+	}
+
+	@Command public void administrators_group(IPrintWriter out,Interpreter interp) {
+		out.print( Permissions.ADMINISTRATORS_GROUP );
+	}
+
+	@Command public void view_permission(IPrintWriter out,Interpreter interp) {
+		out.print( Permissions.VIEW_PERMISSION );
+	}
+
+	public static final CommandSpec set_default_permissions = CommandSpec.NO_OUTPUT()
+		.scopedParameters("do")
+		.dotParameter("do")
+		.parameters("version")
+		.build()
+	;
+
+	@Command public void set_default_permissions(IPrintWriter out,ScopedInterpreter<DefaultPermissionEditorNamespace> interp) {
+		String version = interp.getArgString("version");
+		synchronized( ("permission-lock-"+site().getId()).intern() ) {
+			site = site.getGoodCopy();
+			if( !Permissions.isPermissionVersion(site,interp.getArgString("version")) ) {
+				DefaultPermissionEditorNamespace ns;
+				while(true) {
+					db().beginTransaction();
+					try {
+						Site site = this.site.getGoodCopy();
+						Permissions.setPermissionVersion(site,version);
+						ns = new DefaultPermissionEditorNamespace(site);
+						interp.getArgString(ns,"do");
+						db().commitTransaction();
+					} finally {
+						db().endTransaction();
+					}
+					site = site.getGoodCopy();
+					if( ns.ok(site) )
+						break;
+					logger.error("set_default_permissions failed for "+site+", trying again");
+				}
+			}
+		}
+	}
+
+	@Namespace (
+		name = "default_permission_editor",
+		global = true
+	)
+	public static final class DefaultPermissionEditorNamespace {
+		private static class PermGroup {
+			final String perm;
+			final String group;
+
+			PermGroup(String perm,String group) {
+				this.perm = perm;
+				this.group = group;
+			}
+		}
+
+		private final Site site;
+		private final List<PermGroup> permissions = new ArrayList<PermGroup>();
+		private final List<PermGroup> sitePermissions = new ArrayList<PermGroup>();
+
+		DefaultPermissionEditorNamespace(Site site) {
+			this.site = site;
+		}
+
+		public static final CommandSpec add_permission = CommandSpec.NO_OUTPUT()
+			.parameters("group","permission")
+			.build()
+		;
+
+		@Command public void add_permission(IPrintWriter out,Interpreter interp) {
+			String perm = interp.getArgString("permission");
+			String group = interp.getArgString("group");
+			Permissions.addPermission(site,perm,group);
+			permissions.add(new PermGroup(perm,group));
+		}
+
+		public static final CommandSpec add_site_permission = add_permission;
+
+		@Command public void add_site_permission(IPrintWriter out,Interpreter interp) {
+			String perm = interp.getArgString("permission");
+			String group = interp.getArgString("group");
+			Permissions.addSiteDefaultPermission(site,perm,group);
+			sitePermissions.add(new PermGroup(perm,group));
+		}
+
+		boolean ok(Site site) {
+			for( PermGroup pg : permissions ) {
+				if( !Permissions.hasPermission(site,pg.group,pg.perm) ) {
+					logger.error("add_permission failed for "+site+" permission="+pg.perm+" group="+pg.group);
+					return false;
+				}
+			}
+			for( PermGroup pg : sitePermissions ) {
+				if( !Permissions.hasSiteDefaultPermission(site,pg.group,pg.perm) ) {
+					logger.error("add_site_permission failed for "+site+" permission="+pg.perm+" group="+pg.group);
+					return false;
+				}
+			}
+			return true;
+		}
+	}
+
+	public static final CommandSpec save_site_permissions = CommandSpec.DO;
+
+	@Command public void save_site_permissions(IPrintWriter out,ScopedInterpreter<SitePermissionEditorNamespace> interp) {
+		db().beginTransaction();
+		try {
+			interp.getArgString(new SitePermissionEditorNamespace(site()),"do");
+			db().commitTransaction();
+		} finally {
+			db().endTransaction();
+		}
+	}
+
+	@Namespace (
+		name = "site_permission_editor",
+		global = true
+	)
+	public static final class SitePermissionEditorNamespace {
+		private final Site site;
+
+		SitePermissionEditorNamespace(Site site) {
+			this.site = site;
+		}
+
+		public static final CommandSpec remove_site_permissions = CommandSpec.NO_OUTPUT;
+
+		@Command public void remove_site_permissions(IPrintWriter out,Interpreter interp) {
+			Permissions.removeSitePermissions(site);
+		}
+
+		public static final CommandSpec add_site_permission = CommandSpec.NO_OUTPUT()
+			.parameters("permission")
+			.optionalParameters("group")
+			.build()
+		;
+
+		@Command public void add_site_permission(IPrintWriter out,Interpreter interp) {
+			String perm = interp.getArgString("permission");
+			String group = interp.getArgString("group");
+			if( group == null )
+				Permissions.addSitePermission(site,perm);
+			else
+				Permissions.addSitePermission(site,perm,group);
+		}
+
+		public static final CommandSpec remove_site_permission = CommandSpec.NO_OUTPUT()
+			.parameters("permission")
+			.optionalParameters("group")
+			.build()
+		;
+
+		@Command public void remove_site_permission(IPrintWriter out,Interpreter interp) {
+			String group = interp.getArgString("group");
+			String perm = interp.getArgString("permission");
+			if (group == null)
+				Permissions.removeSitePermission(site,perm);
+			else
+				Permissions.removeSitePermission(site,perm,group);
+		}
+
+	}
+
+	public static final CommandSpec has_default_permission = new CommandSpec.Builder()
+		.parameters("group","permission")
+		.build()
+	;
+
+	@Command public void has_default_permission(IPrintWriter out,Interpreter interp) {
+		String group = interp.getArgString("group");
+		String perm = interp.getArgString("permission");
+		out.print( Permissions.hasPermission(site(),group,perm) );
+	}
+
+	public static final CommandSpec has_site_default_permission = has_default_permission;
+
+	@Command public void has_site_default_permission(IPrintWriter out,Interpreter interp) {
+		String group = interp.getArgString("group");
+		String perm = interp.getArgString("permission");
+		out.print( Permissions.hasSiteDefaultPermission(site(),group,perm) );
+	}
+
+	public static final CommandSpec group_has_site_permission = has_default_permission;
+
+	@Command public void group_has_site_permission(IPrintWriter out,Interpreter interp) {
+		String group = interp.getArgString("group");
+		String perm = interp.getArgString("permission");
+		out.print( Permissions.hasSitePermission(site(),group,perm) );
+	}
+
+	public static final CommandSpec site_has_site_permission = new CommandSpec.Builder()
+		.dotParameter("permission")
+		.build()
+	;
+
+	@Command public void site_has_site_permission(IPrintWriter out,Interpreter interp) {
+		String perm = interp.getArgString("permission");
+		out.print( Permissions.siteHasSitePermission(site(),perm) );
+	}
+
+	public static final CommandSpec remove_group = CommandSpec.NO_OUTPUT()
+		.parameters("group")
+		.build()
+	;
+
+	@Command public void remove_group(IPrintWriter out,Interpreter interp) {
+		String group = interp.getArgString("group");
+		Permissions.removeGroup(site(),group);
+	}
+
+	public static final CommandSpec users_in_group = CommandSpec.DO()
+		.parameters("group")
+		.build()
+	;
+
+	@Command public void users_in_group(IPrintWriter out,ScopedInterpreter<UserNamespace.UserList> interp) {
+		String group = interp.getArgString("group");
+		List<User> users = Permissions.getUsersInGroup(site,group);
+		UserNamespace.UserList usersNs = new UserNamespace.UserList(users);
+		out.print( interp.getArg(usersNs,"do") );
+	}
+
+	public static final CommandSpec site_users = new CommandSpec.Builder()
+		.parameters("length")
+		.optionalParameters("start", "filter")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.outputtedParameters("do")
+		.build()
+	;
+
+	@Command public void site_users(IPrintWriter out,ScopedInterpreter<UserNamespace.UserList> interp) {
+		int start = interp.getArgAsInt("start",0);
+		int length = interp.getArgAsInt("length");
+		String cnd = interp.getArgString("filter");
+		List<User> users = site.getUsersByNodeCount(start,length,cnd);
+		UserNamespace.UserList usersNs = new UserNamespace.UserList(users);
+		out.print( interp.getArg(usersNs,"do") );
+	}
+
+	public static final CommandSpec site_user_count = CommandSpec.DO()
+		.optionalParameters("filter")
+		.build();
+
+	@Command public void site_user_count(IPrintWriter out,Interpreter interp) {
+		String cnd = interp.getArgString("filter");
+		out.print( site.getUserCount(cnd) );
+	}
+
+	@Command public void registered_filter(IPrintWriter out,Interpreter interp) {
+		out.print( "registered is not null" );
+	}
+
+	public static final CommandSpec online_users = CommandSpec.DO()
+		.optionalParameters("include_invisible_users")
+		.build();
+
+	@Command public void online_users(IPrintWriter out,ScopedInterpreter<UserNamespace.UserList> interp) {
+		boolean includeInvisibleUsers = interp.getArgAsBoolean("include_invisible_users", false);
+		List<User> users = OnlineStatus.getOnlineUsers(site, includeInvisibleUsers);
+		UserNamespace.UserList usersNs = new UserNamespace.UserList(users);
+		out.print( interp.getArg(usersNs,"do") );
+	}
+
+	@Command public void online_anonymous_users_count(IPrintWriter out,Interpreter interp) {
+		out.print( OnlineStatus.getOnlineAnonymousUsersCount(site) );
+	}
+
+	@Command public void online_invisible_users_count(IPrintWriter out,Interpreter interp) {
+		out.print( OnlineStatus.getOnlineInvisibleUsersCount(site) );
+	}
+
+	public static final CommandSpec url_belongs_to_site = new CommandSpec.Builder()
+		.parameters("url")
+		.build()
+	;
+
+	@Command public void url_belongs_to_site(IPrintWriter out,Interpreter interp)
+		throws TemplateException
+	{
+		try {
+			String url = interp.getArgString("url");
+			String domain = new URL(url).getHost();
+			Long siteId = Jtp.getSiteIdFromDomain(domain);
+			out.print( siteId != null && siteId.longValue() == site().getId() );
+		} catch(MalformedURLException e) {
+			throw new ModelException.InvalidPermalink();
+		}
+	}
+
+	public static final CommandSpec check_registered_user = new CommandSpec.Builder()
+		.parameters("email","password_hash")
+		.build()
+	;
+
+	@Command public void check_registered_user(IPrintWriter out,Interpreter interp) {
+		String email = interp.getArgString("email");
+		String pwd = interp.getArgString("password_hash");
+		User user = site.getUserFromEmail(email);
+		out.print( user != null && user.isRegistered() && user.checkPasscookie(pwd) );
+	}
+
+	public static final CommandSpec get_or_create_user = CommandSpec.DO()
+		.parameters("email")
+		.build()
+	;
+
+	@Command public void get_or_create_user(IPrintWriter out,ScopedInterpreter<UserNamespace> interp)
+		throws ModelException.EmailFormat
+	{
+		String email = interp.getArgString("email");
+		if (!new MailAddress(email).isValid())
+			throw new ModelException.EmailFormat(email);
+		User user = site.getOrCreateUser(email);
+		out.print( interp.getArg(new UserNamespace(user),"do") );
+	}
+
+	public static final CommandSpec exists_user_for_email = new CommandSpec.Builder()
+		.dotParameter("email")
+		.build()
+	;
+
+	@Command public void exists_user_for_email(IPrintWriter out,Interpreter interp)
+		throws ModelException.EmailFormat
+	{
+		String email = interp.getArgString("email");
+		User user = site.getUserFromEmail(email);
+		out.print( user != null );
+	}
+
+	public static final CommandSpec get_user_from_email = CommandSpec.DO()
+		.parameters("email")
+		.build()
+	;
+
+	@Command public void get_user_from_email(IPrintWriter out,ScopedInterpreter<UserNamespace> interp)
+		throws ModelException.EmailFormat
+	{
+		String email = interp.getArgString("email");
+		User user = site.getUserFromEmail(email);
+		if( user==null ) {
+			out.print( (String)null );
+			return;
+		}
+		out.print( interp.getArg(new UserNamespace(user),"do") );
+	}
+
+	public static final CommandSpec has_authenticated_user_with_name = new CommandSpec.Builder()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void has_authenticated_user_with_name(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print( site.getUserFromName(name) != null );
+	}
+
+	public static final CommandSpec get_authenticated_user_with_name = CommandSpec.DO()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void get_authenticated_user_with_name(IPrintWriter out,ScopedInterpreter<UserNamespace> interp)
+		throws ModelException.EmailFormat
+	{
+		String name = interp.getArgString("name");
+		User user = site.getUserFromName(name);
+		out.print( interp.getArg(new UserNamespace(user),"do") );
+	}
+
+	public static final CommandSpec banned_users = CommandSpec.DO;
+
+	@Command public void banned_users(IPrintWriter out,ScopedInterpreter<UserNamespace.UserList> interp) {
+		List<User> banned = Permissions.getBannedUsers(site());
+		Object block = interp.getArg(new UserNamespace.UserList(banned),"do");
+		out.print(block);
+	}
+
+	@Command public void administrator_notice(IPrintWriter out,Interpreter interp) {
+		out.print( SystemProperties.get("administrator.notice") );
+	}
+
+	@Command public void administrator_notice_version(IPrintWriter out,Interpreter interp) {
+		String version = SystemProperties.get("administrator.notice.version");
+		out.print( version == null? "0" : version );
+	}
+
+
+/*
+	public static final CommandSpec macro_text = new CommandSpec.Builder()
+		.parameters("macro")
+		.optionalParameters("namespace")
+		.build()
+	;
+
+	@Command public void macro_text(IPrintWriter out,Interpreter interp) {
+		String macro = interp.getArgString("macro");
+		String namespace = interp.getArgString("namespace");
+		out.print( site.getTemplates().source().getMacro(macro,namespace) );
+	}
+*/
+
+
+	public static final CommandSpec log = new CommandSpec.Builder()
+		.dotParameter("text")
+		.outputtedParameters()
+		.build()
+	;
+
+	@Command public void log(IPrintWriter out,Interpreter interp) {
+		interp.setEncoder(Encoder.TEXT);
+		String text = interp.getArgString("text");
+		NamlLogger.getLogger(site).log(text);
+	}
+
+	@Command public void get_log(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( NamlLogger.getLogger(site).getLog() ) );
+	}
+
+	public static final CommandSpec clear_log = CommandSpec.NO_OUTPUT;
+
+	@Command public void clear_log(IPrintWriter out,Interpreter interp) {
+		NamlLogger.removeLogger(site);
+	}
+
+
+	@Command public void lucene_is_ready(IPrintWriter out,Interpreter interp) {
+		out.print( Lucene.isReady(site) );
+	}
+
+	@Command public void support_url(IPrintWriter out,Interpreter interp) {
+		out.print( Jtp.supportUrl() );
+	}
+
+	@Command public void dot_pack(IPrintWriter out,Interpreter interp) {
+	}
+
+
+
+
+
+	public static final CommandSpec call_later = new CommandSpec.Builder()
+		.dotParameter("param")
+		.optionalParameters("value")
+		.build()
+	;
+
+	private Map<String,Set<String>> callLaterMap = new LinkedHashMap<String,Set<String>>();
+
+	@Command public void call_later(IPrintWriter out,Interpreter interp) {
+		String param = interp.getArgString("param");
+		Set<String> values = callLaterMap.get(param);
+		if( values == null ) {
+			values = new LinkedHashSet<String>();
+			callLaterMap.put(param,values);
+		}
+		String value = interp.getArgString("value");
+		if( value == null )
+			value = "";
+		values.add(value);
+	}
+
+	private static final int JS_PATH_LIM = 1200;
+
+	@Command public void load_call_later_script(IPrintWriter out,Interpreter interp)
+		throws IOException, ServletException
+	{
+		if( !callLaterMap.isEmpty() ) {
+			String jsPath = "/template/NamlServlet.jtp?macro=js_page";
+			StringBuilder path = new StringBuilder();
+			path.append(jsPath);
+			for (Map.Entry<String, Set<String>> entry : callLaterMap.entrySet()) {
+				String param = entry.getKey();
+				Set<String> values = entry.getValue();
+				boolean isFirst = true;
+				for (String value : values) {
+					if (path.length() > JS_PATH_LIM) {
+						loadScript(out,path);
+						path.setLength(0);
+						path.append(jsPath);
+						isFirst = true;
+					}
+					if( isFirst ) {
+						path.append('&').append(param).append('=');
+						isFirst = false;
+					} else {
+						path.append('|');
+					}
+					path.append(HtmlUtils.urlEncode(HtmlUtils.urlEncode(value)));
+				}
+			}
+			loadScript(out,path);
+		}
+		callLaterMap = null;
+	}
+
+	private static void loadScript(IPrintWriter out,Object path) {
+		out.print( "<script type='text/javascript'>\n" );
+		out.print( "var scriptUrl = '" + path + "';\n" );
+		out.print( "scriptUrl += '&_=' + Math.floor(Math.random()*9999);\n" );
+		out.print( "$.getScript(scriptUrl, function() { Nabble.resizeFrames(); });\n" );
+		out.print( "</script>\n" );
+	}
+
+	public static final CommandSpec get_parameters_from_run_later = CommandSpec.DO()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void get_parameters_from_run_later(IPrintWriter out,ScopedInterpreter<RequestNamespace.ParameterValueList> interp) {
+		String name = interp.getArgString("name");
+		Set<String> values = callLaterMap.get(name);
+		List<String> list = values == null ? Collections.<String>emptyList() : new ArrayList<String>(values);
+		Object block = interp.getArg(new RequestNamespace.ParameterValueList(list),"do");
+		out.print(block);
+	}
+
+	// Tweaks & NAML code
+
+	public static final CommandSpec macro_source = new CommandSpec.Builder()
+		.parameters("id")
+		.optionalParameters("base", "breadcrumbs")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.build()
+	;
+
+	@Command public void macro_source(IPrintWriter out,ScopedInterpreter<MacroSourceNamespace> interp) {
+		MacroSourceNamespace ns = new MacroSourceNamespace(site(),interp.getArgString("id"), interp.getArgString("base"), interp.getArgString("breadcrumbs"));
+		out.print( interp.getArg( ns, "do" ) );
+	}
+
+	public static final CommandSpec macro_editor = CommandSpec.DO()
+		.optionalParameters("id", "base", "breadcrumbs")
+		.build()
+	;
+
+	@Command public void macro_editor(IPrintWriter out,ScopedInterpreter<MacroEditorNamespace> interp) {
+		MacroEditorNamespace ns = new MacroEditorNamespace(site(),interp.getArgString("id"),interp.getArgString("base"), interp.getArgString("breadcrumbs"));
+		out.print( interp.getArg( ns, "do" ) );
+	}
+
+	public static final CommandSpec macro_search = CommandSpec.DO()
+		.parameters("query", "search_by")
+		.build();
+
+	@Command public void macro_search(IPrintWriter out,ScopedInterpreter<MacroSourceNamespace.Commands> interp)
+		throws IOException, ServletException, CompileException {
+		String query = interp.getArgString("query");
+		String searchBy = interp.getArgString("search_by");
+		boolean byName =  "name".equals(searchBy);
+		Map<String,MacroSourceNamespace.CommandInfo> map = new TreeMap<String, MacroSourceNamespace.CommandInfo>();
+		if (query != null) {
+			query = query.toLowerCase();
+			Pattern ptn = compileWildcardPattern(query);
+			Program program = site().getProgram();
+			List<Source> sources = program.getSources();
+			for (Source s : sources) {
+				for (Macro m : s.getMacros()) {
+					if (byName) {
+						boolean isOverridden = program.getMacroWhichOverrides(m) != null;
+						if (isOverridden)
+							continue;
+					}
+					boolean matches = byName? ptn.matcher(m.getName().toLowerCase()).matches() : ptn.matcher(m.element.toString().toLowerCase()).find();
+					if (matches) {
+						// Now we look for the first usage of this meaning
+						MacroSourceNamespace.CommandInfo info = getCommandInfo(m);
+						String key = m.getName() + '-' + MacroSourceNamespace.csv(m.getRequiredNamespaces());
+						map.put(key, info);
+					}
+				}
+			}
+		}
+		List<MacroSourceNamespace.CommandInfo> list = new ArrayList<MacroSourceNamespace.CommandInfo>(map.values());
+		Object block = interp.getArg(new MacroSourceNamespace.Commands(list),"do");
+		out.print(block);
+	}
+
+	private MacroSourceNamespace.CommandInfo getCommandInfo(Macro m) {
+		MacroSourceNamespace.CommandInfo info;
+		Set<Usage> usages = site.getProgram().getUsages(m);
+		if (usages == null || usages.size() == 0) {
+			info = new MacroSourceNamespace.CommandInfo(m, null, null);
+		} else {
+			Usage u = usages.iterator().next();
+			String usageBase = MacroSourceNamespace.asBaseParam(u.baseIds());
+			String breadcrumbs = MacroSourceNamespace.asBreadcrumbsParam(u.macroPath());
+			info = new MacroSourceNamespace.CommandInfo(m, usageBase, breadcrumbs);
+		}
+		return info;
+	}
+
+	private static Pattern compileWildcardPattern(String query) {
+		String[] a = query.split("\\*",-1);
+		StringBuilder regex = new StringBuilder();
+		boolean isFirst = true;
+		for( String s : a ) {
+			if( isFirst )
+				isFirst = false;
+			else
+				regex.append( ".*" );
+			if( s.length() > 0 )
+				regex.append( Pattern.quote(s) );
+		}
+		return Pattern.compile(regex.toString());
+	}
+
+	@Command public void has_custom_macros(IPrintWriter out,Interpreter interp)
+		throws CompileException
+	{
+		out.print(ModuleManager.getCustomMacros(site.getProgram()).size() > 0);
+	}
+
+	public static final CommandSpec custom_macros = CommandSpec.DO;
+
+	@Command public void custom_macros(IPrintWriter out,ScopedInterpreter<MacroSourceNamespace.Commands> interp)
+		throws IOException, ServletException, CompileException
+	{
+		Collection<Macro> macros = ModuleManager.getCustomMacros(site.getProgram());
+		printMacros(macros, out, interp);
+	}
+
+	private void printMacros(Collection<Macro> macros, IPrintWriter out, ScopedInterpreter<MacroSourceNamespace.Commands> interp) {
+		List<MacroSourceNamespace.CommandInfo> list = new ArrayList<MacroSourceNamespace.CommandInfo>();
+		Set<String> names = new HashSet<String>();
+		for (Macro m : macros) {
+			String key = m.getName() + '-' + MacroSourceNamespace.csv(m.getRequiredNamespaces());
+			if (names.contains(key))
+				continue;
+			names.add(key);
+			MacroSourceNamespace.CommandInfo info = getCommandInfo(m);
+			list.add(info);
+		}
+		Collections.sort(list, MacroSourceNamespace.CommandInfo.MACRO_NAME_COMPARATOR);
+		Object block = interp.getArg(new MacroSourceNamespace.Commands(list),"do");
+		out.print(block);
+	}
+
+	@Command public void has_configuration_macros(IPrintWriter out,Interpreter interp)
+		throws CompileException
+	{
+		out.print(ModuleManager.getConfigurationMacros(site.getProgram()).size() > 0);
+	}
+
+	public static final CommandSpec configuration_macros = CommandSpec.DO;
+
+	@Command public void configuration_macros(IPrintWriter out,ScopedInterpreter<MacroSourceNamespace.Commands> interp)
+		throws IOException, ServletException, CompileException
+	{
+		Collection<Macro> macros = ModuleManager.getConfigurationMacros(site.getProgram());
+		printMacros(macros, out, interp);
+	}
+
+	@Command public void has_tweak_exception(IPrintWriter out,Interpreter interp)
+		throws CompileException
+	{
+		out.print(site.getTweakException() != null);
+	}
+
+	@Command public void tweak_exception_message(IPrintWriter out,Interpreter interp)
+		throws CompileException
+	{
+		Exception e = site.getTweakException();
+		out.print(e == null? null : e.getMessage());
+	}
+
+	public static final CommandSpec advanced_editor_path = new CommandSpec.Builder()
+		.parameters("prev_url")
+		.build();
+
+	@Command public void advanced_editor_path(IPrintWriter out,Interpreter interp) {
+		String prev = interp.getArgString("prev_url");
+		out.print("/template/NamlEditor.jtp?prev=" + HtmlUtils.urlEncode(prev));
+	}
+
+	static boolean isCompiledAll(Program program) {
+		Meaning meaning = program.getMeaning(COMPILED_ALL_CHECK_ID);
+		return meaning != null && program.isCompiled(meaning);
+	}
+
+	private static final String COMPILED_ALL_CHECK_ID = "compiled_all_check!nabble:compile_all.naml";
+
+	@Command public void is_compiled_all(IPrintWriter out, Interpreter interp) {
+		Program program = interp.template().program();
+		out.print(isCompiledAll(program));
+	}
+
+	@Command public void run_compile_all(IPrintWriter out,Interpreter interp) throws CompileException {
+		CompileTest.compileAll(site.getProgram());
+	}
+
+	public static final CommandSpec naml_configuration = CommandSpec.DO;
+
+	@Command public void naml_configuration(IPrintWriter out,ScopedInterpreter<NamlConfigurationNamespace> interp)
+		throws IOException, ServletException, CompileException
+	{
+		out.print(interp.getArg(new NamlConfigurationNamespace(),"do"));
+	}
+
+	@Command public void js_basic_nabble_functions(IPrintWriter out,Interpreter interp)
+		throws CompileException
+	{
+		StringWriter sw = new StringWriter();
+		PrintWriter jsOut = new PrintWriter(sw);
+		Javascript.basicNabbleFunctions(jsOut);
+		jsOut.close();
+		out.print(sw.toString());
+	}
+
+	public static final CommandSpec NAME = new CommandSpec.Builder()
+		.dotParameter("name")
+		.build()
+	;
+
+	public static final CommandSpec has_site_property = NAME;
+
+	@Command public void has_site_property(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print(site.getProperty(name) != null);
+	}
+
+	public static final CommandSpec get_site_property = NAME;
+
+	@Command public void get_site_property(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print(site.getProperty(name));
+	}
+
+	public static final CommandSpec delete_site_property = CommandSpec.NO_OUTPUT()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void delete_site_property(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		site.setProperty(name, null);
+		site.update();
+	}
+
+	public static final CommandSpec set_site_property = CommandSpec.NO_OUTPUT()
+		.parameters("name", "value")
+		.build()
+	;
+
+	@Command public void set_site_property(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		String value = interp.getArgString("value");
+		site.setProperty(name, value);
+		site.update();
+	}
+
+
+	public static final CommandSpec to_html_list = CommandSpec.DO()
+		.parameters("text")
+		.build()
+	;
+
+	@Command public void to_html_list(IPrintWriter out,ScopedInterpreter<HtmlListNamespace> interp) {
+		String text = interp.getArgString("text");
+		HtmlListNamespace html = new HtmlListNamespace(new Html(text), null, Message.Format.HTML);
+		interp.getArgString(html,"do");  // processing, no output
+		out.print(html);
+	}
+
+
+	@Command public void is_embarrassing(IPrintWriter out,Interpreter interp) 	{
+		out.print(site.isEmbarrassing());
+	}
+
+	public static final Set<Long> sitesRunningBackup = Collections.synchronizedSet(new HashSet<Long>());
+
+	@Command public void is_running_backup(IPrintWriter out,Interpreter interp) {
+		out.print(sitesRunningBackup.contains(site.getId()));
+	}
+
+	public static final CommandSpec make_backup = CommandSpec.NO_OUTPUT()
+		.parameters("email")
+		.build()
+	;
+
+	@Command public void make_backup(IPrintWriter out,Interpreter interp) {
+		synchronized (sitesRunningBackup) {
+			if (sitesRunningBackup.contains(site.getId()))
+				return;
+			sitesRunningBackup.add(site.getId());
+			final String email = interp.getArgString("email");
+			Executors.executeSometime(new Runnable(){
+				public void run() {
+					File file = site.backup();
+					Template template = site.getTemplate( "backup email",
+						BasicNamespace.class, NabbleNamespace.class
+					);
+					Map<String,Object> params = new HashMap<String,Object>();
+					params.put("email", email);
+					params.put("file", file.getName());
+					template.run( TemplatePrintWriter.NULL, params,
+						new BasicNamespace(template),
+						new NabbleNamespace(site)
+					);
+					sitesRunningBackup.remove(site.getId());
+				}
+			});
+		}
+	}
+
+	@Command public void server_url(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( Jtp.defaultContextUrl() ) );
+	}
+
+	@Command public void javascript_version(IPrintWriter out,Interpreter interp) {
+		out.print(Shared.javascriptVersion);
+	}
+
+	@Command public void css_version(IPrintWriter out,Interpreter interp) {
+		out.print(Shared.cssVersion);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NamespaceUtils.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,24 @@
+package nabble.view.web.template;
+
+import nabble.naml.compiler.Interpreter;
+import nabble.model.Site;
+
+
+public class NamespaceUtils {
+
+	public static int getInt(Interpreter interp, String attributeName, String errorMessage) {
+		return getInt(interp, attributeName, null, errorMessage);
+	}
+
+	public static int getInt(Interpreter interp, String attributeName, Integer defaultValue, String errorMessage) {
+		String value = interp.getArgString(attributeName);
+		if (value == null && defaultValue != null)
+			return defaultValue;
+		try {
+			return Integer.valueOf(value.trim());
+		} catch(NumberFormatException e) {
+			throw new RuntimeException(errorMessage,e);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NamlConfigurationNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,56 @@
+package nabble.view.web.template;
+
+import nabble.model.Site;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+
+
+@Namespace(
+	name = "naml_configuration",
+	global = false
+)
+public class NamlConfigurationNamespace {
+
+	public static final CommandSpec get_value = new CommandSpec.Builder()
+		.parameters("name", "default")
+		.build()
+	;
+
+	@Command public void get_value(IPrintWriter out, Interpreter interp) {
+		Site site = NabbleNamespace.current().site();
+		String name = interp.getArgString("name");
+		String value = site.getConfigurationValue(name);
+		if( value == null )
+			value = interp.getArgString("default");
+		out.print(value);
+	}
+
+	public static final CommandSpec set = new CommandSpec.Builder()
+		.parameters("name", "value", "naml")
+		.optionalParameters("default")
+		.build()
+	;
+
+	@Command public void set(IPrintWriter out, Interpreter interp) {
+		Site site = NabbleNamespace.current().site();
+		String name = interp.getArgString("name");
+		String value = interp.getArgString("value");
+		String defaultValue = interp.getArgString("default");
+		if (defaultValue != null && value.equals(defaultValue)) {
+			site.deleteConfiguration(name);
+		} else {
+			String naml = interp.getArgString("naml");
+			site.saveConfiguration(name,value,naml);
+		}
+		out.print(name);
+	}
+
+	@Command public void apply(IPrintWriter out, Interpreter interp) {
+		Site site = NabbleNamespace.current().site();
+		site.update();
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NamlDownload.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,72 @@
+package nabble.view.web.template;
+
+import fschmidt.util.java.IoUtils;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.UrlMappable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+
+public class NamlDownload extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/naml/naml_.+_\\d+.zip$");
+
+	private static final DateFormat DATE = new SimpleDateFormat("yyyyMMdd");
+
+	public static String getFilePath(Site site) {
+		String filename = Jtp.subjectEncode(site.getRootNode().getSubject());
+		return "/naml/naml_" + filename + '_' + DATE.format(new Date()) + ".zip";
+	}
+
+	public Map<String, String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if (!m.find())
+			throw new RuntimeException();
+		return Collections.emptyMap();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		Map<String,String> tweaks = site.getCustomTweaks();
+		try {
+			ByteArrayOutputStream baos = new ByteArrayOutputStream();
+			ZipOutputStream zout = new ZipOutputStream(baos);
+			for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
+				String name = entry.getKey();
+				String content = entry.getValue();
+				zout.putNextEntry(new ZipEntry(name + ".naml"));
+				zout.write(content.getBytes());
+				zout.flush();
+			}
+			zout.close();
+			byte[] zipFileContents = baos.toByteArray();
+			baos.close();
+			response.setContentType("application/zip");
+			IoUtils.copyAll(new ByteArrayInputStream(zipFileContents), response.getOutputStream());
+		} catch(IOException e) {
+			throw new RuntimeException("Backup generation error", e);
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NamlEditor.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,398 @@
+
+package nabble.view.web.template;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.DailyNumber;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.modules.ModuleManager;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Macro;
+import nabble.naml.compiler.Meaning;
+import nabble.naml.compiler.Source;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+
+public final class NamlEditor extends HttpServlet {
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request, response);
+		if (user == null) {
+			simpleLoginPage("You must login to edit the NAML code of this application.", request, response);
+			return;
+		}
+		boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+		boolean isSysAdmin = Permissions.isSysAdmin(user);
+
+		if (!isSiteAdmin && !isSysAdmin) {
+			simpleLoginPage("You must login to edit the NAML code of this application.", request, response);
+			return;
+		}
+		buildPage(request, response);
+	}
+
+	public static void buildPage(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		Map<String,String> tweaks = new TreeMap<String,String>(site.getCustomTweaks());
+		Node rootNode = site.getRootNode();
+		Exception exception = site.getTweakException();
+
+		String macroDef = null;
+		String selectedFileName = null;
+		String overridenBody = null;
+		String meaningId = request.getParameter("id");
+		if (meaningId != null) {
+			Meaning meaning = site.getProgram().getMeaning(meaningId);
+			Macro macro = (Macro) meaning;
+
+			String macroType = macro.getType().toString().toLowerCase();
+			String macroBody = macro.element.toString();
+			int posOpen = macroBody.indexOf('<');
+			int posClose = macroBody.indexOf('>', posOpen+1);
+			macroDef = macroBody.substring(posOpen, posClose);
+			macroDef = macroDef.replaceFirst("<"+macroType, "<override_"+macroType);
+			int posClosingTag = macroBody.lastIndexOf("</");
+			String closingTag = "</override_" + macroType + '>';
+			overridenBody = macroDef + macroBody.substring(posClose, posClosingTag) + closingTag;
+			for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
+				String name = entry.getKey();
+				String content = entry.getValue();
+				if (content.contains(macroDef)) {
+					selectedFileName = name;
+					break;
+				}
+			}
+		}
+
+		String previousUrl = request.getParameter("prev");
+
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n	<head>\n		<META NAME=\"robots\" CONTENT=\"noindex,nofollow\"/>\n		<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />\n		<title>Naml Editor - " );
+		out.print( (rootNode.getSubject()) );
+		out.print( "</title>\n		" );
+ Shared.head(request, response, false); 
+		out.print( "\n		<style type=\"text/css\">\n			div.main-content {\n				position:absolute;\n				top:5.2em;\n				bottom:1.3em;\n				right:.8em;\n				left:.8em;\n			}\n			div.big-title {\n				margin:.5em 0;\n			}\n			table.vertical-control {\n				border-collapse:collapse;\n				width:100%;\n				height:100%;\n				position:absolute;\n				top:0;\n				bottom:0;\n			}\n			table.vertical-control td {\n				padding:0;\n				vertical-align:top;\n			}\n			table.vertical-control td.options {\n				width:13em;\n				padding-top:.2em;\n			}\n			table.vertical-control td.details {\n				padding: .2em;\n			}\n			ul.vertical-control {\n				width:100%;\n				list-style-type:none;\n				padding:0;\n				margin:.5em 0;\n			}\n			ul.vertical-control li {\n				padding: .3em;\n				white-space:nowrap;\n				cursor: pointer;\n				font-weight:normal;\n				width:13em;\n				overflow: hidden;\n				border-top-left-radius:5px;\n				border-bottom-left-radius:5px;\n				-moz-border-radius-topleft:5px;\n				-moz-border-radius-bottomleft:5px;\n			}\n			ul.vertical-control li.selected {\n				cursor: default;\n				font-weight:bold;\n			}\n			#options-ul {\n				overflow-x:hidden !important;\n				overflow-y:auto !important;\n			}\n			div.more {\n				position:absolute;\n				left:0;\n				width:13em;\n				bottom:0;\n				border-top: 1px dotted #ddd;\n			}\n			img.toolbar-icon {\n				vertical-align: -25%;\n			}\n			div.default {\n				position: absolute;\n				top: .2em;\n				bottom: 0;\n				margin-bottom: .2em;\n				height: auto;\n				overflow: auto;\n			}\n			div.advanced-option-body {\n				padding: 0 1.5em 1.5em;\n			}\n\n			.CodeMirror-line-numbers {\n				font-family: verdana, arial, sans-serif;\n				font-size: 11pt;\n				width: 2.2em;\n				color: #aaa;\n				background-color: #eee;\n				text-align: right;\n				padding-right: .3em;\n				line-height: normal;\n			}\n\n			span.xml-tagname {color: #A0B;}\n			span.xml-attribute {color: #281;}\n			span.xml-punctuation {color: black;}\n			span.xml-attname {color: #00F;}\n			span.xml-comment {color: #A70;}\n			span.xml-cdata {color: #48A;}\n			span.xml-processing {color: #999;}\n			span.xml-entity {color: #A22;}\n			span.xml-error {color: #F00 !important;}\n			span.xml-text {color: black;}\n\n			div.status-label {\n				float:right;\n				font-weight:bold;\n				width:6em;\n				text-align:center;\n				line-height:1.2em;\n				margin:-.2em 0 0 .5em;\n				padding:.35em;\n			}\n			div.no-error {\n				color: #656565;\n				cursor: text;\n			}\n			div.has-error {\n				color: white;\n				cursor: pointer;\n				background: url('/gradients/v20_EE9999_EE5555') #EE5555 repeat-x;\n			}\n			#error-message {\n				position:absolute;\n				display:none;\n				z-index:1000;\n				padding:0 1em 1em;\n				margin-top:.2em;\n				top:5em;\n				left:10%;\n				bottom:10%;\n				right:.7em;\n				overflow:auto;\n				display:none;\n			}\n		</style>\n\n		<script src=\"/util/codemirror/js/codemirror.js\"></script>\n		<script src=\"/util/codemirror/js/highlight.js\"></script>\n		<script src=\"/util/codemirror/js/stringstream.js\"></script>\n		<script src=\"/util/codemirror/js/tokenize.js\"></script>\n		<script src=\"/util/codemirror/js/parsexml.js\"></script>\n\n		<script type=\"text/javascript\">\n			var configFileName = \"" );
+		out.print( (ModuleManager.CONFIGURATION_TWEAK) );
+		out.print( "\";\n			function setVisible(name, show) {\n				if (show) $('#'+name).show();\n				else $('#'+name).hide();\n			};\n			var modified = [];\n			function setModified(id) {\n				if (modified.indexOf(id) >= 0)\n					return;\n				modified.push(id);\n				if (id == 'removed')\n					return;\n				var $tab = $('#tab_'+id);\n				var html = $tab.html();\n				$tab.html(html.replace(/file\\.png/, 'file_modified.png'));\n				setVisible('modified-warning',true);\n			};\n			function resetModified() {\n				for (var i = 0; i < modified.length; i++) {\n					if (modified[i] == 'removed')\n						continue;\n					var $tab = $('#tab_'+modified[i]);\n					var html = $tab.html();\n					$tab.html(html.replace(/file_modified\\.png/, 'file.png'));\n				}\n				setVisible('modified-warning',false);\n				modified = [];\n			}\n			window.onbeforeunload = function() {\n				if (modified.length > 0)\n					return 'You have unsaved changes.\\nDo you really want to leave without saving?';\n			};\n			var fileImg = '<img src=\"/images/file.png\" style=\"vertical-align:-20%\"/> ';\n			Array.prototype.remove = function(s){\n				for(i=0; i<this.length; i++){\n					if (s == this[i])\n						this.splice(i, 1);\n				}\n			}\n			var advancedOptions = {\n				id: 'advanced_options',\n				name: 'Advanced Options',\n				content: null\n			};\n			function isDefaultTab(f) {\n				return f == advancedOptions;\n			};\n\n			var files = [];\n			" );
+
+					int i = 0;
+					for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
+						String name = entry.getKey();
+						String content = entry.getValue();
+						
+		out.print( "\nfiles.push({\nid: " );
+		out.print( (i++) );
+		out.print( ",\nname: \"" );
+		out.print( (HtmlUtils.javascriptStringEncode(name)) );
+		out.print( "\",\ncontents: \"" );
+		out.print( (HtmlUtils.javascriptStringEncode(content)) );
+		out.print( "\"\n});\n" );
+ } 
+		out.print( "\nfunction addFile(file) {\n$('#options-ul').append('<li id=\"tab_'+file.id+'\">'+fileImg+file.name+'</li>');\nclickableTab(file);\n\nvar textareaId = 'textarea_'+file.id;\n$('#details').append('<div id=\"div_'+file.id+'\" style=\"display:none\"><textarea id=\"'+textareaId+'\"></textarea></div>');\nvar $textarea = $('#'+textareaId);\n$textarea.val(file.contents);\n\nfile.editor = CodeMirror.fromTextArea(textareaId, {\n	parserfile: \"parsexml.js\",\n	stylesheet: \"/util/codemirror/css/xmlcolors.css\",\n	path: \"/util/codemirror/js/\",\n	lineNumbers: true,\n	indentUnit: 4,\n	onChange: function() { setModified(file.id); }\n});\n\nvar $wrapping = $textarea.next();\n$wrapping.css({\n	position: 'absolute',\n	top: '2.5em',\n	bottom: 0,\n	marginBottom: '.2em',\n	height: 'auto'\n});\n$wrapping.attr('id', 'wrapping_'+file.id)\n$wrapping.addClass('no-bg-color');\n\n/* File Toolbar */\nvar toolbar = '<div id=\"toolbar_'+file.id+'\" class=\"shaded-bg-color\" style=\"padding:.2em .4em .3em;text-align:right\">';\ntoolbar += '<div class=\"float-left\" style=\"font-style:italic;padding:.3em\">'+file.name+'</div>';\ntoolbar += '<button class=\"toolbar\" style=\"padding:.1em .3em\" onclick=\"renameFile()\"><img class=\"toolbar-icon\" src=\"/images/edit_sm.png\"/> Rename</button> ';\ntoolbar += '<button class=\"toolbar\" style=\"padding:.1em .3em\" onclick=\"removeFile()\"><img class=\"toolbar-icon\" src=\"/images/remove_sm.png\"/> Remove</button>';\ntoolbar += '</div>';\n$textarea.after(toolbar);\n};\nfunction clickableTab(f) {\nvar $tab = $('#tab_'+f.id);\n$tab.click(function() {\n	select(f);\n});\ntabHover($tab);\n};\nfunction tabHover($tab) {\n$tab.hover(function() {\n		if (!$(this).hasClass('selected'))\n			$(this).addClass('light-bg-color');\n	},\n	function() {\n		$(this).removeClass('light-bg-color');\n	}\n);\n};\n\nvar initialized = [];\nfunction showFile(file) {\n$('#div_'+file.id).show();\nif (initialized.indexOf(file.id) == -1) {\n	var cc = Nabble.get('toolbar_'+file.id);\n	$('#wrapping_'+file.id).css('top', $(cc).outerHeight());\n	var frame = window.frames[window.frames.length-1];\n	var doc = window.document;\n	$(frame.document).click(function() { $(doc).click(); });\n	initialized.push(file.id);\n}\n};\n\nvar selected = null;\nfunction select(f) {\nif (selected != f) {\n	$('li').removeClass('selected dark-bg-color');\n	var $tab = $('#tab_'+f.id);\n	$tab.addClass('selected dark-bg-color');\n	$('#details').children().hide();\n	showFile(f);\n	selected = f;\n	layout();\n} else if (f == null || files.length == 0)\n	setVisible('no-selection',true);\n};\nfunction layout() {\nif (selected) {\n	var detailsWidth = $('#details').width();\n	if (!isDefaultTab(selected)) {\n		var $div = $('#div_'+selected.id);\n		var $lineNumbers = $('div.CodeMirror-line-numbers', $div);\n		var width = detailsWidth - $lineNumbers.width() - 5;\n		$('div.CodeMirror-wrapping', $div).width(Math.round(width)+'px');\n	} else {\n		$('div.default').width(Math.round(detailsWidth - 30)+'px');\n	}\n}\nvar $ul = $('#options-ul');\nvar uTop = $ul.position().top;\nvar mTop = $('#more').position().top;\n$ul.height(mTop-uTop-10);\n};\nfunction newFile() {\nvar name = prompt('Name of the file:');\nif (name == configFileName) {\n	alert('You cannot create a file with this name.');\n	return;\n}\nif (name) {\n	var f = {\n		id: new Date().getTime(),\n		name: name,\n		contents: ''\n	};\n	files.push(f);\n	addFile(f);\n	select(f);\n	setModified(f.id);\n}\n};\nfunction renameFile() {\nif (selected) {\n	name = prompt(\"Name of the file:\", selected.name);\n	if (name && name !='null' && Nabble.trim(name).length > 0) {\n		selected.name = Nabble.trim(name);\n		$tab = $('#tab_'+selected.id).html(fileImg + name);\n		$toolbar = $('#toolbar_'+selected.id+' div').html(name);\n		setModified(selected.id);\n	}\n}\n};\nfunction removeFile() {\nif (selected && confirm(\"Do you really want to delete \" + selected.name + \"?\")) {\n	var i = files.indexOf(selected);\n	files.remove(selected);\n	setModified('removed');\n	$('#tab_'+selected.id+',#div_'+selected.id).remove();\n	i = i > 0? i-1 : files.length > 0? 0 : -1;\n	if (i == -1)\n		setVisible('no-selection',true);\n	else\n		select(files[i]);\n}\n};\nfunction saveChanges() {\nnotice('Saving data... please wait');\nvar params = {};\nparams['fileCount'] = files.length;\nvar c = 0;\nfor (var i=0; i < files.length; i++) {\n	var name = files[i].name;\n	params['name'+c] = name;\n	params['contents'+c] = files[i].editor.getCode();\n	c++;\n}\n$.post(\"./NamlEditor$Save.jtp\", params,\n	function(data){\n		if (data == null || data.length == 0) {\n			setError(null);\n			notice('Data successfully saved, no errors found', 2000, 2000);\n			resetModified();\n		} else {\n			setError(data);\n			notice('Data NOT saved, please fix errors', 5000, 2000);\n		}\n	}\n);\n};\n\nfunction getOrCreateFile(name) {\nfor (var i=0; i<files.length; i++) {\n	if (files[i].name == name)\n		return files[i];\n}\nfiles.push({\n	id: new Date().getTime(),\n	name: name,\n	contents:''\n});\nreturn files[files.length-1];\n};\nfunction getLineNumber(s, token) {\nvar line = 1;\nvar pos = s.indexOf(token);\nfor (var i=0; i < pos; i++) {\n	if (s.charAt(i) == '\\n')\n		line++;\n}\nreturn line;\n};\nfunction startDefaultTabs() {\n/* Advanced options tab*/\nvar $tabs = $('#tab_advanced_options');\n$tabs.click(function() { select(advancedOptions); });\n};\nfunction setError(err) {\nvar $errorMsg = $('#error-message');\n$errorMsg.html(err? err : '');\nvar $status = $('#error-status');\nif (err) {\n	var visible = false;\n	function errorClick(e) {\n		e.stopPropagation();\n		if (visible) {\n			$errorMsg.slideUp('fast', function() { $errorMsg.hide() });\n			$status.html('View Error');\n		} else {\n			$errorMsg.slideDown('fast');\n			$status.html('Hide Error');\n		}\n		visible = !visible;\n	};\n	$status.addClass('has-error').removeClass('no-error').html('View Error').click(errorClick).click();\n	$(document).click(function(e) {\n		if ($(e.target).attr('id') == 'error-message')\n			return;\n		var $parents = $(e.target).parents();\n		if ($parents.hasClass('error-message'))\n			return;\n		visible = true;\n		$status.click();\n	});\n} else {\n	$status.addClass('no-error').removeClass('has-error').html('No Errors').unbind('click');\n	$(document).unbind('click');\n}\n};\nvar error = " );
+		out.print( (exception == null? "null" : "\"" + HtmlUtils.javascriptStringEncode(getErrorMessage(exception)) + "\"") );
+		out.print( ";\n\nfunction startEditors() {\nnotice('Starting editor... please wait');\nstartDefaultTabs();\nsetError(error);\nlayout();\nvar selectedFileName = " );
+		out.print( (selectedFileName == null? "null" : "\""+selectedFileName+"\"") );
+		out.print( ";\nvar selectedFile = files[0];\nvar macroDef = " );
+		out.print( (macroDef == null? "null" : "\""+HtmlUtils.javascriptStringEncode(macroDef)+"\"") );
+		out.print( ";\nvar overridenBody = " );
+		out.print( (overridenBody == null? "null" : "\""+HtmlUtils.javascriptStringEncode(overridenBody)+"\"") );
+		out.print( ";\nvar macroLineNumber = 0;\nif (selectedFileName != null) {\n	var f = getOrCreateFile(selectedFileName);\n	macroLineNumber = getLineNumber(f.contents,macroDef);\n} else if (overridenBody != null) {\n	var f = getOrCreateFile('tweaks');\n	selectedFileName = f.name;\n	f.contents = Nabble.trim(f.contents);\n	f.contents = (f.contents.length > 0? f.contents + '\\n\\n':'') + overridenBody;\n	macroLineNumber = getLineNumber(f.contents,macroDef);\n} else {\n	var f = files.length == 0? getOrCreateFile('tweaks') : files[0];\n	selectedFileName = f.name;\n}\nfor (var i=0; i < files.length; i++) {\n	addFile(files[i]);\n	if (files[i].name == selectedFileName)\n		selectedFile = files[i];\n}\nselect(selectedFile);\nif (selectedFile && macroLineNumber > 0) {\n	function goToLine() {\n		var $lines = $('#wrapping_'+selectedFile.id+' div.CodeMirror-line-numbers').children();\n		if (selectedFile.editor.editor && $lines.size() >= macroLineNumber) {\n			var c = 1;\n			$lines.each(function() {\n				var t = $(this).html();\n				if (c == macroLineNumber) {\n					$(this).css('font-weight','bold').addClass('highlight');\n					setTimeout(function(){\n						selectedFile.editor.jumpToLine(Number(macroLineNumber));\n						notice('Ready', 2000, 1000);\n					}, 150);\n					return false;\n				}\n				if (t != \"&nbsp;\")\n					c++;\n			});\n		} else\n			setTimeout(goToLine, 50);\n	};\n	goToLine();\n} else\n	notice('Ready', 2000, 1000);\n$(window).resize(layout);\n};\n\nvar started = false;\n$(document).ready(function() {\nif (!started) {\n	started = true;\n	startEditors();\n}\n});\n</script>\n</head>\n<body>\n<div id=\"notice\" class=\"notice rounded-bottom\"></div>\n" );
+ Shared.minHeader(request, response, site.getRootNode(), null, false); 
+		out.print( "\n\n<div class=\"shaded-bg-color\" style=\"padding:.5em\">\n<div id=\"error-status\" class=\"status-label rounded no-error\">No Errors</div>\n<div id=\"error-message\"class=\"border2 error-message drop-shadow\"></div>\n<span class=\"big-title second-font\">NAML Editor</span>\n</div>\n\n<div class=\"main-content\">\n<table class=\"vertical-control\">\n<tr>\n	<td class=\"options\">\n		<button class=\"toolbar\" onclick=\"newFile()\">Add New File</button>\n		<ul id=\"options-ul\" class=\"vertical-control\">\n		</ul>\n		<div id=\"more\" class=\"more\">\n			<ul class=\"vertical-control\">\n				<li id=\"tab_advanced_options\" class=\"weak-color\">\n					<img src=\"/images/tool.png\" width=\"16\" height=\"17\" style=\"vertical-align:-15%\"/>\n					Advanced Options\n				</li>\n				" );
+ if (previousUrl != null) { 
+		out.print( "\n				<li style=\"cursor:text\">\n					<img src=\"/images/arrowleft.png\" style=\"width:15px;height:15px;vertical-align:-15%;margin-right:1px\"/>\n					<a href=\"" );
+		out.print( (previousUrl) );
+		out.print( "\">Continue</a>\n				</li>\n				" );
+ } 
+		out.print( "\n			</ul>\n			<button class=\"toolbar action-button\" onclick=\"saveChanges()\" style=\"width:100%;padding:.4em 0\">Save Changes</button>\n		</div>\n	</td>\n	<td id=\"details\" class=\"details dark-bg-color\">\n		<div id=\"no-selection\" class=\"no-bg-color\" style=\"display:none;padding:.2em 1em\">\n			<div class=\"big-title second-font weak-color\">No Files</div>\n			<div class=\"weak-color\">Use the button on the left to create a new file.</div>\n		</div>\n		<div id=\"div_advanced_options\" class=\"no-bg-color default\" style=\"display:none;padding:.2em 1em\">\n			<div id=\"modified-warning\" class=\"info-message invisible\" style=\"padding: .2em .5em .5em\">\n				<div class=\"big-title important\">Warning -- You have unsaved changes!</div>\n				It is highly recommended that you save your changes before using the options below.\n				The download option will NOT see your modified files and the upload option will override your last changes. Click on the\n				\"Save Changes\" button first if you don't want to lose your modified files.\n			</div>\n			<div class=\"big-title second-font\">Download Changes</div>\n			<div class=\"advanced-option-body\">\n				Download a ZIP file with your custom NAML code and save a copy on your computer.\n				<div style=\"padding:.3em 0\">\n					<a href=\"" );
+		out.print( (NamlDownload.getFilePath(site)) );
+		out.print( "\">Download ZIP file</a>\n				</div>\n			</div>\n\n			<div class=\"big-title second-font\">Upload Changes</div>\n			<div class=\"advanced-option-body\">\n				Import a ZIP file with custom NAML code.<br/>\n				You may consider downloading a backup before uploading a new file.<br/>\n				<iframe id=\"upload-frame\" src=\"./NamlEditor$UploadForm.jtp\" frameborder=\"0\" width=\"100%\" scrolling=\"0\"></iframe>\n			</div>\n		</div>\n	</td>\n</tr>\n</table>\n</div>\n\n" );
+ Shared.noFooter(request,response); 
+		out.print( "\n" );
+ Shared.analytics(request,response); 
+		out.print( "\n</body>\n</html>\n" );
+
+	}
+
+	public static void save(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		int fileCount = Integer.valueOf(request.getParameter("fileCount"));
+		Map<String, String> tweaks = new HashMap<String, String>();
+		for (int i = 0; i < fileCount; i++) {
+			String name = request.getParameter("name"+i);
+			String contents = request.getParameter("contents"+i);
+			tweaks.put(name, contents);
+		}
+		Map<String, String> previousTweaks = site.getCustomTweaks();
+		site.setCustomTweaks(tweaks);
+		try {
+			site.getProgram().getTemplate("do_nothing");
+		} catch (CompileException e) {
+			response.getWriter().print(getErrorMessage(e));
+			// reverting changes
+			site.setCustomTweaks(previousTweaks);
+		}
+	}
+
+	public static void uploadForm(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\n<html>\n	<head>\n		" );
+ Shared.head(request, response, false); 
+		out.print( "\n	</head>\n	<body style=\"margin-left:0\">\n		<form action=\"./NamlEditor$Upload.jtp\" method=\"POST\" enctype=\"multipart/form-data\">\n			<input name=\"file\" type=\"file\" size=\"20\" />\n			<div style=\"padding:.5em 0\">\n				<input type=\"radio\" name=\"type\" id=\"override\" value=\"override\" checked=\"true\"/> <label for=\"override\">Override my changes</label><br/>\n				<input type=\"radio\" name=\"type\" id=\"append\" value=\"append\"/> <label for=\"append\">Merge with my changes</label><br/>\n			</div>\n			<button type=\"submit\" class=\"toolbar second-font\" style=\"padding:.25em\">Upload ZIP File</button>\n		</form>\n	</body>\n</html>\n" );
+
+	}
+
+	private static final int SIZE_LIMIT = 1024 * 1024 * 2; // 2 Mb
+	private static final CharsetEncoder CHARSET_ENCODER = Charset.forName("UTF-8").newEncoder();
+
+	public static void upload(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		final Map<String, FileItem> map;
+		try {
+			map = Jtp.getFileItems(request);
+		} catch (FileUploadException e) {
+			resultPage("Upload failed: " + e.getMessage(), request, response);
+			return;
+		}
+		FileItem fi = map.get("file");
+		if (fi == null) {
+			resultPage("Image upload failed, please try again.", request, response);
+			return;
+		} else if (fi.getSize() > SIZE_LIMIT) {
+			resultPage("The file you uploaded is too big. Please upload a smaller image (less than 2Mb).", request, response);
+			return;
+		}
+		Site site = Jtp.getSite(request);
+		Map<String,String> tweaks = new HashMap<String, String>(site.getCustomTweaks());
+
+		String type = map.get("type").getString();
+		boolean isOverride = "override".equals(type);
+		if (isOverride)
+			tweaks.clear();
+
+		InputStream in = fi.getInputStream();
+		ZipInputStream zis = new ZipInputStream(in);
+		ZipEntry entry;
+		int i = 0;
+		while ((entry = zis.getNextEntry()) != null) {
+			String name = entry.getName();
+			if (name.endsWith(".naml")) {
+				int size;
+				byte[] buffer = new byte[2048];
+				ByteArrayOutputStream baos = new ByteArrayOutputStream();
+				while ((size = zis.read(buffer, 0, buffer.length)) != -1) {
+					baos.write(buffer, 0, size);
+				}
+				if (CHARSET_ENCODER.canEncode(baos.toString())) {
+					String contents = baos.toString("UTF-8");
+					name = name.replaceAll("\\.naml", ""); // remove ".naml" ext
+					if (tweaks.containsKey(name)) {
+						String original = tweaks.get(name);
+						tweaks.put(name, original + "\n\n----------- Merged -----------\n\n" + contents);
+					} else
+						tweaks.put(name, contents);
+					baos.flush();
+					baos.close();
+					i++;
+				}
+			}
+		}
+		if (i == 0) {
+			resultPage("Invalid ZIP file.", request, response);
+			return;
+		}
+		// Saves tweaks and recompiles XML
+		site.setCustomTweaks(tweaks);
+		site.getTemplate("do_nothing");
+		// Reloads the page (if there is any exception, it will be loaded and displayed)
+		resultPage(null, request, response);
+	}
+
+	private static void resultPage(String message, HttpServletRequest request, HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\n<html>\n	<head>\n		" );
+ Shared.head(request, response, false); 
+		out.print( "\n	</head>\n	<body style=\"margin-left:0\">\n		" );
+ if (message == null) { 
+		out.print( "\n			<script type=\"text/javascript\">\n				parent.location = './NamlEditor.jtp';\n			</script>\n		" );
+ } else { 
+		out.print( "\n			Error: <span style=\"color:red\">" );
+		out.print( (message) );
+		out.print( "</span>\n			<br/><br/>\n			<a href=\"./NamlEditor$UploadForm.jtp\">Try again</a>\n		" );
+ } 
+		out.print( "\n	</body>\n</html>\n" );
+
+	}
+
+	private static String getErrorMessage(Exception exception) {
+		StringBuilder errorMessage = new StringBuilder();
+		if (exception != null) {
+			errorMessage
+				.append("<div class=\"important big-title second-font\">Error</div>")
+				.append(exception.getMessage().replaceAll("\n","<br/>"))
+				.append("<div class=\"bold\" style=\"padding:.5em 0 .3em\">Full Stack Trace</div>");
+			StackTraceElement[] stack = exception.getStackTrace();
+			for (StackTraceElement e : stack)
+				errorMessage.append(e).append("<br/>");
+		}
+		return errorMessage.toString();
+	}
+
+	public static class Save extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			User user = Jtp.getUser(request, response);
+			boolean isSiteAdmin = user != null && Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+			boolean isSysAdmin = user != null && Permissions.isSysAdmin(user);
+			if ((isSiteAdmin || isSysAdmin) && "POST".equals(request.getMethod())) {
+				save(request, response);
+			} else
+				response.getWriter().print("Please log in with your administrator account. Your may have logged out or your session has expired.");
+		}
+	}
+
+	public static class UploadForm extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			uploadForm(request, response);
+		}
+	}
+
+	public static class Upload extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			User user = Jtp.getUser(request, response);
+			if (user == null) {
+				resultPage("You are not logged in as an administrator.", request, response);
+				return;
+			}
+			boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+			boolean isSysAdmin = Permissions.isSysAdmin(user);
+			if (!isSiteAdmin && !isSysAdmin) {
+				resultPage("You are not logged in as an administrator.", request, response);
+				return;
+			}
+
+			upload(request, response);
+		}
+	}
+
+	private static void simpleLoginPage(String message, HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\n	<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n	<html>\n		<head>\n			" );
+ Shared.loadJavascript(request, out); 
+		out.print( "\n			<title>Naml Editor | Login</title>\n		</head>\n		<body style=\"text-align:center;font-family: Verdana,sans-serif;font-size:.84em;padding-top:2em\">\n			<img src=\"/images/naml.png\"/>\n			<p><b>" );
+		out.print( (message) );
+		out.print( "</b></p>\n			<form action=\"./NamlEditor$SimpleLogin.jtp\" method=\"post\">\n				<table style=\"margin:0 auto;font:inherit\">\n					<tr>\n						<td style=\"text-align:right\">Email</td>\n						<td><input type=\"text\" name=\"email\"/></td>\n					</tr>\n					<tr>\n						<td style=\"text-align:right\">Password</td>\n						<td><input type=\"password\" name=\"password\"/></td>\n					</tr>\n					<tr>\n						<td colspan=\"2\" style=\"padding: .5em 0 0;text-align:center\">\n							<input type=\"submit\" value=\"Login\" style=\"font-weight:bold;padding:.3em .4em\"/>\n							or <a href=\"/\">Cancel</a>\n						</td>\n					</tr>\n				</table>\n			</form>\n			<div style=\"margin:2em 15%;text-align:center;background:#eee;padding:.3em 0\">\n			Powered by <a href=\"" );
+		out.print( (Jtp.homePage()) );
+		out.print( "\">Nabble</a>\n			</div>\n		</body>\n	</html>\n" );
+
+	}
+
+	public static class SimpleLogin extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			Site site = Jtp.getSite(request);
+			String email = request.getParameter("email");
+			String password = request.getParameter("password");
+			User user = site.getUserFromEmail(email);
+			if (user != null && user.isRegistered() && user.checkPassword(password) && "post".equalsIgnoreCase(request.getMethod())) {
+				Jtp.doLogin(request,response,user,true);
+				DailyNumber.logins.inc();
+
+				Shared.javascriptRedirect(request,response, "./NamlEditor.jtp");
+			} else {
+				simpleLoginPage("<span style=\"color:#C00\">Incorrect login!</span>", request, response);
+			}
+		}
+	}
+
+	public static class ViewFile extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+			jtpContext.setEtag(request,response);
+
+			Site site = Jtp.getSite(request);
+			String file = request.getParameter("file");
+			PrintWriter out = response.getWriter();
+			List<Source> sources = site.getProgram().getSources();
+			for (Source s : sources) {
+				if (s.id.equals(file)) {
+					file = file.replaceFirst("^.+:", "");
+					file = file.endsWith(".naml")? file : file + ".naml";
+					String code = HtmlUtils.htmlEncode(s.content);
+					
+		out.print( "\n	<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n	<html>\n		<head>\n			<title>" );
+		out.print( (file) );
+		out.print( " | Naml File</title>\n			" );
+ Shared.head(request, response, false); 
+		out.print( "\n		</head>\n		<body>\n			" );
+ Shared.minHeader(request, response, site.getRootNode(), null, false); 
+		out.print( "\n			<h1>" );
+		out.print( (file) );
+		out.print( "</h1>\n			<pre>" );
+		out.print( (code) );
+		out.print( "</pre>\n			" );
+ Shared.footer(request, response); 
+		out.print( "\n		</body>\n	</html>\n" );
+
+					return;
+				}
+			}
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Source file not found.");
+		}
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NamlEditor.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,963 @@
+<%
+package nabble.view.web.template;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.DailyNumber;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.modules.ModuleManager;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Macro;
+import nabble.naml.compiler.Meaning;
+import nabble.naml.compiler.Source;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+
+public final class NamlEditor extends HttpServlet {
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		User user = Jtp.getUser(request, response);
+		if (user == null) {
+			simpleLoginPage("You must login to edit the NAML code of this application.", request, response);
+			return;
+		}
+		boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+		boolean isSysAdmin = Permissions.isSysAdmin(user);
+
+		if (!isSiteAdmin && !isSysAdmin) {
+			simpleLoginPage("You must login to edit the NAML code of this application.", request, response);
+			return;
+		}
+		buildPage(request, response);
+	}
+
+	public static void buildPage(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		Map<String,String> tweaks = new TreeMap<String,String>(site.getCustomTweaks());
+		Node rootNode = site.getRootNode();
+		Exception exception = site.getTweakException();
+
+		String macroDef = null;
+		String selectedFileName = null;
+		String overridenBody = null;
+		String meaningId = request.getParameter("id");
+		if (meaningId != null) {
+			Meaning meaning = site.getProgram().getMeaning(meaningId);
+			Macro macro = (Macro) meaning;
+
+			String macroType = macro.getType().toString().toLowerCase();
+			String macroBody = macro.element.toString();
+			int posOpen = macroBody.indexOf('<');
+			int posClose = macroBody.indexOf('>', posOpen+1);
+			macroDef = macroBody.substring(posOpen, posClose);
+			macroDef = macroDef.replaceFirst("<"+macroType, "<override_"+macroType);
+			int posClosingTag = macroBody.lastIndexOf("</");
+			String closingTag = "</override_" + macroType + '>';
+			overridenBody = macroDef + macroBody.substring(posClose, posClosingTag) + closingTag;
+			for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
+				String name = entry.getKey();
+				String content = entry.getValue();
+				if (content.contains(macroDef)) {
+					selectedFileName = name;
+					break;
+				}
+			}
+		}
+
+		String previousUrl = request.getParameter("prev");
+
+		PrintWriter out = response.getWriter();
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<META NAME="robots" CONTENT="noindex,nofollow"/>
+				<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+				<title>Naml Editor - <%=rootNode.getSubject()%></title>
+				<% Shared.head(request, response, false); %>
+				<style type="text/css">
+					div.main-content {
+						position:absolute;
+						top:5.2em;
+						bottom:1.3em;
+						right:.8em;
+						left:.8em;
+					}
+					div.big-title {
+						margin:.5em 0;
+					}
+					table.vertical-control {
+						border-collapse:collapse;
+						width:100%;
+						height:100%;
+						position:absolute;
+						top:0;
+						bottom:0;
+					}
+					table.vertical-control td {
+						padding:0;
+						vertical-align:top;
+					}
+					table.vertical-control td.options {
+						width:13em;
+						padding-top:.2em;
+					}
+					table.vertical-control td.details {
+						padding: .2em;
+					}
+					ul.vertical-control {
+						width:100%;
+						list-style-type:none;
+						padding:0;
+						margin:.5em 0;
+					}
+					ul.vertical-control li {
+						padding: .3em;
+						white-space:nowrap;
+						cursor: pointer;
+						font-weight:normal;
+						width:13em;
+						overflow: hidden;
+						border-top-left-radius:5px;
+						border-bottom-left-radius:5px;
+						-moz-border-radius-topleft:5px;
+						-moz-border-radius-bottomleft:5px;
+					}
+					ul.vertical-control li.selected {
+						cursor: default;
+						font-weight:bold;
+					}
+					#options-ul {
+						overflow-x:hidden !important;
+						overflow-y:auto !important;
+					}
+					div.more {
+						position:absolute;
+						left:0;
+						width:13em;
+						bottom:0;
+						border-top: 1px dotted #ddd;
+					}
+					img.toolbar-icon {
+						vertical-align: -25%;
+					}
+					div.default {
+						position: absolute;
+						top: .2em;
+						bottom: 0;
+						margin-bottom: .2em;
+						height: auto;
+						overflow: auto;
+					}
+					div.advanced-option-body {
+						padding: 0 1.5em 1.5em;
+					}
+
+					.CodeMirror-line-numbers {
+						font-family: verdana, arial, sans-serif;
+						font-size: 11pt;
+						width: 2.2em;
+						color: #aaa;
+						background-color: #eee;
+						text-align: right;
+						padding-right: .3em;
+						line-height: normal;
+					}
+
+					span.xml-tagname {color: #A0B;}
+					span.xml-attribute {color: #281;}
+					span.xml-punctuation {color: black;}
+					span.xml-attname {color: #00F;}
+					span.xml-comment {color: #A70;}
+					span.xml-cdata {color: #48A;}
+					span.xml-processing {color: #999;}
+					span.xml-entity {color: #A22;}
+					span.xml-error {color: #F00 !important;}
+					span.xml-text {color: black;}
+
+					div.status-label {
+						float:right;
+						font-weight:bold;
+						width:6em;
+						text-align:center;
+						line-height:1.2em;
+						margin:-.2em 0 0 .5em;
+						padding:.35em;
+					}
+					div.no-error {
+						color: #656565;
+						cursor: text;
+					}
+					div.has-error {
+						color: white;
+						cursor: pointer;
+						background: url('/gradients/v20_EE9999_EE5555') #EE5555 repeat-x;
+					}
+					#error-message {
+						position:absolute;
+						display:none;
+						z-index:1000;
+						padding:0 1em 1em;
+						margin-top:.2em;
+						top:5em;
+						left:10%;
+						bottom:10%;
+						right:.7em;
+						overflow:auto;
+						display:none;
+					}
+				</style>
+
+				<script src="/util/codemirror/js/codemirror.js"></script>
+				<script src="/util/codemirror/js/highlight.js"></script>
+				<script src="/util/codemirror/js/stringstream.js"></script>
+				<script src="/util/codemirror/js/tokenize.js"></script>
+				<script src="/util/codemirror/js/parsexml.js"></script>
+
+				<script type="text/javascript">
+					var configFileName = "<%=ModuleManager.CONFIGURATION_TWEAK%>";
+					function setVisible(name, show) {
+						if (show) $('#'+name).show();
+						else $('#'+name).hide();
+					};
+					var modified = [];
+					function setModified(id) {
+						if (modified.indexOf(id) >= 0)
+							return;
+						modified.push(id);
+						if (id == 'removed')
+							return;
+						var $tab = $('#tab_'+id);
+						var html = $tab.html();
+						$tab.html(html.replace(/file\.png/, 'file_modified.png'));
+						setVisible('modified-warning',true);
+					};
+					function resetModified() {
+						for (var i = 0; i < modified.length; i++) {
+							if (modified[i] == 'removed')
+								continue;
+							var $tab = $('#tab_'+modified[i]);
+							var html = $tab.html();
+							$tab.html(html.replace(/file_modified\.png/, 'file.png'));
+						}
+						setVisible('modified-warning',false);
+						modified = [];
+					}
+					window.onbeforeunload = function() {
+						if (modified.length > 0)
+							return 'You have unsaved changes.\nDo you really want to leave without saving?';
+					};
+					var fileImg = '<img src="/images/file.png" style="vertical-align:-20%"/> ';
+					Array.prototype.remove = function(s){
+						for(i=0; i<this.length; i++){
+							if (s == this[i])
+								this.splice(i, 1);
+						}
+					}
+					var advancedOptions = {
+						id: 'advanced_options',
+						name: 'Advanced Options',
+						content: null
+					};
+					function isDefaultTab(f) {
+						return f == advancedOptions;
+					};
+
+					var files = [];
+					<%
+					int i = 0;
+					for( Map.Entry<String,String> entry : tweaks.entrySet() ) {
+						String name = entry.getKey();
+						String content = entry.getValue();
+						%>
+					files.push({
+						id: <%=i++%>,
+						name: "<%=HtmlUtils.javascriptStringEncode(name)%>",
+						contents: "<%=HtmlUtils.javascriptStringEncode(content)%>"
+					});
+					<% } %>
+					function addFile(file) {
+						$('#options-ul').append('<li id="tab_'+file.id+'">'+fileImg+file.name+'</li>');
+						clickableTab(file);
+
+						var textareaId = 'textarea_'+file.id;
+						$('#details').append('<div id="div_'+file.id+'" style="display:none"><textarea id="'+textareaId+'"></textarea></div>');
+						var $textarea = $('#'+textareaId);
+						$textarea.val(file.contents);
+
+						file.editor = CodeMirror.fromTextArea(textareaId, {
+							parserfile: "parsexml.js",
+							stylesheet: "/util/codemirror/css/xmlcolors.css",
+							path: "/util/codemirror/js/",
+							lineNumbers: true,
+							indentUnit: 4,
+							onChange: function() { setModified(file.id); }
+						});
+
+						var $wrapping = $textarea.next();
+						$wrapping.css({
+							position: 'absolute',
+							top: '2.5em',
+							bottom: 0,
+							marginBottom: '.2em',
+							height: 'auto'
+						});
+						$wrapping.attr('id', 'wrapping_'+file.id)
+						$wrapping.addClass('no-bg-color');
+
+						/* File Toolbar */
+						var toolbar = '<div id="toolbar_'+file.id+'" class="shaded-bg-color" style="padding:.2em .4em .3em;text-align:right">';
+						toolbar += '<div class="float-left" style="font-style:italic;padding:.3em">'+file.name+'</div>';
+						toolbar += '<button class="toolbar" style="padding:.1em .3em" onclick="renameFile()"><img class="toolbar-icon" src="/images/edit_sm.png"/> Rename</button> ';
+						toolbar += '<button class="toolbar" style="padding:.1em .3em" onclick="removeFile()"><img class="toolbar-icon" src="/images/remove_sm.png"/> Remove</button>';
+						toolbar += '</div>';
+						$textarea.after(toolbar);
+					};
+					function clickableTab(f) {
+						var $tab = $('#tab_'+f.id);
+						$tab.click(function() {
+							select(f);
+						});
+						tabHover($tab);
+					};
+					function tabHover($tab) {
+						$tab.hover(function() {
+								if (!$(this).hasClass('selected'))
+									$(this).addClass('light-bg-color');
+							},
+							function() {
+								$(this).removeClass('light-bg-color');
+							}
+						);
+					};
+
+					var initialized = [];
+					function showFile(file) {
+						$('#div_'+file.id).show();
+						if (initialized.indexOf(file.id) == -1) {
+							var cc = Nabble.get('toolbar_'+file.id);
+							$('#wrapping_'+file.id).css('top', $(cc).outerHeight());
+							var frame = window.frames[window.frames.length-1];
+							var doc = window.document;
+							$(frame.document).click(function() { $(doc).click(); });
+							initialized.push(file.id);
+						}
+					};
+
+					var selected = null;
+					function select(f) {
+						if (selected != f) {
+							$('li').removeClass('selected dark-bg-color');
+							var $tab = $('#tab_'+f.id);
+							$tab.addClass('selected dark-bg-color');
+							$('#details').children().hide();
+							showFile(f);
+							selected = f;
+							layout();
+						} else if (f == null || files.length == 0)
+							setVisible('no-selection',true);
+					};
+					function layout() {
+						if (selected) {
+							var detailsWidth = $('#details').width();
+							if (!isDefaultTab(selected)) {
+								var $div = $('#div_'+selected.id);
+								var $lineNumbers = $('div.CodeMirror-line-numbers', $div);
+								var width = detailsWidth - $lineNumbers.width() - 5;
+								$('div.CodeMirror-wrapping', $div).width(Math.round(width)+'px');
+							} else {
+								$('div.default').width(Math.round(detailsWidth - 30)+'px');
+							}
+						}
+						var $ul = $('#options-ul');
+						var uTop = $ul.position().top;
+						var mTop = $('#more').position().top;
+						$ul.height(mTop-uTop-10);
+					};
+					function newFile() {
+						var name = prompt('Name of the file:');
+						if (name == configFileName) {
+							alert('You cannot create a file with this name.');
+							return;
+						}
+						if (name) {
+							var f = {
+								id: new Date().getTime(),
+								name: name,
+								contents: ''
+							};
+							files.push(f);
+							addFile(f);
+							select(f);
+							setModified(f.id);
+						}
+					};
+					function renameFile() {
+						if (selected) {
+							name = prompt("Name of the file:", selected.name);
+							if (name && name !='null' && Nabble.trim(name).length > 0) {
+								selected.name = Nabble.trim(name);
+								$tab = $('#tab_'+selected.id).html(fileImg + name);
+								$toolbar = $('#toolbar_'+selected.id+' div').html(name);
+								setModified(selected.id);
+							}
+						}
+					};
+					function removeFile() {
+						if (selected && confirm("Do you really want to delete " + selected.name + "?")) {
+							var i = files.indexOf(selected);
+							files.remove(selected);
+							setModified('removed');
+							$('#tab_'+selected.id+',#div_'+selected.id).remove();
+							i = i > 0? i-1 : files.length > 0? 0 : -1;
+							if (i == -1)
+								setVisible('no-selection',true);
+							else
+								select(files[i]);
+						}
+					};
+					function saveChanges() {
+						notice('Saving data... please wait');
+						var params = {};
+						params['fileCount'] = files.length;
+						var c = 0;
+						for (var i=0; i < files.length; i++) {
+							var name = files[i].name;
+							params['name'+c] = name;
+							params['contents'+c] = files[i].editor.getCode();
+							c++;
+						}
+						$.post("./NamlEditor$Save.jtp", params,
+							function(data){
+								if (data == null || data.length == 0) {
+									setError(null);
+									notice('Data successfully saved, no errors found', 2000, 2000);
+									resetModified();
+								} else {
+									setError(data);
+									notice('Data NOT saved, please fix errors', 5000, 2000);
+								}
+							}
+						);
+					};
+
+					function getOrCreateFile(name) {
+						for (var i=0; i<files.length; i++) {
+							if (files[i].name == name)
+								return files[i];
+						}
+						files.push({
+							id: new Date().getTime(),
+							name: name,
+							contents:''
+						});
+						return files[files.length-1];
+					};
+					function getLineNumber(s, token) {
+						var line = 1;
+						var pos = s.indexOf(token);
+						for (var i=0; i < pos; i++) {
+							if (s.charAt(i) == '\n')
+								line++;
+						}
+						return line;
+					};
+					function startDefaultTabs() {
+						/* Advanced options tab*/
+						var $tabs = $('#tab_advanced_options');
+						$tabs.click(function() { select(advancedOptions); });
+					};
+					function setError(err) {
+						var $errorMsg = $('#error-message');
+						$errorMsg.html(err? err : '');
+						var $status = $('#error-status');
+						if (err) {
+							var visible = false;
+							function errorClick(e) {
+								e.stopPropagation();
+								if (visible) {
+									$errorMsg.slideUp('fast', function() { $errorMsg.hide() });
+									$status.html('View Error');
+								} else {
+									$errorMsg.slideDown('fast');
+									$status.html('Hide Error');
+								}
+								visible = !visible;
+							};
+							$status.addClass('has-error').removeClass('no-error').html('View Error').click(errorClick).click();
+							$(document).click(function(e) {
+								if ($(e.target).attr('id') == 'error-message')
+									return;
+								var $parents = $(e.target).parents();
+								if ($parents.hasClass('error-message'))
+									return;
+								visible = true;
+								$status.click();
+							});
+						} else {
+							$status.addClass('no-error').removeClass('has-error').html('No Errors').unbind('click');
+							$(document).unbind('click');
+						}
+					};
+					var error = <%=exception == null? "null" : "\"" + HtmlUtils.javascriptStringEncode(getErrorMessage(exception)) + "\""%>;
+
+					function startEditors() {
+						notice('Starting editor... please wait');
+						startDefaultTabs();
+						setError(error);
+						layout();
+						var selectedFileName = <%=selectedFileName == null? "null" : "\""+selectedFileName+"\""%>;
+						var selectedFile = files[0];
+						var macroDef = <%=macroDef == null? "null" : "\""+HtmlUtils.javascriptStringEncode(macroDef)+"\""%>;
+						var overridenBody = <%=overridenBody == null? "null" : "\""+HtmlUtils.javascriptStringEncode(overridenBody)+"\""%>;
+						var macroLineNumber = 0;
+						if (selectedFileName != null) {
+							var f = getOrCreateFile(selectedFileName);
+							macroLineNumber = getLineNumber(f.contents,macroDef);
+						} else if (overridenBody != null) {
+							var f = getOrCreateFile('tweaks');
+							selectedFileName = f.name;
+							f.contents = Nabble.trim(f.contents);
+							f.contents = (f.contents.length > 0? f.contents + '\n\n':'') + overridenBody;
+							macroLineNumber = getLineNumber(f.contents,macroDef);
+						} else {
+							var f = files.length == 0? getOrCreateFile('tweaks') : files[0];
+							selectedFileName = f.name;
+						}
+						for (var i=0; i < files.length; i++) {
+							addFile(files[i]);
+							if (files[i].name == selectedFileName)
+								selectedFile = files[i];
+						}
+						select(selectedFile);
+						if (selectedFile && macroLineNumber > 0) {
+							function goToLine() {
+								var $lines = $('#wrapping_'+selectedFile.id+' div.CodeMirror-line-numbers').children();
+								if (selectedFile.editor.editor && $lines.size() >= macroLineNumber) {
+									var c = 1;
+									$lines.each(function() {
+										var t = $(this).html();
+										if (c == macroLineNumber) {
+											$(this).css('font-weight','bold').addClass('highlight');
+											setTimeout(function(){
+												selectedFile.editor.jumpToLine(Number(macroLineNumber));
+												notice('Ready', 2000, 1000);
+											}, 150);
+											return false;
+										}
+										if (t != "&nbsp;")
+											c++;
+									});
+								} else
+									setTimeout(goToLine, 50);
+							};
+							goToLine();
+						} else
+							notice('Ready', 2000, 1000);
+						$(window).resize(layout);
+					};
+
+					var started = false;
+					$(document).ready(function() {
+						if (!started) {
+							started = true;
+							startEditors();
+						}
+					});
+				</script>
+			</head>
+			<body>
+				<div id="notice" class="notice rounded-bottom"></div>
+				<% Shared.minHeader(request, response, site.getRootNode(), null, false); %>
+
+				<div class="shaded-bg-color" style="padding:.5em">
+					<div id="error-status" class="status-label rounded no-error">No Errors</div>
+					<div id="error-message"class="border2 error-message drop-shadow"></div>
+					<span class="big-title second-font">NAML Editor</span>
+				</div>
+
+				<div class="main-content">
+					<table class="vertical-control">
+						<tr>
+							<td class="options">
+								<button class="toolbar" onclick="newFile()">Add New File</button>
+								<ul id="options-ul" class="vertical-control">
+								</ul>
+								<div id="more" class="more">
+									<ul class="vertical-control">
+										<li id="tab_advanced_options" class="weak-color">
+											<img src="/images/tool.png" width="16" height="17" style="vertical-align:-15%"/>
+											Advanced Options
+										</li>
+										<% if (previousUrl != null) { %>
+										<li style="cursor:text">
+											<img src="/images/arrowleft.png" style="width:15px;height:15px;vertical-align:-15%;margin-right:1px"/>
+											<a href="<%=previousUrl%>">Continue</a>
+										</li>
+										<% } %>
+									</ul>
+									<button class="toolbar action-button" onclick="saveChanges()" style="width:100%;padding:.4em 0">Save Changes</button>
+								</div>
+							</td>
+							<td id="details" class="details dark-bg-color">
+								<div id="no-selection" class="no-bg-color" style="display:none;padding:.2em 1em">
+									<div class="big-title second-font weak-color">No Files</div>
+									<div class="weak-color">Use the button on the left to create a new file.</div>
+								</div>
+								<div id="div_advanced_options" class="no-bg-color default" style="display:none;padding:.2em 1em">
+									<div id="modified-warning" class="info-message invisible" style="padding: .2em .5em .5em">
+										<div class="big-title important">Warning -- You have unsaved changes!</div>
+										It is highly recommended that you save your changes before using the options below.
+										The download option will NOT see your modified files and the upload option will override your last changes. Click on the
+										"Save Changes" button first if you don't want to lose your modified files.
+									</div>
+									<div class="big-title second-font">Download Changes</div>
+									<div class="advanced-option-body">
+										Download a ZIP file with your custom NAML code and save a copy on your computer.
+										<div style="padding:.3em 0">
+											<a href="<%=NamlDownload.getFilePath(site)%>">Download ZIP file</a>
+										</div>
+									</div>
+
+									<div class="big-title second-font">Upload Changes</div>
+									<div class="advanced-option-body">
+										Import a ZIP file with custom NAML code.<br/>
+										You may consider downloading a backup before uploading a new file.<br/>
+										<iframe id="upload-frame" src="./NamlEditor$UploadForm.jtp" frameborder="0" width="100%" scrolling="0"></iframe>
+									</div>
+								</div>
+							</td>
+						</tr>
+					</table>
+				</div>
+
+				<% Shared.noFooter(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+
+	public static void save(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		int fileCount = Integer.valueOf(request.getParameter("fileCount"));
+		Map<String, String> tweaks = new HashMap<String, String>();
+		for (int i = 0; i < fileCount; i++) {
+			String name = request.getParameter("name"+i);
+			String contents = request.getParameter("contents"+i);
+			tweaks.put(name, contents);
+		}
+		Map<String, String> previousTweaks = site.getCustomTweaks();
+		site.setCustomTweaks(tweaks);
+		try {
+			site.getProgram().getTemplate("do_nothing");
+		} catch (CompileException e) {
+			response.getWriter().print(getErrorMessage(e));
+			// reverting changes
+			site.setCustomTweaks(previousTweaks);
+		}
+	}
+
+	public static void uploadForm(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<% Shared.head(request, response, false); %>
+			</head>
+			<body style="margin-left:0">
+				<form action="./NamlEditor$Upload.jtp" method="POST" enctype="multipart/form-data">
+					<input name="file" type="file" size="20" />
+					<div style="padding:.5em 0">
+						<input type="radio" name="type" id="override" value="override" checked="true"/> <label for="override">Override my changes</label><br/>
+						<input type="radio" name="type" id="append" value="append"/> <label for="append">Merge with my changes</label><br/>
+					</div>
+					<button type="submit" class="toolbar second-font" style="padding:.25em">Upload ZIP File</button>
+				</form>
+			</body>
+		</html>
+		<%
+	}
+
+	private static final int SIZE_LIMIT = 1024 * 1024 * 2; // 2 Mb
+	private static final CharsetEncoder CHARSET_ENCODER = Charset.forName("UTF-8").newEncoder();
+
+	public static void upload(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		final Map<String, FileItem> map;
+		try {
+			map = Jtp.getFileItems(request);
+		} catch (FileUploadException e) {
+			resultPage("Upload failed: " + e.getMessage(), request, response);
+			return;
+		}
+		FileItem fi = map.get("file");
+		if (fi == null) {
+			resultPage("Image upload failed, please try again.", request, response);
+			return;
+		} else if (fi.getSize() > SIZE_LIMIT) {
+			resultPage("The file you uploaded is too big. Please upload a smaller image (less than 2Mb).", request, response);
+			return;
+		}
+		Site site = Jtp.getSite(request);
+		Map<String,String> tweaks = new HashMap<String, String>(site.getCustomTweaks());
+
+		String type = map.get("type").getString();
+		boolean isOverride = "override".equals(type);
+		if (isOverride)
+			tweaks.clear();
+
+		InputStream in = fi.getInputStream();
+		ZipInputStream zis = new ZipInputStream(in);
+		ZipEntry entry;
+		int i = 0;
+		while ((entry = zis.getNextEntry()) != null) {
+			String name = entry.getName();
+			if (name.endsWith(".naml")) {
+				int size;
+				byte[] buffer = new byte[2048];
+				ByteArrayOutputStream baos = new ByteArrayOutputStream();
+				while ((size = zis.read(buffer, 0, buffer.length)) != -1) {
+					baos.write(buffer, 0, size);
+				}
+				if (CHARSET_ENCODER.canEncode(baos.toString())) {
+					String contents = baos.toString("UTF-8");
+					name = name.replaceAll("\\.naml", ""); // remove ".naml" ext
+					if (tweaks.containsKey(name)) {
+						String original = tweaks.get(name);
+						tweaks.put(name, original + "\n\n----------- Merged -----------\n\n" + contents);
+					} else
+						tweaks.put(name, contents);
+					baos.flush();
+					baos.close();
+					i++;
+				}
+			}
+		}
+		if (i == 0) {
+			resultPage("Invalid ZIP file.", request, response);
+			return;
+		}
+		// Saves tweaks and recompiles XML
+		site.setCustomTweaks(tweaks);
+		site.getTemplate("do_nothing");
+		// Reloads the page (if there is any exception, it will be loaded and displayed)
+		resultPage(null, request, response);
+	}
+
+	private static void resultPage(String message, HttpServletRequest request, HttpServletResponse response)
+		throws IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<% Shared.head(request, response, false); %>
+			</head>
+			<body style="margin-left:0">
+				<% if (message == null) { %>
+					<script type="text/javascript">
+						parent.location = './NamlEditor.jtp';
+					</script>
+				<% } else { %>
+					Error: <span style="color:red"><%=message%></span>
+					<br/><br/>
+					<a href="./NamlEditor$UploadForm.jtp">Try again</a>
+				<% } %>
+			</body>
+		</html>
+		<%
+	}
+
+	private static String getErrorMessage(Exception exception) {
+		StringBuilder errorMessage = new StringBuilder();
+		if (exception != null) {
+			errorMessage
+				.append("<div class=\"important big-title second-font\">Error</div>")
+				.append(exception.getMessage().replaceAll("\n","<br/>"))
+				.append("<div class=\"bold\" style=\"padding:.5em 0 .3em\">Full Stack Trace</div>");
+			StackTraceElement[] stack = exception.getStackTrace();
+			for (StackTraceElement e : stack)
+				errorMessage.append(e).append("<br/>");
+		}
+		return errorMessage.toString();
+	}
+
+	public static class Save extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			User user = Jtp.getUser(request, response);
+			boolean isSiteAdmin = user != null && Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+			boolean isSysAdmin = user != null && Permissions.isSysAdmin(user);
+			if ((isSiteAdmin || isSysAdmin) && "POST".equals(request.getMethod())) {
+				save(request, response);
+			} else
+				response.getWriter().print("Please log in with your administrator account. Your may have logged out or your session has expired.");
+		}
+	}
+
+	public static class UploadForm extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			uploadForm(request, response);
+		}
+	}
+
+	public static class Upload extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			User user = Jtp.getUser(request, response);
+			if (user == null) {
+				resultPage("You are not logged in as an administrator.", request, response);
+				return;
+			}
+			boolean isSiteAdmin = Permissions.isInGroup(user, Permissions.ADMINISTRATORS_GROUP);
+			boolean isSysAdmin = Permissions.isSysAdmin(user);
+			if (!isSiteAdmin && !isSysAdmin) {
+				resultPage("You are not logged in as an administrator.", request, response);
+				return;
+			}
+
+			upload(request, response);
+		}
+	}
+
+	private static void simpleLoginPage(String message, HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+			<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+			<html>
+				<head>
+					<% Shared.loadJavascript(request, out); %>
+					<title>Naml Editor | Login</title>
+				</head>
+				<body style="text-align:center;font-family: Verdana,sans-serif;font-size:.84em;padding-top:2em">
+					<img src="/images/naml.png"/>
+					<p><b><%=message%></b></p>
+					<form action="./NamlEditor$SimpleLogin.jtp" method="post">
+						<table style="margin:0 auto;font:inherit">
+							<tr>
+								<td style="text-align:right">Email</td>
+								<td><input type="text" name="email"/></td>
+							</tr>
+							<tr>
+								<td style="text-align:right">Password</td>
+								<td><input type="password" name="password"/></td>
+							</tr>
+							<tr>
+								<td colspan="2" style="padding: .5em 0 0;text-align:center">
+									<input type="submit" value="Login" style="font-weight:bold;padding:.3em .4em"/>
+									or <a href="/">Cancel</a>
+								</td>
+							</tr>
+						</table>
+					</form>
+					<div style="margin:2em 15%;text-align:center;background:#eee;padding:.3em 0">
+					Powered by <a href="<%=Jtp.homePage()%>">Nabble</a>
+					</div>
+				</body>
+			</html>
+		<%
+	}
+
+	public static class SimpleLogin extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			Site site = Jtp.getSite(request);
+			String email = request.getParameter("email");
+			String password = request.getParameter("password");
+			User user = site.getUserFromEmail(email);
+			if (user != null && user.isRegistered() && user.checkPassword(password) && "post".equalsIgnoreCase(request.getMethod())) {
+				Jtp.doLogin(request,response,user,true);
+				DailyNumber.logins.inc();
+
+				Shared.javascriptRedirect(request,response, "./NamlEditor.jtp");
+			} else {
+				simpleLoginPage("<span style=\"color:#C00\">Incorrect login!</span>", request, response);
+			}
+		}
+	}
+
+	public static class ViewFile extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+			jtpContext.setEtag(request,response);
+
+			Site site = Jtp.getSite(request);
+			String file = request.getParameter("file");
+			PrintWriter out = response.getWriter();
+			List<Source> sources = site.getProgram().getSources();
+			for (Source s : sources) {
+				if (s.id.equals(file)) {
+					file = file.replaceFirst("^.+:", "");
+					file = file.endsWith(".naml")? file : file + ".naml";
+					String code = HtmlUtils.htmlEncode(s.content);
+					%>
+						<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+						<html>
+							<head>
+								<title><%=file%> | Naml File</title>
+								<% Shared.head(request, response, false); %>
+							</head>
+							<body>
+								<% Shared.minHeader(request, response, site.getRootNode(), null, false); %>
+								<h1><%=file%></h1>
+								<pre><%=code%></pre>
+								<% Shared.footer(request, response); %>
+							</body>
+						</html>
+					<%
+					return;
+				}
+			}
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Source file not found.");
+		}
+	}
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NamlLogger.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,45 @@
+package nabble.view.web.template;
+
+import java.util.Map;
+import java.util.HashMap;
+import nabble.model.Init;
+import nabble.model.Site;
+
+
+final class NamlLogger {
+	// log size varies between NAML_LOG_SIZE and 2*NAML_LOG_SIZE
+	private static final int NAML_LOG_SIZE = Init.get("NAML_LOG_SIZE",1000000);
+
+	private String old = "";
+	private final StringBuilder buf = new StringBuilder();
+
+	private NamlLogger() {}
+
+	synchronized void log(String msg) {
+		buf.append(msg).append("\r\n");
+		if( buf.length() >= NAML_LOG_SIZE ) {
+			old = buf.toString();
+			buf.setLength(0);
+		}
+	}
+
+	synchronized String getLog() {
+		return old + buf;
+	}
+
+	private static final Map<Long,NamlLogger> loggers = new HashMap<Long,NamlLogger>();
+
+	static synchronized NamlLogger getLogger(Site site) {
+		Long id = site.getId();
+		NamlLogger logger = loggers.get(id);
+		if( logger == null ) {
+			logger = new NamlLogger();
+			loggers.put(id,logger);
+		}
+		return logger;
+	}
+
+	static synchronized void removeLogger(Site site) {
+		loggers.remove(site.getId());
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NamlServlet.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,195 @@
+package nabble.view.web.template;
+
+import fschmidt.util.servlet.AuthorizingServlet;
+import fschmidt.util.servlet.CanonicalUrl;
+import fschmidt.util.servlet.JtpContext;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.modules.ModuleManager;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplateRuntimeException;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.lib.Jtp;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Date;
+
+
+public final class NamlServlet extends HttpServlet implements CanonicalUrl, AuthorizingServlet {
+
+	private static String runTemplate(HttpServletRequest request,String templateName)
+		throws ServletException
+	{
+		Site site = Jtp.getSite(request);
+		if( site == null )
+			return null;
+		Template template = site.getTemplate( templateName ,
+			BasicNamespace.class, NabbleNamespace.class, RequestNamespace.class
+		);
+		if( template == null )
+			return null;
+		StringWriter sw = new StringWriter();
+		try {
+			ModuleManager.run( template, sw, Collections.<String,Object>emptyMap(),
+				new BasicNamespace(template), new NabbleNamespace(site), new RequestNamespace(request)
+			);
+		} catch(IOException e) {
+			throw new RuntimeException(e);
+		} catch(TemplateRuntimeException e) {
+			checkTweaked(request,site,e);
+			throw e;
+		}
+		return sw.toString();
+	}
+
+	public String getCanonicalUrl(HttpServletRequest request) {
+		if( request.getParameterMap().isEmpty() )
+			return Jtp.homePage();
+		try {
+			String macroName = request.getParameter("macro");
+			String path = runTemplate(request,macroName+" canonical path");
+			if( path == null )
+				return null;
+			path = path.trim();
+			if( path.length() > 0 )
+				return Jtp.getBaseUrl(request)+path;
+		} catch(ServletException e) {
+			return null;
+		}
+/* why bother? - fschmidt
+		Site site = Jtp.getSite(request);
+		StringBuilder url = new StringBuilder();
+		url.append( site.getBaseUrl() ).append( request.getServletPath() );
+		String pathInfo = request.getPathInfo();
+		if( pathInfo != null )
+			url.append( pathInfo );
+		String query = request.getQueryString();
+		if( query != null )
+			url.append( '?' ).append( query );
+		return url.toString();
+*/
+		return null;
+	}
+
+	public String getAuthorizationKey(HttpServletRequest request) throws ServletException {
+		String s = runTemplate(request,"get read authorization key");
+		if( s == null )
+			return null;
+		s = s.trim();
+		return s.length() > 0 ? s : null;
+	}
+
+	@Namespace (
+		name = "read_authorization",
+		global = true
+	)
+	public static final class ReadAuthorization {
+		private final String key;
+
+		private ReadAuthorization(String key) {
+			this.key = key;
+		}
+
+		@Command public void authorization_key(IPrintWriter out,Interpreter interp) {
+			out.print(key);
+		}
+	}
+
+	public boolean authorize(String key,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
+		Site site = Jtp.getSiteNotNull(request);
+		Template template = site.getTemplate( "authorize for read" ,
+			BasicNamespace.class, NabbleNamespace.class, ServletNamespace.class, ReadAuthorization.class
+		);
+		StringWriter sw = new StringWriter();
+		ModuleManager.run( template, sw, Collections.<String,Object>emptyMap(),
+			new BasicNamespace(template), new NabbleNamespace(site), new ServletNamespace(request,response), new ReadAuthorization(key)
+		);
+		String s = sw.toString();
+		return s.trim().equals("true");
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Site site = Jtp.getSite(request);
+		if( site == null ) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "Site Not Found");
+			return;
+		}
+		try {
+			service(site,request,response);
+		} catch(TemplateRuntimeException e) {
+			Throwable cause = e.getCause();
+			if( cause instanceof CompileException ) {
+				CompileException ce = (CompileException)cause;
+				if( site.setTweakException(ce) ) {
+					service(site,request,response);
+					return;
+				}
+			}
+			checkTweaked(request,site,e);
+			throw e;
+		}
+	}
+
+	private void service(Site site,HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		String macroName = request.getParameter("macro");
+		if( macroName == null )
+			throw Jtp.servletException(request,"macroName is null");
+		Template template = site.getTemplate(macroName,
+			BasicNamespace.class, NabbleNamespace.class, ServletNamespace.class
+		);
+		if( template == null )
+			throw Jtp.servletException(request,"macro not found: "+macroName);
+		response.setHeader("X-XSS-Protection","0");
+		BasicNamespace basicNs = new BasicNamespace(template);
+		NabbleNamespace nabbleNs = new NabbleNamespace(site);
+		ServletNamespace servletNs = new ServletNamespace(request, response);
+		StringWriter sw = new StringWriter();
+		PrintWriter out = new PrintWriter(sw);
+		ModuleManager.run( template, out, Collections.<String,Object>emptyMap(),
+			basicNs, nabbleNs, servletNs
+		);
+		out.flush();
+		if( servletNs.cacheEvents != null ) {
+			JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+			jtpContext.setEtag(request,response, servletNs.cacheEvents.toArray(new String[servletNs.cacheEvents.size()]) );
+		}
+		response.getWriter().print(sw.toString());
+	}
+
+	private static void checkTweaked(HttpServletRequest request,Site site,TemplateRuntimeException e)
+		throws ServletException
+	{
+		if( !site.getCustomTweaks().isEmpty() ) {
+			NamlLogger logger = NamlLogger.getLogger(site);
+			DateFormat dateFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+			logger.log( dateFmt.format(new Date()) + "  " + ServletUtils.getCurrentURL(request) + "  referer=" + request.getHeader("referer"));
+			logger.log("IP = "+Jtp.getClientIpAddr(request));
+			User user = Jtp.getUser(request);
+			if( user != null )
+				logger.log("user = "+user.getId());
+			logger.log(e.toString());
+			logger.log("");
+			throw new ServletException("NAML exception in customized site: "+e.getMessage(),e);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NamlTest.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,25 @@
+package nabble.view.web.template;
+
+import java.net.URL;
+import fschmidt.util.java.IoUtils;
+import nabble.naml.dom.Naml;
+import nabble.naml.dom.ParseException;
+
+
+public final class NamlTest {
+
+	public static void main(String[] args) throws Exception {
+		for( URL url : IoUtils.getResources(NamlTest.class) ) {
+			String name = url.toString();
+			if( name.endsWith(".naml") ) {
+				String content = IoUtils.read(url);
+				try {
+					Naml.parser().parse(content);
+				} catch(ParseException e) {
+					throw new RuntimeException("failed to parse "+name,e);
+				}
+			}
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NodeList.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,192 @@
+package nabble.view.web.template;
+
+import fschmidt.util.java.Filter;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.NodeIterator;
+import nabble.model.NodeSearcher;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.ListSequence;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+
+@Namespace (
+	name = "node_list",
+	global = true
+)
+public final class NodeList extends ListSequence<Node> {
+
+	static void subapps(IPrintWriter out,ScopedInterpreter<NodeList> interp,Node loopNode,String filter) {
+		List<Node> nodes = loopNode.getChildApps(filter).get(0, 100);
+		NodeList loop = new NodeList(nodes, loopNode, false);
+		Object block = interp.getArg(loop,"do");
+		out.print(block);
+	}
+
+	static void descendantApps(IPrintWriter out,ScopedInterpreter<NodeList> interp,Node loopNode) {
+		List<Node> nodes = loopNode.getDescendantApps().get(0, 1000);
+		NodeList loop = new NodeList(nodes, loopNode, false);
+		Object block = interp.getArg(loop,"do");
+		out.print(block);
+	}
+
+	static void ancestors(IPrintWriter out,ScopedInterpreter<NodeList> interp,Node loopNode,String order) {
+		Node parent = loopNode.getParent();
+		List<Node> nodes = parent != null? parent.getAncestors().get(0, 1000) : Collections.<Node>emptyList();
+		if ("reverse".equalsIgnoreCase(order) && nodes.size() > 1)
+			Collections.reverse(nodes);
+		NodeList loop = new NodeList(nodes, loopNode, false);
+		Object block = interp.getArg(loop,"do");
+		out.print(block);
+	}
+
+	private static boolean hasMore(List<Node> nodes,int size) {
+		if( nodes.size() <= size )
+			return false;
+		nodes.remove(size);
+		return true;
+	}
+
+	public static void children(IPrintWriter out,ScopedInterpreter<NodeList> interp,Node loopNode,NodeIterator<? extends Node> nodeIter,int indexRecord,int length) {
+		List<Node> nodes = nodeIter.get(indexRecord, length+1);
+		boolean hasMore = hasMore(nodes,length);
+		NodeList loop = new NodeList(nodes, loopNode, hasMore);
+		Object block = interp.getArg(loop,"do");
+		out.print(block);
+	}
+
+	public static void topics(IPrintWriter out,ScopedInterpreter<NodeList> interp,Node loopNode,NodeIterator<? extends Node> nodeIter,int indexRecord,int length) {
+		List<Node> nodes = nodeIter.get(indexRecord, length+1);
+		boolean hasMore = hasMore(nodes,length);
+		NodeList loop = new NodeList(nodes, loopNode, hasMore);
+		Object block = interp.getArg(loop,"do");
+		out.print(block);
+	}
+
+	public static void posts(IPrintWriter out,ScopedInterpreter<NodeList> interp,Node loopNode,int indexRecord,int length,String sortBy,Filter<Node> filter) {
+		List<Node> nodes;
+		if ("date-ascending".equals(sortBy)) {
+			nodes = loopNode.getPostsByDateAscending(filter).get(indexRecord, length+1);
+		} else if ("date-descending".equals(sortBy)) {
+			nodes = loopNode.getPostsByDate(filter).get(indexRecord, length+1);
+		} else {
+			throw new RuntimeException("'sort' attribute not set");
+		}
+		boolean hasMore = hasMore(nodes,length);
+		NodeList loop = new NodeList(nodes, loopNode, hasMore);
+		Object block = interp.getArg(loop,"do");
+		out.print(block);
+	}
+
+	static void userNodes(IPrintWriter out,ScopedInterpreter<NodeList> interp, NodeIterator<? extends Node> nodeIter, int indexRecord,int length) {
+		List<Node> nodes = nodeIter.get(indexRecord,length+1);
+		boolean hasMore = hasMore(nodes,length);
+		NodeList loop = new NodeList(nodes, null, hasMore);
+		Object block = interp.getArg(loop,"do");
+		out.print(block);
+	}
+
+
+	private final Node loopNode;
+	private final boolean hasMore;
+
+	/**
+	 * Cache the lastNodes to prevent them from being
+	 * garbage collected so that there aren't multiple
+	 * database accesses.  This is an optimization.
+	 */
+	private final Collection<Node> lastNodes;
+
+	public NodeList(List<Node> nodes, Node loopNode,boolean hasMore) {
+		super(nodes);
+		this.loopNode = loopNode;
+		this.hasMore = hasMore;
+		lastNodes = NabbleNamespace.current().site().cacheLastNodes(nodes);
+	}
+
+	public static final CommandSpec preload_messages = CommandSpec.NO_OUTPUT;
+
+	@Command public void preload_messages(IPrintWriter out,Interpreter interp) {
+		ModelHome.preloadMessages(elements);
+	}
+
+	@Command public void there_is_more(IPrintWriter out,Interpreter interp) {
+		out.print( hasMore );
+	}
+
+	@Command public void has_loop_node(IPrintWriter out,Interpreter interp) {
+		out.print( loopNode != null );
+	}
+
+	public static final CommandSpec loop_node = CommandSpec.DO;
+
+	@Command public void loop_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+ 		out.print( interp.getArg(new NodeNamespace(loopNode),"do") );
+	}
+
+	public static final CommandSpec current_node = CommandSpec.DO;
+
+	@Command public void current_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		NodeNamespace row = new NodeNamespace(get());
+		Object block = interp.getArg(row,"do");
+		out.print(block);
+	}
+
+	public static final CommandSpec node_at = CommandSpec.DO()
+		.parameters("index")
+		.build()
+	;
+
+	@Command public void node_at(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		int index = interp.getArgAsInt("index");
+		NodeNamespace row = new NodeNamespace(elements.get(index));
+		Object block = interp.getArg(row,"do");
+		out.print(block);
+	}
+
+	@Command public void next_node(IPrintWriter out,Interpreter interp) {
+		next_element(out,interp);
+	}
+
+	@Command public void has_previous_node(IPrintWriter out,Interpreter interp) {
+		out.print( getPrevious() != null );
+	}
+
+	public static final CommandSpec previous_node = CommandSpec.DO;
+
+	@Command public void previous_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		NodeNamespace row = new NodeNamespace(getPrevious());
+		Object block = interp.getArg(row,"do");
+		out.print(block);
+	}
+
+	public static final CommandSpec sub_list = CommandSpec.DO()
+		.parameters("length")
+		.optionalParameters("start")
+		.build()
+	;
+
+	@Command public void sub_list(IPrintWriter out,ScopedInterpreter<NodeList> interp) {
+		int start = interp.getArgAsInt("start",0);
+		int length = interp.getArgAsInt("length");
+		NodeList ns ;
+		if( start==0 && length >= elements.size() ) {
+			ns = this;
+		} else {
+			ns = new NodeList( elements.subList(start,Math.min(elements.size(),start+length)), loopNode, false );
+		}
+		out.print( interp.getArg(ns,"do") );
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NodeNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1109 @@
+package nabble.view.web.template;
+
+import fschmidt.util.java.Filter;
+import fschmidt.util.mail.MailAddress;
+import nabble.model.MailingList;
+import nabble.model.ModelException;
+import nabble.model.Node;
+import nabble.model.NodeIterator;
+import nabble.model.Person;
+import nabble.model.Subscription;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.CommandDoc;
+import nabble.naml.namespaces.ListSequence;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.web.forum.Permalink;
+import nabble.view.web.forum.Thumbnail;
+import nabble.view.web.mailing_list.MailingListNamespace;
+
+import javax.servlet.ServletException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+
+@Namespace (
+	name = "node",
+	global = false
+)
+public final class NodeNamespace {
+	private Node nodeR;
+	public final ServletNamespaceUtils servletNsUtils = new ServletNamespaceUtils();
+
+	public NodeNamespace(Node node) {
+		if( node == null )
+			throw new NullPointerException("node is null");
+		this.nodeR = node;
+	}
+
+	public void refreshNode() {
+		nodeR = nodeR.getGoodCopy();
+	}
+
+	public Node node() {
+		return nodeR;
+	}
+
+	private long nodeId() {
+		return node().getId();
+	}
+
+	public static final CommandSpec this_node = CommandSpec.DO;
+
+	@Command public void this_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		out.print( interp.getArg(this,"do") );
+	}
+
+	// should this be _owner_user?  not sure  -fschmidt
+	public static final CommandSpec owner = CommandSpec.DO;
+
+	@Command public void owner(IPrintWriter out,ScopedInterpreter<UserNamespace> interp) {
+		UserNamespace visitorModel = new UserNamespace(node().getOwner());
+		out.print( interp.getArg(visitorModel,"do") );
+	}
+
+	public static final CommandSpec last_node = CommandSpec.DO;
+
+	@Command public void last_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp)
+	{
+		NodeNamespace ns = new NodeNamespace(node().getLastNode());
+ 		Object obj = interp.getArg(ns,"do");
+		out.print(obj);
+	}
+
+	public static final CommandSpec topic_node = CommandSpec.DO;
+
+	@Command public void topic_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp)
+	{
+		NodeNamespace ns = new NodeNamespace(node().getTopic());
+ 		Object obj = interp.getArg(ns,"do");
+		out.print(obj);
+	}
+
+	public static final CommandSpec topic_count = new CommandSpec.Builder()
+		.optionalParameters("filter")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void topic_count(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		out.print( getTopicCount(interp) );
+	}
+
+	public Filter<Node> filter(Interpreter interp)
+		throws ServletException
+	{
+		if( Jtp.isCached(servletNsUtils.request(interp),servletNsUtils.response(interp)) ) {
+			return Permissions.canBeViewedByParentViewersFilter;
+		} else {
+			return Permissions.canBeViewedByPersonFilter(servletNsUtils.visitor(interp));
+		}
+	}
+
+	private int getTopicCount(Interpreter interp)
+		throws ServletException
+	{
+		String topicFilter = interp.getArgString("filter");
+		return node().getTopicCount(topicFilter,filter(interp));
+	}
+
+	@Command public void child_count(IPrintWriter out,Interpreter interp) {
+		out.print( node().getChildCount() );
+	}
+
+	@Command public void subject_impl(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode(node().getSubject()) );
+	}
+
+	@Command public void raw_subject(IPrintWriter out,Interpreter interp) {
+		out.print( node().getSubject() );
+	}
+
+	@Command public void url_encoded_subject(IPrintWriter out,Interpreter interp) {
+		out.print( Jtp.subjectEncode(node().getSubjectHtml()) );
+	}
+
+	public static final CommandSpec message = CommandSpec.DO;
+
+	@Command public void message(IPrintWriter out,ScopedInterpreter<MessageNamespace> interp) {
+		out.print( interp.getArg( new MessageNamespace(node().getMessage()), "do" ) );
+	}
+
+	@Command public void post_path(IPrintWriter out,Interpreter interp) {
+		Node node = node();
+		if( node.getKind() != Node.Kind.POST )
+			throw new RuntimeException("must be post");
+		Node topic = node.getTopic();
+		if( topic.equals(node) )
+			node = null;
+		out.print( interp.encode( Permalink.path(topic,node) ) );
+	}
+
+	public static final CommandSpec when_created = CommandSpec.DO;
+
+	@Command public void when_created(IPrintWriter out,ScopedInterpreter<DateNamespace> interp) {
+		out.print( interp.getArg( new DateNamespace(node().getWhenCreated()), "do" ) );
+	}
+
+	@Command public void was_updated(IPrintWriter out,Interpreter interp) {
+		out.print( node().getWhenUpdated() != null );
+	}
+
+	public static final CommandSpec when_updated = CommandSpec.DO;
+
+	@Command public void when_updated(IPrintWriter out,ScopedInterpreter<DateNamespace> interp) {
+		out.print( interp.getArg( new DateNamespace(node().getWhenUpdated()), "do" ) );
+	}
+
+	public static final CommandSpec has_topics = new CommandSpec.Builder()
+		.optionalParameters("filter")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void has_topics(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		out.print( getTopicCount(interp) > 0 );
+	}
+
+	@Command public void has_children(IPrintWriter out,Interpreter interp) {
+		boolean hasChildren = node().getChildCount() > 0;
+		out.print( hasChildren );
+	}
+
+
+	private boolean checkedSubapps = false;
+	private boolean hasSubapps;
+
+	@Command public void has_subapps(IPrintWriter out,Interpreter interp) {
+		if( !checkedSubapps ) {
+			hasSubapps = node().hasChildApps();
+			checkedSubapps = true;
+		}
+		out.print(hasSubapps);
+	}
+
+	private boolean checkedPrivateSubapps = false;
+	private boolean hasPrivateSubapps;
+
+	private boolean hasPrivateSubapps(Node node) {
+		NodeIterator<? extends Node> childAppIterator = node.getChildApps();
+		try {
+			for (Node n : childAppIterator) {
+				if (Permissions.isPrivate(n))
+					return true;
+				else {
+					boolean hasPrivateChildren = hasPrivateSubapps(n);
+					if (hasPrivateChildren)
+						return true;
+				}
+			}
+		} finally {
+			childAppIterator.close();
+		}
+		return false;
+	}
+
+	@Command public void has_private_subapps(IPrintWriter out,Interpreter interp) {
+		if( !checkedPrivateSubapps ) {
+			hasPrivateSubapps = hasPrivateSubapps(node());
+			checkedPrivateSubapps = true;
+		}
+		out.print(hasPrivateSubapps);
+	}
+
+	private boolean checkedPinnedSubapps = false;
+	private boolean hasPinnedSubapps;
+
+	@Command public void has_pinned_subapps(IPrintWriter out,Interpreter interp) {
+		if( !checkedPinnedSubapps ) {
+			hasPinnedSubapps = node().hasPinnedApps();
+			checkedPinnedSubapps = true;
+		}
+		out.print(hasPinnedSubapps);
+	}
+
+	private boolean checkedPinnedTopics = false;
+	private boolean hasPinnedTopics;
+
+	@Command public void has_pinned_topics(IPrintWriter out,Interpreter interp) {
+		if( !checkedPinnedTopics ) {
+			hasPinnedTopics = node().hasPinnedTopics();
+			checkedPinnedTopics = true;
+		}
+		out.print(hasPinnedTopics);
+	}
+
+	@Command public void has_child_topics(IPrintWriter out,Interpreter interp) {
+		out.print( node().hasChildTopics() );
+	}
+
+	@Command public void id(IPrintWriter out,Interpreter interp) {
+		out.print( nodeId() );
+	}
+
+	@Command public void post_count(IPrintWriter out,Interpreter interp) {
+		out.print( node().getDescendantPostCount() );
+	}
+
+	@Command public void is_app(IPrintWriter out,Interpreter interp) {
+		out.print( node().getKind() == Node.Kind.APP );
+	}
+
+	@Command public void is_post(IPrintWriter out,Interpreter interp) {
+		out.print( node().getKind() == Node.Kind.POST );
+	}
+
+	@Command public void is_topic(IPrintWriter out,Interpreter interp) {
+		Node node = node();
+		out.print( node.getKind() == Node.Kind.POST && (node.getParent() == null || node.getParent().getKind() == Node.Kind.APP));
+	}
+
+	@Command public void is_private(IPrintWriter out,Interpreter interp) {
+		out.print(Permissions.isPrivate(node()));
+	}
+
+	@Command public void is_pending(IPrintWriter out,Interpreter interp) {
+		Node node = node();
+		Node.MailToList mail = node.getMailToList();
+		out.print(mail != null && node.getOwner() instanceof User && mail.isPending());
+	}
+
+	@Command public void descendant_count(IPrintWriter out,Interpreter interp) {
+		out.print( node().getDescendantCount() );
+	}
+
+	@Command public void replies(IPrintWriter out,Interpreter interp) {
+		out.print( node().getDescendantCount()-1 );
+	}
+
+	@Command public void has_replies(IPrintWriter out,Interpreter interp) {
+		out.print( node().getDescendantCount() > 1 );
+	}
+
+	public static final CommandSpec first_reply = CommandSpec.DO;
+
+	@Command public void first_reply(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		Node node = node();
+		List<Node> children = node.getChildren().get(0,1);
+		if( children.isEmpty() )
+			throw new RuntimeException("node="+node+" replies="+(node.getDescendantCount()-1));
+		NodeNamespace ns = new NodeNamespace(children.get(0));
+ 		Object obj = interp.getArg(ns,"do");
+		out.print(obj);
+	}
+
+	public static final CommandSpec type = new CommandSpec.Builder()
+		.optionalParameters("equals")
+		.build()
+	;
+
+	@Command public void type(IPrintWriter out,Interpreter interp) {
+		String equals = interp.getArgString("equals");
+		if (equals == null)
+			out.print( node().getType() );
+		else {
+			out.print( equals.trim().equals(node().getType()) );
+		}
+	}
+
+	@Command public void is_in_app(IPrintWriter out,Interpreter interp) {
+		out.print( node().getApp() != null );
+	}
+
+	public static final CommandSpec get_app_node = CommandSpec.DO;
+
+	@Command public void get_app_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		NodeNamespace ns = new NodeNamespace(node().getApp());
+ 		Object obj = interp.getArg(ns,"do");
+		out.print(obj);
+	}
+
+	public static final CommandSpec parent_node = CommandSpec.DO;
+
+	@Command public void parent_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+ 		out.print( interp.getArg(new NodeNamespace(node().getParent()),"do") );
+	}
+
+	@Command public void is_root(IPrintWriter out,Interpreter interp) {
+		out.print( node().isRoot() );
+	}
+
+	// loops
+
+	@Command public void change_language_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/app/Languages.jtp" ) );
+	}
+
+	@Command public void extras_and_addons_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/app/Addons.jtp" ) );
+	}
+
+	@Command public void change_domain_name_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/forum/ChangeDomainName.jtp?site=" + node().getSite().getId() ) );
+	}
+
+	@Command public void manage_pinned_topics_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/catalog/ChangePinOrder.jtp?forum=" + nodeId() + "&what=threads" ) );
+	}
+
+	@Command public void manage_sub_apps_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/catalog/ChangePinOrder.jtp?forum=" + nodeId() + "&what=forums" ) );
+	}
+
+	@Command public void parent_options_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/catalog/ChangeParent.jtp?forum=" + nodeId() ) );
+	}
+
+	@Command public void subscription_instructions_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/mailing_list/SubscribeToMailingList.jtp?node=" + nodeId() ) );
+	}
+
+	@Command public void embed_post_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/embed/EmbedOptions.jtp?node=" + nodeId() ) );
+	}
+
+	@Command public void reply_to_author_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/user/SendEmail.jtp?type=pm&post=" + nodeId() ) );
+	}
+
+	@Command public void unsubscription_instructions_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/mailing_list/UnsubscribeFromMailingList.jtp?node=" + nodeId() ) );
+	}
+
+	@Command public void embedding_options_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode("/embed/EmbedOptions.jtp?node=" + nodeId()) );
+	}
+
+	public static final CommandSpec monthly_archives = new CommandSpec.Builder()
+		.scopedParameters("do")
+		.dotParameter("do")
+		.outputtedParameters("do")
+		.build()
+	;
+
+	@Command public void monthly_archives(IPrintWriter out,ScopedInterpreter<MonthlyArchivesNamespace> interp) {
+		Node node = node();
+		if (node.getKind() == Node.Kind.APP) {
+			MonthlyArchivesNamespace archiveNs = new MonthlyArchivesNamespace(node);
+			out.print(interp.getArg(archiveNs,"do"));
+		}
+	}
+
+
+
+	@Command public void is_mail_to_list(IPrintWriter out,Interpreter interp) {
+		out.print( node().getMailToList() != null );
+	}
+
+
+	public static final CommandSpec get_this_mailing_list_archive = CommandSpec.DO;
+
+	@Command public void get_this_mailing_list_archive(IPrintWriter out,ScopedInterpreter<MailingListNamespace> interp) {
+		out.print(interp.getArg(new MailingListNamespace(node()),"do"));
+	}
+
+	public static final CommandSpec get_associated_mailing_list_archive = CommandSpec.DO;
+
+	@Command public void get_associated_mailing_list_archive(IPrintWriter out,ScopedInterpreter<MailingListNamespace> interp) {
+		MailingList mailingList = node().getAssociatedMailingList();
+		out.print(interp.getArg(new MailingListNamespace(mailingList),"do"));
+	}
+
+	@Command public void is_a_mailing_list_archive(IPrintWriter out,Interpreter interp) {
+		out.print(node().getMailingList() != null);
+	}
+
+	@Command public void is_associated_with_mailing_list_archive(IPrintWriter out,Interpreter interp) {
+		out.print(node().getAssociatedMailingList() != null);
+	}
+
+	@Command public void has_sub_archive(IPrintWriter out,Interpreter interp) {
+		boolean hasSubArchive = false;
+		List<Node> childApps = node().getChildApps(null).get(0, 100);
+		for (Node n : childApps) {
+			if (n.getAssociatedMailingList() != null) {
+				hasSubArchive = true;
+				break;
+			}
+		}
+		out.print(hasSubArchive);
+	}
+
+
+	@Command public void default_meta_description(IPrintWriter out,Interpreter interp) {
+		out.print( Jtp.metaDescription(node()) );
+	}
+
+	@Command public void default_meta_keywords(IPrintWriter out,Interpreter interp) {
+		out.print( Jtp.metaKeywords(node()) );
+	}
+
+
+
+	@Command public void pinned_filter(IPrintWriter out,Interpreter interp) {
+		out.print( "pin is not null" );
+	}
+
+	@Command public void no_pinned_subapps_filter(IPrintWriter out,Interpreter interp) {
+		out.print( "(pin is null or is_app = 'f' or is_app is null)" );
+	}
+
+	public static final CommandSpec date_filter = new CommandSpec.Builder()
+		.parameters("date")
+		.build()
+	;
+
+	@CommandDoc(
+		value= "Creates a filter for a specific month and year combination. ",
+		params = {"date=Month and year in the format YYYYMM."},
+		seeAlso = {"date_range_filter"}
+	)
+	@Command public void date_filter(IPrintWriter out,Interpreter interp) {
+		String date = interp.getArgString("date");
+		String currentYear = date.substring(0, 4);
+		int currentMonth = Integer.valueOf(date.substring(4));
+		out.print( "date_part('year', when_created) = " + currentYear + " and date_part('month', when_created) = " + currentMonth );
+	}
+
+	public static final CommandSpec date_range_filter = new CommandSpec.Builder()
+		.parameters("from_date","to_date")
+		.build()
+	;
+
+	private static final Pattern YYYYMMDD = Pattern.compile("\\d{4}\\d{2}\\d{2}");
+
+	@Command public void date_range_filter(IPrintWriter out,Interpreter interp)
+			throws ModelException.InvalidDate
+	{
+		String from = interp.getArgString("from_date");
+		String to = interp.getArgString("to_date");
+		if (!YYYYMMDD.matcher(to).find())
+			throw new ModelException.InvalidDate(to);
+		if (!YYYYMMDD.matcher(from).find())
+			throw new ModelException.InvalidDate(from);
+
+		// SQL format is YYYY-MM-DD
+		String fromCnd = from.substring(0, 4) + '-' + from.substring(4, 6) + '-' + from.substring(6);
+		String toCnd = to.substring(0, 4) + '-' + to.substring(4, 6) + '-' + to.substring(6);
+		out.print( "when_created >= DATE '" + fromCnd + "' and when_created <= DATE '" + toCnd + "'" );
+	}
+
+	public static final CommandSpec exclude_parent_filter = new CommandSpec.Builder()
+		.parameters("parent_id")
+		.build()
+	;
+
+	@Command public void exclude_parent_filter(IPrintWriter out,Interpreter interp) {
+		long parentId = interp.getArgAsLong("parent_id");
+		out.print( "parent_id <> " + parentId );
+	}
+
+	@Command public void children_filter(IPrintWriter out,Interpreter interp) {
+		out.print( "parent_id = " + nodeId() );
+	}
+
+	@Command public void post_filter(IPrintWriter out,Interpreter interp) {
+		out.print( "is_app is null or not is_app" );
+	}
+
+	public static final CommandSpec subapps_list = new CommandSpec.Builder()
+		.optionalParameters("filter")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.build()
+	;
+
+	@Command public void subapps_list(IPrintWriter out,ScopedInterpreter<NodeList> interp) {
+		NodeList.subapps(out,interp,node(),interp.getArgString("filter"));
+	}
+
+	public static final CommandSpec descendant_apps_list = new CommandSpec.Builder()
+		.scopedParameters("do")
+		.dotParameter("do")
+		.build()
+	;
+
+	@Command public void descendant_apps_list(IPrintWriter out,ScopedInterpreter<NodeList> interp) {
+		NodeList.descendantApps(out,interp,node());
+	}
+
+	public static final CommandSpec ancestors_list = new CommandSpec.Builder()
+		.optionalParameters("order")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.build()
+	;
+
+	@Command public void ancestors_list(IPrintWriter out,ScopedInterpreter<NodeList> interp) {
+		NodeList.ancestors(out,interp,node(),interp.getArgString("order"));
+	}
+
+	public static final CommandSpec children_list_standard = CommandSpec.DO()
+		.parameters("length")
+		.optionalParameters("start", "filter")
+		.build()
+	;
+
+	@Command public void children_list_standard(IPrintWriter out,ScopedInterpreter<NodeList> interp) {
+		Node node = node();
+		int start = getLoopStart(interp);
+		int length = getLoopLength(interp);
+		String filter = interp.getArgString("filter");
+		NodeIterator<? extends Node> nodeIter = node.getChildren(filter);
+		NodeList.children(out,interp,node,nodeIter,start,length);
+	}
+
+	public static final CommandSpec topics_list_standard = CommandSpec.DO()
+		.parameters("length")
+		.optionalParameters("start","sort","filter")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void topics_list_standard(IPrintWriter out,ScopedInterpreter<NodeList> interp)
+		throws ServletException
+	{
+		Node node = node();
+		int start = getLoopStart(interp);
+		int length = getLoopLength(interp);
+		String filter = interp.getArgString("filter");
+		String sortBy = interp.getArgString("sort");
+		NodeIterator<? extends Node> nodeIter;
+		if ("pinned-and-last-node-date".equals(sortBy)) {
+			nodeIter = node.getTopicsByPinnedAndLastNodeDate(filter,filter(interp));
+		} else if ("pinned-and-root-node-date".equals(sortBy)) {
+			nodeIter = node.getTopicsByPinnedAndRootNodeDate(filter,filter(interp));
+		} else if ("popularity".equals(sortBy)) {
+			nodeIter = node.getTopicsByPopularity(filter,filter(interp));
+		} else if ("last-node-date".equals(sortBy)) {
+			nodeIter = node.getTopicsByLastNodeDate(filter,filter(interp));
+		} else if ("topic-subject".equals(sortBy)) {
+			nodeIter = node.getTopicsBySubject(filter,filter(interp));
+		} else {
+			throw new RuntimeException("'sort' attribute not set");
+		}
+		try {
+			NodeList.topics(out,interp,node,nodeIter,start,length);
+		} finally {
+			nodeIter.close();
+		}
+	}
+
+	public static final CommandSpec post_list = CommandSpec.DO()
+		.parameters("length","sort")
+		.optionalParameters("start")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void post_list(IPrintWriter out,ScopedInterpreter<NodeList> interp)
+		throws ServletException
+	{
+		NodeList.posts(out,interp,node(),getLoopStart(interp),getLoopLength(interp),interp.getArgString("sort"),filter(interp));
+	}
+
+	public static int getLoopStart(Interpreter interp) {
+		return interp.getArgAsInt("start",0);
+	}
+
+	public static int getLoopLength(Interpreter interp) {
+		try {
+			return Integer.valueOf( interp.getArgString("length").trim() );
+		} catch(NumberFormatException e) {
+			throw new RuntimeException("Invalid loop length",e);
+		}
+	}
+
+	public static final CommandSpec can_be_viewed_by_visitor = new CommandSpec.Builder()
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void can_be_viewed_by_visitor(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		if( Jtp.isCached(servletNsUtils.request(interp),servletNsUtils.response(interp)) ) {
+			out.print( Permissions.canBeViewedByParentViewers(node()) );
+		} else {
+			out.print( Permissions.canBeViewedByPerson(node(),servletNsUtils.visitor(interp)) );
+		}
+	}
+
+	public static final CommandSpec groups_have_permission = new CommandSpec.Builder()
+		.parameters("groups","permission")
+		.build()
+	;
+
+	@Command public void groups_have_permission(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		String groups = interp.getArgString("groups");
+		String perm = interp.getArgString("permission");
+		for( String group : groups.split(",") ) {
+			if( Permissions.hasPermission(node(),group.trim(),perm) ) {
+				out.print( true );
+				return;
+			}
+		}
+		out.print( false );
+	}
+
+	public static final CommandSpec node_has_permission = new CommandSpec.Builder()
+		.dotParameter("permission")
+		.build()
+	;
+
+	@Command public void node_has_permission(IPrintWriter out,Interpreter interp) {
+		String perm = interp.getArgString("permission");
+		out.print( Permissions.nodeHasPermission(node(),perm) );
+	}
+
+	public static final CommandSpec has_permission = new CommandSpec.Builder()
+		.parameters("group","permission")
+		.build()
+	;
+
+	@Command public void has_permission(IPrintWriter out,Interpreter interp) {
+		String group = interp.getArgString("group");
+		String perm = interp.getArgString("permission");
+		out.print( Permissions.hasPermission(node(),group,perm) );
+	}
+
+	public static final CommandSpec node_with_permission = new CommandSpec.Builder()
+		.parameters("permission")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.outputtedParameters("do")
+		.optionalParameters("do")
+		.build()
+	;
+
+	@Command public void node_with_permission(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		String perm = interp.getArgString("permission");
+		Node node = Permissions.getPermissionNode(node(),perm);
+		out.print( interp.getArg(new NodeNamespace(node),"do") );
+	}
+
+	public static final CommandSpec users_with_permission = new CommandSpec.Builder()
+		.parameters("permission")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.outputtedParameters("do")
+		.build()
+	;
+
+	@Command public void users_with_permission(IPrintWriter out,ScopedInterpreter<UserNamespace.UserList> interp) {
+		String perm = interp.getArgString("permission");
+		List<User> users = Permissions.getUsersWithPermission(node(),perm);
+		UserNamespace.UserList usersNs = new UserNamespace.UserList(users);
+		out.print( interp.getArg(usersNs,"do") );
+	}
+
+	public static final CommandSpec has_groups_with_permission = new CommandSpec.Builder()
+		.dotParameter("permission")
+		.build()
+	;
+
+	@Command public void has_groups_with_permission(IPrintWriter out,Interpreter interp) {
+		String perm = interp.getArgString("permission");
+		out.print( Permissions.hasGroupsWithPermission(node(),perm) );
+	}
+
+	public static final CommandSpec groups_with_permission = CommandSpec.DO()
+		.parameters("permission")
+		.build()
+	;
+
+	@Command public void groups_with_permission(IPrintWriter out,ScopedInterpreter<NabbleNamespace.GroupList> interp)
+		throws ServletException
+	{
+		String perm = interp.getArgString("permission");
+		List<String> groups = Permissions.getGroupsWithPermission(node(),perm);
+		Object block = interp.getArg(new NabbleNamespace.GroupList(groups),"do");
+		out.print(block);
+	}
+
+
+	public static final CommandSpec visitor_subscription = new CommandSpec.Builder()
+		.scopedParameters("do")
+		.dotParameter("do")
+		.outputtedParameters("do")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void visitor_subscription(IPrintWriter out, ScopedInterpreter<SubscriptionNamespace> interp)
+		throws ServletException
+	{
+		User user = servletNsUtils.visitorUser(interp);
+		SubscriptionNamespace subscriptionModel = new SubscriptionNamespace(node(), user);
+		out.print( interp.getArg(subscriptionModel,"do") );
+	}
+
+	public static final CommandSpec subscription_for = CommandSpec.DO()
+		.parameters("email")
+		.build()
+	;
+
+	@Command public void subscription_for(IPrintWriter out, ScopedInterpreter<SubscriptionNamespace> interp)
+		throws ModelException.EmailFormat
+	{
+		Node node = node();
+		String email = interp.getArgString("email");
+		if (!new MailAddress(email).isValid())
+			throw new ModelException.EmailFormat(email);
+		User user = node.getSite().getOrCreateUser(email);
+		SubscriptionNamespace subscriptionModel = new SubscriptionNamespace(node, user);
+		out.print( interp.getArg(subscriptionModel,"do") );
+	}
+
+	public static final CommandSpec get_subscription_by_code = new CommandSpec.Builder()
+		.parameters("code")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.outputtedParameters("do")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void get_subscription_by_code(IPrintWriter out, ScopedInterpreter<SubscriptionNamespace> interp)
+		throws TemplateException
+	{
+		String code = interp.getArgString("code");
+		SubscriptionNamespace subscriptionModel = new SubscriptionNamespace(code, servletNsUtils.request(interp));
+		out.print( interp.getArg(subscriptionModel,"do") );
+	}
+
+	public static final CommandSpec visitor_is_subscribed = ServletNamespaceUtils.requiresServletNamespace;
+
+	@Command public void visitor_is_subscribed(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		User user = servletNsUtils.visitorUser(interp);
+		out.print( user != null && user.isSubscribed(node()) );
+	}
+
+	public static final CommandSpec unsubscribe_visitor = new CommandSpec.Builder()
+		.requiredInStack(ServletNamespace.class)
+		.outputtedParameters()
+		.build()
+	;
+
+	@Command public void unsubscribe_visitor(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		User user = servletNsUtils.visitorUser(interp);
+		if( user != null ) {
+			Subscription s = user.getSubscription(node());
+			if( s != null )
+				s.delete();
+		}
+	}
+
+	public static final CommandSpec subscribe_visitor = new CommandSpec.Builder()
+		.requiredInStack(ServletNamespace.class)
+		.outputtedParameters()
+		.build()
+	;
+
+	@Command public void subscribe_visitor(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		Node node = node();
+		User user = servletNsUtils.visitorUser(interp);
+		Subscription s = user.getSubscription(node);
+		if( s == null ) {
+			user.subscribe(node,Subscription.To.DESCENDANTS,Subscription.Type.INSTANT);
+		}
+	}
+
+	public static final CommandSpec user_address = new CommandSpec.Builder()
+		.parameters("email")
+		.build()
+	;
+
+	@Command public void user_address(IPrintWriter out,Interpreter interp) {
+		Node node = node();
+		String email = interp.getArgString("email");
+		User user = node.getSite().getOrCreateUser(email);
+		out.print( user.getDecoratedAddress(node) );
+	}
+
+
+	@Command public void default_rows_per_page(IPrintWriter out,Interpreter interp) {
+		out.print( Jtp.getDefaultRowsPerPage(node().getType()) );
+	}
+
+	public static final CommandSpec delete_message_or_node = CommandSpec.NO_OUTPUT;
+
+	@Command public void delete_message_or_node(IPrintWriter out,Interpreter interp) {
+		node().deleteMessageOrNode();
+	}
+
+	public static final CommandSpec delete_recursively = CommandSpec.NO_OUTPUT;
+
+	@Command public void delete_recursively(IPrintWriter out,Interpreter interp) {
+		node().deleteRecursively();
+	}
+
+
+	public static final CommandSpec as_node_page = CommandSpec.DO;
+
+	@Command public void as_node_page(IPrintWriter out,ScopedInterpreter<NodePageNamespace> interp) {
+		out.print( interp.getArg(new NodePageNamespace(this),"do") );
+	}
+
+
+	@Command public void has_thumbnail(IPrintWriter out,Interpreter interp) {
+		out.print( Thumbnail.getThumbnailFile(node()) != null );
+	}
+
+	@Command public void thumbnail_url(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( Thumbnail.getThumbnailFile(node()) ) );
+	}
+
+	@Command public void is_pinned(IPrintWriter out,Interpreter interp) {
+		out.print( node().isPinned() );
+	}
+
+	public static final CommandSpec pin = CommandSpec.NO_OUTPUT;
+
+	@Command public void pin(IPrintWriter out,Interpreter interp) {
+		Node node = node();
+		Jtp.addPinnedChild(node.getParent(), node);
+	}
+
+	public static final CommandSpec unpin = CommandSpec.NO_OUTPUT;
+
+	@Command public void unpin(IPrintWriter out,Interpreter interp) {
+		Node node = node();
+		Jtp.unpinChild(node.getParent(), node);
+	}
+
+	public static final CommandSpec equals = new CommandSpec.Builder()
+		.dotParameter("node")
+		.build()
+	;
+
+	@Command public void equals(IPrintWriter out,Interpreter interp) {
+		NodeNamespace ns = interp.getArgAsNamespace(NodeNamespace.class,"node");
+		out.print( ns != null && ns.node() != null && ns.node().equals(node()) );
+	}
+
+	public static final CommandSpec NAME = new CommandSpec.Builder()
+		.dotParameter("name")
+		.build()
+	;
+
+	public static final CommandSpec has_property = NAME;
+
+	@Command public void has_property(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print(node().getProperty(name) != null);
+	}
+
+	public static final CommandSpec get_property = NAME;
+
+	@Command public void get_property(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print(node().getProperty(name));
+	}
+
+	public static final CommandSpec delete_property = CommandSpec.NO_OUTPUT()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void delete_property(IPrintWriter out,Interpreter interp) {
+		Node node = node();
+		String name = interp.getArgString("name");
+		node.setProperty(name, null);
+		node.update();
+	}
+
+	public static final CommandSpec set_property = CommandSpec.NO_OUTPUT()
+		.parameters("name", "value")
+		.build()
+	;
+
+	@Command public void set_property(IPrintWriter out,Interpreter interp) {
+		Node node = node();
+		String name = interp.getArgString("name");
+		String value = interp.getArgString("value");
+		node.setProperty(name, value);
+		node.update();
+	}
+
+	@Command public void subscription_count(IPrintWriter out,Interpreter interp) {
+		out.print( node().getSubscriptionCount() );
+	}
+
+	public static final CommandSpec subscriptions = new CommandSpec.Builder()
+		.parameters("length")
+		.optionalParameters("start")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.outputtedParameters("do")
+		.build()
+	;
+
+	@Command public void subscriptions(IPrintWriter out,ScopedInterpreter<SubscriptionList> interp) {
+		int start = interp.getArgAsInt("start",0);
+		int length = interp.getArgAsInt("length");
+		Collection<Subscription> subscriptions = node().getSubscriptions(start,length);
+		List<SubscriptionNamespace> subscriptionNs = new ArrayList<SubscriptionNamespace>(subscriptions.size());
+		for (Subscription s : subscriptions) {
+			subscriptionNs.add(new SubscriptionNamespace(s));
+		}
+		out.print( interp.getArg(new SubscriptionList(subscriptionNs),"do") );
+	}
+
+	@Namespace (
+		name = "subscriptions",
+		global = true
+	)
+	public static final class SubscriptionList extends ListSequence<SubscriptionNamespace> {
+
+		SubscriptionList(List<SubscriptionNamespace> subscriptions) {
+			super(subscriptions);
+		}
+
+		public static final CommandSpec subscription = CommandSpec.DO;
+
+		@Command public void subscription(IPrintWriter out,ScopedInterpreter<SubscriptionNamespace> interp) {
+			out.print(interp.getArg(get(),"do"));
+		}
+	}
+
+
+	public static final CommandSpec get_private_node = CommandSpec.DO;
+
+	@Command public void get_private_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+ 		out.print( interp.getArg(new NodeNamespace(Permissions.getPrivateNode(node())),"do") );
+	}
+
+	private String getRedirectionUrl() {
+		String embeddingUrl = null;
+		for(
+			Node n = node();
+			embeddingUrl == null && n != null;
+			n = n.getParent()
+		) {
+			embeddingUrl = n.getEmbeddingUrl();
+		}
+		return embeddingUrl;
+	}
+
+	@Command public void has_embedding_redirection_url(IPrintWriter out,Interpreter interp) {
+		out.print( getRedirectionUrl() != null );
+	}
+
+	@Command public void embedding_redirection_url(IPrintWriter out,Interpreter interp) {
+		out.print( getRedirectionUrl() );
+	}
+
+
+	@Command public void default_reply_subject(IPrintWriter out,Interpreter interp) {
+		Node node = node();
+		String subject = null;
+		if( node.getKind() == Node.Kind.POST ) {
+			subject = node.getSubject();
+			if( !subject.startsWith("Re: ") && !subject.startsWith("RE: ") )
+				subject = "Re: " + subject;
+		}
+		out.print(subject);
+	}
+
+	public static final CommandSpec descendant_nodes_by_user = CommandSpec.DO;
+
+	@Command public void descendant_nodes_by_user(IPrintWriter out,ScopedInterpreter<NodesGroupedByUser> interp) {
+		Map<User,List<Node>> map = new HashMap<User,List<Node>>();
+		for( Node n : node().getDescendants() ) {
+			Person u = n.getOwner();
+			if( !(u instanceof User) )
+				continue;
+			User owner = (User)u;
+			List<Node> nodes = map.get(owner);
+			if( nodes == null ) {
+				nodes = new ArrayList<Node>();
+				map.put(owner,nodes);
+			}
+			nodes.add(n);
+		}
+		out.print(interp.getArg(new NodesGroupedByUser(map),"do"));
+	}
+
+	@Namespace (
+		name = "nodes_grouped_by_user",
+		global = true
+	)
+	public static final class NodesGroupedByUser extends UserNamespace.UserList {
+
+		Map<User, List<Node>> userNodes;
+
+		NodesGroupedByUser(Map<User, List<Node>> userNodes) {
+			super(Arrays.asList(userNodes.keySet().toArray(new User[userNodes.size()])));
+			this.userNodes = userNodes;
+		}
+
+		public static final CommandSpec nodes_list = CommandSpec.DO;
+
+		@Command public void nodes_list(IPrintWriter out,ScopedInterpreter<NodeList> interp) {
+			List<Node> nodes = userNodes.get(get());
+			out.print(interp.getArg(new NodeList(nodes, null, false),"do"));
+		}
+	}
+
+
+	public static final CommandSpec get_instant_emails = CommandSpec.DO;
+
+	@Command public void get_instant_emails(IPrintWriter out,ScopedInterpreter<InstantMailNamespace> interp) {
+		Node node = node();
+		Map<User,Subscription> map = node.getSubscribersToNotify();
+		if( !map.isEmpty() )
+			out.print( interp.getArg(new InstantMailNamespace(node, map),"do") );
+	}
+
+	@Command public void has_prev_topic(IPrintWriter out, Interpreter interp) {
+		out.print(node().hasPreviousTopic());
+	}
+
+	@Command public void has_next_topic(IPrintWriter out, Interpreter interp) {
+		out.print(node().hasNextTopic());
+	}
+
+	public static final CommandSpec prev_topic = CommandSpec.DO;
+
+	@Command public void prev_topic(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp)
+	{
+		NodeNamespace ns = new NodeNamespace(node().getPreviousTopic());
+ 		Object obj = interp.getArg(ns,"do");
+		out.print(obj);
+	}
+
+	public static final CommandSpec next_topic = CommandSpec.DO;
+
+	@Command public void next_topic(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp)
+	{
+		NodeNamespace ns = new NodeNamespace(node().getNextTopic());
+ 		Object obj = interp.getArg(ns,"do");
+		out.print(obj);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/NodePageNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,226 @@
+package nabble.view.web.template;
+
+import fschmidt.db.DbDatabase;
+import nabble.model.FileUpload;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.web.forum.NodeEditorNamespace;
+import nabble.view.web.forum.SearchNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+import java.util.Random;
+
+
+@Namespace (
+	name = "node_page",
+	global = true
+)
+public final class NodePageNamespace {
+
+	private static final Logger logger = LoggerFactory.getLogger(NodePageNamespace.class);
+
+	private NodeNamespace nodeNs;
+
+	public NodePageNamespace(Node node) {
+		this(new NodeNamespace(node));
+	}
+
+	public NodePageNamespace(NodeNamespace nodeNs) {
+		this.nodeNs = nodeNs;
+	}
+
+	public final Node node() {
+		return nodeNs.node();
+	}
+
+	public final NodeNamespace nodeNamespace() {
+		return nodeNs;
+	}
+
+	private DbDatabase db() {
+		return node().getSite().getDb();
+	}
+
+
+	public static final CommandSpec page_node = CommandSpec.DO;
+
+	@Command public void page_node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		out.print( interp.getArg(nodeNs,"do") );
+	}
+
+	public static final CommandSpec search_namespace = CommandSpec.DO()
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void search_namespace(IPrintWriter out,ScopedInterpreter<SearchNamespace> interp)
+		throws ServletException
+	{
+		ServletNamespace servletNamespace = interp.getFromStack(ServletNamespace.class);
+		out.print( interp.getArg(new SearchNamespace(node(),servletNamespace),"do") );
+	}
+
+
+	public static final CommandSpec edit_page_node = CommandSpec.DO()
+		.optionalParameters("commit")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void edit_page_node(IPrintWriter out,ScopedInterpreter<NodeEditorNamespace> interp) {
+		ServletNamespace servletNs = interp.getFromStack(ServletNamespace.class);
+		boolean commit = interp.getArgAsBoolean("commit",true);
+		DbDatabase db = db();
+		db.beginTransaction();
+		try {
+			nodeNs.refreshNode();
+			interp.getArgString(new NodeEditorNamespace(node(),servletNs),"do");
+			if( commit ) {
+				db.commitTransaction();
+				nodeNs.refreshNode();
+			}
+		} finally {
+			db.endTransaction();
+		}
+	}
+
+
+	public static final CommandSpec create_child_of_page_node = CommandSpec.DO()
+		.parameters("subject","message","is_html","kind")
+		.optionalParameters("commit","type")
+		.restrictedParameter("kind","app","post")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void create_child_of_page_node(IPrintWriter out,ScopedInterpreter<NodeEditorNamespace> interp)
+		throws ModelException, ServletException
+	{
+		ServletNamespace servletNs = interp.getFromStack(ServletNamespace.class);
+		HttpServletRequest request = servletNs.request;
+		boolean commit = interp.getArgAsBoolean("commit",true);
+		String subject = interp.getArgString("subject");
+		String message = interp.getArgString("message");
+		Message.Format msgFmt = interp.getArgAsBoolean("is_html",false) ? Message.Format.HTML : Message.Format.TEXT;
+		String type = interp.getArgString("type");
+		Node.Kind kind = "app".equals(interp.getArgString("kind"))? Node.Kind.APP : Node.Kind.POST;
+		Person visitor = servletNs.getVisitor();
+		DbDatabase db = db();
+		db.beginTransaction();
+		try {
+			nodeNs.refreshNode();
+			Node node = node();
+			Node newNode = visitor.newChildNode(kind,subject,message,msgFmt,node);
+			FileUpload.checkFileTags(newNode.getMessage(), visitor);
+			if (type != null)
+				newNode.setType(type);
+			interp.getArgString(new NodeEditorNamespace(newNode,servletNs),"do");
+			if( commit ) {
+				db.commitTransaction();
+				nodeNs.refreshNode();
+			}
+		} finally {
+			db.endTransaction();
+		}
+	}
+
+	private static final long TWO_HOURS = 2 * 60 * 60 * 1000;
+
+	@Command public void should_show_creation_notice(IPrintWriter out,Interpreter interp) {
+		long created = node().getWhenCreated().getTime();
+		out.print( System.currentTimeMillis() - created < TWO_HOURS );
+	}
+
+
+	//
+	// Spambot security checker
+	//
+	private static final byte SUBMIT_POSSIBILITIES = 10;
+	private static final long START = new Date().getTime();
+	private static final long FIVE_MINUTES = 5 * 60 * 1000;
+	private static final Random SECURITY_RND = new Random();
+
+	public static final CommandSpec antispam_submit_button = CommandSpec.DO()
+		.parameters("class", "value")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void antispam_submit_button(IPrintWriter out,Interpreter interp) {
+		ServletNamespace servletNs = interp.getFromStack(ServletNamespace.class);
+		HttpServletRequest request = servletNs.request;
+
+		String className = interp.getArgString("class");
+		String value = interp.getArgString("value");
+
+		Byte goodIndex = (byte) SECURITY_RND.nextInt(SUBMIT_POSSIBILITIES);
+		request.getSession().setAttribute("sec"+node().getId(), goodIndex);
+		for (byte i = 0; i < SUBMIT_POSSIBILITIES; i++) {
+			out.print("<input id=\"s" + i + "\" type=\"submit\" class=\""+ className + "\" value=\"" + value + "\" name=\"s" + i + "\" style=\"display:none\"/>");
+		}
+		String elementId = "s" + goodIndex;
+		StringBuilder bufValue = new StringBuilder();
+		for (int i = 0; i < elementId.length(); i++) {
+			if (bufValue.length() > 0)
+				bufValue.append(',');
+			bufValue.append((int) elementId.charAt(i));
+		}
+		out.print("<script type='text/javascript'>");
+		out.print("var sv=String.fromCharCode("+ bufValue.toString() +");");
+		out.print("document.getElementById(sv).style.display='inline-block';");
+		out.print("</script>");
+	}
+
+	public static final CommandSpec check_antispam_submit = CommandSpec.DO()
+		.optionalParameters("bypass")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void check_antispam_submit(IPrintWriter out,Interpreter interp)
+			throws TemplateException
+	{
+		ServletNamespace servletNs = interp.getFromStack(ServletNamespace.class);
+		HttpServletRequest request = servletNs.request;
+
+		String bypass = interp.getArgString("bypass");
+		if (bypass != null && request.getParameter(bypass) != null)
+			return;
+
+		String attributeName = "sec"+node().getId();
+		Byte goodIndex = (Byte) request.getSession().getAttribute(attributeName);
+		boolean isSafe = goodIndex != null;
+		if (isSafe) {
+			for (int i= 0; i < SUBMIT_POSSIBILITIES; i++) {
+				String param = request.getParameter("s"+i);
+				if (i == goodIndex)
+					isSafe = isSafe &&  param != null;
+				else
+					isSafe = isSafe && param == null;
+			}
+		}
+		request.getSession().removeAttribute(attributeName);
+		if (!isSafe) {
+			long now = new Date().getTime();
+			if (now - START > FIVE_MINUTES) {
+				logger.error("suspicious_request - IP: " + Jtp.getClientIpAddr(request));
+				throw TemplateException.newInstance("suspicious_request");
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/PagingNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,151 @@
+package nabble.view.web.template;
+
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.namespaces.IntegerNamespace;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.Namespace;
+
+
+@Namespace (
+	name = "paging",
+	global = true
+)
+public final class PagingNamespace {
+	private final int totalRows;
+	private final int rowsPerPage;
+	private final int currentPage;
+	private final int lastPage;
+	private final int before;
+	private final int after;
+
+	PagingNamespace(int totalRows,int currentRow,int rowsPerPage,int neighborPages) {
+		this.totalRows = totalRows;
+		this.rowsPerPage = rowsPerPage;
+
+		this.currentPage = (currentRow / rowsPerPage) + 1;
+		this.lastPage = (totalRows + rowsPerPage - 1) / rowsPerPage;
+		neighborPages++;
+		int before = currentPage - neighborPages;
+		if( before > 1 )
+			before++;
+		this.before = before;
+		int after = currentPage + neighborPages;
+		if( after < lastPage )
+			after--;
+		this.after = after;
+	}
+
+
+	@Command public void rows_per_page(IPrintWriter out,Interpreter interp) {
+		out.print( rowsPerPage );
+	}
+
+	/* Prints the number of rows the current page is meant to display */
+	@Command public void current_page_rows(IPrintWriter out,Interpreter interp) {
+		boolean hasNextPage = currentPage < lastPage;
+		out.print( hasNextPage? rowsPerPage : totalRows % rowsPerPage);
+	}
+
+	@Command public void current_page_number(IPrintWriter out,Interpreter interp) {
+		out.print( currentPage );
+	}
+
+	@Command public void has_paging(IPrintWriter out,Interpreter interp) {
+		out.print( totalRows > rowsPerPage );
+	}
+
+	@Command public void is_at_beginning(IPrintWriter out,Interpreter interp) {
+		out.print( before <= 1 );
+	}
+
+	@Command public void is_at_end(IPrintWriter out,Interpreter interp) {
+		out.print( after >= lastPage );
+	}
+
+	public static final CommandSpec neighboring_pages = CommandSpec.DO;
+
+	@Command public void neighboring_pages(IPrintWriter out,ScopedInterpreter<Page> interp) {
+		final int start = Math.max(1,before);
+		final int limit = Math.min(after,lastPage);
+		for( int i = start; i <= limit; i++ ) {
+			out.print( interp.getArg(page(i),"do") );
+		}
+	}
+
+	public static final CommandSpec current_page = CommandSpec.DO;
+
+	@Command public void current_page(IPrintWriter out,ScopedInterpreter<Page> interp) {
+		out.print( interp.getArg(page(currentPage),"do") );
+	}
+
+	public static final CommandSpec first_page = CommandSpec.DO;
+
+	@Command public void first_page(IPrintWriter out,ScopedInterpreter<Page> interp) {
+		out.print( interp.getArg(page(1),"do") );
+	}
+
+	public static final CommandSpec last_page = CommandSpec.DO;
+
+	@Command public void last_page(IPrintWriter out,ScopedInterpreter<Page> interp) {
+		out.print( interp.getArg(page(lastPage),"do") );
+	}
+
+	@Command public void has_previous_page(IPrintWriter out,Interpreter interp) {
+		out.print( currentPage > 1 );
+	}
+
+	public static final CommandSpec previous_page = CommandSpec.DO;
+
+	@Command public void previous_page(IPrintWriter out,ScopedInterpreter<Page> interp) {
+		out.print( interp.getArg(page(currentPage-1),"do") );
+	}
+
+	@Command public void has_next_page(IPrintWriter out,Interpreter interp) {
+		out.print( currentPage < lastPage );
+	}
+
+	public static final CommandSpec next_page = CommandSpec.DO;
+
+	@Command public void next_page(IPrintWriter out,ScopedInterpreter<Page> interp) {
+		out.print( interp.getArg(page(currentPage+1),"do") );
+	}
+
+	private Page page(int number) {
+		return new Page( number, number==currentPage, (number-1) * rowsPerPage );
+	}
+
+	@Namespace (
+		name = "paging_page",
+		global = true,
+		transparent = true
+	)
+	public static final class Page {
+		private final int number;
+		private final boolean isCurrent;
+		private final int row;
+
+		private Page(int number,boolean isCurrent,int row) {
+			this.number = number;
+			this.isCurrent = isCurrent;
+			this.row = row;
+		}
+
+		@Command public void page_number(IPrintWriter out,Interpreter interp) {
+			out.print( number );
+		}
+
+		@Command public void is_current_page(IPrintWriter out,Interpreter interp) {
+			out.print( isCurrent );
+		}
+
+		public static final CommandSpec page_row = CommandSpec.DO;
+
+		@Command public void page_row(IPrintWriter out,ScopedInterpreter<IntegerNamespace> interp) {
+			out.print( interp.getArg(new IntegerNamespace(row),"do") );
+		}
+
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/RegistrationNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,27 @@
+package nabble.view.web.template;
+
+import nabble.model.ModelException;
+import nabble.model.Site;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+
+@Namespace(
+	name = "registration",
+	global = false
+)
+public class RegistrationNamespace {
+
+	private String key;
+
+	public RegistrationNamespace(Site site, String email, String password, String userName, String nextUrl)
+		throws ModelException
+	{
+		this.key = site.newRegistration(email,password,userName,nextUrl);
+	}
+
+	@Command("key") public void _key(IPrintWriter out, Interpreter interp) {
+		out.print(key);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/RequestNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,181 @@
+package nabble.view.web.template;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.ViewCount;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.StringList;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+@Namespace (
+	name = "http_request",
+	global = true
+)
+public class RequestNamespace {
+	public final HttpServletRequest request;
+
+	public RequestNamespace(HttpServletRequest request) {
+		this.request = request;
+	}
+
+	public static final CommandSpec NAME = new CommandSpec.Builder()
+		.dotParameter("name")
+		.build()
+	;
+
+	public static final CommandSpec get_parameter = NAME;
+
+	@Command public void get_parameter(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print( request.getParameter(name) );
+	}
+
+	public static final CommandSpec get_request_header = NAME;
+
+	@Command public void get_request_header(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print( request.getHeader(name) );
+	}
+
+	public static final CommandSpec get_cookie_value = NAME;
+
+	@Command public void get_cookie_value(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print( ServletUtils.getCookieValue(request, name) );
+	}
+
+	@Command public void request_method(IPrintWriter out,Interpreter interp) {
+		out.print( request.getMethod() );
+	}
+
+	@Command public void servlet_path(IPrintWriter out,Interpreter interp) {
+		out.print( request.getServletPath() );
+	}
+
+	@Command public void server_name(IPrintWriter out,Interpreter interp) {
+		out.print( request.getServerName() );
+	}
+
+	@Command public void current_url(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( ServletUtils.getCurrentURL(request) ) );
+	}
+
+	@Command public void current_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( Jtp.getCurrentPath(request) ) );
+	}
+
+	// overridden in ServletNamespace
+	public static final CommandSpec get_node_from_request_parameter = CommandSpec.DO;
+
+	@Command public void get_node_from_request_parameter(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp)
+		throws IOException, ServletException, TemplateException
+	{
+		Node node = NabbleNamespace.current().site().getNode( Jtp.getLong(request, "node") );
+		if( node != null )
+			out.print( interp.getArg(new NodeNamespace(node),"do") );
+	}
+
+	public static final CommandSpec get_parameter_values = CommandSpec.DO()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void get_parameter_values(IPrintWriter out,ScopedInterpreter<ParameterValueList> interp)
+		throws IOException, ServletException
+	{
+		String name = interp.getArgString("name");
+		String[] values = request.getParameterValues(name);
+		List<String> list = values == null ? Collections.<String>emptyList() : Arrays.asList(values);
+		Object block = interp.getArg(new ParameterValueList(list),"do");
+		out.print(block);
+	}
+
+	public static final CommandSpec get_parameters_pipe_separated = CommandSpec.DO()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void get_parameters_pipe_separated(IPrintWriter out,ScopedInterpreter<ParameterValueList> interp) {
+		String name = interp.getArgString("name");
+		String values = request.getParameter(name);
+		List<String> list = values == null ? Collections.<String>emptyList() : Arrays.asList(values.split("\\|"));
+		for (int i = 0; i < list.size(); i++) {
+			list.set(i, HtmlUtils.urlDecode(list.get(i)));
+		}
+		Object block = interp.getArg(new ParameterValueList(list),"do");
+		out.print(block);
+	}
+
+	@Namespace (
+		name = "parameter_value_list",
+		global = true
+	)
+	public static final class ParameterValueList extends StringList {
+
+		ParameterValueList(List<String> values) {
+			super(values);
+		}
+
+		public List<String> values() {
+			return elements;
+		}
+
+		@Command public void current_parameter_value(IPrintWriter out,Interpreter interp) {
+			out.print(get());
+		}
+
+
+		// hack for now
+
+		private Map<String,Integer> views = null;
+
+		public static final CommandSpec node_views = new CommandSpec.Builder()
+			.parameters("node_id")
+			.build()
+		;
+
+		@Command public void node_views(IPrintWriter out,Interpreter interp) {
+			if( views == null ) {
+				Site site = NabbleNamespace.current().site();
+				List<Long> ids = new ArrayList<Long>();
+				for( String value : elements ) {
+					ids.add(Long.parseLong(value));
+				}
+				views = new HashMap<String,Integer>();
+				for( Map.Entry<Long,Integer> entry : ViewCount.getCounts(site,ids).entrySet() ) {
+					views.put( Long.toString(entry.getKey()), entry.getValue() );
+				}
+			}
+			String nodeId = interp.getArgString("node_id");
+			out.print( views.get(nodeId) );
+		}
+
+	}
+
+
+
+
+
+	@Command public void request_base_url(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( Jtp.getBaseUrl(request) ) );
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/ServletNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,475 @@
+package nabble.view.web.template;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.JtpContext;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.DailyNumber;
+import nabble.model.ModelException;
+import nabble.model.Node;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.model.ViewCount;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.Encoder;
+import nabble.naml.compiler.ExitException;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.CommandDoc;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.MyJtpServlet;
+import nabble.view.lib.Shared;
+import nabble.view.web.user.OnlineStatus;
+import nabble.view.lib.Recaptcha;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+@Namespace (
+	name = "servlet",
+	global = true
+)
+public final class ServletNamespace extends RequestNamespace {
+	private static final Logger logger = LoggerFactory.getLogger(ServletNamespace.class);
+
+	public final HttpServletResponse response;
+	private final NabbleNamespace nabbleNs;
+	private UserNamespace visitorNs = null;
+
+	public ServletNamespace(HttpServletRequest request, HttpServletResponse response) {
+		super(request);
+		this.response = response;
+		this.nabbleNs = NabbleNamespace.current();
+	}
+
+	public final JtpContext jtpContext() {
+		return MyJtpServlet.getJtpContext();
+	}
+
+	private Site site() {
+		return nabbleNs.site();
+	}
+
+	private UserNamespace visitorNamespace()
+		throws ServletException
+	{
+		if( visitorNs == null ) {
+			if(Jtp.isCached(request,response) )
+				throw new RuntimeException("can't get visitor on cached page");
+			Person visitor = Jtp.getVisitor(request, response);
+			visitorNs = new UserNamespace(visitor);
+		}
+		return visitorNs;
+	}
+
+	@Command public void dont_cache(IPrintWriter out,Interpreter interp) {
+		Jtp.dontCache(response);
+	}
+
+	@Command public void default_host_name(IPrintWriter out,Interpreter interp) {
+		out.print(Jtp.getDefaultHost());
+	}
+
+	public Person getVisitor()
+		throws ServletException
+	{
+		return visitorNamespace().person();
+	}
+
+	public User getVisitorUser()
+		throws ServletException
+	{
+		Person visitor = getVisitor();
+		return !(visitor instanceof User) ? null : (User)visitor;
+	}
+
+
+	public static final CommandSpec visitor = CommandSpec.DO;
+
+	@Command public void visitor(IPrintWriter out,ScopedInterpreter<UserNamespace> interp)
+		throws ServletException
+	{
+		out.print( interp.getArg(visitorNamespace(),"do") );
+	}
+
+
+	private Map<String,FieldNamespace> fields;
+
+	public static final CommandSpec field = new CommandSpec.Builder()
+		.scopedParameters("do")
+		.dotParameter("do")
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void field(IPrintWriter out,ScopedInterpreter<FieldNamespace> interp) {
+		String name = interp.getArgString("name");
+		if (fields == null)
+			fields = new HashMap<String,FieldNamespace>();
+		FieldNamespace field = fields.get(name);
+		if( field == null ) {
+			field = new FieldNamespace(name);
+			field.setValue(request);
+			FieldNamespace f = fields.put(name,field);
+			if( f != null )
+				throw new RuntimeException("field named '"+name+"' already defined");
+		}
+		out.print( interp.getArg(field,"do") );
+	}
+
+	public static final CommandSpec redirect_to = CommandSpec.NO_OUTPUT()
+		.dotParameter("url")
+		.build()
+	;
+
+	@Command public void redirect_to(IPrintWriter out,Interpreter interp)
+		throws IOException
+	{
+		interp.setEncoder(Encoder.TEXT);
+		String redirectUrl = interp.getArgString("url").trim();
+		Jtp.sendRedirect(request,response,redirectUrl);
+		throw new ExitException();
+	}
+
+	public static final CommandSpec redirect_with_notice = new CommandSpec.Builder()
+		.parameters("url")
+		.dotParameter("notice")
+		.build()
+	;
+
+	@Command public void redirect_with_notice(IPrintWriter out,Interpreter interp)
+		throws IOException, ServletException
+	{
+		String notice = interp.getArgString("notice").trim();
+		notice = notice.replaceAll("(\n|\r|\t)","");
+		notice = HtmlUtils.javascriptStringEncode(notice);
+		String redirectUrl = interp.getArgString("url").trim();
+		Shared.javascriptRedirect(request, response, redirectUrl, "Nabble.setVar('notice','" + notice + "');");
+		throw new ExitException();
+	}
+
+	public static final CommandSpec profile_update_with_redirection_to = CommandSpec.NO_OUTPUT()
+		.dotParameter("url")
+		.build()
+	;
+
+	@Command public void profile_update_with_redirection_to(IPrintWriter out,Interpreter interp)
+			throws IOException, ServletException
+	{
+		StringBuffer js = new StringBuffer();
+		String userId = ServletUtils.getCookieValue(request,"userId");
+		if (Jtp.isInteger(userId)) {
+			Person visitor = site().getUser(Long.valueOf(userId));
+			if (visitor != null && visitor instanceof User) {
+				User user = (User) visitor;
+				String name = user.getName();
+				String passcookie = user.getPasscookie();
+				ServletUtils.setCookie(request, response, "username", HtmlUtils.urlEncode(name), true, null);
+				ServletUtils.setCookie(request, response, "password", HtmlUtils.urlEncode(passcookie), true, null);
+				js.append("if (parent.nabbleinfo) {");
+				js.append("Nabble.setCookie('username','").append(HtmlUtils.javascriptStringEncode(HtmlUtils.urlEncode(name))).append("');");
+				js.append("Nabble.setCookie('password','").append(HtmlUtils.javascriptStringEncode(HtmlUtils.urlEncode(passcookie))).append("');");
+				js.append("}");
+			}
+		}
+		String redirectUrl = interp.getArgString("url").trim();
+		Shared.javascriptRedirect(request, response, redirectUrl, js.toString());
+		throw new ExitException();
+	}
+
+	public static final CommandSpec send_http_error = new CommandSpec.Builder()
+		.parameters("code")
+		.dotParameter("text")
+		.build()
+	;
+
+	@Command public void send_http_error(IPrintWriter out,Interpreter interp)
+		throws IOException
+	{
+		String text = interp.getArgString("text").trim();
+		int code = interp.getArgAsInt("code");
+		response.sendError(code, text);
+		throw new ExitException();
+	}
+
+
+	@Command public void set_visitor_online(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		Person visitor = Jtp.getVisitor(request, response);
+		OnlineStatus.setOnline(request, visitor, site());
+	}
+
+	public static final CommandSpec author_is_online = new CommandSpec.Builder()
+		.parameters("search_id")
+		.build()
+	;
+
+	@Command public void author_is_online(IPrintWriter out,Interpreter interp) {
+		String authorId = interp.getArgString("search_id");
+		out.print( OnlineStatus.isOnline(authorId, site()) );
+	}
+
+
+	public static final CommandSpec nabble_html = new CommandSpec.Builder()
+		.scopedParameters("do","output")
+		.build()
+	;
+	@CommandDoc(
+		"Commands in \"do\" call \"put_in_head\" and print to the body at the same time.  Commands in \"output\" can't do this.  The \"output\" is the final output."
+	)
+	@Command public void nabble_html(IPrintWriter out,ScopedInterpreter<HtmlNamespace> interp) {
+		HtmlNamespace ns = new HtmlNamespace(interp);
+		out.print( interp.getArg(ns,"output") );
+	}
+
+
+	public static final CommandSpec check_user = new CommandSpec.Builder()
+		.dotParameter("user_id")
+		.outputtedParameters()
+		.build()
+	;
+
+	@Command public void check_user(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		String userId = interp.getArgString("user_id");
+		if( site().getUser(Jtp.parseLong(request,userId)) == null )
+			throw Jtp.servletException(request,"user not found: "+userId);
+	}
+
+
+	public static final CommandSpec do_login = new CommandSpec.Builder()
+		.parameters("email","password")
+		.optionalParameters("nextUrl")
+		.build()
+	;
+
+	@Command public void do_login(IPrintWriter out,Interpreter interp)
+		throws IOException, ServletException
+	{
+		String email = interp.getArgString("email").trim();
+		String password = interp.getArgString("password");
+		String nextUrl = interp.getArgString("nextUrl");
+		if( nextUrl == null )
+			nextUrl = "/";
+		User user = site().getUserFromEmail(email);
+		if( user != null && user.isRegistered() && user.checkPassword(password) ) {
+			Jtp.doLogin(request,response,user,true);
+			DailyNumber.logins.inc();
+
+			Shared.javascriptRedirect(request,response, nextUrl);
+			throw new ExitException();
+		}
+	}
+
+	public static final CommandSpec registration = CommandSpec.DO()
+		.parameters("email","password","user_name")
+		.optionalParameters("next_url")
+		.build()
+	;
+
+	@Command public void registration(IPrintWriter out,ScopedInterpreter<RegistrationNamespace> interp)
+		throws IOException, ServletException, ModelException
+	{
+		String email = interp.getArgString("email").trim();
+		String userName = interp.getArgString("user_name");
+		String password = interp.getArgString("password");
+		String nextUrl = interp.getArgString("next_url");
+		if( nextUrl == null )
+			nextUrl = "/";
+		out.print( interp.getArg(new RegistrationNamespace(site(), email,password,userName,nextUrl),"do") );
+	}
+
+	public static final CommandSpec check_captcha = CommandSpec.NO_OUTPUT;
+
+	@Command public void check_captcha(IPrintWriter out, Interpreter interp)
+		throws ModelException.InvalidRecaptcha, IOException
+	{
+		Recaptcha.check(request);
+	}
+
+	@Command public void do_logout(IPrintWriter out,Interpreter interp) {
+		Jtp.logout(request,response);
+	}
+
+	public static final CommandSpec set_response_header = new CommandSpec.Builder()
+		.parameters("name","value")
+		.outputtedParameters()
+		.build()
+	;
+
+	@Command public void set_response_header(IPrintWriter out,Interpreter interp) {
+		response.setHeader( interp.getArgString("name"), interp.getArgString("value") );
+	}
+
+	@Command public void remote_address(IPrintWriter out,Interpreter interp) {
+		out.print(Jtp.getClientIpAddr(request));
+	}
+
+	public static final CommandSpec increment_node_view_count = new CommandSpec.Builder()
+		.parameters("node_id")
+		.build()
+	;
+
+	@Command public void increment_node_view_count(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		Site site = NabbleNamespace.current().site();
+		long nodeId = interp.getArgAsLong("node_id");
+		boolean isUser = getVisitor() instanceof User;
+		ViewCount.inc(site,nodeId,isUser);
+	}
+
+
+	public static final CommandSpec get_node_from_request_parameter = CommandSpec.DO;
+
+	@Command public void get_node_from_request_parameter(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp)
+		throws IOException, ServletException, TemplateException
+	{
+		Node node = site().getNode( Jtp.getLong(request, "node") );
+		if( node == null ) {
+			throw TemplateException.newInstance("node_not_found");
+		}
+		out.print( interp.getArg(new NodeNamespace(node),"do") );
+	}
+
+	public static final CommandSpec get_user_from_parameter = CommandSpec.DO;
+
+	@Command public void get_user_from_parameter(IPrintWriter out,ScopedInterpreter<UserNamespace> interp)
+		throws IOException, ServletException
+	{
+		String userId = Jtp.getString(request,"user");
+		Person person = site().getPerson(userId);
+		if( person == null ) {
+			response.sendError(HttpServletResponse.SC_NOT_FOUND, "User not found.");
+			throw new ExitException();
+		}
+		out.print( interp.getArg(new UserNamespace(person),"do") );
+	}
+
+
+	Set<String> cacheEvents = null;
+
+	public void cache(String event) {
+		if( cacheEvents != null )
+			cacheEvents.add(event);
+	}
+
+	public static final CommandSpec uncache_for = CommandSpec.NO_OUTPUT()
+		.scopedParameters("do")
+		.dotParameter("do")
+		.optionalParameters("do")
+		.build()
+	;
+
+	@Command public void uncache_for(IPrintWriter out,ScopedInterpreter<CacheNamespace> interp) {
+		if( visitorNs != null )
+			throw new RuntimeException("can't cache page that depends on visitor");
+		CacheNamespace ns = new CacheNamespace();
+		try {
+			interp.getArgString(ns,"do");
+			cacheEvents = ns.events;
+			request.setAttribute(Jtp.CACHED,"yes");
+		} catch(CacheNamespace.DontCache e) {}
+	}
+
+	public static final CommandSpec set_anonymous_name = CommandSpec.NO_OUTPUT()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void set_anonymous_name(IPrintWriter out,Interpreter interp)
+		throws ServletException
+	{
+		Person visitor = getVisitor();
+		String anonymousName = interp.getArgString("name");
+		if( visitor instanceof User
+			|| anonymousName == null
+			|| anonymousName.trim().length() == 0
+			|| !"POST".equals(request.getMethod())  // ???
+		)
+			return;
+		try {
+			visitor.setName(anonymousName);
+		} catch(ModelException e) {
+			throw new RuntimeException(e);
+		}
+        ServletUtils.setCookie(request, response, "anonymousName", HtmlUtils.urlEncode(anonymousName), true, null);
+	}
+
+
+	public static final CommandSpec get_registration = CommandSpec.DO()
+		.parameters("registration_key","email")
+		.build()
+	;
+
+	@Command public void get_registration(IPrintWriter out,ScopedInterpreter<UserNamespace> interp)
+		throws ModelException
+	{
+		String key = interp.getArgString("registration_key");
+		String email = interp.getArgString("email");
+		User user = site().getRegistration(key);
+		if (user==null || !user.getEmail().equalsIgnoreCase(email)) {
+			logger.error("Registration failed: url="+ServletUtils.getCurrentURL(request)+" email="+email+" user="+(user==null?"null":user.getEmail())+" key="+key+" user-agent="+request.getHeader("user-agent"));
+			out.print((String)null);
+			return;
+		}
+		out.print( interp.getArg(new UserNamespace(user),"do") );
+	}
+
+	public static final CommandSpec get_next_url_from_registration = new CommandSpec.Builder()
+		.parameters("registration_key")
+		.build()
+	;
+
+	@Command public void get_next_url_from_registration(IPrintWriter out,Interpreter interp) {
+		String key = interp.getArgString("registration_key");
+		String nextUrl = site().getNextUrl(key);
+		if( nextUrl==null )
+			nextUrl = "/";
+		out.print(nextUrl);
+	}
+
+	@Command public void visitor_ip_address(IPrintWriter out,Interpreter interp) {
+		out.print(Jtp.getClientIpAddr(request));
+	}
+
+	/** hack for now */
+
+	private static final Set<String> testHosts = new HashSet<String>();
+	static {
+		testHosts.add("r.789695.n4.nabble.com");
+		testHosts.add("imagej.1557.x6.nabble.com");
+		testHosts.add("f.27.me.nabble.com:8081");
+	}
+
+	@Command public void link_test(IPrintWriter out,Interpreter interp) {
+		Site site = site();
+		String domain = site.getCustomDomain();
+		String host = request.getHeader("host");
+		if( (domain != null || testHosts.contains(host)) && !site.isEmbarrassing() && request.getServletPath().equals("/") ) {
+//		if( domain != null && !site.isEmbarrassing() && request.getServletPath().equals("/") || testHosts.contains(host) ) {
+			out.print(" - <a clck href='https://www.super-resume.com/'>Free Resume Builder</a>");
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/ServletNamespaceUtils.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,48 @@
+package nabble.view.web.template;
+
+import nabble.model.User;
+import nabble.model.Person;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.CommandSpec;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+public class ServletNamespaceUtils extends NamespaceUtils {
+
+	public static final CommandSpec requiresServletNamespace = new CommandSpec.Builder()
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	private ServletNamespace servletNamespace = null;
+
+	public ServletNamespace servletNamespace(Interpreter interp) {
+//		return interp.getFromStack(ServletNamespace.class);
+		if( servletNamespace == null )
+			servletNamespace = interp.getFromStack(ServletNamespace.class);
+		return servletNamespace;
+	}
+
+	public HttpServletRequest request(Interpreter interp) {
+		return servletNamespace(interp).request;
+	}
+
+	public HttpServletResponse response(Interpreter interp) {
+		return servletNamespace(interp).response;
+	}
+
+	public Person visitor(Interpreter interp)
+		throws ServletException
+	{
+		return servletNamespace(interp).getVisitor();
+	}
+
+	public User visitorUser(Interpreter interp)
+		throws ServletException
+	{
+		return servletNamespace(interp).getVisitorUser();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/SubscriptionNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,191 @@
+package nabble.view.web.template;
+
+import fschmidt.util.java.Base64;
+import fschmidt.util.mail.MailAddress;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.Subscription;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.ListSequence;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Jtp;
+
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+
+
+@Namespace (
+	name = "subscription",
+	global = false
+)
+public final class SubscriptionNamespace {
+	private Node node;
+	private User user;
+	private Subscription subscription;
+	private final Site site;
+
+	public SubscriptionNamespace(Node node, User user) {
+		this.node = node;
+		this.user = user;
+		this.subscription = user.getSubscription(node);
+		this.site = node.getSite();
+	}
+
+	public SubscriptionNamespace(Subscription subscription) {
+		this.node = subscription.getNode();
+		this.user = subscription.getSubscriber();
+		this.subscription = subscription;
+		this.site = node.getSite();
+	}
+
+	public SubscriptionNamespace(String code, HttpServletRequest request)
+		throws TemplateException
+	{
+		this.site = Jtp.getSite(request);
+		Object[] emailAndNode = getValuesFromCode(code, site);
+		this.user = site.getUserFromEmail((String) emailAndNode[0]);
+		this.node = (Node) emailAndNode[1];
+		this.subscription = user.getSubscription(node);
+	}
+
+	public static final CommandSpec this_subscription = CommandSpec.DO;
+
+	@Command public void this_subscription(IPrintWriter out, ScopedInterpreter<SubscriptionNamespace> interp) {
+		out.print( interp.getArg(this,"do") );
+	}
+
+    @Command public void is_subscribed(IPrintWriter out, Interpreter interp) {
+		out.print( subscription != null );
+	}
+
+	public static final CommandSpec _node = CommandSpec.DO;
+
+	@Command("node") public void _node(IPrintWriter out,ScopedInterpreter<NodeNamespace> interp) {
+		NodeNamespace nodeNs = new NodeNamespace(node);
+		out.print( interp.getArg(nodeNs,"do") );
+	}
+
+	public static final CommandSpec _user = CommandSpec.DO;
+
+	@Command("user") public void _user(IPrintWriter out,ScopedInterpreter<UserNamespace> interp) {
+		UserNamespace visitorModel = new UserNamespace(user);
+		out.print( interp.getArg(visitorModel,"do") );
+	}
+
+	@Command public void to(IPrintWriter out, Interpreter interp) {
+		out.print( subscription.getTo() );
+	}
+
+    @Command public void type(IPrintWriter out, Interpreter interp) {
+		out.print( subscription.getType() );
+	}
+
+	public static final CommandSpec save = new CommandSpec.Builder()
+		.parameters("to","type")
+		.build()
+	;
+
+    @Command public void save(IPrintWriter out, Interpreter interp) {
+		String toS = interp.getArgString("to");
+		Subscription.To to = toS==null ? Subscription.To.DESCENDANTS : Subscription.To.valueOf(toS);
+		Subscription.Type type =
+			node.getKind() == Node.Kind.APP?
+			Subscription.Type.valueOf(interp.getArgString("type")) :
+			Subscription.Type.INSTANT;
+
+		if (subscription == null) {
+			subscription = user.subscribe(node, to, type);
+		} else {
+			subscription.setTo(to);
+			subscription.setType(type);
+		}
+	}
+
+    @Command public void remove(IPrintWriter out, Interpreter interp) {
+		subscription.delete();
+	}
+
+	private static final String SALT = "zDf3s";
+
+	@Command public void subscription_code(IPrintWriter out, Interpreter interp) {
+		String email = user.getEmail();	
+		out.print( getCode(email, node) );
+	}
+
+	public static String getCode(String email, Node node) {
+		int key = (email + SALT).hashCode();
+		String params = email + '|' + node.getId() + '|' + key;
+		return Base64.encode(params.getBytes());
+	}
+
+	private static Object[] getValuesFromCode(String code, Site site) throws TemplateException {
+		String[] values = decodeParams(code);
+		if (values == null)
+			values = decodeParams(code+"=="); // try with "==" at the end (most common problem with invalid codes)
+		if (values == null)
+			throw ModelException.newInstance("invalid_link");
+
+		long nodeId = Long.valueOf(values[1]);
+		Node node = site.getNode(nodeId);
+		if (node == null || !node.getSite().equals(site))
+			throw ModelException.newInstance("invalid_link");
+		return new Object[] { values[0], node };
+	}
+
+	private static String[] decodeParams(String code) {
+		String params;
+		try {
+			params = new String(Base64.decode(code));
+		} catch (Base64.InvalidCharEncodedStringException e) {
+			return null;
+		}
+		if (params.indexOf('|') == -1)
+			return null;
+		String[] values = params.split("\\|");
+		if (!new MailAddress(values[0]).isValid())
+			return null;
+		try {
+			Long.valueOf(values[1]);
+		} catch (NumberFormatException e) {
+			return null;
+		}
+		String key = (values[0] + SALT).hashCode() + "";
+		if (!key.equals(values[2]))
+			return null;
+
+		return values;
+	}
+
+
+
+	@Namespace (
+		name = "subscription_list",
+		global = true
+	)
+	public static final class SubscriptionList extends ListSequence<Subscription> {
+		private SubscriptionNamespace ns = null;
+
+		public SubscriptionList(List<Subscription> subscriptions) {
+			super(subscriptions);
+		}
+
+		public static final CommandSpec current_subscription = CommandSpec.DO;
+
+		@Command public void current_subscription(IPrintWriter out,ScopedInterpreter<SubscriptionNamespace> interp) {
+			Subscription subscription = get();
+			if( ns==null || ns.subscription != subscription )
+				ns = new SubscriptionNamespace(subscription);
+			out.print( interp.getArg(ns,"do") );
+		}
+
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/TagArgs.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,15 @@
+package nabble.view.web.template;
+
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.IPrintWriter;
+
+
+public final class TagArgs {
+	public final IPrintWriter out;
+	public final Interpreter interp;
+
+	public TagArgs(IPrintWriter out,Interpreter interp) {
+		this.out = out;
+		this.interp = interp;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/UrlMapperNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,117 @@
+package nabble.view.web.template;
+
+import fschmidt.util.servlet.UrlMapping;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.compiler.Namespace;
+import nabble.view.lib.Jtp;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+
+@Namespace (
+	name = "url_mapper",
+	global = true
+)
+public final class UrlMapperNamespace {
+	private final String path;
+	private final Map<String,String[]> params = new HashMap<String,String[]>();
+
+	private UrlMapperNamespace(HttpServletRequest request) {
+		this( request.getServletPath() + Jtp.hideNull(request.getPathInfo()) );
+	}
+
+	private UrlMapperNamespace(String path) {
+		this.path = path;
+	}
+
+	@Command("path") public void _path(IPrintWriter out,Interpreter interp) {
+		out.print( path );
+	}
+
+	public static final CommandSpec set_parameter = new CommandSpec.Builder()
+		.parameters("name")
+		.dotParameter("value")
+		.outputtedParameters()
+		.build()
+	;
+
+	@Command public void set_parameter(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		String value = interp.getArgString("value");
+		params.put(name,new String[]{value});
+	}
+
+	public static UrlMapping getUrlMapping(HttpServletRequest request) {
+		Site site = Jtp.getSite(request);
+		if( site == null )
+			return null;
+		Template template = site.getTemplate( "url mapper" ,
+			BasicNamespace.class, NabbleNamespace.class, UrlMapperNamespace.class
+		);
+		if( template == null )
+			return null;
+		UrlMapperNamespace umn = new UrlMapperNamespace(request);
+		template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(),
+			new BasicNamespace(template), new NabbleNamespace(site), umn
+		);
+		if( umn.params.isEmpty() )
+			return null;
+		return new UrlMapping(NamlServlet.class,umn.params);
+	}
+
+	public static Map<String,String> getParams(String urlStr) {
+		try {
+			URL url = new URL(urlStr);
+			String domain = url.getHost();
+			Long siteId = Jtp.getSiteIdFromDomain(domain);
+			if( siteId == null )
+				return null;
+			Site site = ModelHome.getSite(siteId);
+			Template template = site.getTemplate( "url mapper" ,
+				BasicNamespace.class, NabbleNamespace.class, UrlMapperNamespace.class
+			);
+			if( template == null )
+				return null;
+			UrlMapperNamespace umn = new UrlMapperNamespace(url.getPath());
+			template.run( TemplatePrintWriter.NULL, Collections.<String,Object>emptyMap(),
+				new BasicNamespace(template), new NabbleNamespace(site), umn
+			);
+			Map<String,String> params = new HashMap<String,String>();
+			for( Map.Entry<String,String[]> entry : umn.params.entrySet() ) {
+				params.put( entry.getKey(), entry.getValue()[0] );
+			}
+			return params;
+		} catch(MalformedURLException e) {
+			return null;
+		}
+	}
+
+	public static Node getNodeFromUrl(String url) {
+		Site site = Jtp.getSiteFromUrl(url);
+		if( site == null )
+			return null;
+		Map<String,String> params = getParams(url);
+		if( params == null )
+			return null;
+		try {
+			return site.getNode( Long.parseLong( params.get("node") ) );
+		} catch(NumberFormatException e) {
+			return null;
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/UserNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,571 @@
+package nabble.view.web.template;
+
+import fschmidt.util.java.Filter;
+import fschmidt.util.java.HtmlUtils;
+import nabble.model.DailyNumber;
+import nabble.model.FilteredNodeIterator;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.NodeIterator;
+import nabble.model.NodeSearcher;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.model.Subscription;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.ListSequence;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+
+@Namespace (
+	name = "user",
+	global = false
+)
+public final class UserNamespace {
+	private final Person personR;
+	private User userR;
+
+	public UserNamespace(Person person) {
+		if( person == null )
+			throw new NullPointerException("person is null");
+		this.personR = person;
+		this.userR = person instanceof User ? (User)person : null;
+	}
+
+	public void refreshUser() {
+		if( isUser() )
+			userR = userR.getGoodCopy();
+	}
+
+	public boolean isUser() {
+		return userR != null;
+	}
+
+	public User user() {
+		if( !isUser() )
+			return null;
+		return userR;
+	}
+
+	public Person person() {
+		return isUser() ? user() : personR;
+	}
+
+	private Site site() {
+		return person().getSite();
+	}
+
+	public static final CommandSpec this_user = CommandSpec.DO;
+
+	@Command public void this_user(IPrintWriter out,ScopedInterpreter<UserNamespace> interp) {
+		out.print( interp.getArg(this,"do") );
+	}
+
+	@Command public void id(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( person().getIdString() ) );
+	}
+
+	@Command public void search_id(IPrintWriter out,Interpreter interp) {
+		out.print( person().getSearchId() );
+	}
+
+	public static final CommandSpec name = new CommandSpec.Builder()
+		.optionalParameters("truncate")
+		.build()
+	;
+
+	@Command public void name(IPrintWriter out,Interpreter interp)
+		throws IOException
+	{
+		String truncate = interp.getArgString("truncate");
+		String name = person().getName();
+		if( name==null ) {
+			out.print((String)null);
+			return;
+		}
+		if (truncate != null) {
+			try {
+				name = Jtp.truncate(name,Integer.valueOf(truncate),"...");
+			} catch (NumberFormatException e) {
+				throw new RuntimeException("Invalid \"truncate\" attribute: " + truncate);
+			}
+		}
+		out.print( interp.encode(name) );
+	}
+
+	public static final CommandSpec avatar_url = new CommandSpec.Builder()
+		.optionalParameters("size")
+		.build()
+	;
+
+	@Command public void avatar_url(IPrintWriter out,Interpreter interp)
+		throws IOException
+	{
+		String size = interp.getArgString("size");
+		out.print( interp.encode(Shared.getAvatarImageURL(person(), "small".equals(size))) );
+	}
+
+	public static final CommandSpec default_avatar_url = new CommandSpec.Builder()
+		.optionalParameters("size")
+		.build()
+	;
+
+	@Command public void default_avatar_url(IPrintWriter out,Interpreter interp)
+		throws IOException
+	{
+		String size = interp.getArgString("size");
+		out.print( interp.encode(Shared.getDefaultAvatarImageURL("small".equals(size))) );
+	}
+
+	@Command public void is_registered(IPrintWriter out,Interpreter interp) {
+		out.print( isUser() && user().isRegistered() );
+	}
+
+	@Command public void has_too_many_posts(IPrintWriter out,Interpreter interp) {
+		out.print( isUser() && user().hasTooManyPosts() );
+	}
+
+	public static final CommandSpec registration_date = CommandSpec.DO;
+
+	@Command public void registration_date(IPrintWriter out,ScopedInterpreter<DateNamespace> interp) {
+		out.print( interp.getArg( new DateNamespace(user().getRegistered()), "do" ) );
+	}
+
+	@Command public void is_deactivated(IPrintWriter out,Interpreter interp) {
+		out.print( isUser() && user().isDeactivated() );
+	}
+
+	@Command public void is_anonymous(IPrintWriter out,Interpreter interp) {
+		out.print( !isUser() );
+	}
+
+	@Command public void is_authenticated(IPrintWriter out,Interpreter interp) {
+		out.print( isUser() );
+	}
+
+	@Command public void user_email(IPrintWriter out,Interpreter interp) {
+		out.print( user().getEmail() );
+	}
+
+	@Command public void resetcode(IPrintWriter out,Interpreter interp) {
+		out.print( user().getResetcode() );
+	}
+
+	@Command public void send_email_url(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/user/SendEmail.jtp?type=user&user=" + user().getId() ) );
+	}
+
+
+	public static final CommandSpec is_in_group = new CommandSpec.Builder()
+		.dotParameter("group")
+		.build()
+	;
+
+	@Command public void is_in_group(IPrintWriter out,Interpreter interp) {
+		String group = interp.getArgString("group");
+		out.print( isUser() && Permissions.isInGroup(user(),group) );
+	}
+
+	public static final CommandSpec has_permission = new CommandSpec.Builder()
+		.parameters("node","permission_node","permission")
+		.build()
+	;
+
+	@Command public void has_permission(IPrintWriter out,Interpreter interp) {
+		NodeNamespace nodeNs = interp.getArgAsNamespace(NodeNamespace.class,"node");
+		NodeNamespace permissionNodeNs = interp.getArgAsNamespace(NodeNamespace.class,"permission_node");
+		String perm = interp.getArgString("permission");
+		out.print( Permissions.hasPermission(permissionNodeNs.node(),nodeNs.node(),person(),perm) );
+	}
+
+	public static final CommandSpec has_site_permission = new CommandSpec.Builder()
+		.parameters("permission")
+		.build()
+	;
+
+	@Command public void has_site_permission(IPrintWriter out,Interpreter interp) {
+		String perm = interp.getArgString("permission");
+		out.print( Permissions.hasSitePermission(NabbleNamespace.current().site(),person(),perm) );
+	}
+
+	public static final CommandSpec owns = new CommandSpec.Builder()
+		.dotParameter("node")
+		.build()
+	;
+
+	@Command public void owns(IPrintWriter out,Interpreter interp) {
+		NodeNamespace nodeNs = interp.getArgAsNamespace(NodeNamespace.class,"node");
+		out.print( nodeNs.node().getOwner().equals(person()) );
+	}
+
+    public static final CommandSpec reply_address_for = new CommandSpec.Builder()
+		.dotParameter("node")
+		.build()
+	;
+
+	@Command public void reply_address_for(IPrintWriter out,Interpreter interp) {
+		NodeNamespace nodeNs = interp.getArgAsNamespace(NodeNamespace.class,"node");
+		out.print( ModelHome.getNodeReplyAddress(nodeNs.node(), user()) );
+	}
+
+	@Command public void bounces_address(IPrintWriter out,Interpreter interp) {
+		out.print( ModelHome.getUserBouncesAddress(user()) );
+	}
+
+	public static final CommandSpec as_user_page = CommandSpec.DO;
+
+	@Command public void as_user_page(IPrintWriter out,ScopedInterpreter<UserPageNamespace> interp) {
+		out.print( interp.getArg(new UserPageNamespace(this),"do") );
+	}
+
+
+	public static final CommandSpec groups = CommandSpec.DO;
+
+	@Command public void groups(IPrintWriter out,ScopedInterpreter<NabbleNamespace.GroupList> interp) {
+		List<String> groups = Permissions.getPersonGroups(person());
+		Object block = interp.getArg(new NabbleNamespace.GroupList(groups),"do");
+		out.print(block);
+	}
+
+	public static final CommandSpec nodes_list = CommandSpec.DO()
+		.parameters("length")
+		.optionalParameters("start","sort","filter")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void nodes_list(IPrintWriter out,ScopedInterpreter<NodeList> interp)
+		throws ServletException
+	{
+		ServletNamespace servletNamespace = interp.getFromStack(ServletNamespace.class);
+		int start = NodeNamespace.getLoopStart(interp);
+		int length = NodeNamespace.getLoopLength(interp);
+		String filter = interp.getArgString("filter");
+		NodeIterator<? extends Node> nodeIter = person().getNodesByDateDesc(filter);
+		try {
+			Filter<Node> viewFilter = Permissions.canBeViewedByPersonFilter(servletNamespace.getVisitorUser());
+			nodeIter = new FilteredNodeIterator(nodeIter,viewFilter);
+			NodeList.userNodes( out, interp, nodeIter, start, length );
+		} finally {
+			nodeIter.close();
+		}
+	}
+
+	@Command public void post_filter(IPrintWriter out,Interpreter interp) {
+		out.print( "(is_app is null or not is_app)" );
+	}
+
+	@Command public void app_filter(IPrintWriter out,Interpreter interp) {
+		out.print( "is_app" );
+	}
+
+	@Command public void topic_filter(IPrintWriter out,Interpreter interp) {
+		out.print( "(is_app is null or not is_app) and exists(select 1 from node p where node.parent_id=p.node_id and is_app)" );
+	}
+
+	@Command public void reply_filter(IPrintWriter out,Interpreter interp) {
+		out.print( "(is_app is null or not is_app) and exists(select 1 from node p where node.parent_id=p.node_id and (is_app is null or not is_app))" );
+	}
+
+	public static final CommandSpec equals = new CommandSpec.Builder()
+		.dotParameter("user")
+		.build()
+	;
+
+	@Command public void equals(IPrintWriter out,Interpreter interp) {
+		UserNamespace userNs = interp.getArgAsNamespace(UserNamespace.class,"user");
+		out.print( userNs.person().equals(person()) );
+	}
+
+
+	@Namespace (
+		name = "user_list",
+		global = true
+	)
+	public static class UserList extends ListSequence<User> {
+
+		public UserList(List<User> nodeUsers) {
+			super(nodeUsers);
+		}
+
+		public static final CommandSpec current_user = CommandSpec.DO;
+
+		@Command public void current_user(IPrintWriter out,ScopedInterpreter<UserNamespace> interp) {
+			out.print( interp.getArg(new UserNamespace(get()),"do") );
+		}
+
+		@Command public void next_user(IPrintWriter out,Interpreter interp) {
+			next_element(out,interp);
+		}
+
+		public static final CommandSpec sort_by_node_count_desc = CommandSpec.NO_OUTPUT;
+
+		@Command public void sort_by_node_count_desc(IPrintWriter out,Interpreter interp) {
+			Collections.sort(elements,User.nodeCountComparatorDesc);
+		}
+
+		public static final CommandSpec sort_by_name = CommandSpec.NO_OUTPUT;
+
+		@Command public void sort_by_name(IPrintWriter out,Interpreter interp) {
+			Collections.sort(elements,User.BY_NAME_COMPARATOR);
+		}
+
+		public static final CommandSpec sub_list = CommandSpec.DO()
+			.parameters("length")
+			.optionalParameters("start")
+			.build()
+		;
+
+		@Command public void sub_list(IPrintWriter out,ScopedInterpreter<UserList> interp) {
+			int start = interp.getArgAsInt("start",0);
+			int length = interp.getArgAsInt("length");
+			UserList ns ;
+			if( start==0 && length >= elements.size() ) {
+				ns = this;
+			} else {
+				ns = new UserList(elements.subList(start,Math.min(elements.size(),start+length)));
+			}
+			out.print( interp.getArg(ns,"do") );
+		}
+	}
+
+	public static final CommandSpec signature_impl = CommandSpec.DO;
+
+	@Command public void signature_impl(IPrintWriter out,ScopedInterpreter<MessageNamespace> interp) {
+		out.print( interp.getArg( new MessageNamespace(person().getSignature()), "do" ) );
+	}
+
+	@Command public void has_signature(IPrintWriter out,Interpreter interp) {
+		out.print( person().getSignature() != null );
+	}
+
+	public static final CommandSpec node_count = new CommandSpec.Builder()
+		.optionalParameters("filter")
+		.build()
+	;
+
+	@Command public void node_count(IPrintWriter out,Interpreter interp) {
+		out.print( person().getNodeCount(interp.getArgString("filter")) );
+	}
+
+	@Command public void is_banned(IPrintWriter out, Interpreter interp) {
+		out.print( isUser() && Permissions.isBanned(user()) );
+	}
+
+	public static final CommandSpec ban = CommandSpec.NO_OUTPUT;
+
+	@Command public void ban(IPrintWriter out, Interpreter interp) {
+		Permissions.ban(user());
+		user().setNoArchive(true);
+		site().update(); // uncache
+	}
+
+	public static final CommandSpec unban = CommandSpec.NO_OUTPUT;
+
+	@Command public void unban(IPrintWriter out, Interpreter interp) {
+		Permissions.unban(user());
+		user().setNoArchive(false);
+		site().update(); // uncache
+	}
+
+	public static final CommandSpec delete_user_nodes = CommandSpec.NO_OUTPUT;
+
+	@Command public void delete_user_nodes(IPrintWriter out, Interpreter interp) {
+		person().deleteNodes();
+	}
+
+
+	@Command public void is_sysadmin(IPrintWriter out, Interpreter interp) {
+		out.print( isUser() && Permissions.isSysAdmin(user()) );
+	}
+
+	public static final CommandSpec reset_password_path = new CommandSpec.Builder()
+		.parameters("email","q")
+		.build()
+	;
+
+	@Command public void reset_password_path(IPrintWriter out,Interpreter interp) {
+		String email = interp.getArgString("email");
+		String q = interp.getArgString("q");
+		out.print( interp.encode( "/user/ResetPassword.jtp" ) );
+		if (email!=null && q!=null)
+			out.print( interp.encode("?email=" + HtmlUtils.urlEncode(email) + "&q=" + HtmlUtils.urlEncode(q)) );
+	}
+
+	@Command public void change_email_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/user/ChangeEmail.jtp?user=" + user().getId() ) );
+	}
+
+	@Command public void change_avatar_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/user/ChangeAvatar.jtp" ) );
+	}
+
+	@Command public void advanced_settings_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/user/Advanced.jtp" ) );
+	}
+
+	@Command public void remove_account_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/user/RemoveAccount.jtp" ) );
+	}
+
+	@Command public void pending_posts_path(IPrintWriter out,Interpreter interp) {
+		out.print( interp.encode( "/user/UserPendingNodes.jtp" ) );
+	}
+
+	private Map<Long,Long> lastVisitedNodeIds = null;
+
+	private Long lastVisitedNodeId(Interpreter interp,long nodeId) {
+		if( !isUser() )
+			return null;
+		if( lastVisitedNodeIds == null ) {
+			RequestNamespace.ParameterValueList params = interp.getFromStack(RequestNamespace.ParameterValueList.class);
+			List<Long> ids = new ArrayList<Long>();
+			for( String value : params.values() ) {
+				ids.add(Long.parseLong(value));
+			}
+			lastVisitedNodeIds = user().lastVisitedNodeIds(ids);
+		}
+		return lastVisitedNodeIds.get(nodeId);
+	}
+
+	public static final CommandSpec has_visited_node = CommandSpec.DO()
+		.parameters("node_id")
+		.requiredInStack(RequestNamespace.ParameterValueList.class)
+		.build()
+	;
+
+	@Command public void has_visited_node(IPrintWriter out, Interpreter interp) {
+		Long id = lastVisitedNodeId(interp, interp.getArgAsLong("node_id"));
+		Node node = id == null? null : site().getNode(id);
+		if (id != null && node == null)
+			lastVisitedNodeIds.remove(id);
+		out.print( node != null);
+	}
+
+	public static final CommandSpec last_visited_node = CommandSpec.DO()
+		.parameters("node_id")
+		.requiredInStack(RequestNamespace.ParameterValueList.class)
+		.build()
+	;
+
+	@Command public void last_visited_node(IPrintWriter out, ScopedInterpreter<NodeNamespace> interp) {
+		Node lastVisitedNode = null;
+		long nodeId = interp.getArgAsLong("node_id");
+		Long id = lastVisitedNodeId(interp,nodeId);
+		if (id != null)
+			lastVisitedNode = site().getNode(id);
+		out.print( interp.getArg( new NodeNamespace(lastVisitedNode), "do" ) );
+	}
+
+	public static final CommandSpec mark_visited = CommandSpec.NO_OUTPUT()
+		.parameters("last_node_id")
+		.dotParameter("node")
+		.build()
+	;
+
+	@Command public void mark_visited(IPrintWriter out, Interpreter interp) {
+		if (isUser()) {
+			NodeNamespace nodeNs = interp.getArgAsNamespace(NodeNamespace.class,"node");
+			long lastNodeId = interp.getArgAsLong("last_node_id");
+			user().markVisited(nodeNs.node(), lastNodeId);
+		}
+	}
+
+	public static final CommandSpec register = new CommandSpec.Builder()
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void register(IPrintWriter out, Interpreter interp)
+		throws ModelException, IOException
+	{
+		user().register();
+		refreshUser();
+		ServletNamespace servletNamespace = interp.getFromStack(ServletNamespace.class);
+		Jtp.doLogin(servletNamespace.request, servletNamespace.response, user(), false);
+		DailyNumber.registrations.inc();
+	}
+
+	public static final CommandSpec NAME = new CommandSpec.Builder()
+		.dotParameter("name")
+		.build()
+	;
+
+	public static final CommandSpec has_property = NAME;
+
+	@Command public void has_property(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print(user().getProperty(name) != null);
+	}
+
+	public static final CommandSpec get_property = NAME;
+
+	@Command public void get_property(IPrintWriter out,Interpreter interp) {
+		String name = interp.getArgString("name");
+		out.print(user().getProperty(name));
+	}
+
+	public static final CommandSpec delete_property = CommandSpec.NO_OUTPUT()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void delete_property(IPrintWriter out,Interpreter interp) {
+		User user = user();
+		String name = interp.getArgString("name");
+		user.setProperty(name, null);
+		user.getSite().update();
+	}
+
+	public static final CommandSpec set_property = CommandSpec.NO_OUTPUT()
+		.parameters("name", "value")
+		.build()
+	;
+
+	@Command public void set_property(IPrintWriter out,Interpreter interp) {
+		User user = user();
+		String name = interp.getArgString("name");
+		String value = interp.getArgString("value");
+		user.setProperty(name, value);
+		user.getSite().update();
+	}
+
+	public static final CommandSpec has_subscription_to_descentants_of = CommandSpec.NO_OUTPUT()
+		.dotParameter("node")
+		.build()
+	;
+
+	@Command public void has_subscription_to_descentants_of(IPrintWriter out,Interpreter interp) {
+		if (userR == null) {
+			out.print(false);
+			return;
+		}
+		NodeNamespace nodeNs = interp.getArgAsNamespace(NodeNamespace.class,"node");
+		for (Node n : nodeNs.node().getAncestors() ) {
+			if (userR.isSubscribed(n)) {
+				Subscription subscription = userR.getSubscription(n);
+				if (subscription.getTo() == Subscription.To.DESCENDANTS) {
+					out.print(true);
+					return;
+				}
+			}
+		}
+		out.print(false);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/UserPageNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,67 @@
+package nabble.view.web.template;
+
+import fschmidt.db.DbDatabase;
+import nabble.model.ModelException;
+import nabble.model.Person;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.view.web.user.UserEditorNamespace;
+
+import javax.servlet.ServletException;
+
+
+@Namespace (
+	name = "user_page",
+	global = true
+)
+public final class UserPageNamespace {
+	private UserNamespace userNs;
+
+	public UserPageNamespace(Person person) {
+		this(new UserNamespace(person));
+	}
+
+	public UserPageNamespace(UserNamespace userNs) {
+		this.userNs = userNs;
+	}
+
+	public UserNamespace userNamespace() {
+		return userNs;
+	}
+
+	public User user() {
+		return userNs.user();
+	}
+
+	public static final CommandSpec page_user = CommandSpec.DO;
+
+	@Command public void page_user(IPrintWriter out,ScopedInterpreter<UserNamespace> interp) {
+		out.print( interp.getArg(userNs,"do") );
+	}
+
+	public static final CommandSpec edit_page_user = CommandSpec.DO()
+		.optionalParameters("commit")
+		.build()
+	;
+
+	@Command public void edit_page_user(IPrintWriter out,ScopedInterpreter<UserEditorNamespace> interp) {
+		boolean commit = interp.getArgAsBoolean("commit",true);
+		DbDatabase db = user().getSite().getDb();
+		db.beginTransaction();
+		try {
+			userNs.refreshUser();
+			interp.getArgString(new UserEditorNamespace(user()),"do");
+			if( commit ) {
+				db.commitTransaction();
+				userNs.refreshUser();
+			}
+		} finally {
+			db.endTransaction();
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/Admin.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,49 @@
+
+package nabble.view.web.tools;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import nabble.view.lib.MyJtpServlet;
+import nabble.view.lib.Jtp;
+import nabble.model.Db;
+import nabble.model.User;
+import nabble.model.ModelHome;
+
+
+public final class Admin extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		if( request.getParameter("clearDbCache") != null ) {
+			Db.clearCache();
+			response.sendRedirect("Admin.jtp?done=database+cache+cleared");
+			return;
+		}
+		if( request.getParameter("clearPageCache") != null ) {
+			MyJtpServlet.getJtpContext().getHttpCache().clear();
+			response.sendRedirect("Admin.jtp?done=page+cache+cleared");
+			return;
+		}
+		
+		out.print( "\r\n<html>\r\n<body>\r\n\r\n" );
+
+		String done = request.getParameter("done");
+		if( done != null ) {
+			
+		out.print( "\r\n<p>" );
+		out.print( (done) );
+		out.print( "</p>\r\n" );
+
+		}
+		
+		out.print( "\r\n\r\n<form>\r\n<input type=\"submit\" name=\"clearDbCache\" value=\"clear database cache\">\r\n</form>\r\n\r\n<form>\r\n<input type=\"submit\" name=\"clearPageCache\" value=\"clear page cache\">\r\n</form>\r\n\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/Admin.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,59 @@
+<%
+package nabble.view.web.tools;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import java.io.PrintWriter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import nabble.view.lib.MyJtpServlet;
+import nabble.view.lib.Jtp;
+import nabble.model.Db;
+import nabble.model.User;
+import nabble.model.ModelHome;
+
+
+public final class Admin extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		if( request.getParameter("clearDbCache") != null ) {
+			Db.clearCache();
+			response.sendRedirect("Admin.jtp?done=database+cache+cleared");
+			return;
+		}
+		if( request.getParameter("clearPageCache") != null ) {
+			MyJtpServlet.getJtpContext().getHttpCache().clear();
+			response.sendRedirect("Admin.jtp?done=page+cache+cleared");
+			return;
+		}
+		%>
+		<html>
+		<body>
+
+		<%
+		String done = request.getParameter("done");
+		if( done != null ) {
+			%>
+			<p><%=done%></p>
+			<%
+		}
+		%>
+
+		<form>
+		<input type="submit" name="clearDbCache" value="clear database cache">
+		</form>
+
+		<form>
+		<input type="submit" name="clearPageCache" value="clear page cache">
+		</form>
+
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/AdminNotice.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,65 @@
+
+package nabble.view.web.tools;
+
+import nabble.model.SystemProperties;
+import nabble.view.lib.Jtp;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class AdminNotice extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+
+		boolean isSave = request.getParameter("save") != null;
+		boolean isPreview = request.getParameter("preview") != null;
+		String errorMsg = null;
+		String text = request.getParameter("text");
+		if (isSave) {
+			if (text == null || text.trim().length() == 0) {
+				SystemProperties.remove("administrator.notice");
+			} else {
+				SystemProperties.set("administrator.notice", text.trim());
+				String version = SystemProperties.get("administrator.notice.version");
+				int versionNumber = version == null? 0 : Integer.valueOf(version);
+				versionNumber++;
+				SystemProperties.set("administrator.notice.version", String.valueOf(versionNumber));
+			}
+			response.sendRedirect("/tools/");
+			return;
+		} else {
+			text = text == null? SystemProperties.get("administrator.notice") : text;
+		}
+		
+		out.print( "\n<html>\n	<head>\n		<title>Administrator Notice</title>\n	</head>\n	<body style=\"font: .8em Verdana, Serif;\">\n		<div>\n			<a href=\"/tools/\">Tools</a>\n		</div>\n\n		<h1>Administrator Notice</h1>\n\n		" );
+ if (errorMsg != null) { 
+		out.print( "\n			<div style=\"color:red;padding:.5em;margin-bottom:1em\">" );
+		out.print( (errorMsg) );
+		out.print( "</div>\n		" );
+ } 
+		out.print( "\n\n		" );
+ if (isPreview) { 
+		out.print( "\n			<div style=\"background:#ffffcc;padding:.5em;margin:.5em 0 1em\">" );
+		out.print( (text) );
+		out.print( "</div>\n		" );
+ } 
+		out.print( "\n\n		<form action=\"AdminNotice.jtp\">\n			<textarea name=\"text\" style=\"width:100%;height:10em\">" );
+		out.print( (Jtp.hideNull(text)) );
+		out.print( "</textarea>\n			<input type=\"submit\" name=\"preview\" value=\"Preview >>\">\n			" );
+ if (isPreview) { 
+		out.print( "\n			<input type=\"submit\" name=\"save\" value=\"Save Notice\">\n			" );
+ } 
+		out.print( "\n		</form>\n\n	</body>\n</html>\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/AdminNotice.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,75 @@
+<%
+package nabble.view.web.tools;
+
+import nabble.model.SystemProperties;
+import nabble.view.lib.Jtp;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class AdminNotice extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+
+		boolean isSave = request.getParameter("save") != null;
+		boolean isPreview = request.getParameter("preview") != null;
+		String errorMsg = null;
+		String text = request.getParameter("text");
+		if (isSave) {
+			if (text == null || text.trim().length() == 0) {
+				SystemProperties.remove("administrator.notice");
+			} else {
+				SystemProperties.set("administrator.notice", text.trim());
+				String version = SystemProperties.get("administrator.notice.version");
+				int versionNumber = version == null? 0 : Integer.valueOf(version);
+				versionNumber++;
+				SystemProperties.set("administrator.notice.version", String.valueOf(versionNumber));
+			}
+			response.sendRedirect("/tools/");
+			return;
+		} else {
+			text = text == null? SystemProperties.get("administrator.notice") : text;
+		}
+		%>
+		<html>
+			<head>
+				<title>Administrator Notice</title>
+			</head>
+			<body style="font: .8em Verdana, Serif;">
+				<div>
+					<a href="/tools/">Tools</a>
+				</div>
+
+				<h1>Administrator Notice</h1>
+
+				<% if (errorMsg != null) { %>
+					<div style="color:red;padding:.5em;margin-bottom:1em"><%=errorMsg%></div>
+				<% } %>
+
+				<% if (isPreview) { %>
+					<div style="background:#ffffcc;padding:.5em;margin:.5em 0 1em"><%=text%></div>
+				<% } %>
+
+				<form action="AdminNotice.jtp">
+					<textarea name="text" style="width:100%;height:10em"><%=Jtp.hideNull(text)%></textarea>
+					<input type="submit" name="preview" value="Preview >>">
+					<% if (isPreview) { %>
+					<input type="submit" name="save" value="Save Notice">
+					<% } %>
+				</form>
+
+			</body>
+		</html>
+		<%
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/Index.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,154 @@
+
+package nabble.view.web.tools;
+
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.servlet.ConnectionLimitFilter;
+import nabble.model.Executors;
+import nabble.model.ModelHome;
+import nabble.model.ViewCount;
+import nabble.model.export.Export;
+import nabble.view.lib.UrlMappable;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.user.OnlineStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+
+public final class Index extends HttpServlet implements UrlMappable {
+
+	private static final Logger logger = LoggerFactory.getLogger(Index.class);
+
+	private static final DecimalFormat FORMATTER = new DecimalFormat("0.00");
+	private static final Pattern URL_PATTERN = Pattern.compile("://[^/]+/tools/$");
+
+	private static String path() {
+		return "/tools/";
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		return Collections.emptyMap();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		float oneMega = 1024f * 1024f;
+		float free = Runtime.getRuntime().freeMemory() / oneMega;
+        float total = Runtime.getRuntime().totalMemory() / oneMega;
+        float used = total - free;
+
+		String loadAverage = null;
+		try {
+			Process process = Runtime.getRuntime().exec("uptime");
+			InputStream input = process.getInputStream();
+			byte[] result = IoUtils.readAll(input);
+			input.close();
+			loadAverage = new String(result).replaceAll(".*average:","");
+		} catch(IOException e) {} // not on linux
+
+		Map<java.lang.Thread, java.lang.StackTraceElement[]> m = Thread.getAllStackTraces();
+		int socketRead = 0;
+		int idleThread = 0;
+		int sleepingThread = 0;
+		for (Map.Entry<Thread, StackTraceElement[]> entry : m.entrySet()) {
+			StackTraceElement[] trace = entry.getValue();
+			if (trace.length > 0) {
+				String firstTrace = trace[0].toString();
+				if (firstTrace.equals("java.net.SocketInputStream.socketRead0(Native Method)"))
+					socketRead++;
+				else if (firstTrace.equals("java.lang.Thread.sleep(Native Method)"))
+					sleepingThread++;
+				else if (firstTrace.equals("java.lang.Object.wait(Native Method)"))
+					idleThread++;
+			}
+		}
+
+		long viewCountDiffMillis = System.currentTimeMillis() - ViewCount.lastSaved;
+		float minutesSinceLastSaved = viewCountDiffMillis / 60000f;
+
+		long digestDiffMillis = System.currentTimeMillis() - ModelHome.lastDigestRun;
+		float hoursSinceLastDigestRun = digestDiffMillis / 3600000f;
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<title>Nabble tools</title>\r\n		<style type=\"text/css\">\r\n			.gray {\r\n				background-color: #eeeeee;\r\n				padding: .5em;\r\n			}\r\n			p { padding-left: .5em; }\r\n			td.row-label {\r\n				font-weight:bold;\r\n				padding: .1em .4em .1em 0;\r\n			}\r\n			td.row-separator { padding-top: 1em; }\r\n\r\n			td.category {\r\n				font-variant:small-caps;\r\n				text-align:center;\r\n				padding: 0 .5em;\r\n			}\r\n		</style>\r\n		<script type=\"text/javascript\">\r\n			var months = [\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"];\r\n			function fmt(i) { return i <= 9? \"0\" + i : i; };\r\n			function formatTime(date) {\r\n				var hours = date.getHours();\r\n				if( hours < 12 ) {\r\n					var xm = \"am\";\r\n					if (hours==0)\r\n						hours = 12;\r\n				} else {\r\n					var xm = \"pm\";\r\n					if (hours > 12)\r\n						hours -= 12;\r\n				}\r\n				return fmt(hours) + \":\" + fmt(date.getMinutes()) + xm;\r\n			};\r\n			function formatDate(date) {\r\n				return months[date.getMonth()] + \" \" + fmt(date.getDate()) + \", \" + date.getFullYear();\r\n			};\r\n			function formatDateTime(date) {\r\n				return formatDate(date) + \"; \" + formatTime(date);\r\n			};\r\n		</script>\r\n	</head>\r\n	<body style=\"font: .8em Verdana, Serif;\">\r\n		<p class=\"gray\">\r\n			Built time =\r\n			<b>\r\n			<script type=\"text/javascript\">\r\n				document.write(formatDateTime(new Date(" );
+		out.print( (new Date(ClassLoader.getSystemResource("nabble/view/web/Index.class").openConnection().getLastModified()).getTime()) );
+		out.print( ")));\r\n			</script>\r\n			</b>\r\n		</p>\r\n\r\n		<table style=\"font-size: 1em;\">\r\n			<tr>\r\n				<td class=\"category\" style=\"background-color:#fafafa;\">General</td>\r\n				<td style=\"padding:.5em 0\">\r\n					<p><a href=\"Admin.jtp\">Caches</a></p>\r\n					<p><a href=\"shell.luan\">Shell</a> (<a href=\"ShellHelp.jtp\">\r\n						<small>help</small>\r\n					</a>)\r\n					</p>\r\n					<p><a href=\"run.luan\">Run Batch</a></p>\r\n					<p><a href=\"SendMail.jtp\">Send mail</a></p>\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td class=\"category\" style=\"background-color:#e0e0e0;\">Sites</td>\r\n				<td style=\"padding:.5em 0\">\r\n					<p><a href=\"AdminNotice.jtp\">Administrator Notice</a></p>\r\n				</td>\r\n			</tr>\r\n			<tr>\r\n				<td class=\"category\" style=\"background-color:#d9d9d9;\">Others</td>\r\n				<td style=\"padding:.5em 0\">\r\n					<p><a href=\"TestMacro.jtp\">Test Macro</a></p>\r\n					<p><a href=\"Profile.jtp\">Profiling</a></p>\r\n					<p><a href=\"/tools2\">Generic tools</a></p>\r\n					<p><a href=\"OnlineUsers.jtp\">Online Users</a></p>\r\n				</td>\r\n			</tr>\r\n		</table>\r\n\r\n		<div class=\"gray\" style=\"margin: 1em 0\">\r\n			<table style=\"font-size:100%\">\r\n				<tr>\r\n					<td class=\"row-label\">Free Memory</td>\r\n					<td>\r\n						" );
+		out.print( (String.format("%.2f",free)) );
+		out.print( " Mb\r\n					</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Used Memory</td>\r\n					<td>" );
+		out.print( (String.format("%.2f",used)) );
+		out.print( " Mb</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Total Memory</td>\r\n					<td>" );
+		out.print( (String.format("%.2f",total)) );
+		out.print( " Mb</td>\r\n				</tr>\r\n				" );
+ if (loadAverage != null) { 
+		out.print( "\r\n				<tr>\r\n					<td class=\"row-label\">Load Average</td>\r\n					<td>" );
+		out.print( (loadAverage) );
+		out.print( "</td>\r\n				</tr>\r\n				" );
+ } 
+		out.print( "\r\n				<tr>\r\n					<td class=\"row-label row-separator\">Pool Active Count</td>\r\n					<td class=\"row-separator\">" );
+		out.print( (Executors.foregroundExecutor.getActiveCount()) );
+		out.print( " threads</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Pool Size</td>\r\n					<td>" );
+		out.print( (Executors.foregroundExecutor.getPoolSize()) );
+		out.print( " threads</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Connection Limit</td>\r\n					<td>" );
+		out.print( (ConnectionLimitFilter.counter.get()) );
+		out.print( "/" );
+		out.print( (ConnectionLimitFilter.max) );
+		out.print( " (" );
+		out.print( (ConnectionLimitFilter.queue.size()) );
+		out.print( ")</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Queue</td>\r\n					<td>" );
+		out.print( (Executors.foregroundExecutor.getQueueSize()) );
+		out.print( " connections</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label row-separator\">Thread Count</td>\r\n					<td class=\"row-separator\">" );
+		out.print( (m.size()) );
+		out.print( " threads</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Idle Threads</td>\r\n					<td>" );
+		out.print( (idleThread) );
+		out.print( " threads are idle</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Database Threads</td>\r\n					<td>" );
+		out.print( (socketRead) );
+		out.print( " socket reading</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Sleeping Threads</td>\r\n					<td>" );
+		out.print( (sleepingThread) );
+		out.print( " threads are sleeping</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label row-separator\">Exporting</td>\r\n					<td class=\"row-separator\">[" );
+		out.print( (csv(Export.exportSiteIds)) );
+		out.print( "]</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Backups Running</td>\r\n					<td>[" );
+		out.print( (csv(NabbleNamespace.sitesRunningBackup)) );
+		out.print( "]</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Online users</td>\r\n					<td>" );
+		out.print( (OnlineStatus.getOnlineStats()) );
+		out.print( "</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Views Last Saved</td>\r\n					<td style=\"color:#" );
+		out.print( (minutesSinceLastSaved > 2f? "red":"black") );
+		out.print( "\">" );
+		out.print( (FORMATTER.format(minutesSinceLastSaved)) );
+		out.print( " minutes ago</td>\r\n				</tr>\r\n				<tr>\r\n					<td class=\"row-label\">Last Digest Run</td>\r\n					<td style=\"color:#" );
+		out.print( (hoursSinceLastDigestRun > 24f? "red":"black") );
+		out.print( "\">" );
+		out.print( (FORMATTER.format(hoursSinceLastDigestRun)) );
+		out.print( " hours ago</td>\r\n				</tr>\r\n			</table>\r\n		</div>\r\n	</body>\r\n</html>\r\n" );
+
+	}
+
+	private String csv(Set<Long> ids) {
+		Long[] idArray = ids.toArray(new Long[0]);
+		StringBuilder buf = new StringBuilder();
+		for (long id : idArray) {
+			if (buf.length() > 0)
+				buf.append(", ");
+			buf.append(id);
+		}
+		return buf.toString();
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/Index.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,268 @@
+<%
+package nabble.view.web.tools;
+
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.servlet.ConnectionLimitFilter;
+import nabble.model.Executors;
+import nabble.model.ModelHome;
+import nabble.model.ViewCount;
+import nabble.model.export.Export;
+import nabble.view.lib.UrlMappable;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.user.OnlineStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+
+public final class Index extends HttpServlet implements UrlMappable {
+
+	private static final Logger logger = LoggerFactory.getLogger(Index.class);
+
+	private static final DecimalFormat FORMATTER = new DecimalFormat("0.00");
+	private static final Pattern URL_PATTERN = Pattern.compile("://[^/]+/tools/$");
+
+	private static String path() {
+		return "/tools/";
+	}
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		return Collections.emptyMap();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		float oneMega = 1024f * 1024f;
+		float free = Runtime.getRuntime().freeMemory() / oneMega;
+        float total = Runtime.getRuntime().totalMemory() / oneMega;
+        float used = total - free;
+
+		String loadAverage = null;
+		try {
+			Process process = Runtime.getRuntime().exec("uptime");
+			InputStream input = process.getInputStream();
+			byte[] result = IoUtils.readAll(input);
+			input.close();
+			loadAverage = new String(result).replaceAll(".*average:","");
+		} catch(IOException e) {} // not on linux
+
+		Map<java.lang.Thread, java.lang.StackTraceElement[]> m = Thread.getAllStackTraces();
+		int socketRead = 0;
+		int idleThread = 0;
+		int sleepingThread = 0;
+		for (Map.Entry<Thread, StackTraceElement[]> entry : m.entrySet()) {
+			StackTraceElement[] trace = entry.getValue();
+			if (trace.length > 0) {
+				String firstTrace = trace[0].toString();
+				if (firstTrace.equals("java.net.SocketInputStream.socketRead0(Native Method)"))
+					socketRead++;
+				else if (firstTrace.equals("java.lang.Thread.sleep(Native Method)"))
+					sleepingThread++;
+				else if (firstTrace.equals("java.lang.Object.wait(Native Method)"))
+					idleThread++;
+			}
+		}
+
+		long viewCountDiffMillis = System.currentTimeMillis() - ViewCount.lastSaved;
+		float minutesSinceLastSaved = viewCountDiffMillis / 60000f;
+
+		long digestDiffMillis = System.currentTimeMillis() - ModelHome.lastDigestRun;
+		float hoursSinceLastDigestRun = digestDiffMillis / 3600000f;
+		%>
+		<html>
+			<head>
+				<title>Nabble tools</title>
+				<style type="text/css">
+					.gray {
+						background-color: #eeeeee;
+						padding: .5em;
+					}
+					p { padding-left: .5em; }
+					td.row-label {
+						font-weight:bold;
+						padding: .1em .4em .1em 0;
+					}
+					td.row-separator { padding-top: 1em; }
+
+					td.category {
+						font-variant:small-caps;
+						text-align:center;
+						padding: 0 .5em;
+					}
+				</style>
+				<script type="text/javascript">
+					var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
+					function fmt(i) { return i <= 9? "0" + i : i; };
+					function formatTime(date) {
+						var hours = date.getHours();
+						if( hours < 12 ) {
+							var xm = "am";
+							if (hours==0)
+								hours = 12;
+						} else {
+							var xm = "pm";
+							if (hours > 12)
+								hours -= 12;
+						}
+						return fmt(hours) + ":" + fmt(date.getMinutes()) + xm;
+					};
+					function formatDate(date) {
+						return months[date.getMonth()] + " " + fmt(date.getDate()) + ", " + date.getFullYear();
+					};
+					function formatDateTime(date) {
+						return formatDate(date) + "; " + formatTime(date);
+					};
+				</script>
+			</head>
+			<body style="font: .8em Verdana, Serif;">
+				<p class="gray">
+					Built time =
+					<b>
+					<script type="text/javascript">
+						document.write(formatDateTime(new Date(<%=new Date(ClassLoader.getSystemResource("nabble/view/web/Index.class").openConnection().getLastModified()).getTime()%>)));
+					</script>
+					</b>
+				</p>
+
+				<table style="font-size: 1em;">
+					<tr>
+						<td class="category" style="background-color:#fafafa;">General</td>
+						<td style="padding:.5em 0">
+							<p><a href="Admin.jtp">Caches</a></p>
+							<p><a href="shell.luan">Shell</a> (<a href="ShellHelp.jtp">
+								<small>help</small>
+							</a>)
+							</p>
+							<p><a href="run.luan">Run Batch</a></p>
+							<p><a href="SendMail.jtp">Send mail</a></p>
+						</td>
+					</tr>
+					<tr>
+						<td class="category" style="background-color:#e0e0e0;">Sites</td>
+						<td style="padding:.5em 0">
+							<p><a href="AdminNotice.jtp">Administrator Notice</a></p>
+						</td>
+					</tr>
+					<tr>
+						<td class="category" style="background-color:#d9d9d9;">Others</td>
+						<td style="padding:.5em 0">
+							<p><a href="TestMacro.jtp">Test Macro</a></p>
+							<p><a href="Profile.jtp">Profiling</a></p>
+							<p><a href="/tools2">Generic tools</a></p>
+							<p><a href="OnlineUsers.jtp">Online Users</a></p>
+						</td>
+					</tr>
+				</table>
+
+				<div class="gray" style="margin: 1em 0">
+					<table style="font-size:100%">
+						<tr>
+							<td class="row-label">Free Memory</td>
+							<td>
+								<%=String.format("%.2f",free)%> Mb
+							</td>
+						</tr>
+						<tr>
+							<td class="row-label">Used Memory</td>
+							<td><%=String.format("%.2f",used)%> Mb</td>
+						</tr>
+						<tr>
+							<td class="row-label">Total Memory</td>
+							<td><%=String.format("%.2f",total)%> Mb</td>
+						</tr>
+						<% if (loadAverage != null) { %>
+						<tr>
+							<td class="row-label">Load Average</td>
+							<td><%=loadAverage%></td>
+						</tr>
+						<% } %>
+						<tr>
+							<td class="row-label row-separator">Pool Active Count</td>
+							<td class="row-separator"><%=Executors.foregroundExecutor.getActiveCount()%> threads</td>
+						</tr>
+						<tr>
+							<td class="row-label">Pool Size</td>
+							<td><%=Executors.foregroundExecutor.getPoolSize()%> threads</td>
+						</tr>
+						<tr>
+							<td class="row-label">Connection Limit</td>
+							<td><%=ConnectionLimitFilter.counter.get()%>/<%=ConnectionLimitFilter.max%> (<%=ConnectionLimitFilter.queue.size()%>)</td>
+						</tr>
+						<tr>
+							<td class="row-label">Queue</td>
+							<td><%=Executors.foregroundExecutor.getQueueSize()%> connections</td>
+						</tr>
+						<tr>
+							<td class="row-label row-separator">Thread Count</td>
+							<td class="row-separator"><%=m.size()%> threads</td>
+						</tr>
+						<tr>
+							<td class="row-label">Idle Threads</td>
+							<td><%=idleThread%> threads are idle</td>
+						</tr>
+						<tr>
+							<td class="row-label">Database Threads</td>
+							<td><%=socketRead%> socket reading</td>
+						</tr>
+						<tr>
+							<td class="row-label">Sleeping Threads</td>
+							<td><%=sleepingThread%> threads are sleeping</td>
+						</tr>
+						<tr>
+							<td class="row-label row-separator">Exporting</td>
+							<td class="row-separator">[<%=csv(Export.exportSiteIds)%>]</td>
+						</tr>
+						<tr>
+							<td class="row-label">Backups Running</td>
+							<td>[<%=csv(NabbleNamespace.sitesRunningBackup)%>]</td>
+						</tr>
+						<tr>
+							<td class="row-label">Online users</td>
+							<td><%=OnlineStatus.getOnlineStats()%></td>
+						</tr>
+						<tr>
+							<td class="row-label">Views Last Saved</td>
+							<td style="color:#<%=minutesSinceLastSaved > 2f? "red":"black"%>"><%=FORMATTER.format(minutesSinceLastSaved)%> minutes ago</td>
+						</tr>
+						<tr>
+							<td class="row-label">Last Digest Run</td>
+							<td style="color:#<%=hoursSinceLastDigestRun > 24f? "red":"black"%>"><%=FORMATTER.format(hoursSinceLastDigestRun)%> hours ago</td>
+						</tr>
+					</table>
+				</div>
+			</body>
+		</html>
+		<%
+	}
+
+	private String csv(Set<Long> ids) {
+		Long[] idArray = ids.toArray(new Long[0]);
+		StringBuilder buf = new StringBuilder();
+		for (long id : idArray) {
+			if (buf.length() > 0)
+				buf.append(", ");
+			buf.append(id);
+		}
+		return buf.toString();
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/NamlEditor.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,48 @@
+package nabble.view.web.tools;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * Similar to the NamlEditor in the template package, but this one
+ * doesn't required login. This is used only by Nabble admins.
+ */
+public class NamlEditor extends HttpServlet {
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		nabble.view.web.template.NamlEditor.buildPage(request, response);
+	}
+
+	public static class Save extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			nabble.view.web.template.NamlEditor.save(request, response);
+		}
+	}
+
+	public static class UploadForm extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			nabble.view.web.template.NamlEditor.uploadForm(request, response);
+		}
+	}
+
+	public static class Upload extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+		{
+			nabble.view.web.template.NamlEditor.upload(request, response);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/OnlineUsers.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,61 @@
+
+package nabble.view.web.tools;
+
+import nabble.view.web.user.OnlineStatus;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+
+
+public final class OnlineUsers extends HttpServlet {
+
+	private static class Entry {
+		String url;
+		int count;
+		Entry(String url, int count) {
+			this.url = url;
+			this.count = count;
+		}
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Map<String, Integer> sites = OnlineStatus.getOnlineSites();
+		ArrayList<Entry> list = new ArrayList<Entry>(sites.size());
+		for (Map.Entry<String, Integer> entry : sites.entrySet()) {
+			list.add(new Entry(entry.getKey(), entry.getValue()));
+		}
+
+		Collections.sort(list, new Comparator<Entry>() {
+			public int compare(Entry o1, Entry o2) {
+				return o1.count == o2.count? 0 : o1.count > o2.count? -1 : 1;
+			}
+		});
+
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\n<html>\n	<head>\n		<title>Online Users</title>\n		<style type=\"text/css\">\n			body {\n				font-family:Verdana, Arial, Serif;\n				font-size:.74em;\n			}\n			table td{padding:.4em .8em;border-bottom:1px solid #eee}\n		</style>\n	</head>\n	<body>\n		<div>\n			<a href=\"/tools/\">Tools</a>\n		</div>\n		<h1>Online Users</h1>\n\n		<table>\n		" );
+ for (Entry entry : list) { 
+		out.print( "\n			<tr><td><a href=\"" );
+		out.print( (entry.url) );
+		out.print( "\">" );
+		out.print( (entry.url) );
+		out.print( "</a></td><td>" );
+		out.print( (entry.count) );
+		out.print( "</td></tr>\n		" );
+ } 
+		out.print( "\n		</table>\n	</body>\n</html>\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/OnlineUsers.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,74 @@
+<%
+package nabble.view.web.tools;
+
+import nabble.view.web.user.OnlineStatus;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Map;
+
+
+public final class OnlineUsers extends HttpServlet {
+
+	private static class Entry {
+		String url;
+		int count;
+		Entry(String url, int count) {
+			this.url = url;
+			this.count = count;
+		}
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Map<String, Integer> sites = OnlineStatus.getOnlineSites();
+		ArrayList<Entry> list = new ArrayList<Entry>(sites.size());
+		for (Map.Entry<String, Integer> entry : sites.entrySet()) {
+			list.add(new Entry(entry.getKey(), entry.getValue()));
+		}
+
+		Collections.sort(list, new Comparator<Entry>() {
+			public int compare(Entry o1, Entry o2) {
+				return o1.count == o2.count? 0 : o1.count > o2.count? -1 : 1;
+			}
+		});
+
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<title>Online Users</title>
+				<style type="text/css">
+					body {
+						font-family:Verdana, Arial, Serif;
+						font-size:.74em;
+					}
+					table td{padding:.4em .8em;border-bottom:1px solid #eee}
+				</style>
+			</head>
+			<body>
+				<div>
+					<a href="/tools/">Tools</a>
+				</div>
+				<h1>Online Users</h1>
+
+				<table>
+				<% for (Entry entry : list) { %>
+					<tr><td><a href="<%=entry.url%>"><%=entry.url%></a></td><td><%=entry.count%></td></tr>
+				<% } %>
+				</table>
+			</body>
+		</html>
+		<%
+	}
+
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/Profile.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,98 @@
+
+package nabble.view.web.tools;
+
+import com.yourkit.api.Controller;
+import com.yourkit.api.ProfilingModes;
+import fschmidt.util.java.HtmlUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class Profile extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(Profile.class);
+
+	private static Controller profiler;
+	static {
+		try {
+			profiler = new com.yourkit.api.Controller();
+		} catch (Exception e) {
+			logger.info("Profiler error: " + e);
+		}
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+
+		float oneMega = 1024f * 1024f;
+		float free = Runtime.getRuntime().freeMemory() / oneMega;
+		float total = Runtime.getRuntime().totalMemory() / oneMega;
+		float used = total - free;
+
+		String action = request.getParameter("action");
+		if ("gc".equals(action)) {
+			System.gc();
+			response.sendRedirect("/tools/Profile.jtp");
+			return;
+		} else if ("memory".equals(action)) {
+			String file;
+			try {
+				file = profiler.captureSnapshot(ProfilingModes.SNAPSHOT_WITH_HEAP);
+			} catch(Exception e) {
+				throw new RuntimeException(e);
+			}
+			response.sendRedirect("/tools/Profile.jtp?file="+HtmlUtils.urlEncode(file));
+			return;
+		} else if ("cpu".equals(action)) {
+			long seconds = Long.valueOf( request.getParameter("seconds") );
+			String file;
+			try {
+				profiler.startCPUProfiling(ProfilingModes.CPU_SAMPLING, null);
+				Thread.sleep(seconds*1000L);
+				profiler.stopCPUProfiling();
+				file = profiler.captureSnapshot(ProfilingModes.SNAPSHOT_WITHOUT_HEAP);
+			} catch(Exception e) {
+				throw new RuntimeException(e);
+			}
+			response.sendRedirect("/tools/Profile.jtp?file="+HtmlUtils.urlEncode(file));
+			return;
+		}
+		
+		out.print( "\n<html>\n	<head>\n		<title>Profiler</title>\n		<style type=\"text/css\">\n			body {\n				font-family:Verdana, Arial, Serif;\n				font-size:.74em;\n			}\n			.gray {\n				background-color: #eeeeee;\n				padding: .5em;\n				margin: .5em;\n				font-size:100%;\n			}\n		</style>\n	</head>\n	<body>\n		<div>\n			<a href=\"/tools/\">Tools</a>\n		</div>\n		" );
+
+				String file = request.getParameter("file");
+				if( file != null ) {
+					
+		out.print( "\n<div style=\"margin:1em .5em .5em;padding:.3em\">\n	<b>Snapshot File</b>: " );
+		out.print( (file) );
+		out.print( "\n</div>\n" );
+
+				}
+				
+		out.print( "\n<table class=\"gray\">\n	<tr>\n		<td class=\"row-label\">Free Memory</td>\n		<td>\n			" );
+		out.print( (String.format("%.2f",free)) );
+		out.print( " Mb\n		</td>\n	</tr>\n	<tr>\n		<td class=\"row-label\">Used Memory</td>\n		<td>" );
+		out.print( (String.format("%.2f",used)) );
+		out.print( " Mb</td>\n	</tr>\n	<tr>\n		<td class=\"row-label\">Total Memory</td>\n		<td>" );
+		out.print( (String.format("%.2f",total)) );
+		out.print( " Mb</td>\n	</tr>\n</table>\n<input type=\"button\" onclick=\"location='/tools/Profile.jtp?action=gc'\" value=\"Run GC\"/>\n" );
+ if (profiler == null) { 
+		out.print( "\n	<div style=\"margin:1em 0\">\n		Yourkit profiler is disabled on this server.\n	</div>\n" );
+ } else { 
+		out.print( "\n	<div style=\"margin:1em 0\">\n		<input type=\"button\" onclick=\"location='/tools/Profile.jtp?action=memory'\" value=\"Capture Memory Snapshot\"/>\n	</div>\n	<div style=\"margin:1em 0\">\n		<form>\n			<input type=\"hidden\" name=\"action\" value=\"cpu\">\n			<input type=\"text\" name=\"seconds\" value=\"5\" size=\"2\"> seconds\n			<input type=\"submit\" value=\"CPU Profiling\">\n		</form>\n	</div>\n" );
+ } 
+		out.print( "\n</body>\n</html>\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/Profile.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,139 @@
+<%
+package nabble.view.web.tools;
+
+import com.yourkit.api.Controller;
+import com.yourkit.api.ProfilingModes;
+import fschmidt.util.java.HtmlUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class Profile extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(Profile.class);
+
+	private static Controller profiler;
+	static {
+		try {
+			profiler = new com.yourkit.api.Controller();
+		} catch (Exception e) {
+			logger.info("Profiler error: " + e);
+		}
+	}
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+
+		float oneMega = 1024f * 1024f;
+		float free = Runtime.getRuntime().freeMemory() / oneMega;
+		float total = Runtime.getRuntime().totalMemory() / oneMega;
+		float used = total - free;
+
+		String action = request.getParameter("action");
+		if ("gc".equals(action)) {
+			System.gc();
+			response.sendRedirect("/tools/Profile.jtp");
+			return;
+		} else if ("memory".equals(action)) {
+			String file;
+			try {
+				file = profiler.captureSnapshot(ProfilingModes.SNAPSHOT_WITH_HEAP);
+			} catch(Exception e) {
+				throw new RuntimeException(e);
+			}
+			response.sendRedirect("/tools/Profile.jtp?file="+HtmlUtils.urlEncode(file));
+			return;
+		} else if ("cpu".equals(action)) {
+			long seconds = Long.valueOf( request.getParameter("seconds") );
+			String file;
+			try {
+				profiler.startCPUProfiling(ProfilingModes.CPU_SAMPLING, null);
+				Thread.sleep(seconds*1000L);
+				profiler.stopCPUProfiling();
+				file = profiler.captureSnapshot(ProfilingModes.SNAPSHOT_WITHOUT_HEAP);
+			} catch(Exception e) {
+				throw new RuntimeException(e);
+			}
+			response.sendRedirect("/tools/Profile.jtp?file="+HtmlUtils.urlEncode(file));
+			return;
+		}
+		%>
+		<html>
+			<head>
+				<title>Profiler</title>
+				<style type="text/css">
+					body {
+						font-family:Verdana, Arial, Serif;
+						font-size:.74em;
+					}
+					.gray {
+						background-color: #eeeeee;
+						padding: .5em;
+						margin: .5em;
+						font-size:100%;
+					}
+				</style>
+			</head>
+			<body>
+				<div>
+					<a href="/tools/">Tools</a>
+				</div>
+				<%
+				String file = request.getParameter("file");
+				if( file != null ) {
+					%>
+					<div style="margin:1em .5em .5em;padding:.3em">
+						<b>Snapshot File</b>: <%=file%>
+					</div>
+					<%
+				}
+				%>
+				<table class="gray">
+					<tr>
+						<td class="row-label">Free Memory</td>
+						<td>
+							<%=String.format("%.2f",free)%> Mb
+						</td>
+					</tr>
+					<tr>
+						<td class="row-label">Used Memory</td>
+						<td><%=String.format("%.2f",used)%> Mb</td>
+					</tr>
+					<tr>
+						<td class="row-label">Total Memory</td>
+						<td><%=String.format("%.2f",total)%> Mb</td>
+					</tr>
+				</table>
+				<input type="button" onclick="location='/tools/Profile.jtp?action=gc'" value="Run GC"/>
+				<% if (profiler == null) { %>
+					<div style="margin:1em 0">
+						Yourkit profiler is disabled on this server.
+					</div>
+				<% } else { %>
+					<div style="margin:1em 0">
+						<input type="button" onclick="location='/tools/Profile.jtp?action=memory'" value="Capture Memory Snapshot"/>
+					</div>
+					<div style="margin:1em 0">
+						<form>
+							<input type="hidden" name="action" value="cpu">
+							<input type="text" name="seconds" value="5" size="2"> seconds
+							<input type="submit" value="CPU Profiling">
+						</form>
+					</div>
+				<% } %>
+			</body>
+		</html>
+		<%
+	}
+
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/SendMail.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,70 @@
+
+package nabble.view.web.tools;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class SendMail extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		boolean sent = false;
+		if( "Post".equals( request.getParameter("Action") ) ) {
+			String mailText = request.getParameter("mail");
+			String from = request.getParameter("from");
+			String smtpFrom = request.getParameter("smtp_from");
+			String to = request.getParameter("to");
+			String subject = request.getParameter("subject");
+			Mail mail = MailHome.newMail();
+			try {
+				mail.setFrom( MailHome.parseAddress(from) );
+			} catch(MailException e) {
+				throw new RuntimeException("bad 'from' address",e);
+			}
+			try {
+				mail.setTo( MailHome.parseAddress(to) );
+			} catch(MailException e) {
+				throw new RuntimeException("bad 'to' address",e);
+			}
+			mail.setSubject(subject);
+			mail.setContent(new PlainTextContent(mailText));
+			if( smtpFrom.equals("") ) {
+				MailHome.getDefaultSmtpServer().send(mail);
+			} else {
+				MailHome.getDefaultSmtpServer().sendFrom(mail,smtpFrom);
+			}
+			sent = true;
+		}
+		
+		out.print( "\r\n<html>\r\n<head>\r\n<title>Send Mail</title>\r\n</head>\r\n<body>\r\n" );
+
+		if (sent) {
+			
+		out.print( "\r\n<b>Your mail has been sent.</b><br>\r\n" );
+
+		} else {
+			
+		out.print( "\r\n<form method=\"post\" action=\"" );
+		out.print( (response.encodeURL("SendMail.jtp")) );
+		out.print( "\" accept-charset=\"UTF-8\">\r\n<input type=\"hidden\" name=\"Action\" value=\"Post\">\r\n<table>\r\n<tr><td>Envelope From</td><td><input type=\"text\" name=\"smtp_from\" size=\"25\"></td></tr>\r\n<tr><td>From:</td><td><input type=\"text\" name=\"from\" size=\"25\"></td></tr>\r\n<tr><td>To:</td><td><input type=\"text\" name=\"to\" size=\"25\" ></td></tr>\r\n<tr><td>Subject:</td><td><input type=\"text\" name=\"subject\" size=\"25\"\"></td></tr>\r\n<tr><td colspan=\"2\"><textarea name=\"mail\" cols=\"72\" rows=\"20\"></textarea></td></tr>\r\n<tr><td><input type=\"submit\" value=\"Send Mail\" /></td></tr>\r\n</table>\r\n</form>\r\n" );
+ 
+		}
+		
+		out.print( "\r\n<br>\r\n<a href=\"/tools/Index.jtp\">back to tools</a>\r\n<br>\r\n</body>\r\n</html>\r\n" );
+
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/SendMail.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,86 @@
+<%
+package nabble.view.web.tools;
+
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class SendMail extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		boolean sent = false;
+		if( "Post".equals( request.getParameter("Action") ) ) {
+			String mailText = request.getParameter("mail");
+			String from = request.getParameter("from");
+			String smtpFrom = request.getParameter("smtp_from");
+			String to = request.getParameter("to");
+			String subject = request.getParameter("subject");
+			Mail mail = MailHome.newMail();
+			try {
+				mail.setFrom( MailHome.parseAddress(from) );
+			} catch(MailException e) {
+				throw new RuntimeException("bad 'from' address",e);
+			}
+			try {
+				mail.setTo( MailHome.parseAddress(to) );
+			} catch(MailException e) {
+				throw new RuntimeException("bad 'to' address",e);
+			}
+			mail.setSubject(subject);
+			mail.setContent(new PlainTextContent(mailText));
+			if( smtpFrom.equals("") ) {
+				MailHome.getDefaultSmtpServer().send(mail);
+			} else {
+				MailHome.getDefaultSmtpServer().sendFrom(mail,smtpFrom);
+			}
+			sent = true;
+		}
+		%>
+		<html>
+		<head>
+		<title>Send Mail</title>
+		</head>
+		<body>
+		<%
+		if (sent) {
+			%>
+			<b>Your mail has been sent.</b><br>
+			<%
+		} else {
+			%>
+			<form method="post" action="<%=response.encodeURL("SendMail.jtp")%>" accept-charset="UTF-8">
+			<input type="hidden" name="Action" value="Post">
+			<table>
+			<tr><td>Envelope From</td><td><input type="text" name="smtp_from" size="25"></td></tr>
+			<tr><td>From:</td><td><input type="text" name="from" size="25"></td></tr>
+			<tr><td>To:</td><td><input type="text" name="to" size="25" ></td></tr>
+			<tr><td>Subject:</td><td><input type="text" name="subject" size="25""></td></tr>
+			<tr><td colspan="2"><textarea name="mail" cols="72" rows="20"></textarea></td></tr>
+			<tr><td><input type="submit" value="Send Mail" /></td></tr>
+			</table>
+			</form>
+			<% 
+		}
+		%>
+		<br>
+		<a href="/tools/Index.jtp">back to tools</a>
+		<br>
+		</body>
+		</html>
+		<%
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/ShellHelp.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,21 @@
+
+package nabble.view.web.tools;	
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public final class ShellHelp extends HttpServlet {
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		PrintWriter out = response.getWriter();
+
+		out.print( "\r\n<html>\r\n	<body>\r\n		<h1>Simple Help for Nabble Shell</h1>\r\n		<h2>Sites</h2>\r\n		site = ModelHome.getSite(1234)\r\n		<h2>Nodes</h2>\r\n		node = ModelHome.getNode(1234)\r\n		<h2>Users</h2>\r\n		user = ModelHome.getUserFromEmail(\"sample@email.com\")<br>\r\n		user = ModelHome.getUserFromName(\"mike\")<br>\r\n		user = ModelHome.getUser(1234)<br>\r\n		user.getName()<br>\r\n		user.getEmail()\r\n		<h2>Embedding URLs</h2>\r\n		node.getEmbeddingUrl()<br/>\r\n		node.setEmbeddingUrl(\"http://www.example.com/forum.html\")<br>\r\n		node.setEmbeddingUrl(nil)\r\n		<h2>Restoring Backups</h2>\r\n		local XMLBackupRestore = require \"java:nabble.model.export.XMLBackupRestore\"<br/>\r\n		XMLBackupRestore.restore(\"c:\\\\temp\\\\\", \"my.email@gmail.com\")\r\n	</body>\r\n</html>\r\n" );
+
+	}
+}				
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/ShellHelp.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,42 @@
+<%
+package nabble.view.web.tools;	
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public final class ShellHelp extends HttpServlet {
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		PrintWriter out = response.getWriter();
+%>
+<html>
+	<body>
+		<h1>Simple Help for Nabble Shell</h1>
+		<h2>Sites</h2>
+		site = ModelHome.getSite(1234)
+		<h2>Nodes</h2>
+		node = ModelHome.getNode(1234)
+		<h2>Users</h2>
+		user = ModelHome.getUserFromEmail("sample@email.com")<br>
+		user = ModelHome.getUserFromName("mike")<br>
+		user = ModelHome.getUser(1234)<br>
+		user.getName()<br>
+		user.getEmail()
+		<h2>Embedding URLs</h2>
+		node.getEmbeddingUrl()<br/>
+		node.setEmbeddingUrl("http://www.example.com/forum.html")<br>
+		node.setEmbeddingUrl(nil)
+		<h2>Restoring Backups</h2>
+		local XMLBackupRestore = require "java:nabble.model.export.XMLBackupRestore"<br/>
+		XMLBackupRestore.restore("c:\\temp\\", "my.email@gmail.com")
+	</body>
+</html>
+<%
+	}
+}				
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/SpamRules.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,301 @@
+
+package nabble.view.web.tools;
+
+import nabble.model.DailyNumber;
+import nabble.model.ModelException;
+import nabble.model.Person;
+import nabble.model.SystemProperties;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+
+public final class SpamRules extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(SpamRules.class);
+
+	private static final List<Rule> RULES = new ArrayList<Rule>();
+
+	static {
+		String spamRules = SystemProperties.get("spam.rules");
+		if (spamRules != null)
+			RULES.addAll(parseRules(spamRules));
+	}
+
+	private interface Rule {
+		public String checkSubject(String subject) throws ModelException.SpamException;
+		public String checkMessage(String message, Person visitor) throws ModelException.SpamException;
+	}
+/*
+	public static void checkSubject(HttpServletRequest request, String subject)
+		throws ModelException.SpamException
+	{
+		if (subject == null)
+			return;
+
+		// Removes duplicated space chars
+		subject = subject.replaceAll("[ ]+"," ");
+		for (Rule rule : RULES) {
+			try {
+				rule.checkSubject(subject);
+			} catch (ModelException.SpamException e) {
+				DailyNumber.blockedSpams.inc();
+				if (request != null)
+					logger.info("host="+request.getServerName()+" | IP=" + Jtp.getClientIpAddr(request) + " | " + e.getMessage());
+				else
+					logger.info("Blocked Mail | " + e.getMessage());
+				throw e;
+			}
+		}
+	}
+
+	public static void checkMessage(HttpServletRequest request, String message, Person visitor)
+		throws ModelException.SpamException
+	{
+		if (message == null)
+			return;
+
+		// Removes duplicated space chars
+		message = message.replaceAll("[ ]+"," ");
+		for (Rule rule : RULES) {
+			try {
+				rule.checkMessage(message, visitor);
+			} catch (ModelException.SpamException e) {
+				DailyNumber.blockedSpams.inc();
+				String email = visitor instanceof User? ((User) visitor).getEmail() : "NO EMAIL";
+				if (request != null)
+					logger.info("host="+request.getServerName()+" | IP=" + Jtp.getClientIpAddr(request) + " | " + email + " | " + e.getMessage());
+				else
+					logger.info("Blocked Mail | " + email + " | " + e.getMessage());
+				throw e;
+			}
+		}
+	}
+*/
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		String spamRules = SystemProperties.get("spam.rules");
+
+		if ("save".equals(request.getParameter("action"))) {
+			String rules = request.getParameter("rules");
+			RULES.clear();
+			if (rules != null && rules.length() > 0) {
+				List<Rule> r = parseRules(rules);
+				if (r.size() > 0)
+					RULES.addAll(r);
+			}
+			SystemProperties.set("spam.rules", rules);
+			response.sendRedirect("/tools/");
+			return;
+		}
+
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\n<html>\n	<head>\n		<script src=\"" );
+		out.print( (Shared.getJQueryPath()) );
+		out.print( "\"></script>\n		<link rel=\"stylesheet\" href=\"" );
+		out.print( (Shared.getCssPath()) );
+		out.print( "\" type=\"text/css\" />\n	</head>\n	<body>\n		<div>\n			<a href=\"/tools/\">Tools</a>\n		</div>\n		<h1>Spam Rules</h1>\n		<div id=\"nabble\" class=\"nabble\">\n			<form action=\"/tools/SpamRules.jtp\" method=\"POST\">\n				<input type=\"hidden\" name=\"action\" value=\"save\">\n				<textarea name=\"rules\" rows=15 style=\"width:80%\">" );
+		out.print( (Jtp.hideNull(spamRules)) );
+		out.print( "</textarea>\n				<br>\n				<input type=\"submit\" value=\"Save Rules\">\n			</form>\n		</div>\n	</body>\n</html>\n" );
+
+	}
+
+	private static synchronized List<Rule> parseRules(String rules) {
+		List<Rule> r = new ArrayList<Rule>();
+		String[] lines = rules.split("\n");
+		for (String line : lines) {
+			if (line.length() == 0 || line.startsWith("#")) {
+				// do nothing
+			} else if (line.startsWith("subject-contains:"))
+				r.add(new SubjectContainsRule(line.substring("subject-contains:".length()).trim()));
+			else if (line.startsWith("message-contains:"))
+				r.add(new MessageContainsRule(line.substring("message-contains:".length()).trim()));
+			else if (line.startsWith("message-contains-pattern:"))
+				r.add(new MessageContainsPattern(line.substring("message-contains-pattern:".length()).trim()));
+			else if (line.startsWith("message-word-count:"))
+				r.add(new WordCountRule(line.substring("message-word-count:".length()).trim()));
+			else if (line.startsWith("subject-max-words:"))
+				r.add(new SubjectMaxWordsRule(line.substring("subject-max-words:".length()).trim()));
+			else if (line.startsWith("message-max-words:"))
+				r.add(new MessageMaxWordsRule(line.substring("message-max-words:".length()).trim()));
+			else if (line.startsWith("user-email-pattern:"))
+				r.add(new UserEmailPattern(line.substring("user-email-pattern:".length()).trim()));
+		}
+		return r;
+	}
+
+	private static class SubjectContainsRule implements Rule {
+		private String text;
+		private String[] parts;
+		public SubjectContainsRule(String text) {
+			this.text = text;
+			this.parts = text.toLowerCase().split("\\+");
+		}
+		public String checkSubject(String subject)
+			throws ModelException.SpamException
+		{
+			if (subject == null) return null;
+			subject = subject.toLowerCase();
+			for (String part : parts) {
+				if (subject.indexOf(part) == -1)
+					return null;
+			}
+			throw new ModelException.SubjectContainsInvalidWord(text);
+		}
+		public String checkMessage(String message, Person visitor) { return null; }
+	}
+
+	private static class SubjectMaxWordsRule implements Rule {
+		private int max;
+		private String[] parts;
+		public SubjectMaxWordsRule(String text) {
+			String[] params = text.split("\\|");
+			this.max = Integer.valueOf(params[0]);
+			this.parts = params[1].toLowerCase().split("\\+");
+		}
+		public String checkSubject(String subject)
+			throws ModelException.SpamException
+		{
+			if (subject == null) return null;
+			subject = subject.toLowerCase();
+			int count = 0;
+			for (String part : parts) {
+				if (subject.indexOf(part) >= 0)
+					count++;
+				if (count >= max)
+					throw new ModelException.SubjectContainsCommonSpamWords(subject);
+			}
+			return null;
+		}
+		public String checkMessage(String message, Person visitor) { return null; }
+	}
+
+	private static class MessageMaxWordsRule implements Rule {
+		private int max;
+		private String[] parts;
+		public MessageMaxWordsRule(String text) {
+			String[] params = text.split("\\|");
+			this.max = Integer.valueOf(params[0]);
+			this.parts = params[1].toLowerCase().split("\\+");
+		}
+		public String checkMessage(String message, Person visitor)
+			throws ModelException.SpamException
+		{
+			if (message == null) return null;
+			message = message.toLowerCase();
+			int count = 0;
+			for (String part : parts) {
+				if (message.indexOf(part) >= 0)
+					count++;
+				if (count >= max)
+					throw new ModelException.MessageContainsCommonSpamWords(message);
+			}
+			return null;
+		}
+		public String checkSubject(String subject) { return null; }
+	}
+
+	private static class MessageContainsRule implements Rule {
+		private String text;
+		private String[] parts;
+		public MessageContainsRule(String text) {
+			this.text = text;
+			this.parts = text.toLowerCase().split("\\+");
+		}
+		public String checkMessage(String message, Person visitor)
+			throws ModelException.SpamException
+		{
+			if (message == null) return null;
+			String msg = message.toLowerCase();
+			for (String part : parts) {
+				if (msg.indexOf(part) == -1)
+					return null;
+			}
+			throw new ModelException.MessageContainsInvalidWord(text);
+		}
+		public String checkSubject(String subject) { return null; }
+	}
+
+	private static class MessageContainsPattern implements Rule {
+		private Pattern[] pattern;
+		public MessageContainsPattern(String patternList) {
+			String[] patterns = patternList.split("____");
+			this.pattern = new Pattern[patterns.length];
+			for (int i = 0; i < patterns.length; i++)
+				this.pattern[i] = Pattern.compile(patterns[i]);
+		}
+		public String checkMessage(String message, Person visitor)
+			throws ModelException.SpamException
+		{
+			if (message == null) return null;
+			for (Pattern p : pattern) {
+				if (!p.matcher(message).find())
+					return null;
+			}
+			throw new ModelException.MessageContainsCommonSpamWords(message);
+		}
+		public String checkSubject(String subject) { return null; }
+	}
+
+	private static class UserEmailPattern implements Rule {
+		private Pattern pattern;
+		public UserEmailPattern(String pattern) {
+			this.pattern = Pattern.compile(pattern);
+		}
+		public String checkMessage(String message, Person visitor)
+			throws ModelException.SpamException
+		{
+			if (visitor instanceof User) {
+				String email = ((User) visitor).getEmail();
+				if (!pattern.matcher(email).matches())
+					return null;
+				throw new ModelException.MessageContainsInvalidWord("EMAIL: " + email);
+			}
+			return null;
+		}
+		public String checkSubject(String subject) { return null; }
+	}
+
+	private static class WordCountRule implements Rule {
+		private String text;
+		private int count;
+		public WordCountRule(String value) {
+			String[] values = value.split(">");
+			this.text = values[0].toLowerCase();
+			this.count = Integer.valueOf(values[1]);
+		}
+		public String checkMessage(String message, Person visitor)
+			throws ModelException.SpamException
+		{
+			if (message == null) return null;
+			String msg = message.toLowerCase();
+			int oldPos = 0;
+			int newPos = msg.indexOf(text);
+			int c = 0;
+			while (newPos > oldPos) {
+				c++;
+				if (c >= count)
+					throw new ModelException.MessageContainsManyInvalidWords(text);
+				oldPos = newPos;
+				newPos = msg.indexOf(text, oldPos+text.length());
+			}
+			return null;
+		}
+		public String checkSubject(String subject) { return null; }
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/SpamRules.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,314 @@
+<%
+package nabble.view.web.tools;
+
+import nabble.model.DailyNumber;
+import nabble.model.ModelException;
+import nabble.model.Person;
+import nabble.model.SystemProperties;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+
+public final class SpamRules extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(SpamRules.class);
+
+	private static final List<Rule> RULES = new ArrayList<Rule>();
+
+	static {
+		String spamRules = SystemProperties.get("spam.rules");
+		if (spamRules != null)
+			RULES.addAll(parseRules(spamRules));
+	}
+
+	private interface Rule {
+		public String checkSubject(String subject) throws ModelException.SpamException;
+		public String checkMessage(String message, Person visitor) throws ModelException.SpamException;
+	}
+/*
+	public static void checkSubject(HttpServletRequest request, String subject)
+		throws ModelException.SpamException
+	{
+		if (subject == null)
+			return;
+
+		// Removes duplicated space chars
+		subject = subject.replaceAll("[ ]+"," ");
+		for (Rule rule : RULES) {
+			try {
+				rule.checkSubject(subject);
+			} catch (ModelException.SpamException e) {
+				DailyNumber.blockedSpams.inc();
+				if (request != null)
+					logger.info("host="+request.getServerName()+" | IP=" + Jtp.getClientIpAddr(request) + " | " + e.getMessage());
+				else
+					logger.info("Blocked Mail | " + e.getMessage());
+				throw e;
+			}
+		}
+	}
+
+	public static void checkMessage(HttpServletRequest request, String message, Person visitor)
+		throws ModelException.SpamException
+	{
+		if (message == null)
+			return;
+
+		// Removes duplicated space chars
+		message = message.replaceAll("[ ]+"," ");
+		for (Rule rule : RULES) {
+			try {
+				rule.checkMessage(message, visitor);
+			} catch (ModelException.SpamException e) {
+				DailyNumber.blockedSpams.inc();
+				String email = visitor instanceof User? ((User) visitor).getEmail() : "NO EMAIL";
+				if (request != null)
+					logger.info("host="+request.getServerName()+" | IP=" + Jtp.getClientIpAddr(request) + " | " + email + " | " + e.getMessage());
+				else
+					logger.info("Blocked Mail | " + email + " | " + e.getMessage());
+				throw e;
+			}
+		}
+	}
+*/
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		String spamRules = SystemProperties.get("spam.rules");
+
+		if ("save".equals(request.getParameter("action"))) {
+			String rules = request.getParameter("rules");
+			RULES.clear();
+			if (rules != null && rules.length() > 0) {
+				List<Rule> r = parseRules(rules);
+				if (r.size() > 0)
+					RULES.addAll(r);
+			}
+			SystemProperties.set("spam.rules", rules);
+			response.sendRedirect("/tools/");
+			return;
+		}
+
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<script src="<%=Shared.getJQueryPath()%>"></script>
+				<link rel="stylesheet" href="<%=Shared.getCssPath()%>" type="text/css" />
+			</head>
+			<body>
+				<div>
+					<a href="/tools/">Tools</a>
+				</div>
+				<h1>Spam Rules</h1>
+				<div id="nabble" class="nabble">
+					<form action="/tools/SpamRules.jtp" method="POST">
+						<input type="hidden" name="action" value="save">
+						<textarea name="rules" rows=15 style="width:80%"><%=Jtp.hideNull(spamRules)%></textarea>
+						<br>
+						<input type="submit" value="Save Rules">
+					</form>
+				</div>
+			</body>
+		</html>
+		<%
+	}
+
+	private static synchronized List<Rule> parseRules(String rules) {
+		List<Rule> r = new ArrayList<Rule>();
+		String[] lines = rules.split("\n");
+		for (String line : lines) {
+			if (line.length() == 0 || line.startsWith("#")) {
+				// do nothing
+			} else if (line.startsWith("subject-contains:"))
+				r.add(new SubjectContainsRule(line.substring("subject-contains:".length()).trim()));
+			else if (line.startsWith("message-contains:"))
+				r.add(new MessageContainsRule(line.substring("message-contains:".length()).trim()));
+			else if (line.startsWith("message-contains-pattern:"))
+				r.add(new MessageContainsPattern(line.substring("message-contains-pattern:".length()).trim()));
+			else if (line.startsWith("message-word-count:"))
+				r.add(new WordCountRule(line.substring("message-word-count:".length()).trim()));
+			else if (line.startsWith("subject-max-words:"))
+				r.add(new SubjectMaxWordsRule(line.substring("subject-max-words:".length()).trim()));
+			else if (line.startsWith("message-max-words:"))
+				r.add(new MessageMaxWordsRule(line.substring("message-max-words:".length()).trim()));
+			else if (line.startsWith("user-email-pattern:"))
+				r.add(new UserEmailPattern(line.substring("user-email-pattern:".length()).trim()));
+		}
+		return r;
+	}
+
+	private static class SubjectContainsRule implements Rule {
+		private String text;
+		private String[] parts;
+		public SubjectContainsRule(String text) {
+			this.text = text;
+			this.parts = text.toLowerCase().split("\\+");
+		}
+		public String checkSubject(String subject)
+			throws ModelException.SpamException
+		{
+			if (subject == null) return null;
+			subject = subject.toLowerCase();
+			for (String part : parts) {
+				if (subject.indexOf(part) == -1)
+					return null;
+			}
+			throw new ModelException.SubjectContainsInvalidWord(text);
+		}
+		public String checkMessage(String message, Person visitor) { return null; }
+	}
+
+	private static class SubjectMaxWordsRule implements Rule {
+		private int max;
+		private String[] parts;
+		public SubjectMaxWordsRule(String text) {
+			String[] params = text.split("\\|");
+			this.max = Integer.valueOf(params[0]);
+			this.parts = params[1].toLowerCase().split("\\+");
+		}
+		public String checkSubject(String subject)
+			throws ModelException.SpamException
+		{
+			if (subject == null) return null;
+			subject = subject.toLowerCase();
+			int count = 0;
+			for (String part : parts) {
+				if (subject.indexOf(part) >= 0)
+					count++;
+				if (count >= max)
+					throw new ModelException.SubjectContainsCommonSpamWords(subject);
+			}
+			return null;
+		}
+		public String checkMessage(String message, Person visitor) { return null; }
+	}
+
+	private static class MessageMaxWordsRule implements Rule {
+		private int max;
+		private String[] parts;
+		public MessageMaxWordsRule(String text) {
+			String[] params = text.split("\\|");
+			this.max = Integer.valueOf(params[0]);
+			this.parts = params[1].toLowerCase().split("\\+");
+		}
+		public String checkMessage(String message, Person visitor)
+			throws ModelException.SpamException
+		{
+			if (message == null) return null;
+			message = message.toLowerCase();
+			int count = 0;
+			for (String part : parts) {
+				if (message.indexOf(part) >= 0)
+					count++;
+				if (count >= max)
+					throw new ModelException.MessageContainsCommonSpamWords(message);
+			}
+			return null;
+		}
+		public String checkSubject(String subject) { return null; }
+	}
+
+	private static class MessageContainsRule implements Rule {
+		private String text;
+		private String[] parts;
+		public MessageContainsRule(String text) {
+			this.text = text;
+			this.parts = text.toLowerCase().split("\\+");
+		}
+		public String checkMessage(String message, Person visitor)
+			throws ModelException.SpamException
+		{
+			if (message == null) return null;
+			String msg = message.toLowerCase();
+			for (String part : parts) {
+				if (msg.indexOf(part) == -1)
+					return null;
+			}
+			throw new ModelException.MessageContainsInvalidWord(text);
+		}
+		public String checkSubject(String subject) { return null; }
+	}
+
+	private static class MessageContainsPattern implements Rule {
+		private Pattern[] pattern;
+		public MessageContainsPattern(String patternList) {
+			String[] patterns = patternList.split("____");
+			this.pattern = new Pattern[patterns.length];
+			for (int i = 0; i < patterns.length; i++)
+				this.pattern[i] = Pattern.compile(patterns[i]);
+		}
+		public String checkMessage(String message, Person visitor)
+			throws ModelException.SpamException
+		{
+			if (message == null) return null;
+			for (Pattern p : pattern) {
+				if (!p.matcher(message).find())
+					return null;
+			}
+			throw new ModelException.MessageContainsCommonSpamWords(message);
+		}
+		public String checkSubject(String subject) { return null; }
+	}
+
+	private static class UserEmailPattern implements Rule {
+		private Pattern pattern;
+		public UserEmailPattern(String pattern) {
+			this.pattern = Pattern.compile(pattern);
+		}
+		public String checkMessage(String message, Person visitor)
+			throws ModelException.SpamException
+		{
+			if (visitor instanceof User) {
+				String email = ((User) visitor).getEmail();
+				if (!pattern.matcher(email).matches())
+					return null;
+				throw new ModelException.MessageContainsInvalidWord("EMAIL: " + email);
+			}
+			return null;
+		}
+		public String checkSubject(String subject) { return null; }
+	}
+
+	private static class WordCountRule implements Rule {
+		private String text;
+		private int count;
+		public WordCountRule(String value) {
+			String[] values = value.split(">");
+			this.text = values[0].toLowerCase();
+			this.count = Integer.valueOf(values[1]);
+		}
+		public String checkMessage(String message, Person visitor)
+			throws ModelException.SpamException
+		{
+			if (message == null) return null;
+			String msg = message.toLowerCase();
+			int oldPos = 0;
+			int newPos = msg.indexOf(text);
+			int c = 0;
+			while (newPos > oldPos) {
+				c++;
+				if (c >= count)
+					throw new ModelException.MessageContainsManyInvalidWords(text);
+				oldPos = newPos;
+				newPos = msg.indexOf(text, oldPos+text.length());
+			}
+			return null;
+		}
+		public String checkSubject(String subject) { return null; }
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/SpamSearch.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,269 @@
+
+package nabble.view.web.tools;
+
+import com.google.gson.Gson;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class SpamSearch extends HttpServlet {
+
+	private static final String GOOGLE_URL = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&key=AIzaSyCjUKhbJVpU83CiRPJ1WV1x7WyszYSntC0&q=";
+    private static final String CHARSET = "UTF-8";
+
+	private static final Pattern POST_PTN = Pattern.compile("/.*-t(d\\d+)|((p\\d+)(p\\d+)?)\\.html$");
+
+	/**
+	 * Defines five blocks per page because google usually returns a block with four results.
+	 * So it shows 20 results per page.
+	*/
+	private static final int BLOCKS_PER_PAGE = 6;
+
+	static String extractDomain(String url) {
+		int posDoubleSlash = url.indexOf("://");
+		if (posDoubleSlash == -1)
+			return null;
+		int posNextSlash = url.indexOf('/', posDoubleSlash+3);
+		return url.substring(posDoubleSlash+3, posNextSlash);
+	}
+
+	private static Long getPostId(String url) {
+		Matcher m = POST_PTN.matcher(url);
+		if (m.find()) {
+			if (m.group(4) != null)
+				return Long.valueOf(m.group(4).substring(1));
+			else if (m.group(3) != null)
+				return Long.valueOf(m.group(3).substring(1));
+			else if (m.group(1) != null)
+				return Long.valueOf(m.group(1).substring(1));
+		}
+		return null;
+	}
+
+	private static Long getSiteId(String domain) {
+		if (domain.endsWith(Jtp.getDefaultHost())) {
+			domain = domain.replace('.'+Jtp.getDefaultHost(), "");
+			int dot = domain.lastIndexOf('.');
+			return Long.valueOf(domain.substring(dot+1));
+		}
+		return null;
+	}
+
+	public void printResult(GoogleResults results, int j, PrintWriter out) {
+		if (results.getResponseData() != null) {
+			GoogleResults.Result result = results.getResponseData().getResults().get(j);
+			String url = result.getUrl();
+			String title = result.getTitle();
+			String content = result.getContent();
+
+			Long siteId = null;
+			Long postId = getPostId(url);
+			if (postId != null) {
+				String domain = extractDomain(url);
+				if (domain != null) {
+					siteId = getSiteId(domain);
+					siteId = siteId == null? ModelHome.getSiteIdFromDomain(domain) : siteId;
+				}
+			}
+			Site site = siteId == null? null : ModelHome.getSite(siteId);
+			Node node = site == null? null : site.getNode(postId);
+			
+		out.print( "\r\n" );
+ if (node != null && !node.getMessage().isDeleted()) { 
+		out.print( "\r\n	<tr>\r\n		<td><input type=\"checkbox\" class=\"checkBox\" value=\"" );
+		out.print( (siteId) );
+		out.print( "|" );
+		out.print( (postId) );
+		out.print( "\" name=\"results\"/></td>\r\n		<td>\r\n			<a href=\"" );
+		out.print( (url) );
+		out.print( "\">" );
+		out.print( (title) );
+		out.print( "</a><br/>\r\n			<div class=\"clickable\">\r\n				<span class='url'>" );
+		out.print( (url) );
+		out.print( "</span><br/>\r\n				" );
+		out.print( (content) );
+		out.print( "<br/>\r\n				" );
+ if (node.getDescendantCount() > 1) { 
+		out.print( "\r\n					<span class=\"badge\" style=\"font-weight:bold;background:red;color:white\">Has Replies</span>\r\n				" );
+ } 
+		out.print( "\r\n				" );
+ Node.MailToList mail = node.getMailToList(); 
+		out.print( "\r\n				" );
+ if (mail != null && mail.isPending()) { 
+		out.print( "\r\n					<span class=\"badge\" style=\"font-weight:bold;background:green;color:white\">Pending</span>\r\n				" );
+ } 
+		out.print( "\r\n			</div>\r\n		</td>\r\n	</tr>\r\n" );
+ } else { 
+		out.print( "\r\n	<tr>\r\n		<td></td>\r\n		<td class=\"grayP\">\r\n			<a href='" );
+		out.print( (url) );
+		out.print( "'>" );
+		out.print( (title) );
+		out.print( "</a><br/>\r\n			<span class='url'>" );
+		out.print( (url) );
+		out.print( "</span><br/>\r\n			" );
+		out.print( (content) );
+		out.print( "\r\n		</td>\r\n	</tr>\r\n" );
+ } 
+		out.print( "\r\n" );
+
+		}
+	}
+
+	public void getResults(String query, String site,HttpServletRequest request,PrintWriter out)
+		throws IOException
+	{
+		int start = 0;
+		int page = 0;
+
+		if(request.getParameter("start") != null)
+			start = Integer.parseInt(request.getParameter("start"));
+
+		if(request.getParameter("page") != null)
+			page = Integer.parseInt(request.getParameter("page"));
+
+		if(!"".equals(query)) {
+			
+		out.print( "\r\n<div>Page " );
+		out.print( (page) );
+		out.print( "</div>\r\n<table>\r\n" );
+
+			for (int k = 0; k < BLOCKS_PER_PAGE; k++) {
+				URL url = new URL(GOOGLE_URL+ URLEncoder.encode("site:"+site+" "+query, CHARSET)+"&start="+start );
+				Reader reader = new InputStreamReader(url.openStream(), CHARSET);
+				GoogleResults results = new Gson().fromJson(reader, GoogleResults.class);
+				if (results.getResponseData() != null) {
+					int block_size = results.getResponseData().getResults().size();
+					start += block_size;
+					for (int j = 0; j < block_size; j++) {
+							printResult(results,j,out);
+					}
+				}
+			}
+			
+		out.print( "\r\n</table>\r\n<br />\r\nPage " );
+		out.print( (page) );
+		out.print( " &ndash;\r\n<a href='?start=" );
+		out.print( (start) );
+		out.print( "&site=" );
+		out.print( (site) );
+		out.print( "&query=" );
+		out.print( (query) );
+		out.print( "&page=" );
+		out.print( ((page+1)) );
+		out.print( "'>Next</a>\r\n<br />\r\n<br />\r\n<input type=\"submit\" value=\"Delete Selected Posts\" />\r\n" );
+
+		}
+	}
+
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		String query = request.getParameter("query");
+		query = query == null? "" : query;
+
+		String site = request.getParameter("site");
+		site = site == null? Jtp.getDefaultHost() : site;
+
+		boolean isDelete = "POST".equals(request.getMethod()) && "delete".equals(request.getParameter("action"));
+		int deleteCounter = 0;
+		if (isDelete) {
+			String[] results = request.getParameterValues("results");
+			for (String r : results) {
+				String[] parts = r.split("\\|");
+				long siteId = Long.valueOf(parts[0]);
+				long nodeId = Long.valueOf(parts[1]);
+				Node n = ModelHome.getSite(siteId).getNode(nodeId);
+				if (n != null) {
+					n.deleteMessageOrNode();
+					deleteCounter++;
+				}
+			}
+		}
+
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		<title>Nabble Search</title>\r\n		" );
+ Shared.loadJavascript(request, out); 
+		out.print( "\r\n		" );
+ javascript(out); 
+		out.print( "\r\n		" );
+ css(out); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		<h1>Nabble Search</h1>\r\n		<form action=\"SpamSearch.jtp\" method=\"get\">\r\n			Search for <input type=\"text\" name=\"query\" value=\"" );
+		out.print( (query) );
+		out.print( "\" /> in <input type=\"text\" name=\"site\" value=\"" );
+		out.print( (site) );
+		out.print( "\" />\r\n			<input type=\"submit\" value=\"Search\" />\r\n			<input type=\"hidden\" value=\"0\" name=\"start\" />\r\n			<input type=\"hidden\" value=\"1\" name=\"page\" />\r\n		</form>\r\n		" );
+ if (isDelete) { 
+		out.print( "\r\n		<div style=\"color:red;padding:.5em 0\">Deleted " );
+		out.print( (deleteCounter) );
+		out.print( " nodes.</div>\r\n		" );
+ } 
+		out.print( "\r\n		<div id='results'>\r\n			<form action=\"SpamSearch.jtp\" method=\"post\">\r\n				<input type=\"hidden\" value=\"delete\" name=\"action\" />\r\n				<input type=\"hidden\" name=\"query\" value=\"" );
+		out.print( (query) );
+		out.print( "\"/>\r\n				<input type=\"hidden\" name=\"site\" value=\"" );
+		out.print( (site) );
+		out.print( "\" />\r\n				" );
+ getResults(query,site,request,out); 
+		out.print( "\r\n			</form>\r\n		</div>\r\n	</body>\r\n</html>\r\n" );
+
+	}
+
+	private void javascript(PrintWriter out) {
+		
+		out.print( "\r\n	<script type=\"text/javascript\">\r\n		$(document).ready(function() {\r\n			$('div.clickable').each(function() {\r\n				var $this = $(this);\r\n				var $checkbox = $this.parent().prev().children().eq(0);\r\n				$this.click(function() {\r\n					var checked = $checkbox.attr('checked');\r\n					if (checked) {\r\n						$this.removeClass('selected-row');\r\n						$checkbox.removeAttr('checked');\r\n					} else {\r\n						$this.addClass('selected-row');\r\n						$checkbox.attr('checked', true);\r\n					}\r\n				});\r\n			});\r\n		});\r\n	</script>\r\n" );
+
+	}
+	private void css(PrintWriter out) {
+		
+		out.print( "\r\n<style type=\"text/css\">\r\n	body {\r\n		padding: 1em;\r\n		font-family: Verdana, Sans-serif;\r\n		font-size:.84em;\r\n	}\r\n	span.url{\r\n		color: green;\r\n	}\r\n	#results table {\r\n		width: 100%;\r\n	}\r\n	#results table td {\r\n		vertical-align:top;\r\n		padding-bottom: 1em;\r\n	}\r\n	#results td.grayP , td.grayP a , td.grayP span {\r\n		color: #C8C8C8 !important;\r\n	}\r\n	form input {\r\n		margin-bottom: 15px;\r\n	}\r\n	span.badge {\r\n		font-size:90%;\r\n		-moz-border-radius: 5px;\r\n		-webkit-border-radius: 5px;\r\n		border-radius: 5px;\r\n		padding: .2em .4em;\r\n	}\r\n	div.clickable { cursor:pointer; }\r\n	.selected-row { background:#ffffcc; }\r\n</style>\r\n" );
+
+	}
+}
+
+
+class GoogleResults {
+
+    private ResponseData responseData;
+    public ResponseData getResponseData() { return responseData; }
+    public void setResponseData(ResponseData responseData) { this.responseData = responseData; }
+    public String toString() { return "ResponseData[" + responseData + "]"; }
+
+    static class ResponseData {
+        private List<Result> results;
+        public List<Result> getResults() { return results; }
+        public void setResults(List<Result> results) { this.results = results; }
+        public String toString() { return "Results[" + results + "]"; }
+    }
+
+    static class Result {
+        private String url;
+        private String title;
+        private String content;
+        public String getUrl() { return url; }
+        public String getTitle() { return title; }
+        public String getContent() { return content; }
+        public void setUrl(String url) { this.url = url; }
+        public void setTitle(String title) { this.title = title; }
+        public String toString() { return "Result[url:" + url +",title:" + title + "]"; }
+    }
+}
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/SpamSearch.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,308 @@
+<%
+package nabble.view.web.tools;
+
+import com.google.gson.Gson;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class SpamSearch extends HttpServlet {
+
+	private static final String GOOGLE_URL = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&key=AIzaSyCjUKhbJVpU83CiRPJ1WV1x7WyszYSntC0&q=";
+    private static final String CHARSET = "UTF-8";
+
+	private static final Pattern POST_PTN = Pattern.compile("/.*-t(d\\d+)|((p\\d+)(p\\d+)?)\\.html$");
+
+	/**
+	 * Defines five blocks per page because google usually returns a block with four results.
+	 * So it shows 20 results per page.
+	*/
+	private static final int BLOCKS_PER_PAGE = 6;
+
+	static String extractDomain(String url) {
+		int posDoubleSlash = url.indexOf("://");
+		if (posDoubleSlash == -1)
+			return null;
+		int posNextSlash = url.indexOf('/', posDoubleSlash+3);
+		return url.substring(posDoubleSlash+3, posNextSlash);
+	}
+
+	private static Long getPostId(String url) {
+		Matcher m = POST_PTN.matcher(url);
+		if (m.find()) {
+			if (m.group(4) != null)
+				return Long.valueOf(m.group(4).substring(1));
+			else if (m.group(3) != null)
+				return Long.valueOf(m.group(3).substring(1));
+			else if (m.group(1) != null)
+				return Long.valueOf(m.group(1).substring(1));
+		}
+		return null;
+	}
+
+	private static Long getSiteId(String domain) {
+		if (domain.endsWith(Jtp.getDefaultHost())) {
+			domain = domain.replace('.'+Jtp.getDefaultHost(), "");
+			int dot = domain.lastIndexOf('.');
+			return Long.valueOf(domain.substring(dot+1));
+		}
+		return null;
+	}
+
+	public void printResult(GoogleResults results, int j, PrintWriter out) {
+		if (results.getResponseData() != null) {
+			GoogleResults.Result result = results.getResponseData().getResults().get(j);
+			String url = result.getUrl();
+			String title = result.getTitle();
+			String content = result.getContent();
+
+			Long siteId = null;
+			Long postId = getPostId(url);
+			if (postId != null) {
+				String domain = extractDomain(url);
+				if (domain != null) {
+					siteId = getSiteId(domain);
+					siteId = siteId == null? ModelHome.getSiteIdFromDomain(domain) : siteId;
+				}
+			}
+			Site site = siteId == null? null : ModelHome.getSite(siteId);
+			Node node = site == null? null : site.getNode(postId);
+			%>
+			<% if (node != null && !node.getMessage().isDeleted()) { %>
+				<tr>
+					<td><input type="checkbox" class="checkBox" value="<%=siteId%>|<%=postId%>" name="results"/></td>
+					<td>
+						<a href="<%=url%>"><%=title%></a><br/>
+						<div class="clickable">
+							<span class='url'><%=url%></span><br/>
+							<%=content%><br/>
+							<% if (node.getDescendantCount() > 1) { %>
+								<span class="badge" style="font-weight:bold;background:red;color:white">Has Replies</span>
+							<% } %>
+							<% Node.MailToList mail = node.getMailToList(); %>
+							<% if (mail != null && mail.isPending()) { %>
+								<span class="badge" style="font-weight:bold;background:green;color:white">Pending</span>
+							<% } %>
+						</div>
+					</td>
+				</tr>
+			<% } else { %>
+				<tr>
+					<td></td>
+					<td class="grayP">
+						<a href='<%=url%>'><%=title%></a><br/>
+						<span class='url'><%=url%></span><br/>
+						<%=content%>
+					</td>
+				</tr>
+			<% } %>
+		<%
+		}
+	}
+
+	public void getResults(String query, String site,HttpServletRequest request,PrintWriter out)
+		throws IOException
+	{
+		int start = 0;
+		int page = 0;
+
+		if(request.getParameter("start") != null)
+			start = Integer.parseInt(request.getParameter("start"));
+
+		if(request.getParameter("page") != null)
+			page = Integer.parseInt(request.getParameter("page"));
+
+		if(!"".equals(query)) {
+			%>
+			<div>Page <%=page%></div>
+			<table>
+			<%
+			for (int k = 0; k < BLOCKS_PER_PAGE; k++) {
+				URL url = new URL(GOOGLE_URL+ URLEncoder.encode("site:"+site+" "+query, CHARSET)+"&start="+start );
+				Reader reader = new InputStreamReader(url.openStream(), CHARSET);
+				GoogleResults results = new Gson().fromJson(reader, GoogleResults.class);
+				if (results.getResponseData() != null) {
+					int block_size = results.getResponseData().getResults().size();
+					start += block_size;
+					for (int j = 0; j < block_size; j++) {
+							printResult(results,j,out);
+					}
+				}
+			}
+			%>
+			</table>
+			<br />
+			Page <%=page%> &ndash;
+			<a href='?start=<%=start%>&site=<%=site%>&query=<%=query%>&page=<%=(page+1)%>'>Next</a>
+			<br />
+			<br />
+			<input type="submit" value="Delete Selected Posts" />
+			<%
+		}
+	}
+
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		String query = request.getParameter("query");
+		query = query == null? "" : query;
+
+		String site = request.getParameter("site");
+		site = site == null? Jtp.getDefaultHost() : site;
+
+		boolean isDelete = "POST".equals(request.getMethod()) && "delete".equals(request.getParameter("action"));
+		int deleteCounter = 0;
+		if (isDelete) {
+			String[] results = request.getParameterValues("results");
+			for (String r : results) {
+				String[] parts = r.split("\\|");
+				long siteId = Long.valueOf(parts[0]);
+				long nodeId = Long.valueOf(parts[1]);
+				Node n = ModelHome.getSite(siteId).getNode(nodeId);
+				if (n != null) {
+					n.deleteMessageOrNode();
+					deleteCounter++;
+				}
+			}
+		}
+
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<title>Nabble Search</title>
+				<% Shared.loadJavascript(request, out); %>
+				<% javascript(out); %>
+				<% css(out); %>
+			</head>
+			<body>
+				<h1>Nabble Search</h1>
+				<form action="SpamSearch.jtp" method="get">
+					Search for <input type="text" name="query" value="<%=query%>" /> in <input type="text" name="site" value="<%=site%>" />
+					<input type="submit" value="Search" />
+					<input type="hidden" value="0" name="start" />
+					<input type="hidden" value="1" name="page" />
+				</form>
+				<% if (isDelete) { %>
+				<div style="color:red;padding:.5em 0">Deleted <%=deleteCounter%> nodes.</div>
+				<% } %>
+				<div id='results'>
+					<form action="SpamSearch.jtp" method="post">
+						<input type="hidden" value="delete" name="action" />
+						<input type="hidden" name="query" value="<%=query%>"/>
+						<input type="hidden" name="site" value="<%=site%>" />
+						<% getResults(query,site,request,out); %>
+					</form>
+				</div>
+			</body>
+		</html>
+		<%
+	}
+
+	private void javascript(PrintWriter out) {
+		%>
+			<script type="text/javascript">
+				$(document).ready(function() {
+					$('div.clickable').each(function() {
+						var $this = $(this);
+						var $checkbox = $this.parent().prev().children().eq(0);
+						$this.click(function() {
+							var checked = $checkbox.attr('checked');
+							if (checked) {
+								$this.removeClass('selected-row');
+								$checkbox.removeAttr('checked');
+							} else {
+								$this.addClass('selected-row');
+								$checkbox.attr('checked', true);
+							}
+						});
+					});
+				});
+			</script>
+		<%
+	}
+	private void css(PrintWriter out) {
+		%>
+		<style type="text/css">
+			body {
+				padding: 1em;
+				font-family: Verdana, Sans-serif;
+				font-size:.84em;
+			}
+			span.url{
+				color: green;
+			}
+			#results table {
+				width: 100%;
+			}
+			#results table td {
+				vertical-align:top;
+				padding-bottom: 1em;
+			}
+			#results td.grayP , td.grayP a , td.grayP span {
+				color: #C8C8C8 !important;
+			}
+			form input {
+				margin-bottom: 15px;
+			}
+			span.badge {
+				font-size:90%;
+				-moz-border-radius: 5px;
+				-webkit-border-radius: 5px;
+				border-radius: 5px;
+				padding: .2em .4em;
+			}
+			div.clickable { cursor:pointer; }
+			.selected-row { background:#ffffcc; }
+		</style>
+		<%
+	}
+}
+
+
+class GoogleResults {
+
+    private ResponseData responseData;
+    public ResponseData getResponseData() { return responseData; }
+    public void setResponseData(ResponseData responseData) { this.responseData = responseData; }
+    public String toString() { return "ResponseData[" + responseData + "]"; }
+
+    static class ResponseData {
+        private List<Result> results;
+        public List<Result> getResults() { return results; }
+        public void setResults(List<Result> results) { this.results = results; }
+        public String toString() { return "Results[" + results + "]"; }
+    }
+
+    static class Result {
+        private String url;
+        private String title;
+        private String content;
+        public String getUrl() { return url; }
+        public String getTitle() { return title; }
+        public String getContent() { return content; }
+        public void setUrl(String url) { this.url = url; }
+        public void setTitle(String title) { this.title = title; }
+        public String toString() { return "Result[url:" + url +",title:" + title + "]"; }
+    }
+}
+
+
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/TestMacro.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,68 @@
+
+package nabble.view.web.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import fschmidt.html.Html;
+import fschmidt.util.servlet.JtpContext;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Source;
+import nabble.model.Site;
+import nabble.model.ModelHome;
+import nabble.view.lib.Jtp;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.ServletNamespace;
+import nabble.view.web.template.NodeNamespace;
+import nabble.modules.ModuleManager;
+import nabble.modules.NamlModule;
+import nabble.naml.compiler.Module;
+
+
+public final class TestMacro extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		Site site = Jtp.getSiteNotNull(request);
+		final String macro = request.getParameter("macro");
+		if( macro==null ) {
+			
+		out.print( "\r\n<html>\r\n<head>\r\n<title>Test Macro</title>\r\n</head>\r\n<body>\r\n<form method=\"post\">\r\n<p>Macro:<br/>\r\n<textarea name=\"macro\" rows=\"30\" cols=\"90\" wrap=\"off\">\r\n<macro name=\"test\">\r\n\r\n</macro>\r\n</textarea>\r\n</macro>\r\n<p><input type=\"submit\" value=\"run macro\"></p>\r\n</form>\r\n</body>\r\n</html>\r\n" );
+
+			return;
+		}
+		response.setContentType("text/plain");
+		List<Module> modules = ModuleManager.getGenericModules();
+		Source source = Source.getInstance("test", macro);
+		Module module = new NamlModule( "test", Collections.singleton(source), Collections.<String>emptySet() );
+		modules.add(module);
+		Program program = Program.getInstance(modules);
+		try {
+			Template template = program.getTemplate("test",
+				BasicNamespace.class, NabbleNamespace.class, ServletNamespace.class
+			);
+			ModuleManager.run( template, out, Collections.<String,Object>emptyMap(),
+				new BasicNamespace(template), new NabbleNamespace(site), new ServletNamespace(request, response)
+			);
+		} catch(CompileException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/TestMacro.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,84 @@
+<%
+package nabble.view.web.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import fschmidt.html.Html;
+import fschmidt.util.servlet.JtpContext;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.Program;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.compiler.CompileException;
+import nabble.naml.compiler.Source;
+import nabble.model.Site;
+import nabble.model.ModelHome;
+import nabble.view.lib.Jtp;
+import nabble.view.web.template.NabbleNamespace;
+import nabble.view.web.template.ServletNamespace;
+import nabble.view.web.template.NodeNamespace;
+import nabble.modules.ModuleManager;
+import nabble.modules.NamlModule;
+import nabble.naml.compiler.Module;
+
+
+public final class TestMacro extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		Site site = Jtp.getSiteNotNull(request);
+		final String macro = request.getParameter("macro");
+		if( macro==null ) {
+			%>
+			<html>
+			<head>
+			<title>Test Macro</title>
+			</head>
+			<body>
+			<form method="post">
+			<p>Macro:<br/>
+			<textarea name="macro" rows="30" cols="90" wrap="off">
+			<macro name="test">
+
+			</macro>
+			</textarea>
+			</macro>
+			<p><input type="submit" value="run macro"></p>
+			</form>
+			</body>
+			</html>
+			<%
+			return;
+		}
+		response.setContentType("text/plain");
+		List<Module> modules = ModuleManager.getGenericModules();
+		Source source = Source.getInstance("test", macro);
+		Module module = new NamlModule( "test", Collections.singleton(source), Collections.<String>emptySet() );
+		modules.add(module);
+		Program program = Program.getInstance(modules);
+		try {
+			Template template = program.getTemplate("test",
+				BasicNamespace.class, NabbleNamespace.class, ServletNamespace.class
+			);
+			ModuleManager.run( template, out, Collections.<String,Object>emptyMap(),
+				new BasicNamespace(template), new NabbleNamespace(site), new ServletNamespace(request, response)
+			);
+		} catch(CompileException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/UploadMbox.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,50 @@
+
+package nabble.view.web.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+
+public final class UploadMbox extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+        MailingList mailingList = forum.getMailingList();
+        String listName = (mailingList == null) ? null : mailingList.getListName();
+        boolean hasListNameSet = listName != null && !"".equals(listName);
+		
+		out.print( "\r\n<html>\r\n<head>\r\n" );
+
+		Shared.title(request,response,"upload mbox file");
+		
+		out.print( "\r\n</head>\r\n<body onload='document.theForm.image.focus();'>\r\n<h2>upload mbox file to " );
+		out.print( (forum.getSubjectHtml()) );
+		out.print( "</h2>\r\n<p>\r\n<form name=\"theForm\" action=\"UploadMbox2.jtp\" method=\"POST\" enctype=\"multipart/form-data\">\r\n<input type=\"hidden\" name=\"forum\" value=\"" );
+		out.print( (forum.getId()) );
+		out.print( "\" />\r\n        " );
+
+            if(!hasListNameSet) {
+            
+		out.print( "\r\n            <font color=\"red\">\r\n            <h3>Mailing list archive does not have ListName set yet!</h3>\r\n            </font>\r\n            " );
+
+            }
+        
+		out.print( "\r\n<p>\r\nMbox File to Upload:\r\n<input name=\"mbox\" type=\"file\" size=\"40\" />\r\n</p>\r\n<p>\r\nor download Mbox from url:\r\n<input name=\"mboxurl\" type=\"text\" size=\"100\" />\r\n</p>\r\n<p>\r\nor read from " );
+		out.print( (getInitParameter("exportDir")) );
+		out.print( "<input name=\"mboxfile\" type=\"text\" size=\"40\" />\r\n</p>\r\n<p>Mail errors to: <input name=\"mailErrorsTo\" size=\"30\" /></p>\r\n<p>Max errors: <input name=\"maxErrors\" value=\"10\" /></p>\r\n<p><input type=\"checkbox\" name=\"runRethread\" value=\"x\" /> rethread after import</p>\r\n<p><input type=\"submit\" value=\"upload mbox file\" /></p>\r\n</form>\r\n</p>\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/UploadMbox.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,70 @@
+<%
+package nabble.view.web.tools;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import nabble.model.MailingList;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+
+public final class UploadMbox extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("forum")));
+        MailingList mailingList = forum.getMailingList();
+        String listName = (mailingList == null) ? null : mailingList.getListName();
+        boolean hasListNameSet = listName != null && !"".equals(listName);
+		%>
+		<html>
+		<head>
+		<%
+		Shared.title(request,response,"upload mbox file");
+		%>
+		</head>
+		<body onload='document.theForm.image.focus();'>
+		<h2>upload mbox file to <%=forum.getSubjectHtml()%></h2>
+		<p>
+		<form name="theForm" action="UploadMbox2.jtp" method="POST" enctype="multipart/form-data">
+		<input type="hidden" name="forum" value="<%=forum.getId()%>" />
+        <%
+            if(!hasListNameSet) {
+            %>
+            <font color="red">
+            <h3>Mailing list archive does not have ListName set yet!</h3>
+            </font>
+            <%
+            }
+        %>
+		<p>
+		Mbox File to Upload:
+		<input name="mbox" type="file" size="40" />
+		</p>
+		<p>
+		or download Mbox from url:
+		<input name="mboxurl" type="text" size="100" />
+		</p>
+		<p>
+		or read from <%=getInitParameter("exportDir")%><input name="mboxfile" type="text" size="40" />
+		</p>
+		<p>Mail errors to: <input name="mailErrorsTo" size="30" /></p>
+		<p>Max errors: <input name="maxErrors" value="10" /></p>
+		<p><input type="checkbox" name="runRethread" value="x" /> rethread after import</p>
+		<p><input type="submit" value="upload mbox file" /></p>
+		</form>
+		</p>
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/UploadMbox2.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,130 @@
+
+package nabble.view.web.tools;
+
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.MailingList;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Executors;
+import nabble.model.Node;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.Date;
+import java.util.Map;
+
+
+public final class UploadMbox2 extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(UploadMbox2.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		final Map<String,FileItem> map;
+		try {
+			map = Jtp.getFileItems(request);
+		} catch(FileUploadException e) {
+			throw new RuntimeException(e);
+		}
+		final Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(map.get("forum").getString()));
+		final String mailErrorsTo = map.get("mailErrorsTo").getString();
+		final int maxErrors = Integer.parseInt(map.get("maxErrors").getString());
+		final boolean runRethread = map.get("runRethread") != null;
+		final String exportDir = getInitParameter("exportDir");
+
+		Executors.executeNow(new Runnable(){public void run() {
+			try {
+				StringBuilder buf = new StringBuilder();
+				InputStream in;
+				if (map.get("mbox").getName()!=null && map.get("mbox").getName().length()>0) {
+					in = map.get("mbox").getInputStream();
+				} else if (map.get("mboxurl").getString()!=null && map.get("mboxurl").getString().length()>0) {
+					in = new URL(map.get("mboxurl").getString()).openStream();
+				} else if (map.get("mboxfile").getString()!=null && map.get("mboxfile").getString().length()>0) {
+					in = new FileInputStream(new File(exportDir+map.get("mboxfile").getString()));
+				} else throw new RuntimeException("no mbox file or url defined");
+				File file = File.createTempFile("mbox",null);
+				try {
+					OutputStream out2 = new FileOutputStream(file);
+					IoUtils.copyAll(in,out2);
+					out2.close();
+					in.close();
+					try {
+						MailingList.ImportResult ir = forum.getMailingList().importMbox(file,mailErrorsTo,maxErrors);
+						buf.append("Results from mbox import to forum "+forum.getSubject()+":\n");
+						buf.append("imported "+ir.getImported()+" messages\n");
+						buf.append("errors "+ir.getErrors()+"\n");
+					} catch (ModelException e) {
+						buf.append("Import error:\n");
+						buf.append(e.getMessage());
+						logger.error("",e);
+					}
+					Mail mail = MailHome.newMail();
+					MailAddress to = new MailAddress(mailErrorsTo);
+					mail.setFrom(to);
+					mail.setTo(to);
+					mail.setSentDate(new Date());
+					mail.setContent(new PlainTextContent(buf.toString()));
+					mail.setSubject("mbox import");
+					ModelHome.send(mail);
+
+				} finally {
+					file.delete();
+				}
+			} catch (IOException e) {
+				throw new RuntimeException(e);
+			}
+
+			if(runRethread) {
+				StringBuilder buf = new StringBuilder();
+				buf.append("Rethread procedure scheduled after mbox import for forum '" + forum.getSubject() + "' has finished\n");
+				forum.getMailingList().rethread();
+				Mail mail = MailHome.newMail();
+				MailAddress to = new MailAddress(mailErrorsTo);
+				mail.setFrom(to);
+				mail.setTo(to);
+				mail.setSentDate(new Date());
+				mail.setContent(new PlainTextContent(buf.toString()));
+				mail.setSubject("Rethreading finished");
+				ModelHome.send(mail);
+			}
+
+		}});
+
+		
+		out.print( "\r\n<html>\r\n<head>\r\n" );
+
+		Shared.title(request,response,"uploaded mbox file");
+		
+		out.print( "\r\n</head>\r\n<body>\r\n<h2>uploading mbox file to " );
+		out.print( (forum.getSubjectHtml()) );
+		out.print( "</h2>\r\n<p>Results will be emailed to " );
+		out.print( (mailErrorsTo) );
+		out.print( " after the import has completed.\r\n<p>return to forum: " );
+		out.print( (Jtp.link(forum)) );
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/UploadMbox2.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,131 @@
+<%
+package nabble.view.web.tools;
+
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.MailingList;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Executors;
+import nabble.model.Node;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.Date;
+import java.util.Map;
+
+
+public final class UploadMbox2 extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(UploadMbox2.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		final Map<String,FileItem> map;
+		try {
+			map = Jtp.getFileItems(request);
+		} catch(FileUploadException e) {
+			throw new RuntimeException(e);
+		}
+		final Node forum = Jtp.getSiteNotNull(request).getNode(Long.parseLong(map.get("forum").getString()));
+		final String mailErrorsTo = map.get("mailErrorsTo").getString();
+		final int maxErrors = Integer.parseInt(map.get("maxErrors").getString());
+		final boolean runRethread = map.get("runRethread") != null;
+		final String exportDir = getInitParameter("exportDir");
+
+		Executors.executeNow(new Runnable(){public void run() {
+			try {
+				StringBuilder buf = new StringBuilder();
+				InputStream in;
+				if (map.get("mbox").getName()!=null && map.get("mbox").getName().length()>0) {
+					in = map.get("mbox").getInputStream();
+				} else if (map.get("mboxurl").getString()!=null && map.get("mboxurl").getString().length()>0) {
+					in = new URL(map.get("mboxurl").getString()).openStream();
+				} else if (map.get("mboxfile").getString()!=null && map.get("mboxfile").getString().length()>0) {
+					in = new FileInputStream(new File(exportDir+map.get("mboxfile").getString()));
+				} else throw new RuntimeException("no mbox file or url defined");
+				File file = File.createTempFile("mbox",null);
+				try {
+					OutputStream out2 = new FileOutputStream(file);
+					IoUtils.copyAll(in,out2);
+					out2.close();
+					in.close();
+					try {
+						MailingList.ImportResult ir = forum.getMailingList().importMbox(file,mailErrorsTo,maxErrors);
+						buf.append("Results from mbox import to forum "+forum.getSubject()+":\n");
+						buf.append("imported "+ir.getImported()+" messages\n");
+						buf.append("errors "+ir.getErrors()+"\n");
+					} catch (ModelException e) {
+						buf.append("Import error:\n");
+						buf.append(e.getMessage());
+						logger.error("",e);
+					}
+					Mail mail = MailHome.newMail();
+					MailAddress to = new MailAddress(mailErrorsTo);
+					mail.setFrom(to);
+					mail.setTo(to);
+					mail.setSentDate(new Date());
+					mail.setContent(new PlainTextContent(buf.toString()));
+					mail.setSubject("mbox import");
+					ModelHome.send(mail);
+
+				} finally {
+					file.delete();
+				}
+			} catch (IOException e) {
+				throw new RuntimeException(e);
+			}
+
+			if(runRethread) {
+				StringBuilder buf = new StringBuilder();
+				buf.append("Rethread procedure scheduled after mbox import for forum '" + forum.getSubject() + "' has finished\n");
+				forum.getMailingList().rethread();
+				Mail mail = MailHome.newMail();
+				MailAddress to = new MailAddress(mailErrorsTo);
+				mail.setFrom(to);
+				mail.setTo(to);
+				mail.setSentDate(new Date());
+				mail.setContent(new PlainTextContent(buf.toString()));
+				mail.setSubject("Rethreading finished");
+				ModelHome.send(mail);
+			}
+
+		}});
+
+		%>
+		<html>
+		<head>
+		<%
+		Shared.title(request,response,"uploaded mbox file");
+		%>
+		</head>
+		<body>
+		<h2>uploading mbox file to <%=forum.getSubjectHtml()%></h2>
+		<p>Results will be emailed to <%=mailErrorsTo%> after the import has completed.
+		<p>return to forum: <%=Jtp.link(forum)%>
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/run.luan	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1 @@
+return require("luan:http/tools/Run.luan").respond
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/tools/shell.luan	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,14 @@
+local init = [[
+java()
+ModelHome = require "java:nabble.model.ModelHome"
+]]
+
+java()
+local JavaLuan = require "java:luan.modules.JavaLuan"
+JavaLuan.privateAccess = true
+
+local shell = require "luan:http/tools/Shell.luan"
+local Luan = require "luan:Luan.luan"
+local load = Luan.load
+load(init,"<nabble>",shell.env)()
+return shell.respond
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/Advanced.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,43 @@
+
+package nabble.view.web.user;
+
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class Advanced extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to change your settings.",request,response);
+			return;
+		}
+		
+		out.print( "\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n	" );
+ Shared.title(request,response,"Advanced Settings"); 
+		out.print( "\r\n	<script type=\"text/javascript\">\r\n		Nabble.currentStyle = function(t) {\r\n			return t.currentStyle ? t.currentStyle : getComputedStyle(t,null);\r\n		};\r\n\r\n		Nabble.init = function() {\r\n			var dateFmt = Nabble.getCookie(\"date_fmt\");\r\n			if( dateFmt==null )\r\n				dateFmt = \"default\";\r\n			Nabble.get(\"nabble.date_fmt.\"+dateFmt).checked = true;\r\n			var maxWidth = Nabble.getCookie(\"max_width\");\r\n			if( maxWidth!=null )\r\n				Nabble.get(\"nabble.max_width\").value = maxWidth;\r\n			var fontSize = Nabble.getCookie(\"font_size\");\r\n			if( fontSize==null ) {\r\n				var t = Nabble.get('nabble');\r\n				fontSize = Nabble.currentStyle(t).fontSize;\r\n				Nabble.get(\"nabble.font_size_comment\").innerHTML = '(default value)';\r\n			} else {\r\n				Nabble.get(\"nabble.font_size_comment\").innerHTML = '(leave blank to reset to default)';\r\n			}\r\n			Nabble.get(\"nabble.font_size\").value = fontSize;\r\n			Nabble.get(\"nabble-online\").checked = Nabble.getCookie('visible') != null;\r\n		};\r\n\r\n		Nabble.submit = function() {\r\n			var dateFmts = [\"default\",\"euro\",\"tech\"];\r\n			for( var i=0; i<dateFmts.length; i++ ) {\r\n				if( Nabble.get(\"nabble.date_fmt.\"+dateFmts[i]).checked ) {\r\n					var dateFmt = dateFmts[i];\r\n					break;\r\n				}\r\n			}\r\n			if( dateFmt==\"default\" ) {\r\n				Nabble.deleteCookie(\"date_fmt\");\r\n			} else {\r\n				Nabble.setPersistentCookie(\"date_fmt\",dateFmt);\r\n			}\r\n			var maxWidth = Nabble.get(\"nabble.max_width\").value.replace(/ /,\"\");\r\n			if( maxWidth==\"\" ) {\r\n				Nabble.deleteCookie(\"max_width\");\r\n			} else {\r\n				Nabble.setPersistentCookie(\"max_width\",maxWidth);\r\n			}\r\n			var t = Nabble.get('nabble');\r\n			var defaultFontSize = Nabble.currentStyle(t).fontSize;\r\n			var fontSize = Nabble.get(\"nabble.font_size\").value.replace(/ /,\"\");\r\n			if( fontSize==\"\" || fontSize==defaultFontSize ) {\r\n				Nabble.deleteCookie(\"font_size\");\r\n			} else {\r\n				Nabble.setPersistentCookie(\"font_size\",fontSize);\r\n			}\r\n			var invisible = Nabble.get(\"nabble-online\").checked;\r\n			if (invisible)\r\n				Nabble.setPersistentCookie('visible', 'off');\r\n			else\r\n				Nabble.deleteCookie('visible');\r\n\r\n			location = \"Advanced2.jtp\";\r\n		};\r\n	</script>\r\n	<style type=\"text/css\">\r\n		.nabble .advanced-table td {\r\n			padding: 0.5em;\r\n		}\r\n		.field-title {\r\n			text-align: right;\r\n		}\r\n	</style>\r\n</head>\r\n<body>\r\n	" );
+
+			Shared.minHeaderGlobal(request,response);
+			Shared.profileHeading(request,out,user,"Advanced Settings");
+			
+		out.print( "\r\n\r\n<div>\r\n	Configurations on this screen are saved on this computer only.\r\n</div>\r\n\r\n<form action=\"/user/Advanced.jtp\" style=\"display:inline;\">\r\n	<table class=\"advanced-table\" style=\"margin: 1em;border-collapse: collapse;\">\r\n		<tr valign=\"top\">\r\n			<td class=\"second-font field-title weak-color\" style=\"padding-top:.8em\">Use Date Format<br /></td>\r\n			<td>\r\n				<input type=\"radio\" name=\"nabble.date_fmt\" id=\"nabble.date_fmt.default\" /><label for=\"nabble.date_fmt.default\">Aug 11, 2006; 09:06pm</label><br />\r\n				<input type=\"radio\" name=\"nabble.date_fmt\" id=\"nabble.date_fmt.euro\" /><label for=\"nabble.date_fmt.euro\">21:06, 11.Aug.2006</label><br />\r\n				<input type=\"radio\" name=\"nabble.date_fmt\" id=\"nabble.date_fmt.tech\" /><label for=\"nabble.date_fmt.tech\">2006-08-11 21:06</label><br />\r\n			</td>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"second-font field-title weak-color\">Maximum width of messages<br /></td>\r\n			<td>\r\n				<input id=\"nabble.max_width\" size=\"10\" />\r\n				(like 600px or 50em or leave blank for variable width)\r\n			</td>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"second-font field-title weak-color\">Font Size<br /></td>\r\n			<td>\r\n				<input id=\"nabble.font_size\" size=\"10\" />\r\n				<span id=\"nabble.font_size_comment\"></span>\r\n			</td>\r\n		</tr>\r\n		<tr>\r\n			<td class=\"second-font field-title weak-color\">Online Status</td>\r\n			<td>\r\n				<input type=\"checkbox\" id=\"nabble-online\" value=\"y\" />\r\n				<label for=\"nabble-online\">Hide my online indicator (invisible mode)</label>\r\n			</td>\r\n		</tr>\r\n	</table>\r\n\r\n	<input type=\"submit\" value=\"Update Settings\" onclick=\"Nabble.submit(); return false;\" />\r\n	or <a href=\"/template/NamlServlet.jtp?macro=user_profile\">Cancel</a>\r\n</form>\r\n<script type=\"text/javascript\">\r\n	Nabble.init();\r\n</script>\r\n\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/Advanced.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,159 @@
+<%
+package nabble.view.web.user;
+
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class Advanced extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to change your settings.",request,response);
+			return;
+		}
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+		<head>
+			<% Shared.title(request,response,"Advanced Settings"); %>
+			<script type="text/javascript">
+				Nabble.currentStyle = function(t) {
+					return t.currentStyle ? t.currentStyle : getComputedStyle(t,null);
+				};
+
+				Nabble.init = function() {
+					var dateFmt = Nabble.getCookie("date_fmt");
+					if( dateFmt==null )
+						dateFmt = "default";
+					Nabble.get("nabble.date_fmt."+dateFmt).checked = true;
+					var maxWidth = Nabble.getCookie("max_width");
+					if( maxWidth!=null )
+						Nabble.get("nabble.max_width").value = maxWidth;
+					var fontSize = Nabble.getCookie("font_size");
+					if( fontSize==null ) {
+						var t = Nabble.get('nabble');
+						fontSize = Nabble.currentStyle(t).fontSize;
+						Nabble.get("nabble.font_size_comment").innerHTML = '(default value)';
+					} else {
+						Nabble.get("nabble.font_size_comment").innerHTML = '(leave blank to reset to default)';
+					}
+					Nabble.get("nabble.font_size").value = fontSize;
+					Nabble.get("nabble-online").checked = Nabble.getCookie('visible') != null;
+				};
+
+				Nabble.submit = function() {
+					var dateFmts = ["default","euro","tech"];
+					for( var i=0; i<dateFmts.length; i++ ) {
+						if( Nabble.get("nabble.date_fmt."+dateFmts[i]).checked ) {
+							var dateFmt = dateFmts[i];
+							break;
+						}
+					}
+					if( dateFmt=="default" ) {
+						Nabble.deleteCookie("date_fmt");
+					} else {
+						Nabble.setPersistentCookie("date_fmt",dateFmt);
+					}
+					var maxWidth = Nabble.get("nabble.max_width").value.replace(/ /,"");
+					if( maxWidth=="" ) {
+						Nabble.deleteCookie("max_width");
+					} else {
+						Nabble.setPersistentCookie("max_width",maxWidth);
+					}
+					var t = Nabble.get('nabble');
+					var defaultFontSize = Nabble.currentStyle(t).fontSize;
+					var fontSize = Nabble.get("nabble.font_size").value.replace(/ /,"");
+					if( fontSize=="" || fontSize==defaultFontSize ) {
+						Nabble.deleteCookie("font_size");
+					} else {
+						Nabble.setPersistentCookie("font_size",fontSize);
+					}
+					var invisible = Nabble.get("nabble-online").checked;
+					if (invisible)
+						Nabble.setPersistentCookie('visible', 'off');
+					else
+						Nabble.deleteCookie('visible');
+
+					location = "Advanced2.jtp";
+				};
+			</script>
+			<style type="text/css">
+				.nabble .advanced-table td {
+					padding: 0.5em;
+				}
+				.field-title {
+					text-align: right;
+				}
+			</style>
+		</head>
+		<body>
+			<%
+			Shared.minHeaderGlobal(request,response);
+			Shared.profileHeading(request,out,user,"Advanced Settings");
+			%>
+
+			<div>
+				Configurations on this screen are saved on this computer only.
+			</div>
+
+			<form action="/user/Advanced.jtp" style="display:inline;">
+				<table class="advanced-table" style="margin: 1em;border-collapse: collapse;">
+					<tr valign="top">
+						<td class="second-font field-title weak-color" style="padding-top:.8em">Use Date Format<br /></td>
+						<td>
+							<input type="radio" name="nabble.date_fmt" id="nabble.date_fmt.default" /><label for="nabble.date_fmt.default">Aug 11, 2006; 09:06pm</label><br />
+							<input type="radio" name="nabble.date_fmt" id="nabble.date_fmt.euro" /><label for="nabble.date_fmt.euro">21:06, 11.Aug.2006</label><br />
+							<input type="radio" name="nabble.date_fmt" id="nabble.date_fmt.tech" /><label for="nabble.date_fmt.tech">2006-08-11 21:06</label><br />
+						</td>
+					</tr>
+					<tr>
+						<td class="second-font field-title weak-color">Maximum width of messages<br /></td>
+						<td>
+							<input id="nabble.max_width" size="10" />
+							(like 600px or 50em or leave blank for variable width)
+						</td>
+					</tr>
+					<tr>
+						<td class="second-font field-title weak-color">Font Size<br /></td>
+						<td>
+							<input id="nabble.font_size" size="10" />
+							<span id="nabble.font_size_comment"></span>
+						</td>
+					</tr>
+					<tr>
+						<td class="second-font field-title weak-color">Online Status</td>
+						<td>
+							<input type="checkbox" id="nabble-online" value="y" />
+							<label for="nabble-online">Hide my online indicator (invisible mode)</label>
+						</td>
+					</tr>
+				</table>
+
+				<input type="submit" value="Update Settings" onclick="Nabble.submit(); return false;" />
+				or <a href="/template/NamlServlet.jtp?macro=user_profile">Cancel</a>
+			</form>
+			<script type="text/javascript">
+				Nabble.init();
+			</script>
+
+			<% Shared.footer(request,response); %>
+			<% Shared.analytics(request,response); %>
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/Advanced2.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,43 @@
+
+package nabble.view.web.user;
+
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class Advanced2 extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to change your settings.",request,response);
+			return;
+		}
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request,response,"Advanced Settings"); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+
+				Shared.minHeaderGlobal(request, response);
+				Shared.profileHeading(request, out, user, "Advanced Settings");
+				
+		out.print( "\r\n<br />\r\n\r\n<table class=\"profile-table\">\r\n	<tr>\r\n		<td>\r\n		<p>Your settings have been updated.<br /><br /></p>\r\n\r\n		<p><a href=\"/template/NamlServlet.jtp?macro=user_profile\">Return to menu</a></p>\r\n		</td>\r\n	</tr>\r\n</table>\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/Advanced2.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,55 @@
+<%
+package nabble.view.web.user;
+
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class Advanced2 extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to change your settings.",request,response);
+			return;
+		}
+		%>
+		<html>
+			<head>
+				<% Shared.title(request,response,"Advanced Settings"); %>
+			</head>
+			<body>
+				<%
+				Shared.minHeaderGlobal(request, response);
+				Shared.profileHeading(request, out, user, "Advanced Settings");
+				%>
+				<br />
+
+				<table class="profile-table">
+					<tr>
+						<td>
+						<p>Your settings have been updated.<br /><br /></p>
+
+						<p><a href="/template/NamlServlet.jtp?macro=user_profile">Return to menu</a></p>
+						</td>
+					</tr>
+				</table>
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ChangeAvatar.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,46 @@
+
+package nabble.view.web.user;
+
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class ChangeAvatar extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if (user==null) {
+			Jtp.login("You must login to change your email.",request,response);
+			return;
+		}
+
+		
+		out.print( "\r\n<html>\r\n<head>\r\n	" );
+ Shared.title(request,response,"Change Avatar"); 
+		out.print( "\r\n	<script type=\"text/javascript\">\r\n		function deleteAvatar() {\r\n			if (!confirm('Do you really want to reset your avatar?'))\r\n				return;\r\n			var newLocation = \"/user/ChangeAvatar2.jtp?action=delete\";\r\n			Nabble.setTop(newLocation);\r\n		};\r\n	</script>\r\n</head>\r\n<body>\r\n	" );
+
+			Shared.minHeaderGlobal(request,response);
+			Shared.profileHeading(request,out,user,"Change Your Picture");
+			
+		out.print( "\r\n<br/>\r\n<form action=\"/user/ChangeAvatar2.jtp\" method=\"POST\" enctype=\"multipart/form-data\">\r\n	<table cellspacing=\"3\">\r\n		<tr valign=\"top\">\r\n			<td>\r\n				<img class=\"avatar light-border-color\" src=\"" );
+		out.print( (Shared.getAvatarImageURL(user, false)) );
+		out.print( "\"/><br/>\r\n				<div style=\"text-align:center\"><a href=\"javascript: deleteAvatar();\">Reset Picture</a></div>\r\n			</td>\r\n			<td>\r\n				<span class=\"form-label\">Choose a file to upload:</span><br/>\r\n				<input name=\"image\" id=\"image\" type=\"file\" size=\"35\" /><br/>\r\n				<span style=\"font-size:80%\"><strong>Important:</strong> The file size cannot exceed 4 Mb and the image must be at least 100 x 100 px.</span><br/><br/>\r\n				<input type=\"submit\" value=\"Upload Image\">\r\n				or <a href=\"/template/NamlServlet.jtp?macro=user_profile\">Cancel</a>\r\n			</td>\r\n		</tr>\r\n	</table>\r\n</form>\r\n\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ChangeAvatar.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,72 @@
+<%
+package nabble.view.web.user;
+
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class ChangeAvatar extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if (user==null) {
+			Jtp.login("You must login to change your email.",request,response);
+			return;
+		}
+
+		%>
+		<html>
+		<head>
+			<% Shared.title(request,response,"Change Avatar"); %>
+			<script type="text/javascript">
+				function deleteAvatar() {
+					if (!confirm('Do you really want to reset your avatar?'))
+						return;
+					var newLocation = "/user/ChangeAvatar2.jtp?action=delete";
+					Nabble.setTop(newLocation);
+				};
+			</script>
+		</head>
+		<body>
+			<%
+			Shared.minHeaderGlobal(request,response);
+			Shared.profileHeading(request,out,user,"Change Your Picture");
+			%>
+			<br/>
+			<form action="/user/ChangeAvatar2.jtp" method="POST" enctype="multipart/form-data">
+				<table cellspacing="3">
+					<tr valign="top">
+						<td>
+							<img class="avatar light-border-color" src="<%=Shared.getAvatarImageURL(user, false)%>"/><br/>
+							<div style="text-align:center"><a href="javascript: deleteAvatar();">Reset Picture</a></div>
+						</td>
+						<td>
+							<span class="form-label">Choose a file to upload:</span><br/>
+							<input name="image" id="image" type="file" size="35" /><br/>
+							<span style="font-size:80%"><strong>Important:</strong> The file size cannot exceed 4 Mb and the image must be at least 100 x 100 px.</span><br/><br/>
+							<input type="submit" value="Upload Image">
+							or <a href="/template/NamlServlet.jtp?macro=user_profile">Cancel</a>
+						</td>
+					</tr>
+				</table>
+			</form>
+
+			<% Shared.footer(request,response); %>
+			<% Shared.analytics(request,response); %>
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ChangeAvatar2.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,252 @@
+
+package nabble.view.web.user;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.java.ImageUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.FileUpload;
+import nabble.model.Init;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.imageio.ImageIO;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.image.BufferedImage;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Random;
+
+
+public final class ChangeAvatar2 extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(ChangeAvatar2.class);
+
+	private static final long SIZE_LIMIT = 4194304; // 4 Mb
+
+	private void handleError(HttpServletRequest request, String errorMessage, PrintWriter out) {
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.loadJavascript(request, out); 
+		out.print( "\r\n		<script type=\"text/javascript\">\r\n			function say() {\r\n				alert(\"" );
+		out.print( (HtmlUtils.javascriptStringEncode(errorMessage)) );
+		out.print( "\");\r\n				location.replace(\"" );
+		out.print( (request.getContextPath()) );
+		out.print( "/user/ChangeAvatar.jtp\");\r\n			};\r\n		</script>\r\n	</head>\r\n	<body onload=\"say()\"></body>\r\n</html>\r\n" );
+
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setTimeLimit(request,0L);
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		User user = Jtp.getUser(request, response);
+		if (user == null) {
+			Jtp.login("You must login to change your avatar.", request, response);
+			return;
+		}
+
+		String errorMessage = null;
+
+		String action = request.getParameter("action");
+		if ("crop".equals(action)) {
+			String imageName = request.getParameter("image");
+			int x = Jtp.getInt(request, "crop_x");
+			int y = Jtp.getInt(request, "crop_y");
+			int width = Jtp.getInt(request, "crop_width");
+			int height = Jtp.getInt(request, "crop_height");
+			try {
+				Message.Source srcTemp = Message.SourceType.getType('t').getSource(user.getSite(),user.getId());
+				InputStream in = FileUpload.getFileContent(srcTemp, imageName);
+				if (in == null) {
+					handleError(request, "Error uploading image. Please try again.", out);
+					return;
+				}				
+				BufferedImage originalImage = ImageIO.read(in);
+				in.close();
+				if (originalImage.getWidth() < 100 || originalImage.getHeight() < 100) {
+					handleError(request, "The width and height must be greater than 100 pixels.", out);
+					return;
+				}
+
+				BufferedImage croppedImage100 = ImageUtils.cropImage(originalImage, x, y, width, height);
+				croppedImage100 = ImageUtils.getThumbnail(croppedImage100, 100, 100);
+
+				BufferedImage croppedImage24 = ImageUtils.blurImage(croppedImage100);
+				croppedImage24 = ImageUtils.resizeImage(croppedImage24, 24, 24);
+
+				user.saveAvatar(croppedImage24,croppedImage100);
+				FileUpload.deleteFile(imageName, srcTemp);
+			} catch (ModelException e) {
+				logger.error(toString(), e);
+				errorMessage = e.getMessage();
+			}
+
+			if (errorMessage == null) {
+				response.sendRedirect("/user/ChangeAvatar2$ReloadAvatars.jtp");
+				return;
+			} else {
+				handleError(request, errorMessage, out);
+				return;
+			}
+		} else if ("delete".equals(action)) {
+			user.deleteAvatar();
+			response.sendRedirect("/user/ChangeAvatar2$ReloadAvatars.jtp");
+			return;
+		}
+
+		int imageWidth = 0;
+		int imageHeight = 0;
+		String fileName = "_user" + user.getId() + ".png";
+		try {
+			final Map<String, FileItem> map;
+			try {
+				map = Jtp.getFileItems(request);
+			} catch (FileUploadException e) {
+				logger.warn("",e);
+				throw ModelException.newInstance("upload_avatar_failed","upload failed - " + e.getMessage());
+			}
+			FileItem fi = map.get("image");
+
+			if (fi == null) {
+				handleError(request, "Image upload failed.", out);
+				return;
+			} else if (fi.getSize() > SIZE_LIMIT) {
+				handleError(request, "The file you uploaded is too big. Please upload a smaller image (less than 4Mb).", out);
+				return;
+			}
+
+			InputStream in = fi.getInputStream();
+			BufferedImage image;
+			try {
+				image = ImageIO.read(in);
+			} catch (javax.imageio.IIOException e) {
+				handleError(request, "The uploaded image is broken.", out);
+				return;
+			} finally {
+				in.close();
+			}
+			if (image == null) {
+				handleError(request, "Please upload the image again.", out);
+				return;
+			} else if (image.getWidth() < 100 || image.getHeight() < 100) {
+				handleError(request, "The width and height must be greater than 100 pixels.", out);
+				return;
+			} else if (image.getWidth() > 800 || image.getHeight() > 800) {
+				try {
+					image = ImageUtils.getThumbnail(image, 800, 800);
+				} catch (RuntimeException e) {
+					// This catch block will be improved later when I discover the exact line that throws
+					// a RuntimeException in ImageUtils [Hugo - April/2010]
+					if (fi.getSize() <= FileUpload.MAX_IMAGE_SIZE) {
+						if( Init.tempDir == null ) {
+							logger.info("Init.tempDir == null");
+						} else {
+							FileOutputStream fos = new FileOutputStream(Init.tempDir + FileUpload.getName(fi));
+							fos.write(fi.get());
+							fos.close();
+						}
+					}
+					handleError(request, "Unable to handle this image.", out);
+					return;
+				}
+			}
+
+			imageWidth = image.getWidth();
+			imageHeight = image.getHeight();
+
+			Message.Source tempSrc = Message.SourceType.getType('t').getSource(user.getSite(),user.getId());
+			FileUpload.saveImage(image, fileName, tempSrc);
+		} catch (ModelException e) {
+			errorMessage = e.getMessage();
+			logger.warn("image upload failed", e);
+		}
+
+		if (errorMessage != null) {
+			handleError(request, errorMessage, out);
+			return;
+		}
+		
+		out.print( "\r\n<html>\r\n<head>\r\n	" );
+ Shared.title(request,response,"Change Avatar"); 
+		out.print( "\r\n	<style type=\"text/css\">\r\n		#imageContainer{\r\n			margin:15px;\r\n			left:0px;\r\n			top:0px;\r\n			position:relative;\r\n		}\r\n\r\n		.crop_transparentDiv{\r\n			background-color:#FFF;\r\n			filter:alpha(opacity=80);\r\n			-khtml-opacity: 0.8;\r\n			-moz-opacity: 0.8;\r\n			opacity:0.8;\r\n			position:absolute;\r\n		}\r\n		.crop_dottedDiv{\r\n			position:absolute;\r\n			border:1px dotted red;\r\n			z-index:10000;\r\n		}\r\n\r\n		.crop_dottedDiv div{\r\n			filter:alpha(opacity=0);\r\n			opacity:0;\r\n			-khtml-opacity: 0;\r\n			-moz-opacity: 0;\r\n			width:100%;\r\n			height:100%;\r\n			background-color:#FFF;\r\n		}\r\n	</style>\r\n	<script type=\"text/javascript\">\r\n		var crop_script_alwaysPreserveAspectRatio = true; // Always preserve aspect ratio\r\n		var crop_script_fixedRatio = 1; // Width relative to height 2 = ratio 2:1\r\n\r\n		var cropToolBorderWidth = 1; // Width of dotted border around crop rectangle\r\n		var smallSquareWidth = 7; // Size of small squares used to resize crop rectangle\r\n\r\n		// Size of image shown in crop tool\r\n		var crop_imageWidth = " );
+		out.print( (imageWidth) );
+		out.print( ";\r\n		var crop_imageHeight = " );
+		out.print( (imageHeight) );
+		out.print( ";\r\n\r\n		// Size of original image\r\n		var crop_originalImageWidth = crop_imageWidth;\r\n		var crop_originalImageHeight = crop_imageHeight;\r\n\r\n		var crop_minimumPercent = 10; // Minimum percent - resize\r\n		var crop_maximumPercent = 100; // Maximum percent -resize\r\n\r\n		var crop_minimumWidthHeight = 100; // Minimum width and height of crop area\r\n\r\n		var updateFormValuesAsYouDrag = true; // This variable indicates if form values should be updated as we drag. This process could make the script work a little bit slow. That's why this option is set as a variable.\r\n		if(!document.all)updateFormValuesAsYouDrag = false; // Enable this feature only in IE\r\n	</script>\r\n	<script src=\"" );
+		out.print( (context) );
+		out.print( "/util/image-crop.js\" type=\"text/javascript\"></script>\r\n</head>\r\n<body>\r\n" );
+
+		Shared.minHeaderGlobal(request, response);
+		Shared.profileHeading(request,out,user,"Change Avatar - Crop Image");
+		
+		out.print( "\r\n<b>Please select a square region on the image below.</b><br/>\r\nYou can drag the red handles to resize the square or drag the whole selected area.\r\n<br/><br/>\r\n<form action=\"/user/ChangeAvatar2.jtp\" method=\"POST\" accept-charset=\"UTF-8\">\r\n	<input type=\"hidden\" name=\"action\" value=\"crop\">\r\n	<input type=\"hidden\" name=\"image\" value=\"" );
+		out.print( (fileName) );
+		out.print( "\">\r\n	<input type=\"hidden\" name=\"crop_x\" id=\"input_crop_x\">\r\n	<input type=\"hidden\" name=\"crop_y\" id=\"input_crop_y\">\r\n	<input type=\"hidden\" name=\"crop_width\" id=\"input_crop_width\">\r\n	<input type=\"hidden\" name=\"crop_height\" id=\"input_crop_height\">\r\n	<input type=\"hidden\" name=\"crop_percent_size\" id=\"crop_percent_size\">\r\n	<input type=\"submit\" value=\"Crop Image\">\r\n</form>\r\n<div class=\"crop_content\">\r\n	<div id=\"imageContainer\">\r\n		" );
+
+					// Here I create a random number that is appended to the end
+					// of the image URL. This prevents the browser from showing an
+					// old cached image.
+					Random random = new Random();
+					int any = random.nextInt(1000);
+				
+		out.print( "\r\n<img src=\"/file/t" );
+		out.print( (user.getId()) );
+		out.print( "/" );
+		out.print( (fileName) );
+		out.print( "?" );
+		out.print( (any) );
+		out.print( "\"/>\r\n</div>\r\n</div>\r\n\r\n<script type=\"text/javascript\">\r\ninit_imageCrop('http://" );
+		out.print( (request.getHeader("host")) );
+		out.print( "');\r\n\r\nif (Nabble.isEmbedded) {\r\n$(window).load(Nabble.resizeFrames);\r\n}\r\n</script>\r\n\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+
+	public static class ReloadAvatars extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			/*
+			This servlet is a tricky way to force the browser to reload the user avatars.
+			Since we don't specify cache headers for images, browsers cache them using
+			internal algorithms. The only way to update those images is by refreshing the page
+			either by pressing F5 or using location.reload(true).
+			*/
+			PrintWriter out = response.getWriter();
+			User user = Jtp.getUser(request, response);
+			if (user == null)
+				return;
+			Jtp.dontCache(response);
+			
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.loadJavascript(request, out); 
+		out.print( "\r\n		<script type=\"text/javascript\">\r\n			function init() {\r\n				var done = Nabble.getCookie('done');\r\n				if (done) {\r\n					Nabble.deleteCookie('done');\r\n					location = '/template/NamlServlet.jtp?macro=user_profile';\r\n				} else {\r\n					Nabble.setCookie('done','y');\r\n					location.reload(true);\r\n				}\r\n			};\r\n		</script>\r\n	</head>\r\n	<body onload=\"init()\">\r\n		<div style=\"display:none\">\r\n			<img src=\"" );
+		out.print( (Shared.getAvatarImageURL(user, false)) );
+		out.print( "\"/>\r\n			<img src=\"" );
+		out.print( (Shared.getAvatarImageURL(user, true)) );
+		out.print( "\"/>\r\n		</div>\r\n	</body>\r\n</html>\r\n" );
+
+		}
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ChangeAvatar2.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,340 @@
+<%
+package nabble.view.web.user;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.java.ImageUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.model.FileUpload;
+import nabble.model.Init;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.imageio.ImageIO;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.image.BufferedImage;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Random;
+
+
+public final class ChangeAvatar2 extends HttpServlet {
+
+	private static final Logger logger = LoggerFactory.getLogger(ChangeAvatar2.class);
+
+	private static final long SIZE_LIMIT = 4194304; // 4 Mb
+
+	private void handleError(HttpServletRequest request, String errorMessage, PrintWriter out) {
+		%>
+		<html>
+			<head>
+				<% Shared.loadJavascript(request, out); %>
+				<script type="text/javascript">
+					function say() {
+						alert("<%=HtmlUtils.javascriptStringEncode(errorMessage)%>");
+						location.replace("<%=request.getContextPath()%>/user/ChangeAvatar.jtp");
+					};
+				</script>
+			</head>
+			<body onload="say()"></body>
+		</html>
+		<%
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setTimeLimit(request,0L);
+		PrintWriter out = response.getWriter();
+		String context = request.getContextPath();
+		User user = Jtp.getUser(request, response);
+		if (user == null) {
+			Jtp.login("You must login to change your avatar.", request, response);
+			return;
+		}
+
+		String errorMessage = null;
+
+		String action = request.getParameter("action");
+		if ("crop".equals(action)) {
+			String imageName = request.getParameter("image");
+			int x = Jtp.getInt(request, "crop_x");
+			int y = Jtp.getInt(request, "crop_y");
+			int width = Jtp.getInt(request, "crop_width");
+			int height = Jtp.getInt(request, "crop_height");
+			try {
+				Message.Source srcTemp = Message.SourceType.getType('t').getSource(user.getSite(),user.getId());
+				InputStream in = FileUpload.getFileContent(srcTemp, imageName);
+				if (in == null) {
+					handleError(request, "Error uploading image. Please try again.", out);
+					return;
+				}				
+				BufferedImage originalImage = ImageIO.read(in);
+				in.close();
+				if (originalImage.getWidth() < 100 || originalImage.getHeight() < 100) {
+					handleError(request, "The width and height must be greater than 100 pixels.", out);
+					return;
+				}
+
+				BufferedImage croppedImage100 = ImageUtils.cropImage(originalImage, x, y, width, height);
+				croppedImage100 = ImageUtils.getThumbnail(croppedImage100, 100, 100);
+
+				BufferedImage croppedImage24 = ImageUtils.blurImage(croppedImage100);
+				croppedImage24 = ImageUtils.resizeImage(croppedImage24, 24, 24);
+
+				user.saveAvatar(croppedImage24,croppedImage100);
+				FileUpload.deleteFile(imageName, srcTemp);
+			} catch (ModelException e) {
+				logger.error(toString(), e);
+				errorMessage = e.getMessage();
+			}
+
+			if (errorMessage == null) {
+				response.sendRedirect("/user/ChangeAvatar2$ReloadAvatars.jtp");
+				return;
+			} else {
+				handleError(request, errorMessage, out);
+				return;
+			}
+		} else if ("delete".equals(action)) {
+			user.deleteAvatar();
+			response.sendRedirect("/user/ChangeAvatar2$ReloadAvatars.jtp");
+			return;
+		}
+
+		int imageWidth = 0;
+		int imageHeight = 0;
+		String fileName = "_user" + user.getId() + ".png";
+		try {
+			final Map<String, FileItem> map;
+			try {
+				map = Jtp.getFileItems(request);
+			} catch (FileUploadException e) {
+				logger.warn("",e);
+				throw ModelException.newInstance("upload_avatar_failed","upload failed - " + e.getMessage());
+			}
+			FileItem fi = map.get("image");
+
+			if (fi == null) {
+				handleError(request, "Image upload failed.", out);
+				return;
+			} else if (fi.getSize() > SIZE_LIMIT) {
+				handleError(request, "The file you uploaded is too big. Please upload a smaller image (less than 4Mb).", out);
+				return;
+			}
+
+			InputStream in = fi.getInputStream();
+			BufferedImage image;
+			try {
+				image = ImageIO.read(in);
+			} catch (javax.imageio.IIOException e) {
+				handleError(request, "The uploaded image is broken.", out);
+				return;
+			} finally {
+				in.close();
+			}
+			if (image == null) {
+				handleError(request, "Please upload the image again.", out);
+				return;
+			} else if (image.getWidth() < 100 || image.getHeight() < 100) {
+				handleError(request, "The width and height must be greater than 100 pixels.", out);
+				return;
+			} else if (image.getWidth() > 800 || image.getHeight() > 800) {
+				try {
+					image = ImageUtils.getThumbnail(image, 800, 800);
+				} catch (RuntimeException e) {
+					// This catch block will be improved later when I discover the exact line that throws
+					// a RuntimeException in ImageUtils [Hugo - April/2010]
+					if (fi.getSize() <= FileUpload.MAX_IMAGE_SIZE) {
+						if( Init.tempDir == null ) {
+							logger.info("Init.tempDir == null");
+						} else {
+							FileOutputStream fos = new FileOutputStream(Init.tempDir + FileUpload.getName(fi));
+							fos.write(fi.get());
+							fos.close();
+						}
+					}
+					handleError(request, "Unable to handle this image.", out);
+					return;
+				}
+			}
+
+			imageWidth = image.getWidth();
+			imageHeight = image.getHeight();
+
+			Message.Source tempSrc = Message.SourceType.getType('t').getSource(user.getSite(),user.getId());
+			FileUpload.saveImage(image, fileName, tempSrc);
+		} catch (ModelException e) {
+			errorMessage = e.getMessage();
+			logger.warn("image upload failed", e);
+		}
+
+		if (errorMessage != null) {
+			handleError(request, errorMessage, out);
+			return;
+		}
+		%>
+		<html>
+		<head>
+			<% Shared.title(request,response,"Change Avatar"); %>
+			<style type="text/css">
+				#imageContainer{
+					margin:15px;
+					left:0px;
+					top:0px;
+					position:relative;
+				}
+
+				.crop_transparentDiv{
+					background-color:#FFF;
+					filter:alpha(opacity=80);
+					-khtml-opacity: 0.8;
+					-moz-opacity: 0.8;
+					opacity:0.8;
+					position:absolute;
+				}
+				.crop_dottedDiv{
+					position:absolute;
+					border:1px dotted red;
+					z-index:10000;
+				}
+
+				.crop_dottedDiv div{
+					filter:alpha(opacity=0);
+					opacity:0;
+					-khtml-opacity: 0;
+					-moz-opacity: 0;
+					width:100%;
+					height:100%;
+					background-color:#FFF;
+				}
+			</style>
+			<script type="text/javascript">
+				var crop_script_alwaysPreserveAspectRatio = true; // Always preserve aspect ratio
+				var crop_script_fixedRatio = 1; // Width relative to height 2 = ratio 2:1
+
+				var cropToolBorderWidth = 1; // Width of dotted border around crop rectangle
+				var smallSquareWidth = 7; // Size of small squares used to resize crop rectangle
+
+				// Size of image shown in crop tool
+				var crop_imageWidth = <%=imageWidth%>;
+				var crop_imageHeight = <%=imageHeight%>;
+
+				// Size of original image
+				var crop_originalImageWidth = crop_imageWidth;
+				var crop_originalImageHeight = crop_imageHeight;
+
+				var crop_minimumPercent = 10; // Minimum percent - resize
+				var crop_maximumPercent = 100; // Maximum percent -resize
+
+				var crop_minimumWidthHeight = 100; // Minimum width and height of crop area
+
+				var updateFormValuesAsYouDrag = true; // This variable indicates if form values should be updated as we drag. This process could make the script work a little bit slow. That's why this option is set as a variable.
+				if(!document.all)updateFormValuesAsYouDrag = false; // Enable this feature only in IE
+			</script>
+			<script src="<%=context%>/util/image-crop.js" type="text/javascript"></script>
+		</head>
+		<body>
+		<%
+		Shared.minHeaderGlobal(request, response);
+		Shared.profileHeading(request,out,user,"Change Avatar - Crop Image");
+		%>
+		<b>Please select a square region on the image below.</b><br/>
+		You can drag the red handles to resize the square or drag the whole selected area.
+		<br/><br/>
+		<form action="/user/ChangeAvatar2.jtp" method="POST" accept-charset="UTF-8">
+			<input type="hidden" name="action" value="crop">
+			<input type="hidden" name="image" value="<%=fileName%>">
+			<input type="hidden" name="crop_x" id="input_crop_x">
+			<input type="hidden" name="crop_y" id="input_crop_y">
+			<input type="hidden" name="crop_width" id="input_crop_width">
+			<input type="hidden" name="crop_height" id="input_crop_height">
+			<input type="hidden" name="crop_percent_size" id="crop_percent_size">
+			<input type="submit" value="Crop Image">
+		</form>
+		<div class="crop_content">
+			<div id="imageContainer">
+				<%
+					// Here I create a random number that is appended to the end
+					// of the image URL. This prevents the browser from showing an
+					// old cached image.
+					Random random = new Random();
+					int any = random.nextInt(1000);
+				%>
+				<img src="/file/t<%=user.getId()%>/<%=fileName%>?<%=any%>"/>
+			</div>
+		</div>
+
+		<script type="text/javascript">
+			init_imageCrop('http://<%=request.getHeader("host")%>');
+
+			if (Nabble.isEmbedded) {
+				$(window).load(Nabble.resizeFrames);
+			}
+		</script>
+		
+		<% Shared.footer(request,response); %>
+		<% Shared.analytics(request,response); %>
+		</body>
+		</html>
+		<%
+	}
+
+	public static class ReloadAvatars extends HttpServlet {
+
+		protected void service(HttpServletRequest request, HttpServletResponse response)
+				throws ServletException, IOException
+		{
+			/*
+			This servlet is a tricky way to force the browser to reload the user avatars.
+			Since we don't specify cache headers for images, browsers cache them using
+			internal algorithms. The only way to update those images is by refreshing the page
+			either by pressing F5 or using location.reload(true).
+			*/
+			PrintWriter out = response.getWriter();
+			User user = Jtp.getUser(request, response);
+			if (user == null)
+				return;
+			Jtp.dontCache(response);
+			%>
+			<html>
+				<head>
+					<% Shared.loadJavascript(request, out); %>
+					<script type="text/javascript">
+						function init() {
+							var done = Nabble.getCookie('done');
+							if (done) {
+								Nabble.deleteCookie('done');
+								location = '/template/NamlServlet.jtp?macro=user_profile';
+							} else {
+								Nabble.setCookie('done','y');
+								location.reload(true);
+							}
+						};
+					</script>
+				</head>
+				<body onload="init()">
+					<div style="display:none">
+						<img src="<%=Shared.getAvatarImageURL(user, false)%>"/>
+						<img src="<%=Shared.getAvatarImageURL(user, true)%>"/>
+					</div>
+				</body>
+			</html>
+			<%
+		}
+
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ChangeEmail.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,93 @@
+
+package nabble.view.web.user;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.ChangeEmailMail;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class ChangeEmail extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		long userId = Jtp.getLong(request, "user");
+		Site site = Jtp.getSiteNotNull(request);
+		User user = site.getUser(userId);
+
+		User visitor = Jtp.getUser(request,response);
+		boolean isAllowed = user.equals(visitor) || Permissions.isInGroup(visitor, Permissions.ADMINISTRATORS_GROUP);
+		if (visitor == null || !isAllowed) {
+			Jtp.login("You must login to change your email.",request,response);
+			return;
+		}
+		String email = request.getParameter("email");
+		String errorMsg = null;
+
+		if( "Change".equals( request.getParameter("Action") ) && "POST".equals(request.getMethod()) ) {
+			try {
+				email = email.trim();
+				ModelHome.validateEmail(email);
+				if( !email.equalsIgnoreCase(user.getEmail()) && Jtp.getSite(request).getUserFromEmail(email) != null)
+					throw ModelException.newInstance("duplicate_email","Email already in use");
+				user.setProperty("_new_email", email);
+				String url = ServletUtils.getContextURL(request)
+					+ "/user/ChangeEmail3.jtp?email=" + HtmlUtils.urlEncode(email)
+					+ "&user=" + user.getId()
+					+ "&h=" + emailHash(email)
+				;
+				ChangeEmailMail.send(site, user.getName(), user.getEmail(), email, url);
+				response.sendRedirect( "ChangeEmail2.jtp");
+				return;
+			} catch(ModelException e) {
+				errorMsg = e.getMessage();
+			}
+		}
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request,response,"Change Email"); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+
+				Shared.minHeaderGlobal(request,response);
+				Shared.profileHeading(request,out,user,"Change Email");
+
+				Shared.errorMessage(request,response,errorMsg,
+					"Please enter a correct email address and click Change Email."
+				);
+				
+		out.print( "\r\n\r\n<form method=post action=\"" );
+		out.print( (response.encodeURL("ChangeEmail.jtp")) );
+		out.print( "\">\r\n	<input type=hidden name=\"Action\" value=\"Change\">\r\n	<input type=hidden name=\"user\" value=\"" );
+		out.print( (user.getId()) );
+		out.print( "\">\r\n\r\n	<div class=\"second-font field-title\">\r\n		Current Email\r\n	</div>\r\n	<div class=\"weak-color\" style=\"margin-left:1.9em\">\r\n		" );
+		out.print( (user.getEmail()) );
+		out.print( "\r\n	</div>\r\n\r\n	<div class=\"second-font field-title\">\r\n		Change email\r\n	</div>\r\n	<div class=\"weak-color\" style=\"margin-bottom:1em\">\r\n		<input name=\"email\" size=\"30\" value=\"" );
+		out.print( (Jtp.hideNull(email)) );
+		out.print( "\">\r\n	</div>\r\n\r\n	<input type=submit value=\"Change Email\"></input>\r\n	or <a href=\"/template/NamlServlet.jtp?macro=user_profile\">Cancel</a>\r\n   </form>\r\n\r\n" );
+ Shared.footer(request, response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+
+	static int emailHash(String email) {
+		return (email+"jyk.y/$sh%EW4w2333").hashCode();
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ChangeEmail.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,109 @@
+<%
+package nabble.view.web.user;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.ChangeEmailMail;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Permissions;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class ChangeEmail extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		long userId = Jtp.getLong(request, "user");
+		Site site = Jtp.getSiteNotNull(request);
+		User user = site.getUser(userId);
+
+		User visitor = Jtp.getUser(request,response);
+		boolean isAllowed = user.equals(visitor) || Permissions.isInGroup(visitor, Permissions.ADMINISTRATORS_GROUP);
+		if (visitor == null || !isAllowed) {
+			Jtp.login("You must login to change your email.",request,response);
+			return;
+		}
+		String email = request.getParameter("email");
+		String errorMsg = null;
+
+		if( "Change".equals( request.getParameter("Action") ) && "POST".equals(request.getMethod()) ) {
+			try {
+				email = email.trim();
+				ModelHome.validateEmail(email);
+				if( !email.equalsIgnoreCase(user.getEmail()) && Jtp.getSite(request).getUserFromEmail(email) != null)
+					throw ModelException.newInstance("duplicate_email","Email already in use");
+				user.setProperty("_new_email", email);
+				String url = ServletUtils.getContextURL(request)
+					+ "/user/ChangeEmail3.jtp?email=" + HtmlUtils.urlEncode(email)
+					+ "&user=" + user.getId()
+					+ "&h=" + emailHash(email)
+				;
+				ChangeEmailMail.send(site, user.getName(), user.getEmail(), email, url);
+				response.sendRedirect( "ChangeEmail2.jtp");
+				return;
+			} catch(ModelException e) {
+				errorMsg = e.getMessage();
+			}
+		}
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+			<head>
+				<% Shared.title(request,response,"Change Email"); %>
+			</head>
+			<body>
+				<%
+				Shared.minHeaderGlobal(request,response);
+				Shared.profileHeading(request,out,user,"Change Email");
+
+				Shared.errorMessage(request,response,errorMsg,
+					"Please enter a correct email address and click Change Email."
+				);
+				%>
+
+				<form method=post action="<%=response.encodeURL("ChangeEmail.jtp")%>">
+					<input type=hidden name="Action" value="Change">
+					<input type=hidden name="user" value="<%=user.getId()%>">
+
+					<div class="second-font field-title">
+						Current Email
+					</div>
+					<div class="weak-color" style="margin-left:1.9em">
+						<%=user.getEmail()%>
+					</div>
+
+					<div class="second-font field-title">
+						Change email
+					</div>
+					<div class="weak-color" style="margin-bottom:1em">
+						<input name="email" size="30" value="<%=Jtp.hideNull(email)%>">
+					</div>
+
+					<input type=submit value="Change Email"></input>
+					or <a href="/template/NamlServlet.jtp?macro=user_profile">Cancel</a>
+			   </form>
+
+				<% Shared.footer(request, response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+
+	static int emailHash(String email) {
+		return (email+"jyk.y/$sh%EW4w2333").hashCode();
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ChangeEmail2.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,42 @@
+
+package nabble.view.web.user;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.model.User;
+
+
+public final class ChangeEmail2 extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to change your email.",request,response);
+			return;
+		}
+		
+		out.print( "\r\n<html>\r\n<head>\r\n" );
+ Shared.title(request,response,"Changing Email"); 
+		out.print( "\r\n</head>\r\n<body>\r\n" );
+
+		Shared.minHeaderGlobal(request,response);
+		Shared.profileHeading(request,out,user,"Changing Email");
+		
+		out.print( "\r\n<br />\r\n<table class=\"profile-table\">\r\n	<tr>\r\n		<td>\r\n		<p>An email has been sent to the new address.</p>\r\n		<p>Please follow the instructions in the email to complete the email change.</p>\r\n		<p><a href=\"/template/NamlServlet.jtp?macro=user_profile\">Go back to menu</a></p>\r\n	</td>\r\n</tr>\r\n</table>\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ChangeEmail2.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,53 @@
+<%
+package nabble.view.web.user;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.model.User;
+
+
+public final class ChangeEmail2 extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to change your email.",request,response);
+			return;
+		}
+		%>
+		<html>
+		<head>
+		<% Shared.title(request,response,"Changing Email"); %>
+		</head>
+		<body>
+		<%
+		Shared.minHeaderGlobal(request,response);
+		Shared.profileHeading(request,out,user,"Changing Email");
+		%>
+		<br />
+		<table class="profile-table">
+			<tr>
+				<td>
+				<p>An email has been sent to the new address.</p>
+				<p>Please follow the instructions in the email to complete the email change.</p>
+				<p><a href="/template/NamlServlet.jtp?macro=user_profile">Go back to menu</a></p>
+			</td>
+		</tr>
+		</table>
+		<% Shared.footer(request,response); %>
+		<% Shared.analytics(request,response); %>
+		</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ChangeEmail3.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,107 @@
+
+package nabble.view.web.user;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.ModelException;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.web.template.NabbleNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public final class ChangeEmail3 extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(ChangeEmail3.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<html>\r\n<head>\r\n	" );
+ Shared.title(request,response,"Change Email Confirmation"); 
+		out.print( "\r\n</head>\r\n<body>\r\n	" );
+ Shared.minHeaderGlobal(request,response); 
+		out.print( "\r\n	<h1>Change Email Confirmation</h1>\r\n	" );
+
+			String email = request.getParameter("email");
+			long userId = Long.valueOf(request.getParameter("user"));
+
+			Site site = Jtp.getSite(request);
+			User user = site.getUser(userId);
+			String newEmail = user.getProperty("_new_email");
+			int hash = Integer.parseInt(request.getParameter("h"));
+			user.setProperty("_new_email", null); // delete key
+			if (newEmail == null || !newEmail.equals(email) || hash != ChangeEmail.emailHash(newEmail))
+			{
+				
+		out.print( "\r\n<p><strong>We were unable to change your email address.</strong></p>\r\n<p>Please try <a href=\"ChangeEmail.jtp?user=" );
+		out.print( (user.getId()) );
+		out.print( "\">changing your email</a> again or <a href=\"/template/NamlServlet.jtp?macro=user_profile\">go back to menu</a>.</p>\r\n" );
+
+			} else {
+				DbDatabase db = site.getDb();
+				db.beginTransaction();
+				try {
+					User u = user.getGoodCopy();
+					String oldEmail = u.getEmail();
+					u.setEmail(email);
+					u.update();
+					ServletUtils.setCookie(request, response, "email", email, true, null);
+					db.commitTransaction();
+					callNaml(site, oldEmail, email);
+					
+		out.print( "\r\n<p>Your email has been changed.</p>\r\n<p><a href=\"/template/NamlServlet.jtp?macro=user_profile\">Go back to your profile</a></p>\r\n" );
+
+				} catch(ModelException e) {
+					logger.info("ex inTrans="+db.isInTransaction(),e);
+					
+		out.print( "\r\nEmail change failed:\r\n<br />" );
+		out.print( (e.getMessage()) );
+		out.print( "\r\n" );
+
+				} catch(RuntimeException e) {
+					logger.error("ex inTrans="+db.isInTransaction(),e);
+					throw e;
+				} finally {
+					db.endTransaction();
+				}
+			}
+			
+		out.print( "\r\n\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+
+	private static void callNaml(Site site, String oldEmail, String newEmail) {
+		Template template = site.getTemplate( "user email changed",
+			BasicNamespace.class, NabbleNamespace.class
+		);
+		Map<String,Object> params = new HashMap<String,Object>();
+		params.put("old_email",oldEmail);
+		params.put("new_email",newEmail);
+		template.run( TemplatePrintWriter.NULL, params,
+			new BasicNamespace(template),
+			new NabbleNamespace(site)
+		);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ChangeEmail3.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,108 @@
+<%
+package nabble.view.web.user;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.ModelException;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.naml.compiler.Template;
+import nabble.naml.compiler.TemplatePrintWriter;
+import nabble.naml.namespaces.BasicNamespace;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.web.template.NabbleNamespace;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public final class ChangeEmail3 extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(ChangeEmail3.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		%>
+		<html>
+		<head>
+			<% Shared.title(request,response,"Change Email Confirmation"); %>
+		</head>
+		<body>
+			<% Shared.minHeaderGlobal(request,response); %>
+			<h1>Change Email Confirmation</h1>
+			<%
+			String email = request.getParameter("email");
+			long userId = Long.valueOf(request.getParameter("user"));
+
+			Site site = Jtp.getSite(request);
+			User user = site.getUser(userId);
+			String newEmail = user.getProperty("_new_email");
+			int hash = Integer.parseInt(request.getParameter("h"));
+			user.setProperty("_new_email", null); // delete key
+			if (newEmail == null || !newEmail.equals(email) || hash != ChangeEmail.emailHash(newEmail))
+			{
+				%>
+				<p><strong>We were unable to change your email address.</strong></p>
+				<p>Please try <a href="ChangeEmail.jtp?user=<%=user.getId()%>">changing your email</a> again or <a href="/template/NamlServlet.jtp?macro=user_profile">go back to menu</a>.</p>
+				<%
+			} else {
+				DbDatabase db = site.getDb();
+				db.beginTransaction();
+				try {
+					User u = user.getGoodCopy();
+					String oldEmail = u.getEmail();
+					u.setEmail(email);
+					u.update();
+					ServletUtils.setCookie(request, response, "email", email, true, null);
+					db.commitTransaction();
+					callNaml(site, oldEmail, email);
+					%>
+					<p>Your email has been changed.</p>
+					<p><a href="/template/NamlServlet.jtp?macro=user_profile">Go back to your profile</a></p>
+					<%
+				} catch(ModelException e) {
+					logger.info("ex inTrans="+db.isInTransaction(),e);
+					%>
+					Email change failed:
+					<br /><%=e.getMessage()%>
+					<%
+				} catch(RuntimeException e) {
+					logger.error("ex inTrans="+db.isInTransaction(),e);
+					throw e;
+				} finally {
+					db.endTransaction();
+				}
+			}
+			%>
+
+			<% Shared.footer(request,response); %>
+			<% Shared.analytics(request,response); %>
+		</body>
+		</html>
+		<%
+	}
+
+	private static void callNaml(Site site, String oldEmail, String newEmail) {
+		Template template = site.getTemplate( "user email changed",
+			BasicNamespace.class, NabbleNamespace.class
+		);
+		Map<String,Object> params = new HashMap<String,Object>();
+		params.put("old_email",oldEmail);
+		params.put("new_email",newEmail);
+		template.run( TemplatePrintWriter.NULL, params,
+			new BasicNamespace(template),
+			new NabbleNamespace(site)
+		);
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/EditProfile.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,106 @@
+
+package nabble.view.web.user;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Db;
+import nabble.model.ModelException;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class EditProfile extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to edit your profile.",request,response);
+			return;
+		}
+		String password1 = null;
+		String password2 = null;
+		String name;
+		String errorMsg = null;
+
+		if ("save".equals(request.getParameter("action")) && "POST".equals(request.getMethod())) {
+			password1 = request.getParameter("password1");
+			password2 = request.getParameter("password2");
+			name = request.getParameter("name");
+			if (!password1.equals(password2) ) {
+				errorMsg = "The password fields don't match.";
+			} else if (password1.length() > 0 && password1.trim().length() == 0) {
+				errorMsg = "Your password must contain valid alphanumeric characters.";
+			} else {
+				DbDatabase db = user.getSite().getDb();
+				db.beginTransaction();
+				try {
+					User u = user.getGoodCopy();
+					if (password1.length() > 0)
+						u.setPassword(password1);
+					u.setName(name);
+					u.update();
+					db.commitTransaction();
+					String pwd = u.getPasscookie();
+					ServletUtils.setCookie(request,response,"username", HtmlUtils.urlEncode(name), false, null);
+					ServletUtils.setCookie(request,response,"password", HtmlUtils.urlEncode(pwd), false, null);
+
+					StringBuffer js = new StringBuffer();
+					js.append("if (parent.nabbleinfo) {");
+					js.append("Nabble.setCookie('username','").append(HtmlUtils.javascriptStringEncode(HtmlUtils.urlEncode(name))).append("');");
+					js.append("Nabble.setCookie('password','").append(HtmlUtils.javascriptStringEncode(HtmlUtils.urlEncode(pwd))).append("');");
+					js.append("}");
+
+					Shared.javascriptRedirect(request,response, "/template/NamlServlet.jtp?macro=user_profile", js.toString());
+					return;
+				} catch(ModelException e) {
+					errorMsg = e.getMessage();
+				} finally {
+					db.endTransaction();
+				}
+			}
+		} else {
+			name = user.getName();
+		}
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request,response,"Edit Personal Information"); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.minHeaderGlobal(request, response); 
+		out.print( "\r\n		" );
+ Shared.profileHeading(request,out,user,"Edit Personal Information"); 
+		out.print( "\r\n		" );
+ Shared.errorMessage(request,response,errorMsg, "Please re-enter the information and click on \"Update Information\"."); 
+		out.print( "\r\n		<style>\r\n			div.field-title {\r\n				margin-top: 0;\r\n			}\r\n		</style>\r\n		<form method=post action=\"EditProfile.jtp\">\r\n			<input type=hidden name=\"action\" value=\"save\">\r\n\r\n			<div class=\"field-box light-border-color\">\r\n				<div class=\"second-font field-title\">Email</div>\r\n				<div class=\"weak-color\">\r\n					" );
+		out.print( (user.getEmail()) );
+		out.print( "\r\n					&#187; <a href=\"ChangeEmail.jtp\">Change Email</a>\r\n				</div>\r\n			</div>\r\n\r\n			<div class=\"field-box light-border-color\" id=\"username-field\" >\r\n				<div class=\"second-font field-title\">Your User Name</div>\r\n				<div class=\"weak-color\">\r\n					Your user name must be unique in " );
+		out.print( (user.getSite().getRootNode().getSubjectHtml()) );
+		out.print( ".\r\n				</div>\r\n				<div><input name=\"name\" size=\"25\" maxlength=\"25\" value=\"" );
+		out.print( (HtmlUtils.htmlEncode(Jtp.hideNull(name))) );
+		out.print( "\" /></div>\r\n			</div>\r\n\r\n			<div class=\"field-box light-border-color\">\r\n				<div class=\"second-font field-title\">Change Password</div>\r\n				<div class=\"weak-color\">Nabble encrypts your password (<a href=\"" );
+		out.print( (Help.password.url(request)) );
+		out.print( "\">?</a>)</div>\r\n				<table style=\"margin: .4em 0\" class=\"shaded-bg-color\">\r\n					<tr valign=\"top\">\r\n						<td class=\"form-label\" style=\"padding-top:.6em\">Password:&nbsp;</td>\r\n						<td><input type=\"password\" name=\"password1\" size=\"25\" value=\"" );
+		out.print( (Jtp.hideNull(password1)) );
+		out.print( "\"/></td>\r\n					</tr>\r\n					<tr>\r\n						<td class=\"form-label\">Confirm Password:&nbsp;</td>\r\n						<td><input type=\"password\" name=\"password2\" size=\"25\" value=\"" );
+		out.print( (Jtp.hideNull(password2)) );
+		out.print( "\"/></td>\r\n					</tr>\r\n				</table>\r\n			</div>\r\n\r\n			<div class=\"field-box light-border-color\" style=\"padding-top:0\">\r\n				<input type=submit value=\"Update Personal Information\" />\r\n				or <a href=\"/template/NamlServlet.jtp?macro=user_profile\">Cancel</a>\r\n			</div>\r\n		</form>\r\n\r\n		" );
+ Shared.footer(request,response); 
+		out.print( "\r\n		" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/EditProfile.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,138 @@
+<%
+package nabble.view.web.user;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Db;
+import nabble.model.ModelException;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class EditProfile extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to edit your profile.",request,response);
+			return;
+		}
+		String password1 = null;
+		String password2 = null;
+		String name;
+		String errorMsg = null;
+
+		if ("save".equals(request.getParameter("action")) && "POST".equals(request.getMethod())) {
+			password1 = request.getParameter("password1");
+			password2 = request.getParameter("password2");
+			name = request.getParameter("name");
+			if (!password1.equals(password2) ) {
+				errorMsg = "The password fields don't match.";
+			} else if (password1.length() > 0 && password1.trim().length() == 0) {
+				errorMsg = "Your password must contain valid alphanumeric characters.";
+			} else {
+				DbDatabase db = user.getSite().getDb();
+				db.beginTransaction();
+				try {
+					User u = user.getGoodCopy();
+					if (password1.length() > 0)
+						u.setPassword(password1);
+					u.setName(name);
+					u.update();
+					db.commitTransaction();
+					String pwd = u.getPasscookie();
+					ServletUtils.setCookie(request,response,"username", HtmlUtils.urlEncode(name), false, null);
+					ServletUtils.setCookie(request,response,"password", HtmlUtils.urlEncode(pwd), false, null);
+
+					StringBuffer js = new StringBuffer();
+					js.append("if (parent.nabbleinfo) {");
+					js.append("Nabble.setCookie('username','").append(HtmlUtils.javascriptStringEncode(HtmlUtils.urlEncode(name))).append("');");
+					js.append("Nabble.setCookie('password','").append(HtmlUtils.javascriptStringEncode(HtmlUtils.urlEncode(pwd))).append("');");
+					js.append("}");
+
+					Shared.javascriptRedirect(request,response, "/template/NamlServlet.jtp?macro=user_profile", js.toString());
+					return;
+				} catch(ModelException e) {
+					errorMsg = e.getMessage();
+				} finally {
+					db.endTransaction();
+				}
+			}
+		} else {
+			name = user.getName();
+		}
+		%>
+		<html>
+			<head>
+				<% Shared.title(request,response,"Edit Personal Information"); %>
+			</head>
+			<body>
+				<% Shared.minHeaderGlobal(request, response); %>
+				<% Shared.profileHeading(request,out,user,"Edit Personal Information"); %>
+				<% Shared.errorMessage(request,response,errorMsg, "Please re-enter the information and click on \"Update Information\"."); %>
+				<style>
+					div.field-title {
+						margin-top: 0;
+					}
+				</style>
+				<form method=post action="EditProfile.jtp">
+					<input type=hidden name="action" value="save">
+
+					<div class="field-box light-border-color">
+						<div class="second-font field-title">Email</div>
+						<div class="weak-color">
+							<%=user.getEmail()%>
+							&#187; <a href="ChangeEmail.jtp">Change Email</a>
+						</div>
+					</div>
+
+					<div class="field-box light-border-color" id="username-field" >
+						<div class="second-font field-title">Your User Name</div>
+						<div class="weak-color">
+							Your user name must be unique in <%=user.getSite().getRootNode().getSubjectHtml()%>.
+						</div>
+						<div><input name="name" size="25" maxlength="25" value="<%=HtmlUtils.htmlEncode(Jtp.hideNull(name))%>" /></div>
+					</div>
+
+					<div class="field-box light-border-color">
+						<div class="second-font field-title">Change Password</div>
+						<div class="weak-color">Nabble encrypts your password (<a href="<%=Help.password.url(request)%>">?</a>)</div>
+						<table style="margin: .4em 0" class="shaded-bg-color">
+							<tr valign="top">
+								<td class="form-label" style="padding-top:.6em">Password:&nbsp;</td>
+								<td><input type="password" name="password1" size="25" value="<%=Jtp.hideNull(password1)%>"/></td>
+							</tr>
+							<tr>
+								<td class="form-label">Confirm Password:&nbsp;</td>
+								<td><input type="password" name="password2" size="25" value="<%=Jtp.hideNull(password2)%>"/></td>
+							</tr>
+						</table>
+					</div>
+
+					<div class="field-box light-border-color" style="padding-top:0">
+						<input type=submit value="Update Personal Information" />
+						or <a href="/template/NamlServlet.jtp?macro=user_profile">Cancel</a>
+					</div>
+				</form>
+
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/OnlineStatus.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,173 @@
+package nabble.view.web.user;
+
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Executors;
+import nabble.model.ModelHome;
+import nabble.model.Person;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+public class OnlineStatus {
+
+	private static final Logger logger = LoggerFactory.getLogger(OnlineStatus.class);
+
+	private static final long TIMEOUT = 3 * 60 * 1000L; // 3 minutes
+	private static final String INVISIBLE = "invisible:";
+
+	/** Maps a site ID to a map (Person-Source-ID > last time) */
+	private static final Map<Long, Map<String, Long>> sitesMap = new HashMap<Long, Map<String, Long>>();
+
+	public static void setOnline(HttpServletRequest request, Person visitor, Site site) {
+		setOnline(request,visitor.getSearchId(),site);
+	}
+
+	private static void setOnline(HttpServletRequest request, String sourceId, Site site) {
+		synchronized(sitesMap) {
+			Map<String, Long> map = sitesMap.get(site.getId());
+			if (map == null) {
+				map = new HashMap<String, Long>();
+				sitesMap.put(site.getId(), map);
+				logger.debug("New map for site ID = " + site.getId());
+			}
+			boolean isVisible = ServletUtils.getCookieValue(request, "visible") == null || !Jtp.isInteger(sourceId);
+			map.put(isVisible? sourceId : INVISIBLE+sourceId, System.currentTimeMillis());
+		}
+	}
+
+	public static boolean isOnline(Person visitor, Site site) {
+		return isOnline(visitor.getSearchId(),site);
+	}
+
+	public static boolean isOnline(String sourceId, Site site) {
+		synchronized(sitesMap) {
+			Map<String, Long> map = sitesMap.get(site.getId());
+			return map != null && map.get(sourceId) != null;
+		}
+	}
+
+	private static void runGC() {
+		final long start = System.currentTimeMillis();
+		synchronized(sitesMap) {
+			for( Iterator<Map<String, Long>> iter = sitesMap.values().iterator(); iter.hasNext(); ) {
+				Map<String, Long> map = iter.next();
+				for( Iterator<Long> subIter = map.values().iterator(); subIter.hasNext(); ) {
+					long time = subIter.next();
+					if (start - time > TIMEOUT) {
+						subIter.remove();
+						logger.debug("Removed user");
+					}
+				}
+				if( map.isEmpty() ) {
+					iter.remove();
+					logger.debug("Removed map");
+				}
+			}
+		}
+		logger.info("OnlineStatus GC took " + (System.currentTimeMillis()-start) + " ms");
+	}
+
+	public static String getOnlineStats() {
+		int count = 0;
+		int sites;
+		float ratio = 0f;
+		synchronized(sitesMap) {
+			Collection<Map<String, Long>> entries = sitesMap.values();
+			for (Map<String, Long> entry : entries) {
+				count += entry.keySet().size();
+			}
+			sites =  sitesMap.size();
+			if (sites > 0)
+				ratio = count / (float) sitesMap.size();
+		}
+		return "Users = " + count + " | Sites = " + sites + " | Ratio = " + String.format("%.2f", ratio);
+	}
+
+	public static Map<String, Integer> getOnlineSites() {
+		Map<String, Integer> sites = new HashMap<String, Integer>();
+
+		synchronized(sitesMap) {
+			for (Map.Entry<Long, Map<String, Long>> entry : sitesMap.entrySet()) {
+				String url = ModelHome.getSite(entry.getKey()).getBaseUrl();
+				sites.put(url, entry.getValue().size());
+			}
+		}
+		return sites;
+	}
+
+	public static List<User> getOnlineUsers(Site site, boolean includeInvisibleUsers) {
+		String[] visitorIDs = getVisitorIds(site);
+		List<User> users = new ArrayList<User>();
+		for (String id : visitorIDs) {
+			long userId = 0;
+			if (Jtp.isInteger(id)) {
+				userId = Long.valueOf(id);
+			} else if (includeInvisibleUsers && isInvisible(id)) {
+				userId = Long.valueOf(id.substring(INVISIBLE.length()));
+			}
+			if (userId > 0) {
+				User u = site.getUser(userId);
+				if (u != null)
+					users.add(u);
+			}
+		}
+		return users;
+	}
+
+	public static int getOnlineAnonymousUsersCount(Site site) {
+		String[] visitorIDs = getVisitorIds(site);
+		int c = 0;
+		for (String id : visitorIDs)
+			if (!Jtp.isInteger(id) && !isInvisible(id)) {
+				c++;
+		}
+		return c;
+	}
+
+	public static int getOnlineInvisibleUsersCount(Site site) {
+		String[] visitorIDs = getVisitorIds(site);
+		int c = 0;
+		for (String id : visitorIDs) {
+			if (isInvisible(id))
+				c++;
+		}
+		return c;
+	}
+
+	private static String[] getVisitorIds(Site site) {
+		synchronized(sitesMap) {
+			Map<String, Long> map = sitesMap.get(site.getId());
+			if (map == null)
+				return new String[0];
+			Set<String> set = map.keySet();
+			String[] visitorIds = new String[set.size()];
+			set.toArray(visitorIds);
+			return visitorIds;
+		}
+	}
+
+	private static boolean isInvisible(String id) {
+		return id.startsWith(INVISIBLE);
+	}
+
+	static {
+		Executors.scheduleWithFixedDelay(new Runnable(){
+			public void run(){
+				runGC();
+			}
+		}, 2*60, 2*60, TimeUnit.SECONDS); // Every 2 minutes
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/RemoveAccount.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,56 @@
+
+package nabble.view.web.user;
+
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class RemoveAccount extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if (user==null) {
+			Jtp.login("You must login to change your email.",request,response);
+			return;
+		}
+
+		String action = request.getParameter("action");
+		if ("remove".equals(action)) {
+			user.deactivate();
+			Jtp.dontCache(response);
+			Jtp.logout(request,response);
+			response.sendRedirect("/");
+			return;
+		}
+		Node node = Jtp.getSite(request).getRootNode();
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request,response,"Remove Your Account"); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+
+				Shared.minHeaderGlobal(request,response);
+				Shared.profileHeading(request,out,user,"Remove Your Account");
+				
+		out.print( "\r\n<br/>\r\n<form action=\"RemoveAccount.jtp\" method=\"POST\">\r\n	<input type=\"hidden\" name=\"action\" value=\"remove\"/>\r\n	Do you really want to remove your account and all your posts from " );
+		out.print( (node.getSubjectHtml()) );
+		out.print( "?\r\n	<br/><br/>\r\n	<input type=\"submit\" value=\"Yes, please remove my account\">\r\n	or <a href=\"/template/NamlServlet.jtp?macro=user_profile\">Cancel</a>\r\n</form>\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/RemoveAccount.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,63 @@
+<%
+package nabble.view.web.user;
+
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class RemoveAccount extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if (user==null) {
+			Jtp.login("You must login to change your email.",request,response);
+			return;
+		}
+
+		String action = request.getParameter("action");
+		if ("remove".equals(action)) {
+			user.deactivate();
+			Jtp.dontCache(response);
+			Jtp.logout(request,response);
+			response.sendRedirect("/");
+			return;
+		}
+		Node node = Jtp.getSite(request).getRootNode();
+		%>
+		<html>
+			<head>
+				<% Shared.title(request,response,"Remove Your Account"); %>
+			</head>
+			<body>
+				<%
+				Shared.minHeaderGlobal(request,response);
+				Shared.profileHeading(request,out,user,"Remove Your Account");
+				%>
+				<br/>
+				<form action="RemoveAccount.jtp" method="POST">
+					<input type="hidden" name="action" value="remove"/>
+					Do you really want to remove your account and all your posts from <%=node.getSubjectHtml()%>?
+					<br/><br/>
+					<input type="submit" value="Yes, please remove my account">
+					or <a href="/template/NamlServlet.jtp?macro=user_profile">Cancel</a>
+				</form>
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ResetPassword.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,110 @@
+
+package nabble.view.web.user;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Db;
+import nabble.model.ModelException;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class ResetPassword extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		if ( Jtp.getUser(request,response) != null ) {
+			Jtp.logout(request,response);
+		}
+		String email = request.getParameter("email");
+		String resetcode = request.getParameter("q");
+		if ( email==null || resetcode==null || resetcode.trim().length()==0 ) {
+			Jtp.login("This password reset link is not valid.",request,response);
+			return;
+		}
+		User user = Jtp.getSiteNotNull(request).getUserFromEmail(email);
+		if ( ! (user!=null && user.isRegistered() && user.checkResetcode(resetcode)) ) {
+			Jtp.login("This password reset link is no longer valid.",request,response);
+			return;
+		}
+		String password1 = null;
+		String password2 = null;
+		String errorMsg = null;
+
+		if ("save".equals(request.getParameter("action")) && "POST".equals(request.getMethod())) {
+			password1 = request.getParameter("password1");
+			password2 = request.getParameter("password2");
+			if (!password1.equals(password2) ) {
+				errorMsg = "The password fields don't match.";
+			} else if (password1.trim().length() == 0) {
+				errorMsg = "Your password must contain valid alphanumeric characters.";
+			} else {
+				DbDatabase db = user.getSite().getDb();
+				db.beginTransaction();
+				try {
+					User u = user.getGoodCopy();
+					u.setPassword(password1);
+					u.update();
+					db.commitTransaction();
+					String pwd = u.getPasscookie();
+					Jtp.doLogin(request,response,u,false);
+
+					StringBuffer js = new StringBuffer();
+					js.append("if (parent.nabbleinfo) {");
+					js.append("Nabble.setCookie('username','").append(HtmlUtils.javascriptStringEncode(HtmlUtils.urlEncode(u.getName()))).append("');");
+					js.append("Nabble.setCookie('password','").append(HtmlUtils.javascriptStringEncode(HtmlUtils.urlEncode(pwd))).append("');");
+					js.append("}");
+
+					Shared.javascriptRedirect(request,response, "/template/NamlServlet.jtp?macro=user_profile", js.toString());
+					return;
+				} catch(ModelException e) {
+					errorMsg = e.getMessage();
+				} finally {
+					db.endTransaction();
+				}
+			}
+		}
+		
+		out.print( "\n<html>\n	<head>\n		" );
+ Shared.title(request,response,"Reset Password"); 
+		out.print( "\n	</head>\n	<body>\n		" );
+ Shared.minHeaderGlobal(request, response); 
+		out.print( "\n		" );
+ Shared.profileHeading(request,out,user,"Reset Password"); 
+		out.print( "\n		" );
+ Shared.errorMessage(request,response,errorMsg, "Please re-enter the information and click on \"Update Information\"."); 
+		out.print( "\n		<style>\n			div.field-title {\n				margin-top: 0;\n			}\n		</style>\n		<form method=post action=\"ResetPassword.jtp\">\n			<input type=hidden name=\"action\" value=\"save\">\n			<input type=hidden name=\"email\" value=\"" );
+		out.print( (Jtp.hideNull(email)) );
+		out.print( "\">\n			<input type=hidden name=\"q\" value=\"" );
+		out.print( (Jtp.hideNull(resetcode)) );
+		out.print( "\">\n			\n			<div class=\"field-box light-border-color\">\n				<div class=\"second-font field-title\">Your Email</div>\n				<div class=\"weak-color\">" );
+		out.print( (user.getEmail()) );
+		out.print( "</div>\n			</div>\n\n			<div class=\"field-box light-border-color\">\n				<div class=\"second-font field-title\">Your User Name</div>\n				<div class=\"weak-color\">" );
+		out.print( (user.getNameHtml()) );
+		out.print( "</div>\n			</div>\n\n			<div class=\"field-box light-border-color\">\n				<div class=\"second-font field-title\">Change Password</div>\n				<div class=\"weak-color\">Nabble encrypts your password (<a href=\"" );
+		out.print( (Help.password.url(request)) );
+		out.print( "\">?</a>)</div>						\n				<table style=\"margin: .4em 0\" class=\"shaded-bg-color\">\n					<tr valign=\"top\">\n						<td class=\"form-label\" style=\"padding-top:.6em\">Password:&nbsp;</td>\n						<td><input type=\"password\" name=\"password1\" size=\"25\" value=\"" );
+		out.print( (Jtp.hideNull(password1)) );
+		out.print( "\"/></td>\n					</tr>\n					<tr>\n						<td class=\"form-label\">Confirm Password:&nbsp;</td>\n						<td><input type=\"password\" name=\"password2\" size=\"25\" value=\"" );
+		out.print( (Jtp.hideNull(password2)) );
+		out.print( "\"/></td>\n					</tr>\n				</table>\n			</div>\n\n			<div class=\"field-box light-border-color\" style=\"padding-top:0\">\n				<input type=submit value=\"Update Password\" />\n				or <a href=\"/template/NamlServlet.jtp?macro=user_profile\">Cancel</a>\n			</div>\n		</form>\n\n		" );
+ Shared.footer(request,response); 
+		out.print( "\n		" );
+ Shared.analytics(request,response); 
+		out.print( "\n	</body>\n</html>\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/ResetPassword.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,136 @@
+<%
+package nabble.view.web.user;
+
+import fschmidt.db.DbDatabase;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.servlet.ServletUtils;
+import nabble.model.Db;
+import nabble.model.ModelException;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.help.Help;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class ResetPassword extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		if ( Jtp.getUser(request,response) != null ) {
+			Jtp.logout(request,response);
+		}
+		String email = request.getParameter("email");
+		String resetcode = request.getParameter("q");
+		if ( email==null || resetcode==null || resetcode.trim().length()==0 ) {
+			Jtp.login("This password reset link is not valid.",request,response);
+			return;
+		}
+		User user = Jtp.getSiteNotNull(request).getUserFromEmail(email);
+		if ( ! (user!=null && user.isRegistered() && user.checkResetcode(resetcode)) ) {
+			Jtp.login("This password reset link is no longer valid.",request,response);
+			return;
+		}
+		String password1 = null;
+		String password2 = null;
+		String errorMsg = null;
+
+		if ("save".equals(request.getParameter("action")) && "POST".equals(request.getMethod())) {
+			password1 = request.getParameter("password1");
+			password2 = request.getParameter("password2");
+			if (!password1.equals(password2) ) {
+				errorMsg = "The password fields don't match.";
+			} else if (password1.trim().length() == 0) {
+				errorMsg = "Your password must contain valid alphanumeric characters.";
+			} else {
+				DbDatabase db = user.getSite().getDb();
+				db.beginTransaction();
+				try {
+					User u = user.getGoodCopy();
+					u.setPassword(password1);
+					u.update();
+					db.commitTransaction();
+					String pwd = u.getPasscookie();
+					Jtp.doLogin(request,response,u,false);
+
+					StringBuffer js = new StringBuffer();
+					js.append("if (parent.nabbleinfo) {");
+					js.append("Nabble.setCookie('username','").append(HtmlUtils.javascriptStringEncode(HtmlUtils.urlEncode(u.getName()))).append("');");
+					js.append("Nabble.setCookie('password','").append(HtmlUtils.javascriptStringEncode(HtmlUtils.urlEncode(pwd))).append("');");
+					js.append("}");
+
+					Shared.javascriptRedirect(request,response, "/template/NamlServlet.jtp?macro=user_profile", js.toString());
+					return;
+				} catch(ModelException e) {
+					errorMsg = e.getMessage();
+				} finally {
+					db.endTransaction();
+				}
+			}
+		}
+		%>
+		<html>
+			<head>
+				<% Shared.title(request,response,"Reset Password"); %>
+			</head>
+			<body>
+				<% Shared.minHeaderGlobal(request, response); %>
+				<% Shared.profileHeading(request,out,user,"Reset Password"); %>
+				<% Shared.errorMessage(request,response,errorMsg, "Please re-enter the information and click on \"Update Information\"."); %>
+				<style>
+					div.field-title {
+						margin-top: 0;
+					}
+				</style>
+				<form method=post action="ResetPassword.jtp">
+					<input type=hidden name="action" value="save">
+					<input type=hidden name="email" value="<%=Jtp.hideNull(email)%>">
+					<input type=hidden name="q" value="<%=Jtp.hideNull(resetcode)%>">
+					
+					<div class="field-box light-border-color">
+						<div class="second-font field-title">Your Email</div>
+						<div class="weak-color"><%=user.getEmail()%></div>
+					</div>
+
+					<div class="field-box light-border-color">
+						<div class="second-font field-title">Your User Name</div>
+						<div class="weak-color"><%=user.getNameHtml()%></div>
+					</div>
+
+					<div class="field-box light-border-color">
+						<div class="second-font field-title">Change Password</div>
+						<div class="weak-color">Nabble encrypts your password (<a href="<%=Help.password.url(request)%>">?</a>)</div>						
+						<table style="margin: .4em 0" class="shaded-bg-color">
+							<tr valign="top">
+								<td class="form-label" style="padding-top:.6em">Password:&nbsp;</td>
+								<td><input type="password" name="password1" size="25" value="<%=Jtp.hideNull(password1)%>"/></td>
+							</tr>
+							<tr>
+								<td class="form-label">Confirm Password:&nbsp;</td>
+								<td><input type="password" name="password2" size="25" value="<%=Jtp.hideNull(password2)%>"/></td>
+							</tr>
+						</table>
+					</div>
+
+					<div class="field-box light-border-color" style="padding-top:0">
+						<input type=submit value="Update Password" />
+						or <a href="/template/NamlServlet.jtp?macro=user_profile">Cancel</a>
+					</div>
+				</form>
+
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/SendEmail.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,174 @@
+
+package nabble.view.web.user;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.Recaptcha;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+
+
+public final class SendEmail extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(SendEmail.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to send an email.",request,response);
+			return;
+		}
+		String type = request.getParameter("type");
+		String emailTo;
+		String emailDisp;
+		String subject = request.getParameter("subject");
+		String message = request.getParameter("message");
+		Set<String> paramNames = new HashSet<String>();
+		if( type.equals("email") ) {
+			emailTo = request.getParameter("email");
+			emailDisp = ModelHome.hideEmail(emailTo);
+			paramNames.add("email");
+		} else if( type.equals("user") ) {
+			User to = Jtp.getSite(request).getUser(Long.parseLong(request.getParameter("user")));
+			emailTo = to.getEmail();
+			emailDisp = to.getName();
+			paramNames.add("user");
+		} else if( type.equals("node") ) {
+			Node node = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("node")));
+			int i = Integer.parseInt(request.getParameter("i"));
+			emailTo = node.getMessage().getEmail(i);
+			emailDisp = ModelHome.hideEmail(emailTo);
+			paramNames.add("node");
+			paramNames.add("i");
+		} else if( type.equals("sig") ) {
+			User to = Jtp.getSite(request).getUser(Long.parseLong(request.getParameter("user")));
+			int i = Integer.parseInt(request.getParameter("i"));
+			emailTo = to.getSignature().getEmail(i);
+			emailDisp = ModelHome.hideEmail(emailTo);
+			paramNames.add("user");
+			paramNames.add("i");
+		} else if( type.equals("pm") ) {
+			Node post = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("post")));
+			User to = (User)post.getOwner();
+			emailTo = to.getEmail();
+			emailDisp = to.getName();
+			if( subject==null ) {
+				subject = post.getSubject();
+				String original =
+					"\n<quote author='" + post.getOwner().getNameHtml() + "'>\n"
+					+ post.getMessage().getText() + "\n"
+					+ "</quote>\n"
+					+ "Quoted from:  " + Jtp.url(post) + "\n"
+				;
+				message = Message.wrapQuoteText(original);
+			}
+			paramNames.add("post");
+		} else {
+			throw new RuntimeException("type="+type);
+		}
+		String errorMsg = null;
+
+		if( request.getParameter("send") != null && "POST".equals(request.getMethod()) ) {
+			try {
+				Recaptcha.check(request);
+
+				Site site = Jtp.getSite(request);
+				if (site != null) {
+					message += "\n\n_____________________________________\nSent from " + site.getBaseUrl() + "\n\n";
+				}
+
+				Mail mail = MailHome.newMail();
+				mail.setFrom( new MailAddress(user.getEmail()) );
+				mail.setTo( new MailAddress(emailTo) );
+				mail.setSubject(subject);
+				mail.setContent( new PlainTextContent(message) );
+				ModelHome.send(mail);
+				if( request.getParameter("cc") != null ) {
+					mail = MailHome.newMail();
+					mail.setFrom( new MailAddress(user.getEmail()) );
+					mail.setTo( new MailAddress(user.getEmail()) );
+					mail.setSubject(subject);
+					mail.setContent( new PlainTextContent(message) );
+					ModelHome.send(mail);
+				}
+				logger.info(
+					"From: " + user.getEmail() + " (user ID=" + user.getId() + ")\n" +
+					"To: " + emailTo + '\n' +
+					"Subject: " + subject + '\n' + message +
+					"\n------------------------------------------------------------\n"
+				);
+				response.sendRedirect("SendEmail2.jtp?to=" + HtmlUtils.urlEncode(emailDisp));
+				return;
+			} catch(MailException e) {
+				logger.warn("",e);
+				errorMsg = e.getMessage();
+			} catch (ModelException.InvalidRecaptcha e) {
+				logger.warn("",e);
+				errorMsg = e.getMessage();
+			}
+		}
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request,response,"Send Email to "+HtmlUtils.htmlEncode(emailDisp)); 
+		out.print( "\r\n		<style type=\"text/css\">\r\n			div.field-title {\r\n				margin-top: 0;\r\n			}\r\n		</style>\r\n		<script type=\"text/javascript\">\r\n			$(document).ready(\r\n				function() {\r\n					Nabble.get(\"nabble.subject\").focus();\r\n				}\r\n			);\r\n		</script>\r\n		" );
+		out.print( ( Recaptcha.JS ) );
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.minHeaderGlobal(request,response); 
+		out.print( "\r\n		<h1>Send Email to " );
+		out.print( (HtmlUtils.htmlEncode(emailDisp)) );
+		out.print( "</h1>\r\n\r\n		" );
+ Shared.errorMessage(request,response,errorMsg,"Please fix the error and try again.");
+		out.print( "\r\n		<form method=\"post\">\r\n			<input type=\"hidden\" name=\"type\" value=\"" );
+		out.print( (type) );
+		out.print( "\">\r\n			" );
+
+					for (String name : paramNames) {
+						
+		out.print( "\r\n<input type=\"hidden\" name=\"" );
+		out.print( (name) );
+		out.print( "\" value=\"" );
+		out.print( (request.getParameter(name)) );
+		out.print( "\">\r\n" );
+
+					}
+					
+		out.print( "\r\n\r\n<div class=\"field-box light-border-color\">\r\n	<div class=\"second-font field-title\">Email Subject</div>\r\n	<div class=\"weak-color\">\r\n		<input name=\"subject\" id=\"nabble.subject\" size=\"70\" value=\"" );
+		out.print( (Jtp.hideNull(subject)) );
+		out.print( "\" tabindex=\"1\">\r\n	</div>\r\n</div>\r\n\r\n<div class=\"field-box light-border-color\">\r\n	<div class=\"second-font field-title\">Message</div>\r\n	<div class=\"weak-color\">\r\n		<textarea style=\"width:40em; height: 150px;\" name=\"message\" wrap=\"SOFT\" tabindex=\"2\">" );
+		out.print( (HtmlUtils.htmlEncode(Jtp.hideNull(message))) );
+		out.print( "</textarea>\r\n	</div>\r\n</div>\r\n\r\n<table style=\"margin-bottom:1em\">\r\n	<tr>\r\n		<td><input id=\"cc\" type=\"checkbox\" name=\"cc\" value=\"y\" /></td>\r\n		<td><label for=\"cc\">Send a copy of this message to me</label></td>\r\n	</tr>\r\n</table>\r\n\r\n" );
+		out.print( ( Recaptcha.DIV ) );
+		out.print( "<br>\r\n\r\n<input type=\"submit\" name=\"send\" value=\"Send Email\" />\r\n</form>\r\n\r\n" );
+ Shared.returnToJs(request,response); 
+		out.print( "\r\n\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/SendEmail.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,201 @@
+<%
+package nabble.view.web.user;
+
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.mail.Mail;
+import fschmidt.util.mail.MailAddress;
+import fschmidt.util.mail.MailException;
+import fschmidt.util.mail.MailHome;
+import fschmidt.util.mail.PlainTextContent;
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.Site;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.Recaptcha;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+
+
+public final class SendEmail extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(SendEmail.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request,response);
+		if( user==null ) {
+			Jtp.login("You must login to send an email.",request,response);
+			return;
+		}
+		String type = request.getParameter("type");
+		String emailTo;
+		String emailDisp;
+		String subject = request.getParameter("subject");
+		String message = request.getParameter("message");
+		Set<String> paramNames = new HashSet<String>();
+		if( type.equals("email") ) {
+			emailTo = request.getParameter("email");
+			emailDisp = ModelHome.hideEmail(emailTo);
+			paramNames.add("email");
+		} else if( type.equals("user") ) {
+			User to = Jtp.getSite(request).getUser(Long.parseLong(request.getParameter("user")));
+			emailTo = to.getEmail();
+			emailDisp = to.getName();
+			paramNames.add("user");
+		} else if( type.equals("node") ) {
+			Node node = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("node")));
+			int i = Integer.parseInt(request.getParameter("i"));
+			emailTo = node.getMessage().getEmail(i);
+			emailDisp = ModelHome.hideEmail(emailTo);
+			paramNames.add("node");
+			paramNames.add("i");
+		} else if( type.equals("sig") ) {
+			User to = Jtp.getSite(request).getUser(Long.parseLong(request.getParameter("user")));
+			int i = Integer.parseInt(request.getParameter("i"));
+			emailTo = to.getSignature().getEmail(i);
+			emailDisp = ModelHome.hideEmail(emailTo);
+			paramNames.add("user");
+			paramNames.add("i");
+		} else if( type.equals("pm") ) {
+			Node post = Jtp.getSiteNotNull(request).getNode(Long.parseLong(request.getParameter("post")));
+			User to = (User)post.getOwner();
+			emailTo = to.getEmail();
+			emailDisp = to.getName();
+			if( subject==null ) {
+				subject = post.getSubject();
+				String original =
+					"\n<quote author='" + post.getOwner().getNameHtml() + "'>\n"
+					+ post.getMessage().getText() + "\n"
+					+ "</quote>\n"
+					+ "Quoted from:  " + Jtp.url(post) + "\n"
+				;
+				message = Message.wrapQuoteText(original);
+			}
+			paramNames.add("post");
+		} else {
+			throw new RuntimeException("type="+type);
+		}
+		String errorMsg = null;
+
+		if( request.getParameter("send") != null && "POST".equals(request.getMethod()) ) {
+			try {
+				Recaptcha.check(request);
+
+				Site site = Jtp.getSite(request);
+				if (site != null) {
+					message += "\n\n_____________________________________\nSent from " + site.getBaseUrl() + "\n\n";
+				}
+
+				Mail mail = MailHome.newMail();
+				mail.setFrom( new MailAddress(user.getEmail()) );
+				mail.setTo( new MailAddress(emailTo) );
+				mail.setSubject(subject);
+				mail.setContent( new PlainTextContent(message) );
+				ModelHome.send(mail);
+				if( request.getParameter("cc") != null ) {
+					mail = MailHome.newMail();
+					mail.setFrom( new MailAddress(user.getEmail()) );
+					mail.setTo( new MailAddress(user.getEmail()) );
+					mail.setSubject(subject);
+					mail.setContent( new PlainTextContent(message) );
+					ModelHome.send(mail);
+				}
+				logger.info(
+					"From: " + user.getEmail() + " (user ID=" + user.getId() + ")\n" +
+					"To: " + emailTo + '\n' +
+					"Subject: " + subject + '\n' + message +
+					"\n------------------------------------------------------------\n"
+				);
+				response.sendRedirect("SendEmail2.jtp?to=" + HtmlUtils.urlEncode(emailDisp));
+				return;
+			} catch(MailException e) {
+				logger.warn("",e);
+				errorMsg = e.getMessage();
+			} catch (ModelException.InvalidRecaptcha e) {
+				logger.warn("",e);
+				errorMsg = e.getMessage();
+			}
+		}
+		%>
+		<html>
+			<head>
+				<% Shared.title(request,response,"Send Email to "+HtmlUtils.htmlEncode(emailDisp)); %>
+				<style type="text/css">
+					div.field-title {
+						margin-top: 0;
+					}
+				</style>
+				<script type="text/javascript">
+					$(document).ready(
+						function() {
+							Nabble.get("nabble.subject").focus();
+						}
+					);
+				</script>
+				<%= Recaptcha.JS %>
+			</head>
+			<body>
+				<% Shared.minHeaderGlobal(request,response); %>
+				<h1>Send Email to <%=HtmlUtils.htmlEncode(emailDisp)%></h1>
+
+				<% Shared.errorMessage(request,response,errorMsg,"Please fix the error and try again.");%>
+				<form method="post">
+					<input type="hidden" name="type" value="<%=type%>">
+					<%
+					for (String name : paramNames) {
+						%>
+						<input type="hidden" name="<%=name%>" value="<%=request.getParameter(name)%>">
+						<%
+					}
+					%>
+
+					<div class="field-box light-border-color">
+						<div class="second-font field-title">Email Subject</div>
+						<div class="weak-color">
+							<input name="subject" id="nabble.subject" size="70" value="<%=Jtp.hideNull(subject)%>" tabindex="1">
+						</div>
+					</div>
+
+					<div class="field-box light-border-color">
+						<div class="second-font field-title">Message</div>
+						<div class="weak-color">
+							<textarea style="width:40em; height: 150px;" name="message" wrap="SOFT" tabindex="2"><%=HtmlUtils.htmlEncode(Jtp.hideNull(message))%></textarea>
+						</div>
+					</div>
+
+					<table style="margin-bottom:1em">
+						<tr>
+							<td><input id="cc" type="checkbox" name="cc" value="y" /></td>
+							<td><label for="cc">Send a copy of this message to me</label></td>
+						</tr>
+					</table>
+
+					<%= Recaptcha.DIV %><br>
+
+					<input type="submit" name="send" value="Send Email" />
+				</form>
+
+				<% Shared.returnToJs(request,response); %>
+
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/SendEmail2.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,42 @@
+
+package nabble.view.web.user;
+
+import fschmidt.util.java.HtmlUtils;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class SendEmail2 extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String emailDisp = request.getParameter("to");
+		emailDisp = HtmlUtils.htmlEncode(emailDisp);
+		
+		out.print( "\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request,response,"Sent email to "+emailDisp); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.minHeaderGlobal(request,response); 
+		out.print( "\r\n		<h1>Sent email to " );
+		out.print( (emailDisp) );
+		out.print( "</h1>\r\n\r\n		<p>Your email has been sent to " );
+		out.print( (emailDisp) );
+		out.print( ".</p>\r\n\r\n		" );
+ Shared.returnToJs(request,response); 
+		out.print( "\r\n\r\n		" );
+ Shared.footer(request,response); 
+		out.print( "\r\n		" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n	</body>\r\n</html>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/SendEmail2.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,43 @@
+<%
+package nabble.view.web.user;
+
+import fschmidt.util.java.HtmlUtils;
+import nabble.view.lib.Shared;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+public final class SendEmail2 extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		String emailDisp = request.getParameter("to");
+		emailDisp = HtmlUtils.htmlEncode(emailDisp);
+		%>
+		<html>
+			<head>
+				<% Shared.title(request,response,"Sent email to "+emailDisp); %>
+			</head>
+			<body>
+				<% Shared.minHeaderGlobal(request,response); %>
+				<h1>Sent email to <%=emailDisp%></h1>
+
+				<p>Your email has been sent to <%=emailDisp%>.</p>
+
+				<% Shared.returnToJs(request,response); %>
+
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/UserEditorNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,103 @@
+package nabble.view.web.user;
+
+import nabble.model.Message;
+import nabble.model.ModelException;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.namespaces.TemplateException;
+import nabble.view.lib.Permissions;
+import nabble.view.web.template.ServletNamespace;
+
+
+@Namespace (
+	name = "user_editor",
+	global = true
+)
+public final class UserEditorNamespace {
+	private final User user;
+
+	public UserEditorNamespace(User user) {
+		this.user = user;
+	}
+
+	public static final CommandSpec save_user = CommandSpec.NO_OUTPUT;
+
+	@Command public void save_user(IPrintWriter out,Interpreter interp) {
+		user.update();
+	}
+
+	public static final CommandSpec add_to_group = new CommandSpec.Builder()
+		.dotParameter("group")
+		.build()
+	;
+
+	@Command public void add_to_group(IPrintWriter out,Interpreter interp)
+		throws TemplateException
+	{
+		String group = interp.getArgString("group");
+		Permissions.addToGroup(user, group);
+	}
+
+	public static final CommandSpec remove_from_group = new CommandSpec.Builder()
+		.dotParameter("group")
+		.build()
+	;
+
+	@Command public void remove_from_group(IPrintWriter out,Interpreter interp)
+		throws TemplateException
+	{
+		String group = interp.getArgString("group");
+		Permissions.removeFromGroup(user, group);
+	}
+
+	public static final CommandSpec set_signature = CommandSpec.NO_OUTPUT()
+		.parameters("signature","is_html")
+		.requiredInStack(ServletNamespace.class)
+		.build()
+	;
+
+	@Command public void set_signature(IPrintWriter out,Interpreter interp)
+		throws ModelException
+	{
+		String signature = interp.getArgString("signature");
+		boolean isHtml = interp.getArgAsBoolean("is_html", false);
+		Message.Format fmt = isHtml? Message.Format.HTML : Message.Format.TEXT;
+		ServletNamespace servletNs = interp.getFromStack(ServletNamespace.class);
+		user.setSignature(signature, fmt);
+	}
+
+	public static final CommandSpec set_name = CommandSpec.NO_OUTPUT()
+		.parameters("name")
+		.build()
+	;
+
+	@Command public void set_name(IPrintWriter out,Interpreter interp)
+		throws ModelException
+	{
+		String name = interp.getArgString("name");
+		user.setName(name);
+	}
+
+	public static final CommandSpec set_password = CommandSpec.NO_OUTPUT()
+		.parameters("password1", "password2")
+		.build()
+	;
+
+	@Command public void set_password(IPrintWriter out,Interpreter interp)
+		throws TemplateException
+	{
+		String password1 = interp.getArgString("password1");
+		String password2 = interp.getArgString("password2");
+		if (password1.trim().isEmpty() && password2.trim().isEmpty())
+			return; // skip change
+		if (password1.equals(password2)) {
+			user.setPassword(password1);
+		} else
+			throw ModelException.newInstance("passwords_dont_match");
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/UserPendingNodes.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,136 @@
+
+package nabble.view.web.user;
+
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.HtmlViewUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public final class UserPendingNodes extends HttpServlet {
+
+	private static final int MAX_ROWS = 20;
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request, response);
+
+		if ( user == null ) {
+			Jtp.login("You must register/login to edit your profile.", request, response);
+			return;
+		}
+
+		String iRecS = request.getParameter("i");
+		int iRec = iRecS == null? 0 : Integer.valueOf(iRecS);
+
+		List<Node> nodeArray = user.getPendingPosts().get(0, 1000);
+		SearchResults searchResults = cutResults(nodeArray, iRec);
+
+		String title = "Pending Posts of ";
+		title += user.getName();
+
+		String url = "/user/UserPendingNodes.jtp";
+		HtmlViewUtils.GenericPagingPath pagingPath = new HtmlViewUtils.GenericPagingPath(url);
+		
+		out.print( "\r\n<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n	<head>\r\n		" );
+ Shared.title(request, response, title); 
+		out.print( "\r\n	</head>\r\n	<body>\r\n		" );
+ Shared.minHeaderGlobal(request, response); 
+		out.print( "\r\n		<table>\r\n			<tr valign=\"top\">\r\n				<td><img src=\"" );
+		out.print( (Shared.getAvatarImageURL(user, false)) );
+		out.print( "\" class=\"avatar light-border-color\" width=100 height=100/></td>\r\n				<td style=\"width:100%\">\r\n					<div class=\"second-font\" style=\"font-size:170%\">\r\n						" );
+		out.print( (user.getNameHtml()) );
+		out.print( "\r\n					</div>\r\n					<div style=\"margin-top:.5em\">\r\n						<a href=\"/template/NamlServlet.jtp?macro=user_profile\">Account Settings</a>\r\n					</div>\r\n				</td>\r\n			</tr>\r\n		</table>\r\n\r\n		<table style=\"border-collapse:collapse;width:100%;margin-top:.5em\">\r\n			<tr>\r\n				<td class=\"title-row light-border-color\" colspan=3>\r\n					<div style=\"float:left;padding-top:.2em\">\r\n						<b>Pending Messages</b>\r\n					</div>\r\n					" );
+ HtmlViewUtils.genericPaging(request, response, searchResults.getCount(), iRec, MAX_ROWS, pagingPath, ".4em 0 0 0"); 
+		out.print( "\r\n				</td>\r\n			</tr>\r\n		</table>\r\n\r\n		<style type=\"text/css\">\r\n			table.nodes {\r\n				width:100%;\r\n				border-width: 1px;\r\n				border-style: solid;\r\n				border-collapse:collapse;\r\n				margin-top:.5em;\r\n			}\r\n\r\n			table.nodes td {\r\n				padding:.1em;\r\n			}\r\n\r\n			table.nodes td.header {\r\n				padding: .2em .3em;\r\n				border-bottom-width: 1px;\r\n				border-bottom-style: solid;\r\n			}\r\n		</style>\r\n\r\n		" );
+
+				String[] columns = new String[] { "Message", "Pending Since", "Forum" };
+				
+		out.print( "\r\n\r\n<table class=\"nodes medium-border-color\">\r\n	<tr class=\"shaded-bg-color\" style=\"font-weight: bold\">\r\n		<td class=\"header\" style=\"width:16px\"></td>\r\n		<td class=\"header\">" );
+		out.print( (columns[0]) );
+		out.print( "</td>\r\n		<td class=\"header\">" );
+		out.print( (columns[1]) );
+		out.print( "</td>\r\n		<td class=\"header\">" );
+		out.print( (columns[2]) );
+		out.print( "</td>\r\n	</tr>\r\n	" );
+
+					Node[] nodes = searchResults.getNodes();
+					int i = 0;
+					if (nodes.length > 0) {
+						for (Node node : nodes) {
+							
+		out.print( "\r\n<tr " );
+		out.print( (i++%2==1?"class=\"light-bg-color\"":"") );
+		out.print( ">\r\n	<td align=\"center\">\r\n		" );
+Shared.showPending(out, node);
+		out.print( "\r\n	</td>\r\n	<td style=\"padding:.3em .4em;\">\r\n		<a href=\"" );
+		out.print( (Jtp.url(node)) );
+		out.print( "\">" );
+		out.print( (node.getSubjectHtml()) );
+		out.print( "</a>\r\n	</td>\r\n	<td style=\"padding:.2em\">\r\n		" );
+		out.print( (node.getMailToList() == null? "" : Jtp.formatDateLong(node.getMailToList().getWhenSent())) );
+		out.print( "\r\n	</td>\r\n	<td style=\"padding:.2em\">\r\n		" );
+		out.print( (Jtp.link(node.getApp())) );
+		out.print( "\r\n	</td>\r\n</tr>\r\n" );
+
+						}
+					} else {
+						
+		out.print( "\r\n<tr><td colspan=4 style=\"padding:.3em\">None</td></tr>\r\n" );
+
+					}
+					
+		out.print( "\r\n</table>\r\n" );
+ HtmlViewUtils.genericPaging(request, response, searchResults.getCount(), iRec, MAX_ROWS, pagingPath, ".5em 0 0 0"); 
+		out.print( "\r\n\r\n" );
+ Shared.footer(request,response); 
+		out.print( "\r\n" );
+ Shared.analytics(request,response); 
+		out.print( "\r\n</body>\r\n</html>\r\n" );
+
+	}
+
+	private class SearchResults {
+		private int count;
+		private Node[] nodes;
+
+		public SearchResults() {}
+
+		public SearchResults(int count, Node[] nodes) {
+			this.count = count;
+			this.nodes = nodes;
+		}
+
+		public int getCount() { return count; }
+		public void setCount(int count) { this.count = count; }
+		public Node[] getNodes() { return nodes; }
+		public void setNodes(Node[] nodes) { this.nodes = nodes; }
+	}
+
+	private SearchResults cutResults(List<Node> array, int iRec) {
+		int i = iRec;
+		int count = 0;
+		List<Node> nodes = new ArrayList<Node>();
+		while (count < MAX_ROWS) {
+			if (array.size()-1 < i)
+				break;
+			nodes.add(array.get(i++));
+			count++;
+		}
+		Node[] nodesArray = nodes.toArray(new Node[0]);
+		return new SearchResults(array.size(), nodesArray);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/user/UserPendingNodes.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,179 @@
+<%
+package nabble.view.web.user;
+
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.Shared;
+import nabble.view.lib.HtmlViewUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public final class UserPendingNodes extends HttpServlet {
+
+	private static final int MAX_ROWS = 20;
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		PrintWriter out = response.getWriter();
+		User user = Jtp.getUser(request, response);
+
+		if ( user == null ) {
+			Jtp.login("You must register/login to edit your profile.", request, response);
+			return;
+		}
+
+		String iRecS = request.getParameter("i");
+		int iRec = iRecS == null? 0 : Integer.valueOf(iRecS);
+
+		List<Node> nodeArray = user.getPendingPosts().get(0, 1000);
+		SearchResults searchResults = cutResults(nodeArray, iRec);
+
+		String title = "Pending Posts of ";
+		title += user.getName();
+
+		String url = "/user/UserPendingNodes.jtp";
+		HtmlViewUtils.GenericPagingPath pagingPath = new HtmlViewUtils.GenericPagingPath(url);
+		%>
+		<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+		<html>
+			<head>
+				<% Shared.title(request, response, title); %>
+			</head>
+			<body>
+				<% Shared.minHeaderGlobal(request, response); %>
+				<table>
+					<tr valign="top">
+						<td><img src="<%=Shared.getAvatarImageURL(user, false)%>" class="avatar light-border-color" width=100 height=100/></td>
+						<td style="width:100%">
+							<div class="second-font" style="font-size:170%">
+								<%=user.getNameHtml()%>
+							</div>
+							<div style="margin-top:.5em">
+								<a href="/template/NamlServlet.jtp?macro=user_profile">Account Settings</a>
+							</div>
+						</td>
+					</tr>
+				</table>
+
+				<table style="border-collapse:collapse;width:100%;margin-top:.5em">
+					<tr>
+						<td class="title-row light-border-color" colspan=3>
+							<div style="float:left;padding-top:.2em">
+								<b>Pending Messages</b>
+							</div>
+							<% HtmlViewUtils.genericPaging(request, response, searchResults.getCount(), iRec, MAX_ROWS, pagingPath, ".4em 0 0 0"); %>
+						</td>
+					</tr>
+				</table>
+
+				<style type="text/css">
+					table.nodes {
+						width:100%;
+						border-width: 1px;
+						border-style: solid;
+						border-collapse:collapse;
+						margin-top:.5em;
+					}
+
+					table.nodes td {
+						padding:.1em;
+					}
+
+					table.nodes td.header {
+						padding: .2em .3em;
+						border-bottom-width: 1px;
+						border-bottom-style: solid;
+					}
+				</style>
+
+				<%
+				String[] columns = new String[] { "Message", "Pending Since", "Forum" };
+				%>
+
+				<table class="nodes medium-border-color">
+					<tr class="shaded-bg-color" style="font-weight: bold">
+						<td class="header" style="width:16px"></td>
+						<td class="header"><%=columns[0]%></td>
+						<td class="header"><%=columns[1]%></td>
+						<td class="header"><%=columns[2]%></td>
+					</tr>
+					<%
+					Node[] nodes = searchResults.getNodes();
+					int i = 0;
+					if (nodes.length > 0) {
+						for (Node node : nodes) {
+							%>
+							<tr <%=i++%2==1?"class=\"light-bg-color\"":""%>>
+								<td align="center">
+									<%Shared.showPending(out, node);%>
+								</td>
+								<td style="padding:.3em .4em;">
+									<a href="<%=Jtp.url(node)%>"><%=node.getSubjectHtml()%></a>
+								</td>
+								<td style="padding:.2em">
+									<%=node.getMailToList() == null? "" : Jtp.formatDateLong(node.getMailToList().getWhenSent())%>
+								</td>
+								<td style="padding:.2em">
+									<%=Jtp.link(node.getApp())%>
+								</td>
+							</tr>
+							<%
+						}
+					} else {
+						%>
+						<tr><td colspan=4 style="padding:.3em">None</td></tr>
+						<%
+					}
+					%>
+				</table>
+				<% HtmlViewUtils.genericPaging(request, response, searchResults.getCount(), iRec, MAX_ROWS, pagingPath, ".5em 0 0 0"); %>
+
+				<% Shared.footer(request,response); %>
+				<% Shared.analytics(request,response); %>
+			</body>
+		</html>
+		<%
+	}
+
+	private class SearchResults {
+		private int count;
+		private Node[] nodes;
+
+		public SearchResults() {}
+
+		public SearchResults(int count, Node[] nodes) {
+			this.count = count;
+			this.nodes = nodes;
+		}
+
+		public int getCount() { return count; }
+		public void setCount(int count) { this.count = count; }
+		public Node[] getNodes() { return nodes; }
+		public void setNodes(Node[] nodes) { this.nodes = nodes; }
+	}
+
+	private SearchResults cutResults(List<Node> array, int iRec) {
+		int i = iRec;
+		int count = 0;
+		List<Node> nodes = new ArrayList<Node>();
+		while (count < MAX_ROWS) {
+			if (array.size()-1 < i)
+				break;
+			nodes.add(array.get(i++));
+			count++;
+		}
+		Node[] nodesArray = nodes.toArray(new Node[0]);
+		return new SearchResults(array.size(), nodesArray);
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/Empty.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,19 @@
+
+package nabble.view.web.util;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import nabble.view.lib.Jtp;
+
+
+public final class Empty extends HttpServlet {
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Jtp.cacheMe(request,response);
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/Empty.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,19 @@
+<%
+package nabble.view.web.util;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import nabble.view.lib.Jtp;
+
+
+public final class Empty extends HttpServlet {
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Jtp.cacheMe(request,response);
+	}
+}
+%>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/GradientImage.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,108 @@
+package nabble.view.web.util;
+
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.UrlMappable;
+
+import javax.imageio.ImageIO;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.Color;
+import java.awt.GradientPaint;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Hugo Teixeira
+ */
+public class GradientImage extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/gradients/(v|h)(\\d+)_([A-Fa-f0-9]{6})([A-Fa-f0-9]{2})?_([A-Fa-f0-9]{6})([A-Fa-f0-9]{2})?$");
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		String direction = m.group(1);
+		params.put("direction",new String[]{"v".equals(direction)? "vertical" : "horizontal"});
+		String size = m.group(2);
+		params.put("size",new String[]{size});
+		String fromColor = m.group(3) + (m.group(4) == null? "" : m.group(4));
+		params.put( "from", new String[]{fromColor} );
+		String toColor = m.group(5) + (m.group(6) == null? "" : m.group(6));
+		params.put( "to", new String[]{toColor} );
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		OutputStream out = response.getOutputStream();
+		response.setContentType("image/png");
+
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+
+		String direction = request.getParameter("direction");
+		String size = request.getParameter("size");
+		String fromColor = request.getParameter("from");
+		String toColor = request.getParameter("to");
+
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		ImageIO.write(createImage(direction, size, fromColor, toColor), "png", baos);
+		final byte[] bytes = baos.toByteArray();
+
+		InputStream in = new ByteArrayInputStream(bytes);
+		try {
+			IoUtils.copyAll(in,out);
+		} finally {
+			in.close();
+		}
+	}
+
+	private RenderedImage createImage(String direction, String size, String fromColor, String toColor) {
+		boolean isVertical = "vertical".equals(direction);
+		int width = isVertical? 1 : Integer.valueOf(size);
+		int height = isVertical? Integer.valueOf(size) : 1;
+		Color from = parseColor(fromColor);
+		Color to = parseColor(toColor);
+		BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+		Graphics2D g2 = img.createGraphics();
+		GradientPaint paint;
+		if (isVertical)
+			paint = new GradientPaint(0, 0, from, 0, height, to);
+		else
+			paint = new GradientPaint(0, 0, from, width, 0, to);
+		g2.setPaint(paint);
+		g2.fillRect(0, 0, width, height);
+		g2.dispose();
+		return img;
+	}
+
+	private Color parseColor(String c) {
+		if (c.length() == 6)
+			return Color.decode("0x"+c);
+		int r = Integer.parseInt(c.substring(0,2), 16);
+		int g = Integer.parseInt(c.substring(2,4), 16);
+		int b = Integer.parseInt(c.substring(4,6), 16);
+		int a = Integer.parseInt(c.substring(6,8), 16);
+		return new Color(r, g, b, a);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/JsHolder.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,35 @@
+package nabble.view.web.util;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+
+public final class JsHolder extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		response.setHeader("Content-Type","application/x-javascript");
+		response.setHeader("Cache-Control","no-cache");
+		response.setHeader("Cache-Control","no-store");
+		response.setHeader("Cache-Control","max-age=0");
+		HttpSession session = request.getSession();
+		String key = "js-" + request.getParameter("key");
+		String js = request.getParameter("js");
+		if( js != null ) {
+			session.setAttribute(key,js);
+			return;
+		}
+		js = (String)session.getAttribute(key);
+		if( js==null )
+			return;
+		PrintWriter out = response.getWriter();
+		out.print(js);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/Redirect.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,18 @@
+package nabble.view.web.util;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import nabble.view.lib.Shared;
+
+
+public final class Redirect extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		Shared.javascriptRedirect(request,response,request.getParameter("url"));
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/Rmi.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,21 @@
+ package nabble.view.web.util;
+
+import nabble.model.Init;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+public final class Rmi extends HttpServlet {
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		if( Init.localRmiServer == null )
+			throw new RuntimeException("localRmiServer not set");
+		response.getWriter().print(Init.localRmiServer);
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/RoundedCorner.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,109 @@
+package nabble.view.web.util;
+
+import fschmidt.util.java.IoUtils;
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.UrlMappable;
+
+import javax.imageio.ImageIO;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.geom.Ellipse2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Quadrants
+ * 1 | 2
+ * 3 | 4
+ *
+ * @author Hugo Teixeira
+ */
+public class RoundedCorner extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/rounded/q(\\d)_([A-Fa-f0-9]{6})([A-Fa-f0-9]{2})?$");
+
+	public Map<String,String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if( !m.find() )
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		String quadrant = m.group(1);
+		params.put("quadrant",new String[]{quadrant});
+		String bgColor = m.group(2) + (m.group(3) == null? "" : m.group(3));
+		params.put( "bgColor", new String[]{bgColor} );
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+		throws ServletException, IOException {
+		OutputStream out = response.getOutputStream();
+		response.setContentType("image/png");
+
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+
+		int quadrant = Integer.valueOf(request.getParameter("quadrant"));
+		String bgColor = request.getParameter("bgColor");
+
+		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+		ImageIO.write(createImage(quadrant, bgColor), "png", baos);
+		final byte[] bytes = baos.toByteArray();
+
+		InputStream in = new ByteArrayInputStream(bytes);
+		try {
+			IoUtils.copyAll(in,out);
+		} finally {
+			in.close();
+		}
+	}
+
+	private RenderedImage createImage(int quadrant, String bgColor) {
+		int radius = 12;
+		int[] xy = { 0, 0 };
+		if (quadrant == 2) xy = new int[]{ -radius, 0 };
+		else if (quadrant == 3) xy = new int[]{ 0, -radius };
+		else if (quadrant == 4) xy = new int[]{ -radius, -radius };
+
+		Color bg = parseColor(bgColor);
+		BufferedImage img = new BufferedImage(radius, radius, BufferedImage.TYPE_INT_ARGB);
+		Graphics2D g2 = img.createGraphics();
+		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+		g2.setColor(bg);
+		g2.fillRect(0, 0, radius, radius);
+		g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.5f));
+		Shape s = new Ellipse2D.Float(xy[0], xy[1], 2 * radius, 2* radius);
+		g2.fill(s);
+		g2.dispose();
+		return img;
+	}
+
+	private Color parseColor(String c) {
+		if (c.length() == 6)
+			return Color.decode("0x"+c);
+		int r = Integer.parseInt(c.substring(0,2), 16);
+		int g = Integer.parseInt(c.substring(2,4), 16);
+		int b = Integer.parseInt(c.substring(4,6), 16);
+		int a = Integer.parseInt(c.substring(6,8), 16);
+		return new Color(r, g, b, a);
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/SessionService.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,92 @@
+package nabble.view.web.util;
+
+import nabble.view.lib.Jtp;
+import nabble.model.Executors;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+
+public final class SessionService extends HttpServlet {
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException
+	{
+		response.setHeader("Content-Type","application/x-javascript");
+		Jtp.dontCache(response);
+		String action = request.getParameter("action");
+		String clientID = request.getParameter("cid");
+		if (clientID == null)
+			return;
+
+		SessionData sessionData = getSessionData(clientID);
+		if ("set".equals(action)) {
+			String[] vs = request.getParameterValues("v");
+			for (String v : vs) {
+				int posPipe = v.indexOf('|');
+				String key = v.substring(0, posPipe);
+				String oldValue = sessionData.attributes.get(key);
+				String value = v.substring(posPipe+1);
+				value = oldValue == null? value : oldValue + value; // concat
+				sessionData.attributes.put(key, value);
+			}
+		} else if ("get".equals(action)) {
+			PrintWriter out = response.getWriter();
+			String[] keys = request.getParameterValues("key");
+			for (String key : keys) {
+				String value = sessionData.attributes.get(key);
+				if (value != null) {
+					out.print(value);
+					sessionData.attributes.remove(key);
+				}
+			}
+		}
+	}
+
+
+	private static final Map<String, SessionData> sessions = Collections.synchronizedMap(new HashMap<String, SessionData>());
+
+	private static synchronized SessionData getSessionData(String clientID) {
+		SessionData sessionData = sessions.get(clientID);
+		if (sessionData == null) {
+			sessionData = new SessionData();
+			sessions.put(clientID, sessionData);
+		}
+		sessionData.lastUsage = System.currentTimeMillis();
+		return sessionData;
+	}
+
+	private static class SessionData {
+		private long lastUsage;
+		private final Map<String, String> attributes = new HashMap<String, String>();
+	}
+
+	private static final long TIME_LIMIT = 2L * 60L * 1000L; // two minutes
+	static {
+		// Custom Garbage Collection algorithm:
+		// Removes SessionData objects that are old in memory.
+
+		// A - If the sessionData has no cookies, then it was used just for the iframe resize.
+		// So we can delete them after two minutes without problems. If the user tries to resize
+		// the iframe after that, another object will be created for the transfer.
+
+		Executors.scheduleWithFixedDelay(new Runnable(){public void run(){
+			synchronized(SessionService.class) {
+				long now = System.currentTimeMillis();
+				String[] keys = sessions.keySet().toArray(new String[0]);
+				for( String key : keys ) {
+					if( now - sessions.get(key).lastUsage > TIME_LIMIT )
+						sessions.remove(key);
+				}
+			}
+		}}, 5*60, 5*60, TimeUnit.SECONDS); // Every 5 minutes
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/VisitCounter.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,97 @@
+package nabble.view.web.util;
+
+import nabble.model.DailyNumber;
+import nabble.view.lib.Jtp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+public final class VisitCounter extends HttpServlet {
+	private static final Logger logger = LoggerFactory.getLogger(VisitCounter.class);
+
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws ServletException, IOException
+	{
+		response.setHeader("Content-Type", "application/x-javascript" );
+		Jtp.dontCache(response);
+		String referrer = request.getParameter("referrer");
+		logger.debug("VisitCounter referrer="+referrer);
+		String url = request.getHeader("Referer");
+		logger.debug("VisitCounter url="+url);
+		countVisit( request, referrer, url );
+	}
+
+	private static void countVisit(HttpServletRequest request,String referrer, String url) {
+		DailyNumber.totalVisits.inc();
+		if( referrer==null || referrer.equals("") ) {
+			DailyNumber.directVisits.inc();
+		} else if( isFromAd(url) ) {
+			DailyNumber.adVisits.inc();
+		} else if( isSearchEngine(referrer) ) {
+			DailyNumber.searchEngineVisits.inc();
+		} else {
+			DailyNumber.externalVisits.inc();
+		}
+	}
+
+	private static boolean isFromAd(String url) {
+		return url != null && url.indexOf("?keyword=") >= 0;
+	}
+
+	// logic from _uOrg() in urchin.js
+	private static boolean isSearchEngine(String referrer) {
+		int i = referrer.indexOf("://");
+		if( i == -1 )
+			return false;
+		String h = referrer.substring(i+3);
+		if (h.indexOf("/") > -1) {
+			h = h.substring(0,h.indexOf("/"));
+		}
+		for (SearchEngine se : searchEngines) {
+			boolean isSearchEngine = h.indexOf(se.name) > -1;
+			boolean isQuery = referrer.indexOf("?" + se.query + "=") > -1|| referrer.indexOf("&" + se.query + "=") > -1;
+			if (isSearchEngine && isQuery)
+				return true;
+		}
+		return false;
+	}
+
+	private static final class SearchEngine {
+		final String name;
+		final String query;
+
+		SearchEngine(String name,String query) {
+			this.name = name;
+			this.query = query;
+		}
+	}
+
+	private static final SearchEngine[] searchEngines = new SearchEngine[]{
+		new SearchEngine("google","q"),
+		new SearchEngine("yahoo","p"),
+		new SearchEngine("msn","q"),
+		new SearchEngine("aol","query"),
+		new SearchEngine("lycos","query"),
+		new SearchEngine("ask","q"),
+		new SearchEngine("altavista","q"),
+		new SearchEngine("search","q"),
+		new SearchEngine("netscape","query"),
+		new SearchEngine("earthlink","q"),
+		new SearchEngine("cnn","query"),
+		new SearchEngine("looksmart","key"),
+		new SearchEngine("about","terms"),
+		new SearchEngine("excite","qkw"),
+		new SearchEngine("mamma","query"),
+		new SearchEngine("alltheweb","q"),
+		new SearchEngine("gigablast","q"),
+		new SearchEngine("voila","kw"),
+		new SearchEngine("virgilio","qs"),
+		new SearchEngine("teoma","q"),
+	};
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/LICENSE	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,23 @@
+ Copyright (c) 2007-2010 Marijn Haverbeke
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any
+ damages arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any
+ purpose, including commercial applications, and to alter it and
+ redistribute it freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must
+    not claim that you wrote the original software. If you use this
+    software in a product, an acknowledgment in the product
+    documentation would be appreciated but is not required.
+
+ 2. Altered source versions must be plainly marked as such, and must
+    not be misrepresented as being the original software.
+
+ 3. This notice may not be removed or altered from any source
+    distribution.
+
+ Marijn Haverbeke
+ marijnh@gmail.com
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/css/docs.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,158 @@
+body {
+  font-family: Arial, sans-serif;
+  line-height: 1.5;
+  max-width: 64.3em;
+  margin: 3em auto;
+  padding: 0 1em;
+}
+body.droid {
+  font-family: Droid Sans, Arial, sans-serif;
+}
+
+h1 {
+  letter-spacing: -3px;
+  font-size: 3.23em;
+  font-weight: bold;
+  margin: 0;
+}
+
+h2 {
+  font-size: 1.23em;
+  font-weight: bold;
+  margin: .5em 0;
+  letter-spacing: -1px;
+}
+
+h3 {
+  font-size: 1em;
+  font-weight: bold;
+  margin: .4em 0;
+}
+
+pre {
+  font-family: Courier New, monospaced;
+  background-color: #eee;
+  -moz-border-radius: 6px;
+  -webkit-border-radius: 6px;
+  border-radius: 6px;
+  padding: 1em;
+}
+
+pre.code {
+  margin: 0 1em;
+}
+
+.grey {
+  font-size: 2em;
+  padding: .5em 1em;
+  line-height: 1.2em;
+  margin-top: .5em;
+  position: relative;
+}
+
+img.logo {
+  position: absolute;
+  right: -25px;
+  bottom: 4px;
+}
+
+a:link, a:visited, .quasilink {
+  color: #df0019;
+  cursor: pointer;
+  text-decoration: none;
+}
+
+a:hover, .quasilink:hover {
+  color: #800004;
+}
+
+h1 a:link, h1 a:visited, h1 a:hover {
+  color: black;
+}
+
+ul {
+  margin: 0;
+  padding-left: 1.2em;
+}
+
+a.download {
+  color: white;
+  background-color: #df0019;
+  width: 100%;
+  display: block;
+  text-align: center;
+  font-size: 1.23em;
+  font-weight: bold;
+  text-decoration: none;
+  -moz-border-radius: 6px;
+  -webkit-border-radius: 6px;
+  border-radius: 6px;
+  padding: .5em 0;
+  margin-bottom: 1em;
+}
+
+a.download:hover {
+  background-color: #bb0010;
+}
+
+.rel {
+  margin-bottom: 0;
+}
+
+.rel-note {
+  color: #777;
+  font-size: .9em;
+  margin-top: .1em;
+}
+
+.logo-braces {
+  color: #df0019;
+  position: relative;
+  top: -4px;
+}
+
+.blk {
+  float: left;
+}
+
+.left {
+  width: 37em;
+  padding-right: 6.53em;
+  padding-bottom: 1em;
+}
+
+.left1 {
+  width: 15.24em;
+  padding-right: 6.45em;
+}
+
+.left2 {
+  width: 15.24em;
+}
+
+.right {
+  width: 20.68em;
+}
+
+.leftbig {
+  width: 42.44em;
+  padding-right: 6.53em;
+}
+
+.rightsmall {
+  width: 15.24em;
+}
+
+.clear:after {
+  visibility: hidden;
+  display: block;
+  font-size: 0;
+  content: " ";
+  clear: both;
+  height: 0;
+}
+.clear { display: inline-block; }
+/* start commented backslash hack \*/
+* html .clear { height: 1%; }
+.clear { display: block; }
+/* close commented backslash hack */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/css/font.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,15 @@
+function waitForStyles() {
+  for (var i = 0; i < document.styleSheets.length; i++)
+    if (/googleapis/.test(document.styleSheets[i].href))
+      return document.body.className += " droid";
+  setTimeout(waitForStyles, 100);
+}
+setTimeout(function() {
+  if (/AppleWebKit/.test(navigator.userAgent) && /iP[oa]d|iPhone/.test(navigator.userAgent)) return;
+  var link = document.createElement("LINK");
+  link.type = "text/css";
+  link.rel = "stylesheet";
+  link.href = "http://fonts.googleapis.com/css?family=Droid+Sans|Droid+Sans:bold";
+  document.documentElement.getElementsByTagName("HEAD")[0].appendChild(link);
+  waitForStyles();
+}, 20);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/css/xmlcolors.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,20 @@
+html {cursor: text;}
+.editbox {
+	margin: 0 .4em .4em;
+	padding: 0;
+	font-family: verdana, arial, sans-serif;
+	font-size: 11pt;
+	color: black;
+	background-color:white;
+}
+.editbox p {margin: 0;}
+span.xml-tagname {color: #A0B;}
+span.xml-attribute {color: #281;}
+span.xml-punctuation {color: black;}
+span.xml-attname {color: #00F;}
+span.xml-comment {color: #A70;}
+span.xml-cdata {color: #48A;}
+span.xml-processing {color: #999;}
+span.xml-entity {color: #A22;}
+span.xml-error {color: #F00 !important;}
+span.xml-text {color: black;}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/codemirror.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,590 @@
+/* CodeMirror main module (http://codemirror.net/)
+ *
+ * Implements the CodeMirror constructor and prototype, which take care
+ * of initializing the editor frame, and providing the outside interface.
+ */
+
+// The CodeMirrorConfig object is used to specify a default
+// configuration. If you specify such an object before loading this
+// file, the values you put into it will override the defaults given
+// below. You can also assign to it after loading.
+var CodeMirrorConfig = window.CodeMirrorConfig || {};
+
+var CodeMirror = (function(){
+  function setDefaults(object, defaults) {
+    for (var option in defaults) {
+      if (!object.hasOwnProperty(option))
+        object[option] = defaults[option];
+    }
+  }
+  function forEach(array, action) {
+    for (var i = 0; i < array.length; i++)
+      action(array[i]);
+  }
+  function createHTMLElement(el) {
+    if (document.createElementNS && document.documentElement.namespaceURI !== null)
+      return document.createElementNS("http://www.w3.org/1999/xhtml", el)
+    else
+      return document.createElement(el)
+  }
+
+  // These default options can be overridden by passing a set of
+  // options to a specific CodeMirror constructor. See manual.html for
+  // their meaning.
+  setDefaults(CodeMirrorConfig, {
+    stylesheet: [],
+    path: "",
+    parserfile: [],
+    basefiles: ["util.js", "stringstream.js", "select.js", "undo.js", "editor.js", "tokenize.js"],
+    iframeClass: null,
+    passDelay: 200,
+    passTime: 50,
+    lineNumberDelay: 200,
+    lineNumberTime: 50,
+    continuousScanning: false,
+    saveFunction: null,
+    onLoad: null,
+    onChange: null,
+    undoDepth: 50,
+    undoDelay: 800,
+    disableSpellcheck: true,
+    textWrapping: true,
+    readOnly: false,
+    width: "",
+    height: "300px",
+    minHeight: 100,
+    onDynamicHeightChange: null,
+    autoMatchParens: false,
+    markParen: null,
+    unmarkParen: null,
+    parserConfig: null,
+    tabMode: "indent", // or "spaces", "default", "shift"
+    enterMode: "indent", // or "keep", "flat"
+    electricChars: true,
+    reindentOnLoad: false,
+    activeTokens: null,
+    onCursorActivity: null,
+    lineNumbers: false,
+    firstLineNumber: 1,
+    onLineNumberClick: null,
+    indentUnit: 2,
+    domain: null,
+    noScriptCaching: false,
+    incrementalLoading: false
+  });
+
+  function addLineNumberDiv(container, firstNum) {
+    var nums = createHTMLElement("div"),
+        scroller = createHTMLElement("div");
+    nums.style.position = "absolute";
+    nums.style.height = "100%";
+    if (nums.style.setExpression) {
+      try {nums.style.setExpression("height", "this.previousSibling.offsetHeight + 'px'");}
+      catch(e) {} // Seems to throw 'Not Implemented' on some IE8 versions
+    }
+    nums.style.top = "0px";
+    nums.style.left = "0px";
+    nums.style.overflow = "hidden";
+    container.appendChild(nums);
+    scroller.className = "CodeMirror-line-numbers";
+    nums.appendChild(scroller);
+    scroller.innerHTML = "<div>" + firstNum + "</div>";
+    return nums;
+  }
+
+  function frameHTML(options) {
+    if (typeof options.parserfile == "string")
+      options.parserfile = [options.parserfile];
+    if (typeof options.basefiles == "string")
+      options.basefiles = [options.basefiles];
+    if (typeof options.stylesheet == "string")
+      options.stylesheet = [options.stylesheet];
+
+    var sp = " spellcheck=\"" + (options.disableSpellcheck ? "false" : "true") + "\"";
+    var html = ["<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\"><html" + sp + "><head>"];
+    // Hack to work around a bunch of IE8-specific problems.
+    html.push("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=EmulateIE7\"/>");
+    var queryStr = options.noScriptCaching ? "?nocache=" + new Date().getTime().toString(16) : "";
+    forEach(options.stylesheet, function(file) {
+      html.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"" + file + queryStr + "\"/>");
+    });
+    forEach(options.basefiles.concat(options.parserfile), function(file) {
+      if (!/^https?:/.test(file)) file = options.path + file;
+      html.push("<script type=\"text/javascript\" src=\"" + file + queryStr + "\"><" + "/script>");
+    });
+    html.push("</head><body style=\"border-width: 0;\" class=\"editbox\"" + sp + "></body></html>");
+    return html.join("");
+  }
+
+  var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);
+
+  function CodeMirror(place, options) {
+    // Use passed options, if any, to override defaults.
+    this.options = options = options || {};
+    setDefaults(options, CodeMirrorConfig);
+
+    // Backward compatibility for deprecated options.
+    if (options.dumbTabs) options.tabMode = "spaces";
+    else if (options.normalTab) options.tabMode = "default";
+    if (options.cursorActivity) options.onCursorActivity = options.cursorActivity;
+
+    var frame = this.frame = createHTMLElement("iframe");
+    if (options.iframeClass) frame.className = options.iframeClass;
+    frame.frameBorder = 0;
+    frame.style.border = "0";
+    frame.style.width = '100%';
+    frame.style.height = '100%';
+    // display: block occasionally suppresses some Firefox bugs, so we
+    // always add it, redundant as it sounds.
+    frame.style.display = "block";
+
+    var div = this.wrapping = createHTMLElement("div");
+    div.style.position = "relative";
+    div.className = "CodeMirror-wrapping";
+    div.style.width = options.width;
+    div.style.height = (options.height == "dynamic") ? options.minHeight + "px" : options.height;
+    // This is used by Editor.reroutePasteEvent
+    var teHack = this.textareaHack = createHTMLElement("textarea");
+    div.appendChild(teHack);
+    teHack.style.position = "absolute";
+    teHack.style.left = "-10000px";
+    teHack.style.width = "10px";
+    teHack.tabIndex = 100000;
+
+    // Link back to this object, so that the editor can fetch options
+    // and add a reference to itself.
+    frame.CodeMirror = this;
+    if (options.domain && internetExplorer) {
+      this.html = frameHTML(options);
+      frame.src = "javascript:(function(){document.open();" +
+        (options.domain ? "document.domain=\"" + options.domain + "\";" : "") +
+        "document.write(window.frameElement.CodeMirror.html);document.close();})()";
+    }
+    else {
+      frame.src = "javascript:;";
+    }
+
+    if (place.appendChild) place.appendChild(div);
+    else place(div);
+    div.appendChild(frame);
+    if (options.lineNumbers) this.lineNumbers = addLineNumberDiv(div, options.firstLineNumber);
+
+    this.win = frame.contentWindow;
+    if (!options.domain || !internetExplorer) {
+      this.win.document.open();
+      this.win.document.write(frameHTML(options));
+      this.win.document.close();
+    }
+  }
+
+  CodeMirror.prototype = {
+    init: function() {
+      // Deprecated, but still supported.
+      if (this.options.initCallback) this.options.initCallback(this);
+      if (this.options.onLoad) this.options.onLoad(this);
+      if (this.options.lineNumbers) this.activateLineNumbers();
+      if (this.options.reindentOnLoad) this.reindent();
+      if (this.options.height == "dynamic") this.setDynamicHeight();
+    },
+
+    getCode: function() {return this.editor.getCode();},
+    setCode: function(code) {this.editor.importCode(code);},
+    selection: function() {this.focusIfIE(); return this.editor.selectedText();},
+    reindent: function() {this.editor.reindent();},
+    reindentSelection: function() {this.focusIfIE(); this.editor.reindentSelection(null);},
+
+    focusIfIE: function() {
+      // in IE, a lot of selection-related functionality only works when the frame is focused
+      if (this.win.select.ie_selection && document.activeElement != this.frame)
+        this.focus();
+    },
+    focus: function() {
+      this.win.focus();
+      if (this.editor.selectionSnapshot) // IE hack
+        this.win.select.setBookmark(this.win.document.body, this.editor.selectionSnapshot);
+    },
+    replaceSelection: function(text) {
+      this.focus();
+      this.editor.replaceSelection(text);
+      return true;
+    },
+    replaceChars: function(text, start, end) {
+      this.editor.replaceChars(text, start, end);
+    },
+    getSearchCursor: function(string, fromCursor, caseFold) {
+      return this.editor.getSearchCursor(string, fromCursor, caseFold);
+    },
+
+    undo: function() {this.editor.history.undo();},
+    redo: function() {this.editor.history.redo();},
+    historySize: function() {return this.editor.history.historySize();},
+    clearHistory: function() {this.editor.history.clear();},
+
+    grabKeys: function(callback, filter) {this.editor.grabKeys(callback, filter);},
+    ungrabKeys: function() {this.editor.ungrabKeys();},
+
+    setParser: function(name, parserConfig) {this.editor.setParser(name, parserConfig);},
+    setSpellcheck: function(on) {this.win.document.body.spellcheck = on;},
+    setStylesheet: function(names) {
+      if (typeof names === "string") names = [names];
+      var activeStylesheets = {};
+      var matchedNames = {};
+      var links = this.win.document.getElementsByTagName("link");
+      // Create hashes of active stylesheets and matched names.
+      // This is O(n^2) but n is expected to be very small.
+      for (var x = 0, link; link = links[x]; x++) {
+        if (link.rel.indexOf("stylesheet") !== -1) {
+          for (var y = 0; y < names.length; y++) {
+            var name = names[y];
+            if (link.href.substring(link.href.length - name.length) === name) {
+              activeStylesheets[link.href] = true;
+              matchedNames[name] = true;
+            }
+          }
+        }
+      }
+      // Activate the selected stylesheets and disable the rest.
+      for (var x = 0, link; link = links[x]; x++) {
+        if (link.rel.indexOf("stylesheet") !== -1) {
+          link.disabled = !(link.href in activeStylesheets);
+        }
+      }
+      // Create any new stylesheets.
+      for (var y = 0; y < names.length; y++) {
+        var name = names[y];
+        if (!(name in matchedNames)) {
+          var link = this.win.document.createElement("link");
+          link.rel = "stylesheet";
+          link.type = "text/css";
+          link.href = name;
+          this.win.document.getElementsByTagName('head')[0].appendChild(link);
+        }
+      }
+    },
+    setTextWrapping: function(on) {
+      if (on == this.options.textWrapping) return;
+      this.win.document.body.style.whiteSpace = on ? "" : "nowrap";
+      this.options.textWrapping = on;
+      if (this.lineNumbers) {
+        this.setLineNumbers(false);
+        this.setLineNumbers(true);
+      }
+    },
+    setIndentUnit: function(unit) {this.win.indentUnit = unit;},
+    setUndoDepth: function(depth) {this.editor.history.maxDepth = depth;},
+    setTabMode: function(mode) {this.options.tabMode = mode;},
+    setEnterMode: function(mode) {this.options.enterMode = mode;},
+    setLineNumbers: function(on) {
+      if (on && !this.lineNumbers) {
+        this.lineNumbers = addLineNumberDiv(this.wrapping,this.options.firstLineNumber);
+        this.activateLineNumbers();
+      }
+      else if (!on && this.lineNumbers) {
+        this.wrapping.removeChild(this.lineNumbers);
+        this.wrapping.style.paddingLeft = "";
+        this.lineNumbers = null;
+      }
+    },
+
+    cursorPosition: function(start) {this.focusIfIE(); return this.editor.cursorPosition(start);},
+    firstLine: function() {return this.editor.firstLine();},
+    lastLine: function() {return this.editor.lastLine();},
+    nextLine: function(line) {return this.editor.nextLine(line);},
+    prevLine: function(line) {return this.editor.prevLine(line);},
+    lineContent: function(line) {return this.editor.lineContent(line);},
+    setLineContent: function(line, content) {this.editor.setLineContent(line, content);},
+    removeLine: function(line){this.editor.removeLine(line);},
+    insertIntoLine: function(line, position, content) {this.editor.insertIntoLine(line, position, content);},
+    selectLines: function(startLine, startOffset, endLine, endOffset) {
+      this.win.focus();
+      this.editor.selectLines(startLine, startOffset, endLine, endOffset);
+    },
+    nthLine: function(n) {
+      var line = this.firstLine();
+      for (; n > 1 && line !== false; n--)
+        line = this.nextLine(line);
+      return line;
+    },
+    lineNumber: function(line) {
+      var num = 0;
+      while (line !== false) {
+        num++;
+        line = this.prevLine(line);
+      }
+      return num;
+    },
+    jumpToLine: function(line) {
+      if (typeof line == "number") line = this.nthLine(line);
+      this.selectLines(line, 0);
+      this.win.focus();
+    },
+    currentLine: function() { // Deprecated, but still there for backward compatibility
+      return this.lineNumber(this.cursorLine());
+    },
+    cursorLine: function() {
+      return this.cursorPosition().line;
+    },
+    cursorCoords: function(start) {return this.editor.cursorCoords(start);},
+
+    activateLineNumbers: function() {
+      var frame = this.frame, win = frame.contentWindow, doc = win.document, body = doc.body,
+          nums = this.lineNumbers, scroller = nums.firstChild, self = this;
+      var barWidth = null;
+
+      nums.onclick = function(e) {
+        var handler = self.options.onLineNumberClick;
+        if (handler) {
+          var div = (e || window.event).target || (e || window.event).srcElement;
+          var num = div == nums ? NaN : Number(div.innerHTML);
+          if (!isNaN(num)) handler(num, div);
+        }
+      };
+
+      function sizeBar() {
+        if (frame.offsetWidth == 0) return;
+        for (var root = frame; root.parentNode; root = root.parentNode){}
+        if (!nums.parentNode || root != document || !win.Editor) {
+          // Clear event handlers (their nodes might already be collected, so try/catch)
+          try{clear();}catch(e){}
+          clearInterval(sizeInterval);
+          return;
+        }
+
+        if (nums.offsetWidth != barWidth) {
+          barWidth = nums.offsetWidth;
+          frame.parentNode.style.paddingLeft = barWidth + "px";
+        }
+      }
+      function doScroll() {
+        nums.scrollTop = body.scrollTop || doc.documentElement.scrollTop || 0;
+      }
+      // Cleanup function, registered by nonWrapping and wrapping.
+      var clear = function(){};
+      sizeBar();
+      var sizeInterval = setInterval(sizeBar, 500);
+
+      function ensureEnoughLineNumbers(fill) {
+        var lineHeight = scroller.firstChild.offsetHeight;
+        if (lineHeight == 0) return;
+        var targetHeight = 50 + Math.max(body.offsetHeight, Math.max(frame.offsetHeight, body.scrollHeight || 0)),
+            lastNumber = Math.ceil(targetHeight / lineHeight);
+        for (var i = scroller.childNodes.length; i <= lastNumber; i++) {
+          var div = createHTMLElement("div");
+          div.appendChild(document.createTextNode(fill ? String(i + self.options.firstLineNumber) : "\u00a0"));
+          scroller.appendChild(div);
+        }
+      }
+
+      function nonWrapping() {
+        function update() {
+          ensureEnoughLineNumbers(true);
+          doScroll();
+        }
+        self.updateNumbers = update;
+        var onScroll = win.addEventHandler(win, "scroll", doScroll, true),
+            onResize = win.addEventHandler(win, "resize", update, true);
+        clear = function(){
+          onScroll(); onResize();
+          if (self.updateNumbers == update) self.updateNumbers = null;
+        };
+        update();
+      }
+
+      function wrapping() {
+        var node, lineNum, next, pos, changes = [], styleNums = self.options.styleNumbers;
+
+        function setNum(n, node) {
+          // Does not typically happen (but can, if you mess with the
+          // document during the numbering)
+          if (!lineNum) lineNum = scroller.appendChild(createHTMLElement("div"));
+          if (styleNums) styleNums(lineNum, node, n);
+          // Changes are accumulated, so that the document layout
+          // doesn't have to be recomputed during the pass
+          changes.push(lineNum); changes.push(n);
+          pos = lineNum.offsetHeight + lineNum.offsetTop;
+          lineNum = lineNum.nextSibling;
+        }
+        function commitChanges() {
+          for (var i = 0; i < changes.length; i += 2)
+            changes[i].innerHTML = changes[i + 1];
+          changes = [];
+        }
+        function work() {
+          if (!scroller.parentNode || scroller.parentNode != self.lineNumbers) return;
+
+          var endTime = new Date().getTime() + self.options.lineNumberTime;
+          while (node) {
+            setNum(next++, node.previousSibling);
+            for (; node && !win.isBR(node); node = node.nextSibling) {
+              var bott = node.offsetTop + node.offsetHeight;
+              while (scroller.offsetHeight && bott - 3 > pos) {
+                var oldPos = pos;
+                setNum("&nbsp;");
+                if (pos <= oldPos) break;
+              }
+            }
+            if (node) node = node.nextSibling;
+            if (new Date().getTime() > endTime) {
+              commitChanges();
+              pending = setTimeout(work, self.options.lineNumberDelay);
+              return;
+            }
+          }
+          while (lineNum) setNum(next++);
+          commitChanges();
+          doScroll();
+        }
+        function start(firstTime) {
+          doScroll();
+          ensureEnoughLineNumbers(firstTime);
+          node = body.firstChild;
+          lineNum = scroller.firstChild;
+          pos = 0;
+          next = self.options.firstLineNumber;
+          work();
+        }
+
+        start(true);
+        var pending = null;
+        function update() {
+          if (pending) clearTimeout(pending);
+          if (self.editor.allClean()) start();
+          else pending = setTimeout(update, 200);
+        }
+        self.updateNumbers = update;
+        var onScroll = win.addEventHandler(win, "scroll", doScroll, true),
+            onResize = win.addEventHandler(win, "resize", update, true);
+        clear = function(){
+          if (pending) clearTimeout(pending);
+          if (self.updateNumbers == update) self.updateNumbers = null;
+          onScroll();
+          onResize();
+        };
+      }
+      (this.options.textWrapping || this.options.styleNumbers ? wrapping : nonWrapping)();
+    },
+
+    setDynamicHeight: function() {
+      var self = this, activity = self.options.onCursorActivity, win = self.win, body = win.document.body,
+          lineHeight = null, timeout = null, vmargin = 2 * self.frame.offsetTop;
+      body.style.overflowY = "hidden";
+      win.document.documentElement.style.overflowY = "hidden";
+      this.frame.scrolling = "no";
+
+      function updateHeight() {
+        var trailingLines = 0, node = body.lastChild, computedHeight;
+        while (node && win.isBR(node)) {
+          if (!node.hackBR) trailingLines++;
+          node = node.previousSibling;
+        }
+        if (node) {
+          lineHeight = node.offsetHeight;
+          computedHeight = node.offsetTop + (1 + trailingLines) * lineHeight;
+        }
+        else if (lineHeight) {
+          computedHeight = trailingLines * lineHeight;
+        }
+        if (computedHeight) {
+          if (self.options.onDynamicHeightChange)
+            computedHeight = self.options.onDynamicHeightChange(computedHeight);
+          if (computedHeight)
+            self.wrapping.style.height = Math.max(vmargin + computedHeight, self.options.minHeight) + "px";
+        }
+      }
+      setTimeout(updateHeight, 300);
+      self.options.onCursorActivity = function(x) {
+        if (activity) activity(x);
+        clearTimeout(timeout);
+        timeout = setTimeout(updateHeight, 100);
+      };
+    }
+  };
+
+  CodeMirror.InvalidLineHandle = {toString: function(){return "CodeMirror.InvalidLineHandle";}};
+
+  CodeMirror.replace = function(element) {
+    if (typeof element == "string")
+      element = document.getElementById(element);
+    return function(newElement) {
+      element.parentNode.replaceChild(newElement, element);
+    };
+  };
+
+  CodeMirror.fromTextArea = function(area, options) {
+    if (typeof area == "string")
+      area = document.getElementById(area);
+
+    options = options || {};
+    if (area.style.width && options.width == null)
+      options.width = area.style.width;
+    if (area.style.height && options.height == null)
+      options.height = area.style.height;
+    if (options.content == null) options.content = area.value;
+
+    function updateField() {
+      area.value = mirror.getCode();
+    }
+    if (area.form) {
+      if (typeof area.form.addEventListener == "function")
+        area.form.addEventListener("submit", updateField, false);
+      else
+        area.form.attachEvent("onsubmit", updateField);
+      if (typeof area.form.submit == "function") {
+        var realSubmit = area.form.submit;
+        function wrapSubmit() {
+          updateField();
+          // Can't use realSubmit.apply because IE6 is too stupid
+          area.form.submit = realSubmit;
+          area.form.submit();
+          area.form.submit = wrapSubmit;
+        }
+        area.form.submit = wrapSubmit;
+      }
+    }
+
+    function insert(frame) {
+      if (area.nextSibling)
+        area.parentNode.insertBefore(frame, area.nextSibling);
+      else
+        area.parentNode.appendChild(frame);
+    }
+
+    area.style.display = "none";
+    var mirror = new CodeMirror(insert, options);
+    mirror.save = updateField;
+    mirror.toTextArea = function() {
+      updateField();
+      area.parentNode.removeChild(mirror.wrapping);
+      area.style.display = "";
+      if (area.form) {
+        if (typeof area.form.submit == "function")
+          area.form.submit = realSubmit;
+        if (typeof area.form.removeEventListener == "function")
+          area.form.removeEventListener("submit", updateField, false);
+        else
+          area.form.detachEvent("onsubmit", updateField);
+      }
+    };
+
+    return mirror;
+  };
+
+  CodeMirror.isProbablySupported = function() {
+    // This is rather awful, but can be useful.
+    var match;
+    if (window.opera)
+      return Number(window.opera.version()) >= 9.52;
+    else if (/Apple Computer, Inc/.test(navigator.vendor) && (match = navigator.userAgent.match(/Version\/(\d+(?:\.\d+)?)\./)))
+      return Number(match[1]) >= 3;
+    else if (document.selection && window.ActiveXObject && (match = navigator.userAgent.match(/MSIE (\d+(?:\.\d*)?)\b/)))
+      return Number(match[1]) >= 6;
+    else if (match = navigator.userAgent.match(/gecko\/(\d{8})/i))
+      return Number(match[1]) >= 20050901;
+    else if (match = navigator.userAgent.match(/AppleWebKit\/(\d+)/))
+      return Number(match[1]) >= 525;
+    else
+      return null;
+  };
+
+  return CodeMirror;
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/editor.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,1671 @@
+/* The Editor object manages the content of the editable frame. It
+ * catches events, colours nodes, and indents lines. This file also
+ * holds some functions for transforming arbitrary DOM structures into
+ * plain sequences of <span> and <br> elements
+ */
+
+var internetExplorer = document.selection && window.ActiveXObject && /MSIE/.test(navigator.userAgent);
+var webkit = /AppleWebKit/.test(navigator.userAgent);
+var safari = /Apple Computer, Inc/.test(navigator.vendor);
+var gecko = navigator.userAgent.match(/gecko\/(\d{8})/i);
+if (gecko) gecko = Number(gecko[1]);
+var mac = /Mac/.test(navigator.platform);
+
+// TODO this is related to the backspace-at-end-of-line bug. Remove
+// this if Opera gets their act together, make the version check more
+// broad if they don't.
+var brokenOpera = window.opera && /Version\/10.[56]/.test(navigator.userAgent);
+// TODO remove this once WebKit 533 becomes less common.
+var slowWebkit = /AppleWebKit\/533/.test(navigator.userAgent);
+
+// Make sure a string does not contain two consecutive 'collapseable'
+// whitespace characters.
+function makeWhiteSpace(n) {
+  var buffer = [], nb = true;
+  for (; n > 0; n--) {
+    buffer.push((nb || n == 1) ? nbsp : " ");
+    nb ^= true;
+  }
+  return buffer.join("");
+}
+
+// Create a set of white-space characters that will not be collapsed
+// by the browser, but will not break text-wrapping either.
+function fixSpaces(string) {
+  if (string.charAt(0) == " ") string = nbsp + string.slice(1);
+  return string.replace(/\t/g, function() {return makeWhiteSpace(indentUnit);})
+    .replace(/[ \u00a0]{2,}/g, function(s) {return makeWhiteSpace(s.length);});
+}
+
+function cleanText(text) {
+  return text.replace(/\u00a0/g, " ").replace(/\u200b/g, "");
+}
+
+// Create a SPAN node with the expected properties for document part
+// spans.
+function makePartSpan(value) {
+  var text = value;
+  if (value.nodeType == 3) text = value.nodeValue;
+  else value = document.createTextNode(text);
+
+  var span = document.createElement("span");
+  span.isPart = true;
+  span.appendChild(value);
+  span.currentText = text;
+  return span;
+}
+
+function alwaysZero() {return 0;}
+
+// On webkit, when the last BR of the document does not have text
+// behind it, the cursor can not be put on the line after it. This
+// makes pressing enter at the end of the document occasionally do
+// nothing (or at least seem to do nothing). To work around it, this
+// function makes sure the document ends with a span containing a
+// zero-width space character. The traverseDOM iterator filters such
+// character out again, so that the parsers won't see them. This
+// function is called from a few strategic places to make sure the
+// zwsp is restored after the highlighting process eats it.
+var webkitLastLineHack = webkit ?
+  function(container) {
+    var last = container.lastChild;
+    if (!last || !last.hackBR) {
+      var br = document.createElement("br");
+      br.hackBR = true;
+      container.appendChild(br);
+    }
+  } : function() {};
+
+function asEditorLines(string) {
+  var tab = makeWhiteSpace(indentUnit);
+  return map(string.replace(/\t/g, tab).replace(/\u00a0/g, " ").replace(/\r\n?/g, "\n").split("\n"), fixSpaces);
+}
+
+var Editor = (function(){
+  // The HTML elements whose content should be suffixed by a newline
+  // when converting them to flat text.
+  var newlineElements = {"P": true, "DIV": true, "LI": true};
+
+  // Helper function for traverseDOM. Flattens an arbitrary DOM node
+  // into an array of textnodes and <br> tags.
+  function simplifyDOM(root, atEnd) {
+    var result = [];
+    var leaving = true;
+
+    function simplifyNode(node, top) {
+      if (node.nodeType == 3) {
+        var text = node.nodeValue = fixSpaces(node.nodeValue.replace(/[\r\u200b]/g, "").replace(/\n/g, " "));
+        if (text.length) leaving = false;
+        result.push(node);
+      }
+      else if (isBR(node) && node.childNodes.length == 0) {
+        leaving = true;
+        result.push(node);
+      }
+      else {
+        for (var n = node.firstChild; n; n = n.nextSibling) simplifyNode(n);
+        if (!leaving && newlineElements.hasOwnProperty(node.nodeName.toUpperCase())) {
+          leaving = true;
+          if (!atEnd || !top)
+            result.push(document.createElement("br"));
+        }
+      }
+    }
+
+    simplifyNode(root, true);
+    return result;
+  }
+
+  // Creates a MochiKit-style iterator that goes over a series of DOM
+  // nodes. The values it yields are strings, the textual content of
+  // the nodes. It makes sure that all nodes up to and including the
+  // one whose text is being yielded have been 'normalized' to be just
+  // <span> and <br> elements.
+  function traverseDOM(start){
+    var nodeQueue = [];
+
+    // Create a function that can be used to insert nodes after the
+    // one given as argument.
+    function pointAt(node){
+      var parent = node.parentNode;
+      var next = node.nextSibling;
+      return function(newnode) {
+        parent.insertBefore(newnode, next);
+      };
+    }
+    var point = null;
+
+    // This an Opera-specific hack -- always insert an empty span
+    // between two BRs, because Opera's cursor code gets terribly
+    // confused when the cursor is between two BRs.
+    var afterBR = true;
+
+    // Insert a normalized node at the current point. If it is a text
+    // node, wrap it in a <span>, and give that span a currentText
+    // property -- this is used to cache the nodeValue, because
+    // directly accessing nodeValue is horribly slow on some browsers.
+    // The dirty property is used by the highlighter to determine
+    // which parts of the document have to be re-highlighted.
+    function insertPart(part){
+      var text = "\n";
+      if (part.nodeType == 3) {
+        select.snapshotChanged();
+        part = makePartSpan(part);
+        text = part.currentText;
+        afterBR = false;
+      }
+      else {
+        if (afterBR && window.opera)
+          point(makePartSpan(""));
+        afterBR = true;
+      }
+      part.dirty = true;
+      nodeQueue.push(part);
+      point(part);
+      return text;
+    }
+
+    // Extract the text and newlines from a DOM node, insert them into
+    // the document, and return the textual content. Used to replace
+    // non-normalized nodes.
+    function writeNode(node, end) {
+      var simplified = simplifyDOM(node, end);
+      for (var i = 0; i < simplified.length; i++)
+        simplified[i] = insertPart(simplified[i]);
+      return simplified.join("");
+    }
+
+    // Check whether a node is a normalized <span> element.
+    function partNode(node){
+      if (node.isPart && node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
+        var text = node.firstChild.nodeValue;
+        node.dirty = node.dirty || text != node.currentText;
+        node.currentText = text;
+        return !/[\n\t\r]/.test(node.currentText);
+      }
+      return false;
+    }
+
+    // Advance to next node, return string for current node.
+    function next() {
+      if (!start) throw StopIteration;
+      var node = start;
+      start = node.nextSibling;
+
+      if (partNode(node)){
+        nodeQueue.push(node);
+        afterBR = false;
+        return node.currentText;
+      }
+      else if (isBR(node)) {
+        if (afterBR && window.opera)
+          node.parentNode.insertBefore(makePartSpan(""), node);
+        nodeQueue.push(node);
+        afterBR = true;
+        return "\n";
+      }
+      else {
+        var end = !node.nextSibling;
+        point = pointAt(node);
+        removeElement(node);
+        return writeNode(node, end);
+      }
+    }
+
+    // MochiKit iterators are objects with a next function that
+    // returns the next value or throws StopIteration when there are
+    // no more values.
+    return {next: next, nodes: nodeQueue};
+  }
+
+  // Determine the text size of a processed node.
+  function nodeSize(node) {
+    return isBR(node) ? 1 : node.currentText.length;
+  }
+
+  // Search backwards through the top-level nodes until the next BR or
+  // the start of the frame.
+  function startOfLine(node) {
+    while (node && !isBR(node)) node = node.previousSibling;
+    return node;
+  }
+  function endOfLine(node, container) {
+    if (!node) node = container.firstChild;
+    else if (isBR(node)) node = node.nextSibling;
+
+    while (node && !isBR(node)) node = node.nextSibling;
+    return node;
+  }
+
+  function time() {return new Date().getTime();}
+
+  // Client interface for searching the content of the editor. Create
+  // these by calling CodeMirror.getSearchCursor. To use, call
+  // findNext on the resulting object -- this returns a boolean
+  // indicating whether anything was found, and can be called again to
+  // skip to the next find. Use the select and replace methods to
+  // actually do something with the found locations.
+  function SearchCursor(editor, pattern, from, caseFold) {
+    this.editor = editor;
+    this.history = editor.history;
+    this.history.commit();
+    this.valid = !!pattern;
+    this.atOccurrence = false;
+    if (caseFold == undefined) caseFold = typeof pattern == "string" && pattern == pattern.toLowerCase();
+
+    function getText(node){
+      var line = cleanText(editor.history.textAfter(node));
+      return (caseFold ? line.toLowerCase() : line);
+    }
+
+    var topPos = {node: null, offset: 0}, self = this;
+    if (from && typeof from == "object" && typeof from.character == "number") {
+      editor.checkLine(from.line);
+      var pos = {node: from.line, offset: from.character};
+      this.pos = {from: pos, to: pos};
+    }
+    else if (from) {
+      this.pos = {from: select.cursorPos(editor.container, true) || topPos,
+                  to: select.cursorPos(editor.container, false) || topPos};
+    }
+    else {
+      this.pos = {from: topPos, to: topPos};
+    }
+
+    if (typeof pattern != "string") { // Regexp match
+      this.matches = function(reverse, node, offset) {
+        if (reverse) {
+          var line = getText(node).slice(0, offset), match = line.match(pattern), start = 0;
+          while (match) {
+            var ind = line.indexOf(match[0]);
+            start += ind;
+            line = line.slice(ind + 1);
+            var newmatch = line.match(pattern);
+            if (newmatch) match = newmatch;
+            else break;
+          }
+        }
+        else {
+          var line = getText(node).slice(offset), match = line.match(pattern),
+              start = match && offset + line.indexOf(match[0]);
+        }
+        if (match) {
+          self.currentMatch = match;
+          return {from: {node: node, offset: start},
+                  to: {node: node, offset: start + match[0].length}};
+        }
+      };
+      return;
+    }
+
+    if (caseFold) pattern = pattern.toLowerCase();
+    // Create a matcher function based on the kind of string we have.
+    var target = pattern.split("\n");
+    this.matches = (target.length == 1) ?
+      // For one-line strings, searching can be done simply by calling
+      // indexOf or lastIndexOf on the current line.
+      function(reverse, node, offset) {
+        var line = getText(node), len = pattern.length, match;
+        if (reverse ? (offset >= len && (match = line.lastIndexOf(pattern, offset - len)) != -1)
+                    : (match = line.indexOf(pattern, offset)) != -1)
+          return {from: {node: node, offset: match},
+                  to: {node: node, offset: match + len}};
+      } :
+      // Multi-line strings require internal iteration over lines, and
+      // some clunky checks to make sure the first match ends at the
+      // end of the line and the last match starts at the start.
+      function(reverse, node, offset) {
+        var idx = (reverse ? target.length - 1 : 0), match = target[idx], line = getText(node);
+        var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match));
+        if (reverse ? offsetA >= offset || offsetA != match.length
+                    : offsetA <= offset || offsetA != line.length - match.length)
+          return;
+
+        var pos = node;
+        while (true) {
+          if (reverse && !pos) return;
+          pos = (reverse ? this.history.nodeBefore(pos) : this.history.nodeAfter(pos) );
+          if (!reverse && !pos) return;
+
+          line = getText(pos);
+          match = target[reverse ? --idx : ++idx];
+
+          if (idx > 0 && idx < target.length - 1) {
+            if (line != match) return;
+            else continue;
+          }
+          var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length);
+          if (reverse ? offsetB != line.length - match.length : offsetB != match.length)
+            return;
+          return {from: {node: reverse ? pos : node, offset: reverse ? offsetB : offsetA},
+                  to: {node: reverse ? node : pos, offset: reverse ? offsetA : offsetB}};
+        }
+      };
+  }
+
+  SearchCursor.prototype = {
+    findNext: function() {return this.find(false);},
+    findPrevious: function() {return this.find(true);},
+
+    find: function(reverse) {
+      if (!this.valid) return false;
+
+      var self = this, pos = reverse ? this.pos.from : this.pos.to,
+          node = pos.node, offset = pos.offset;
+      // Reset the cursor if the current line is no longer in the DOM tree.
+      if (node && !node.parentNode) {
+        node = null; offset = 0;
+      }
+      function savePosAndFail() {
+        var pos = {node: node, offset: offset};
+        self.pos = {from: pos, to: pos};
+        self.atOccurrence = false;
+        return false;
+      }
+
+      while (true) {
+        if (this.pos = this.matches(reverse, node, offset)) {
+          this.atOccurrence = true;
+          return true;
+        }
+
+        if (reverse) {
+          if (!node) return savePosAndFail();
+          node = this.history.nodeBefore(node);
+          offset = this.history.textAfter(node).length;
+        }
+        else {
+          var next = this.history.nodeAfter(node);
+          if (!next) {
+            offset = this.history.textAfter(node).length;
+            return savePosAndFail();
+          }
+          node = next;
+          offset = 0;
+        }        
+      }
+    },
+
+    select: function() {
+      if (this.atOccurrence) {
+        select.setCursorPos(this.editor.container, this.pos.from, this.pos.to);
+        select.scrollToCursor(this.editor.container);
+      }
+    },
+
+    replace: function(string) {
+      if (this.atOccurrence) {
+        var fragments = this.currentMatch;
+        if (fragments)
+          string = string.replace(/\\(\d)/, function(m, i){return fragments[i];});
+        var end = this.editor.replaceRange(this.pos.from, this.pos.to, string);
+        this.pos.to = end;
+        this.atOccurrence = false;
+      }
+    },
+
+    position: function() {
+      if (this.atOccurrence)
+        return {line: this.pos.from.node, character: this.pos.from.offset};
+    }
+  };
+
+  // The Editor object is the main inside-the-iframe interface.
+  function Editor(options) {
+    this.options = options;
+    window.indentUnit = options.indentUnit;
+    var container = this.container = document.body;
+    this.history = new UndoHistory(container, options.undoDepth, options.undoDelay, this);
+    var self = this;
+
+    if (!Editor.Parser)
+      throw "No parser loaded.";
+    if (options.parserConfig && Editor.Parser.configure)
+      Editor.Parser.configure(options.parserConfig);
+
+    if (!options.readOnly && !internetExplorer)
+      select.setCursorPos(container, {node: null, offset: 0});
+
+    this.dirty = [];
+    this.importCode(options.content || "");
+    this.history.onChange = options.onChange;
+
+    if (!options.readOnly) {
+      if (options.continuousScanning !== false) {
+        this.scanner = this.documentScanner(options.passTime);
+        this.delayScanning();
+      }
+
+      function setEditable() {
+        // Use contentEditable instead of designMode on IE, since designMode frames
+        // can not run any scripts. It would be nice if we could use contentEditable
+        // everywhere, but it is significantly flakier than designMode on every
+        // single non-IE browser.
+        if (document.body.contentEditable != undefined && internetExplorer)
+          document.body.contentEditable = "true";
+        else
+          document.designMode = "on";
+
+        // Work around issue where you have to click on the actual
+        // body of the document to focus it in IE, making focusing
+        // hard when the document is small.
+        if (internetExplorer && options.height != "dynamic")
+          document.body.style.minHeight = (
+            window.frameElement.clientHeight - 2 * document.body.offsetTop - 5) + "px";
+
+        document.documentElement.style.borderWidth = "0";
+        if (!options.textWrapping)
+          container.style.whiteSpace = "nowrap";
+      }
+
+      // If setting the frame editable fails, try again when the user
+      // focus it (happens when the frame is not visible on
+      // initialisation, in Firefox).
+      try {
+        setEditable();
+      }
+      catch(e) {
+        var focusEvent = addEventHandler(document, "focus", function() {
+          focusEvent();
+          setEditable();
+        }, true);
+      }
+
+      addEventHandler(document, "keydown", method(this, "keyDown"));
+      addEventHandler(document, "keypress", method(this, "keyPress"));
+      addEventHandler(document, "keyup", method(this, "keyUp"));
+
+      function cursorActivity() {self.cursorActivity(false);}
+      addEventHandler(internetExplorer ? document.body : window, "mouseup", cursorActivity);
+      addEventHandler(document.body, "cut", cursorActivity);
+
+      // workaround for a gecko bug [?] where going forward and then
+      // back again breaks designmode (no more cursor)
+      if (gecko)
+        addEventHandler(window, "pagehide", function(){self.unloaded = true;});
+
+      addEventHandler(document.body, "paste", function(event) {
+        cursorActivity();
+        var text = null;
+        try {
+          var clipboardData = event.clipboardData || window.clipboardData;
+          if (clipboardData) text = clipboardData.getData('Text');
+        }
+        catch(e) {}
+        if (text !== null) {
+          event.stop();
+          self.replaceSelection(text);
+          select.scrollToCursor(self.container);
+        }
+      });
+
+      if (this.options.autoMatchParens)
+        addEventHandler(document.body, "click", method(this, "scheduleParenHighlight"));
+    }
+    else if (!options.textWrapping) {
+      container.style.whiteSpace = "nowrap";
+    }
+  }
+
+  function isSafeKey(code) {
+    return (code >= 16 && code <= 18) || // shift, control, alt
+           (code >= 33 && code <= 40); // arrows, home, end
+  }
+
+  Editor.prototype = {
+    // Import a piece of code into the editor.
+    importCode: function(code) {
+      var lines = asEditorLines(code), chunk = 1000;
+      if (!this.options.incrementalLoading || lines.length < chunk) {
+        this.history.push(null, null, lines);
+        this.history.reset();
+      }
+      else {
+        var cur = 0, self = this;
+        function addChunk() {
+          var chunklines = lines.slice(cur, cur + chunk);
+          chunklines.push("");
+          self.history.push(self.history.nodeBefore(null), null, chunklines);
+          self.history.reset();
+          cur += chunk;
+          if (cur < lines.length)
+            parent.setTimeout(addChunk, 1000);
+        }
+        addChunk();
+      }
+    },
+
+    // Extract the code from the editor.
+    getCode: function() {
+      if (!this.container.firstChild)
+        return "";
+
+      var accum = [];
+      select.markSelection();
+      forEach(traverseDOM(this.container.firstChild), method(accum, "push"));
+      select.selectMarked();
+      // On webkit, don't count last (empty) line if the webkitLastLineHack BR is present
+      if (webkit && this.container.lastChild.hackBR)
+        accum.pop();
+      webkitLastLineHack(this.container);
+      return cleanText(accum.join(""));
+    },
+
+    checkLine: function(node) {
+      if (node === false || !(node == null || node.parentNode == this.container || node.hackBR))
+        throw parent.CodeMirror.InvalidLineHandle;
+    },
+
+    cursorPosition: function(start) {
+      if (start == null) start = true;
+      var pos = select.cursorPos(this.container, start);
+      if (pos) return {line: pos.node, character: pos.offset};
+      else return {line: null, character: 0};
+    },
+
+    firstLine: function() {
+      return null;
+    },
+
+    lastLine: function() {
+      var last = this.container.lastChild;
+      if (last) last = startOfLine(last);
+      if (last && last.hackBR) last = startOfLine(last.previousSibling);
+      return last;
+    },
+
+    nextLine: function(line) {
+      this.checkLine(line);
+      var end = endOfLine(line, this.container);
+      if (!end || end.hackBR) return false;
+      else return end;
+    },
+
+    prevLine: function(line) {
+      this.checkLine(line);
+      if (line == null) return false;
+      return startOfLine(line.previousSibling);
+    },
+
+    visibleLineCount: function() {
+      var line = this.container.firstChild;
+      while (line && isBR(line)) line = line.nextSibling; // BR heights are unreliable
+      if (!line) return false;
+      var innerHeight = (window.innerHeight
+                         || document.documentElement.clientHeight
+                         || document.body.clientHeight);
+      return Math.floor(innerHeight / line.offsetHeight);
+    },
+
+    selectLines: function(startLine, startOffset, endLine, endOffset) {
+      this.checkLine(startLine);
+      var start = {node: startLine, offset: startOffset}, end = null;
+      if (endOffset !== undefined) {
+        this.checkLine(endLine);
+        end = {node: endLine, offset: endOffset};
+      }
+      select.setCursorPos(this.container, start, end);
+      select.scrollToCursor(this.container);
+    },
+
+    lineContent: function(line) {
+      var accum = [];
+      for (line = line ? line.nextSibling : this.container.firstChild;
+           line && !isBR(line); line = line.nextSibling)
+        accum.push(nodeText(line));
+      return cleanText(accum.join(""));
+    },
+
+    setLineContent: function(line, content) {
+      this.history.commit();
+      this.replaceRange({node: line, offset: 0},
+                        {node: line, offset: this.history.textAfter(line).length},
+                        content);
+      this.addDirtyNode(line);
+      this.scheduleHighlight();
+    },
+
+    removeLine: function(line) {
+      var node = line ? line.nextSibling : this.container.firstChild;
+      while (node) {
+        var next = node.nextSibling;
+        removeElement(node);
+        if (isBR(node)) break;
+        node = next;
+      }
+      this.addDirtyNode(line);
+      this.scheduleHighlight();
+    },
+
+    insertIntoLine: function(line, position, content) {
+      var before = null;
+      if (position == "end") {
+        before = endOfLine(line, this.container);
+      }
+      else {
+        for (var cur = line ? line.nextSibling : this.container.firstChild; cur; cur = cur.nextSibling) {
+          if (position == 0) {
+            before = cur;
+            break;
+          }
+          var text = nodeText(cur);
+          if (text.length > position) {
+            before = cur.nextSibling;
+            content = text.slice(0, position) + content + text.slice(position);
+            removeElement(cur);
+            break;
+          }
+          position -= text.length;
+        }
+      }
+
+      var lines = asEditorLines(content);
+      for (var i = 0; i < lines.length; i++) {
+        if (i > 0) this.container.insertBefore(document.createElement("BR"), before);
+        this.container.insertBefore(makePartSpan(lines[i]), before);
+      }
+      this.addDirtyNode(line);
+      this.scheduleHighlight();
+    },
+
+    // Retrieve the selected text.
+    selectedText: function() {
+      var h = this.history;
+      h.commit();
+
+      var start = select.cursorPos(this.container, true),
+          end = select.cursorPos(this.container, false);
+      if (!start || !end) return "";
+
+      if (start.node == end.node)
+        return h.textAfter(start.node).slice(start.offset, end.offset);
+
+      var text = [h.textAfter(start.node).slice(start.offset)];
+      for (var pos = h.nodeAfter(start.node); pos != end.node; pos = h.nodeAfter(pos))
+        text.push(h.textAfter(pos));
+      text.push(h.textAfter(end.node).slice(0, end.offset));
+      return cleanText(text.join("\n"));
+    },
+
+    // Replace the selection with another piece of text.
+    replaceSelection: function(text) {
+      this.history.commit();
+
+      var start = select.cursorPos(this.container, true),
+          end = select.cursorPos(this.container, false);
+      if (!start || !end) return;
+
+      end = this.replaceRange(start, end, text);
+      select.setCursorPos(this.container, end);
+      webkitLastLineHack(this.container);
+    },
+
+    cursorCoords: function(start, internal) {
+      var sel = select.cursorPos(this.container, start);
+      if (!sel) return null;
+      var off = sel.offset, node = sel.node, self = this;
+      function measureFromNode(node, xOffset) {
+        var y = -(document.body.scrollTop || document.documentElement.scrollTop || 0),
+            x = -(document.body.scrollLeft || document.documentElement.scrollLeft || 0) + xOffset;
+        forEach([node, internal ? null : window.frameElement], function(n) {
+          while (n) {x += n.offsetLeft; y += n.offsetTop;n = n.offsetParent;}
+        });
+        return {x: x, y: y, yBot: y + node.offsetHeight};
+      }
+      function withTempNode(text, f) {
+        var node = document.createElement("SPAN");
+        node.appendChild(document.createTextNode(text));
+        try {return f(node);}
+        finally {if (node.parentNode) node.parentNode.removeChild(node);}
+      }
+
+      while (off) {
+        node = node ? node.nextSibling : this.container.firstChild;
+        var txt = nodeText(node);
+        if (off < txt.length)
+          return withTempNode(txt.substr(0, off), function(tmp) {
+            tmp.style.position = "absolute"; tmp.style.visibility = "hidden";
+            tmp.className = node.className;
+            self.container.appendChild(tmp);
+            return measureFromNode(node, tmp.offsetWidth);
+          });
+        off -= txt.length;
+      }
+      if (node && isSpan(node))
+        return measureFromNode(node, node.offsetWidth);
+      else if (node && node.nextSibling && isSpan(node.nextSibling))
+        return measureFromNode(node.nextSibling, 0);
+      else
+        return withTempNode("\u200b", function(tmp) {
+          if (node) node.parentNode.insertBefore(tmp, node.nextSibling);
+          else self.container.insertBefore(tmp, self.container.firstChild);
+          return measureFromNode(tmp, 0);
+        });
+    },
+
+    reroutePasteEvent: function() {
+      if (this.capturingPaste || window.opera || (gecko && gecko >= 20101026)) return;
+      this.capturingPaste = true;
+      var te = window.frameElement.CodeMirror.textareaHack;
+      var coords = this.cursorCoords(true, true);
+      te.style.top = coords.y + "px";
+      if (internetExplorer) {
+        var snapshot = select.getBookmark(this.container);
+        if (snapshot) this.selectionSnapshot = snapshot;
+      }
+      parent.focus();
+      te.value = "";
+      te.focus();
+
+      var self = this;
+      parent.setTimeout(function() {
+        self.capturingPaste = false;
+        window.focus();
+        if (self.selectionSnapshot) // IE hack
+          window.select.setBookmark(self.container, self.selectionSnapshot);
+        var text = te.value;
+        if (text) {
+          self.replaceSelection(text);
+          select.scrollToCursor(self.container);
+        }
+      }, 10);
+    },
+
+    replaceRange: function(from, to, text) {
+      var lines = asEditorLines(text);
+      lines[0] = this.history.textAfter(from.node).slice(0, from.offset) + lines[0];
+      var lastLine = lines[lines.length - 1];
+      lines[lines.length - 1] = lastLine + this.history.textAfter(to.node).slice(to.offset);
+      var end = this.history.nodeAfter(to.node);
+      this.history.push(from.node, end, lines);
+      return {node: this.history.nodeBefore(end),
+              offset: lastLine.length};
+    },
+
+    getSearchCursor: function(string, fromCursor, caseFold) {
+      return new SearchCursor(this, string, fromCursor, caseFold);
+    },
+
+    // Re-indent the whole buffer
+    reindent: function() {
+      if (this.container.firstChild)
+        this.indentRegion(null, this.container.lastChild);
+    },
+
+    reindentSelection: function(direction) {
+      if (!select.somethingSelected()) {
+        this.indentAtCursor(direction);
+      }
+      else {
+        var start = select.selectionTopNode(this.container, true),
+            end = select.selectionTopNode(this.container, false);
+        if (start === false || end === false) return;
+        this.indentRegion(start, end, direction, true);
+      }
+    },
+
+    grabKeys: function(eventHandler, filter) {
+      this.frozen = eventHandler;
+      this.keyFilter = filter;
+    },
+    ungrabKeys: function() {
+      this.frozen = "leave";
+    },
+
+    setParser: function(name, parserConfig) {
+      Editor.Parser = window[name];
+      parserConfig = parserConfig || this.options.parserConfig;
+      if (parserConfig && Editor.Parser.configure)
+        Editor.Parser.configure(parserConfig);
+
+      if (this.container.firstChild) {
+        forEach(this.container.childNodes, function(n) {
+          if (n.nodeType != 3) n.dirty = true;
+        });
+        this.addDirtyNode(this.firstChild);
+        this.scheduleHighlight();
+      }
+    },
+
+    // Intercept enter and tab, and assign their new functions.
+    keyDown: function(event) {
+      if (this.frozen == "leave") {this.frozen = null; this.keyFilter = null;}
+      if (this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode, event))) {
+        event.stop();
+        this.frozen(event);
+        return;
+      }
+
+      var code = event.keyCode;
+      // Don't scan when the user is typing.
+      this.delayScanning();
+      // Schedule a paren-highlight event, if configured.
+      if (this.options.autoMatchParens)
+        this.scheduleParenHighlight();
+
+      // The various checks for !altKey are there because AltGr sets both
+      // ctrlKey and altKey to true, and should not be recognised as
+      // Control.
+      if (code == 13) { // enter
+        if (event.ctrlKey && !event.altKey) {
+          this.reparseBuffer();
+        }
+        else {
+          select.insertNewlineAtCursor();
+          var mode = this.options.enterMode;
+          if (mode != "flat") this.indentAtCursor(mode == "keep" ? "keep" : undefined);
+          select.scrollToCursor(this.container);
+        }
+        event.stop();
+      }
+      else if (code == 9 && this.options.tabMode != "default" && !event.ctrlKey) { // tab
+        this.handleTab(!event.shiftKey);
+        event.stop();
+      }
+      else if (code == 32 && event.shiftKey && this.options.tabMode == "default") { // space
+        this.handleTab(true);
+        event.stop();
+      }
+      else if (code == 36 && !event.shiftKey && !event.ctrlKey) { // home
+        if (this.home()) event.stop();
+      }
+      else if (code == 35 && !event.shiftKey && !event.ctrlKey) { // end
+        if (this.end()) event.stop();
+      }
+      // Only in Firefox is the default behavior for PgUp/PgDn correct.
+      else if (code == 33 && !event.shiftKey && !event.ctrlKey && !gecko) { // PgUp
+        if (this.pageUp()) event.stop();
+      }
+      else if (code == 34 && !event.shiftKey && !event.ctrlKey && !gecko) {  // PgDn
+        if (this.pageDown()) event.stop();
+      }
+      else if ((code == 219 || code == 221) && event.ctrlKey && !event.altKey) { // [, ]
+        this.highlightParens(event.shiftKey, true);
+        event.stop();
+      }
+      else if (event.metaKey && !event.shiftKey && (code == 37 || code == 39)) { // Meta-left/right
+        var cursor = select.selectionTopNode(this.container);
+        if (cursor === false || !this.container.firstChild) return;
+
+        if (code == 37) select.focusAfterNode(startOfLine(cursor), this.container);
+        else {
+          var end = endOfLine(cursor, this.container);
+          select.focusAfterNode(end ? end.previousSibling : this.container.lastChild, this.container);
+        }
+        event.stop();
+      }
+      else if ((event.ctrlKey || event.metaKey) && !event.altKey) {
+        if ((event.shiftKey && code == 90) || code == 89) { // shift-Z, Y
+          select.scrollToNode(this.history.redo());
+          event.stop();
+        }
+        else if (code == 90 || (safari && code == 8)) { // Z, backspace
+          select.scrollToNode(this.history.undo());
+          event.stop();
+        }
+        else if (code == 83 && this.options.saveFunction) { // S
+          this.options.saveFunction();
+          event.stop();
+        }
+        else if (code == 86 && !mac) { // V
+          this.reroutePasteEvent();
+        }
+      }
+    },
+
+    // Check for characters that should re-indent the current line,
+    // and prevent Opera from handling enter and tab anyway.
+    keyPress: function(event) {
+      var electric = this.options.electricChars && Editor.Parser.electricChars, self = this;
+      // Hack for Opera, and Firefox on OS X, in which stopping a
+      // keydown event does not prevent the associated keypress event
+      // from happening, so we have to cancel enter and tab again
+      // here.
+      if ((this.frozen && (!this.keyFilter || this.keyFilter(event.keyCode || event.code, event))) ||
+          event.code == 13 || (event.code == 9 && this.options.tabMode != "default") ||
+          (event.code == 32 && event.shiftKey && this.options.tabMode == "default"))
+        event.stop();
+      else if (mac && (event.ctrlKey || event.metaKey) && event.character == "v") {
+        this.reroutePasteEvent();
+      }
+      else if (electric && electric.indexOf(event.character) != -1)
+        parent.setTimeout(function(){self.indentAtCursor(null);}, 0);
+      // Work around a bug where pressing backspace at the end of a
+      // line, or delete at the start, often causes the cursor to jump
+      // to the start of the line in Opera 10.60.
+      else if (brokenOpera) {
+        if (event.code == 8) { // backspace
+          var sel = select.selectionTopNode(this.container), self = this,
+              next = sel ? sel.nextSibling : this.container.firstChild;
+          if (sel !== false && next && isBR(next))
+            parent.setTimeout(function(){
+              if (select.selectionTopNode(self.container) == next)
+                select.focusAfterNode(next.previousSibling, self.container);
+            }, 20);
+        }
+        else if (event.code == 46) { // delete
+          var sel = select.selectionTopNode(this.container), self = this;
+          if (sel && isBR(sel)) {
+            parent.setTimeout(function(){
+              if (select.selectionTopNode(self.container) != sel)
+                select.focusAfterNode(sel, self.container);
+            }, 20);
+          }
+        }
+      }
+      // In 533.* WebKit versions, when the document is big, typing
+      // something at the end of a line causes the browser to do some
+      // kind of stupid heavy operation, creating delays of several
+      // seconds before the typed characters appear. This very crude
+      // hack inserts a temporary zero-width space after the cursor to
+      // make it not be at the end of the line.
+      else if (slowWebkit) {
+        var sel = select.selectionTopNode(this.container),
+            next = sel ? sel.nextSibling : this.container.firstChild;
+        // Doesn't work on empty lines, for some reason those always
+        // trigger the delay.
+        if (sel && next && isBR(next) && !isBR(sel)) {
+          var cheat = document.createTextNode("\u200b");
+          this.container.insertBefore(cheat, next);
+          parent.setTimeout(function() {
+            if (cheat.nodeValue == "\u200b") removeElement(cheat);
+            else cheat.nodeValue = cheat.nodeValue.replace("\u200b", "");
+          }, 20);
+        }
+      }
+
+      // Magic incantation that works abound a webkit bug when you
+      // can't type on a blank line following a line that's wider than
+      // the window.
+      if (webkit && !this.options.textWrapping)
+        setTimeout(function () {
+          var node = select.selectionTopNode(self.container, true);
+          if (node && node.nodeType == 3 && node.previousSibling && isBR(node.previousSibling)
+              && node.nextSibling && isBR(node.nextSibling))
+            node.parentNode.replaceChild(document.createElement("BR"), node.previousSibling);
+        }, 50);
+    },
+
+    // Mark the node at the cursor dirty when a non-safe key is
+    // released.
+    keyUp: function(event) {
+      this.cursorActivity(isSafeKey(event.keyCode));
+    },
+
+    // Indent the line following a given <br>, or null for the first
+    // line. If given a <br> element, this must have been highlighted
+    // so that it has an indentation method. Returns the whitespace
+    // element that has been modified or created (if any).
+    indentLineAfter: function(start, direction) {
+      function whiteSpaceAfter(node) {
+        var ws = node ? node.nextSibling : self.container.firstChild;
+        if (!ws || !hasClass(ws, "whitespace")) return null;
+        return ws;
+      }
+
+      // whiteSpace is the whitespace span at the start of the line,
+      // or null if there is no such node.
+      var self = this, whiteSpace = whiteSpaceAfter(start);
+      var newIndent = 0, curIndent = whiteSpace ? whiteSpace.currentText.length : 0;
+
+      var firstText = whiteSpace ? whiteSpace.nextSibling : (start ? start.nextSibling : this.container.firstChild);
+      if (direction == "keep") {
+        if (start) {
+          var prevWS = whiteSpaceAfter(startOfLine(start.previousSibling))
+          if (prevWS) newIndent = prevWS.currentText.length;
+        }
+      }
+      else {
+        // Sometimes the start of the line can influence the correct
+        // indentation, so we retrieve it.
+        var nextChars = (start && firstText && firstText.currentText) ? firstText.currentText : "";
+
+        // Ask the lexical context for the correct indentation, and
+        // compute how much this differs from the current indentation.
+        if (direction != null && this.options.tabMode != "indent")
+          newIndent = direction ? curIndent + indentUnit : Math.max(0, curIndent - indentUnit)
+        else if (start)
+          newIndent = start.indentation(nextChars, curIndent, direction, firstText);
+        else if (Editor.Parser.firstIndentation)
+          newIndent = Editor.Parser.firstIndentation(nextChars, curIndent, direction, firstText);
+      }
+      
+      var indentDiff = newIndent - curIndent;
+
+      // If there is too much, this is just a matter of shrinking a span.
+      if (indentDiff < 0) {
+        if (newIndent == 0) {
+          if (firstText) select.snapshotMove(whiteSpace.firstChild, firstText.firstChild || firstText, 0);
+          removeElement(whiteSpace);
+          whiteSpace = null;
+        }
+        else {
+          select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, indentDiff, true);
+          whiteSpace.currentText = makeWhiteSpace(newIndent);
+          whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
+        }
+      }
+      // Not enough...
+      else if (indentDiff > 0) {
+        // If there is whitespace, we grow it.
+        if (whiteSpace) {
+          whiteSpace.currentText = makeWhiteSpace(newIndent);
+          whiteSpace.firstChild.nodeValue = whiteSpace.currentText;
+          select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, indentDiff, true);
+        }
+        // Otherwise, we have to add a new whitespace node.
+        else {
+          whiteSpace = makePartSpan(makeWhiteSpace(newIndent));
+          whiteSpace.className = "whitespace";
+          if (start) insertAfter(whiteSpace, start);
+          else this.container.insertBefore(whiteSpace, this.container.firstChild);
+          select.snapshotMove(firstText && (firstText.firstChild || firstText),
+                              whiteSpace.firstChild, newIndent, false, true);
+        }
+      }
+      // Make sure cursor ends up after the whitespace
+      else if (whiteSpace) {
+	select.snapshotMove(whiteSpace.firstChild, whiteSpace.firstChild, newIndent, false);
+      }
+      if (indentDiff != 0) this.addDirtyNode(start);
+    },
+
+    // Re-highlight the selected part of the document.
+    highlightAtCursor: function() {
+      var pos = select.selectionTopNode(this.container, true);
+      var to = select.selectionTopNode(this.container, false);
+      if (pos === false || to === false) return false;
+
+      select.markSelection();
+      if (this.highlight(pos, endOfLine(to, this.container), true, 20) === false)
+        return false;
+      select.selectMarked();
+      return true;
+    },
+
+    // When tab is pressed with text selected, the whole selection is
+    // re-indented, when nothing is selected, the line with the cursor
+    // is re-indented.
+    handleTab: function(direction) {
+      if (this.options.tabMode == "spaces" && !select.somethingSelected())
+        select.insertTabAtCursor();
+      else
+        this.reindentSelection(direction);
+    },
+
+    // Custom home behaviour that doesn't land the cursor in front of
+    // leading whitespace unless pressed twice.
+    home: function() {
+      var cur = select.selectionTopNode(this.container, true), start = cur;
+      if (cur === false || !(!cur || cur.isPart || isBR(cur)) || !this.container.firstChild)
+        return false;
+
+      while (cur && !isBR(cur)) cur = cur.previousSibling;
+      var next = cur ? cur.nextSibling : this.container.firstChild;
+      if (next && next != start && next.isPart && hasClass(next, "whitespace"))
+        select.focusAfterNode(next, this.container);
+      else
+        select.focusAfterNode(cur, this.container);
+
+      select.scrollToCursor(this.container);
+      return true;
+    },
+
+    // Some browsers (Opera) don't manage to handle the end key
+    // properly in the face of vertical scrolling.
+    end: function() {
+      var cur = select.selectionTopNode(this.container, true);
+      if (cur === false) return false;
+      cur = endOfLine(cur, this.container);
+      if (!cur) return false;
+      select.focusAfterNode(cur.previousSibling, this.container);
+      select.scrollToCursor(this.container);
+      return true;
+    },
+
+    pageUp: function() {
+      var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount();
+      if (line === false || scrollAmount === false) return false;
+      // Try to keep one line on the screen.
+      scrollAmount -= 2;
+      for (var i = 0; i < scrollAmount; i++) {
+        line = this.prevLine(line);
+        if (line === false) break;
+      }
+      if (i == 0) return false; // Already at first line
+      select.setCursorPos(this.container, {node: line, offset: 0});
+      select.scrollToCursor(this.container);
+      return true;
+    },
+
+    pageDown: function() {
+      var line = this.cursorPosition().line, scrollAmount = this.visibleLineCount();
+      if (line === false || scrollAmount === false) return false;
+      // Try to move to the last line of the current page.
+      scrollAmount -= 2;
+      for (var i = 0; i < scrollAmount; i++) {
+        var nextLine = this.nextLine(line);
+        if (nextLine === false) break;
+        line = nextLine;
+      }
+      if (i == 0) return false; // Already at last line
+      select.setCursorPos(this.container, {node: line, offset: 0});
+      select.scrollToCursor(this.container);
+      return true;
+    },
+
+    // Delay (or initiate) the next paren highlight event.
+    scheduleParenHighlight: function() {
+      if (this.parenEvent) parent.clearTimeout(this.parenEvent);
+      var self = this;
+      this.parenEvent = parent.setTimeout(function(){self.highlightParens();}, 300);
+    },
+
+    // Take the token before the cursor. If it contains a character in
+    // '()[]{}', search for the matching paren/brace/bracket, and
+    // highlight them in green for a moment, or red if no proper match
+    // was found.
+    highlightParens: function(jump, fromKey) {
+      var self = this, mark = this.options.markParen;
+      if (typeof mark == "string") mark = [mark, mark];
+      // give the relevant nodes a colour.
+      function highlight(node, ok) {
+        if (!node) return;
+        if (!mark) {
+          node.style.fontWeight = "bold";
+          node.style.color = ok ? "#8F8" : "#F88";
+        }
+        else if (mark.call) mark(node, ok);
+        else node.className += " " + mark[ok ? 0 : 1];
+      }
+      function unhighlight(node) {
+        if (!node) return;
+        if (mark && !mark.call)
+          removeClass(removeClass(node, mark[0]), mark[1]);
+        else if (self.options.unmarkParen)
+          self.options.unmarkParen(node);
+        else {
+          node.style.fontWeight = "";
+          node.style.color = "";
+        }
+      }
+      if (!fromKey && self.highlighted) {
+        unhighlight(self.highlighted[0]);
+        unhighlight(self.highlighted[1]);
+      }
+
+      if (!window || !window.parent || !window.select) return;
+      // Clear the event property.
+      if (this.parenEvent) parent.clearTimeout(this.parenEvent);
+      this.parenEvent = null;
+
+      // Extract a 'paren' from a piece of text.
+      function paren(node) {
+        if (node.currentText) {
+          var match = node.currentText.match(/^[\s\u00a0]*([\(\)\[\]{}])[\s\u00a0]*$/);
+          return match && match[1];
+        }
+      }
+      // Determine the direction a paren is facing.
+      function forward(ch) {
+        return /[\(\[\{]/.test(ch);
+      }
+
+      var ch, cursor = select.selectionTopNode(this.container, true);
+      if (!cursor || !this.highlightAtCursor()) return;
+      cursor = select.selectionTopNode(this.container, true);
+      if (!(cursor && ((ch = paren(cursor)) || (cursor = cursor.nextSibling) && (ch = paren(cursor)))))
+        return;
+      // We only look for tokens with the same className.
+      var className = cursor.className, dir = forward(ch), match = matching[ch];
+
+      // Since parts of the document might not have been properly
+      // highlighted, and it is hard to know in advance which part we
+      // have to scan, we just try, and when we find dirty nodes we
+      // abort, parse them, and re-try.
+      function tryFindMatch() {
+        var stack = [], ch, ok = true;
+        for (var runner = cursor; runner; runner = dir ? runner.nextSibling : runner.previousSibling) {
+          if (runner.className == className && isSpan(runner) && (ch = paren(runner))) {
+            if (forward(ch) == dir)
+              stack.push(ch);
+            else if (!stack.length)
+              ok = false;
+            else if (stack.pop() != matching[ch])
+              ok = false;
+            if (!stack.length) break;
+          }
+          else if (runner.dirty || !isSpan(runner) && !isBR(runner)) {
+            return {node: runner, status: "dirty"};
+          }
+        }
+        return {node: runner, status: runner && ok};
+      }
+
+      while (true) {
+        var found = tryFindMatch();
+        if (found.status == "dirty") {
+          this.highlight(found.node, endOfLine(found.node));
+          // Needed because in some corner cases a highlight does not
+          // reach a node.
+          found.node.dirty = false;
+          continue;
+        }
+        else {
+          highlight(cursor, found.status);
+          highlight(found.node, found.status);
+          if (fromKey)
+            parent.setTimeout(function() {unhighlight(cursor); unhighlight(found.node);}, 500);
+          else
+            self.highlighted = [cursor, found.node];
+          if (jump && found.node)
+            select.focusAfterNode(found.node.previousSibling, this.container);
+          break;
+        }
+      }
+    },
+
+    // Adjust the amount of whitespace at the start of the line that
+    // the cursor is on so that it is indented properly.
+    indentAtCursor: function(direction) {
+      if (!this.container.firstChild) return;
+      // The line has to have up-to-date lexical information, so we
+      // highlight it first.
+      if (!this.highlightAtCursor()) return;
+      var cursor = select.selectionTopNode(this.container, false);
+      // If we couldn't determine the place of the cursor,
+      // there's nothing to indent.
+      if (cursor === false)
+        return;
+      select.markSelection();
+      this.indentLineAfter(startOfLine(cursor), direction);
+      select.selectMarked();
+    },
+
+    // Indent all lines whose start falls inside of the current
+    // selection.
+    indentRegion: function(start, end, direction, selectAfter) {
+      var current = (start = startOfLine(start)), before = start && startOfLine(start.previousSibling);
+      if (!isBR(end)) end = endOfLine(end, this.container);
+      this.addDirtyNode(start);
+
+      do {
+        var next = endOfLine(current, this.container);
+        if (current) this.highlight(before, next, true);
+        this.indentLineAfter(current, direction);
+        before = current;
+        current = next;
+      } while (current != end);
+      if (selectAfter)
+        select.setCursorPos(this.container, {node: start, offset: 0}, {node: end, offset: 0});
+    },
+
+    // Find the node that the cursor is in, mark it as dirty, and make
+    // sure a highlight pass is scheduled.
+    cursorActivity: function(safe) {
+      // pagehide event hack above
+      if (this.unloaded) {
+        window.document.designMode = "off";
+        window.document.designMode = "on";
+        this.unloaded = false;
+      }
+
+      if (internetExplorer) {
+        this.container.createTextRange().execCommand("unlink");
+        clearTimeout(this.saveSelectionSnapshot);
+        var self = this;
+        this.saveSelectionSnapshot = setTimeout(function() {
+          var snapshot = select.getBookmark(self.container);
+          if (snapshot) self.selectionSnapshot = snapshot;
+        }, 200);
+      }
+
+      var activity = this.options.onCursorActivity;
+      if (!safe || activity) {
+        var cursor = select.selectionTopNode(this.container, false);
+        if (cursor === false || !this.container.firstChild) return;
+        cursor = cursor || this.container.firstChild;
+        if (activity) activity(cursor);
+        if (!safe) {
+          this.scheduleHighlight();
+          this.addDirtyNode(cursor);
+        }
+      }
+    },
+
+    reparseBuffer: function() {
+      forEach(this.container.childNodes, function(node) {node.dirty = true;});
+      if (this.container.firstChild)
+        this.addDirtyNode(this.container.firstChild);
+    },
+
+    // Add a node to the set of dirty nodes, if it isn't already in
+    // there.
+    addDirtyNode: function(node) {
+      node = node || this.container.firstChild;
+      if (!node) return;
+
+      for (var i = 0; i < this.dirty.length; i++)
+        if (this.dirty[i] == node) return;
+
+      if (node.nodeType != 3)
+        node.dirty = true;
+      this.dirty.push(node);
+    },
+
+    allClean: function() {
+      return !this.dirty.length;
+    },
+
+    // Cause a highlight pass to happen in options.passDelay
+    // milliseconds. Clear the existing timeout, if one exists. This
+    // way, the passes do not happen while the user is typing, and
+    // should as unobtrusive as possible.
+    scheduleHighlight: function() {
+      // Timeouts are routed through the parent window, because on
+      // some browsers designMode windows do not fire timeouts.
+      var self = this;
+      parent.clearTimeout(this.highlightTimeout);
+      this.highlightTimeout = parent.setTimeout(function(){self.highlightDirty();}, this.options.passDelay);
+    },
+
+    // Fetch one dirty node, and remove it from the dirty set.
+    getDirtyNode: function() {
+      while (this.dirty.length > 0) {
+        var found = this.dirty.pop();
+        // IE8 sometimes throws an unexplainable 'invalid argument'
+        // exception for found.parentNode
+        try {
+          // If the node has been coloured in the meantime, or is no
+          // longer in the document, it should not be returned.
+          while (found && found.parentNode != this.container)
+            found = found.parentNode;
+          if (found && (found.dirty || found.nodeType == 3))
+            return found;
+        } catch (e) {}
+      }
+      return null;
+    },
+
+    // Pick dirty nodes, and highlight them, until options.passTime
+    // milliseconds have gone by. The highlight method will continue
+    // to next lines as long as it finds dirty nodes. It returns
+    // information about the place where it stopped. If there are
+    // dirty nodes left after this function has spent all its lines,
+    // it shedules another highlight to finish the job.
+    highlightDirty: function(force) {
+      // Prevent FF from raising an error when it is firing timeouts
+      // on a page that's no longer loaded.
+      if (!window || !window.parent || !window.select) return false;
+
+      if (!this.options.readOnly) select.markSelection();
+      var start, endTime = force ? null : time() + this.options.passTime;
+      while ((time() < endTime || force) && (start = this.getDirtyNode())) {
+        var result = this.highlight(start, endTime);
+        if (result && result.node && result.dirty)
+          this.addDirtyNode(result.node.nextSibling);
+      }
+      if (!this.options.readOnly) select.selectMarked();
+      if (start) this.scheduleHighlight();
+      return this.dirty.length == 0;
+    },
+
+    // Creates a function that, when called through a timeout, will
+    // continuously re-parse the document.
+    documentScanner: function(passTime) {
+      var self = this, pos = null;
+      return function() {
+        // FF timeout weirdness workaround.
+        if (!window || !window.parent || !window.select) return;
+        // If the current node is no longer in the document... oh
+        // well, we start over.
+        if (pos && pos.parentNode != self.container)
+          pos = null;
+        select.markSelection();
+        var result = self.highlight(pos, time() + passTime, true);
+        select.selectMarked();
+        var newPos = result ? (result.node && result.node.nextSibling) : null;
+        pos = (pos == newPos) ? null : newPos;
+        self.delayScanning();
+      };
+    },
+
+    // Starts the continuous scanning process for this document after
+    // a given interval.
+    delayScanning: function() {
+      if (this.scanner) {
+        parent.clearTimeout(this.documentScan);
+        this.documentScan = parent.setTimeout(this.scanner, this.options.continuousScanning);
+      }
+    },
+
+    // The function that does the actual highlighting/colouring (with
+    // help from the parser and the DOM normalizer). Its interface is
+    // rather overcomplicated, because it is used in different
+    // situations: ensuring that a certain line is highlighted, or
+    // highlighting up to X milliseconds starting from a certain
+    // point. The 'from' argument gives the node at which it should
+    // start. If this is null, it will start at the beginning of the
+    // document. When a timestamp is given with the 'target' argument,
+    // it will stop highlighting at that time. If this argument holds
+    // a DOM node, it will highlight until it reaches that node. If at
+    // any time it comes across two 'clean' lines (no dirty nodes), it
+    // will stop, except when 'cleanLines' is true. maxBacktrack is
+    // the maximum number of lines to backtrack to find an existing
+    // parser instance. This is used to give up in situations where a
+    // highlight would take too long and freeze the browser interface.
+    highlight: function(from, target, cleanLines, maxBacktrack){
+      var container = this.container, self = this, active = this.options.activeTokens;
+      var endTime = (typeof target == "number" ? target : null);
+
+      if (!container.firstChild)
+        return false;
+      // Backtrack to the first node before from that has a partial
+      // parse stored.
+      while (from && (!from.parserFromHere || from.dirty)) {
+        if (maxBacktrack != null && isBR(from) && (--maxBacktrack) < 0)
+          return false;
+        from = from.previousSibling;
+      }
+      // If we are at the end of the document, do nothing.
+      if (from && !from.nextSibling)
+        return false;
+
+      // Check whether a part (<span> node) and the corresponding token
+      // match.
+      function correctPart(token, part){
+        return !part.reduced && part.currentText == token.value && part.className == token.style;
+      }
+      // Shorten the text associated with a part by chopping off
+      // characters from the front. Note that only the currentText
+      // property gets changed. For efficiency reasons, we leave the
+      // nodeValue alone -- we set the reduced flag to indicate that
+      // this part must be replaced.
+      function shortenPart(part, minus){
+        part.currentText = part.currentText.substring(minus);
+        part.reduced = true;
+      }
+      // Create a part corresponding to a given token.
+      function tokenPart(token){
+        var part = makePartSpan(token.value);     
+        part.className = token.style;
+        return part;
+      }
+
+      function maybeTouch(node) {
+        if (node) {
+          var old = node.oldNextSibling;
+          if (lineDirty || old === undefined || node.nextSibling != old)
+            self.history.touch(node);
+          node.oldNextSibling = node.nextSibling;
+        }
+        else {
+          var old = self.container.oldFirstChild;
+          if (lineDirty || old === undefined || self.container.firstChild != old)
+            self.history.touch(null);
+          self.container.oldFirstChild = self.container.firstChild;
+        }
+      }
+
+      // Get the token stream. If from is null, we start with a new
+      // parser from the start of the frame, otherwise a partial parse
+      // is resumed.
+      var traversal = traverseDOM(from ? from.nextSibling : container.firstChild),
+          stream = stringStream(traversal),
+          parsed = from ? from.parserFromHere(stream) : Editor.Parser.make(stream);
+
+      function surroundedByBRs(node) {
+        return (node.previousSibling == null || isBR(node.previousSibling)) &&
+               (node.nextSibling == null || isBR(node.nextSibling));
+      }
+
+      // parts is an interface to make it possible to 'delay' fetching
+      // the next DOM node until we are completely done with the one
+      // before it. This is necessary because often the next node is
+      // not yet available when we want to proceed past the current
+      // one.
+      var parts = {
+        current: null,
+        // Fetch current node.
+        get: function(){
+          if (!this.current)
+            this.current = traversal.nodes.shift();
+          return this.current;
+        },
+        // Advance to the next part (do not fetch it yet).
+        next: function(){
+          this.current = null;
+        },
+        // Remove the current part from the DOM tree, and move to the
+        // next.
+        remove: function(){
+          container.removeChild(this.get());
+          this.current = null;
+        },
+        // Advance to the next part that is not empty, discarding empty
+        // parts.
+        getNonEmpty: function(){
+          var part = this.get();
+          // Allow empty nodes when they are alone on a line, needed
+          // for the FF cursor bug workaround (see select.js,
+          // insertNewlineAtCursor).
+          while (part && isSpan(part) && part.currentText == "") {
+            // Leave empty nodes that are alone on a line alone in
+            // Opera, since that browsers doesn't deal well with
+            // having 2 BRs in a row.
+            if (window.opera && surroundedByBRs(part)) {
+              this.next();
+              part = this.get();
+            }
+            else {
+              var old = part;
+              this.remove();
+              part = this.get();
+              // Adjust selection information, if any. See select.js for details.
+              select.snapshotMove(old.firstChild, part && (part.firstChild || part), 0);
+            }
+          }
+          
+          return part;
+        }
+      };
+
+      var lineDirty = false, prevLineDirty = true, lineNodes = 0;
+
+      // This forEach loops over the tokens from the parsed stream, and
+      // at the same time uses the parts object to proceed through the
+      // corresponding DOM nodes.
+      forEach(parsed, function(token){
+        var part = parts.getNonEmpty();
+
+        if (token.value == "\n"){
+          // The idea of the two streams actually staying synchronized
+          // is such a long shot that we explicitly check.
+          if (!isBR(part))
+            throw "Parser out of sync. Expected BR.";
+
+          if (part.dirty || !part.indentation) lineDirty = true;
+          maybeTouch(from);
+          from = part;
+
+          // Every <br> gets a copy of the parser state and a lexical
+          // context assigned to it. The first is used to be able to
+          // later resume parsing from this point, the second is used
+          // for indentation.
+          part.parserFromHere = parsed.copy();
+          part.indentation = token.indentation || alwaysZero;
+          part.dirty = false;
+
+          // If the target argument wasn't an integer, go at least
+          // until that node.
+          if (endTime == null && part == target) throw StopIteration;
+
+          // A clean line with more than one node means we are done.
+          // Throwing a StopIteration is the way to break out of a
+          // MochiKit forEach loop.
+          if ((endTime != null && time() >= endTime) || (!lineDirty && !prevLineDirty && lineNodes > 1 && !cleanLines))
+            throw StopIteration;
+          prevLineDirty = lineDirty; lineDirty = false; lineNodes = 0;
+          parts.next();
+        }
+        else {
+          if (!isSpan(part))
+            throw "Parser out of sync. Expected SPAN.";
+          if (part.dirty)
+            lineDirty = true;
+          lineNodes++;
+
+          // If the part matches the token, we can leave it alone.
+          if (correctPart(token, part)){
+            if (active && part.dirty) active(part, token, self);
+            part.dirty = false;
+            parts.next();
+          }
+          // Otherwise, we have to fix it.
+          else {
+            lineDirty = true;
+            // Insert the correct part.
+            var newPart = tokenPart(token);
+            container.insertBefore(newPart, part);
+            if (active) active(newPart, token, self);
+            var tokensize = token.value.length;
+            var offset = 0;
+            // Eat up parts until the text for this token has been
+            // removed, adjusting the stored selection info (see
+            // select.js) in the process.
+            while (tokensize > 0) {
+              part = parts.get();
+              var partsize = part.currentText.length;
+              select.snapshotReplaceNode(part.firstChild, newPart.firstChild, tokensize, offset);
+              if (partsize > tokensize){
+                shortenPart(part, tokensize);
+                tokensize = 0;
+              }
+              else {
+                tokensize -= partsize;
+                offset += partsize;
+                parts.remove();
+              }
+            }
+          }
+        }
+      });
+      maybeTouch(from);
+      webkitLastLineHack(this.container);
+
+      // The function returns some status information that is used by
+      // hightlightDirty to determine whether and where it has to
+      // continue.
+      return {node: parts.getNonEmpty(),
+              dirty: lineDirty};
+    }
+  };
+
+  return Editor;
+})();
+
+addEventHandler(window, "load", function() {
+  var CodeMirror = window.frameElement.CodeMirror;
+  var e = CodeMirror.editor = new Editor(CodeMirror.options);
+  parent.setTimeout(method(CodeMirror, "init"), 0);
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/highlight.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,68 @@
+// Minimal framing needed to use CodeMirror-style parsers to highlight
+// code. Load this along with tokenize.js, stringstream.js, and your
+// parser. Then call highlightText, passing a string as the first
+// argument, and as the second argument either a callback function
+// that will be called with an array of SPAN nodes for every line in
+// the code, or a DOM node to which to append these spans, and
+// optionally (not needed if you only loaded one parser) a parser
+// object.
+
+// Stuff from util.js that the parsers are using.
+var StopIteration = {toString: function() {return "StopIteration"}};
+
+var Editor = {};
+var indentUnit = 2;
+
+(function(){
+  function normaliseString(string) {
+    var tab = "";
+    for (var i = 0; i < indentUnit; i++) tab += " ";
+
+    string = string.replace(/\t/g, tab).replace(/\u00a0/g, " ").replace(/\r\n?/g, "\n");
+    var pos = 0, parts = [], lines = string.split("\n");
+    for (var line = 0; line < lines.length; line++) {
+      if (line != 0) parts.push("\n");
+      parts.push(lines[line]);
+    }
+
+    return {
+      next: function() {
+        if (pos < parts.length) return parts[pos++];
+        else throw StopIteration;
+      }
+    };
+  }
+
+  window.highlightText = function(string, callback, parser) {
+    parser = (parser || Editor.Parser).make(stringStream(normaliseString(string)));
+    var line = [];
+    if (callback.nodeType == 1) {
+      var node = callback;
+      callback = function(line) {
+        for (var i = 0; i < line.length; i++)
+          node.appendChild(line[i]);
+        node.appendChild(document.createElement("br"));
+      };
+    }
+
+    try {
+      while (true) {
+        var token = parser.next();
+        if (token.value == "\n") {
+          callback(line);
+          line = [];
+        }
+        else {
+          var span = document.createElement("span");
+          span.className = token.style;
+          span.appendChild(document.createTextNode(token.value));
+          line.push(span);
+        }
+      }
+    }
+    catch (e) {
+      if (e != StopIteration) throw e;
+    }
+    if (line.length) callback(line);
+  }
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/mirrorframe.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,81 @@
+/* Demonstration of embedding CodeMirror in a bigger application. The
+ * interface defined here is a mess of prompts and confirms, and
+ * should probably not be used in a real project.
+ */
+
+function MirrorFrame(place, options) {
+  this.home = document.createElement("div");
+  if (place.appendChild)
+    place.appendChild(this.home);
+  else
+    place(this.home);
+
+  var self = this;
+  function makeButton(name, action) {
+    var button = document.createElement("input");
+    button.type = "button";
+    button.value = name;
+    self.home.appendChild(button);
+    button.onclick = function(){self[action].call(self);};
+  }
+
+  makeButton("Search", "search");
+  makeButton("Replace", "replace");
+  makeButton("Current line", "line");
+  makeButton("Jump to line", "jump");
+  makeButton("Insert constructor", "macro");
+  makeButton("Indent all", "reindent");
+
+  this.mirror = new CodeMirror(this.home, options);
+}
+
+MirrorFrame.prototype = {
+  search: function() {
+    var text = prompt("Enter search term:", "");
+    if (!text) return;
+
+    var first = true;
+    do {
+      var cursor = this.mirror.getSearchCursor(text, first);
+      first = false;
+      while (cursor.findNext()) {
+        cursor.select();
+        if (!confirm("Search again?"))
+          return;
+      }
+    } while (confirm("End of document reached. Start over?"));
+  },
+
+  replace: function() {
+    // This is a replace-all, but it is possible to implement a
+    // prompting replace.
+    var from = prompt("Enter search string:", ""), to;
+    if (from) to = prompt("What should it be replaced with?", "");
+    if (to == null) return;
+
+    var cursor = this.mirror.getSearchCursor(from, false);
+    while (cursor.findNext())
+      cursor.replace(to);
+  },
+
+  jump: function() {
+    var line = prompt("Jump to line:", "");
+    if (line && !isNaN(Number(line)))
+      this.mirror.jumpToLine(Number(line));
+  },
+
+  line: function() {
+    alert("The cursor is currently at line " + this.mirror.currentLine());
+    this.mirror.focus();
+  },
+
+  macro: function() {
+    var name = prompt("Name your constructor:", "");
+    if (name)
+      this.mirror.replaceSelection("function " + name + "() {\n  \n}\n\n" + name + ".prototype = {\n  \n};\n");
+  },
+
+  reindent: function() {
+    this.mirror.reindent();
+  }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/parsecss.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,161 @@
+/* Simple parser for CSS */
+
+var CSSParser = Editor.Parser = (function() {
+  var tokenizeCSS = (function() {
+    function normal(source, setState) {
+      var ch = source.next();
+      if (ch == "@") {
+        source.nextWhileMatches(/\w/);
+        return "css-at";
+      }
+      else if (ch == "/" && source.equals("*")) {
+        setState(inCComment);
+        return null;
+      }
+      else if (ch == "<" && source.equals("!")) {
+        setState(inSGMLComment);
+        return null;
+      }
+      else if (ch == "=") {
+        return "css-compare";
+      }
+      else if (source.equals("=") && (ch == "~" || ch == "|")) {
+        source.next();
+        return "css-compare";
+      }
+      else if (ch == "\"" || ch == "'") {
+        setState(inString(ch));
+        return null;
+      }
+      else if (ch == "#") {
+        source.nextWhileMatches(/\w/);
+        return "css-hash";
+      }
+      else if (ch == "!") {
+        source.nextWhileMatches(/[ \t]/);
+        source.nextWhileMatches(/\w/);
+        return "css-important";
+      }
+      else if (/\d/.test(ch)) {
+        source.nextWhileMatches(/[\w.%]/);
+        return "css-unit";
+      }
+      else if (/[,.+>*\/]/.test(ch)) {
+        return "css-select-op";
+      }
+      else if (/[;{}:\[\]]/.test(ch)) {
+        return "css-punctuation";
+      }
+      else {
+        source.nextWhileMatches(/[\w\\\-_]/);
+        return "css-identifier";
+      }
+    }
+
+    function inCComment(source, setState) {
+      var maybeEnd = false;
+      while (!source.endOfLine()) {
+        var ch = source.next();
+        if (maybeEnd && ch == "/") {
+          setState(normal);
+          break;
+        }
+        maybeEnd = (ch == "*");
+      }
+      return "css-comment";
+    }
+
+    function inSGMLComment(source, setState) {
+      var dashes = 0;
+      while (!source.endOfLine()) {
+        var ch = source.next();
+        if (dashes >= 2 && ch == ">") {
+          setState(normal);
+          break;
+        }
+        dashes = (ch == "-") ? dashes + 1 : 0;
+      }
+      return "css-comment";
+    }
+
+    function inString(quote) {
+      return function(source, setState) {
+        var escaped = false;
+        while (!source.endOfLine()) {
+          var ch = source.next();
+          if (ch == quote && !escaped)
+            break;
+          escaped = !escaped && ch == "\\";
+        }
+        if (!escaped)
+          setState(normal);
+        return "css-string";
+      };
+    }
+
+    return function(source, startState) {
+      return tokenizer(source, startState || normal);
+    };
+  })();
+
+  function indentCSS(inBraces, inRule, base) {
+    return function(nextChars) {
+      if (!inBraces || /^\}/.test(nextChars)) return base;
+      else if (inRule) return base + indentUnit * 2;
+      else return base + indentUnit;
+    };
+  }
+
+  // This is a very simplistic parser -- since CSS does not really
+  // nest, it works acceptably well, but some nicer colouroing could
+  // be provided with a more complicated parser.
+  function parseCSS(source, basecolumn) {
+    basecolumn = basecolumn || 0;
+    var tokens = tokenizeCSS(source);
+    var inBraces = false, inRule = false, inDecl = false;;
+
+    var iter = {
+      next: function() {
+        var token = tokens.next(), style = token.style, content = token.content;
+
+        if (style == "css-hash")
+          style = token.style =  inRule ? "css-colorcode" : "css-identifier";
+        if (style == "css-identifier") {
+          if (inRule) token.style = "css-value";
+          else if (!inBraces && !inDecl) token.style = "css-selector";
+        }
+
+        if (content == "\n")
+          token.indentation = indentCSS(inBraces, inRule, basecolumn);
+
+        if (content == "{" && inDecl == "@media")
+          inDecl = false;
+        else if (content == "{")
+          inBraces = true;
+        else if (content == "}")
+          inBraces = inRule = inDecl = false;
+        else if (content == ";")
+          inRule = inDecl = false;
+        else if (inBraces && style != "css-comment" && style != "whitespace")
+          inRule = true;
+        else if (!inBraces && style == "css-at")
+          inDecl = content;
+
+        return token;
+      },
+
+      copy: function() {
+        var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state;
+        return function(source) {
+          tokens = tokenizeCSS(source, _tokenState);
+          inBraces = _inBraces;
+          inRule = _inRule;
+          return iter;
+        };
+      }
+    };
+    return iter;
+  }
+
+  return {make: parseCSS, electricChars: "}"};
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/parsedummy.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,32 @@
+var DummyParser = Editor.Parser = (function() {
+  function tokenizeDummy(source) {
+    while (!source.endOfLine()) source.next();
+    return "text";
+  }
+  function parseDummy(source) {
+    function indentTo(n) {return function() {return n;}}
+    source = tokenizer(source, tokenizeDummy);
+    var space = 0;
+
+    var iter = {
+      next: function() {
+        var tok = source.next();
+        if (tok.type == "whitespace") {
+          if (tok.value == "\n") tok.indentation = indentTo(space);
+          else space = tok.value.length;
+        }
+        return tok;
+      },
+      copy: function() {
+        var _space = space;
+        return function(_source) {
+          space = _space;
+          source = tokenizer(_source, tokenizeDummy);
+          return iter;
+        };
+      }
+    };
+    return iter;
+  }
+  return {make: parseDummy};
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/parsehtmlmixed.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,93 @@
+var HTMLMixedParser = Editor.Parser = (function() {
+
+  // tags that trigger seperate parsers
+  var triggers = {
+    "script": "JSParser",
+    "style":  "CSSParser"
+  };
+
+  function checkDependencies() {
+    var parsers = ['XMLParser'];
+    for (var p in triggers) parsers.push(triggers[p]);
+    for (var i in parsers) {
+      if (!window[parsers[i]]) throw new Error(parsers[i] + " parser must be loaded for HTML mixed mode to work.");
+    }
+    XMLParser.configure({useHTMLKludges: true});
+  }
+
+  function parseMixed(stream) {
+    checkDependencies();
+    var htmlParser = XMLParser.make(stream), localParser = null, inTag = false;
+    var iter = {next: top, copy: copy};
+
+    function top() {
+      var token = htmlParser.next();
+      if (token.content == "<")
+        inTag = true;
+      else if (token.style == "xml-tagname" && inTag === true)
+        inTag = token.content.toLowerCase();
+      else if (token.content == ">") {
+        if (triggers[inTag]) {
+          var parser = window[triggers[inTag]];
+          iter.next = local(parser, "</" + inTag);
+        }
+        inTag = false;
+      }
+      return token;
+    }
+    function local(parser, tag) {
+      var baseIndent = htmlParser.indentation();
+      localParser = parser.make(stream, baseIndent + indentUnit);
+      return function() {
+        if (stream.lookAhead(tag, false, false, true)) {
+          localParser = null;
+          iter.next = top;
+          return top();
+        }
+
+        var token = localParser.next();
+        var lt = token.value.lastIndexOf("<"), sz = Math.min(token.value.length - lt, tag.length);
+        if (lt != -1 && token.value.slice(lt, lt + sz).toLowerCase() == tag.slice(0, sz) &&
+            stream.lookAhead(tag.slice(sz), false, false, true)) {
+          stream.push(token.value.slice(lt));
+          token.value = token.value.slice(0, lt);
+        }
+
+        if (token.indentation) {
+          var oldIndent = token.indentation;
+          token.indentation = function(chars) {
+            if (chars == "</")
+              return baseIndent;
+            else
+              return oldIndent(chars);
+          };
+        }
+
+        return token;
+      };
+    }
+
+    function copy() {
+      var _html = htmlParser.copy(), _local = localParser && localParser.copy(),
+          _next = iter.next, _inTag = inTag;
+      return function(_stream) {
+        stream = _stream;
+        htmlParser = _html(_stream);
+        localParser = _local && _local(_stream);
+        iter.next = _next;
+        inTag = _inTag;
+        return iter;
+      };
+    }
+    return iter;
+  }
+
+  return {
+    make: parseMixed,
+    electricChars: "{}/:",
+    configure: function(obj) {
+      if (obj.triggers) triggers = obj.triggers;
+    }
+  };
+
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/parsejavascript.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,359 @@
+/* Parse function for JavaScript. Makes use of the tokenizer from
+ * tokenizejavascript.js. Note that your parsers do not have to be
+ * this complicated -- if you don't want to recognize local variables,
+ * in many languages it is enough to just look for braces, semicolons,
+ * parentheses, etc, and know when you are inside a string or comment.
+ *
+ * See manual.html for more info about the parser interface.
+ */
+
+var JSParser = Editor.Parser = (function() {
+  // Token types that can be considered to be atoms.
+  var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
+  // Setting that can be used to have JSON data indent properly.
+  var json = false;
+  // Constructor for the lexical context objects.
+  function JSLexical(indented, column, type, align, prev, info) {
+    // indentation at start of this line
+    this.indented = indented;
+    // column at which this scope was opened
+    this.column = column;
+    // type of scope ('vardef', 'stat' (statement), 'form' (special form), '[', '{', or '(')
+    this.type = type;
+    // '[', '{', or '(' blocks that have any text after their opening
+    // character are said to be 'aligned' -- any lines below are
+    // indented all the way to the opening character.
+    if (align != null)
+      this.align = align;
+    // Parent scope, if any.
+    this.prev = prev;
+    this.info = info;
+  }
+
+  // My favourite JavaScript indentation rules.
+  function indentJS(lexical) {
+    return function(firstChars) {
+      var firstChar = firstChars && firstChars.charAt(0), type = lexical.type;
+      var closing = firstChar == type;
+      if (type == "vardef")
+        return lexical.indented + 4;
+      else if (type == "form" && firstChar == "{")
+        return lexical.indented;
+      else if (type == "stat" || type == "form")
+        return lexical.indented + indentUnit;
+      else if (lexical.info == "switch" && !closing)
+        return lexical.indented + (/^(?:case|default)\b/.test(firstChars) ? indentUnit : 2 * indentUnit);
+      else if (lexical.align)
+        return lexical.column - (closing ? 1 : 0);
+      else
+        return lexical.indented + (closing ? 0 : indentUnit);
+    };
+  }
+
+  // The parser-iterator-producing function itself.
+  function parseJS(input, basecolumn) {
+    // Wrap the input in a token stream
+    var tokens = tokenizeJavaScript(input);
+    // The parser state. cc is a stack of actions that have to be
+    // performed to finish the current statement. For example we might
+    // know that we still need to find a closing parenthesis and a
+    // semicolon. Actions at the end of the stack go first. It is
+    // initialized with an infinitely looping action that consumes
+    // whole statements.
+    var cc = [json ? expressions : statements];
+    // Context contains information about the current local scope, the
+    // variables defined in that, and the scopes above it.
+    var context = null;
+    // The lexical scope, used mostly for indentation.
+    var lexical = new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false);
+    // Current column, and the indentation at the start of the current
+    // line. Used to create lexical scope objects.
+    var column = 0;
+    var indented = 0;
+    // Variables which are used by the mark, cont, and pass functions
+    // below to communicate with the driver loop in the 'next'
+    // function.
+    var consume, marked;
+  
+    // The iterator object.
+    var parser = {next: next, copy: copy};
+
+    function next(){
+      // Start by performing any 'lexical' actions (adjusting the
+      // lexical variable), or the operations below will be working
+      // with the wrong lexical state.
+      while(cc[cc.length - 1].lex)
+        cc.pop()();
+
+      // Fetch a token.
+      var token = tokens.next();
+
+      // Adjust column and indented.
+      if (token.type == "whitespace" && column == 0)
+        indented = token.value.length;
+      column += token.value.length;
+      if (token.content == "\n"){
+        indented = column = 0;
+        // If the lexical scope's align property is still undefined at
+        // the end of the line, it is an un-aligned scope.
+        if (!("align" in lexical))
+          lexical.align = false;
+        // Newline tokens get an indentation function associated with
+        // them.
+        token.indentation = indentJS(lexical);
+      }
+      // No more processing for meaningless tokens.
+      if (token.type == "whitespace" || token.type == "comment")
+        return token;
+      // When a meaningful token is found and the lexical scope's
+      // align is undefined, it is an aligned scope.
+      if (!("align" in lexical))
+        lexical.align = true;
+
+      // Execute actions until one 'consumes' the token and we can
+      // return it.
+      while(true) {
+        consume = marked = false;
+        // Take and execute the topmost action.
+        cc.pop()(token.type, token.content);
+        if (consume){
+          // Marked is used to change the style of the current token.
+          if (marked)
+            token.style = marked;
+          // Here we differentiate between local and global variables.
+          else if (token.type == "variable" && inScope(token.content))
+            token.style = "js-localvariable";
+          return token;
+        }
+      }
+    }
+
+    // This makes a copy of the parser state. It stores all the
+    // stateful variables in a closure, and returns a function that
+    // will restore them when called with a new input stream. Note
+    // that the cc array has to be copied, because it is contantly
+    // being modified. Lexical objects are not mutated, and context
+    // objects are not mutated in a harmful way, so they can be shared
+    // between runs of the parser.
+    function copy(){
+      var _context = context, _lexical = lexical, _cc = cc.concat([]), _tokenState = tokens.state;
+  
+      return function copyParser(input){
+        context = _context;
+        lexical = _lexical;
+        cc = _cc.concat([]); // copies the array
+        column = indented = 0;
+        tokens = tokenizeJavaScript(input, _tokenState);
+        return parser;
+      };
+    }
+
+    // Helper function for pushing a number of actions onto the cc
+    // stack in reverse order.
+    function push(fs){
+      for (var i = fs.length - 1; i >= 0; i--)
+        cc.push(fs[i]);
+    }
+    // cont and pass are used by the action functions to add other
+    // actions to the stack. cont will cause the current token to be
+    // consumed, pass will leave it for the next action.
+    function cont(){
+      push(arguments);
+      consume = true;
+    }
+    function pass(){
+      push(arguments);
+      consume = false;
+    }
+    // Used to change the style of the current token.
+    function mark(style){
+      marked = style;
+    }
+
+    // Push a new scope. Will automatically link the current scope.
+    function pushcontext(){
+      context = {prev: context, vars: {"this": true, "arguments": true}};
+    }
+    // Pop off the current scope.
+    function popcontext(){
+      context = context.prev;
+    }
+    // Register a variable in the current scope.
+    function register(varname){
+      if (context){
+        mark("js-variabledef");
+        context.vars[varname] = true;
+      }
+    }
+    // Check whether a variable is defined in the current scope.
+    function inScope(varname){
+      var cursor = context;
+      while (cursor) {
+        if (cursor.vars[varname])
+          return true;
+        cursor = cursor.prev;
+      }
+      return false;
+    }
+  
+    // Push a new lexical context of the given type.
+    function pushlex(type, info) {
+      var result = function(){
+        lexical = new JSLexical(indented, column, type, null, lexical, info)
+      };
+      result.lex = true;
+      return result;
+    }
+    // Pop off the current lexical context.
+    function poplex(){
+      if (lexical.type == ")")
+        indented = lexical.indented;
+      lexical = lexical.prev;
+    }
+    poplex.lex = true;
+    // The 'lex' flag on these actions is used by the 'next' function
+    // to know they can (and have to) be ran before moving on to the
+    // next token.
+  
+    // Creates an action that discards tokens until it finds one of
+    // the given type.
+    function expect(wanted){
+      return function expecting(type){
+        if (type == wanted) cont();
+        else if (wanted == ";") pass();
+        else cont(arguments.callee);
+      };
+    }
+
+    // Looks for a statement, and then calls itself.
+    function statements(type){
+      return pass(statement, statements);
+    }
+    function expressions(type){
+      return pass(expression, expressions);
+    }
+    // Dispatches various types of statements based on the type of the
+    // current token.
+    function statement(type){
+      if (type == "var") cont(pushlex("vardef"), vardef1, expect(";"), poplex);
+      else if (type == "keyword a") cont(pushlex("form"), expression, statement, poplex);
+      else if (type == "keyword b") cont(pushlex("form"), statement, poplex);
+      else if (type == "{") cont(pushlex("}"), block, poplex);
+      else if (type == ";") cont();
+      else if (type == "function") cont(functiondef);
+      else if (type == "for") cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"), poplex, statement, poplex);
+      else if (type == "variable") cont(pushlex("stat"), maybelabel);
+      else if (type == "switch") cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"), block, poplex, poplex);
+      else if (type == "case") cont(expression, expect(":"));
+      else if (type == "default") cont(expect(":"));
+      else if (type == "catch") cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"), statement, poplex, popcontext);
+      else pass(pushlex("stat"), expression, expect(";"), poplex);
+    }
+    // Dispatch expression types.
+    function expression(type){
+      if (atomicTypes.hasOwnProperty(type)) cont(maybeoperator);
+      else if (type == "function") cont(functiondef);
+      else if (type == "keyword c") cont(expression);
+      else if (type == "(") cont(pushlex(")"), expression, expect(")"), poplex, maybeoperator);
+      else if (type == "operator") cont(expression);
+      else if (type == "[") cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
+      else if (type == "{") cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
+      else cont();
+    }
+    // Called for places where operators, function calls, or
+    // subscripts are valid. Will skip on to the next action if none
+    // is found.
+    function maybeoperator(type, value){
+      if (type == "operator" && /\+\+|--/.test(value)) cont(maybeoperator);
+      else if (type == "operator") cont(expression);
+      else if (type == ";") pass();
+      else if (type == "(") cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
+      else if (type == ".") cont(property, maybeoperator);
+      else if (type == "[") cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
+    }
+    // When a statement starts with a variable name, it might be a
+    // label. If no colon follows, it's a regular statement.
+    function maybelabel(type){
+      if (type == ":") cont(poplex, statement);
+      else pass(maybeoperator, expect(";"), poplex);
+    }
+    // Property names need to have their style adjusted -- the
+    // tokenizer thinks they are variables.
+    function property(type){
+      if (type == "variable") {mark("js-property"); cont();}
+    }
+    // This parses a property and its value in an object literal.
+    function objprop(type){
+      if (type == "variable") mark("js-property");
+      if (atomicTypes.hasOwnProperty(type)) cont(expect(":"), expression);
+    }
+    // Parses a comma-separated list of the things that are recognized
+    // by the 'what' argument.
+    function commasep(what, end){
+      function proceed(type) {
+        if (type == ",") cont(what, proceed);
+        else if (type == end) cont();
+        else cont(expect(end));
+      }
+      return function commaSeparated(type) {
+        if (type == end) cont();
+        else pass(what, proceed);
+      };
+    }
+    // Look for statements until a closing brace is found.
+    function block(type){
+      if (type == "}") cont();
+      else pass(statement, block);
+    }
+    // Variable definitions are split into two actions -- 1 looks for
+    // a name or the end of the definition, 2 looks for an '=' sign or
+    // a comma.
+    function vardef1(type, value){
+      if (type == "variable"){register(value); cont(vardef2);}
+      else cont();
+    }
+    function vardef2(type, value){
+      if (value == "=") cont(expression, vardef2);
+      else if (type == ",") cont(vardef1);
+    }
+    // For loops.
+    function forspec1(type){
+      if (type == "var") cont(vardef1, forspec2);
+      else if (type == ";") pass(forspec2);
+      else if (type == "variable") cont(formaybein);
+      else pass(forspec2);
+    }
+    function formaybein(type, value){
+      if (value == "in") cont(expression);
+      else cont(maybeoperator, forspec2);
+    }
+    function forspec2(type, value){
+      if (type == ";") cont(forspec3);
+      else if (value == "in") cont(expression);
+      else cont(expression, expect(";"), forspec3);
+    }
+    function forspec3(type) {
+      if (type == ")") pass();
+      else cont(expression);
+    }
+    // A function definition creates a new context, and the variables
+    // in its argument list have to be added to this context.
+    function functiondef(type, value){
+      if (type == "variable"){register(value); cont(functiondef);}
+      else if (type == "(") cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
+    }
+    function funarg(type, value){
+      if (type == "variable"){register(value); cont();}
+    }
+  
+    return parser;
+  }
+
+  return {
+    make: parseJS,
+    electricChars: "{}:",
+    configure: function(obj) {
+      if (obj.json != null) json = obj.json;
+    }
+  };
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/parsesparql.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,162 @@
+var SparqlParser = Editor.Parser = (function() {
+  function wordRegexp(words) {
+    return new RegExp("^(?:" + words.join("|") + ")$", "i");
+  }
+  var ops = wordRegexp(["str", "lang", "langmatches", "datatype", "bound", "sameterm", "isiri", "isuri",
+                        "isblank", "isliteral", "union", "a"]);
+  var keywords = wordRegexp(["base", "prefix", "select", "distinct", "reduced", "construct", "describe",
+                             "ask", "from", "named", "where", "order", "limit", "offset", "filter", "optional",
+                             "graph", "by", "asc", "desc"]);
+  var operatorChars = /[*+\-<>=&|]/;
+
+  var tokenizeSparql = (function() {
+    function normal(source, setState) {
+      var ch = source.next();
+      if (ch == "$" || ch == "?") {
+        source.nextWhileMatches(/[\w\d]/);
+        return "sp-var";
+      }
+      else if (ch == "<" && !source.matches(/[\s\u00a0=]/)) {
+        source.nextWhileMatches(/[^\s\u00a0>]/);
+        if (source.equals(">")) source.next();
+        return "sp-uri";
+      }
+      else if (ch == "\"" || ch == "'") {
+        setState(inLiteral(ch));
+        return null;
+      }
+      else if (/[{}\(\),\.;\[\]]/.test(ch)) {
+        return "sp-punc";
+      }
+      else if (ch == "#") {
+        while (!source.endOfLine()) source.next();
+        return "sp-comment";
+      }
+      else if (operatorChars.test(ch)) {
+        source.nextWhileMatches(operatorChars);
+        return "sp-operator";
+      }
+      else if (ch == ":") {
+        source.nextWhileMatches(/[\w\d\._\-]/);
+        return "sp-prefixed";
+      }
+      else {
+        source.nextWhileMatches(/[_\w\d]/);
+        if (source.equals(":")) {
+          source.next();
+          source.nextWhileMatches(/[\w\d_\-]/);
+          return "sp-prefixed";
+        }
+        var word = source.get(), type;
+        if (ops.test(word))
+          type = "sp-operator";
+        else if (keywords.test(word))
+          type = "sp-keyword";
+        else
+          type = "sp-word";
+        return {style: type, content: word};
+      }
+    }
+
+    function inLiteral(quote) {
+      return function(source, setState) {
+        var escaped = false;
+        while (!source.endOfLine()) {
+          var ch = source.next();
+          if (ch == quote && !escaped) {
+            setState(normal);
+            break;
+          }
+          escaped = !escaped && ch == "\\";
+        }
+        return "sp-literal";
+      };
+    }
+
+    return function(source, startState) {
+      return tokenizer(source, startState || normal);
+    };
+  })();
+
+  function indentSparql(context) {
+    return function(nextChars) {
+      var firstChar = nextChars && nextChars.charAt(0);
+      if (/[\]\}]/.test(firstChar))
+        while (context && context.type == "pattern") context = context.prev;
+
+      var closing = context && firstChar == matching[context.type];
+      if (!context)
+        return 0;
+      else if (context.type == "pattern")
+        return context.col;
+      else if (context.align)
+        return context.col - (closing ? context.width : 0);
+      else
+        return context.indent + (closing ? 0 : indentUnit);
+    }
+  }
+
+  function parseSparql(source) {
+    var tokens = tokenizeSparql(source);
+    var context = null, indent = 0, col = 0;
+    function pushContext(type, width) {
+      context = {prev: context, indent: indent, col: col, type: type, width: width};
+    }
+    function popContext() {
+      context = context.prev;
+    }
+
+    var iter = {
+      next: function() {
+        var token = tokens.next(), type = token.style, content = token.content, width = token.value.length;
+
+        if (content == "\n") {
+          token.indentation = indentSparql(context);
+          indent = col = 0;
+          if (context && context.align == null) context.align = false;
+        }
+        else if (type == "whitespace" && col == 0) {
+          indent = width;
+        }
+        else if (type != "sp-comment" && context && context.align == null) {
+          context.align = true;
+        }
+
+        if (content != "\n") col += width;
+
+        if (/[\[\{\(]/.test(content)) {
+          pushContext(content, width);
+        }
+        else if (/[\]\}\)]/.test(content)) {
+          while (context && context.type == "pattern")
+            popContext();
+          if (context && content == matching[context.type])
+            popContext();
+        }
+        else if (content == "." && context && context.type == "pattern") {
+          popContext();
+        }
+        else if ((type == "sp-word" || type == "sp-prefixed" || type == "sp-uri" || type == "sp-var" || type == "sp-literal") &&
+                 context && /[\{\[]/.test(context.type)) {
+          pushContext("pattern", width);
+        }
+
+        return token;
+      },
+
+      copy: function() {
+        var _context = context, _indent = indent, _col = col, _tokenState = tokens.state;
+        return function(source) {
+          tokens = tokenizeSparql(source, _tokenState);
+          context = _context;
+          indent = _indent;
+          col = _col;
+          return iter;
+        };
+      }
+    };
+    return iter;
+  }
+
+  return {make: parseSparql, electricChars: "}]"};
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/parsexml.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,291 @@
+/* This file defines an XML parser, with a few kludges to make it
+ * useable for HTML. autoSelfClosers defines a set of tag names that
+ * are expected to not have a closing tag, and doNotIndent specifies
+ * the tags inside of which no indentation should happen (see Config
+ * object). These can be disabled by passing the editor an object like
+ * {useHTMLKludges: false} as parserConfig option.
+ */
+
+var XMLParser = Editor.Parser = (function() {
+  var Kludges = {
+    autoSelfClosers: {"br": true, "img": true, "hr": true, "link": true, "input": true,
+                      "meta": true, "col": true, "frame": true, "base": true, "area": true},
+    doNotIndent: {"pre": true, "!cdata": true}
+  };
+  var NoKludges = {autoSelfClosers: {}, doNotIndent: {"!cdata": true}};
+  var UseKludges = Kludges;
+  var alignCDATA = false;
+
+  // Simple stateful tokenizer for XML documents. Returns a
+  // MochiKit-style iterator, with a state property that contains a
+  // function encapsulating the current state. See tokenize.js.
+  var tokenizeXML = (function() {
+    function inText(source, setState) {
+      var ch = source.next();
+      if (ch == "<") {
+        if (source.equals("!")) {
+          source.next();
+          if (source.equals("[")) {
+            if (source.lookAhead("[CDATA[", true)) {
+              setState(inBlock("xml-cdata", "]]>"));
+              return null;
+            }
+            else {
+              return "xml-text";
+            }
+          }
+          else if (source.lookAhead("--", true)) {
+            setState(inBlock("xml-comment", "-->"));
+            return null;
+          }
+          else if (source.lookAhead("DOCTYPE", true)) {
+            source.nextWhileMatches(/[\w\._\-]/);
+            setState(inBlock("xml-doctype", ">"));
+            return "xml-doctype";
+          }
+          else {
+            return "xml-text";
+          }
+        }
+        else if (source.equals("?")) {
+          source.next();
+          source.nextWhileMatches(/[\w\._\-]/);
+          setState(inBlock("xml-processing", "?>"));
+          return "xml-processing";
+        }
+        else {
+          if (source.equals("/")) source.next();
+          setState(inTag);
+          return "xml-punctuation";
+        }
+      }
+      else if (ch == "&") {
+        while (!source.endOfLine()) {
+          if (source.next() == ";")
+            break;
+        }
+        return "xml-entity";
+      }
+      else {
+        source.nextWhileMatches(/[^&<\n]/);
+        return "xml-text";
+      }
+    }
+
+    function inTag(source, setState) {
+      var ch = source.next();
+      if (ch == ">") {
+        setState(inText);
+        return "xml-punctuation";
+      }
+      else if (/[?\/]/.test(ch) && source.equals(">")) {
+        source.next();
+        setState(inText);
+        return "xml-punctuation";
+      }
+      else if (ch == "=") {
+        return "xml-punctuation";
+      }
+      else if (/[\'\"]/.test(ch)) {
+        setState(inAttribute(ch));
+        return null;
+      }
+      else {
+        source.nextWhileMatches(/[^\s\u00a0=<>\"\'\/?]/);
+        return "xml-name";
+      }
+    }
+
+    function inAttribute(quote) {
+      return function(source, setState) {
+        while (!source.endOfLine()) {
+          if (source.next() == quote) {
+            setState(inTag);
+            break;
+          }
+        }
+        return "xml-attribute";
+      };
+    }
+
+    function inBlock(style, terminator) {
+      return function(source, setState) {
+        while (!source.endOfLine()) {
+          if (source.lookAhead(terminator, true)) {
+            setState(inText);
+            break;
+          }
+          source.next();
+        }
+        return style;
+      };
+    }
+
+    return function(source, startState) {
+      return tokenizer(source, startState || inText);
+    };
+  })();
+
+  // The parser. The structure of this function largely follows that of
+  // parseJavaScript in parsejavascript.js (there is actually a bit more
+  // shared code than I'd like), but it is quite a bit simpler.
+  function parseXML(source) {
+    var tokens = tokenizeXML(source), token;
+    var cc = [base];
+    var tokenNr = 0, indented = 0;
+    var currentTag = null, context = null;
+    var consume;
+    
+    function push(fs) {
+      for (var i = fs.length - 1; i >= 0; i--)
+        cc.push(fs[i]);
+    }
+    function cont() {
+      push(arguments);
+      consume = true;
+    }
+    function pass() {
+      push(arguments);
+      consume = false;
+    }
+
+    function markErr() {
+      token.style += " xml-error";
+    }
+    function expect(text) {
+      return function(style, content) {
+        if (content == text) cont();
+        else {markErr(); cont(arguments.callee);}
+      };
+    }
+
+    function pushContext(tagname, startOfLine) {
+      var noIndent = UseKludges.doNotIndent.hasOwnProperty(tagname) || (context && context.noIndent);
+      context = {prev: context, name: tagname, indent: indented, startOfLine: startOfLine, noIndent: noIndent};
+    }
+    function popContext() {
+      context = context.prev;
+    }
+    function computeIndentation(baseContext) {
+      return function(nextChars, current) {
+        var context = baseContext;
+        if (context && context.noIndent)
+          return current;
+        if (alignCDATA && /<!\[CDATA\[/.test(nextChars))
+          return 0;
+        if (context && /^<\//.test(nextChars))
+          context = context.prev;
+        while (context && !context.startOfLine)
+          context = context.prev;
+        if (context)
+          return context.indent + indentUnit;
+        else
+          return 0;
+      };
+    }
+
+    function base() {
+      return pass(element, base);
+    }
+    var harmlessTokens = {"xml-text": true, "xml-entity": true, "xml-comment": true, "xml-processing": true, "xml-doctype": true};
+    function element(style, content) {
+      if (content == "<") cont(tagname, attributes, endtag(tokenNr == 1));
+      else if (content == "</") cont(closetagname, expect(">"));
+      else if (style == "xml-cdata") {
+        if (!context || context.name != "!cdata") pushContext("!cdata");
+        if (/\]\]>$/.test(content)) popContext();
+        cont();
+      }
+      else if (harmlessTokens.hasOwnProperty(style)) cont();
+      else {markErr(); cont();}
+    }
+    function tagname(style, content) {
+      if (style == "xml-name") {
+        currentTag = content.toLowerCase();
+        token.style = "xml-tagname";
+        cont();
+      }
+      else {
+        currentTag = null;
+        pass();
+      }
+    }
+    function closetagname(style, content) {
+      if (style == "xml-name") {
+        token.style = "xml-tagname";
+        if (context && content.toLowerCase() == context.name) popContext();
+        else markErr();
+      }
+      cont();
+    }
+    function endtag(startOfLine) {
+      return function(style, content) {
+        if (content == "/>" || (content == ">" && UseKludges.autoSelfClosers.hasOwnProperty(currentTag))) cont();
+        else if (content == ">") {pushContext(currentTag, startOfLine); cont();}
+        else {markErr(); cont(arguments.callee);}
+      };
+    }
+    function attributes(style) {
+      if (style == "xml-name") {token.style = "xml-attname"; cont(attribute, attributes);}
+      else pass();
+    }
+    function attribute(style, content) {
+      if (content == "=") cont(value);
+      else if (content == ">" || content == "/>") pass(endtag);
+      else pass();
+    }
+    function value(style) {
+      if (style == "xml-attribute") cont(value);
+      else pass();
+    }
+
+    return {
+      indentation: function() {return indented;},
+
+      next: function(){
+        token = tokens.next();
+        if (token.style == "whitespace" && tokenNr == 0)
+          indented = token.value.length;
+        else
+          tokenNr++;
+        if (token.content == "\n") {
+          indented = tokenNr = 0;
+          token.indentation = computeIndentation(context);
+        }
+
+        if (token.style == "whitespace" || token.type == "xml-comment")
+          return token;
+
+        while(true){
+          consume = false;
+          cc.pop()(token.style, token.content);
+          if (consume) return token;
+        }
+      },
+
+      copy: function(){
+        var _cc = cc.concat([]), _tokenState = tokens.state, _context = context;
+        var parser = this;
+        
+        return function(input){
+          cc = _cc.concat([]);
+          tokenNr = indented = 0;
+          context = _context;
+          tokens = tokenizeXML(input, _tokenState);
+          return parser;
+        };
+      }
+    };
+  }
+
+  return {
+    make: parseXML,
+    electricChars: "/",
+    configure: function(config) {
+      if (config.useHTMLKludges != null)
+        UseKludges = config.useHTMLKludges ? Kludges : NoKludges;
+      if (config.alignCDATA)
+        alignCDATA = config.alignCDATA;
+    }
+  };
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/select.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,699 @@
+/* Functionality for finding, storing, and restoring selections
+ *
+ * This does not provide a generic API, just the minimal functionality
+ * required by the CodeMirror system.
+ */
+
+// Namespace object.
+var select = {};
+
+(function() {
+  select.ie_selection = document.selection && document.selection.createRangeCollection;
+
+  // Find the 'top-level' (defined as 'a direct child of the node
+  // passed as the top argument') node that the given node is
+  // contained in. Return null if the given node is not inside the top
+  // node.
+  function topLevelNodeAt(node, top) {
+    while (node && node.parentNode != top)
+      node = node.parentNode;
+    return node;
+  }
+
+  // Find the top-level node that contains the node before this one.
+  function topLevelNodeBefore(node, top) {
+    while (!node.previousSibling && node.parentNode != top)
+      node = node.parentNode;
+    return topLevelNodeAt(node.previousSibling, top);
+  }
+
+  var fourSpaces = "\u00a0\u00a0\u00a0\u00a0";
+
+  select.scrollToNode = function(node, cursor) {
+    if (!node) return;
+    var element = node, body = document.body,
+        html = document.documentElement,
+        atEnd = !element.nextSibling || !element.nextSibling.nextSibling
+                || !element.nextSibling.nextSibling.nextSibling;
+    // In Opera (and recent Webkit versions), BR elements *always*
+    // have a offsetTop property of zero.
+    var compensateHack = 0;
+    while (element && !element.offsetTop) {
+      compensateHack++;
+      element = element.previousSibling;
+    }
+    // atEnd is another kludge for these browsers -- if the cursor is
+    // at the end of the document, and the node doesn't have an
+    // offset, just scroll to the end.
+    if (compensateHack == 0) atEnd = false;
+
+    // WebKit has a bad habit of (sometimes) happily returning bogus
+    // offsets when the document has just been changed. This seems to
+    // always be 5/5, so we don't use those.
+    if (webkit && element && element.offsetTop == 5 && element.offsetLeft == 5)
+      return;
+
+    var y = compensateHack * (element ? element.offsetHeight : 0), x = 0,
+        width = (node ? node.offsetWidth : 0), pos = element;
+    while (pos && pos.offsetParent) {
+      y += pos.offsetTop;
+      // Don't count X offset for <br> nodes
+      if (!isBR(pos))
+        x += pos.offsetLeft;
+      pos = pos.offsetParent;
+    }
+
+    var scroll_x = body.scrollLeft || html.scrollLeft || 0,
+        scroll_y = body.scrollTop || html.scrollTop || 0,
+        scroll = false, screen_width = window.innerWidth || html.clientWidth || 0;
+
+    if (cursor || width < screen_width) {
+      if (cursor) {
+        var off = select.offsetInNode(node), size = nodeText(node).length;
+        if (size) x += width * (off / size);
+      }
+      var screen_x = x - scroll_x;
+      if (screen_x < 0 || screen_x > screen_width) {
+        scroll_x = x;
+        scroll = true;
+      }
+    }
+    var screen_y = y - scroll_y;
+    if (screen_y < 0 || atEnd || screen_y > (window.innerHeight || html.clientHeight || 0) - 50) {
+      scroll_y = atEnd ? 1e6 : y;
+      scroll = true;
+    }
+    if (scroll) window.scrollTo(scroll_x, scroll_y);
+  };
+
+  select.scrollToCursor = function(container) {
+    select.scrollToNode(select.selectionTopNode(container, true) || container.firstChild, true);
+  };
+
+  // Used to prevent restoring a selection when we do not need to.
+  var currentSelection = null;
+
+  select.snapshotChanged = function() {
+    if (currentSelection) currentSelection.changed = true;
+  };
+
+  // Find the 'leaf' node (BR or text) after the given one.
+  function baseNodeAfter(node) {
+    var next = node.nextSibling;
+    if (next) {
+      while (next.firstChild) next = next.firstChild;
+      if (next.nodeType == 3 || isBR(next)) return next;
+      else return baseNodeAfter(next);
+    }
+    else {
+      var parent = node.parentNode;
+      while (parent && !parent.nextSibling) parent = parent.parentNode;
+      return parent && baseNodeAfter(parent);
+    }
+  }
+
+  // This is called by the code in editor.js whenever it is replacing
+  // a text node. The function sees whether the given oldNode is part
+  // of the current selection, and updates this selection if it is.
+  // Because nodes are often only partially replaced, the length of
+  // the part that gets replaced has to be taken into account -- the
+  // selection might stay in the oldNode if the newNode is smaller
+  // than the selection's offset. The offset argument is needed in
+  // case the selection does move to the new object, and the given
+  // length is not the whole length of the new node (part of it might
+  // have been used to replace another node).
+  select.snapshotReplaceNode = function(from, to, length, offset) {
+    if (!currentSelection) return;
+
+    function replace(point) {
+      if (from == point.node) {
+        currentSelection.changed = true;
+        if (length && point.offset > length) {
+          point.offset -= length;
+        }
+        else {
+          point.node = to;
+          point.offset += (offset || 0);
+        }
+      }
+      else if (select.ie_selection && point.offset == 0 && point.node == baseNodeAfter(from)) {
+        currentSelection.changed = true;
+      }
+    }
+    replace(currentSelection.start);
+    replace(currentSelection.end);
+  };
+
+  select.snapshotMove = function(from, to, distance, relative, ifAtStart) {
+    if (!currentSelection) return;
+
+    function move(point) {
+      if (from == point.node && (!ifAtStart || point.offset == 0)) {
+        currentSelection.changed = true;
+        point.node = to;
+        if (relative) point.offset = Math.max(0, point.offset + distance);
+        else point.offset = distance;
+      }
+    }
+    move(currentSelection.start);
+    move(currentSelection.end);
+  };
+
+  // Most functions are defined in two ways, one for the IE selection
+  // model, one for the W3C one.
+  if (select.ie_selection) {
+    function selRange() {
+      var sel = document.selection;
+      if (!sel) return null;
+      if (sel.createRange) return sel.createRange();
+      else return sel.createTextRange();
+    }
+
+    function selectionNode(start) {
+      var range = selRange();
+      range.collapse(start);
+
+      function nodeAfter(node) {
+        var found = null;
+        while (!found && node) {
+          found = node.nextSibling;
+          node = node.parentNode;
+        }
+        return nodeAtStartOf(found);
+      }
+
+      function nodeAtStartOf(node) {
+        while (node && node.firstChild) node = node.firstChild;
+        return {node: node, offset: 0};
+      }
+
+      var containing = range.parentElement();
+      if (!isAncestor(document.body, containing)) return null;
+      if (!containing.firstChild) return nodeAtStartOf(containing);
+
+      var working = range.duplicate();
+      working.moveToElementText(containing);
+      working.collapse(true);
+      for (var cur = containing.firstChild; cur; cur = cur.nextSibling) {
+        if (cur.nodeType == 3) {
+          var size = cur.nodeValue.length;
+          working.move("character", size);
+        }
+        else {
+          working.moveToElementText(cur);
+          working.collapse(false);
+        }
+
+        var dir = range.compareEndPoints("StartToStart", working);
+        if (dir == 0) return nodeAfter(cur);
+        if (dir == 1) continue;
+        if (cur.nodeType != 3) return nodeAtStartOf(cur);
+
+        working.setEndPoint("StartToEnd", range);
+        return {node: cur, offset: size - working.text.length};
+      }
+      return nodeAfter(containing);
+    }
+
+    select.markSelection = function() {
+      currentSelection = null;
+      var sel = document.selection;
+      if (!sel) return;
+      var start = selectionNode(true),
+          end = selectionNode(false);
+      if (!start || !end) return;
+      currentSelection = {start: start, end: end, changed: false};
+    };
+
+    select.selectMarked = function() {
+      if (!currentSelection || !currentSelection.changed) return;
+
+      function makeRange(point) {
+        var range = document.body.createTextRange(),
+            node = point.node;
+        if (!node) {
+          range.moveToElementText(document.body);
+          range.collapse(false);
+        }
+        else if (node.nodeType == 3) {
+          range.moveToElementText(node.parentNode);
+          var offset = point.offset;
+          while (node.previousSibling) {
+            node = node.previousSibling;
+            offset += (node.innerText || "").length;
+          }
+          range.move("character", offset);
+        }
+        else {
+          range.moveToElementText(node);
+          range.collapse(true);
+        }
+        return range;
+      }
+
+      var start = makeRange(currentSelection.start), end = makeRange(currentSelection.end);
+      start.setEndPoint("StartToEnd", end);
+      start.select();
+    };
+
+    select.offsetInNode = function(node) {
+      var range = selRange();
+      if (!range) return 0;
+      var range2 = range.duplicate();
+      try {range2.moveToElementText(node);} catch(e){return 0;}
+      range.setEndPoint("StartToStart", range2);
+      return range.text.length;
+    };
+
+    // Get the top-level node that one end of the cursor is inside or
+    // after. Note that this returns false for 'no cursor', and null
+    // for 'start of document'.
+    select.selectionTopNode = function(container, start) {
+      var range = selRange();
+      if (!range) return false;
+      var range2 = range.duplicate();
+      range.collapse(start);
+      var around = range.parentElement();
+      if (around && isAncestor(container, around)) {
+        // Only use this node if the selection is not at its start.
+        range2.moveToElementText(around);
+        if (range.compareEndPoints("StartToStart", range2) == 1)
+          return topLevelNodeAt(around, container);
+      }
+
+      // Move the start of a range to the start of a node,
+      // compensating for the fact that you can't call
+      // moveToElementText with text nodes.
+      function moveToNodeStart(range, node) {
+        if (node.nodeType == 3) {
+          var count = 0, cur = node.previousSibling;
+          while (cur && cur.nodeType == 3) {
+            count += cur.nodeValue.length;
+            cur = cur.previousSibling;
+          }
+          if (cur) {
+            try{range.moveToElementText(cur);}
+            catch(e){return false;}
+            range.collapse(false);
+          }
+          else range.moveToElementText(node.parentNode);
+          if (count) range.move("character", count);
+        }
+        else {
+          try{range.moveToElementText(node);}
+          catch(e){return false;}
+        }
+        return true;
+      }
+
+      // Do a binary search through the container object, comparing
+      // the start of each node to the selection
+      var start = 0, end = container.childNodes.length - 1;
+      while (start < end) {
+        var middle = Math.ceil((end + start) / 2), node = container.childNodes[middle];
+        if (!node) return false; // Don't ask. IE6 manages this sometimes.
+        if (!moveToNodeStart(range2, node)) return false;
+        if (range.compareEndPoints("StartToStart", range2) == 1)
+          start = middle;
+        else
+          end = middle - 1;
+      }
+      
+      if (start == 0) {
+        var test1 = selRange(), test2 = test1.duplicate();
+        try {
+          test2.moveToElementText(container);
+        } catch(exception) {
+          return null;
+        }
+        if (test1.compareEndPoints("StartToStart", test2) == 0)
+          return null;
+      }
+      return container.childNodes[start] || null;
+    };
+
+    // Place the cursor after this.start. This is only useful when
+    // manually moving the cursor instead of restoring it to its old
+    // position.
+    select.focusAfterNode = function(node, container) {
+      var range = document.body.createTextRange();
+      range.moveToElementText(node || container);
+      range.collapse(!node);
+      range.select();
+    };
+
+    select.somethingSelected = function() {
+      var range = selRange();
+      return range && (range.text != "");
+    };
+
+    function insertAtCursor(html) {
+      var range = selRange();
+      if (range) {
+        range.pasteHTML(html);
+        range.collapse(false);
+        range.select();
+      }
+    }
+
+    // Used to normalize the effect of the enter key, since browsers
+    // do widely different things when pressing enter in designMode.
+    select.insertNewlineAtCursor = function() {
+      insertAtCursor("<br>");
+    };
+
+    select.insertTabAtCursor = function() {
+      insertAtCursor(fourSpaces);
+    };
+
+    // Get the BR node at the start of the line on which the cursor
+    // currently is, and the offset into the line. Returns null as
+    // node if cursor is on first line.
+    select.cursorPos = function(container, start) {
+      var range = selRange();
+      if (!range) return null;
+
+      var topNode = select.selectionTopNode(container, start);
+      while (topNode && !isBR(topNode))
+        topNode = topNode.previousSibling;
+
+      var range2 = range.duplicate();
+      range.collapse(start);
+      if (topNode) {
+        range2.moveToElementText(topNode);
+        range2.collapse(false);
+      }
+      else {
+        // When nothing is selected, we can get all kinds of funky errors here.
+        try { range2.moveToElementText(container); }
+        catch (e) { return null; }
+        range2.collapse(true);
+      }
+      range.setEndPoint("StartToStart", range2);
+
+      return {node: topNode, offset: range.text.length};
+    };
+
+    select.setCursorPos = function(container, from, to) {
+      function rangeAt(pos) {
+        var range = document.body.createTextRange();
+        if (!pos.node) {
+          range.moveToElementText(container);
+          range.collapse(true);
+        }
+        else {
+          range.moveToElementText(pos.node);
+          range.collapse(false);
+        }
+        range.move("character", pos.offset);
+        return range;
+      }
+
+      var range = rangeAt(from);
+      if (to && to != from)
+        range.setEndPoint("EndToEnd", rangeAt(to));
+      range.select();
+    }
+
+    // Some hacks for storing and re-storing the selection when the editor loses and regains focus.
+    select.getBookmark = function (container) {
+      var from = select.cursorPos(container, true), to = select.cursorPos(container, false);
+      if (from && to) return {from: from, to: to};
+    };
+
+    // Restore a stored selection.
+    select.setBookmark = function(container, mark) {
+      if (!mark) return;
+      select.setCursorPos(container, mark.from, mark.to);
+    };
+  }
+  // W3C model
+  else {
+    // Find the node right at the cursor, not one of its
+    // ancestors with a suitable offset. This goes down the DOM tree
+    // until a 'leaf' is reached (or is it *up* the DOM tree?).
+    function innerNode(node, offset) {
+      while (node.nodeType != 3 && !isBR(node)) {
+        var newNode = node.childNodes[offset] || node.nextSibling;
+        offset = 0;
+        while (!newNode && node.parentNode) {
+          node = node.parentNode;
+          newNode = node.nextSibling;
+        }
+        node = newNode;
+        if (!newNode) break;
+      }
+      return {node: node, offset: offset};
+    }
+
+    // Store start and end nodes, and offsets within these, and refer
+    // back to the selection object from those nodes, so that this
+    // object can be updated when the nodes are replaced before the
+    // selection is restored.
+    select.markSelection = function () {
+      var selection = window.getSelection();
+      if (!selection || selection.rangeCount == 0)
+        return (currentSelection = null);
+      var range = selection.getRangeAt(0);
+
+      currentSelection = {
+        start: innerNode(range.startContainer, range.startOffset),
+        end: innerNode(range.endContainer, range.endOffset),
+        changed: false
+      };
+    };
+
+    select.selectMarked = function () {
+      var cs = currentSelection;
+      // on webkit-based browsers, it is apparently possible that the
+      // selection gets reset even when a node that is not one of the
+      // endpoints get messed with. the most common situation where
+      // this occurs is when a selection is deleted or overwitten. we
+      // check for that here.
+      function focusIssue() {
+        if (cs.start.node == cs.end.node && cs.start.offset == cs.end.offset) {
+          var selection = window.getSelection();
+          if (!selection || selection.rangeCount == 0) return true;
+          var range = selection.getRangeAt(0), point = innerNode(range.startContainer, range.startOffset);
+          return cs.start.node != point.node || cs.start.offset != point.offset;
+        }
+      }
+      if (!cs || !(cs.changed || (webkit && focusIssue()))) return;
+      var range = document.createRange();
+
+      function setPoint(point, which) {
+        if (point.node) {
+          // Some magic to generalize the setting of the start and end
+          // of a range.
+          if (point.offset == 0)
+            range["set" + which + "Before"](point.node);
+          else
+            range["set" + which](point.node, point.offset);
+        }
+        else {
+          range.setStartAfter(document.body.lastChild || document.body);
+        }
+      }
+
+      setPoint(cs.end, "End");
+      setPoint(cs.start, "Start");
+      selectRange(range);
+    };
+
+    // Helper for selecting a range object.
+    function selectRange(range) {
+      var selection = window.getSelection();
+      if (!selection) return;
+      selection.removeAllRanges();
+      selection.addRange(range);
+    }
+    function selectionRange() {
+      var selection = window.getSelection();
+      if (!selection || selection.rangeCount == 0)
+        return false;
+      else
+        return selection.getRangeAt(0);
+    }
+
+    // Finding the top-level node at the cursor in the W3C is, as you
+    // can see, quite an involved process.
+    select.selectionTopNode = function(container, start) {
+      var range = selectionRange();
+      if (!range) return false;
+
+      var node = start ? range.startContainer : range.endContainer;
+      var offset = start ? range.startOffset : range.endOffset;
+      // Work around (yet another) bug in Opera's selection model.
+      if (window.opera && !start && range.endContainer == container && range.endOffset == range.startOffset + 1 &&
+          container.childNodes[range.startOffset] && isBR(container.childNodes[range.startOffset]))
+        offset--;
+
+      // For text nodes, we look at the node itself if the cursor is
+      // inside, or at the node before it if the cursor is at the
+      // start.
+      if (node.nodeType == 3){
+        if (offset > 0)
+          return topLevelNodeAt(node, container);
+        else
+          return topLevelNodeBefore(node, container);
+      }
+      // Occasionally, browsers will return the HTML node as
+      // selection. If the offset is 0, we take the start of the frame
+      // ('after null'), otherwise, we take the last node.
+      else if (node.nodeName.toUpperCase() == "HTML") {
+        return (offset == 1 ? null : container.lastChild);
+      }
+      // If the given node is our 'container', we just look up the
+      // correct node by using the offset.
+      else if (node == container) {
+        return (offset == 0) ? null : node.childNodes[offset - 1];
+      }
+      // In any other case, we have a regular node. If the cursor is
+      // at the end of the node, we use the node itself, if it is at
+      // the start, we use the node before it, and in any other
+      // case, we look up the child before the cursor and use that.
+      else {
+        if (offset == node.childNodes.length)
+          return topLevelNodeAt(node, container);
+        else if (offset == 0)
+          return topLevelNodeBefore(node, container);
+        else
+          return topLevelNodeAt(node.childNodes[offset - 1], container);
+      }
+    };
+
+    select.focusAfterNode = function(node, container) {
+      var range = document.createRange();
+      range.setStartBefore(container.firstChild || container);
+      // In Opera, setting the end of a range at the end of a line
+      // (before a BR) will cause the cursor to appear on the next
+      // line, so we set the end inside of the start node when
+      // possible.
+      if (node && !node.firstChild)
+        range.setEndAfter(node);
+      else if (node)
+        range.setEnd(node, node.childNodes.length);
+      else
+        range.setEndBefore(container.firstChild || container);
+      range.collapse(false);
+      selectRange(range);
+    };
+
+    select.somethingSelected = function() {
+      var range = selectionRange();
+      return range && !range.collapsed;
+    };
+
+    select.offsetInNode = function(node) {
+      var range = selectionRange();
+      if (!range) return 0;
+      range = range.cloneRange();
+      range.setStartBefore(node);
+      return range.toString().length;
+    };
+
+    select.insertNodeAtCursor = function(node) {
+      var range = selectionRange();
+      if (!range) return;
+
+      range.deleteContents();
+      range.insertNode(node);
+      webkitLastLineHack(document.body);
+
+      // work around weirdness where Opera will magically insert a new
+      // BR node when a BR node inside a span is moved around. makes
+      // sure the BR ends up outside of spans.
+      if (window.opera && isBR(node) && isSpan(node.parentNode)) {
+        var next = node.nextSibling, p = node.parentNode, outer = p.parentNode;
+        outer.insertBefore(node, p.nextSibling);
+        var textAfter = "";
+        for (; next && next.nodeType == 3; next = next.nextSibling) {
+          textAfter += next.nodeValue;
+          removeElement(next);
+        }
+        outer.insertBefore(makePartSpan(textAfter, document), node.nextSibling);
+      }
+      range = document.createRange();
+      range.selectNode(node);
+      range.collapse(false);
+      selectRange(range);
+    }
+
+    select.insertNewlineAtCursor = function() {
+      select.insertNodeAtCursor(document.createElement("BR"));
+    };
+
+    select.insertTabAtCursor = function() {
+      select.insertNodeAtCursor(document.createTextNode(fourSpaces));
+    };
+
+    select.cursorPos = function(container, start) {
+      var range = selectionRange();
+      if (!range) return;
+
+      var topNode = select.selectionTopNode(container, start);
+      while (topNode && !isBR(topNode))
+        topNode = topNode.previousSibling;
+
+      range = range.cloneRange();
+      range.collapse(start);
+      if (topNode)
+        range.setStartAfter(topNode);
+      else
+        range.setStartBefore(container);
+
+      var text = range.toString();
+      return {node: topNode, offset: text.length};
+    };
+
+    select.setCursorPos = function(container, from, to) {
+      var range = document.createRange();
+
+      function setPoint(node, offset, side) {
+        if (offset == 0 && node && !node.nextSibling) {
+          range["set" + side + "After"](node);
+          return true;
+        }
+
+        if (!node)
+          node = container.firstChild;
+        else
+          node = node.nextSibling;
+
+        if (!node) return;
+
+        if (offset == 0) {
+          range["set" + side + "Before"](node);
+          return true;
+        }
+
+        var backlog = []
+        function decompose(node) {
+          if (node.nodeType == 3)
+            backlog.push(node);
+          else
+            forEach(node.childNodes, decompose);
+        }
+        while (true) {
+          while (node && !backlog.length) {
+            decompose(node);
+            node = node.nextSibling;
+          }
+          var cur = backlog.shift();
+          if (!cur) return false;
+
+          var length = cur.nodeValue.length;
+          if (length >= offset) {
+            range["set" + side](cur, offset);
+            return true;
+          }
+          offset -= length;
+        }
+      }
+
+      to = to || from;
+      if (setPoint(to.node, to.offset, "End") && setPoint(from.node, from.offset, "Start"))
+        selectRange(range);
+    };
+  }
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/stringstream.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,159 @@
+/* String streams are the things fed to parsers (which can feed them
+ * to a tokenizer if they want). They provide peek and next methods
+ * for looking at the current character (next 'consumes' this
+ * character, peek does not), and a get method for retrieving all the
+ * text that was consumed since the last time get was called.
+ *
+ * An easy mistake to make is to let a StopIteration exception finish
+ * the token stream while there are still characters pending in the
+ * string stream (hitting the end of the buffer while parsing a
+ * token). To make it easier to detect such errors, the stringstreams
+ * throw an exception when this happens.
+ */
+
+// Make a stringstream stream out of an iterator that returns strings.
+// This is applied to the result of traverseDOM (see codemirror.js),
+// and the resulting stream is fed to the parser.
+var stringStream = function(source){
+  // String that's currently being iterated over.
+  var current = "";
+  // Position in that string.
+  var pos = 0;
+  // Accumulator for strings that have been iterated over but not
+  // get()-ed yet.
+  var accum = "";
+  // Make sure there are more characters ready, or throw
+  // StopIteration.
+  function ensureChars() {
+    while (pos == current.length) {
+      accum += current;
+      current = ""; // In case source.next() throws
+      pos = 0;
+      try {current = source.next();}
+      catch (e) {
+        if (e != StopIteration) throw e;
+        else return false;
+      }
+    }
+    return true;
+  }
+
+  return {
+    // peek: -> character
+    // Return the next character in the stream.
+    peek: function() {
+      if (!ensureChars()) return null;
+      return current.charAt(pos);
+    },
+    // next: -> character
+    // Get the next character, throw StopIteration if at end, check
+    // for unused content.
+    next: function() {
+      if (!ensureChars()) {
+        if (accum.length > 0)
+          throw "End of stringstream reached without emptying buffer ('" + accum + "').";
+        else
+          throw StopIteration;
+      }
+      return current.charAt(pos++);
+    },
+    // get(): -> string
+    // Return the characters iterated over since the last call to
+    // .get().
+    get: function() {
+      var temp = accum;
+      accum = "";
+      if (pos > 0){
+        temp += current.slice(0, pos);
+        current = current.slice(pos);
+        pos = 0;
+      }
+      return temp;
+    },
+    // Push a string back into the stream.
+    push: function(str) {
+      current = current.slice(0, pos) + str + current.slice(pos);
+    },
+    lookAhead: function(str, consume, skipSpaces, caseInsensitive) {
+      function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}
+      str = cased(str);
+      var found = false;
+
+      var _accum = accum, _pos = pos;
+      if (skipSpaces) this.nextWhileMatches(/[\s\u00a0]/);
+
+      while (true) {
+        var end = pos + str.length, left = current.length - pos;
+        if (end <= current.length) {
+          found = str == cased(current.slice(pos, end));
+          pos = end;
+          break;
+        }
+        else if (str.slice(0, left) == cased(current.slice(pos))) {
+          accum += current; current = "";
+          try {current = source.next();}
+          catch (e) {if (e != StopIteration) throw e; break;}
+          pos = 0;
+          str = str.slice(left);
+        }
+        else {
+          break;
+        }
+      }
+
+      if (!(found && consume)) {
+        current = accum.slice(_accum.length) + current;
+        pos = _pos;
+        accum = _accum;
+      }
+
+      return found;
+    },
+    // Wont't match past end of line.
+    lookAheadRegex: function(regex, consume) {
+      if (regex.source.charAt(0) != "^")
+        throw new Error("Regexps passed to lookAheadRegex must start with ^");
+
+      // Fetch the rest of the line
+      while (current.indexOf("\n", pos) == -1) {
+        try {current += source.next();}
+        catch (e) {if (e != StopIteration) throw e; break;}
+      }
+      var matched = current.slice(pos).match(regex);
+      if (matched && consume) pos += matched[0].length;
+      return matched;
+    },
+
+    // Utils built on top of the above
+    // more: -> boolean
+    // Produce true if the stream isn't empty.
+    more: function() {
+      return this.peek() !== null;
+    },
+    applies: function(test) {
+      var next = this.peek();
+      return (next !== null && test(next));
+    },
+    nextWhile: function(test) {
+      var next;
+      while ((next = this.peek()) !== null && test(next))
+        this.next();
+    },
+    matches: function(re) {
+      var next = this.peek();
+      return (next !== null && re.test(next));
+    },
+    nextWhileMatches: function(re) {
+      var next;
+      while ((next = this.peek()) !== null && re.test(next))
+        this.next();
+    },
+    equals: function(ch) {
+      return ch === this.peek();
+    },
+    endOfLine: function() {
+      var next = this.peek();
+      return next == null || next == "\n";
+    }
+  };
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/tokenize.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,57 @@
+// A framework for simple tokenizers. Takes care of newlines and
+// white-space, and of getting the text from the source stream into
+// the token object. A state is a function of two arguments -- a
+// string stream and a setState function. The second can be used to
+// change the tokenizer's state, and can be ignored for stateless
+// tokenizers. This function should advance the stream over a token
+// and return a string or object containing information about the next
+// token, or null to pass and have the (new) state be called to finish
+// the token. When a string is given, it is wrapped in a {style, type}
+// object. In the resulting object, the characters consumed are stored
+// under the content property. Any whitespace following them is also
+// automatically consumed, and added to the value property. (Thus,
+// content is the actual meaningful part of the token, while value
+// contains all the text it spans.)
+
+function tokenizer(source, state) {
+  // Newlines are always a separate token.
+  function isWhiteSpace(ch) {
+    // The messy regexp is because IE's regexp matcher is of the
+    // opinion that non-breaking spaces are no whitespace.
+    return ch != "\n" && /^[\s\u00a0]*$/.test(ch);
+  }
+
+  var tokenizer = {
+    state: state,
+
+    take: function(type) {
+      if (typeof(type) == "string")
+        type = {style: type, type: type};
+
+      type.content = (type.content || "") + source.get();
+      if (!/\n$/.test(type.content))
+        source.nextWhile(isWhiteSpace);
+      type.value = type.content + source.get();
+      return type;
+    },
+
+    next: function () {
+      if (!source.more()) throw StopIteration;
+
+      var type;
+      if (source.equals("\n")) {
+        source.next();
+        return this.take("whitespace");
+      }
+      
+      if (source.applies(isWhiteSpace))
+        type = "whitespace";
+      else
+        while (!type)
+          type = this.state(source, function(s) {tokenizer.state = s;});
+
+      return this.take(type);
+    }
+  };
+  return tokenizer;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/tokenizejavascript.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,174 @@
+/* Tokenizer for JavaScript code */
+
+var tokenizeJavaScript = (function() {
+  // Advance the stream until the given character (not preceded by a
+  // backslash) is encountered, or the end of the line is reached.
+  function nextUntilUnescaped(source, end) {
+    var escaped = false;
+    while (!source.endOfLine()) {
+      var next = source.next();
+      if (next == end && !escaped)
+        return false;
+      escaped = !escaped && next == "\\";
+    }
+    return escaped;
+  }
+
+  // A map of JavaScript's keywords. The a/b/c keyword distinction is
+  // very rough, but it gives the parser enough information to parse
+  // correct code correctly (we don't care that much how we parse
+  // incorrect code). The style information included in these objects
+  // is used by the highlighter to pick the correct CSS style for a
+  // token.
+  var keywords = function(){
+    function result(type, style){
+      return {type: type, style: "js-" + style};
+    }
+    // keywords that take a parenthised expression, and then a
+    // statement (if)
+    var keywordA = result("keyword a", "keyword");
+    // keywords that take just a statement (else)
+    var keywordB = result("keyword b", "keyword");
+    // keywords that optionally take an expression, and form a
+    // statement (return)
+    var keywordC = result("keyword c", "keyword");
+    var operator = result("operator", "keyword");
+    var atom = result("atom", "atom");
+    return {
+      "if": keywordA, "while": keywordA, "with": keywordA,
+      "else": keywordB, "do": keywordB, "try": keywordB, "finally": keywordB,
+      "return": keywordC, "break": keywordC, "continue": keywordC, "new": keywordC, "delete": keywordC, "throw": keywordC,
+      "in": operator, "typeof": operator, "instanceof": operator,
+      "var": result("var", "keyword"), "function": result("function", "keyword"), "catch": result("catch", "keyword"),
+      "for": result("for", "keyword"), "switch": result("switch", "keyword"),
+      "case": result("case", "keyword"), "default": result("default", "keyword"),
+      "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
+    };
+  }();
+
+  // Some helper regexps
+  var isOperatorChar = /[+\-*&%=<>!?|]/;
+  var isHexDigit = /[0-9A-Fa-f]/;
+  var isWordChar = /[\w\$_]/;
+
+  // Wrapper around jsToken that helps maintain parser state (whether
+  // we are inside of a multi-line comment and whether the next token
+  // could be a regular expression).
+  function jsTokenState(inside, regexp) {
+    return function(source, setState) {
+      var newInside = inside;
+      var type = jsToken(inside, regexp, source, function(c) {newInside = c;});
+      var newRegexp = type.type == "operator" || type.type == "keyword c" || type.type.match(/^[\[{}\(,;:]$/);
+      if (newRegexp != regexp || newInside != inside)
+        setState(jsTokenState(newInside, newRegexp));
+      return type;
+    };
+  }
+
+  // The token reader, intended to be used by the tokenizer from
+  // tokenize.js (through jsTokenState). Advances the source stream
+  // over a token, and returns an object containing the type and style
+  // of that token.
+  function jsToken(inside, regexp, source, setInside) {
+    function readHexNumber(){
+      source.next(); // skip the 'x'
+      source.nextWhileMatches(isHexDigit);
+      return {type: "number", style: "js-atom"};
+    }
+
+    function readNumber() {
+      source.nextWhileMatches(/[0-9]/);
+      if (source.equals(".")){
+        source.next();
+        source.nextWhileMatches(/[0-9]/);
+      }
+      if (source.equals("e") || source.equals("E")){
+        source.next();
+        if (source.equals("-"))
+          source.next();
+        source.nextWhileMatches(/[0-9]/);
+      }
+      return {type: "number", style: "js-atom"};
+    }
+    // Read a word, look it up in keywords. If not found, it is a
+    // variable, otherwise it is a keyword of the type found.
+    function readWord() {
+      source.nextWhileMatches(isWordChar);
+      var word = source.get();
+      var known = keywords.hasOwnProperty(word) && keywords.propertyIsEnumerable(word) && keywords[word];
+      return known ? {type: known.type, style: known.style, content: word} :
+      {type: "variable", style: "js-variable", content: word};
+    }
+    function readRegexp() {
+      nextUntilUnescaped(source, "/");
+      source.nextWhileMatches(/[gimy]/); // 'y' is "sticky" option in Mozilla
+      return {type: "regexp", style: "js-string"};
+    }
+    // Mutli-line comments are tricky. We want to return the newlines
+    // embedded in them as regular newline tokens, and then continue
+    // returning a comment token for every line of the comment. So
+    // some state has to be saved (inside) to indicate whether we are
+    // inside a /* */ sequence.
+    function readMultilineComment(start){
+      var newInside = "/*";
+      var maybeEnd = (start == "*");
+      while (true) {
+        if (source.endOfLine())
+          break;
+        var next = source.next();
+        if (next == "/" && maybeEnd){
+          newInside = null;
+          break;
+        }
+        maybeEnd = (next == "*");
+      }
+      setInside(newInside);
+      return {type: "comment", style: "js-comment"};
+    }
+    function readOperator() {
+      source.nextWhileMatches(isOperatorChar);
+      return {type: "operator", style: "js-operator"};
+    }
+    function readString(quote) {
+      var endBackSlash = nextUntilUnescaped(source, quote);
+      setInside(endBackSlash ? quote : null);
+      return {type: "string", style: "js-string"};
+    }
+
+    // Fetch the next token. Dispatches on first character in the
+    // stream, or first two characters when the first is a slash.
+    if (inside == "\"" || inside == "'")
+      return readString(inside);
+    var ch = source.next();
+    if (inside == "/*")
+      return readMultilineComment(ch);
+    else if (ch == "\"" || ch == "'")
+      return readString(ch);
+    // with punctuation, the type of the token is the symbol itself
+    else if (/[\[\]{}\(\),;\:\.]/.test(ch))
+      return {type: ch, style: "js-punctuation"};
+    else if (ch == "0" && (source.equals("x") || source.equals("X")))
+      return readHexNumber();
+    else if (/[0-9]/.test(ch))
+      return readNumber();
+    else if (ch == "/"){
+      if (source.equals("*"))
+      { source.next(); return readMultilineComment(ch); }
+      else if (source.equals("/"))
+      { nextUntilUnescaped(source, null); return {type: "comment", style: "js-comment"};}
+      else if (regexp)
+        return readRegexp();
+      else
+        return readOperator();
+    }
+    else if (isOperatorChar.test(ch))
+      return readOperator();
+    else
+      return readWord();
+  }
+
+  // The external interface to the tokenizer.
+  return function(source, startState) {
+    return tokenizer(source, startState || jsTokenState(false, true));
+  };
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/undo.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,413 @@
+/**
+ * Storage and control for undo information within a CodeMirror
+ * editor. 'Why on earth is such a complicated mess required for
+ * that?', I hear you ask. The goal, in implementing this, was to make
+ * the complexity of storing and reverting undo information depend
+ * only on the size of the edited or restored content, not on the size
+ * of the whole document. This makes it necessary to use a kind of
+ * 'diff' system, which, when applied to a DOM tree, causes some
+ * complexity and hackery.
+ *
+ * In short, the editor 'touches' BR elements as it parses them, and
+ * the UndoHistory stores these. When nothing is touched in commitDelay
+ * milliseconds, the changes are committed: It goes over all touched
+ * nodes, throws out the ones that did not change since last commit or
+ * are no longer in the document, and assembles the rest into zero or
+ * more 'chains' -- arrays of adjacent lines. Links back to these
+ * chains are added to the BR nodes, while the chain that previously
+ * spanned these nodes is added to the undo history. Undoing a change
+ * means taking such a chain off the undo history, restoring its
+ * content (text is saved per line) and linking it back into the
+ * document.
+ */
+
+// A history object needs to know about the DOM container holding the
+// document, the maximum amount of undo levels it should store, the
+// delay (of no input) after which it commits a set of changes, and,
+// unfortunately, the 'parent' window -- a window that is not in
+// designMode, and on which setTimeout works in every browser.
+function UndoHistory(container, maxDepth, commitDelay, editor) {
+  this.container = container;
+  this.maxDepth = maxDepth; this.commitDelay = commitDelay;
+  this.editor = editor;
+  // This line object represents the initial, empty editor.
+  var initial = {text: "", from: null, to: null};
+  // As the borders between lines are represented by BR elements, the
+  // start of the first line and the end of the last one are
+  // represented by null. Since you can not store any properties
+  // (links to line objects) in null, these properties are used in
+  // those cases.
+  this.first = initial; this.last = initial;
+  // Similarly, a 'historyTouched' property is added to the BR in
+  // front of lines that have already been touched, and 'firstTouched'
+  // is used for the first line.
+  this.firstTouched = false;
+  // History is the set of committed changes, touched is the set of
+  // nodes touched since the last commit.
+  this.history = []; this.redoHistory = []; this.touched = []; this.lostundo = 0;
+}
+
+UndoHistory.prototype = {
+  // Schedule a commit (if no other touches come in for commitDelay
+  // milliseconds).
+  scheduleCommit: function() {
+    var self = this;
+    parent.clearTimeout(this.commitTimeout);
+    this.commitTimeout = parent.setTimeout(function(){self.tryCommit();}, this.commitDelay);
+  },
+
+  // Mark a node as touched. Null is a valid argument.
+  touch: function(node) {
+    this.setTouched(node);
+    this.scheduleCommit();
+  },
+
+  // Undo the last change.
+  undo: function() {
+    // Make sure pending changes have been committed.
+    this.commit();
+
+    if (this.history.length) {
+      // Take the top diff from the history, apply it, and store its
+      // shadow in the redo history.
+      var item = this.history.pop();
+      this.redoHistory.push(this.updateTo(item, "applyChain"));
+      this.notifyEnvironment();
+      return this.chainNode(item);
+    }
+  },
+
+  // Redo the last undone change.
+  redo: function() {
+    this.commit();
+    if (this.redoHistory.length) {
+      // The inverse of undo, basically.
+      var item = this.redoHistory.pop();
+      this.addUndoLevel(this.updateTo(item, "applyChain"));
+      this.notifyEnvironment();
+      return this.chainNode(item);
+    }
+  },
+
+  clear: function() {
+    this.history = [];
+    this.redoHistory = [];
+    this.lostundo = 0;
+  },
+
+  // Ask for the size of the un/redo histories.
+  historySize: function() {
+    return {undo: this.history.length, redo: this.redoHistory.length, lostundo: this.lostundo};
+  },
+
+  // Push a changeset into the document.
+  push: function(from, to, lines) {
+    var chain = [];
+    for (var i = 0; i < lines.length; i++) {
+      var end = (i == lines.length - 1) ? to : document.createElement("br");
+      chain.push({from: from, to: end, text: cleanText(lines[i])});
+      from = end;
+    }
+    this.pushChains([chain], from == null && to == null);
+    this.notifyEnvironment();
+  },
+
+  pushChains: function(chains, doNotHighlight) {
+    this.commit(doNotHighlight);
+    this.addUndoLevel(this.updateTo(chains, "applyChain"));
+    this.redoHistory = [];
+  },
+
+  // Retrieve a DOM node from a chain (for scrolling to it after undo/redo).
+  chainNode: function(chains) {
+    for (var i = 0; i < chains.length; i++) {
+      var start = chains[i][0], node = start && (start.from || start.to);
+      if (node) return node;
+    }
+  },
+
+  // Clear the undo history, make the current document the start
+  // position.
+  reset: function() {
+    this.history = []; this.redoHistory = []; this.lostundo = 0;
+  },
+
+  textAfter: function(br) {
+    return this.after(br).text;
+  },
+
+  nodeAfter: function(br) {
+    return this.after(br).to;
+  },
+
+  nodeBefore: function(br) {
+    return this.before(br).from;
+  },
+
+  // Commit unless there are pending dirty nodes.
+  tryCommit: function() {
+    if (!window || !window.parent || !window.UndoHistory) return; // Stop when frame has been unloaded
+    if (this.editor.highlightDirty()) this.commit(true);
+    else this.scheduleCommit();
+  },
+
+  // Check whether the touched nodes hold any changes, if so, commit
+  // them.
+  commit: function(doNotHighlight) {
+    parent.clearTimeout(this.commitTimeout);
+    // Make sure there are no pending dirty nodes.
+    if (!doNotHighlight) this.editor.highlightDirty(true);
+    // Build set of chains.
+    var chains = this.touchedChains(), self = this;
+
+    if (chains.length) {
+      this.addUndoLevel(this.updateTo(chains, "linkChain"));
+      this.redoHistory = [];
+      this.notifyEnvironment();
+    }
+  },
+
+  // [ end of public interface ]
+
+  // Update the document with a given set of chains, return its
+  // shadow. updateFunc should be "applyChain" or "linkChain". In the
+  // second case, the chains are taken to correspond the the current
+  // document, and only the state of the line data is updated. In the
+  // first case, the content of the chains is also pushed iinto the
+  // document.
+  updateTo: function(chains, updateFunc) {
+    var shadows = [], dirty = [];
+    for (var i = 0; i < chains.length; i++) {
+      shadows.push(this.shadowChain(chains[i]));
+      dirty.push(this[updateFunc](chains[i]));
+    }
+    if (updateFunc == "applyChain")
+      this.notifyDirty(dirty);
+    return shadows;
+  },
+
+  // Notify the editor that some nodes have changed.
+  notifyDirty: function(nodes) {
+    forEach(nodes, method(this.editor, "addDirtyNode"))
+    this.editor.scheduleHighlight();
+  },
+
+  notifyEnvironment: function() {
+    if (this.onChange) this.onChange(this.editor);
+    // Used by the line-wrapping line-numbering code.
+    if (window.frameElement && window.frameElement.CodeMirror.updateNumbers)
+      window.frameElement.CodeMirror.updateNumbers();
+  },
+
+  // Link a chain into the DOM nodes (or the first/last links for null
+  // nodes).
+  linkChain: function(chain) {
+    for (var i = 0; i < chain.length; i++) {
+      var line = chain[i];
+      if (line.from) line.from.historyAfter = line;
+      else this.first = line;
+      if (line.to) line.to.historyBefore = line;
+      else this.last = line;
+    }
+  },
+
+  // Get the line object after/before a given node.
+  after: function(node) {
+    return node ? node.historyAfter : this.first;
+  },
+  before: function(node) {
+    return node ? node.historyBefore : this.last;
+  },
+
+  // Mark a node as touched if it has not already been marked.
+  setTouched: function(node) {
+    if (node) {
+      if (!node.historyTouched) {
+        this.touched.push(node);
+        node.historyTouched = true;
+      }
+    }
+    else {
+      this.firstTouched = true;
+    }
+  },
+
+  // Store a new set of undo info, throw away info if there is more of
+  // it than allowed.
+  addUndoLevel: function(diffs) {
+    this.history.push(diffs);
+    if (this.history.length > this.maxDepth) {
+      this.history.shift();
+      this.lostundo += 1;
+    }
+  },
+
+  // Build chains from a set of touched nodes.
+  touchedChains: function() {
+    var self = this;
+
+    // The temp system is a crummy hack to speed up determining
+    // whether a (currently touched) node has a line object associated
+    // with it. nullTemp is used to store the object for the first
+    // line, other nodes get it stored in their historyTemp property.
+    var nullTemp = null;
+    function temp(node) {return node ? node.historyTemp : nullTemp;}
+    function setTemp(node, line) {
+      if (node) node.historyTemp = line;
+      else nullTemp = line;
+    }
+
+    function buildLine(node) {
+      var text = [];
+      for (var cur = node ? node.nextSibling : self.container.firstChild;
+           cur && (!isBR(cur) || cur.hackBR); cur = cur.nextSibling)
+        if (!cur.hackBR && cur.currentText) text.push(cur.currentText);
+      return {from: node, to: cur, text: cleanText(text.join(""))};
+    }
+
+    // Filter out unchanged lines and nodes that are no longer in the
+    // document. Build up line objects for remaining nodes.
+    var lines = [];
+    if (self.firstTouched) self.touched.push(null);
+    forEach(self.touched, function(node) {
+      if (node && (node.parentNode != self.container || node.hackBR)) return;
+
+      if (node) node.historyTouched = false;
+      else self.firstTouched = false;
+
+      var line = buildLine(node), shadow = self.after(node);
+      if (!shadow || shadow.text != line.text || shadow.to != line.to) {
+        lines.push(line);
+        setTemp(node, line);
+      }
+    });
+
+    // Get the BR element after/before the given node.
+    function nextBR(node, dir) {
+      var link = dir + "Sibling", search = node[link];
+      while (search && !isBR(search))
+        search = search[link];
+      return search;
+    }
+
+    // Assemble line objects into chains by scanning the DOM tree
+    // around them.
+    var chains = []; self.touched = [];
+    forEach(lines, function(line) {
+      // Note that this makes the loop skip line objects that have
+      // been pulled into chains by lines before them.
+      if (!temp(line.from)) return;
+
+      var chain = [], curNode = line.from, safe = true;
+      // Put any line objects (referred to by temp info) before this
+      // one on the front of the array.
+      while (true) {
+        var curLine = temp(curNode);
+        if (!curLine) {
+          if (safe) break;
+          else curLine = buildLine(curNode);
+        }
+        chain.unshift(curLine);
+        setTemp(curNode, null);
+        if (!curNode) break;
+        safe = self.after(curNode);
+        curNode = nextBR(curNode, "previous");
+      }
+      curNode = line.to; safe = self.before(line.from);
+      // Add lines after this one at end of array.
+      while (true) {
+        if (!curNode) break;
+        var curLine = temp(curNode);
+        if (!curLine) {
+          if (safe) break;
+          else curLine = buildLine(curNode);
+        }
+        chain.push(curLine);
+        setTemp(curNode, null);
+        safe = self.before(curNode);
+        curNode = nextBR(curNode, "next");
+      }
+      chains.push(chain);
+    });
+
+    return chains;
+  },
+
+  // Find the 'shadow' of a given chain by following the links in the
+  // DOM nodes at its start and end.
+  shadowChain: function(chain) {
+    var shadows = [], next = this.after(chain[0].from), end = chain[chain.length - 1].to;
+    while (true) {
+      shadows.push(next);
+      var nextNode = next.to;
+      if (!nextNode || nextNode == end)
+        break;
+      else
+        next = nextNode.historyAfter || this.before(end);
+      // (The this.before(end) is a hack -- FF sometimes removes
+      // properties from BR nodes, in which case the best we can hope
+      // for is to not break.)
+    }
+    return shadows;
+  },
+
+  // Update the DOM tree to contain the lines specified in a given
+  // chain, link this chain into the DOM nodes.
+  applyChain: function(chain) {
+    // Some attempt is made to prevent the cursor from jumping
+    // randomly when an undo or redo happens. It still behaves a bit
+    // strange sometimes.
+    var cursor = select.cursorPos(this.container, false), self = this;
+
+    // Remove all nodes in the DOM tree between from and to (null for
+    // start/end of container).
+    function removeRange(from, to) {
+      var pos = from ? from.nextSibling : self.container.firstChild;
+      while (pos != to) {
+        var temp = pos.nextSibling;
+        removeElement(pos);
+        pos = temp;
+      }
+    }
+
+    var start = chain[0].from, end = chain[chain.length - 1].to;
+    // Clear the space where this change has to be made.
+    removeRange(start, end);
+
+    // Insert the content specified by the chain into the DOM tree.
+    for (var i = 0; i < chain.length; i++) {
+      var line = chain[i];
+      // The start and end of the space are already correct, but BR
+      // tags inside it have to be put back.
+      if (i > 0)
+        self.container.insertBefore(line.from, end);
+
+      // Add the text.
+      var node = makePartSpan(fixSpaces(line.text));
+      self.container.insertBefore(node, end);
+      // See if the cursor was on this line. Put it back, adjusting
+      // for changed line length, if it was.
+      if (cursor && cursor.node == line.from) {
+        var cursordiff = 0;
+        var prev = this.after(line.from);
+        if (prev && i == chain.length - 1) {
+          // Only adjust if the cursor is after the unchanged part of
+          // the line.
+          for (var match = 0; match < cursor.offset &&
+               line.text.charAt(match) == prev.text.charAt(match); match++){}
+          if (cursor.offset > match)
+            cursordiff = line.text.length - prev.text.length;
+        }
+        select.setCursorPos(this.container, {node: line.from, offset: Math.max(0, cursor.offset + cursordiff)});
+      }
+      // Cursor was in removed line, this is last new line.
+      else if (cursor && (i == chain.length - 1) && cursor.node && cursor.node.parentNode != this.container) {
+        select.setCursorPos(this.container, {node: line.from, offset: line.text.length});
+      }
+    }
+
+    // Anchor the chain in the DOM tree.
+    this.linkChain(chain);
+    return start;
+  }
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/unittests.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,44 @@
+/**
+ * Test Harness for CodeMirror
+ * JS-unit compatible tests here.  The two available assertions are
+ * assertEquals (strict equality) and assertEquivalent (looser equivalency).
+ *
+ * 'editor' is a global object for the CodeMirror editor shared between all
+ * tests.  After manipulating it in each test, try to restore it to
+ * approximately its original state.
+ */
+
+function testSetGet() {
+  var code = 'It was the best of times.\nIt was the worst of times.';
+  editor.setCode(code);
+  assertEquals(code, editor.getCode());
+  editor.setCode('');
+  assertEquals('', editor.getCode());
+}
+
+function testSetStylesheet() {
+  function cssStatus() {
+    // Returns a list of tuples, for each CSS link return the filename and
+    // whether it is enabled.
+    links = editor.win.document.getElementsByTagName('link');
+    css = [];
+    for (var x = 0, link; link = links[x]; x++) {
+      if (link.rel.indexOf("stylesheet") !== -1) {
+        css.push([link.href.substring(link.href.lastIndexOf('/') + 1),
+                 !link.disabled])
+      }
+    }
+    return css;
+  }
+  assertEquivalent([], cssStatus());
+  editor.setStylesheet('css/jscolors.css');
+  assertEquivalent([['jscolors.css', true]], cssStatus());
+  editor.setStylesheet(['css/csscolors.css', 'css/xmlcolors.css']);
+  assertEquivalent([['jscolors.css', false], ['csscolors.css', true], ['xmlcolors.css', true]], cssStatus());
+  editor.setStylesheet([]);
+  assertEquivalent([['jscolors.css', false], ['csscolors.css', false], ['xmlcolors.css', false]], cssStatus());
+}
+
+// Update this list of tests as new ones are added.
+var tests = ['testSetGet', 'testSetStylesheet'];
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/codemirror/js/util.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,133 @@
+/* A few useful utility functions. */
+
+// Capture a method on an object.
+function method(obj, name) {
+  return function() {obj[name].apply(obj, arguments);};
+}
+
+// The value used to signal the end of a sequence in iterators.
+var StopIteration = {toString: function() {return "StopIteration"}};
+
+// Apply a function to each element in a sequence.
+function forEach(iter, f) {
+  if (iter.next) {
+    try {while (true) f(iter.next());}
+    catch (e) {if (e != StopIteration) throw e;}
+  }
+  else {
+    for (var i = 0; i < iter.length; i++)
+      f(iter[i]);
+  }
+}
+
+// Map a function over a sequence, producing an array of results.
+function map(iter, f) {
+  var accum = [];
+  forEach(iter, function(val) {accum.push(f(val));});
+  return accum;
+}
+
+// Create a predicate function that tests a string againsts a given
+// regular expression. No longer used but might be used by 3rd party
+// parsers.
+function matcher(regexp){
+  return function(value){return regexp.test(value);};
+}
+
+// Test whether a DOM node has a certain CSS class.
+function hasClass(element, className) {
+  var classes = element.className;
+  return classes && new RegExp("(^| )" + className + "($| )").test(classes);
+}
+function removeClass(element, className) {
+  element.className = element.className.replace(new RegExp(" " + className + "\\b", "g"), "");
+  return element;
+}
+
+// Insert a DOM node after another node.
+function insertAfter(newNode, oldNode) {
+  var parent = oldNode.parentNode;
+  parent.insertBefore(newNode, oldNode.nextSibling);
+  return newNode;
+}
+
+function removeElement(node) {
+  if (node.parentNode)
+    node.parentNode.removeChild(node);
+}
+
+function clearElement(node) {
+  while (node.firstChild)
+    node.removeChild(node.firstChild);
+}
+
+// Check whether a node is contained in another one.
+function isAncestor(node, child) {
+  while (child = child.parentNode) {
+    if (node == child)
+      return true;
+  }
+  return false;
+}
+
+// The non-breaking space character.
+var nbsp = "\u00a0";
+var matching = {"{": "}", "[": "]", "(": ")",
+                "}": "{", "]": "[", ")": "("};
+
+// Standardize a few unportable event properties.
+function normalizeEvent(event) {
+  if (!event.stopPropagation) {
+    event.stopPropagation = function() {this.cancelBubble = true;};
+    event.preventDefault = function() {this.returnValue = false;};
+  }
+  if (!event.stop) {
+    event.stop = function() {
+      this.stopPropagation();
+      this.preventDefault();
+    };
+  }
+
+  if (event.type == "keypress") {
+    event.code = (event.charCode == null) ? event.keyCode : event.charCode;
+    event.character = String.fromCharCode(event.code);
+  }
+  return event;
+}
+
+// Portably register event handlers.
+function addEventHandler(node, type, handler, removeFunc) {
+  function wrapHandler(event) {
+    handler(normalizeEvent(event || window.event));
+  }
+  if (typeof node.addEventListener == "function") {
+    node.addEventListener(type, wrapHandler, false);
+    if (removeFunc) return function() {node.removeEventListener(type, wrapHandler, false);};
+  }
+  else {
+    node.attachEvent("on" + type, wrapHandler);
+    if (removeFunc) return function() {node.detachEvent("on" + type, wrapHandler);};
+  }
+}
+
+function nodeText(node) {
+  return node.textContent || node.innerText || node.nodeValue || "";
+}
+
+function nodeTop(node) {
+  var top = 0;
+  while (node.offsetParent) {
+    top += node.offsetTop;
+    node = node.offsetParent;
+  }
+  return top;
+}
+
+function isBR(node) {
+  var nn = node.nodeName;
+  return nn == "BR" || nn == "br";
+}
+function isSpan(node) {
+  var nn = node.nodeName;
+  return nn == "SPAN" || nn == "span";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/dropdown_menu_hack.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,144 @@
+// based on http://www.hedgerwow.com/360/dhtml/ui_select_with_fixed_width/demo.php
+
+
+function dropdown_menu_hack(el)
+{
+	if( navigator.appName.indexOf("Microsoft") == -1 )
+		return;
+	if(el.runtimeStyle.behavior.toLowerCase() != "none"){
+		el.runtimeStyle.behavior="none";
+	
+		el.ondblclick = function(e)
+		{
+			window.event.returnValue = false;
+			return false;
+		}
+		
+	
+		function showMenu()
+		{
+			
+			function selectMenu(obj) {
+				el.contentIndex = obj.selectedIndex;
+				el.selectedIndex = el.contentIndex;
+				el.menu.hide();
+				el.onchange();
+			}		
+			
+			
+			el.menu.show(0 , el.offsetHeight, 10,  10, el);
+			var mb = el.menu.document.body;
+			
+//			mb.style.cssText ="border:solid 1px black;margin:0;padding:0;overflow-y:auto;overflow-x:auto;background:white;text-aligbn:center;font-family:Verdana;font-size:12px;";
+//			mb.style.cssText ="border:solid 1px black;margin:0;padding:0;overflow-y:auto;overflow-x:auto;background:white;font-family:tahoma,geneva,helvetica,arial,sans-serif;font-size:10pt;";
+			mb.style.border = "solid 1px black";
+			mb.style.overflowY = "auto";
+			mb.style.margin = "0";
+			mb.style.padding = "0";
+			mb.style.fontSize = "8pt";
+			mb.style.fontFamily = "tahoma,geneva,helvetica,arial,sans-serif";
+			var t = el.contentHTML;
+			t = t.replace(/<select/gi,'<ul');
+			t = t.replace(/<option/gi,'<li');
+			t = t.replace(/<\/option/gi,'</li');
+			t = t.replace(/<\/select/gi,'</ul');
+			t = t.replace(/>  /gi,'>&nbsp;&nbsp;');
+			mb.innerHTML = t;
+			
+			el.select = mb.all.tags("ul")[0];
+			el.select.style.cssText="list-style:none;margin:0;padding:0;";
+			mb.options = el.select.getElementsByTagName("li");
+			
+			for(var i=0;i<mb.options.length;i++)
+			{
+				mb.options[i].selectedIndex = i;
+				mb.options[i].style.cssText = "list-style:none;margin:0;padding:1px 2px;width:100%;cursor:hand;cursor:pointer;white-space:nowrap;"
+				mb.options[i].title = mb.options[i].innerHTML;
+				mb.options[i].innerHTML = "<nobr>" + mb.options[i].innerHTML + "</nobr>";
+
+				mb.options[i].onmouseover = function()
+					{
+						if( mb.options.selected ){mb.options.selected.style.background="white";mb.options.selected.style.color="black";}
+						mb.options.selected = this;
+						this.style.background = "#316ac5";
+						this.style.color = "white";
+					}
+
+				mb.options[i].onmouseout = function(){
+					this.style.background="white";this.style.color="black";
+				}
+
+				mb.options[i].onmousedown = function(){selectMenu(this);}
+				mb.options[i].onkeydown = function(){selectMenu(this);}
+					
+	
+				if(i == el.contentIndex)
+				{
+					mb.options[i].style.background = "#316ac5";
+					mb.options[i].style.color = "white";	
+					mb.options.selected = mb.options[i];
+				}
+			}
+		
+//			var mwPadding = 22;	
+			var mwPadding = 4;
+			var mw = Math.max(   ( el.select.offsetWidth + mwPadding ), el.offsetWidth + mwPadding  );
+			mw = Math.max(  mw, ( mb.scrollWidth+mwPadding) );
+//			var mh =  mb.options.length * 15  + 8 ;
+			var mh =  mb.options.length * 15  + 2 ;
+				 
+			var mx = 0;
+			var my = el.offsetHeight - 2;
+			var docH =   document.documentElement.offsetHeight ;
+			var bottomH = docH  - el.getBoundingClientRect().bottom ; 
+	
+			mh = Math.min(mh, Math.max(( docH - el.getBoundingClientRect().top - 50),100)		);
+			
+			if( bottomH < mh )
+			{
+				
+				mh = Math.max( (bottomH - 12),10);
+				if( mh <100 ) 
+				{
+					my = -100 ;
+	
+				}
+				mh = Math.max(mh,100);			
+			}
+	
+			
+			self.focus(); 
+			
+			el.menu.show( mx, my+2,  mw, mh , el); 
+//			sync = null;
+			if(mb.options.selected)
+			{
+				mb.scrollTop = mb.options.selected.offsetTop;
+			}
+			
+			window.onresize = function(){el.menu.hide()};		
+		}
+
+		if(dropdown_menu_hack.menu ==null)
+		{
+			dropdown_menu_hack.menu =  window.createPopup();
+			document.attachEvent("onkeydown",dropdown_menu_hack.menu.hide);
+		}
+		el.menu = dropdown_menu_hack.menu ;
+		el.onclick = showMenu;
+	}
+	el.contentOptions = new Array();
+	el.contentIndex = el.selectedIndex;
+	el.contentHTML = el.outerHTML;
+
+	for(var i=0;i<el.options.length;i++)
+	{	
+		el.contentOptions [el.contentOptions.length] = 
+		{
+			"value": el.options[i].value,
+			"text": el.options[i].innerHTML
+		}
+
+//		if(!el.options[i].selected){el.options[i].removeNode(true);i--;};
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/image-crop.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,537 @@
+/*
+(C) www.dhtmlgoodies.com, April 2006
+This is a script from www.dhtmlgoodies.com. You will find this and a lot of other scripts at our website.
+Terms of use:
+You are free to use this script as long as the copyright message is kept intact. However, you may not
+redistribute, sell or repost it without our permission.
+Thank you!
+www.dhtmlgoodies.com
+Alf Magne Kalleland
+*/
+var crop_script_alwaysPreserveAspectRatio = true;
+var crop_script_fixedRatio = false;
+var crop_script_browserIsOpera = navigator.userAgent.indexOf('Opera') >= 0 ? true : false;
+var cropDiv_left = false;
+var cropDiv_top = false;
+var cropDiv_right = false;
+var cropDiv_bottom = false;
+var cropDiv_dotted = false;
+var crop_currentResizeType = false;
+var cropEvent_posX;
+var cropEvent_posY;
+var cropEvent_eventX;
+var cropEvent_eventY;
+var crop_resizeCounter = -1;
+var crop_moveCounter = -1;
+var crop_imageDiv = false;
+var imageDiv_currentWidth = false;
+var imageDiv_currentHeight = false;
+var imageDiv_currentLeft = false;
+var imageDiv_currentTop = false;
+var smallSquare_tl;
+var smallSquare_tc;
+var smallSquare_tr;
+var smallSquare_lc;
+var smallSquare_rc;
+var smallSquare_bl;
+var smallSquare_bc;
+var smallSquare_br;
+
+var offsetSmallSquares = Math.floor(smallSquareWidth / 2);
+
+var cropScriptAjaxObjects = new Array();
+var preserveAspectRatio = false;
+
+var cropWidthRatio = false;
+// width of cropping area relative to height
+function crop_createDivElements(basedir) {
+	crop_imageDiv = document.getElementById('imageContainer');
+
+	cropDiv_left = document.createElement('DIV');
+	cropDiv_left.className = 'crop_transparentDiv';
+	cropDiv_left.style.visibility = 'visible';
+	cropDiv_left.style.left = '0px';
+	cropDiv_left.style.top = '0px';
+	cropDiv_left.style.height = crop_imageHeight + 'px';
+	cropDiv_left.style.width = '0px';
+	cropDiv_left.innerHTML = '<span></span>';
+	crop_imageDiv.appendChild(cropDiv_left);
+
+	cropDiv_top = document.createElement('DIV');
+	cropDiv_top.className = 'crop_transparentDiv';
+	cropDiv_top.style.visibility = 'visible';
+	cropDiv_top.style.left = '0px';
+	cropDiv_top.style.top = '0px';
+	cropDiv_top.style.height = '0px';
+	cropDiv_top.style.width = crop_imageWidth + 'px';
+	cropDiv_top.innerHTML = '<span></span>';
+	crop_imageDiv.appendChild(cropDiv_top);
+
+	cropDiv_right = document.createElement('DIV');
+	cropDiv_right.className = 'crop_transparentDiv';
+	cropDiv_right.style.visibility = 'visible';
+	cropDiv_right.style.left = (crop_imageWidth) + 'px';
+	cropDiv_right.style.top = '0px';
+	cropDiv_right.style.height = crop_imageHeight + 'px';
+	cropDiv_right.style.width = '0px';
+	cropDiv_right.innerHTML = '<span></span>';
+	crop_imageDiv.appendChild(cropDiv_right);
+
+	cropDiv_bottom = document.createElement('DIV');
+	cropDiv_bottom.className = 'crop_transparentDiv';
+	cropDiv_bottom.style.visibility = 'visible';
+	cropDiv_bottom.style.left = '0px';
+	cropDiv_bottom.style.top = (crop_imageHeight) + 'px';
+	cropDiv_bottom.style.height = '0px';
+	cropDiv_bottom.style.width = crop_imageWidth + 'px';
+	cropDiv_bottom.innerHTML = '<span></span>';
+	crop_imageDiv.appendChild(cropDiv_bottom);
+
+	cropDiv_dotted = document.createElement('DIV');
+	cropDiv_dotted.className = 'crop_dottedDiv';
+	cropDiv_dotted.style.left = '0px';
+	cropDiv_dotted.style.top = '0px';
+	cropDiv_dotted.style.width = (crop_imageWidth - (cropToolBorderWidth * 2)) + 'px';
+	cropDiv_dotted.style.height = (crop_imageHeight - (cropToolBorderWidth * 2)) + 'px';
+	cropDiv_dotted.innerHTML = '<div></div>';
+	cropDiv_dotted.style.cursor = 'move';
+
+	if (crop_script_browserIsOpera) {
+		var div = cropDiv_dotted.getElementsByTagName('DIV')[0];
+		div.style.backgroundColor = 'transparent';
+		cropDiv_bottom.style.backgroundColor = 'transparent';
+		cropDiv_right.style.backgroundColor = 'transparent';
+		cropDiv_top.style.backgroundColor = 'transparent';
+		cropDiv_left.style.backgroundColor = 'transparent';
+	}
+
+	cropDiv_dotted.onmousedown = cropScript_initMove;
+
+	smallSquare_tl = document.createElement('IMG');
+	smallSquare_tl.src = basedir + '/images/small_square.gif';
+	smallSquare_tl.style.position = 'absolute';
+	smallSquare_tl.style.left = (-offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_tl.style.top = (-offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_tl.style.cursor = 'nw-resize';
+	smallSquare_tl.id = 'nw-resize';
+	smallSquare_tl.onmousedown = cropScript_initResize;
+	cropDiv_dotted.appendChild(smallSquare_tl);
+
+	smallSquare_tr = document.createElement('IMG');
+	smallSquare_tr.src = basedir + '/images/small_square.gif';
+	smallSquare_tr.style.position = 'absolute';
+	smallSquare_tr.style.left = (crop_imageWidth - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_tr.style.top = (-offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_tr.style.cursor = 'ne-resize';
+	smallSquare_tr.id = 'ne-resize';
+	smallSquare_tr.onmousedown = cropScript_initResize;
+	cropDiv_dotted.appendChild(smallSquare_tr);
+
+	smallSquare_bl = document.createElement('IMG');
+	smallSquare_bl.src = basedir + '/images/small_square.gif';
+	smallSquare_bl.style.position = 'absolute';
+	smallSquare_bl.style.left = (-offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_bl.style.top = (crop_imageHeight - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_bl.style.cursor = 'sw-resize';
+	smallSquare_bl.id = 'sw-resize';
+	smallSquare_bl.onmousedown = cropScript_initResize;
+	cropDiv_dotted.appendChild(smallSquare_bl);
+
+	smallSquare_br = document.createElement('IMG');
+	smallSquare_br.src = basedir + '/images/small_square.gif';
+	smallSquare_br.style.position = 'absolute';
+	smallSquare_br.style.left = (crop_imageWidth - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_br.style.top = (crop_imageHeight - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_br.style.cursor = 'se-resize';
+	smallSquare_br.id = 'se-resize';
+	smallSquare_br.onmousedown = cropScript_initResize;
+	cropDiv_dotted.appendChild(smallSquare_br);
+
+	smallSquare_tc = document.createElement('IMG');
+	smallSquare_tc.src = basedir + '/images/small_square.gif';
+	smallSquare_tc.style.position = 'absolute';
+	smallSquare_tc.style.left = (Math.floor(crop_imageWidth / 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_tc.style.top = (-offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_tc.style.cursor = 's-resize';
+	smallSquare_tc.id = 'n-resize';
+	smallSquare_tc.onmousedown = cropScript_initResize;
+	cropDiv_dotted.appendChild(smallSquare_tc);
+
+	smallSquare_bc = document.createElement('IMG');
+	smallSquare_bc.src = basedir + '/images/small_square.gif';
+	smallSquare_bc.style.position = 'absolute';
+	smallSquare_bc.style.left = (Math.floor(crop_imageWidth / 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_bc.style.top = (crop_imageHeight - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_bc.style.cursor = 's-resize';
+	smallSquare_bc.id = 's-resize';
+	smallSquare_bc.onmousedown = cropScript_initResize;
+	cropDiv_dotted.appendChild(smallSquare_bc);
+
+	smallSquare_lc = document.createElement('IMG');
+	smallSquare_lc.src = basedir + '/images/small_square.gif';
+	smallSquare_lc.style.position = 'absolute';
+	smallSquare_lc.style.left = (-offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_lc.style.top = (Math.floor(crop_imageHeight / 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_lc.style.cursor = 'e-resize';
+	smallSquare_lc.id = 'w-resize';
+	smallSquare_lc.onmousedown = cropScript_initResize;
+	cropDiv_dotted.appendChild(smallSquare_lc);
+
+	smallSquare_rc = document.createElement('IMG');
+	smallSquare_rc.src = basedir + '/images/small_square.gif';
+	smallSquare_rc.style.position = 'absolute';
+	smallSquare_rc.style.left = (crop_imageWidth - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_rc.style.top = (Math.floor(crop_imageHeight / 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_rc.style.cursor = 'e-resize';
+	smallSquare_rc.id = 'e-resize';
+	smallSquare_rc.onmousedown = cropScript_initResize;
+	cropDiv_dotted.appendChild(smallSquare_rc);
+
+	crop_imageDiv.appendChild(cropDiv_dotted);
+};
+
+function cropScript_initMove(e) {
+	if (document.all)e = event;
+
+	if (e.target) source = e.target;
+	else if (e.srcElement) source = e.srcElement;
+	if (source.nodeType == 3) // defeat Safari bug
+		source = source.parentNode;
+
+	if (source.id && source.id.indexOf('resize') >= 0)return;
+
+	imageDiv_currentLeft = cropDiv_dotted.style.left.replace('px', '') / 1;
+	imageDiv_currentTop = cropDiv_dotted.style.top.replace('px', '') / 1;
+	imageDiv_currentWidth = cropDiv_dotted.style.width.replace('px', '') / 1;
+	imageDiv_currentHeight = cropDiv_dotted.style.height.replace('px', '') / 1;
+	cropEvent_eventX = e.clientX;
+	cropEvent_eventY = e.clientY;
+
+	crop_moveCounter = 0;
+	cropScript_timerMove();
+	return false;
+};
+
+function cropScript_timerMove() {
+	if (crop_moveCounter >= 0 && crop_moveCounter < 10) {
+		crop_moveCounter++;
+		setTimeout('cropScript_timerMove()', 1);
+		return;
+	}
+};
+
+function cropScript_initResize(e) {
+	if (document.all)e = event;
+
+	cropDiv_dotted.style.cursor = 'default';
+	crop_currentResizeType = this.id;
+
+	cropEvent_eventX = e.clientX;
+	cropEvent_eventY = e.clientY;
+	crop_resizeCounter = 0;
+	imageDiv_currentWidth = cropDiv_dotted.style.width.replace('px', '') / 1;
+	imageDiv_currentHeight = cropDiv_dotted.style.height.replace('px', '') / 1;
+	imageDiv_currentLeft = cropDiv_dotted.style.left.replace('px', '') / 1;
+	imageDiv_currentTop = cropDiv_dotted.style.top.replace('px', '') / 1;
+
+	cropWidthRatio = cropDiv_dotted.offsetWidth / cropDiv_dotted.offsetHeight;
+	if (crop_script_fixedRatio)cropWidthRatio = crop_script_fixedRatio;
+
+	if (document.all) {
+		var div = cropDiv_dotted.getElementsByTagName('DIV')[0];
+		div.style.display = 'none';
+	}
+
+	cropScript_timerResize();
+	return false;
+};
+
+function cropScript_timerResize()
+{
+	if (crop_resizeCounter >= 0 && crop_resizeCounter < 10) {
+		crop_resizeCounter = crop_resizeCounter + 1;
+		setTimeout('cropScript_timerResize()', 1);
+		return;
+	}
+};
+
+function cropScript_executeCrop(buttonObj)
+{
+	buttonObj.style.visibility = 'hidden';
+	var ajaxIndex = cropScriptAjaxObjects.length;
+	cropScriptAjaxObjects[ajaxIndex] = new sack();
+	var url = crop_script_server_file + '?image_ref=' + document.getElementById('input_image_ref').value
+			+ '&x=' + document.getElementById('input_crop_x').value
+			+ '&y=' + document.getElementById('input_crop_y').value
+			+ '&width=' + document.getElementById('input_crop_width').value
+			+ '&height=' + document.getElementById('input_crop_height').value
+			+ '&percentSize=' + document.getElementById('crop_percent_size').value
+			+ '&convertTo=' + document.getElementById('input_convert_to').options[document.getElementById('input_convert_to').selectedIndex].value;
+
+	cropScriptAjaxObjects[ajaxIndex].requestFile = url;
+	// Specifying which file to get
+	cropScriptAjaxObjects[ajaxIndex].onCompletion = function() {
+		cropScript_cropCompleted(ajaxIndex, buttonObj);
+	};
+	cropScriptAjaxObjects[ajaxIndex].runAJAX();
+};
+
+function cropScript_cropCompleted(ajaxIndex, buttonObj)
+{
+	buttonObj.style.visibility = '';
+	eval(cropScriptAjaxObjects[ajaxIndex].response);
+	cropScriptAjaxObjects[ajaxIndex] = false;
+};
+
+function crop_cancelEvent(e)
+{
+	if (document.all)e = event;
+	if (e.target) source = e.target;
+	else if (e.srcElement) source = e.srcElement;
+	if (source.nodeType == 3) // defeat Safari bug
+		source = source.parentNode;
+
+	if (source.tagName && source.tagName.toLowerCase() == 'input')return true;
+	return false;
+};
+
+var mouseMoveEventInProgress = false;
+function cropScript_mouseMove(e)
+{
+	if (mouseMoveEventInProgress)return;
+	if (crop_moveCounter < 10 && crop_resizeCounter < 10)return;
+	if (document.all)mouseMoveEventInProgress = true;
+	if (document.all)e = event;
+
+	if (crop_resizeCounter == 10) {
+		if (crop_currentResizeType == 'e-resize' || crop_currentResizeType == 'ne-resize' || crop_currentResizeType == 'se-resize') {
+			cropDiv_dotted.style.width = Math.max(crop_minimumWidthHeight, (imageDiv_currentWidth + e.clientX - cropEvent_eventX)) + 'px';
+		}
+		if (crop_currentResizeType == 's-resize' || crop_currentResizeType == 'sw-resize' || crop_currentResizeType == 'se-resize') {
+			cropDiv_dotted.style.height = Math.max(crop_minimumWidthHeight, (imageDiv_currentHeight + e.clientY - cropEvent_eventY)) + 'px';
+		}
+
+		if (crop_currentResizeType == 'w-resize' || crop_currentResizeType == 'sw-resize' || crop_currentResizeType == 'nw-resize') {
+			var tmpTop = cropDiv_dotted.style.left.replace('px', '') / 1;
+			var newTop = Math.max(0, (imageDiv_currentLeft + e.clientX - cropEvent_eventX));
+			if ((newTop + crop_minimumWidthHeight) > (cropDiv_dotted.style.left.replace('px', '') / 1 + cropDiv_dotted.style.width.replace('px', '') / 1)) {
+				newTop = (cropDiv_dotted.style.left.replace('px', '') / 1 + cropDiv_dotted.style.width.replace('px', '') / 1) - crop_minimumWidthHeight;
+			}
+			cropDiv_dotted.style.left = newTop + 'px';
+			cropDiv_dotted.style.width = (cropDiv_dotted.style.width.replace('px', '') / 1 + tmpTop - cropDiv_dotted.style.left.replace('px', '') / 1) + 'px';
+		}
+
+		if (crop_currentResizeType == 'n-resize' || crop_currentResizeType == 'nw-resize' || crop_currentResizeType == 'ne-resize') {
+			var tmpTop = cropDiv_dotted.style.top.replace('px', '') / 1;
+			var newTop = Math.max(0, (imageDiv_currentTop + e.clientY - cropEvent_eventY));
+			if ((newTop + crop_minimumWidthHeight) > (cropDiv_dotted.style.top.replace('px', '') / 1 + cropDiv_dotted.style.height.replace('px', '') / 1)) {
+				newTop = (cropDiv_dotted.style.top.replace('px', '') / 1 + cropDiv_dotted.style.height.replace('px', '') / 1) - crop_minimumWidthHeight;
+			}
+			cropDiv_dotted.style.top = newTop + 'px';
+			cropDiv_dotted.style.height = (cropDiv_dotted.style.height.replace('px', '') / 1 + tmpTop - cropDiv_dotted.style.top.replace('px', '') / 1) + 'px';
+		}
+
+		if ((cropDiv_dotted.offsetHeight + (cropToolBorderWidth * 2) + cropDiv_dotted.style.top.replace('px', '') / 1) > crop_imageHeight)
+			cropDiv_dotted.style.height = (crop_imageHeight - cropDiv_dotted.style.top.replace('px', '') / 1 - (cropToolBorderWidth * 2)) + 'px';
+
+		if ((cropDiv_dotted.offsetWidth + (cropToolBorderWidth * 2) + cropDiv_dotted.style.left.replace('px', '') / 1) > crop_imageWidth)
+			cropDiv_dotted.style.width = (crop_imageWidth - cropDiv_dotted.style.left.replace('px', '') / 1 - (cropToolBorderWidth * 2)) + 'px';
+
+		if (e.ctrlKey || crop_script_alwaysPreserveAspectRatio)preserveAspectRatio = true; else preserveAspectRatio = false;
+
+		if (preserveAspectRatio) {
+			var tmpRatio = cropDiv_dotted.offsetWidth / cropDiv_dotted.offsetHeight;
+			if (tmpRatio < cropWidthRatio) {
+				cropDiv_dotted.style.width = (cropDiv_dotted.style.height.replace('px', '') / 1 * cropWidthRatio) + 'px';
+			} else {
+				cropDiv_dotted.style.height = (cropDiv_dotted.style.width.replace('px', '') / 1 / cropWidthRatio) + 'px';
+			}
+
+			if ((cropDiv_dotted.offsetHeight + (cropToolBorderWidth * 2) + cropDiv_dotted.style.top.replace('px', '') / 1) > crop_imageHeight) {
+				var ratioToAdjust = (crop_imageHeight - cropDiv_dotted.style.top.replace('px', '') / 1) / (cropDiv_dotted.offsetHeight + (cropToolBorderWidth * 2));
+				if (Math.round((cropDiv_dotted.style.width.replace('px', '') * ratioToAdjust) + cropToolBorderWidth) >= crop_minimumWidthHeight) {
+					cropDiv_dotted.style.height = Math.round((cropDiv_dotted.style.height.replace('px', '') * ratioToAdjust) + cropToolBorderWidth) + 'px';
+					cropDiv_dotted.style.width = Math.round((cropDiv_dotted.style.width.replace('px', '') * ratioToAdjust) + cropToolBorderWidth) + 'px';
+				} else {
+					cropDiv_dotted.style.top = (crop_imageHeight - cropDiv_dotted.style.height.replace('px', '')) + 'px';
+				}
+			}
+
+			if ((cropDiv_dotted.offsetWidth + (cropToolBorderWidth * 2) + cropDiv_dotted.style.left.replace('px', '') / 1) > crop_imageWidth) {
+				var ratioToAdjust = (crop_imageWidth - cropDiv_dotted.style.left.replace('px', '') / 1) / (cropDiv_dotted.offsetWidth + (cropToolBorderWidth * 2));
+				if (Math.round((cropDiv_dotted.style.height.replace('px', '') * ratioToAdjust) + cropToolBorderWidth) >= crop_minimumWidthHeight) {
+					cropDiv_dotted.style.height = Math.round((cropDiv_dotted.style.height.replace('px', '') * ratioToAdjust) + cropToolBorderWidth) + 'px';
+					cropDiv_dotted.style.width = Math.round((cropDiv_dotted.style.width.replace('px', '') * ratioToAdjust) + cropToolBorderWidth) + 'px';
+				} else {
+					cropDiv_dotted.style.left = (crop_imageWidth - cropDiv_dotted.style.width.replace('px', '')) + 'px';
+				}
+			}
+		}
+		if (!crop_script_fixedRatio && !e.ctrlKey)cropWidthRatio = cropDiv_dotted.offsetWidth / cropDiv_dotted.offsetHeight;
+	}
+
+	if (crop_moveCounter == 10) {
+		var tmpLeft = imageDiv_currentLeft + e.clientX - cropEvent_eventX;
+		if (tmpLeft < 0)tmpLeft = 0;
+		if ((tmpLeft + imageDiv_currentWidth + (cropToolBorderWidth * 2)) > crop_imageWidth)tmpLeft = crop_imageWidth - imageDiv_currentWidth - (cropToolBorderWidth * 2);
+		cropDiv_dotted.style.left = tmpLeft + 'px';
+		var tmpTop = imageDiv_currentTop + e.clientY - cropEvent_eventY;
+		if (tmpTop < 0)tmpTop = 0;
+		if ((tmpTop + imageDiv_currentHeight + (cropToolBorderWidth * 2)) > crop_imageHeight)tmpTop = crop_imageHeight - imageDiv_currentHeight - (cropToolBorderWidth * 2);
+		cropDiv_dotted.style.top = tmpTop + 'px';
+	}
+
+	var w = cropDiv_dotted.style.width.replace('px', '') / 1;
+	var h = cropDiv_dotted.style.height.replace('px', '') / 1;
+	if (w < 100) {
+		cropDiv_dotted.style.width = '100px';
+		cropDiv_dotted.style.left = '0px';
+	}
+	if (h < 100) { 
+		cropDiv_dotted.style.height = '100px';
+		cropDiv_dotted.style.top = '0px';
+	}
+
+	repositionSmallSquares();
+	resizeTransparentSquares();
+	if (updateFormValuesAsYouDrag)cropScript_updateFormValues();
+	mouseMoveEventInProgress = false;
+};
+
+function repositionSmallSquares()
+{
+	var w = cropDiv_dotted.style.width.replace('px', '') / 1;
+	var h = cropDiv_dotted.style.height.replace('px', '') / 1;
+	smallSquare_tc.style.left = (Math.floor((w + (cropToolBorderWidth * 2)) / 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_bc.style.left = (Math.floor((w + (cropToolBorderWidth * 2)) / 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_tr.style.left = (w + (cropToolBorderWidth * 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_rc.style.left = (w + (cropToolBorderWidth * 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_br.style.left = (w + (cropToolBorderWidth * 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+
+	smallSquare_br.style.top = (h + (cropToolBorderWidth * 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_bc.style.top = (h + (cropToolBorderWidth * 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_bl.style.top = (h + (cropToolBorderWidth * 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_lc.style.top = (Math.floor((h + cropToolBorderWidth) / 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+	smallSquare_rc.style.top = (Math.floor((h + cropToolBorderWidth) / 2) - offsetSmallSquares - (cropToolBorderWidth * 2)) + 'px';
+};
+
+function resizeTransparentSquares()
+{
+	var w = cropDiv_dotted.style.width.replace('px', '') / 1;
+	cropDiv_left.style.width = cropDiv_dotted.style.left;
+	cropDiv_right.style.width = Math.max(0, crop_imageWidth - (cropToolBorderWidth * 2) - (w + cropDiv_dotted.style.left.replace('px', '') / 1)) + 'px';
+	cropDiv_right.style.left = (w + (cropToolBorderWidth * 2) + cropDiv_dotted.style.left.replace('px', '') / 1) + 'px';
+	cropDiv_bottom.style.height = Math.max(0, crop_imageHeight - (cropToolBorderWidth * 2) - (cropDiv_dotted.style.height.replace('px', '') / 1 + cropDiv_dotted.style.top.replace('px', '') / 1)) + 'px';
+	cropDiv_bottom.style.top = (cropDiv_dotted.style.height.replace('px', '') / 1 + (cropToolBorderWidth * 2) + cropDiv_dotted.style.top.replace('px', '') / 1) + 'px';
+
+	cropDiv_top.style.height = cropDiv_dotted.style.top;
+
+	cropDiv_bottom.style.left = cropDiv_dotted.style.left;
+	cropDiv_bottom.style.width = (w + (cropToolBorderWidth * 2)) + 'px';
+	cropDiv_top.style.left = cropDiv_dotted.style.left;
+	cropDiv_top.style.width = (w + (cropToolBorderWidth * 2)) + 'px';
+
+	if (cropDiv_left.style.width == '0px')cropDiv_left.style.visibility = 'hidden'; else cropDiv_left.style.visibility = 'visible';
+	if (cropDiv_right.style.width == '0px')cropDiv_right.style.visibility = 'hidden'; else cropDiv_right.style.visibility = 'visible';
+	if (cropDiv_bottom.style.width == '0px')cropDiv_bottom.style.visibility = 'hidden'; else cropDiv_bottom.style.visibility = 'visible';
+};
+
+function cropScript_updateFormValues()
+{
+	document.getElementById('input_crop_x').value = Math.round(cropDiv_dotted.style.left.replace('px', '') / 1 * (crop_originalImageWidth / crop_imageWidth));
+	document.getElementById('input_crop_y').value = Math.round(cropDiv_dotted.style.top.replace('px', '') / 1 * (crop_originalImageHeight / crop_imageHeight));
+	document.getElementById('input_crop_width').value = Math.round((cropDiv_dotted.style.width.replace('px', '') / 1 + (cropToolBorderWidth * 2)) * (crop_originalImageWidth / crop_imageWidth));
+	document.getElementById('input_crop_height').value = Math.round((cropDiv_dotted.style.height.replace('px', '') / 1 + (cropToolBorderWidth * 2)) * (crop_originalImageHeight / crop_imageHeight));
+};
+
+function cropScript_stopResizeMove()
+{
+	crop_resizeCounter = -1;
+	crop_moveCounter = -1;
+	cropDiv_dotted.style.cursor = 'move';
+	cropScript_updateFormValues();
+	if (document.all) {
+		var div = cropDiv_dotted.getElementsByTagName('DIV')[0];
+		div.style.display = 'block';
+	}
+};
+
+function cropScript_setCropSizeByInput()
+{
+	var obj_x = document.getElementById('input_crop_x');
+	var obj_y = document.getElementById('input_crop_y');
+	var obj_width = document.getElementById('input_crop_width');
+	var obj_height = document.getElementById('input_crop_height');
+
+	obj_x.value = obj_x.value.replace(/[^0-9]/gi, '');
+	obj_y.value = obj_y.value.replace(/[^0-9]/gi, '');
+	obj_width.value = obj_width.value.replace(/[^0-9]/gi, '');
+	obj_height.value = obj_height.value.replace(/[^0-9]/gi, '');
+
+	if (obj_x.value.length == 0)obj_x.value = 0;
+	if (obj_y.value.length == 0)obj_y.value = 0;
+	if (obj_width.value.length == 0)obj_width.value = crop_originalImageWidth;
+	if (obj_height.value.length == 0)obj_height.value = crop_originalImageHeight;
+
+	if (obj_x.value > (crop_originalImageWidth - crop_minimumWidthHeight))obj_x.value = crop_originalImageWidth - crop_minimumWidthHeight;
+	if (obj_y.value > (crop_originalImageHeight - crop_minimumWidthHeight))obj_y.value = crop_originalImageHeight - crop_minimumWidthHeight;
+
+	if (obj_width.value / 1 > crop_originalImageWidth)obj_width.value = crop_originalImageWidth - obj_x.value / 1;
+	if (obj_height.value / 1 > crop_originalImageHeight)obj_height.value = crop_originalImageHeight - obj_y.value / 1;
+
+	if (obj_x.value / 1 + obj_width.value / 1 > crop_originalImageWidth)obj_width.value = crop_originalImageWidth - obj_x.value;
+	if (obj_y.value / 1 + obj_height.value / 1 > crop_originalImageHeight)obj_height.value = crop_originalImageHeight - obj_y.value;
+
+	cropDiv_dotted.style.left = Math.round(obj_x.value / 1 * (crop_imageWidth / crop_originalImageWidth)) + 'px';
+	cropDiv_dotted.style.top = Math.round(obj_y.value / 1 * (crop_imageHeight / crop_originalImageHeight)) + 'px';
+	cropDiv_dotted.style.width = Math.round((obj_width.value / 1 - (cropToolBorderWidth * 2)) * (crop_imageWidth / crop_originalImageWidth)) + 'px';
+	cropDiv_dotted.style.height = Math.round((obj_height.value / 1 - (cropToolBorderWidth * 2)) * (crop_imageHeight / crop_originalImageHeight)) + 'px';
+
+	repositionSmallSquares();
+	resizeTransparentSquares();
+};
+
+function cropScript_setBasicEvents()
+{
+	document.documentElement.ondragstart = crop_cancelEvent;
+	document.documentElement.onselectstart = crop_cancelEvent;
+	document.documentElement.onmousemove = cropScript_mouseMove;
+	document.documentElement.onmouseup = cropScript_stopResizeMove;
+
+	document.getElementById('input_crop_x').onblur = cropScript_setCropSizeByInput;
+	document.getElementById('input_crop_y').onblur = cropScript_setCropSizeByInput;
+	document.getElementById('input_crop_width').onblur = cropScript_setCropSizeByInput;
+	document.getElementById('input_crop_height').onblur = cropScript_setCropSizeByInput;
+	document.getElementById('crop_percent_size').onblur = cropScript_validatePercent;
+};
+
+function cropScript_validatePercent()
+{
+	this.value = this.value.replace(/[^0-9]/gi, '');
+	if (this.value.length == 0)this.value = '1';
+	if (this.value / 1 > crop_maximumPercent)this.value = '100';
+	if (this.value / 1 < crop_minimumPercent)this.value = crop_minimumPercent
+};
+
+function crop_initFixedRatio()
+{
+	crop_script_fixedRatio = 1;
+	document.getElementById('input_crop_width').value = Math.round(document.getElementById('input_crop_height').value) / crop_script_fixedRatio;
+	cropScript_setCropSizeByInput();
+};
+
+function init_imageCrop(basedir)
+{
+	cropScript_setBasicEvents();
+	crop_createDivElements(basedir);
+	cropScript_updateFormValues();
+	crop_initFixedRatio();
+
+	cropDiv_dotted.style.left = '0px';
+	cropDiv_dotted.style.top = '0px';
+	cropDiv_dotted.style.width = '100px';
+	cropDiv_dotted.style.height = '100px';
+	repositionSmallSquares();
+	resizeTransparentSquares();
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/jquery-1.7.2.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,9404 @@
+/*!
+ * jQuery JavaScript Library v1.7.2
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Wed Mar 21 12:46:34 2012 -0700
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document,
+	navigator = window.navigator,
+	location = window.location;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+		// The jQuery object is actually just the init constructor 'enhanced'
+		return new jQuery.fn.init( selector, context, rootjQuery );
+	},
+
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+
+	// Map over the $ in case of overwrite
+	_$ = window.$,
+
+	// A central reference to the root jQuery(document)
+	rootjQuery,
+
+	// A simple way to check for HTML strings or ID strings
+	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+	quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+	// Check if a string has a non-whitespace character in it
+	rnotwhite = /\S/,
+
+	// Used for trimming whitespace
+	trimLeft = /^\s+/,
+	trimRight = /\s+$/,
+
+	// Match a standalone tag
+	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+	// JSON RegExp
+	rvalidchars = /^[\],:{}\s]*$/,
+	rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+	rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+	rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+	// Useragent RegExp
+	rwebkit = /(webkit)[ \/]([\w.]+)/,
+	ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+	rmsie = /(msie) ([\w.]+)/,
+	rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+	// Matches dashed string for camelizing
+	rdashAlpha = /-([a-z]|[0-9])/ig,
+	rmsPrefix = /^-ms-/,
+
+	// Used by jQuery.camelCase as callback to replace()
+	fcamelCase = function( all, letter ) {
+		return ( letter + "" ).toUpperCase();
+	},
+
+	// Keep a UserAgent string for use with jQuery.browser
+	userAgent = navigator.userAgent,
+
+	// For matching the engine and version of the browser
+	browserMatch,
+
+	// The deferred used on DOM ready
+	readyList,
+
+	// The ready event handler
+	DOMContentLoaded,
+
+	// Save a reference to some core methods
+	toString = Object.prototype.toString,
+	hasOwn = Object.prototype.hasOwnProperty,
+	push = Array.prototype.push,
+	slice = Array.prototype.slice,
+	trim = String.prototype.trim,
+	indexOf = Array.prototype.indexOf,
+
+	// [[Class]] -> type pairs
+	class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+	constructor: jQuery,
+	init: function( selector, context, rootjQuery ) {
+		var match, elem, ret, doc;
+
+		// Handle $(""), $(null), or $(undefined)
+		if ( !selector ) {
+			return this;
+		}
+
+		// Handle $(DOMElement)
+		if ( selector.nodeType ) {
+			this.context = this[0] = selector;
+			this.length = 1;
+			return this;
+		}
+
+		// The body element only exists once, optimize finding it
+		if ( selector === "body" && !context && document.body ) {
+			this.context = document;
+			this[0] = document.body;
+			this.selector = selector;
+			this.length = 1;
+			return this;
+		}
+
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			// Are we dealing with HTML string or an ID?
+			if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+				// Assume that strings that start and end with <> are HTML and skip the regex check
+				match = [ null, selector, null ];
+
+			} else {
+				match = quickExpr.exec( selector );
+			}
+
+			// Verify a match, and that no context was specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] ) {
+					context = context instanceof jQuery ? context[0] : context;
+					doc = ( context ? context.ownerDocument || context : document );
+
+					// If a single string is passed in and it's a single tag
+					// just do a createElement and skip the rest
+					ret = rsingleTag.exec( selector );
+
+					if ( ret ) {
+						if ( jQuery.isPlainObject( context ) ) {
+							selector = [ document.createElement( ret[1] ) ];
+							jQuery.fn.attr.call( selector, context, true );
+
+						} else {
+							selector = [ doc.createElement( ret[1] ) ];
+						}
+
+					} else {
+						ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+						selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
+					}
+
+					return jQuery.merge( this, selector );
+
+				// HANDLE: $("#id")
+				} else {
+					elem = document.getElementById( match[2] );
+
+					// Check parentNode to catch when Blackberry 4.6 returns
+					// nodes that are no longer in the document #6963
+					if ( elem && elem.parentNode ) {
+						// Handle the case where IE and Opera return items
+						// by name instead of ID
+						if ( elem.id !== match[2] ) {
+							return rootjQuery.find( selector );
+						}
+
+						// Otherwise, we inject the element directly into the jQuery object
+						this.length = 1;
+						this[0] = elem;
+					}
+
+					this.context = document;
+					this.selector = selector;
+					return this;
+				}
+
+			// HANDLE: $(expr, $(...))
+			} else if ( !context || context.jquery ) {
+				return ( context || rootjQuery ).find( selector );
+
+			// HANDLE: $(expr, context)
+			// (which is just equivalent to: $(context).find(expr)
+			} else {
+				return this.constructor( context ).find( selector );
+			}
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) ) {
+			return rootjQuery.ready( selector );
+		}
+
+		if ( selector.selector !== undefined ) {
+			this.selector = selector.selector;
+			this.context = selector.context;
+		}
+
+		return jQuery.makeArray( selector, this );
+	},
+
+	// Start with an empty selector
+	selector: "",
+
+	// The current version of jQuery being used
+	jquery: "1.7.2",
+
+	// The default length of a jQuery object is 0
+	length: 0,
+
+	// The number of elements contained in the matched element set
+	size: function() {
+		return this.length;
+	},
+
+	toArray: function() {
+		return slice.call( this, 0 );
+	},
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num == null ?
+
+			// Return a 'clean' array
+			this.toArray() :
+
+			// Return just the object
+			( num < 0 ? this[ this.length + num ] : this[ num ] );
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems, name, selector ) {
+		// Build a new jQuery matched element set
+		var ret = this.constructor();
+
+		if ( jQuery.isArray( elems ) ) {
+			push.apply( ret, elems );
+
+		} else {
+			jQuery.merge( ret, elems );
+		}
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		ret.context = this.context;
+
+		if ( name === "find" ) {
+			ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+		} else if ( name ) {
+			ret.selector = this.selector + "." + name + "(" + selector + ")";
+		}
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	ready: function( fn ) {
+		// Attach the listeners
+		jQuery.bindReady();
+
+		// Add the callback
+		readyList.add( fn );
+
+		return this;
+	},
+
+	eq: function( i ) {
+		i = +i;
+		return i === -1 ?
+			this.slice( i ) :
+			this.slice( i, i + 1 );
+	},
+
+	first: function() {
+		return this.eq( 0 );
+	},
+
+	last: function() {
+		return this.eq( -1 );
+	},
+
+	slice: function() {
+		return this.pushStack( slice.apply( this, arguments ),
+			"slice", slice.call(arguments).join(",") );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function( elem, i ) {
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	end: function() {
+		return this.prevObject || this.constructor(null);
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: push,
+	sort: [].sort,
+	splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+	var options, name, src, copy, copyIsArray, clone,
+		target = arguments[0] || {},
+		i = 1,
+		length = arguments.length,
+		deep = false;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+		target = {};
+	}
+
+	// extend jQuery itself if only one argument is passed
+	if ( length === i ) {
+		target = this;
+		--i;
+	}
+
+	for ( ; i < length; i++ ) {
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null ) {
+			// Extend the base object
+			for ( name in options ) {
+				src = target[ name ];
+				copy = options[ name ];
+
+				// Prevent never-ending loop
+				if ( target === copy ) {
+					continue;
+				}
+
+				// Recurse if we're merging plain objects or arrays
+				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+					if ( copyIsArray ) {
+						copyIsArray = false;
+						clone = src && jQuery.isArray(src) ? src : [];
+
+					} else {
+						clone = src && jQuery.isPlainObject(src) ? src : {};
+					}
+
+					// Never move original objects, clone them
+					target[ name ] = jQuery.extend( deep, clone, copy );
+
+				// Don't bring in undefined values
+				} else if ( copy !== undefined ) {
+					target[ name ] = copy;
+				}
+			}
+		}
+	}
+
+	// Return the modified object
+	return target;
+};
+
+jQuery.extend({
+	noConflict: function( deep ) {
+		if ( window.$ === jQuery ) {
+			window.$ = _$;
+		}
+
+		if ( deep && window.jQuery === jQuery ) {
+			window.jQuery = _jQuery;
+		}
+
+		return jQuery;
+	},
+
+	// Is the DOM ready to be used? Set to true once it occurs.
+	isReady: false,
+
+	// A counter to track how many items to wait for before
+	// the ready event fires. See #6781
+	readyWait: 1,
+
+	// Hold (or release) the ready event
+	holdReady: function( hold ) {
+		if ( hold ) {
+			jQuery.readyWait++;
+		} else {
+			jQuery.ready( true );
+		}
+	},
+
+	// Handle when the DOM is ready
+	ready: function( wait ) {
+		// Either a released hold or an DOMready/load event and not yet ready
+		if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
+			// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+			if ( !document.body ) {
+				return setTimeout( jQuery.ready, 1 );
+			}
+
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+
+			// If a normal DOM Ready event fired, decrement, and wait if need be
+			if ( wait !== true && --jQuery.readyWait > 0 ) {
+				return;
+			}
+
+			// If there are functions bound, to execute
+			readyList.fireWith( document, [ jQuery ] );
+
+			// Trigger any bound ready events
+			if ( jQuery.fn.trigger ) {
+				jQuery( document ).trigger( "ready" ).off( "ready" );
+			}
+		}
+	},
+
+	bindReady: function() {
+		if ( readyList ) {
+			return;
+		}
+
+		readyList = jQuery.Callbacks( "once memory" );
+
+		// Catch cases where $(document).ready() is called after the
+		// browser event has already occurred.
+		if ( document.readyState === "complete" ) {
+			// Handle it asynchronously to allow scripts the opportunity to delay ready
+			return setTimeout( jQuery.ready, 1 );
+		}
+
+		// Mozilla, Opera and webkit nightlies currently support this event
+		if ( document.addEventListener ) {
+			// Use the handy event callback
+			document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+			// A fallback to window.onload, that will always work
+			window.addEventListener( "load", jQuery.ready, false );
+
+		// If IE event model is used
+		} else if ( document.attachEvent ) {
+			// ensure firing before onload,
+			// maybe late but safe also for iframes
+			document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+			// A fallback to window.onload, that will always work
+			window.attachEvent( "onload", jQuery.ready );
+
+			// If IE and not a frame
+			// continually check to see if the document is ready
+			var toplevel = false;
+
+			try {
+				toplevel = window.frameElement == null;
+			} catch(e) {}
+
+			if ( document.documentElement.doScroll && toplevel ) {
+				doScrollCheck();
+			}
+		}
+	},
+
+	// See test/unit/core.js for details concerning isFunction.
+	// Since version 1.3, DOM methods and functions like alert
+	// aren't supported. They return false on IE (#2968).
+	isFunction: function( obj ) {
+		return jQuery.type(obj) === "function";
+	},
+
+	isArray: Array.isArray || function( obj ) {
+		return jQuery.type(obj) === "array";
+	},
+
+	isWindow: function( obj ) {
+		return obj != null && obj == obj.window;
+	},
+
+	isNumeric: function( obj ) {
+		return !isNaN( parseFloat(obj) ) && isFinite( obj );
+	},
+
+	type: function( obj ) {
+		return obj == null ?
+			String( obj ) :
+			class2type[ toString.call(obj) ] || "object";
+	},
+
+	isPlainObject: function( obj ) {
+		// Must be an Object.
+		// Because of IE, we also have to check the presence of the constructor property.
+		// Make sure that DOM nodes and window objects don't pass through, as well
+		if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+			return false;
+		}
+
+		try {
+			// Not own constructor property must be Object
+			if ( obj.constructor &&
+				!hasOwn.call(obj, "constructor") &&
+				!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+				return false;
+			}
+		} catch ( e ) {
+			// IE8,9 Will throw exceptions on certain host objects #9897
+			return false;
+		}
+
+		// Own properties are enumerated firstly, so to speed up,
+		// if last one is own, then all properties are own.
+
+		var key;
+		for ( key in obj ) {}
+
+		return key === undefined || hasOwn.call( obj, key );
+	},
+
+	isEmptyObject: function( obj ) {
+		for ( var name in obj ) {
+			return false;
+		}
+		return true;
+	},
+
+	error: function( msg ) {
+		throw new Error( msg );
+	},
+
+	parseJSON: function( data ) {
+		if ( typeof data !== "string" || !data ) {
+			return null;
+		}
+
+		// Make sure leading/trailing whitespace is removed (IE can't handle it)
+		data = jQuery.trim( data );
+
+		// Attempt to parse using the native JSON parser first
+		if ( window.JSON && window.JSON.parse ) {
+			return window.JSON.parse( data );
+		}
+
+		// Make sure the incoming data is actual JSON
+		// Logic borrowed from http://json.org/json2.js
+		if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+			.replace( rvalidtokens, "]" )
+			.replace( rvalidbraces, "")) ) {
+
+			return ( new Function( "return " + data ) )();
+
+		}
+		jQuery.error( "Invalid JSON: " + data );
+	},
+
+	// Cross-browser xml parsing
+	parseXML: function( data ) {
+		if ( typeof data !== "string" || !data ) {
+			return null;
+		}
+		var xml, tmp;
+		try {
+			if ( window.DOMParser ) { // Standard
+				tmp = new DOMParser();
+				xml = tmp.parseFromString( data , "text/xml" );
+			} else { // IE
+				xml = new ActiveXObject( "Microsoft.XMLDOM" );
+				xml.async = "false";
+				xml.loadXML( data );
+			}
+		} catch( e ) {
+			xml = undefined;
+		}
+		if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+			jQuery.error( "Invalid XML: " + data );
+		}
+		return xml;
+	},
+
+	noop: function() {},
+
+	// Evaluates a script in a global context
+	// Workarounds based on findings by Jim Driscoll
+	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+	globalEval: function( data ) {
+		if ( data && rnotwhite.test( data ) ) {
+			// We use execScript on Internet Explorer
+			// We use an anonymous function so that context is window
+			// rather than jQuery in Firefox
+			( window.execScript || function( data ) {
+				window[ "eval" ].call( window, data );
+			} )( data );
+		}
+	},
+
+	// Convert dashed to camelCase; used by the css and data modules
+	// Microsoft forgot to hump their vendor prefix (#9572)
+	camelCase: function( string ) {
+		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+	},
+
+	// args is for internal usage only
+	each: function( object, callback, args ) {
+		var name, i = 0,
+			length = object.length,
+			isObj = length === undefined || jQuery.isFunction( object );
+
+		if ( args ) {
+			if ( isObj ) {
+				for ( name in object ) {
+					if ( callback.apply( object[ name ], args ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( ; i < length; ) {
+					if ( callback.apply( object[ i++ ], args ) === false ) {
+						break;
+					}
+				}
+			}
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( isObj ) {
+				for ( name in object ) {
+					if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( ; i < length; ) {
+					if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
+						break;
+					}
+				}
+			}
+		}
+
+		return object;
+	},
+
+	// Use native String.trim function wherever possible
+	trim: trim ?
+		function( text ) {
+			return text == null ?
+				"" :
+				trim.call( text );
+		} :
+
+		// Otherwise use our own trimming functionality
+		function( text ) {
+			return text == null ?
+				"" :
+				text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+		},
+
+	// results is for internal usage only
+	makeArray: function( array, results ) {
+		var ret = results || [];
+
+		if ( array != null ) {
+			// The window, strings (and functions) also have 'length'
+			// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+			var type = jQuery.type( array );
+
+			if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+				push.call( ret, array );
+			} else {
+				jQuery.merge( ret, array );
+			}
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, array, i ) {
+		var len;
+
+		if ( array ) {
+			if ( indexOf ) {
+				return indexOf.call( array, elem, i );
+			}
+
+			len = array.length;
+			i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+			for ( ; i < len; i++ ) {
+				// Skip accessing in sparse arrays
+				if ( i in array && array[ i ] === elem ) {
+					return i;
+				}
+			}
+		}
+
+		return -1;
+	},
+
+	merge: function( first, second ) {
+		var i = first.length,
+			j = 0;
+
+		if ( typeof second.length === "number" ) {
+			for ( var l = second.length; j < l; j++ ) {
+				first[ i++ ] = second[ j ];
+			}
+
+		} else {
+			while ( second[j] !== undefined ) {
+				first[ i++ ] = second[ j++ ];
+			}
+		}
+
+		first.length = i;
+
+		return first;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var ret = [], retVal;
+		inv = !!inv;
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( var i = 0, length = elems.length; i < length; i++ ) {
+			retVal = !!callback( elems[ i ], i );
+			if ( inv !== retVal ) {
+				ret.push( elems[ i ] );
+			}
+		}
+
+		return ret;
+	},
+
+	// arg is for internal usage only
+	map: function( elems, callback, arg ) {
+		var value, key, ret = [],
+			i = 0,
+			length = elems.length,
+			// jquery objects are treated as arrays
+			isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+		// Go through the array, translating each of the items to their
+		if ( isArray ) {
+			for ( ; i < length; i++ ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+
+		// Go through every key on the object,
+		} else {
+			for ( key in elems ) {
+				value = callback( elems[ key ], key, arg );
+
+				if ( value != null ) {
+					ret[ ret.length ] = value;
+				}
+			}
+		}
+
+		// Flatten any nested arrays
+		return ret.concat.apply( [], ret );
+	},
+
+	// A global GUID counter for objects
+	guid: 1,
+
+	// Bind a function to a context, optionally partially applying any
+	// arguments.
+	proxy: function( fn, context ) {
+		if ( typeof context === "string" ) {
+			var tmp = fn[ context ];
+			context = fn;
+			fn = tmp;
+		}
+
+		// Quick check to determine if target is callable, in the spec
+		// this throws a TypeError, but we will just return undefined.
+		if ( !jQuery.isFunction( fn ) ) {
+			return undefined;
+		}
+
+		// Simulated bind
+		var args = slice.call( arguments, 2 ),
+			proxy = function() {
+				return fn.apply( context, args.concat( slice.call( arguments ) ) );
+			};
+
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+
+		return proxy;
+	},
+
+	// Mutifunctional method to get and set values to a collection
+	// The value/s can optionally be executed if it's a function
+	access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+		var exec,
+			bulk = key == null,
+			i = 0,
+			length = elems.length;
+
+		// Sets many values
+		if ( key && typeof key === "object" ) {
+			for ( i in key ) {
+				jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+			}
+			chainable = 1;
+
+		// Sets one value
+		} else if ( value !== undefined ) {
+			// Optionally, function values get executed if exec is true
+			exec = pass === undefined && jQuery.isFunction( value );
+
+			if ( bulk ) {
+				// Bulk operations only iterate when executing function values
+				if ( exec ) {
+					exec = fn;
+					fn = function( elem, key, value ) {
+						return exec.call( jQuery( elem ), value );
+					};
+
+				// Otherwise they run against the entire set
+				} else {
+					fn.call( elems, value );
+					fn = null;
+				}
+			}
+
+			if ( fn ) {
+				for (; i < length; i++ ) {
+					fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+				}
+			}
+
+			chainable = 1;
+		}
+
+		return chainable ?
+			elems :
+
+			// Gets
+			bulk ?
+				fn.call( elems ) :
+				length ? fn( elems[0], key ) : emptyGet;
+	},
+
+	now: function() {
+		return ( new Date() ).getTime();
+	},
+
+	// Use of jQuery.browser is frowned upon.
+	// More details: http://docs.jquery.com/Utilities/jQuery.browser
+	uaMatch: function( ua ) {
+		ua = ua.toLowerCase();
+
+		var match = rwebkit.exec( ua ) ||
+			ropera.exec( ua ) ||
+			rmsie.exec( ua ) ||
+			ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+			[];
+
+		return { browser: match[1] || "", version: match[2] || "0" };
+	},
+
+	sub: function() {
+		function jQuerySub( selector, context ) {
+			return new jQuerySub.fn.init( selector, context );
+		}
+		jQuery.extend( true, jQuerySub, this );
+		jQuerySub.superclass = this;
+		jQuerySub.fn = jQuerySub.prototype = this();
+		jQuerySub.fn.constructor = jQuerySub;
+		jQuerySub.sub = this.sub;
+		jQuerySub.fn.init = function init( selector, context ) {
+			if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+				context = jQuerySub( context );
+			}
+
+			return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+		};
+		jQuerySub.fn.init.prototype = jQuerySub.fn;
+		var rootjQuerySub = jQuerySub(document);
+		return jQuerySub;
+	},
+
+	browser: {}
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+	jQuery.browser[ browserMatch.browser ] = true;
+	jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+	jQuery.browser.safari = true;
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+	trimLeft = /^[\s\xA0]+/;
+	trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+	DOMContentLoaded = function() {
+		document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+		jQuery.ready();
+	};
+
+} else if ( document.attachEvent ) {
+	DOMContentLoaded = function() {
+		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+		if ( document.readyState === "complete" ) {
+			document.detachEvent( "onreadystatechange", DOMContentLoaded );
+			jQuery.ready();
+		}
+	};
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+	if ( jQuery.isReady ) {
+		return;
+	}
+
+	try {
+		// If IE is used, use the trick by Diego Perini
+		// http://javascript.nwbox.com/IEContentLoaded/
+		document.documentElement.doScroll("left");
+	} catch(e) {
+		setTimeout( doScrollCheck, 1 );
+		return;
+	}
+
+	// and execute any waiting functions
+	jQuery.ready();
+}
+
+return jQuery;
+
+})();
+
+
+// String to Object flags format cache
+var flagsCache = {};
+
+// Convert String-formatted flags into Object-formatted ones and store in cache
+function createFlags( flags ) {
+	var object = flagsCache[ flags ] = {},
+		i, length;
+	flags = flags.split( /\s+/ );
+	for ( i = 0, length = flags.length; i < length; i++ ) {
+		object[ flags[i] ] = true;
+	}
+	return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ *	flags:	an optional list of space-separated flags that will change how
+ *			the callback list behaves
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible flags:
+ *
+ *	once:			will ensure the callback list can only be fired once (like a Deferred)
+ *
+ *	memory:			will keep track of previous values and will call any callback added
+ *					after the list has been fired right away with the latest "memorized"
+ *					values (like a Deferred)
+ *
+ *	unique:			will ensure a callback can only be added once (no duplicate in the list)
+ *
+ *	stopOnFalse:	interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( flags ) {
+
+	// Convert flags from String-formatted to Object-formatted
+	// (we check in cache first)
+	flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
+
+	var // Actual callback list
+		list = [],
+		// Stack of fire calls for repeatable lists
+		stack = [],
+		// Last fire value (for non-forgettable lists)
+		memory,
+		// Flag to know if list was already fired
+		fired,
+		// Flag to know if list is currently firing
+		firing,
+		// First callback to fire (used internally by add and fireWith)
+		firingStart,
+		// End of the loop when firing
+		firingLength,
+		// Index of currently firing callback (modified by remove if needed)
+		firingIndex,
+		// Add one or several callbacks to the list
+		add = function( args ) {
+			var i,
+				length,
+				elem,
+				type,
+				actual;
+			for ( i = 0, length = args.length; i < length; i++ ) {
+				elem = args[ i ];
+				type = jQuery.type( elem );
+				if ( type === "array" ) {
+					// Inspect recursively
+					add( elem );
+				} else if ( type === "function" ) {
+					// Add if not in unique mode and callback is not in
+					if ( !flags.unique || !self.has( elem ) ) {
+						list.push( elem );
+					}
+				}
+			}
+		},
+		// Fire callbacks
+		fire = function( context, args ) {
+			args = args || [];
+			memory = !flags.memory || [ context, args ];
+			fired = true;
+			firing = true;
+			firingIndex = firingStart || 0;
+			firingStart = 0;
+			firingLength = list.length;
+			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+				if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
+					memory = true; // Mark as halted
+					break;
+				}
+			}
+			firing = false;
+			if ( list ) {
+				if ( !flags.once ) {
+					if ( stack && stack.length ) {
+						memory = stack.shift();
+						self.fireWith( memory[ 0 ], memory[ 1 ] );
+					}
+				} else if ( memory === true ) {
+					self.disable();
+				} else {
+					list = [];
+				}
+			}
+		},
+		// Actual Callbacks object
+		self = {
+			// Add a callback or a collection of callbacks to the list
+			add: function() {
+				if ( list ) {
+					var length = list.length;
+					add( arguments );
+					// Do we need to add the callbacks to the
+					// current firing batch?
+					if ( firing ) {
+						firingLength = list.length;
+					// With memory, if we're not firing then
+					// we should call right away, unless previous
+					// firing was halted (stopOnFalse)
+					} else if ( memory && memory !== true ) {
+						firingStart = length;
+						fire( memory[ 0 ], memory[ 1 ] );
+					}
+				}
+				return this;
+			},
+			// Remove a callback from the list
+			remove: function() {
+				if ( list ) {
+					var args = arguments,
+						argIndex = 0,
+						argLength = args.length;
+					for ( ; argIndex < argLength ; argIndex++ ) {
+						for ( var i = 0; i < list.length; i++ ) {
+							if ( args[ argIndex ] === list[ i ] ) {
+								// Handle firingIndex and firingLength
+								if ( firing ) {
+									if ( i <= firingLength ) {
+										firingLength--;
+										if ( i <= firingIndex ) {
+											firingIndex--;
+										}
+									}
+								}
+								// Remove the element
+								list.splice( i--, 1 );
+								// If we have some unicity property then
+								// we only need to do this once
+								if ( flags.unique ) {
+									break;
+								}
+							}
+						}
+					}
+				}
+				return this;
+			},
+			// Control if a given callback is in the list
+			has: function( fn ) {
+				if ( list ) {
+					var i = 0,
+						length = list.length;
+					for ( ; i < length; i++ ) {
+						if ( fn === list[ i ] ) {
+							return true;
+						}
+					}
+				}
+				return false;
+			},
+			// Remove all callbacks from the list
+			empty: function() {
+				list = [];
+				return this;
+			},
+			// Have the list do nothing anymore
+			disable: function() {
+				list = stack = memory = undefined;
+				return this;
+			},
+			// Is it disabled?
+			disabled: function() {
+				return !list;
+			},
+			// Lock the list in its current state
+			lock: function() {
+				stack = undefined;
+				if ( !memory || memory === true ) {
+					self.disable();
+				}
+				return this;
+			},
+			// Is it locked?
+			locked: function() {
+				return !stack;
+			},
+			// Call all callbacks with the given context and arguments
+			fireWith: function( context, args ) {
+				if ( stack ) {
+					if ( firing ) {
+						if ( !flags.once ) {
+							stack.push( [ context, args ] );
+						}
+					} else if ( !( flags.once && memory ) ) {
+						fire( context, args );
+					}
+				}
+				return this;
+			},
+			// Call all the callbacks with the given arguments
+			fire: function() {
+				self.fireWith( this, arguments );
+				return this;
+			},
+			// To know if the callbacks have already been called at least once
+			fired: function() {
+				return !!fired;
+			}
+		};
+
+	return self;
+};
+
+
+
+
+var // Static reference to slice
+	sliceDeferred = [].slice;
+
+jQuery.extend({
+
+	Deferred: function( func ) {
+		var doneList = jQuery.Callbacks( "once memory" ),
+			failList = jQuery.Callbacks( "once memory" ),
+			progressList = jQuery.Callbacks( "memory" ),
+			state = "pending",
+			lists = {
+				resolve: doneList,
+				reject: failList,
+				notify: progressList
+			},
+			promise = {
+				done: doneList.add,
+				fail: failList.add,
+				progress: progressList.add,
+
+				state: function() {
+					return state;
+				},
+
+				// Deprecated
+				isResolved: doneList.fired,
+				isRejected: failList.fired,
+
+				then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
+					deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
+					return this;
+				},
+				always: function() {
+					deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
+					return this;
+				},
+				pipe: function( fnDone, fnFail, fnProgress ) {
+					return jQuery.Deferred(function( newDefer ) {
+						jQuery.each( {
+							done: [ fnDone, "resolve" ],
+							fail: [ fnFail, "reject" ],
+							progress: [ fnProgress, "notify" ]
+						}, function( handler, data ) {
+							var fn = data[ 0 ],
+								action = data[ 1 ],
+								returned;
+							if ( jQuery.isFunction( fn ) ) {
+								deferred[ handler ](function() {
+									returned = fn.apply( this, arguments );
+									if ( returned && jQuery.isFunction( returned.promise ) ) {
+										returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
+									} else {
+										newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+									}
+								});
+							} else {
+								deferred[ handler ]( newDefer[ action ] );
+							}
+						});
+					}).promise();
+				},
+				// Get a promise for this deferred
+				// If obj is provided, the promise aspect is added to the object
+				promise: function( obj ) {
+					if ( obj == null ) {
+						obj = promise;
+					} else {
+						for ( var key in promise ) {
+							obj[ key ] = promise[ key ];
+						}
+					}
+					return obj;
+				}
+			},
+			deferred = promise.promise({}),
+			key;
+
+		for ( key in lists ) {
+			deferred[ key ] = lists[ key ].fire;
+			deferred[ key + "With" ] = lists[ key ].fireWith;
+		}
+
+		// Handle state
+		deferred.done( function() {
+			state = "resolved";
+		}, failList.disable, progressList.lock ).fail( function() {
+			state = "rejected";
+		}, doneList.disable, progressList.lock );
+
+		// Call given func if any
+		if ( func ) {
+			func.call( deferred, deferred );
+		}
+
+		// All done!
+		return deferred;
+	},
+
+	// Deferred helper
+	when: function( firstParam ) {
+		var args = sliceDeferred.call( arguments, 0 ),
+			i = 0,
+			length = args.length,
+			pValues = new Array( length ),
+			count = length,
+			pCount = length,
+			deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
+				firstParam :
+				jQuery.Deferred(),
+			promise = deferred.promise();
+		function resolveFunc( i ) {
+			return function( value ) {
+				args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+				if ( !( --count ) ) {
+					deferred.resolveWith( deferred, args );
+				}
+			};
+		}
+		function progressFunc( i ) {
+			return function( value ) {
+				pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
+				deferred.notifyWith( promise, pValues );
+			};
+		}
+		if ( length > 1 ) {
+			for ( ; i < length; i++ ) {
+				if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
+					args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
+				} else {
+					--count;
+				}
+			}
+			if ( !count ) {
+				deferred.resolveWith( deferred, args );
+			}
+		} else if ( deferred !== firstParam ) {
+			deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
+		}
+		return promise;
+	}
+});
+
+
+
+
+jQuery.support = (function() {
+
+	var support,
+		all,
+		a,
+		select,
+		opt,
+		input,
+		fragment,
+		tds,
+		events,
+		eventName,
+		i,
+		isSupported,
+		div = document.createElement( "div" ),
+		documentElement = document.documentElement;
+
+	// Preliminary tests
+	div.setAttribute("className", "t");
+	div.innerHTML = "   <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+	all = div.getElementsByTagName( "*" );
+	a = div.getElementsByTagName( "a" )[ 0 ];
+
+	// Can't get basic test support
+	if ( !all || !all.length || !a ) {
+		return {};
+	}
+
+	// First batch of supports tests
+	select = document.createElement( "select" );
+	opt = select.appendChild( document.createElement("option") );
+	input = div.getElementsByTagName( "input" )[ 0 ];
+
+	support = {
+		// IE strips leading whitespace when .innerHTML is used
+		leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+		// Make sure that tbody elements aren't automatically inserted
+		// IE will insert them into empty tables
+		tbody: !div.getElementsByTagName("tbody").length,
+
+		// Make sure that link elements get serialized correctly by innerHTML
+		// This requires a wrapper element in IE
+		htmlSerialize: !!div.getElementsByTagName("link").length,
+
+		// Get the style information from getAttribute
+		// (IE uses .cssText instead)
+		style: /top/.test( a.getAttribute("style") ),
+
+		// Make sure that URLs aren't manipulated
+		// (IE normalizes it by default)
+		hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+		// Make sure that element opacity exists
+		// (IE uses filter instead)
+		// Use a regex to work around a WebKit issue. See #5145
+		opacity: /^0.55/.test( a.style.opacity ),
+
+		// Verify style float existence
+		// (IE uses styleFloat instead of cssFloat)
+		cssFloat: !!a.style.cssFloat,
+
+		// Make sure that if no value is specified for a checkbox
+		// that it defaults to "on".
+		// (WebKit defaults to "" instead)
+		checkOn: ( input.value === "on" ),
+
+		// Make sure that a selected-by-default option has a working selected property.
+		// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+		optSelected: opt.selected,
+
+		// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+		getSetAttribute: div.className !== "t",
+
+		// Tests for enctype support on a form(#6743)
+		enctype: !!document.createElement("form").enctype,
+
+		// Makes sure cloning an html5 element does not cause problems
+		// Where outerHTML is undefined, this still works
+		html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+		// Will be defined later
+		submitBubbles: true,
+		changeBubbles: true,
+		focusinBubbles: false,
+		deleteExpando: true,
+		noCloneEvent: true,
+		inlineBlockNeedsLayout: false,
+		shrinkWrapBlocks: false,
+		reliableMarginRight: true,
+		pixelMargin: true
+	};
+
+	// jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead
+	jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat");
+
+	// Make sure checked status is properly cloned
+	input.checked = true;
+	support.noCloneChecked = input.cloneNode( true ).checked;
+
+	// Make sure that the options inside disabled selects aren't marked as disabled
+	// (WebKit marks them as disabled)
+	select.disabled = true;
+	support.optDisabled = !opt.disabled;
+
+	// Test to see if it's possible to delete an expando from an element
+	// Fails in Internet Explorer
+	try {
+		delete div.test;
+	} catch( e ) {
+		support.deleteExpando = false;
+	}
+
+	if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+		div.attachEvent( "onclick", function() {
+			// Cloning a node shouldn't copy over any
+			// bound event handlers (IE does this)
+			support.noCloneEvent = false;
+		});
+		div.cloneNode( true ).fireEvent( "onclick" );
+	}
+
+	// Check if a radio maintains its value
+	// after being appended to the DOM
+	input = document.createElement("input");
+	input.value = "t";
+	input.setAttribute("type", "radio");
+	support.radioValue = input.value === "t";
+
+	input.setAttribute("checked", "checked");
+
+	// #11217 - WebKit loses check when the name is after the checked attribute
+	input.setAttribute( "name", "t" );
+
+	div.appendChild( input );
+	fragment = document.createDocumentFragment();
+	fragment.appendChild( div.lastChild );
+
+	// WebKit doesn't clone checked state correctly in fragments
+	support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+	// Check if a disconnected checkbox will retain its checked
+	// value of true after appended to the DOM (IE6/7)
+	support.appendChecked = input.checked;
+
+	fragment.removeChild( input );
+	fragment.appendChild( div );
+
+	// Technique from Juriy Zaytsev
+	// http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+	// We only care about the case where non-standard event systems
+	// are used, namely in IE. Short-circuiting here helps us to
+	// avoid an eval call (in setAttribute) which can cause CSP
+	// to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+	if ( div.attachEvent ) {
+		for ( i in {
+			submit: 1,
+			change: 1,
+			focusin: 1
+		}) {
+			eventName = "on" + i;
+			isSupported = ( eventName in div );
+			if ( !isSupported ) {
+				div.setAttribute( eventName, "return;" );
+				isSupported = ( typeof div[ eventName ] === "function" );
+			}
+			support[ i + "Bubbles" ] = isSupported;
+		}
+	}
+
+	fragment.removeChild( div );
+
+	// Null elements to avoid leaks in IE
+	fragment = select = opt = div = input = null;
+
+	// Run tests that need a body at doc ready
+	jQuery(function() {
+		var container, outer, inner, table, td, offsetSupport,
+			marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight,
+			paddingMarginBorderVisibility, paddingMarginBorder,
+			body = document.getElementsByTagName("body")[0];
+
+		if ( !body ) {
+			// Return for frameset docs that don't have a body
+			return;
+		}
+
+		conMarginTop = 1;
+		paddingMarginBorder = "padding:0;margin:0;border:";
+		positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;";
+		paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;";
+		style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;";
+		html = "<div " + style + "display:block;'><div style='" + paddingMarginBorder + "0;display:block;overflow:hidden;'></div></div>" +
+			"<table " + style + "' cellpadding='0' cellspacing='0'>" +
+			"<tr><td></td></tr></table>";
+
+		container = document.createElement("div");
+		container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
+		body.insertBefore( container, body.firstChild );
+
+		// Construct the test element
+		div = document.createElement("div");
+		container.appendChild( div );
+
+		// Check if table cells still have offsetWidth/Height when they are set
+		// to display:none and there are still other visible table cells in a
+		// table row; if so, offsetWidth/Height are not reliable for use when
+		// determining if an element has been hidden directly using
+		// display:none (it is still safe to use offsets if a parent element is
+		// hidden; don safety goggles and see bug #4512 for more information).
+		// (only IE 8 fails this test)
+		div.innerHTML = "<table><tr><td style='" + paddingMarginBorder + "0;display:none'></td><td>t</td></tr></table>";
+		tds = div.getElementsByTagName( "td" );
+		isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+		tds[ 0 ].style.display = "";
+		tds[ 1 ].style.display = "none";
+
+		// Check if empty table cells still have offsetWidth/Height
+		// (IE <= 8 fail this test)
+		support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+		// Check if div with explicit width and no margin-right incorrectly
+		// gets computed margin-right based on width of container. For more
+		// info see bug #3333
+		// Fails in WebKit before Feb 2011 nightlies
+		// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+		if ( window.getComputedStyle ) {
+			div.innerHTML = "";
+			marginDiv = document.createElement( "div" );
+			marginDiv.style.width = "0";
+			marginDiv.style.marginRight = "0";
+			div.style.width = "2px";
+			div.appendChild( marginDiv );
+			support.reliableMarginRight =
+				( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
+		}
+
+		if ( typeof div.style.zoom !== "undefined" ) {
+			// Check if natively block-level elements act like inline-block
+			// elements when setting their display to 'inline' and giving
+			// them layout
+			// (IE < 8 does this)
+			div.innerHTML = "";
+			div.style.width = div.style.padding = "1px";
+			div.style.border = 0;
+			div.style.overflow = "hidden";
+			div.style.display = "inline";
+			div.style.zoom = 1;
+			support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+			// Check if elements with layout shrink-wrap their children
+			// (IE 6 does this)
+			div.style.display = "block";
+			div.style.overflow = "visible";
+			div.innerHTML = "<div style='width:5px;'></div>";
+			support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+		}
+
+		div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility;
+		div.innerHTML = html;
+
+		outer = div.firstChild;
+		inner = outer.firstChild;
+		td = outer.nextSibling.firstChild.firstChild;
+
+		offsetSupport = {
+			doesNotAddBorder: ( inner.offsetTop !== 5 ),
+			doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
+		};
+
+		inner.style.position = "fixed";
+		inner.style.top = "20px";
+
+		// safari subtracts parent border width here which is 5px
+		offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
+		inner.style.position = inner.style.top = "";
+
+		outer.style.overflow = "hidden";
+		outer.style.position = "relative";
+
+		offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
+		offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
+
+		if ( window.getComputedStyle ) {
+			div.style.marginTop = "1%";
+			support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%";
+		}
+
+		if ( typeof container.style.zoom !== "undefined" ) {
+			container.style.zoom = 1;
+		}
+
+		body.removeChild( container );
+		marginDiv = div = container = null;
+
+		jQuery.extend( support, offsetSupport );
+	});
+
+	return support;
+})();
+
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/,
+	rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+	cache: {},
+
+	// Please use with caution
+	uuid: 0,
+
+	// Unique for each copy of jQuery on the page
+	// Non-digits removed to match rinlinejQuery
+	expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+	// The following elements throw uncatchable exceptions if you
+	// attempt to add expando properties to them.
+	noData: {
+		"embed": true,
+		// Ban all objects except for Flash (which handle expandos)
+		"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+		"applet": true
+	},
+
+	hasData: function( elem ) {
+		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+		return !!elem && !isEmptyDataObject( elem );
+	},
+
+	data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var privateCache, thisCache, ret,
+			internalKey = jQuery.expando,
+			getByName = typeof name === "string",
+
+			// We have to handle DOM nodes and JS objects differently because IE6-7
+			// can't GC object references properly across the DOM-JS boundary
+			isNode = elem.nodeType,
+
+			// Only DOM nodes need the global jQuery cache; JS object data is
+			// attached directly to the object so GC can occur automatically
+			cache = isNode ? jQuery.cache : elem,
+
+			// Only defining an ID for JS objects if its cache already exists allows
+			// the code to shortcut on the same path as a DOM node with no cache
+			id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
+			isEvents = name === "events";
+
+		// Avoid doing any more work than we need to when trying to get data on an
+		// object that has no data at all
+		if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
+			return;
+		}
+
+		if ( !id ) {
+			// Only DOM nodes need a new unique ID for each element since their data
+			// ends up in the global cache
+			if ( isNode ) {
+				elem[ internalKey ] = id = ++jQuery.uuid;
+			} else {
+				id = internalKey;
+			}
+		}
+
+		if ( !cache[ id ] ) {
+			cache[ id ] = {};
+
+			// Avoids exposing jQuery metadata on plain JS objects when the object
+			// is serialized using JSON.stringify
+			if ( !isNode ) {
+				cache[ id ].toJSON = jQuery.noop;
+			}
+		}
+
+		// An object can be passed to jQuery.data instead of a key/value pair; this gets
+		// shallow copied over onto the existing cache
+		if ( typeof name === "object" || typeof name === "function" ) {
+			if ( pvt ) {
+				cache[ id ] = jQuery.extend( cache[ id ], name );
+			} else {
+				cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+			}
+		}
+
+		privateCache = thisCache = cache[ id ];
+
+		// jQuery data() is stored in a separate object inside the object's internal data
+		// cache in order to avoid key collisions between internal data and user-defined
+		// data.
+		if ( !pvt ) {
+			if ( !thisCache.data ) {
+				thisCache.data = {};
+			}
+
+			thisCache = thisCache.data;
+		}
+
+		if ( data !== undefined ) {
+			thisCache[ jQuery.camelCase( name ) ] = data;
+		}
+
+		// Users should not attempt to inspect the internal events object using jQuery.data,
+		// it is undocumented and subject to change. But does anyone listen? No.
+		if ( isEvents && !thisCache[ name ] ) {
+			return privateCache.events;
+		}
+
+		// Check for both converted-to-camel and non-converted data property names
+		// If a data property was specified
+		if ( getByName ) {
+
+			// First Try to find as-is property data
+			ret = thisCache[ name ];
+
+			// Test for null|undefined property data
+			if ( ret == null ) {
+
+				// Try to find the camelCased property
+				ret = thisCache[ jQuery.camelCase( name ) ];
+			}
+		} else {
+			ret = thisCache;
+		}
+
+		return ret;
+	},
+
+	removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var thisCache, i, l,
+
+			// Reference to internal data cache key
+			internalKey = jQuery.expando,
+
+			isNode = elem.nodeType,
+
+			// See jQuery.data for more information
+			cache = isNode ? jQuery.cache : elem,
+
+			// See jQuery.data for more information
+			id = isNode ? elem[ internalKey ] : internalKey;
+
+		// If there is already no cache entry for this object, there is no
+		// purpose in continuing
+		if ( !cache[ id ] ) {
+			return;
+		}
+
+		if ( name ) {
+
+			thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+			if ( thisCache ) {
+
+				// Support array or space separated string names for data keys
+				if ( !jQuery.isArray( name ) ) {
+
+					// try the string as a key before any manipulation
+					if ( name in thisCache ) {
+						name = [ name ];
+					} else {
+
+						// split the camel cased version by spaces unless a key with the spaces exists
+						name = jQuery.camelCase( name );
+						if ( name in thisCache ) {
+							name = [ name ];
+						} else {
+							name = name.split( " " );
+						}
+					}
+				}
+
+				for ( i = 0, l = name.length; i < l; i++ ) {
+					delete thisCache[ name[i] ];
+				}
+
+				// If there is no data left in the cache, we want to continue
+				// and let the cache object itself get destroyed
+				if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+					return;
+				}
+			}
+		}
+
+		// See jQuery.data for more information
+		if ( !pvt ) {
+			delete cache[ id ].data;
+
+			// Don't destroy the parent cache unless the internal data object
+			// had been the only thing left in it
+			if ( !isEmptyDataObject(cache[ id ]) ) {
+				return;
+			}
+		}
+
+		// Browsers that fail expando deletion also refuse to delete expandos on
+		// the window, but it will allow it on all other JS objects; other browsers
+		// don't care
+		// Ensure that `cache` is not a window object #10080
+		if ( jQuery.support.deleteExpando || !cache.setInterval ) {
+			delete cache[ id ];
+		} else {
+			cache[ id ] = null;
+		}
+
+		// We destroyed the cache and need to eliminate the expando on the node to avoid
+		// false lookups in the cache for entries that no longer exist
+		if ( isNode ) {
+			// IE does not allow us to delete expando properties from nodes,
+			// nor does it have a removeAttribute function on Document nodes;
+			// we must handle all of these cases
+			if ( jQuery.support.deleteExpando ) {
+				delete elem[ internalKey ];
+			} else if ( elem.removeAttribute ) {
+				elem.removeAttribute( internalKey );
+			} else {
+				elem[ internalKey ] = null;
+			}
+		}
+	},
+
+	// For internal use only.
+	_data: function( elem, name, data ) {
+		return jQuery.data( elem, name, data, true );
+	},
+
+	// A method for determining if a DOM node can handle the data expando
+	acceptData: function( elem ) {
+		if ( elem.nodeName ) {
+			var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+			if ( match ) {
+				return !(match === true || elem.getAttribute("classid") !== match);
+			}
+		}
+
+		return true;
+	}
+});
+
+jQuery.fn.extend({
+	data: function( key, value ) {
+		var parts, part, attr, name, l,
+			elem = this[0],
+			i = 0,
+			data = null;
+
+		// Gets all values
+		if ( key === undefined ) {
+			if ( this.length ) {
+				data = jQuery.data( elem );
+
+				if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+					attr = elem.attributes;
+					for ( l = attr.length; i < l; i++ ) {
+						name = attr[i].name;
+
+						if ( name.indexOf( "data-" ) === 0 ) {
+							name = jQuery.camelCase( name.substring(5) );
+
+							dataAttr( elem, name, data[ name ] );
+						}
+					}
+					jQuery._data( elem, "parsedAttrs", true );
+				}
+			}
+
+			return data;
+		}
+
+		// Sets multiple values
+		if ( typeof key === "object" ) {
+			return this.each(function() {
+				jQuery.data( this, key );
+			});
+		}
+
+		parts = key.split( ".", 2 );
+		parts[1] = parts[1] ? "." + parts[1] : "";
+		part = parts[1] + "!";
+
+		return jQuery.access( this, function( value ) {
+
+			if ( value === undefined ) {
+				data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+				// Try to fetch any internally stored data first
+				if ( data === undefined && elem ) {
+					data = jQuery.data( elem, key );
+					data = dataAttr( elem, key, data );
+				}
+
+				return data === undefined && parts[1] ?
+					this.data( parts[0] ) :
+					data;
+			}
+
+			parts[1] = value;
+			this.each(function() {
+				var self = jQuery( this );
+
+				self.triggerHandler( "setData" + part, parts );
+				jQuery.data( this, key, value );
+				self.triggerHandler( "changeData" + part, parts );
+			});
+		}, null, value, arguments.length > 1, null, false );
+	},
+
+	removeData: function( key ) {
+		return this.each(function() {
+			jQuery.removeData( this, key );
+		});
+	}
+});
+
+function dataAttr( elem, key, data ) {
+	// If nothing was found internally, try to fetch any
+	// data from the HTML5 data-* attribute
+	if ( data === undefined && elem.nodeType === 1 ) {
+
+		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+		data = elem.getAttribute( name );
+
+		if ( typeof data === "string" ) {
+			try {
+				data = data === "true" ? true :
+				data === "false" ? false :
+				data === "null" ? null :
+				jQuery.isNumeric( data ) ? +data :
+					rbrace.test( data ) ? jQuery.parseJSON( data ) :
+					data;
+			} catch( e ) {}
+
+			// Make sure we set the data so it isn't changed later
+			jQuery.data( elem, key, data );
+
+		} else {
+			data = undefined;
+		}
+	}
+
+	return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+	for ( var name in obj ) {
+
+		// if the public data object is empty, the private is still empty
+		if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+			continue;
+		}
+		if ( name !== "toJSON" ) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+
+
+
+function handleQueueMarkDefer( elem, type, src ) {
+	var deferDataKey = type + "defer",
+		queueDataKey = type + "queue",
+		markDataKey = type + "mark",
+		defer = jQuery._data( elem, deferDataKey );
+	if ( defer &&
+		( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
+		( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
+		// Give room for hard-coded callbacks to fire first
+		// and eventually mark/queue something else on the element
+		setTimeout( function() {
+			if ( !jQuery._data( elem, queueDataKey ) &&
+				!jQuery._data( elem, markDataKey ) ) {
+				jQuery.removeData( elem, deferDataKey, true );
+				defer.fire();
+			}
+		}, 0 );
+	}
+}
+
+jQuery.extend({
+
+	_mark: function( elem, type ) {
+		if ( elem ) {
+			type = ( type || "fx" ) + "mark";
+			jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
+		}
+	},
+
+	_unmark: function( force, elem, type ) {
+		if ( force !== true ) {
+			type = elem;
+			elem = force;
+			force = false;
+		}
+		if ( elem ) {
+			type = type || "fx";
+			var key = type + "mark",
+				count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
+			if ( count ) {
+				jQuery._data( elem, key, count );
+			} else {
+				jQuery.removeData( elem, key, true );
+				handleQueueMarkDefer( elem, type, "mark" );
+			}
+		}
+	},
+
+	queue: function( elem, type, data ) {
+		var q;
+		if ( elem ) {
+			type = ( type || "fx" ) + "queue";
+			q = jQuery._data( elem, type );
+
+			// Speed up dequeue by getting out quickly if this is just a lookup
+			if ( data ) {
+				if ( !q || jQuery.isArray(data) ) {
+					q = jQuery._data( elem, type, jQuery.makeArray(data) );
+				} else {
+					q.push( data );
+				}
+			}
+			return q || [];
+		}
+	},
+
+	dequeue: function( elem, type ) {
+		type = type || "fx";
+
+		var queue = jQuery.queue( elem, type ),
+			fn = queue.shift(),
+			hooks = {};
+
+		// If the fx queue is dequeued, always remove the progress sentinel
+		if ( fn === "inprogress" ) {
+			fn = queue.shift();
+		}
+
+		if ( fn ) {
+			// Add a progress sentinel to prevent the fx queue from being
+			// automatically dequeued
+			if ( type === "fx" ) {
+				queue.unshift( "inprogress" );
+			}
+
+			jQuery._data( elem, type + ".run", hooks );
+			fn.call( elem, function() {
+				jQuery.dequeue( elem, type );
+			}, hooks );
+		}
+
+		if ( !queue.length ) {
+			jQuery.removeData( elem, type + "queue " + type + ".run", true );
+			handleQueueMarkDefer( elem, type, "queue" );
+		}
+	}
+});
+
+jQuery.fn.extend({
+	queue: function( type, data ) {
+		var setter = 2;
+
+		if ( typeof type !== "string" ) {
+			data = type;
+			type = "fx";
+			setter--;
+		}
+
+		if ( arguments.length < setter ) {
+			return jQuery.queue( this[0], type );
+		}
+
+		return data === undefined ?
+			this :
+			this.each(function() {
+				var queue = jQuery.queue( this, type, data );
+
+				if ( type === "fx" && queue[0] !== "inprogress" ) {
+					jQuery.dequeue( this, type );
+				}
+			});
+	},
+	dequeue: function( type ) {
+		return this.each(function() {
+			jQuery.dequeue( this, type );
+		});
+	},
+	// Based off of the plugin by Clint Helfers, with permission.
+	// http://blindsignals.com/index.php/2009/07/jquery-delay/
+	delay: function( time, type ) {
+		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+		type = type || "fx";
+
+		return this.queue( type, function( next, hooks ) {
+			var timeout = setTimeout( next, time );
+			hooks.stop = function() {
+				clearTimeout( timeout );
+			};
+		});
+	},
+	clearQueue: function( type ) {
+		return this.queue( type || "fx", [] );
+	},
+	// Get a promise resolved when queues of a certain type
+	// are emptied (fx is the type by default)
+	promise: function( type, object ) {
+		if ( typeof type !== "string" ) {
+			object = type;
+			type = undefined;
+		}
+		type = type || "fx";
+		var defer = jQuery.Deferred(),
+			elements = this,
+			i = elements.length,
+			count = 1,
+			deferDataKey = type + "defer",
+			queueDataKey = type + "queue",
+			markDataKey = type + "mark",
+			tmp;
+		function resolve() {
+			if ( !( --count ) ) {
+				defer.resolveWith( elements, [ elements ] );
+			}
+		}
+		while( i-- ) {
+			if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
+					( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
+						jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
+					jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
+				count++;
+				tmp.add( resolve );
+			}
+		}
+		resolve();
+		return defer.promise( object );
+	}
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+	rspace = /\s+/,
+	rreturn = /\r/g,
+	rtype = /^(?:button|input)$/i,
+	rfocusable = /^(?:button|input|object|select|textarea)$/i,
+	rclickable = /^a(?:rea)?$/i,
+	rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+	getSetAttribute = jQuery.support.getSetAttribute,
+	nodeHook, boolHook, fixSpecified;
+
+jQuery.fn.extend({
+	attr: function( name, value ) {
+		return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+	},
+
+	removeAttr: function( name ) {
+		return this.each(function() {
+			jQuery.removeAttr( this, name );
+		});
+	},
+
+	prop: function( name, value ) {
+		return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+	},
+
+	removeProp: function( name ) {
+		name = jQuery.propFix[ name ] || name;
+		return this.each(function() {
+			// try/catch handles cases where IE balks (such as removing a property on window)
+			try {
+				this[ name ] = undefined;
+				delete this[ name ];
+			} catch( e ) {}
+		});
+	},
+
+	addClass: function( value ) {
+		var classNames, i, l, elem,
+			setClass, c, cl;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).addClass( value.call(this, j, this.className) );
+			});
+		}
+
+		if ( value && typeof value === "string" ) {
+			classNames = value.split( rspace );
+
+			for ( i = 0, l = this.length; i < l; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.nodeType === 1 ) {
+					if ( !elem.className && classNames.length === 1 ) {
+						elem.className = value;
+
+					} else {
+						setClass = " " + elem.className + " ";
+
+						for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+							if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
+								setClass += classNames[ c ] + " ";
+							}
+						}
+						elem.className = jQuery.trim( setClass );
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		var classNames, i, l, elem, className, c, cl;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).removeClass( value.call(this, j, this.className) );
+			});
+		}
+
+		if ( (value && typeof value === "string") || value === undefined ) {
+			classNames = ( value || "" ).split( rspace );
+
+			for ( i = 0, l = this.length; i < l; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.nodeType === 1 && elem.className ) {
+					if ( value ) {
+						className = (" " + elem.className + " ").replace( rclass, " " );
+						for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+							className = className.replace(" " + classNames[ c ] + " ", " ");
+						}
+						elem.className = jQuery.trim( className );
+
+					} else {
+						elem.className = "";
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	toggleClass: function( value, stateVal ) {
+		var type = typeof value,
+			isBool = typeof stateVal === "boolean";
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+			});
+		}
+
+		return this.each(function() {
+			if ( type === "string" ) {
+				// toggle individual class names
+				var className,
+					i = 0,
+					self = jQuery( this ),
+					state = stateVal,
+					classNames = value.split( rspace );
+
+				while ( (className = classNames[ i++ ]) ) {
+					// check each className given, space seperated list
+					state = isBool ? state : !self.hasClass( className );
+					self[ state ? "addClass" : "removeClass" ]( className );
+				}
+
+			} else if ( type === "undefined" || type === "boolean" ) {
+				if ( this.className ) {
+					// store className if set
+					jQuery._data( this, "__className__", this.className );
+				}
+
+				// toggle whole className
+				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+			}
+		});
+	},
+
+	hasClass: function( selector ) {
+		var className = " " + selector + " ",
+			i = 0,
+			l = this.length;
+		for ( ; i < l; i++ ) {
+			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+				return true;
+			}
+		}
+
+		return false;
+	},
+
+	val: function( value ) {
+		var hooks, ret, isFunction,
+			elem = this[0];
+
+		if ( !arguments.length ) {
+			if ( elem ) {
+				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+					return ret;
+				}
+
+				ret = elem.value;
+
+				return typeof ret === "string" ?
+					// handle most common string cases
+					ret.replace(rreturn, "") :
+					// handle cases where value is null/undef or number
+					ret == null ? "" : ret;
+			}
+
+			return;
+		}
+
+		isFunction = jQuery.isFunction( value );
+
+		return this.each(function( i ) {
+			var self = jQuery(this), val;
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( isFunction ) {
+				val = value.call( this, i, self.val() );
+			} else {
+				val = value;
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+			} else if ( typeof val === "number" ) {
+				val += "";
+			} else if ( jQuery.isArray( val ) ) {
+				val = jQuery.map(val, function ( value ) {
+					return value == null ? "" : value + "";
+				});
+			}
+
+			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+			// If set returns undefined, fall back to normal setting
+			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+				this.value = val;
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	valHooks: {
+		option: {
+			get: function( elem ) {
+				// attributes.value is undefined in Blackberry 4.7 but
+				// uses .value. See #6932
+				var val = elem.attributes.value;
+				return !val || val.specified ? elem.value : elem.text;
+			}
+		},
+		select: {
+			get: function( elem ) {
+				var value, i, max, option,
+					index = elem.selectedIndex,
+					values = [],
+					options = elem.options,
+					one = elem.type === "select-one";
+
+				// Nothing was selected
+				if ( index < 0 ) {
+					return null;
+				}
+
+				// Loop through all the selected options
+				i = one ? index : 0;
+				max = one ? index + 1 : options.length;
+				for ( ; i < max; i++ ) {
+					option = options[ i ];
+
+					// Don't return options that are disabled or in a disabled optgroup
+					if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+							(!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+						// Get the specific value for the option
+						value = jQuery( option ).val();
+
+						// We don't need an array for one selects
+						if ( one ) {
+							return value;
+						}
+
+						// Multi-Selects return an array
+						values.push( value );
+					}
+				}
+
+				// Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+				if ( one && !values.length && options.length ) {
+					return jQuery( options[ index ] ).val();
+				}
+
+				return values;
+			},
+
+			set: function( elem, value ) {
+				var values = jQuery.makeArray( value );
+
+				jQuery(elem).find("option").each(function() {
+					this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+				});
+
+				if ( !values.length ) {
+					elem.selectedIndex = -1;
+				}
+				return values;
+			}
+		}
+	},
+
+	attrFn: {
+		val: true,
+		css: true,
+		html: true,
+		text: true,
+		data: true,
+		width: true,
+		height: true,
+		offset: true
+	},
+
+	attr: function( elem, name, value, pass ) {
+		var ret, hooks, notxml,
+			nType = elem.nodeType;
+
+		// don't get/set attributes on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		if ( pass && name in jQuery.attrFn ) {
+			return jQuery( elem )[ name ]( value );
+		}
+
+		// Fallback to prop when attributes are not supported
+		if ( typeof elem.getAttribute === "undefined" ) {
+			return jQuery.prop( elem, name, value );
+		}
+
+		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		// All attributes are lowercase
+		// Grab necessary hook if one is defined
+		if ( notxml ) {
+			name = name.toLowerCase();
+			hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+		}
+
+		if ( value !== undefined ) {
+
+			if ( value === null ) {
+				jQuery.removeAttr( elem, name );
+				return;
+
+			} else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				elem.setAttribute( name, "" + value );
+				return value;
+			}
+
+		} else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+			return ret;
+
+		} else {
+
+			ret = elem.getAttribute( name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return ret === null ?
+				undefined :
+				ret;
+		}
+	},
+
+	removeAttr: function( elem, value ) {
+		var propName, attrNames, name, l, isBool,
+			i = 0;
+
+		if ( value && elem.nodeType === 1 ) {
+			attrNames = value.toLowerCase().split( rspace );
+			l = attrNames.length;
+
+			for ( ; i < l; i++ ) {
+				name = attrNames[ i ];
+
+				if ( name ) {
+					propName = jQuery.propFix[ name ] || name;
+					isBool = rboolean.test( name );
+
+					// See #9699 for explanation of this approach (setting first, then removal)
+					// Do not do this for boolean attributes (see #10870)
+					if ( !isBool ) {
+						jQuery.attr( elem, name, "" );
+					}
+					elem.removeAttribute( getSetAttribute ? name : propName );
+
+					// Set corresponding property to false for boolean attributes
+					if ( isBool && propName in elem ) {
+						elem[ propName ] = false;
+					}
+				}
+			}
+		}
+	},
+
+	attrHooks: {
+		type: {
+			set: function( elem, value ) {
+				// We can't allow the type property to be changed (since it causes problems in IE)
+				if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+					jQuery.error( "type property can't be changed" );
+				} else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+					// Setting the type on a radio button after the value resets the value in IE6-9
+					// Reset value to it's default in case type is set after value
+					// This is for element creation
+					var val = elem.value;
+					elem.setAttribute( "type", value );
+					if ( val ) {
+						elem.value = val;
+					}
+					return value;
+				}
+			}
+		},
+		// Use the value property for back compat
+		// Use the nodeHook for button elements in IE6/7 (#1954)
+		value: {
+			get: function( elem, name ) {
+				if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+					return nodeHook.get( elem, name );
+				}
+				return name in elem ?
+					elem.value :
+					null;
+			},
+			set: function( elem, value, name ) {
+				if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+					return nodeHook.set( elem, value, name );
+				}
+				// Does not return so that setAttribute is also used
+				elem.value = value;
+			}
+		}
+	},
+
+	propFix: {
+		tabindex: "tabIndex",
+		readonly: "readOnly",
+		"for": "htmlFor",
+		"class": "className",
+		maxlength: "maxLength",
+		cellspacing: "cellSpacing",
+		cellpadding: "cellPadding",
+		rowspan: "rowSpan",
+		colspan: "colSpan",
+		usemap: "useMap",
+		frameborder: "frameBorder",
+		contenteditable: "contentEditable"
+	},
+
+	prop: function( elem, name, value ) {
+		var ret, hooks, notxml,
+			nType = elem.nodeType;
+
+		// don't get/set properties on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		if ( notxml ) {
+			// Fix name and attach hooks
+			name = jQuery.propFix[ name ] || name;
+			hooks = jQuery.propHooks[ name ];
+		}
+
+		if ( value !== undefined ) {
+			if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				return ( elem[ name ] = value );
+			}
+
+		} else {
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+				return ret;
+
+			} else {
+				return elem[ name ];
+			}
+		}
+	},
+
+	propHooks: {
+		tabIndex: {
+			get: function( elem ) {
+				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+				var attributeNode = elem.getAttributeNode("tabindex");
+
+				return attributeNode && attributeNode.specified ?
+					parseInt( attributeNode.value, 10 ) :
+					rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+						0 :
+						undefined;
+			}
+		}
+	}
+});
+
+// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
+jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
+
+// Hook for boolean attributes
+boolHook = {
+	get: function( elem, name ) {
+		// Align boolean attributes with corresponding properties
+		// Fall back to attribute presence where some booleans are not supported
+		var attrNode,
+			property = jQuery.prop( elem, name );
+		return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+			name.toLowerCase() :
+			undefined;
+	},
+	set: function( elem, value, name ) {
+		var propName;
+		if ( value === false ) {
+			// Remove boolean attributes when set to false
+			jQuery.removeAttr( elem, name );
+		} else {
+			// value is true since we know at this point it's type boolean and not false
+			// Set boolean attributes to the same name and set the DOM property
+			propName = jQuery.propFix[ name ] || name;
+			if ( propName in elem ) {
+				// Only set the IDL specifically if it already exists on the element
+				elem[ propName ] = true;
+			}
+
+			elem.setAttribute( name, name.toLowerCase() );
+		}
+		return name;
+	}
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+	fixSpecified = {
+		name: true,
+		id: true,
+		coords: true
+	};
+
+	// Use this for any attribute in IE6/7
+	// This fixes almost every IE6/7 issue
+	nodeHook = jQuery.valHooks.button = {
+		get: function( elem, name ) {
+			var ret;
+			ret = elem.getAttributeNode( name );
+			return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
+				ret.nodeValue :
+				undefined;
+		},
+		set: function( elem, value, name ) {
+			// Set the existing or create a new attribute node
+			var ret = elem.getAttributeNode( name );
+			if ( !ret ) {
+				ret = document.createAttribute( name );
+				elem.setAttributeNode( ret );
+			}
+			return ( ret.nodeValue = value + "" );
+		}
+	};
+
+	// Apply the nodeHook to tabindex
+	jQuery.attrHooks.tabindex.set = nodeHook.set;
+
+	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
+	// This is for removals
+	jQuery.each([ "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+			set: function( elem, value ) {
+				if ( value === "" ) {
+					elem.setAttribute( name, "auto" );
+					return value;
+				}
+			}
+		});
+	});
+
+	// Set contenteditable to false on removals(#10429)
+	// Setting to empty string throws an error as an invalid value
+	jQuery.attrHooks.contenteditable = {
+		get: nodeHook.get,
+		set: function( elem, value, name ) {
+			if ( value === "" ) {
+				value = "false";
+			}
+			nodeHook.set( elem, value, name );
+		}
+	};
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+	jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+		jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+			get: function( elem ) {
+				var ret = elem.getAttribute( name, 2 );
+				return ret === null ? undefined : ret;
+			}
+		});
+	});
+}
+
+if ( !jQuery.support.style ) {
+	jQuery.attrHooks.style = {
+		get: function( elem ) {
+			// Return undefined in the case of empty string
+			// Normalize to lowercase since IE uppercases css property names
+			return elem.style.cssText.toLowerCase() || undefined;
+		},
+		set: function( elem, value ) {
+			return ( elem.style.cssText = "" + value );
+		}
+	};
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+	jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+		get: function( elem ) {
+			var parent = elem.parentNode;
+
+			if ( parent ) {
+				parent.selectedIndex;
+
+				// Make sure that it also works with optgroups, see #5701
+				if ( parent.parentNode ) {
+					parent.parentNode.selectedIndex;
+				}
+			}
+			return null;
+		}
+	});
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+	jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+	jQuery.each([ "radio", "checkbox" ], function() {
+		jQuery.valHooks[ this ] = {
+			get: function( elem ) {
+				// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+				return elem.getAttribute("value") === null ? "on" : elem.value;
+			}
+		};
+	});
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+	jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+		set: function( elem, value ) {
+			if ( jQuery.isArray( value ) ) {
+				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+			}
+		}
+	});
+});
+
+
+
+
+var rformElems = /^(?:textarea|input|select)$/i,
+	rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
+	rhoverHack = /(?:^|\s)hover(\.\S+)?\b/,
+	rkeyEvent = /^key/,
+	rmouseEvent = /^(?:mouse|contextmenu)|click/,
+	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+	rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
+	quickParse = function( selector ) {
+		var quick = rquickIs.exec( selector );
+		if ( quick ) {
+			//   0  1    2   3
+			// [ _, tag, id, class ]
+			quick[1] = ( quick[1] || "" ).toLowerCase();
+			quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
+		}
+		return quick;
+	},
+	quickIs = function( elem, m ) {
+		var attrs = elem.attributes || {};
+		return (
+			(!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
+			(!m[2] || (attrs.id || {}).value === m[2]) &&
+			(!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
+		);
+	},
+	hoverHack = function( events ) {
+		return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+	};
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+	add: function( elem, types, handler, data, selector ) {
+
+		var elemData, eventHandle, events,
+			t, tns, type, namespaces, handleObj,
+			handleObjIn, quick, handlers, special;
+
+		// Don't attach events to noData or text/comment nodes (allow plain objects tho)
+		if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+			return;
+		}
+
+		// Caller can pass in an object of custom data in lieu of the handler
+		if ( handler.handler ) {
+			handleObjIn = handler;
+			handler = handleObjIn.handler;
+			selector = handleObjIn.selector;
+		}
+
+		// Make sure that the handler has a unique ID, used to find/remove it later
+		if ( !handler.guid ) {
+			handler.guid = jQuery.guid++;
+		}
+
+		// Init the element's event structure and main handler, if this is the first
+		events = elemData.events;
+		if ( !events ) {
+			elemData.events = events = {};
+		}
+		eventHandle = elemData.handle;
+		if ( !eventHandle ) {
+			elemData.handle = eventHandle = function( e ) {
+				// Discard the second event of a jQuery.event.trigger() and
+				// when an event is called after a page has unloaded
+				return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+					undefined;
+			};
+			// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+			eventHandle.elem = elem;
+		}
+
+		// Handle multiple events separated by a space
+		// jQuery(...).bind("mouseover mouseout", fn);
+		types = jQuery.trim( hoverHack(types) ).split( " " );
+		for ( t = 0; t < types.length; t++ ) {
+
+			tns = rtypenamespace.exec( types[t] ) || [];
+			type = tns[1];
+			namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+			// If event changes its type, use the special event handlers for the changed type
+			special = jQuery.event.special[ type ] || {};
+
+			// If selector defined, determine special event api type, otherwise given type
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+
+			// Update special based on newly reset type
+			special = jQuery.event.special[ type ] || {};
+
+			// handleObj is passed to all event handlers
+			handleObj = jQuery.extend({
+				type: type,
+				origType: tns[1],
+				data: data,
+				handler: handler,
+				guid: handler.guid,
+				selector: selector,
+				quick: selector && quickParse( selector ),
+				namespace: namespaces.join(".")
+			}, handleObjIn );
+
+			// Init the event handler queue if we're the first
+			handlers = events[ type ];
+			if ( !handlers ) {
+				handlers = events[ type ] = [];
+				handlers.delegateCount = 0;
+
+				// Only use addEventListener/attachEvent if the special events handler returns false
+				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+					// Bind the global event handler to the element
+					if ( elem.addEventListener ) {
+						elem.addEventListener( type, eventHandle, false );
+
+					} else if ( elem.attachEvent ) {
+						elem.attachEvent( "on" + type, eventHandle );
+					}
+				}
+			}
+
+			if ( special.add ) {
+				special.add.call( elem, handleObj );
+
+				if ( !handleObj.handler.guid ) {
+					handleObj.handler.guid = handler.guid;
+				}
+			}
+
+			// Add to the element's handler list, delegates in front
+			if ( selector ) {
+				handlers.splice( handlers.delegateCount++, 0, handleObj );
+			} else {
+				handlers.push( handleObj );
+			}
+
+			// Keep track of which events have ever been used, for event optimization
+			jQuery.event.global[ type ] = true;
+		}
+
+		// Nullify elem to prevent memory leaks in IE
+		elem = null;
+	},
+
+	global: {},
+
+	// Detach an event or set of events from an element
+	remove: function( elem, types, handler, selector, mappedTypes ) {
+
+		var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+			t, tns, type, origType, namespaces, origCount,
+			j, events, special, handle, eventType, handleObj;
+
+		if ( !elemData || !(events = elemData.events) ) {
+			return;
+		}
+
+		// Once for each type.namespace in types; type may be omitted
+		types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+		for ( t = 0; t < types.length; t++ ) {
+			tns = rtypenamespace.exec( types[t] ) || [];
+			type = origType = tns[1];
+			namespaces = tns[2];
+
+			// Unbind all events (on this namespace, if provided) for the element
+			if ( !type ) {
+				for ( type in events ) {
+					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+				}
+				continue;
+			}
+
+			special = jQuery.event.special[ type ] || {};
+			type = ( selector? special.delegateType : special.bindType ) || type;
+			eventType = events[ type ] || [];
+			origCount = eventType.length;
+			namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+
+			// Remove matching events
+			for ( j = 0; j < eventType.length; j++ ) {
+				handleObj = eventType[ j ];
+
+				if ( ( mappedTypes || origType === handleObj.origType ) &&
+					 ( !handler || handler.guid === handleObj.guid ) &&
+					 ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+					 ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+					eventType.splice( j--, 1 );
+
+					if ( handleObj.selector ) {
+						eventType.delegateCount--;
+					}
+					if ( special.remove ) {
+						special.remove.call( elem, handleObj );
+					}
+				}
+			}
+
+			// Remove generic event handler if we removed something and no more handlers exist
+			// (avoids potential for endless recursion during removal of special event handlers)
+			if ( eventType.length === 0 && origCount !== eventType.length ) {
+				if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+					jQuery.removeEvent( elem, type, elemData.handle );
+				}
+
+				delete events[ type ];
+			}
+		}
+
+		// Remove the expando if it's no longer used
+		if ( jQuery.isEmptyObject( events ) ) {
+			handle = elemData.handle;
+			if ( handle ) {
+				handle.elem = null;
+			}
+
+			// removeData also checks for emptiness and clears the expando if empty
+			// so use it instead of delete
+			jQuery.removeData( elem, [ "events", "handle" ], true );
+		}
+	},
+
+	// Events that are safe to short-circuit if no handlers are attached.
+	// Native DOM events should not be added, they may have inline handlers.
+	customEvent: {
+		"getData": true,
+		"setData": true,
+		"changeData": true
+	},
+
+	trigger: function( event, data, elem, onlyHandlers ) {
+		// Don't do events on text and comment nodes
+		if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+			return;
+		}
+
+		// Event object or event type
+		var type = event.type || event,
+			namespaces = [],
+			cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
+
+		// focus/blur morphs to focusin/out; ensure we're not firing them right now
+		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+			return;
+		}
+
+		if ( type.indexOf( "!" ) >= 0 ) {
+			// Exclusive events trigger only for the exact event (no namespaces)
+			type = type.slice(0, -1);
+			exclusive = true;
+		}
+
+		if ( type.indexOf( "." ) >= 0 ) {
+			// Namespaced trigger; create a regexp to match event type in handle()
+			namespaces = type.split(".");
+			type = namespaces.shift();
+			namespaces.sort();
+		}
+
+		if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+			// No jQuery handlers for this event type, and it can't have inline handlers
+			return;
+		}
+
+		// Caller can pass in an Event, Object, or just an event type string
+		event = typeof event === "object" ?
+			// jQuery.Event object
+			event[ jQuery.expando ] ? event :
+			// Object literal
+			new jQuery.Event( type, event ) :
+			// Just the event type (string)
+			new jQuery.Event( type );
+
+		event.type = type;
+		event.isTrigger = true;
+		event.exclusive = exclusive;
+		event.namespace = namespaces.join( "." );
+		event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
+		ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+		// Handle a global trigger
+		if ( !elem ) {
+
+			// TODO: Stop taunting the data cache; remove global events and always attach to document
+			cache = jQuery.cache;
+			for ( i in cache ) {
+				if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+					jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+				}
+			}
+			return;
+		}
+
+		// Clean up the event in case it is being reused
+		event.result = undefined;
+		if ( !event.target ) {
+			event.target = elem;
+		}
+
+		// Clone any incoming data and prepend the event, creating the handler arg list
+		data = data != null ? jQuery.makeArray( data ) : [];
+		data.unshift( event );
+
+		// Allow special events to draw outside the lines
+		special = jQuery.event.special[ type ] || {};
+		if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+			return;
+		}
+
+		// Determine event propagation path in advance, per W3C events spec (#9951)
+		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+		eventPath = [[ elem, special.bindType || type ]];
+		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+			bubbleType = special.delegateType || type;
+			cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+			old = null;
+			for ( ; cur; cur = cur.parentNode ) {
+				eventPath.push([ cur, bubbleType ]);
+				old = cur;
+			}
+
+			// Only add window if we got to document (e.g., not plain obj or detached DOM)
+			if ( old && old === elem.ownerDocument ) {
+				eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+			}
+		}
+
+		// Fire handlers on the event path
+		for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+			cur = eventPath[i][0];
+			event.type = eventPath[i][1];
+
+			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+			if ( handle ) {
+				handle.apply( cur, data );
+			}
+			// Note that this is a bare JS function and not a jQuery handler
+			handle = ontype && cur[ ontype ];
+			if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
+				event.preventDefault();
+			}
+		}
+		event.type = type;
+
+		// If nobody prevented the default action, do it now
+		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+			if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+				!(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+				// Call a native DOM method on the target with the same name name as the event.
+				// Can't use an .isFunction() check here because IE6/7 fails that test.
+				// Don't do default actions on window, that's where global variables be (#6170)
+				// IE<9 dies on focus/blur to hidden element (#1486)
+				if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+					// Don't re-trigger an onFOO event when we call its FOO() method
+					old = elem[ ontype ];
+
+					if ( old ) {
+						elem[ ontype ] = null;
+					}
+
+					// Prevent re-triggering of the same event, since we already bubbled it above
+					jQuery.event.triggered = type;
+					elem[ type ]();
+					jQuery.event.triggered = undefined;
+
+					if ( old ) {
+						elem[ ontype ] = old;
+					}
+				}
+			}
+		}
+
+		return event.result;
+	},
+
+	dispatch: function( event ) {
+
+		// Make a writable jQuery.Event from the native event object
+		event = jQuery.event.fix( event || window.event );
+
+		var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+			delegateCount = handlers.delegateCount,
+			args = [].slice.call( arguments, 0 ),
+			run_all = !event.exclusive && !event.namespace,
+			special = jQuery.event.special[ event.type ] || {},
+			handlerQueue = [],
+			i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
+
+		// Use the fix-ed jQuery.Event rather than the (read-only) native event
+		args[0] = event;
+		event.delegateTarget = this;
+
+		// Call the preDispatch hook for the mapped type, and let it bail if desired
+		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+			return;
+		}
+
+		// Determine handlers that should run if there are delegated events
+		// Avoid non-left-click bubbling in Firefox (#3861)
+		if ( delegateCount && !(event.button && event.type === "click") ) {
+
+			// Pregenerate a single jQuery object for reuse with .is()
+			jqcur = jQuery(this);
+			jqcur.context = this.ownerDocument || this;
+
+			for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+				// Don't process events on disabled elements (#6911, #8165)
+				if ( cur.disabled !== true ) {
+					selMatch = {};
+					matches = [];
+					jqcur[0] = cur;
+					for ( i = 0; i < delegateCount; i++ ) {
+						handleObj = handlers[ i ];
+						sel = handleObj.selector;
+
+						if ( selMatch[ sel ] === undefined ) {
+							selMatch[ sel ] = (
+								handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
+							);
+						}
+						if ( selMatch[ sel ] ) {
+							matches.push( handleObj );
+						}
+					}
+					if ( matches.length ) {
+						handlerQueue.push({ elem: cur, matches: matches });
+					}
+				}
+			}
+		}
+
+		// Add the remaining (directly-bound) handlers
+		if ( handlers.length > delegateCount ) {
+			handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+		}
+
+		// Run delegates first; they may want to stop propagation beneath us
+		for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+			matched = handlerQueue[ i ];
+			event.currentTarget = matched.elem;
+
+			for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+				handleObj = matched.matches[ j ];
+
+				// Triggered event must either 1) be non-exclusive and have no namespace, or
+				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+				if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+					event.data = handleObj.data;
+					event.handleObj = handleObj;
+
+					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+							.apply( matched.elem, args );
+
+					if ( ret !== undefined ) {
+						event.result = ret;
+						if ( ret === false ) {
+							event.preventDefault();
+							event.stopPropagation();
+						}
+					}
+				}
+			}
+		}
+
+		// Call the postDispatch hook for the mapped type
+		if ( special.postDispatch ) {
+			special.postDispatch.call( this, event );
+		}
+
+		return event.result;
+	},
+
+	// Includes some event props shared by KeyEvent and MouseEvent
+	// *** attrChange attrName relatedNode srcElement  are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+	props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+	fixHooks: {},
+
+	keyHooks: {
+		props: "char charCode key keyCode".split(" "),
+		filter: function( event, original ) {
+
+			// Add which for key events
+			if ( event.which == null ) {
+				event.which = original.charCode != null ? original.charCode : original.keyCode;
+			}
+
+			return event;
+		}
+	},
+
+	mouseHooks: {
+		props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+		filter: function( event, original ) {
+			var eventDoc, doc, body,
+				button = original.button,
+				fromElement = original.fromElement;
+
+			// Calculate pageX/Y if missing and clientX/Y available
+			if ( event.pageX == null && original.clientX != null ) {
+				eventDoc = event.target.ownerDocument || document;
+				doc = eventDoc.documentElement;
+				body = eventDoc.body;
+
+				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
+			}
+
+			// Add relatedTarget, if necessary
+			if ( !event.relatedTarget && fromElement ) {
+				event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+			}
+
+			// Add which for click: 1 === left; 2 === middle; 3 === right
+			// Note: button is not normalized, so don't use it
+			if ( !event.which && button !== undefined ) {
+				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+			}
+
+			return event;
+		}
+	},
+
+	fix: function( event ) {
+		if ( event[ jQuery.expando ] ) {
+			return event;
+		}
+
+		// Create a writable copy of the event object and normalize some properties
+		var i, prop,
+			originalEvent = event,
+			fixHook = jQuery.event.fixHooks[ event.type ] || {},
+			copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+		event = jQuery.Event( originalEvent );
+
+		for ( i = copy.length; i; ) {
+			prop = copy[ --i ];
+			event[ prop ] = originalEvent[ prop ];
+		}
+
+		// Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+		if ( !event.target ) {
+			event.target = originalEvent.srcElement || document;
+		}
+
+		// Target should not be a text node (#504, Safari)
+		if ( event.target.nodeType === 3 ) {
+			event.target = event.target.parentNode;
+		}
+
+		// For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
+		if ( event.metaKey === undefined ) {
+			event.metaKey = event.ctrlKey;
+		}
+
+		return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+	},
+
+	special: {
+		ready: {
+			// Make sure the ready event is setup
+			setup: jQuery.bindReady
+		},
+
+		load: {
+			// Prevent triggered image.load events from bubbling to window.load
+			noBubble: true
+		},
+
+		focus: {
+			delegateType: "focusin"
+		},
+		blur: {
+			delegateType: "focusout"
+		},
+
+		beforeunload: {
+			setup: function( data, namespaces, eventHandle ) {
+				// We only want to do this special case on windows
+				if ( jQuery.isWindow( this ) ) {
+					this.onbeforeunload = eventHandle;
+				}
+			},
+
+			teardown: function( namespaces, eventHandle ) {
+				if ( this.onbeforeunload === eventHandle ) {
+					this.onbeforeunload = null;
+				}
+			}
+		}
+	},
+
+	simulate: function( type, elem, event, bubble ) {
+		// Piggyback on a donor event to simulate a different one.
+		// Fake originalEvent to avoid donor's stopPropagation, but if the
+		// simulated event prevents default then we do the same on the donor.
+		var e = jQuery.extend(
+			new jQuery.Event(),
+			event,
+			{ type: type,
+				isSimulated: true,
+				originalEvent: {}
+			}
+		);
+		if ( bubble ) {
+			jQuery.event.trigger( e, null, elem );
+		} else {
+			jQuery.event.dispatch.call( elem, e );
+		}
+		if ( e.isDefaultPrevented() ) {
+			event.preventDefault();
+		}
+	}
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+	function( elem, type, handle ) {
+		if ( elem.removeEventListener ) {
+			elem.removeEventListener( type, handle, false );
+		}
+	} :
+	function( elem, type, handle ) {
+		if ( elem.detachEvent ) {
+			elem.detachEvent( "on" + type, handle );
+		}
+	};
+
+jQuery.Event = function( src, props ) {
+	// Allow instantiation without the 'new' keyword
+	if ( !(this instanceof jQuery.Event) ) {
+		return new jQuery.Event( src, props );
+	}
+
+	// Event object
+	if ( src && src.type ) {
+		this.originalEvent = src;
+		this.type = src.type;
+
+		// Events bubbling up the document may have been marked as prevented
+		// by a handler lower down the tree; reflect the correct value.
+		this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+			src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+	// Event type
+	} else {
+		this.type = src;
+	}
+
+	// Put explicitly provided properties onto the event object
+	if ( props ) {
+		jQuery.extend( this, props );
+	}
+
+	// Create a timestamp if incoming event doesn't have one
+	this.timeStamp = src && src.timeStamp || jQuery.now();
+
+	// Mark it as fixed
+	this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+	return false;
+}
+function returnTrue() {
+	return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+	preventDefault: function() {
+		this.isDefaultPrevented = returnTrue;
+
+		var e = this.originalEvent;
+		if ( !e ) {
+			return;
+		}
+
+		// if preventDefault exists run it on the original event
+		if ( e.preventDefault ) {
+			e.preventDefault();
+
+		// otherwise set the returnValue property of the original event to false (IE)
+		} else {
+			e.returnValue = false;
+		}
+	},
+	stopPropagation: function() {
+		this.isPropagationStopped = returnTrue;
+
+		var e = this.originalEvent;
+		if ( !e ) {
+			return;
+		}
+		// if stopPropagation exists run it on the original event
+		if ( e.stopPropagation ) {
+			e.stopPropagation();
+		}
+		// otherwise set the cancelBubble property of the original event to true (IE)
+		e.cancelBubble = true;
+	},
+	stopImmediatePropagation: function() {
+		this.isImmediatePropagationStopped = returnTrue;
+		this.stopPropagation();
+	},
+	isDefaultPrevented: returnFalse,
+	isPropagationStopped: returnFalse,
+	isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+	mouseenter: "mouseover",
+	mouseleave: "mouseout"
+}, function( orig, fix ) {
+	jQuery.event.special[ orig ] = {
+		delegateType: fix,
+		bindType: fix,
+
+		handle: function( event ) {
+			var target = this,
+				related = event.relatedTarget,
+				handleObj = event.handleObj,
+				selector = handleObj.selector,
+				ret;
+
+			// For mousenter/leave call the handler if related is outside the target.
+			// NB: No relatedTarget if the mouse left/entered the browser window
+			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+				event.type = handleObj.origType;
+				ret = handleObj.handler.apply( this, arguments );
+				event.type = fix;
+			}
+			return ret;
+		}
+	};
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+	jQuery.event.special.submit = {
+		setup: function() {
+			// Only need this for delegated form submit events
+			if ( jQuery.nodeName( this, "form" ) ) {
+				return false;
+			}
+
+			// Lazy-add a submit handler when a descendant form may potentially be submitted
+			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+				// Node name check avoids a VML-related crash in IE (#9807)
+				var elem = e.target,
+					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+				if ( form && !form._submit_attached ) {
+					jQuery.event.add( form, "submit._submit", function( event ) {
+						event._submit_bubble = true;
+					});
+					form._submit_attached = true;
+				}
+			});
+			// return undefined since we don't need an event listener
+		},
+		
+		postDispatch: function( event ) {
+			// If form was submitted by the user, bubble the event up the tree
+			if ( event._submit_bubble ) {
+				delete event._submit_bubble;
+				if ( this.parentNode && !event.isTrigger ) {
+					jQuery.event.simulate( "submit", this.parentNode, event, true );
+				}
+			}
+		},
+
+		teardown: function() {
+			// Only need this for delegated form submit events
+			if ( jQuery.nodeName( this, "form" ) ) {
+				return false;
+			}
+
+			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+			jQuery.event.remove( this, "._submit" );
+		}
+	};
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+	jQuery.event.special.change = {
+
+		setup: function() {
+
+			if ( rformElems.test( this.nodeName ) ) {
+				// IE doesn't fire change on a check/radio until blur; trigger it on click
+				// after a propertychange. Eat the blur-change in special.change.handle.
+				// This still fires onchange a second time for check/radio after blur.
+				if ( this.type === "checkbox" || this.type === "radio" ) {
+					jQuery.event.add( this, "propertychange._change", function( event ) {
+						if ( event.originalEvent.propertyName === "checked" ) {
+							this._just_changed = true;
+						}
+					});
+					jQuery.event.add( this, "click._change", function( event ) {
+						if ( this._just_changed && !event.isTrigger ) {
+							this._just_changed = false;
+							jQuery.event.simulate( "change", this, event, true );
+						}
+					});
+				}
+				return false;
+			}
+			// Delegated event; lazy-add a change handler on descendant inputs
+			jQuery.event.add( this, "beforeactivate._change", function( e ) {
+				var elem = e.target;
+
+				if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
+					jQuery.event.add( elem, "change._change", function( event ) {
+						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+							jQuery.event.simulate( "change", this.parentNode, event, true );
+						}
+					});
+					elem._change_attached = true;
+				}
+			});
+		},
+
+		handle: function( event ) {
+			var elem = event.target;
+
+			// Swallow native change events from checkbox/radio, we already triggered them above
+			if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+				return event.handleObj.handler.apply( this, arguments );
+			}
+		},
+
+		teardown: function() {
+			jQuery.event.remove( this, "._change" );
+
+			return rformElems.test( this.nodeName );
+		}
+	};
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+		// Attach a single capturing handler while someone wants focusin/focusout
+		var attaches = 0,
+			handler = function( event ) {
+				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+			};
+
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+				if ( attaches++ === 0 ) {
+					document.addEventListener( orig, handler, true );
+				}
+			},
+			teardown: function() {
+				if ( --attaches === 0 ) {
+					document.removeEventListener( orig, handler, true );
+				}
+			}
+		};
+	});
+}
+
+jQuery.fn.extend({
+
+	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+		var origFn, type;
+
+		// Types can be a map of types/handlers
+		if ( typeof types === "object" ) {
+			// ( types-Object, selector, data )
+			if ( typeof selector !== "string" ) { // && selector != null
+				// ( types-Object, data )
+				data = data || selector;
+				selector = undefined;
+			}
+			for ( type in types ) {
+				this.on( type, selector, data, types[ type ], one );
+			}
+			return this;
+		}
+
+		if ( data == null && fn == null ) {
+			// ( types, fn )
+			fn = selector;
+			data = selector = undefined;
+		} else if ( fn == null ) {
+			if ( typeof selector === "string" ) {
+				// ( types, selector, fn )
+				fn = data;
+				data = undefined;
+			} else {
+				// ( types, data, fn )
+				fn = data;
+				data = selector;
+				selector = undefined;
+			}
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		} else if ( !fn ) {
+			return this;
+		}
+
+		if ( one === 1 ) {
+			origFn = fn;
+			fn = function( event ) {
+				// Can use an empty set, since event contains the info
+				jQuery().off( event );
+				return origFn.apply( this, arguments );
+			};
+			// Use same guid so caller can remove using origFn
+			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+		}
+		return this.each( function() {
+			jQuery.event.add( this, types, fn, data, selector );
+		});
+	},
+	one: function( types, selector, data, fn ) {
+		return this.on( types, selector, data, fn, 1 );
+	},
+	off: function( types, selector, fn ) {
+		if ( types && types.preventDefault && types.handleObj ) {
+			// ( event )  dispatched jQuery.Event
+			var handleObj = types.handleObj;
+			jQuery( types.delegateTarget ).off(
+				handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+				handleObj.selector,
+				handleObj.handler
+			);
+			return this;
+		}
+		if ( typeof types === "object" ) {
+			// ( types-object [, selector] )
+			for ( var type in types ) {
+				this.off( type, selector, types[ type ] );
+			}
+			return this;
+		}
+		if ( selector === false || typeof selector === "function" ) {
+			// ( types [, fn] )
+			fn = selector;
+			selector = undefined;
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		}
+		return this.each(function() {
+			jQuery.event.remove( this, types, fn, selector );
+		});
+	},
+
+	bind: function( types, data, fn ) {
+		return this.on( types, null, data, fn );
+	},
+	unbind: function( types, fn ) {
+		return this.off( types, null, fn );
+	},
+
+	live: function( types, data, fn ) {
+		jQuery( this.context ).on( types, this.selector, data, fn );
+		return this;
+	},
+	die: function( types, fn ) {
+		jQuery( this.context ).off( types, this.selector || "**", fn );
+		return this;
+	},
+
+	delegate: function( selector, types, data, fn ) {
+		return this.on( types, selector, data, fn );
+	},
+	undelegate: function( selector, types, fn ) {
+		// ( namespace ) or ( selector, types [, fn] )
+		return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
+	},
+
+	trigger: function( type, data ) {
+		return this.each(function() {
+			jQuery.event.trigger( type, data, this );
+		});
+	},
+	triggerHandler: function( type, data ) {
+		if ( this[0] ) {
+			return jQuery.event.trigger( type, data, this[0], true );
+		}
+	},
+
+	toggle: function( fn ) {
+		// Save reference to arguments for access in closure
+		var args = arguments,
+			guid = fn.guid || jQuery.guid++,
+			i = 0,
+			toggler = function( event ) {
+				// Figure out which function to execute
+				var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+				jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+				// Make sure that clicks stop
+				event.preventDefault();
+
+				// and execute the function
+				return args[ lastToggle ].apply( this, arguments ) || false;
+			};
+
+		// link all the functions, so any of them can unbind this click handler
+		toggler.guid = guid;
+		while ( i < args.length ) {
+			args[ i++ ].guid = guid;
+		}
+
+		return this.click( toggler );
+	},
+
+	hover: function( fnOver, fnOut ) {
+		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+	}
+});
+
+jQuery.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( i, name ) {
+
+	// Handle event binding
+	jQuery.fn[ name ] = function( data, fn ) {
+		if ( fn == null ) {
+			fn = data;
+			data = null;
+		}
+
+		return arguments.length > 0 ?
+			this.on( name, null, data, fn ) :
+			this.trigger( name );
+	};
+
+	if ( jQuery.attrFn ) {
+		jQuery.attrFn[ name ] = true;
+	}
+
+	if ( rkeyEvent.test( name ) ) {
+		jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+	}
+
+	if ( rmouseEvent.test( name ) ) {
+		jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+	}
+});
+
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ *  Copyright 2011, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+	expando = "sizcache" + (Math.random() + '').replace('.', ''),
+	done = 0,
+	toString = Object.prototype.toString,
+	hasDuplicate = false,
+	baseHasDuplicate = true,
+	rBackslash = /\\/g,
+	rReturn = /\r\n/g,
+	rNonWord = /\W/;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+//   Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+	baseHasDuplicate = false;
+	return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+	results = results || [];
+	context = context || document;
+
+	var origContext = context;
+
+	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+		return [];
+	}
+
+	if ( !selector || typeof selector !== "string" ) {
+		return results;
+	}
+
+	var m, set, checkSet, extra, ret, cur, pop, i,
+		prune = true,
+		contextXML = Sizzle.isXML( context ),
+		parts = [],
+		soFar = selector;
+
+	// Reset the position of the chunker regexp (start from head)
+	do {
+		chunker.exec( "" );
+		m = chunker.exec( soFar );
+
+		if ( m ) {
+			soFar = m[3];
+
+			parts.push( m[1] );
+
+			if ( m[2] ) {
+				extra = m[3];
+				break;
+			}
+		}
+	} while ( m );
+
+	if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+			set = posProcess( parts[0] + parts[1], context, seed );
+
+		} else {
+			set = Expr.relative[ parts[0] ] ?
+				[ context ] :
+				Sizzle( parts.shift(), context );
+
+			while ( parts.length ) {
+				selector = parts.shift();
+
+				if ( Expr.relative[ selector ] ) {
+					selector += parts.shift();
+				}
+
+				set = posProcess( selector, set, seed );
+			}
+		}
+
+	} else {
+		// Take a shortcut and set the context if the root selector is an ID
+		// (but not if it'll be faster if the inner selector is an ID)
+		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+			ret = Sizzle.find( parts.shift(), context, contextXML );
+			context = ret.expr ?
+				Sizzle.filter( ret.expr, ret.set )[0] :
+				ret.set[0];
+		}
+
+		if ( context ) {
+			ret = seed ?
+				{ expr: parts.pop(), set: makeArray(seed) } :
+				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+			set = ret.expr ?
+				Sizzle.filter( ret.expr, ret.set ) :
+				ret.set;
+
+			if ( parts.length > 0 ) {
+				checkSet = makeArray( set );
+
+			} else {
+				prune = false;
+			}
+
+			while ( parts.length ) {
+				cur = parts.pop();
+				pop = cur;
+
+				if ( !Expr.relative[ cur ] ) {
+					cur = "";
+				} else {
+					pop = parts.pop();
+				}
+
+				if ( pop == null ) {
+					pop = context;
+				}
+
+				Expr.relative[ cur ]( checkSet, pop, contextXML );
+			}
+
+		} else {
+			checkSet = parts = [];
+		}
+	}
+
+	if ( !checkSet ) {
+		checkSet = set;
+	}
+
+	if ( !checkSet ) {
+		Sizzle.error( cur || selector );
+	}
+
+	if ( toString.call(checkSet) === "[object Array]" ) {
+		if ( !prune ) {
+			results.push.apply( results, checkSet );
+
+		} else if ( context && context.nodeType === 1 ) {
+			for ( i = 0; checkSet[i] != null; i++ ) {
+				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+					results.push( set[i] );
+				}
+			}
+
+		} else {
+			for ( i = 0; checkSet[i] != null; i++ ) {
+				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+					results.push( set[i] );
+				}
+			}
+		}
+
+	} else {
+		makeArray( checkSet, results );
+	}
+
+	if ( extra ) {
+		Sizzle( extra, origContext, results, seed );
+		Sizzle.uniqueSort( results );
+	}
+
+	return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+	if ( sortOrder ) {
+		hasDuplicate = baseHasDuplicate;
+		results.sort( sortOrder );
+
+		if ( hasDuplicate ) {
+			for ( var i = 1; i < results.length; i++ ) {
+				if ( results[i] === results[ i - 1 ] ) {
+					results.splice( i--, 1 );
+				}
+			}
+		}
+	}
+
+	return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+	return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+	return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+	var set, i, len, match, type, left;
+
+	if ( !expr ) {
+		return [];
+	}
+
+	for ( i = 0, len = Expr.order.length; i < len; i++ ) {
+		type = Expr.order[i];
+
+		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+			left = match[1];
+			match.splice( 1, 1 );
+
+			if ( left.substr( left.length - 1 ) !== "\\" ) {
+				match[1] = (match[1] || "").replace( rBackslash, "" );
+				set = Expr.find[ type ]( match, context, isXML );
+
+				if ( set != null ) {
+					expr = expr.replace( Expr.match[ type ], "" );
+					break;
+				}
+			}
+		}
+	}
+
+	if ( !set ) {
+		set = typeof context.getElementsByTagName !== "undefined" ?
+			context.getElementsByTagName( "*" ) :
+			[];
+	}
+
+	return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+	var match, anyFound,
+		type, found, item, filter, left,
+		i, pass,
+		old = expr,
+		result = [],
+		curLoop = set,
+		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+	while ( expr && set.length ) {
+		for ( type in Expr.filter ) {
+			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+				filter = Expr.filter[ type ];
+				left = match[1];
+
+				anyFound = false;
+
+				match.splice(1,1);
+
+				if ( left.substr( left.length - 1 ) === "\\" ) {
+					continue;
+				}
+
+				if ( curLoop === result ) {
+					result = [];
+				}
+
+				if ( Expr.preFilter[ type ] ) {
+					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+					if ( !match ) {
+						anyFound = found = true;
+
+					} else if ( match === true ) {
+						continue;
+					}
+				}
+
+				if ( match ) {
+					for ( i = 0; (item = curLoop[i]) != null; i++ ) {
+						if ( item ) {
+							found = filter( item, match, i, curLoop );
+							pass = not ^ found;
+
+							if ( inplace && found != null ) {
+								if ( pass ) {
+									anyFound = true;
+
+								} else {
+									curLoop[i] = false;
+								}
+
+							} else if ( pass ) {
+								result.push( item );
+								anyFound = true;
+							}
+						}
+					}
+				}
+
+				if ( found !== undefined ) {
+					if ( !inplace ) {
+						curLoop = result;
+					}
+
+					expr = expr.replace( Expr.match[ type ], "" );
+
+					if ( !anyFound ) {
+						return [];
+					}
+
+					break;
+				}
+			}
+		}
+
+		// Improper expression
+		if ( expr === old ) {
+			if ( anyFound == null ) {
+				Sizzle.error( expr );
+
+			} else {
+				break;
+			}
+		}
+
+		old = expr;
+	}
+
+	return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+	throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Utility function for retreiving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+var getText = Sizzle.getText = function( elem ) {
+    var i, node,
+		nodeType = elem.nodeType,
+		ret = "";
+
+	if ( nodeType ) {
+		if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+			// Use textContent || innerText for elements
+			if ( typeof elem.textContent === 'string' ) {
+				return elem.textContent;
+			} else if ( typeof elem.innerText === 'string' ) {
+				// Replace IE's carriage returns
+				return elem.innerText.replace( rReturn, '' );
+			} else {
+				// Traverse it's children
+				for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
+					ret += getText( elem );
+				}
+			}
+		} else if ( nodeType === 3 || nodeType === 4 ) {
+			return elem.nodeValue;
+		}
+	} else {
+
+		// If no nodeType, this is expected to be an array
+		for ( i = 0; (node = elem[i]); i++ ) {
+			// Do not traverse comment nodes
+			if ( node.nodeType !== 8 ) {
+				ret += getText( node );
+			}
+		}
+	}
+	return ret;
+};
+
+var Expr = Sizzle.selectors = {
+	order: [ "ID", "NAME", "TAG" ],
+
+	match: {
+		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+	},
+
+	leftMatch: {},
+
+	attrMap: {
+		"class": "className",
+		"for": "htmlFor"
+	},
+
+	attrHandle: {
+		href: function( elem ) {
+			return elem.getAttribute( "href" );
+		},
+		type: function( elem ) {
+			return elem.getAttribute( "type" );
+		}
+	},
+
+	relative: {
+		"+": function(checkSet, part){
+			var isPartStr = typeof part === "string",
+				isTag = isPartStr && !rNonWord.test( part ),
+				isPartStrNotTag = isPartStr && !isTag;
+
+			if ( isTag ) {
+				part = part.toLowerCase();
+			}
+
+			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+				if ( (elem = checkSet[i]) ) {
+					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+						elem || false :
+						elem === part;
+				}
+			}
+
+			if ( isPartStrNotTag ) {
+				Sizzle.filter( part, checkSet, true );
+			}
+		},
+
+		">": function( checkSet, part ) {
+			var elem,
+				isPartStr = typeof part === "string",
+				i = 0,
+				l = checkSet.length;
+
+			if ( isPartStr && !rNonWord.test( part ) ) {
+				part = part.toLowerCase();
+
+				for ( ; i < l; i++ ) {
+					elem = checkSet[i];
+
+					if ( elem ) {
+						var parent = elem.parentNode;
+						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+					}
+				}
+
+			} else {
+				for ( ; i < l; i++ ) {
+					elem = checkSet[i];
+
+					if ( elem ) {
+						checkSet[i] = isPartStr ?
+							elem.parentNode :
+							elem.parentNode === part;
+					}
+				}
+
+				if ( isPartStr ) {
+					Sizzle.filter( part, checkSet, true );
+				}
+			}
+		},
+
+		"": function(checkSet, part, isXML){
+			var nodeCheck,
+				doneName = done++,
+				checkFn = dirCheck;
+
+			if ( typeof part === "string" && !rNonWord.test( part ) ) {
+				part = part.toLowerCase();
+				nodeCheck = part;
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+		},
+
+		"~": function( checkSet, part, isXML ) {
+			var nodeCheck,
+				doneName = done++,
+				checkFn = dirCheck;
+
+			if ( typeof part === "string" && !rNonWord.test( part ) ) {
+				part = part.toLowerCase();
+				nodeCheck = part;
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+		}
+	},
+
+	find: {
+		ID: function( match, context, isXML ) {
+			if ( typeof context.getElementById !== "undefined" && !isXML ) {
+				var m = context.getElementById(match[1]);
+				// Check parentNode to catch when Blackberry 4.6 returns
+				// nodes that are no longer in the document #6963
+				return m && m.parentNode ? [m] : [];
+			}
+		},
+
+		NAME: function( match, context ) {
+			if ( typeof context.getElementsByName !== "undefined" ) {
+				var ret = [],
+					results = context.getElementsByName( match[1] );
+
+				for ( var i = 0, l = results.length; i < l; i++ ) {
+					if ( results[i].getAttribute("name") === match[1] ) {
+						ret.push( results[i] );
+					}
+				}
+
+				return ret.length === 0 ? null : ret;
+			}
+		},
+
+		TAG: function( match, context ) {
+			if ( typeof context.getElementsByTagName !== "undefined" ) {
+				return context.getElementsByTagName( match[1] );
+			}
+		}
+	},
+	preFilter: {
+		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+			match = " " + match[1].replace( rBackslash, "" ) + " ";
+
+			if ( isXML ) {
+				return match;
+			}
+
+			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+				if ( elem ) {
+					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+						if ( !inplace ) {
+							result.push( elem );
+						}
+
+					} else if ( inplace ) {
+						curLoop[i] = false;
+					}
+				}
+			}
+
+			return false;
+		},
+
+		ID: function( match ) {
+			return match[1].replace( rBackslash, "" );
+		},
+
+		TAG: function( match, curLoop ) {
+			return match[1].replace( rBackslash, "" ).toLowerCase();
+		},
+
+		CHILD: function( match ) {
+			if ( match[1] === "nth" ) {
+				if ( !match[2] ) {
+					Sizzle.error( match[0] );
+				}
+
+				match[2] = match[2].replace(/^\+|\s*/g, '');
+
+				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+				// calculate the numbers (first)n+(last) including if they are negative
+				match[2] = (test[1] + (test[2] || 1)) - 0;
+				match[3] = test[3] - 0;
+			}
+			else if ( match[2] ) {
+				Sizzle.error( match[0] );
+			}
+
+			// TODO: Move to normal caching system
+			match[0] = done++;
+
+			return match;
+		},
+
+		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+			var name = match[1] = match[1].replace( rBackslash, "" );
+
+			if ( !isXML && Expr.attrMap[name] ) {
+				match[1] = Expr.attrMap[name];
+			}
+
+			// Handle if an un-quoted value was used
+			match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
+
+			if ( match[2] === "~=" ) {
+				match[4] = " " + match[4] + " ";
+			}
+
+			return match;
+		},
+
+		PSEUDO: function( match, curLoop, inplace, result, not ) {
+			if ( match[1] === "not" ) {
+				// If we're dealing with a complex expression, or a simple one
+				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+					match[3] = Sizzle(match[3], null, null, curLoop);
+
+				} else {
+					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+					if ( !inplace ) {
+						result.push.apply( result, ret );
+					}
+
+					return false;
+				}
+
+			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+				return true;
+			}
+
+			return match;
+		},
+
+		POS: function( match ) {
+			match.unshift( true );
+
+			return match;
+		}
+	},
+
+	filters: {
+		enabled: function( elem ) {
+			return elem.disabled === false && elem.type !== "hidden";
+		},
+
+		disabled: function( elem ) {
+			return elem.disabled === true;
+		},
+
+		checked: function( elem ) {
+			return elem.checked === true;
+		},
+
+		selected: function( elem ) {
+			// Accessing this property makes selected-by-default
+			// options in Safari work properly
+			if ( elem.parentNode ) {
+				elem.parentNode.selectedIndex;
+			}
+
+			return elem.selected === true;
+		},
+
+		parent: function( elem ) {
+			return !!elem.firstChild;
+		},
+
+		empty: function( elem ) {
+			return !elem.firstChild;
+		},
+
+		has: function( elem, i, match ) {
+			return !!Sizzle( match[3], elem ).length;
+		},
+
+		header: function( elem ) {
+			return (/h\d/i).test( elem.nodeName );
+		},
+
+		text: function( elem ) {
+			var attr = elem.getAttribute( "type" ), type = elem.type;
+			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+			// use getAttribute instead to test this case
+			return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
+		},
+
+		radio: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
+		},
+
+		checkbox: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
+		},
+
+		file: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
+		},
+
+		password: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
+		},
+
+		submit: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && "submit" === elem.type;
+		},
+
+		image: function( elem ) {
+			return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
+		},
+
+		reset: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return (name === "input" || name === "button") && "reset" === elem.type;
+		},
+
+		button: function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && "button" === elem.type || name === "button";
+		},
+
+		input: function( elem ) {
+			return (/input|select|textarea|button/i).test( elem.nodeName );
+		},
+
+		focus: function( elem ) {
+			return elem === elem.ownerDocument.activeElement;
+		}
+	},
+	setFilters: {
+		first: function( elem, i ) {
+			return i === 0;
+		},
+
+		last: function( elem, i, match, array ) {
+			return i === array.length - 1;
+		},
+
+		even: function( elem, i ) {
+			return i % 2 === 0;
+		},
+
+		odd: function( elem, i ) {
+			return i % 2 === 1;
+		},
+
+		lt: function( elem, i, match ) {
+			return i < match[3] - 0;
+		},
+
+		gt: function( elem, i, match ) {
+			return i > match[3] - 0;
+		},
+
+		nth: function( elem, i, match ) {
+			return match[3] - 0 === i;
+		},
+
+		eq: function( elem, i, match ) {
+			return match[3] - 0 === i;
+		}
+	},
+	filter: {
+		PSEUDO: function( elem, match, i, array ) {
+			var name = match[1],
+				filter = Expr.filters[ name ];
+
+			if ( filter ) {
+				return filter( elem, i, match, array );
+
+			} else if ( name === "contains" ) {
+				return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+			} else if ( name === "not" ) {
+				var not = match[3];
+
+				for ( var j = 0, l = not.length; j < l; j++ ) {
+					if ( not[j] === elem ) {
+						return false;
+					}
+				}
+
+				return true;
+
+			} else {
+				Sizzle.error( name );
+			}
+		},
+
+		CHILD: function( elem, match ) {
+			var first, last,
+				doneName, parent, cache,
+				count, diff,
+				type = match[1],
+				node = elem;
+
+			switch ( type ) {
+				case "only":
+				case "first":
+					while ( (node = node.previousSibling) ) {
+						if ( node.nodeType === 1 ) {
+							return false;
+						}
+					}
+
+					if ( type === "first" ) {
+						return true;
+					}
+
+					node = elem;
+
+					/* falls through */
+				case "last":
+					while ( (node = node.nextSibling) ) {
+						if ( node.nodeType === 1 ) {
+							return false;
+						}
+					}
+
+					return true;
+
+				case "nth":
+					first = match[2];
+					last = match[3];
+
+					if ( first === 1 && last === 0 ) {
+						return true;
+					}
+
+					doneName = match[0];
+					parent = elem.parentNode;
+
+					if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
+						count = 0;
+
+						for ( node = parent.firstChild; node; node = node.nextSibling ) {
+							if ( node.nodeType === 1 ) {
+								node.nodeIndex = ++count;
+							}
+						}
+
+						parent[ expando ] = doneName;
+					}
+
+					diff = elem.nodeIndex - last;
+
+					if ( first === 0 ) {
+						return diff === 0;
+
+					} else {
+						return ( diff % first === 0 && diff / first >= 0 );
+					}
+			}
+		},
+
+		ID: function( elem, match ) {
+			return elem.nodeType === 1 && elem.getAttribute("id") === match;
+		},
+
+		TAG: function( elem, match ) {
+			return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
+		},
+
+		CLASS: function( elem, match ) {
+			return (" " + (elem.className || elem.getAttribute("class")) + " ")
+				.indexOf( match ) > -1;
+		},
+
+		ATTR: function( elem, match ) {
+			var name = match[1],
+				result = Sizzle.attr ?
+					Sizzle.attr( elem, name ) :
+					Expr.attrHandle[ name ] ?
+					Expr.attrHandle[ name ]( elem ) :
+					elem[ name ] != null ?
+						elem[ name ] :
+						elem.getAttribute( name ),
+				value = result + "",
+				type = match[2],
+				check = match[4];
+
+			return result == null ?
+				type === "!=" :
+				!type && Sizzle.attr ?
+				result != null :
+				type === "=" ?
+				value === check :
+				type === "*=" ?
+				value.indexOf(check) >= 0 :
+				type === "~=" ?
+				(" " + value + " ").indexOf(check) >= 0 :
+				!check ?
+				value && result !== false :
+				type === "!=" ?
+				value !== check :
+				type === "^=" ?
+				value.indexOf(check) === 0 :
+				type === "$=" ?
+				value.substr(value.length - check.length) === check :
+				type === "|=" ?
+				value === check || value.substr(0, check.length + 1) === check + "-" :
+				false;
+		},
+
+		POS: function( elem, match, i, array ) {
+			var name = match[2],
+				filter = Expr.setFilters[ name ];
+
+			if ( filter ) {
+				return filter( elem, i, match, array );
+			}
+		}
+	}
+};
+
+var origPOS = Expr.match.POS,
+	fescape = function(all, num){
+		return "\\" + (num - 0 + 1);
+	};
+
+for ( var type in Expr.match ) {
+	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+// Expose origPOS
+// "global" as in regardless of relation to brackets/parens
+Expr.match.globalPOS = origPOS;
+
+var makeArray = function( array, results ) {
+	array = Array.prototype.slice.call( array, 0 );
+
+	if ( results ) {
+		results.push.apply( results, array );
+		return results;
+	}
+
+	return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+	makeArray = function( array, results ) {
+		var i = 0,
+			ret = results || [];
+
+		if ( toString.call(array) === "[object Array]" ) {
+			Array.prototype.push.apply( ret, array );
+
+		} else {
+			if ( typeof array.length === "number" ) {
+				for ( var l = array.length; i < l; i++ ) {
+					ret.push( array[i] );
+				}
+
+			} else {
+				for ( ; array[i]; i++ ) {
+					ret.push( array[i] );
+				}
+			}
+		}
+
+		return ret;
+	};
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+	sortOrder = function( a, b ) {
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+			return a.compareDocumentPosition ? -1 : 1;
+		}
+
+		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+	};
+
+} else {
+	sortOrder = function( a, b ) {
+		// The nodes are identical, we can exit early
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+
+		// Fallback to using sourceIndex (in IE) if it's available on both nodes
+		} else if ( a.sourceIndex && b.sourceIndex ) {
+			return a.sourceIndex - b.sourceIndex;
+		}
+
+		var al, bl,
+			ap = [],
+			bp = [],
+			aup = a.parentNode,
+			bup = b.parentNode,
+			cur = aup;
+
+		// If the nodes are siblings (or identical) we can do a quick check
+		if ( aup === bup ) {
+			return siblingCheck( a, b );
+
+		// If no parents were found then the nodes are disconnected
+		} else if ( !aup ) {
+			return -1;
+
+		} else if ( !bup ) {
+			return 1;
+		}
+
+		// Otherwise they're somewhere else in the tree so we need
+		// to build up a full list of the parentNodes for comparison
+		while ( cur ) {
+			ap.unshift( cur );
+			cur = cur.parentNode;
+		}
+
+		cur = bup;
+
+		while ( cur ) {
+			bp.unshift( cur );
+			cur = cur.parentNode;
+		}
+
+		al = ap.length;
+		bl = bp.length;
+
+		// Start walking down the tree looking for a discrepancy
+		for ( var i = 0; i < al && i < bl; i++ ) {
+			if ( ap[i] !== bp[i] ) {
+				return siblingCheck( ap[i], bp[i] );
+			}
+		}
+
+		// We ended someplace up the tree so do a sibling check
+		return i === al ?
+			siblingCheck( a, bp[i], -1 ) :
+			siblingCheck( ap[i], b, 1 );
+	};
+
+	siblingCheck = function( a, b, ret ) {
+		if ( a === b ) {
+			return ret;
+		}
+
+		var cur = a.nextSibling;
+
+		while ( cur ) {
+			if ( cur === b ) {
+				return -1;
+			}
+
+			cur = cur.nextSibling;
+		}
+
+		return 1;
+	};
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+	// We're going to inject a fake input element with a specified name
+	var form = document.createElement("div"),
+		id = "script" + (new Date()).getTime(),
+		root = document.documentElement;
+
+	form.innerHTML = "<a name='" + id + "'/>";
+
+	// Inject it into the root element, check its status, and remove it quickly
+	root.insertBefore( form, root.firstChild );
+
+	// The workaround has to do additional checks after a getElementById
+	// Which slows things down for other browsers (hence the branching)
+	if ( document.getElementById( id ) ) {
+		Expr.find.ID = function( match, context, isXML ) {
+			if ( typeof context.getElementById !== "undefined" && !isXML ) {
+				var m = context.getElementById(match[1]);
+
+				return m ?
+					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+						[m] :
+						undefined :
+					[];
+			}
+		};
+
+		Expr.filter.ID = function( elem, match ) {
+			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+			return elem.nodeType === 1 && node && node.nodeValue === match;
+		};
+	}
+
+	root.removeChild( form );
+
+	// release memory in IE
+	root = form = null;
+})();
+
+(function(){
+	// Check to see if the browser returns only elements
+	// when doing getElementsByTagName("*")
+
+	// Create a fake element
+	var div = document.createElement("div");
+	div.appendChild( document.createComment("") );
+
+	// Make sure no comments are found
+	if ( div.getElementsByTagName("*").length > 0 ) {
+		Expr.find.TAG = function( match, context ) {
+			var results = context.getElementsByTagName( match[1] );
+
+			// Filter out possible comments
+			if ( match[1] === "*" ) {
+				var tmp = [];
+
+				for ( var i = 0; results[i]; i++ ) {
+					if ( results[i].nodeType === 1 ) {
+						tmp.push( results[i] );
+					}
+				}
+
+				results = tmp;
+			}
+
+			return results;
+		};
+	}
+
+	// Check to see if an attribute returns normalized href attributes
+	div.innerHTML = "<a href='#'></a>";
+
+	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+			div.firstChild.getAttribute("href") !== "#" ) {
+
+		Expr.attrHandle.href = function( elem ) {
+			return elem.getAttribute( "href", 2 );
+		};
+	}
+
+	// release memory in IE
+	div = null;
+})();
+
+if ( document.querySelectorAll ) {
+	(function(){
+		var oldSizzle = Sizzle,
+			div = document.createElement("div"),
+			id = "__sizzle__";
+
+		div.innerHTML = "<p class='TEST'></p>";
+
+		// Safari can't handle uppercase or unicode characters when
+		// in quirks mode.
+		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+			return;
+		}
+
+		Sizzle = function( query, context, extra, seed ) {
+			context = context || document;
+
+			// Only use querySelectorAll on non-XML documents
+			// (ID selectors don't work in non-HTML documents)
+			if ( !seed && !Sizzle.isXML(context) ) {
+				// See if we find a selector to speed up
+				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+
+				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+					// Speed-up: Sizzle("TAG")
+					if ( match[1] ) {
+						return makeArray( context.getElementsByTagName( query ), extra );
+
+					// Speed-up: Sizzle(".CLASS")
+					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+						return makeArray( context.getElementsByClassName( match[2] ), extra );
+					}
+				}
+
+				if ( context.nodeType === 9 ) {
+					// Speed-up: Sizzle("body")
+					// The body element only exists once, optimize finding it
+					if ( query === "body" && context.body ) {
+						return makeArray( [ context.body ], extra );
+
+					// Speed-up: Sizzle("#ID")
+					} else if ( match && match[3] ) {
+						var elem = context.getElementById( match[3] );
+
+						// Check parentNode to catch when Blackberry 4.6 returns
+						// nodes that are no longer in the document #6963
+						if ( elem && elem.parentNode ) {
+							// Handle the case where IE and Opera return items
+							// by name instead of ID
+							if ( elem.id === match[3] ) {
+								return makeArray( [ elem ], extra );
+							}
+
+						} else {
+							return makeArray( [], extra );
+						}
+					}
+
+					try {
+						return makeArray( context.querySelectorAll(query), extra );
+					} catch(qsaError) {}
+
+				// qSA works strangely on Element-rooted queries
+				// We can work around this by specifying an extra ID on the root
+				// and working up from there (Thanks to Andrew Dupont for the technique)
+				// IE 8 doesn't work on object elements
+				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+					var oldContext = context,
+						old = context.getAttribute( "id" ),
+						nid = old || id,
+						hasParent = context.parentNode,
+						relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+					if ( !old ) {
+						context.setAttribute( "id", nid );
+					} else {
+						nid = nid.replace( /'/g, "\\$&" );
+					}
+					if ( relativeHierarchySelector && hasParent ) {
+						context = context.parentNode;
+					}
+
+					try {
+						if ( !relativeHierarchySelector || hasParent ) {
+							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+						}
+
+					} catch(pseudoError) {
+					} finally {
+						if ( !old ) {
+							oldContext.removeAttribute( "id" );
+						}
+					}
+				}
+			}
+
+			return oldSizzle(query, context, extra, seed);
+		};
+
+		for ( var prop in oldSizzle ) {
+			Sizzle[ prop ] = oldSizzle[ prop ];
+		}
+
+		// release memory in IE
+		div = null;
+	})();
+}
+
+(function(){
+	var html = document.documentElement,
+		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
+
+	if ( matches ) {
+		// Check to see if it's possible to do matchesSelector
+		// on a disconnected node (IE 9 fails this)
+		var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
+			pseudoWorks = false;
+
+		try {
+			// This should fail with an exception
+			// Gecko does not error, returns false instead
+			matches.call( document.documentElement, "[test!='']:sizzle" );
+
+		} catch( pseudoError ) {
+			pseudoWorks = true;
+		}
+
+		Sizzle.matchesSelector = function( node, expr ) {
+			// Make sure that attribute selectors are quoted
+			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+			if ( !Sizzle.isXML( node ) ) {
+				try {
+					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+						var ret = matches.call( node, expr );
+
+						// IE 9's matchesSelector returns false on disconnected nodes
+						if ( ret || !disconnectedMatch ||
+								// As well, disconnected nodes are said to be in a document
+								// fragment in IE 9, so check for that
+								node.document && node.document.nodeType !== 11 ) {
+							return ret;
+						}
+					}
+				} catch(e) {}
+			}
+
+			return Sizzle(expr, null, null, [node]).length > 0;
+		};
+	}
+})();
+
+(function(){
+	var div = document.createElement("div");
+
+	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+	// Opera can't find a second classname (in 9.6)
+	// Also, make sure that getElementsByClassName actually exists
+	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+		return;
+	}
+
+	// Safari caches class attributes, doesn't catch changes (in 3.2)
+	div.lastChild.className = "e";
+
+	if ( div.getElementsByClassName("e").length === 1 ) {
+		return;
+	}
+
+	Expr.order.splice(1, 0, "CLASS");
+	Expr.find.CLASS = function( match, context, isXML ) {
+		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+			return context.getElementsByClassName(match[1]);
+		}
+	};
+
+	// release memory in IE
+	div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+
+		if ( elem ) {
+			var match = false;
+
+			elem = elem[dir];
+
+			while ( elem ) {
+				if ( elem[ expando ] === doneName ) {
+					match = checkSet[elem.sizset];
+					break;
+				}
+
+				if ( elem.nodeType === 1 && !isXML ){
+					elem[ expando ] = doneName;
+					elem.sizset = i;
+				}
+
+				if ( elem.nodeName.toLowerCase() === cur ) {
+					match = elem;
+					break;
+				}
+
+				elem = elem[dir];
+			}
+
+			checkSet[i] = match;
+		}
+	}
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+
+		if ( elem ) {
+			var match = false;
+
+			elem = elem[dir];
+
+			while ( elem ) {
+				if ( elem[ expando ] === doneName ) {
+					match = checkSet[elem.sizset];
+					break;
+				}
+
+				if ( elem.nodeType === 1 ) {
+					if ( !isXML ) {
+						elem[ expando ] = doneName;
+						elem.sizset = i;
+					}
+
+					if ( typeof cur !== "string" ) {
+						if ( elem === cur ) {
+							match = true;
+							break;
+						}
+
+					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+						match = elem;
+						break;
+					}
+				}
+
+				elem = elem[dir];
+			}
+
+			checkSet[i] = match;
+		}
+	}
+}
+
+if ( document.documentElement.contains ) {
+	Sizzle.contains = function( a, b ) {
+		return a !== b && (a.contains ? a.contains(b) : true);
+	};
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+	Sizzle.contains = function( a, b ) {
+		return !!(a.compareDocumentPosition(b) & 16);
+	};
+
+} else {
+	Sizzle.contains = function() {
+		return false;
+	};
+}
+
+Sizzle.isXML = function( elem ) {
+	// documentElement is verified for cases where it doesn't yet exist
+	// (such as loading iframes in IE - #4833)
+	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+	return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context, seed ) {
+	var match,
+		tmpSet = [],
+		later = "",
+		root = context.nodeType ? [context] : context;
+
+	// Position selectors must be done after the filter
+	// And so must :not(positional) so we move all PSEUDOs to the end
+	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+		later += match[0];
+		selector = selector.replace( Expr.match.PSEUDO, "" );
+	}
+
+	selector = Expr.relative[selector] ? selector + "*" : selector;
+
+	for ( var i = 0, l = root.length; i < l; i++ ) {
+		Sizzle( selector, root[i], tmpSet, seed );
+	}
+
+	return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+Sizzle.selectors.attrMap = {};
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+	rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+	// Note: This RegExp should be improved, or likely pulled from Sizzle
+	rmultiselector = /,/,
+	isSimple = /^.[^:#\[\.,]*$/,
+	slice = Array.prototype.slice,
+	POS = jQuery.expr.match.globalPOS,
+	// methods guaranteed to produce a unique set when starting from a unique set
+	guaranteedUnique = {
+		children: true,
+		contents: true,
+		next: true,
+		prev: true
+	};
+
+jQuery.fn.extend({
+	find: function( selector ) {
+		var self = this,
+			i, l;
+
+		if ( typeof selector !== "string" ) {
+			return jQuery( selector ).filter(function() {
+				for ( i = 0, l = self.length; i < l; i++ ) {
+					if ( jQuery.contains( self[ i ], this ) ) {
+						return true;
+					}
+				}
+			});
+		}
+
+		var ret = this.pushStack( "", "find", selector ),
+			length, n, r;
+
+		for ( i = 0, l = this.length; i < l; i++ ) {
+			length = ret.length;
+			jQuery.find( selector, this[i], ret );
+
+			if ( i > 0 ) {
+				// Make sure that the results are unique
+				for ( n = length; n < ret.length; n++ ) {
+					for ( r = 0; r < length; r++ ) {
+						if ( ret[r] === ret[n] ) {
+							ret.splice(n--, 1);
+							break;
+						}
+					}
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	has: function( target ) {
+		var targets = jQuery( target );
+		return this.filter(function() {
+			for ( var i = 0, l = targets.length; i < l; i++ ) {
+				if ( jQuery.contains( this, targets[i] ) ) {
+					return true;
+				}
+			}
+		});
+	},
+
+	not: function( selector ) {
+		return this.pushStack( winnow(this, selector, false), "not", selector);
+	},
+
+	filter: function( selector ) {
+		return this.pushStack( winnow(this, selector, true), "filter", selector );
+	},
+
+	is: function( selector ) {
+		return !!selector && (
+			typeof selector === "string" ?
+				// If this is a positional selector, check membership in the returned set
+				// so $("p:first").is("p:last") won't return true for a doc with two "p".
+				POS.test( selector ) ?
+					jQuery( selector, this.context ).index( this[0] ) >= 0 :
+					jQuery.filter( selector, this ).length > 0 :
+				this.filter( selector ).length > 0 );
+	},
+
+	closest: function( selectors, context ) {
+		var ret = [], i, l, cur = this[0];
+
+		// Array (deprecated as of jQuery 1.7)
+		if ( jQuery.isArray( selectors ) ) {
+			var level = 1;
+
+			while ( cur && cur.ownerDocument && cur !== context ) {
+				for ( i = 0; i < selectors.length; i++ ) {
+
+					if ( jQuery( cur ).is( selectors[ i ] ) ) {
+						ret.push({ selector: selectors[ i ], elem: cur, level: level });
+					}
+				}
+
+				cur = cur.parentNode;
+				level++;
+			}
+
+			return ret;
+		}
+
+		// String
+		var pos = POS.test( selectors ) || typeof selectors !== "string" ?
+				jQuery( selectors, context || this.context ) :
+				0;
+
+		for ( i = 0, l = this.length; i < l; i++ ) {
+			cur = this[i];
+
+			while ( cur ) {
+				if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+					ret.push( cur );
+					break;
+
+				} else {
+					cur = cur.parentNode;
+					if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
+						break;
+					}
+				}
+			}
+		}
+
+		ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+		return this.pushStack( ret, "closest", selectors );
+	},
+
+	// Determine the position of an element within
+	// the matched set of elements
+	index: function( elem ) {
+
+		// No argument, return index in parent
+		if ( !elem ) {
+			return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+		}
+
+		// index in selector
+		if ( typeof elem === "string" ) {
+			return jQuery.inArray( this[0], jQuery( elem ) );
+		}
+
+		// Locate the position of the desired element
+		return jQuery.inArray(
+			// If it receives a jQuery object, the first element is used
+			elem.jquery ? elem[0] : elem, this );
+	},
+
+	add: function( selector, context ) {
+		var set = typeof selector === "string" ?
+				jQuery( selector, context ) :
+				jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+			all = jQuery.merge( this.get(), set );
+
+		return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+			all :
+			jQuery.unique( all ) );
+	},
+
+	andSelf: function() {
+		return this.add( this.prevObject );
+	}
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+	return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+	parent: function( elem ) {
+		var parent = elem.parentNode;
+		return parent && parent.nodeType !== 11 ? parent : null;
+	},
+	parents: function( elem ) {
+		return jQuery.dir( elem, "parentNode" );
+	},
+	parentsUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "parentNode", until );
+	},
+	next: function( elem ) {
+		return jQuery.nth( elem, 2, "nextSibling" );
+	},
+	prev: function( elem ) {
+		return jQuery.nth( elem, 2, "previousSibling" );
+	},
+	nextAll: function( elem ) {
+		return jQuery.dir( elem, "nextSibling" );
+	},
+	prevAll: function( elem ) {
+		return jQuery.dir( elem, "previousSibling" );
+	},
+	nextUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "nextSibling", until );
+	},
+	prevUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "previousSibling", until );
+	},
+	siblings: function( elem ) {
+		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+	},
+	children: function( elem ) {
+		return jQuery.sibling( elem.firstChild );
+	},
+	contents: function( elem ) {
+		return jQuery.nodeName( elem, "iframe" ) ?
+			elem.contentDocument || elem.contentWindow.document :
+			jQuery.makeArray( elem.childNodes );
+	}
+}, function( name, fn ) {
+	jQuery.fn[ name ] = function( until, selector ) {
+		var ret = jQuery.map( this, fn, until );
+
+		if ( !runtil.test( name ) ) {
+			selector = until;
+		}
+
+		if ( selector && typeof selector === "string" ) {
+			ret = jQuery.filter( selector, ret );
+		}
+
+		ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+		if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+			ret = ret.reverse();
+		}
+
+		return this.pushStack( ret, name, slice.call( arguments ).join(",") );
+	};
+});
+
+jQuery.extend({
+	filter: function( expr, elems, not ) {
+		if ( not ) {
+			expr = ":not(" + expr + ")";
+		}
+
+		return elems.length === 1 ?
+			jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+			jQuery.find.matches(expr, elems);
+	},
+
+	dir: function( elem, dir, until ) {
+		var matched = [],
+			cur = elem[ dir ];
+
+		while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+			if ( cur.nodeType === 1 ) {
+				matched.push( cur );
+			}
+			cur = cur[dir];
+		}
+		return matched;
+	},
+
+	nth: function( cur, result, dir, elem ) {
+		result = result || 1;
+		var num = 0;
+
+		for ( ; cur; cur = cur[dir] ) {
+			if ( cur.nodeType === 1 && ++num === result ) {
+				break;
+			}
+		}
+
+		return cur;
+	},
+
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType === 1 && n !== elem ) {
+				r.push( n );
+			}
+		}
+
+		return r;
+	}
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+	// Can't pass null or undefined to indexOf in Firefox 4
+	// Set to 0 to skip string check
+	qualifier = qualifier || 0;
+
+	if ( jQuery.isFunction( qualifier ) ) {
+		return jQuery.grep(elements, function( elem, i ) {
+			var retVal = !!qualifier.call( elem, i, elem );
+			return retVal === keep;
+		});
+
+	} else if ( qualifier.nodeType ) {
+		return jQuery.grep(elements, function( elem, i ) {
+			return ( elem === qualifier ) === keep;
+		});
+
+	} else if ( typeof qualifier === "string" ) {
+		var filtered = jQuery.grep(elements, function( elem ) {
+			return elem.nodeType === 1;
+		});
+
+		if ( isSimple.test( qualifier ) ) {
+			return jQuery.filter(qualifier, filtered, !keep);
+		} else {
+			qualifier = jQuery.filter( qualifier, filtered );
+		}
+	}
+
+	return jQuery.grep(elements, function( elem, i ) {
+		return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+	});
+}
+
+
+
+
+function createSafeFragment( document ) {
+	var list = nodeNames.split( "|" ),
+	safeFrag = document.createDocumentFragment();
+
+	if ( safeFrag.createElement ) {
+		while ( list.length ) {
+			safeFrag.createElement(
+				list.pop()
+			);
+		}
+	}
+	return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+		"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+	rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+	rleadingWhitespace = /^\s+/,
+	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+	rtagName = /<([\w:]+)/,
+	rtbody = /<tbody/i,
+	rhtml = /<|&#?\w+;/,
+	rnoInnerhtml = /<(?:script|style)/i,
+	rnocache = /<(?:script|object|embed|option|style)/i,
+	rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rscriptType = /\/(java|ecma)script/i,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
+	wrapMap = {
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+		legend: [ 1, "<fieldset>", "</fieldset>" ],
+		thead: [ 1, "<table>", "</table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+		area: [ 1, "<map>", "</map>" ],
+		_default: [ 0, "", "" ]
+	},
+	safeFragment = createSafeFragment( document );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+	wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+	text: function( value ) {
+		return jQuery.access( this, function( value ) {
+			return value === undefined ?
+				jQuery.text( this ) :
+				this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+		}, null, value, arguments.length );
+	},
+
+	wrapAll: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapAll( html.call(this, i) );
+			});
+		}
+
+		if ( this[0] ) {
+			// The elements to wrap the target around
+			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+			if ( this[0].parentNode ) {
+				wrap.insertBefore( this[0] );
+			}
+
+			wrap.map(function() {
+				var elem = this;
+
+				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+					elem = elem.firstChild;
+				}
+
+				return elem;
+			}).append( this );
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapInner( html.call(this, i) );
+			});
+		}
+
+		return this.each(function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		});
+	},
+
+	wrap: function( html ) {
+		var isFunction = jQuery.isFunction( html );
+
+		return this.each(function(i) {
+			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+		});
+	},
+
+	unwrap: function() {
+		return this.parent().each(function() {
+			if ( !jQuery.nodeName( this, "body" ) ) {
+				jQuery( this ).replaceWith( this.childNodes );
+			}
+		}).end();
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 ) {
+				this.appendChild( elem );
+			}
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 ) {
+				this.insertBefore( elem, this.firstChild );
+			}
+		});
+	},
+
+	before: function() {
+		if ( this[0] && this[0].parentNode ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this );
+			});
+		} else if ( arguments.length ) {
+			var set = jQuery.clean( arguments );
+			set.push.apply( set, this.toArray() );
+			return this.pushStack( set, "before", arguments );
+		}
+	},
+
+	after: function() {
+		if ( this[0] && this[0].parentNode ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			});
+		} else if ( arguments.length ) {
+			var set = this.pushStack( this, "after", arguments );
+			set.push.apply( set, jQuery.clean(arguments) );
+			return set;
+		}
+	},
+
+	// keepData is for internal use only--do not document
+	remove: function( selector, keepData ) {
+		for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+			if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+				if ( !keepData && elem.nodeType === 1 ) {
+					jQuery.cleanData( elem.getElementsByTagName("*") );
+					jQuery.cleanData( [ elem ] );
+				}
+
+				if ( elem.parentNode ) {
+					elem.parentNode.removeChild( elem );
+				}
+			}
+		}
+
+		return this;
+	},
+
+	empty: function() {
+		for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+			// Remove element nodes and prevent memory leaks
+			if ( elem.nodeType === 1 ) {
+				jQuery.cleanData( elem.getElementsByTagName("*") );
+			}
+
+			// Remove any remaining nodes
+			while ( elem.firstChild ) {
+				elem.removeChild( elem.firstChild );
+			}
+		}
+
+		return this;
+	},
+
+	clone: function( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map( function () {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		});
+	},
+
+	html: function( value ) {
+		return jQuery.access( this, function( value ) {
+			var elem = this[0] || {},
+				i = 0,
+				l = this.length;
+
+			if ( value === undefined ) {
+				return elem.nodeType === 1 ?
+					elem.innerHTML.replace( rinlinejQuery, "" ) :
+					null;
+			}
+
+
+			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+				( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+				!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+				value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+				try {
+					for (; i < l; i++ ) {
+						// Remove element nodes and prevent memory leaks
+						elem = this[i] || {};
+						if ( elem.nodeType === 1 ) {
+							jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+							elem.innerHTML = value;
+						}
+					}
+
+					elem = 0;
+
+				// If using innerHTML throws an exception, use the fallback method
+				} catch(e) {}
+			}
+
+			if ( elem ) {
+				this.empty().append( value );
+			}
+		}, null, value, arguments.length );
+	},
+
+	replaceWith: function( value ) {
+		if ( this[0] && this[0].parentNode ) {
+			// Make sure that the elements are removed from the DOM before they are inserted
+			// this can help fix replacing a parent with child elements
+			if ( jQuery.isFunction( value ) ) {
+				return this.each(function(i) {
+					var self = jQuery(this), old = self.html();
+					self.replaceWith( value.call( this, i, old ) );
+				});
+			}
+
+			if ( typeof value !== "string" ) {
+				value = jQuery( value ).detach();
+			}
+
+			return this.each(function() {
+				var next = this.nextSibling,
+					parent = this.parentNode;
+
+				jQuery( this ).remove();
+
+				if ( next ) {
+					jQuery(next).before( value );
+				} else {
+					jQuery(parent).append( value );
+				}
+			});
+		} else {
+			return this.length ?
+				this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+				this;
+		}
+	},
+
+	detach: function( selector ) {
+		return this.remove( selector, true );
+	},
+
+	domManip: function( args, table, callback ) {
+		var results, first, fragment, parent,
+			value = args[0],
+			scripts = [];
+
+		// We can't cloneNode fragments that contain checked, in WebKit
+		if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+			return this.each(function() {
+				jQuery(this).domManip( args, table, callback, true );
+			});
+		}
+
+		if ( jQuery.isFunction(value) ) {
+			return this.each(function(i) {
+				var self = jQuery(this);
+				args[0] = value.call(this, i, table ? self.html() : undefined);
+				self.domManip( args, table, callback );
+			});
+		}
+
+		if ( this[0] ) {
+			parent = value && value.parentNode;
+
+			// If we're in a fragment, just use that instead of building a new one
+			if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+				results = { fragment: parent };
+
+			} else {
+				results = jQuery.buildFragment( args, this, scripts );
+			}
+
+			fragment = results.fragment;
+
+			if ( fragment.childNodes.length === 1 ) {
+				first = fragment = fragment.firstChild;
+			} else {
+				first = fragment.firstChild;
+			}
+
+			if ( first ) {
+				table = table && jQuery.nodeName( first, "tr" );
+
+				for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+					callback.call(
+						table ?
+							root(this[i], first) :
+							this[i],
+						// Make sure that we do not leak memory by inadvertently discarding
+						// the original fragment (which might have attached data) instead of
+						// using it; in addition, use the original fragment object for the last
+						// item instead of first because it can end up being emptied incorrectly
+						// in certain situations (Bug #8070).
+						// Fragments from the fragment cache must always be cloned and never used
+						// in place.
+						results.cacheable || ( l > 1 && i < lastIndex ) ?
+							jQuery.clone( fragment, true, true ) :
+							fragment
+					);
+				}
+			}
+
+			if ( scripts.length ) {
+				jQuery.each( scripts, function( i, elem ) {
+					if ( elem.src ) {
+						jQuery.ajax({
+							type: "GET",
+							global: false,
+							url: elem.src,
+							async: false,
+							dataType: "script"
+						});
+					} else {
+						jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
+					}
+
+					if ( elem.parentNode ) {
+						elem.parentNode.removeChild( elem );
+					}
+				});
+			}
+		}
+
+		return this;
+	}
+});
+
+function root( elem, cur ) {
+	return jQuery.nodeName(elem, "table") ?
+		(elem.getElementsByTagName("tbody")[0] ||
+		elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+		elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+		return;
+	}
+
+	var type, i, l,
+		oldData = jQuery._data( src ),
+		curData = jQuery._data( dest, oldData ),
+		events = oldData.events;
+
+	if ( events ) {
+		delete curData.handle;
+		curData.events = {};
+
+		for ( type in events ) {
+			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+				jQuery.event.add( dest, type, events[ type ][ i ] );
+			}
+		}
+	}
+
+	// make the cloned public data object a copy from the original
+	if ( curData.data ) {
+		curData.data = jQuery.extend( {}, curData.data );
+	}
+}
+
+function cloneFixAttributes( src, dest ) {
+	var nodeName;
+
+	// We do not need to do anything for non-Elements
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	// clearAttributes removes the attributes, which we don't want,
+	// but also removes the attachEvent events, which we *do* want
+	if ( dest.clearAttributes ) {
+		dest.clearAttributes();
+	}
+
+	// mergeAttributes, in contrast, only merges back on the
+	// original attributes, not the events
+	if ( dest.mergeAttributes ) {
+		dest.mergeAttributes( src );
+	}
+
+	nodeName = dest.nodeName.toLowerCase();
+
+	// IE6-8 fail to clone children inside object elements that use
+	// the proprietary classid attribute value (rather than the type
+	// attribute) to identify the type of content to display
+	if ( nodeName === "object" ) {
+		dest.outerHTML = src.outerHTML;
+
+	} else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+		// IE6-8 fails to persist the checked state of a cloned checkbox
+		// or radio button. Worse, IE6-7 fail to give the cloned element
+		// a checked appearance if the defaultChecked value isn't also set
+		if ( src.checked ) {
+			dest.defaultChecked = dest.checked = src.checked;
+		}
+
+		// IE6-7 get confused and end up setting the value of a cloned
+		// checkbox/radio button to an empty string instead of "on"
+		if ( dest.value !== src.value ) {
+			dest.value = src.value;
+		}
+
+	// IE6-8 fails to return the selected option to the default selected
+	// state when cloning options
+	} else if ( nodeName === "option" ) {
+		dest.selected = src.defaultSelected;
+
+	// IE6-8 fails to set the defaultValue to the correct value when
+	// cloning other types of input fields
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+
+	// IE blanks contents when cloning scripts
+	} else if ( nodeName === "script" && dest.text !== src.text ) {
+		dest.text = src.text;
+	}
+
+	// Event data gets referenced instead of copied if the expando
+	// gets copied too
+	dest.removeAttribute( jQuery.expando );
+
+	// Clear flags for bubbling special change/submit events, they must
+	// be reattached when the newly cloned events are first activated
+	dest.removeAttribute( "_submit_attached" );
+	dest.removeAttribute( "_change_attached" );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+	var fragment, cacheable, cacheresults, doc,
+	first = args[ 0 ];
+
+	// nodes may contain either an explicit document object,
+	// a jQuery collection or context object.
+	// If nodes[0] contains a valid object to assign to doc
+	if ( nodes && nodes[0] ) {
+		doc = nodes[0].ownerDocument || nodes[0];
+	}
+
+	// Ensure that an attr object doesn't incorrectly stand in as a document object
+	// Chrome and Firefox seem to allow this to occur and will throw exception
+	// Fixes #8950
+	if ( !doc.createDocumentFragment ) {
+		doc = document;
+	}
+
+	// Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+	// Cloning options loses the selected state, so don't cache them
+	// IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+	// Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+	// Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+	if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
+		first.charAt(0) === "<" && !rnocache.test( first ) &&
+		(jQuery.support.checkClone || !rchecked.test( first )) &&
+		(jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+		cacheable = true;
+
+		cacheresults = jQuery.fragments[ first ];
+		if ( cacheresults && cacheresults !== 1 ) {
+			fragment = cacheresults;
+		}
+	}
+
+	if ( !fragment ) {
+		fragment = doc.createDocumentFragment();
+		jQuery.clean( args, doc, fragment, scripts );
+	}
+
+	if ( cacheable ) {
+		jQuery.fragments[ first ] = cacheresults ? fragment : 1;
+	}
+
+	return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function( name, original ) {
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = [],
+			insert = jQuery( selector ),
+			parent = this.length === 1 && this[0].parentNode;
+
+		if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+			insert[ original ]( this[0] );
+			return this;
+
+		} else {
+			for ( var i = 0, l = insert.length; i < l; i++ ) {
+				var elems = ( i > 0 ? this.clone(true) : this ).get();
+				jQuery( insert[i] )[ original ]( elems );
+				ret = ret.concat( elems );
+			}
+
+			return this.pushStack( ret, name, insert.selector );
+		}
+	};
+});
+
+function getAll( elem ) {
+	if ( typeof elem.getElementsByTagName !== "undefined" ) {
+		return elem.getElementsByTagName( "*" );
+
+	} else if ( typeof elem.querySelectorAll !== "undefined" ) {
+		return elem.querySelectorAll( "*" );
+
+	} else {
+		return [];
+	}
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+	if ( elem.type === "checkbox" || elem.type === "radio" ) {
+		elem.defaultChecked = elem.checked;
+	}
+}
+// Finds all inputs and passes them to fixDefaultChecked
+function findInputs( elem ) {
+	var nodeName = ( elem.nodeName || "" ).toLowerCase();
+	if ( nodeName === "input" ) {
+		fixDefaultChecked( elem );
+	// Skip scripts, get other children
+	} else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) {
+		jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+	}
+}
+
+// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js
+function shimCloneNode( elem ) {
+	var div = document.createElement( "div" );
+	safeFragment.appendChild( div );
+
+	div.innerHTML = elem.outerHTML;
+	return div.firstChild;
+}
+
+jQuery.extend({
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var srcElements,
+			destElements,
+			i,
+			// IE<=8 does not properly clone detached, unknown element nodes
+			clone = jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ?
+				elem.cloneNode( true ) :
+				shimCloneNode( elem );
+
+		if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+			// IE copies events bound via attachEvent when using cloneNode.
+			// Calling detachEvent on the clone will also remove the events
+			// from the original. In order to get around this, we use some
+			// proprietary methods to clear the events. Thanks to MooTools
+			// guys for this hotness.
+
+			cloneFixAttributes( elem, clone );
+
+			// Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+			srcElements = getAll( elem );
+			destElements = getAll( clone );
+
+			// Weird iteration because IE will replace the length property
+			// with an element if you are cloning the body and one of the
+			// elements on the page has a name or id of "length"
+			for ( i = 0; srcElements[i]; ++i ) {
+				// Ensure that the destination node is not null; Fixes #9587
+				if ( destElements[i] ) {
+					cloneFixAttributes( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+			cloneCopyEvent( elem, clone );
+
+			if ( deepDataAndEvents ) {
+				srcElements = getAll( elem );
+				destElements = getAll( clone );
+
+				for ( i = 0; srcElements[i]; ++i ) {
+					cloneCopyEvent( srcElements[i], destElements[i] );
+				}
+			}
+		}
+
+		srcElements = destElements = null;
+
+		// Return the cloned set
+		return clone;
+	},
+
+	clean: function( elems, context, fragment, scripts ) {
+		var checkScriptType, script, j,
+				ret = [];
+
+		context = context || document;
+
+		// !context.createElement fails in IE with an error but returns typeof 'object'
+		if ( typeof context.createElement === "undefined" ) {
+			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+		}
+
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			if ( typeof elem === "number" ) {
+				elem += "";
+			}
+
+			if ( !elem ) {
+				continue;
+			}
+
+			// Convert html string into DOM nodes
+			if ( typeof elem === "string" ) {
+				if ( !rhtml.test( elem ) ) {
+					elem = context.createTextNode( elem );
+				} else {
+					// Fix "XHTML"-style tags in all browsers
+					elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+					// Trim whitespace, otherwise indexOf won't work as expected
+					var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),
+						wrap = wrapMap[ tag ] || wrapMap._default,
+						depth = wrap[0],
+						div = context.createElement("div"),
+						safeChildNodes = safeFragment.childNodes,
+						remove;
+
+					// Append wrapper element to unknown element safe doc fragment
+					if ( context === document ) {
+						// Use the fragment we've already created for this document
+						safeFragment.appendChild( div );
+					} else {
+						// Use a fragment created with the owner document
+						createSafeFragment( context ).appendChild( div );
+					}
+
+					// Go to html and back, then peel off extra wrappers
+					div.innerHTML = wrap[1] + elem + wrap[2];
+
+					// Move to the right depth
+					while ( depth-- ) {
+						div = div.lastChild;
+					}
+
+					// Remove IE's autoinserted <tbody> from table fragments
+					if ( !jQuery.support.tbody ) {
+
+						// String was a <table>, *may* have spurious <tbody>
+						var hasBody = rtbody.test(elem),
+							tbody = tag === "table" && !hasBody ?
+								div.firstChild && div.firstChild.childNodes :
+
+								// String was a bare <thead> or <tfoot>
+								wrap[1] === "<table>" && !hasBody ?
+									div.childNodes :
+									[];
+
+						for ( j = tbody.length - 1; j >= 0 ; --j ) {
+							if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+								tbody[ j ].parentNode.removeChild( tbody[ j ] );
+							}
+						}
+					}
+
+					// IE completely kills leading whitespace when innerHTML is used
+					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+						div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+					}
+
+					elem = div.childNodes;
+
+					// Clear elements from DocumentFragment (safeFragment or otherwise)
+					// to avoid hoarding elements. Fixes #11356
+					if ( div ) {
+						div.parentNode.removeChild( div );
+
+						// Guard against -1 index exceptions in FF3.6
+						if ( safeChildNodes.length > 0 ) {
+							remove = safeChildNodes[ safeChildNodes.length - 1 ];
+
+							if ( remove && remove.parentNode ) {
+								remove.parentNode.removeChild( remove );
+							}
+						}
+					}
+				}
+			}
+
+			// Resets defaultChecked for any radios and checkboxes
+			// about to be appended to the DOM in IE 6/7 (#8060)
+			var len;
+			if ( !jQuery.support.appendChecked ) {
+				if ( elem[0] && typeof (len = elem.length) === "number" ) {
+					for ( j = 0; j < len; j++ ) {
+						findInputs( elem[j] );
+					}
+				} else {
+					findInputs( elem );
+				}
+			}
+
+			if ( elem.nodeType ) {
+				ret.push( elem );
+			} else {
+				ret = jQuery.merge( ret, elem );
+			}
+		}
+
+		if ( fragment ) {
+			checkScriptType = function( elem ) {
+				return !elem.type || rscriptType.test( elem.type );
+			};
+			for ( i = 0; ret[i]; i++ ) {
+				script = ret[i];
+				if ( scripts && jQuery.nodeName( script, "script" ) && (!script.type || rscriptType.test( script.type )) ) {
+					scripts.push( script.parentNode ? script.parentNode.removeChild( script ) : script );
+
+				} else {
+					if ( script.nodeType === 1 ) {
+						var jsTags = jQuery.grep( script.getElementsByTagName( "script" ), checkScriptType );
+
+						ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+					}
+					fragment.appendChild( script );
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	cleanData: function( elems ) {
+		var data, id,
+			cache = jQuery.cache,
+			special = jQuery.event.special,
+			deleteExpando = jQuery.support.deleteExpando;
+
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+				continue;
+			}
+
+			id = elem[ jQuery.expando ];
+
+			if ( id ) {
+				data = cache[ id ];
+
+				if ( data && data.events ) {
+					for ( var type in data.events ) {
+						if ( special[ type ] ) {
+							jQuery.event.remove( elem, type );
+
+						// This is a shortcut to avoid jQuery.event.remove's overhead
+						} else {
+							jQuery.removeEvent( elem, type, data.handle );
+						}
+					}
+
+					// Null the DOM reference to avoid IE6/7/8 leak (#7054)
+					if ( data.handle ) {
+						data.handle.elem = null;
+					}
+				}
+
+				if ( deleteExpando ) {
+					delete elem[ jQuery.expando ];
+
+				} else if ( elem.removeAttribute ) {
+					elem.removeAttribute( jQuery.expando );
+				}
+
+				delete cache[ id ];
+			}
+		}
+	}
+});
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+	ropacity = /opacity=([^)]*)/,
+	// fixed for IE9, see #8346
+	rupper = /([A-Z]|^ms)/g,
+	rnum = /^[\-+]?(?:\d*\.)?\d+$/i,
+	rnumnonpx = /^-?(?:\d*\.)?\d+(?!px)[^\d\s]+$/i,
+	rrelNum = /^([\-+])=([\-+.\de]+)/,
+	rmargin = /^margin/,
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+
+	// order is important!
+	cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+
+	curCSS,
+
+	getComputedStyle,
+	currentStyle;
+
+jQuery.fn.css = function( name, value ) {
+	return jQuery.access( this, function( elem, name, value ) {
+		return value !== undefined ?
+			jQuery.style( elem, name, value ) :
+			jQuery.css( elem, name );
+	}, name, value, arguments.length > 1 );
+};
+
+jQuery.extend({
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity" );
+					return ret === "" ? "1" : ret;
+
+				} else {
+					return elem.style.opacity;
+				}
+			}
+		}
+	},
+
+	// Exclude the following css properties to add px
+	cssNumber: {
+		"fillOpacity": true,
+		"fontWeight": true,
+		"lineHeight": true,
+		"opacity": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		// normalize float css property
+		"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+	},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, type, origName = jQuery.camelCase( name ),
+			style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+		name = jQuery.cssProps[ origName ] || origName;
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			type = typeof value;
+
+			// convert relative number strings (+= or -=) to relative numbers. #7345
+			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+				value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// Make sure that NaN and null values aren't set. See: #7116
+			if ( value == null || type === "number" && isNaN( value ) ) {
+				return;
+			}
+
+			// If a number was passed in, add 'px' to the (except for certain CSS properties)
+			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+				value += "px";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+				// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+				// Fixes bug #5509
+				try {
+					style[ name ] = value;
+				} catch(e) {}
+			}
+
+		} else {
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, extra ) {
+		var ret, hooks;
+
+		// Make sure that we're working with the right name
+		name = jQuery.camelCase( name );
+		hooks = jQuery.cssHooks[ name ];
+		name = jQuery.cssProps[ name ] || name;
+
+		// cssFloat needs a special treatment
+		if ( name === "cssFloat" ) {
+			name = "float";
+		}
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+			return ret;
+
+		// Otherwise, if a way to get the computed value exists, use that
+		} else if ( curCSS ) {
+			return curCSS( elem, name );
+		}
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var old = {},
+			ret, name;
+
+		// Remember the old values, and insert the new ones
+		for ( name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		ret = callback.call( elem );
+
+		// Revert the old values
+		for ( name in options ) {
+			elem.style[ name ] = old[ name ];
+		}
+
+		return ret;
+	}
+});
+
+// DEPRECATED in 1.3, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+	getComputedStyle = function( elem, name ) {
+		var ret, defaultView, computedStyle, width,
+			style = elem.style;
+
+		name = name.replace( rupper, "-$1" ).toLowerCase();
+
+		if ( (defaultView = elem.ownerDocument.defaultView) &&
+				(computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+
+			ret = computedStyle.getPropertyValue( name );
+			if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+				ret = jQuery.style( elem, name );
+			}
+		}
+
+		// A tribute to the "awesome hack by Dean Edwards"
+		// WebKit uses "computed value (percentage if specified)" instead of "used value" for margins
+		// which is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+		if ( !jQuery.support.pixelMargin && computedStyle && rmargin.test( name ) && rnumnonpx.test( ret ) ) {
+			width = style.width;
+			style.width = ret;
+			ret = computedStyle.width;
+			style.width = width;
+		}
+
+		return ret;
+	};
+}
+
+if ( document.documentElement.currentStyle ) {
+	currentStyle = function( elem, name ) {
+		var left, rsLeft, uncomputed,
+			ret = elem.currentStyle && elem.currentStyle[ name ],
+			style = elem.style;
+
+		// Avoid setting ret to empty string here
+		// so we don't default to auto
+		if ( ret == null && style && (uncomputed = style[ name ]) ) {
+			ret = uncomputed;
+		}
+
+		// From the awesome hack by Dean Edwards
+		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+		// If we're not dealing with a regular pixel number
+		// but a number that has a weird ending, we need to convert it to pixels
+		if ( rnumnonpx.test( ret ) ) {
+
+			// Remember the original values
+			left = style.left;
+			rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+			// Put in the new values to get a computed value out
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = elem.currentStyle.left;
+			}
+			style.left = name === "fontSize" ? "1em" : ret;
+			ret = style.pixelLeft + "px";
+
+			// Revert the changed values
+			style.left = left;
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = rsLeft;
+			}
+		}
+
+		return ret === "" ? "auto" : ret;
+	};
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWidthOrHeight( elem, name, extra ) {
+
+	// Start with offset property
+	var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+		i = name === "width" ? 1 : 0,
+		len = 4;
+
+	if ( val > 0 ) {
+		if ( extra !== "border" ) {
+			for ( ; i < len; i += 2 ) {
+				if ( !extra ) {
+					val -= parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
+				}
+				if ( extra === "margin" ) {
+					val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ] ) ) || 0;
+				} else {
+					val -= parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+				}
+			}
+		}
+
+		return val + "px";
+	}
+
+	// Fall back to computed then uncomputed css if necessary
+	val = curCSS( elem, name );
+	if ( val < 0 || val == null ) {
+		val = elem.style[ name ];
+	}
+
+	// Computed unit is not pixels. Stop here and return.
+	if ( rnumnonpx.test(val) ) {
+		return val;
+	}
+
+	// Normalize "", auto, and prepare for extra
+	val = parseFloat( val ) || 0;
+
+	// Add padding, border, margin
+	if ( extra ) {
+		for ( ; i < len; i += 2 ) {
+			val += parseFloat( jQuery.css( elem, "padding" + cssExpand[ i ] ) ) || 0;
+			if ( extra !== "padding" ) {
+				val += parseFloat( jQuery.css( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+			}
+			if ( extra === "margin" ) {
+				val += parseFloat( jQuery.css( elem, extra + cssExpand[ i ]) ) || 0;
+			}
+		}
+	}
+
+	return val + "px";
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+	jQuery.cssHooks[ name ] = {
+		get: function( elem, computed, extra ) {
+			if ( computed ) {
+				if ( elem.offsetWidth !== 0 ) {
+					return getWidthOrHeight( elem, name, extra );
+				} else {
+					return jQuery.swap( elem, cssShow, function() {
+						return getWidthOrHeight( elem, name, extra );
+					});
+				}
+			}
+		},
+
+		set: function( elem, value ) {
+			return rnum.test( value ) ?
+				value + "px" :
+				value;
+		}
+	};
+});
+
+if ( !jQuery.support.opacity ) {
+	jQuery.cssHooks.opacity = {
+		get: function( elem, computed ) {
+			// IE uses filters for opacity
+			return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+				( parseFloat( RegExp.$1 ) / 100 ) + "" :
+				computed ? "1" : "";
+		},
+
+		set: function( elem, value ) {
+			var style = elem.style,
+				currentStyle = elem.currentStyle,
+				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+				filter = currentStyle && currentStyle.filter || style.filter || "";
+
+			// IE has trouble with opacity if it does not have layout
+			// Force it by setting the zoom level
+			style.zoom = 1;
+
+			// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+			if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
+
+				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+				// if "filter:" is present at all, clearType is disabled, we want to avoid this
+				// style.removeAttribute is IE Only, but so apparently is this code path...
+				style.removeAttribute( "filter" );
+
+				// if there there is no filter style applied in a css rule, we are done
+				if ( currentStyle && !currentStyle.filter ) {
+					return;
+				}
+			}
+
+			// otherwise, set new filter values
+			style.filter = ralpha.test( filter ) ?
+				filter.replace( ralpha, opacity ) :
+				filter + " " + opacity;
+		}
+	};
+}
+
+jQuery(function() {
+	// This hook cannot be added until DOM ready because the support test
+	// for it is not run until after DOM ready
+	if ( !jQuery.support.reliableMarginRight ) {
+		jQuery.cssHooks.marginRight = {
+			get: function( elem, computed ) {
+				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+				// Work around by temporarily setting element display to inline-block
+				return jQuery.swap( elem, { "display": "inline-block" }, function() {
+					if ( computed ) {
+						return curCSS( elem, "margin-right" );
+					} else {
+						return elem.style.marginRight;
+					}
+				});
+			}
+		};
+	}
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.hidden = function( elem ) {
+		var width = elem.offsetWidth,
+			height = elem.offsetHeight;
+
+		return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
+	};
+
+	jQuery.expr.filters.visible = function( elem ) {
+		return !jQuery.expr.filters.hidden( elem );
+	};
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+	margin: "",
+	padding: "",
+	border: "Width"
+}, function( prefix, suffix ) {
+
+	jQuery.cssHooks[ prefix + suffix ] = {
+		expand: function( value ) {
+			var i,
+
+				// assumes a single number if not a string
+				parts = typeof value === "string" ? value.split(" ") : [ value ],
+				expanded = {};
+
+			for ( i = 0; i < 4; i++ ) {
+				expanded[ prefix + cssExpand[ i ] + suffix ] =
+					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+			}
+
+			return expanded;
+		}
+	};
+});
+
+
+
+
+var r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rhash = /#.*$/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+	rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+	rquery = /\?/,
+	rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+	rselectTextarea = /^(?:select|textarea)/i,
+	rspacesAjax = /\s+/,
+	rts = /([?&])_=[^&]*/,
+	rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
+
+	// Keep a copy of the old load method
+	_load = jQuery.fn.load,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Document location
+	ajaxLocation,
+
+	// Document location segments
+	ajaxLocParts,
+
+	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+	allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+	ajaxLocation = location.href;
+} catch( e ) {
+	// Use the href attribute of an A element
+	// since IE will modify it given document.location
+	ajaxLocation = document.createElement( "a" );
+	ajaxLocation.href = "";
+	ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		if ( jQuery.isFunction( func ) ) {
+			var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+				i = 0,
+				length = dataTypes.length,
+				dataType,
+				list,
+				placeBefore;
+
+			// For each dataType in the dataTypeExpression
+			for ( ; i < length; i++ ) {
+				dataType = dataTypes[ i ];
+				// We control if we're asked to add before
+				// any existing element
+				placeBefore = /^\+/.test( dataType );
+				if ( placeBefore ) {
+					dataType = dataType.substr( 1 ) || "*";
+				}
+				list = structure[ dataType ] = structure[ dataType ] || [];
+				// then we add to the structure accordingly
+				list[ placeBefore ? "unshift" : "push" ]( func );
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+		dataType /* internal */, inspected /* internal */ ) {
+
+	dataType = dataType || options.dataTypes[ 0 ];
+	inspected = inspected || {};
+
+	inspected[ dataType ] = true;
+
+	var list = structure[ dataType ],
+		i = 0,
+		length = list ? list.length : 0,
+		executeOnly = ( structure === prefilters ),
+		selection;
+
+	for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+		selection = list[ i ]( options, originalOptions, jqXHR );
+		// If we got redirected to another dataType
+		// we try there if executing only and not done already
+		if ( typeof selection === "string" ) {
+			if ( !executeOnly || inspected[ selection ] ) {
+				selection = undefined;
+			} else {
+				options.dataTypes.unshift( selection );
+				selection = inspectPrefiltersOrTransports(
+						structure, options, originalOptions, jqXHR, selection, inspected );
+			}
+		}
+	}
+	// If we're only executing or nothing was selected
+	// we try the catchall dataType if not done already
+	if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+		selection = inspectPrefiltersOrTransports(
+				structure, options, originalOptions, jqXHR, "*", inspected );
+	}
+	// unnecessary when only executing (prefilters)
+	// but it'll be ignored by the caller in that case
+	return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+	var key, deep,
+		flatOptions = jQuery.ajaxSettings.flatOptions || {};
+	for ( key in src ) {
+		if ( src[ key ] !== undefined ) {
+			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+		}
+	}
+	if ( deep ) {
+		jQuery.extend( true, target, deep );
+	}
+}
+
+jQuery.fn.extend({
+	load: function( url, params, callback ) {
+		if ( typeof url !== "string" && _load ) {
+			return _load.apply( this, arguments );
+
+		// Don't do a request if no elements are being requested
+		} else if ( !this.length ) {
+			return this;
+		}
+
+		var off = url.indexOf( " " );
+		if ( off >= 0 ) {
+			var selector = url.slice( off, url.length );
+			url = url.slice( 0, off );
+		}
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params ) {
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = undefined;
+
+			// Otherwise, build a param string
+			} else if ( typeof params === "object" ) {
+				params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+				type = "POST";
+			}
+		}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			dataType: "html",
+			data: params,
+			// Complete callback (responseText is used internally)
+			complete: function( jqXHR, status, responseText ) {
+				// Store the response as specified by the jqXHR object
+				responseText = jqXHR.responseText;
+				// If successful, inject the HTML into all the matched elements
+				if ( jqXHR.isResolved() ) {
+					// #4825: Get the actual response in case
+					// a dataFilter is present in ajaxSettings
+					jqXHR.done(function( r ) {
+						responseText = r;
+					});
+					// See if a selector was specified
+					self.html( selector ?
+						// Create a dummy div to hold the results
+						jQuery("<div>")
+							// inject the contents of the document in, removing the scripts
+							// to avoid any 'Permission Denied' errors in IE
+							.append(responseText.replace(rscript, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						responseText );
+				}
+
+				if ( callback ) {
+					self.each( callback, [ responseText, status, jqXHR ] );
+				}
+			}
+		});
+
+		return this;
+	},
+
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+
+	serializeArray: function() {
+		return this.map(function(){
+			return this.elements ? jQuery.makeArray( this.elements ) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled &&
+				( this.checked || rselectTextarea.test( this.nodeName ) ||
+					rinput.test( this.type ) );
+		})
+		.map(function( i, elem ){
+			var val = jQuery( this ).val();
+
+			return val == null ?
+				null :
+				jQuery.isArray( val ) ?
+					jQuery.map( val, function( val, i ){
+						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+					}) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		}).get();
+	}
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+	jQuery.fn[ o ] = function( f ){
+		return this.on( o, f );
+	};
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+		// shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		return jQuery.ajax({
+			type: method,
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	};
+});
+
+jQuery.extend({
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function( target, settings ) {
+		if ( settings ) {
+			// Building a settings object
+			ajaxExtend( target, jQuery.ajaxSettings );
+		} else {
+			// Extending ajaxSettings
+			settings = target;
+			target = jQuery.ajaxSettings;
+		}
+		ajaxExtend( target, settings );
+		return target;
+	},
+
+	ajaxSettings: {
+		url: ajaxLocation,
+		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+		global: true,
+		type: "GET",
+		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+		processData: true,
+		async: true,
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			text: "text/plain",
+			json: "application/json, text/javascript",
+			"*": allTypes
+		},
+
+		contents: {
+			xml: /xml/,
+			html: /html/,
+			json: /json/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText"
+		},
+
+		// List of data converters
+		// 1) key format is "source_type destination_type" (a single space in-between)
+		// 2) the catchall symbol "*" can be used for source_type
+		converters: {
+
+			// Convert anything to text
+			"* text": window.String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		},
+
+		// For options that shouldn't be deep extended:
+		// you can add your own custom options here if
+		// and when you create one that shouldn't be
+		// deep extended (see ajaxExtend)
+		flatOptions: {
+			context: true,
+			url: true
+		}
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var // Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+			// Callbacks context
+			callbackContext = s.context || s,
+			// Context for global events
+			// It's the callbackContext if one was provided in the options
+			// and if it's a DOM node or a jQuery collection
+			globalEventContext = callbackContext !== s &&
+				( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+						jQuery( callbackContext ) : jQuery.event,
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery.Callbacks( "once memory" ),
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+			// ifModified key
+			ifModifiedKey,
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+			// transport
+			transport,
+			// timeout handle
+			timeoutTimer,
+			// Cross-domain detection vars
+			parts,
+			// The jqXHR state
+			state = 0,
+			// To know if global events are to be dispatched
+			fireGlobals,
+			// Loop variable
+			i,
+			// Fake xhr
+			jqXHR = {
+
+				readyState: 0,
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					if ( !state ) {
+						var lname = name.toLowerCase();
+						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match === undefined ? null : match;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					statusText = statusText || "abort";
+					if ( transport ) {
+						transport.abort( statusText );
+					}
+					done( 0, statusText );
+					return this;
+				}
+			};
+
+		// Callback for when everything is done
+		// It is defined here because jslint complains if it is declared
+		// at the end of the function (which would be more logical and readable)
+		function done( status, nativeStatusText, responses, headers ) {
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status > 0 ? 4 : 0;
+
+			var isSuccess,
+				success,
+				error,
+				statusText = nativeStatusText,
+				response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
+				lastModified,
+				etag;
+
+			// If successful, handle type chaining
+			if ( status >= 200 && status < 300 || status === 304 ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+
+					if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
+						jQuery.lastModified[ ifModifiedKey ] = lastModified;
+					}
+					if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
+						jQuery.etag[ ifModifiedKey ] = etag;
+					}
+				}
+
+				// If not modified
+				if ( status === 304 ) {
+
+					statusText = "notmodified";
+					isSuccess = true;
+
+				// If we have data
+				} else {
+
+					try {
+						success = ajaxConvert( s, response );
+						statusText = "success";
+						isSuccess = true;
+					} catch(e) {
+						// We have a parsererror
+						statusText = "parsererror";
+						error = e;
+					}
+				}
+			} else {
+				// We extract error from statusText
+				// then normalize statusText and status for non-aborts
+				error = statusText;
+				if ( !statusText || status ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = "" + ( nativeStatusText || statusText );
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+						[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger( "ajaxStop" );
+				}
+			}
+		}
+
+		// Attach deferreds
+		deferred.promise( jqXHR );
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+		jqXHR.complete = completeDeferred.add;
+
+		// Status-dependent callbacks
+		jqXHR.statusCode = function( map ) {
+			if ( map ) {
+				var tmp;
+				if ( state < 2 ) {
+					for ( tmp in map ) {
+						statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+					}
+				} else {
+					tmp = map[ jqXHR.status ];
+					jqXHR.then( tmp, tmp );
+				}
+			}
+			return this;
+		};
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+		// Determine if a cross-domain request is in order
+		if ( s.crossDomain == null ) {
+			parts = rurl.exec( s.url.toLowerCase() );
+			s.crossDomain = !!( parts &&
+				( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
+					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+			);
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefilter, stop there
+		if ( state === 2 ) {
+			return false;
+		}
+
+		// We can fire global events as of now if asked to
+		fireGlobals = s.global;
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger( "ajaxStart" );
+		}
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+				// #9682: remove data so that it's not used in an eventual retry
+				delete s.data;
+			}
+
+			// Get ifModifiedKey before adding the anti-cache parameter
+			ifModifiedKey = s.url;
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+
+				var ts = jQuery.now(),
+					// try replacing _= if it is there
+					ret = s.url.replace( rts, "$1_=" + ts );
+
+				// if nothing was replaced, add timestamp to the end
+				s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			ifModifiedKey = ifModifiedKey || s.url;
+			if ( jQuery.lastModified[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+			}
+			if ( jQuery.etag[ ifModifiedKey ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+			}
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+				// Abort if not done already
+				jqXHR.abort();
+				return false;
+
+		}
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = setTimeout( function(){
+					jqXHR.abort( "timeout" );
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch (e) {
+				// Propagate exception as error if not done
+				if ( state < 2 ) {
+					done( -1, e );
+				// Simply rethrow otherwise
+				} else {
+					throw e;
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a, traditional ) {
+		var s = [],
+			add = function( key, value ) {
+				// If value is a function, invoke it and return its value
+				value = jQuery.isFunction( value ) ? value() : value;
+				s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+			};
+
+		// Set traditional to true for jQuery <= 1.3.2 behavior.
+		if ( traditional === undefined ) {
+			traditional = jQuery.ajaxSettings.traditional;
+		}
+
+		// If an array was passed in, assume that it is an array of form elements.
+		if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+			// Serialize the form elements
+			jQuery.each( a, function() {
+				add( this.name, this.value );
+			});
+
+		} else {
+			// If traditional, encode the "old" way (the way 1.3.2 or older
+			// did it), otherwise encode params recursively.
+			for ( var prefix in a ) {
+				buildParams( prefix, a[ prefix ], traditional, add );
+			}
+		}
+
+		// Return the resulting serialization
+		return s.join( "&" ).replace( r20, "+" );
+	}
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+	if ( jQuery.isArray( obj ) ) {
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+				// If array item is non-scalar (array or object), encode its
+				// numeric index to resolve deserialization ambiguity issues.
+				// Note that rack (as of 1.0.0) can't currently deserialize
+				// nested arrays properly, and attempting to do so may cause
+				// a server error. Possible fixes are to modify rack's
+				// deserialization algorithm or to provide an option or flag
+				// to force array serialization to be shallow.
+				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+			}
+		});
+
+	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
+		// Serialize object item.
+		for ( var name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+		}
+
+	} else {
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+	var contents = s.contents,
+		dataTypes = s.dataTypes,
+		responseFields = s.responseFields,
+		ct,
+		type,
+		finalDataType,
+		firstDataType;
+
+	// Fill responseXXX fields
+	for ( type in responseFields ) {
+		if ( type in responses ) {
+			jqXHR[ responseFields[type] ] = responses[ type ];
+		}
+	}
+
+	// Remove auto dataType and get content-type in the process
+	while( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+	// Apply the dataFilter if provided
+	if ( s.dataFilter ) {
+		response = s.dataFilter( response, s.dataType );
+	}
+
+	var dataTypes = s.dataTypes,
+		converters = {},
+		i,
+		key,
+		length = dataTypes.length,
+		tmp,
+		// Current and previous dataTypes
+		current = dataTypes[ 0 ],
+		prev,
+		// Conversion expression
+		conversion,
+		// Conversion function
+		conv,
+		// Conversion functions (transitive conversion)
+		conv1,
+		conv2;
+
+	// For each dataType in the chain
+	for ( i = 1; i < length; i++ ) {
+
+		// Create converters map
+		// with lowercased keys
+		if ( i === 1 ) {
+			for ( key in s.converters ) {
+				if ( typeof key === "string" ) {
+					converters[ key.toLowerCase() ] = s.converters[ key ];
+				}
+			}
+		}
+
+		// Get the dataTypes
+		prev = current;
+		current = dataTypes[ i ];
+
+		// If current is auto dataType, update it to prev
+		if ( current === "*" ) {
+			current = prev;
+		// If no auto and dataTypes are actually different
+		} else if ( prev !== "*" && prev !== current ) {
+
+			// Get the converter
+			conversion = prev + " " + current;
+			conv = converters[ conversion ] || converters[ "* " + current ];
+
+			// If there is no direct converter, search transitively
+			if ( !conv ) {
+				conv2 = undefined;
+				for ( conv1 in converters ) {
+					tmp = conv1.split( " " );
+					if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+						conv2 = converters[ tmp[1] + " " + current ];
+						if ( conv2 ) {
+							conv1 = converters[ conv1 ];
+							if ( conv1 === true ) {
+								conv = conv2;
+							} else if ( conv2 === true ) {
+								conv = conv1;
+							}
+							break;
+						}
+					}
+				}
+			}
+			// If we found no converter, dispatch an error
+			if ( !( conv || conv2 ) ) {
+				jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+			}
+			// If found converter is not an equivalence
+			if ( conv !== true ) {
+				// Convert with 1 or 2 converters accordingly
+				response = conv ? conv( response ) : conv2( conv1(response) );
+			}
+		}
+	}
+	return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+	jsre = /(\=)\?(&|$)|\?\?/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+	jsonp: "callback",
+	jsonpCallback: function() {
+		return jQuery.expando + "_" + ( jsc++ );
+	}
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var inspectData = ( typeof s.data === "string" ) && /^application\/x\-www\-form\-urlencoded/.test( s.contentType );
+
+	if ( s.dataTypes[ 0 ] === "jsonp" ||
+		s.jsonp !== false && ( jsre.test( s.url ) ||
+				inspectData && jsre.test( s.data ) ) ) {
+
+		var responseContainer,
+			jsonpCallback = s.jsonpCallback =
+				jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+			previous = window[ jsonpCallback ],
+			url = s.url,
+			data = s.data,
+			replace = "$1" + jsonpCallback + "$2";
+
+		if ( s.jsonp !== false ) {
+			url = url.replace( jsre, replace );
+			if ( s.url === url ) {
+				if ( inspectData ) {
+					data = data.replace( jsre, replace );
+				}
+				if ( s.data === data ) {
+					// Add callback manually
+					url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+				}
+			}
+		}
+
+		s.url = url;
+		s.data = data;
+
+		// Install callback
+		window[ jsonpCallback ] = function( response ) {
+			responseContainer = [ response ];
+		};
+
+		// Clean-up function
+		jqXHR.always(function() {
+			// Set callback back to previous value
+			window[ jsonpCallback ] = previous;
+			// Call if it was a function and we have a response
+			if ( responseContainer && jQuery.isFunction( previous ) ) {
+				window[ jsonpCallback ]( responseContainer[ 0 ] );
+			}
+		});
+
+		// Use data converter to retrieve json after script execution
+		s.converters["script json"] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( jsonpCallback + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Delegate to script
+		return "script";
+	}
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+	accepts: {
+		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /javascript|ecmascript/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+		s.global = false;
+	}
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+
+		var script,
+			head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+		return {
+
+			send: function( _, callback ) {
+
+				script = document.createElement( "script" );
+
+				script.async = "async";
+
+				if ( s.scriptCharset ) {
+					script.charset = s.scriptCharset;
+				}
+
+				script.src = s.url;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+						// Handle memory leak in IE
+						script.onload = script.onreadystatechange = null;
+
+						// Remove the script
+						if ( head && script.parentNode ) {
+							head.removeChild( script );
+						}
+
+						// Dereference the script
+						script = undefined;
+
+						// Callback if not abort
+						if ( !isAbort ) {
+							callback( 200, "success" );
+						}
+					}
+				};
+				// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+				// This arises when a base node is used (#2709 and #4378).
+				head.insertBefore( script, head.firstChild );
+			},
+
+			abort: function() {
+				if ( script ) {
+					script.onload( 0, 1 );
+				}
+			}
+		};
+	}
+});
+
+
+
+
+var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+	xhrOnUnloadAbort = window.ActiveXObject ? function() {
+		// Abort all pending requests
+		for ( var key in xhrCallbacks ) {
+			xhrCallbacks[ key ]( 0, 1 );
+		}
+	} : false,
+	xhrId = 0,
+	xhrCallbacks;
+
+// Functions to create xhrs
+function createStandardXHR() {
+	try {
+		return new window.XMLHttpRequest();
+	} catch( e ) {}
+}
+
+function createActiveXHR() {
+	try {
+		return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+	} catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+	/* Microsoft failed to properly
+	 * implement the XMLHttpRequest in IE7 (can't request local files),
+	 * so we use the ActiveXObject when it is available
+	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+	 * we need a fallback.
+	 */
+	function() {
+		return !this.isLocal && createStandardXHR() || createActiveXHR();
+	} :
+	// For all other browsers, use the standard XMLHttpRequest object
+	createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+	jQuery.extend( jQuery.support, {
+		ajax: !!xhr,
+		cors: !!xhr && ( "withCredentials" in xhr )
+	});
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+	jQuery.ajaxTransport(function( s ) {
+		// Cross domain only allowed if supported through XMLHttpRequest
+		if ( !s.crossDomain || jQuery.support.cors ) {
+
+			var callback;
+
+			return {
+				send: function( headers, complete ) {
+
+					// Get a new xhr
+					var xhr = s.xhr(),
+						handle,
+						i;
+
+					// Open the socket
+					// Passing null username, generates a login popup on Opera (#2865)
+					if ( s.username ) {
+						xhr.open( s.type, s.url, s.async, s.username, s.password );
+					} else {
+						xhr.open( s.type, s.url, s.async );
+					}
+
+					// Apply custom fields if provided
+					if ( s.xhrFields ) {
+						for ( i in s.xhrFields ) {
+							xhr[ i ] = s.xhrFields[ i ];
+						}
+					}
+
+					// Override mime type if needed
+					if ( s.mimeType && xhr.overrideMimeType ) {
+						xhr.overrideMimeType( s.mimeType );
+					}
+
+					// X-Requested-With header
+					// For cross-domain requests, seeing as conditions for a preflight are
+					// akin to a jigsaw puzzle, we simply never set it to be sure.
+					// (it can always be set on a per-request basis or even using ajaxSetup)
+					// For same-domain requests, won't change header if already provided.
+					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+						headers[ "X-Requested-With" ] = "XMLHttpRequest";
+					}
+
+					// Need an extra try/catch for cross domain requests in Firefox 3
+					try {
+						for ( i in headers ) {
+							xhr.setRequestHeader( i, headers[ i ] );
+						}
+					} catch( _ ) {}
+
+					// Do send the request
+					// This may raise an exception which is actually
+					// handled in jQuery.ajax (so no try/catch here)
+					xhr.send( ( s.hasContent && s.data ) || null );
+
+					// Listener
+					callback = function( _, isAbort ) {
+
+						var status,
+							statusText,
+							responseHeaders,
+							responses,
+							xml;
+
+						// Firefox throws exceptions when accessing properties
+						// of an xhr when a network error occured
+						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+						try {
+
+							// Was never called and is aborted or complete
+							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+								// Only called once
+								callback = undefined;
+
+								// Do not keep as active anymore
+								if ( handle ) {
+									xhr.onreadystatechange = jQuery.noop;
+									if ( xhrOnUnloadAbort ) {
+										delete xhrCallbacks[ handle ];
+									}
+								}
+
+								// If it's an abort
+								if ( isAbort ) {
+									// Abort it manually if needed
+									if ( xhr.readyState !== 4 ) {
+										xhr.abort();
+									}
+								} else {
+									status = xhr.status;
+									responseHeaders = xhr.getAllResponseHeaders();
+									responses = {};
+									xml = xhr.responseXML;
+
+									// Construct response list
+									if ( xml && xml.documentElement /* #4958 */ ) {
+										responses.xml = xml;
+									}
+
+									// When requesting binary data, IE6-9 will throw an exception
+									// on any attempt to access responseText (#11426)
+									try {
+										responses.text = xhr.responseText;
+									} catch( _ ) {
+									}
+
+									// Firefox throws an exception when accessing
+									// statusText for faulty cross-domain requests
+									try {
+										statusText = xhr.statusText;
+									} catch( e ) {
+										// We normalize with Webkit giving an empty statusText
+										statusText = "";
+									}
+
+									// Filter status for non standard behaviors
+
+									// If the request is local and we have data: assume a success
+									// (success with no data won't get notified, that's the best we
+									// can do given current implementations)
+									if ( !status && s.isLocal && !s.crossDomain ) {
+										status = responses.text ? 200 : 404;
+									// IE - #1450: sometimes returns 1223 when it should be 204
+									} else if ( status === 1223 ) {
+										status = 204;
+									}
+								}
+							}
+						} catch( firefoxAccessException ) {
+							if ( !isAbort ) {
+								complete( -1, firefoxAccessException );
+							}
+						}
+
+						// Call complete if needed
+						if ( responses ) {
+							complete( status, statusText, responses, responseHeaders );
+						}
+					};
+
+					// if we're in sync mode or it's in cache
+					// and has been retrieved directly (IE6 & IE7)
+					// we need to manually fire the callback
+					if ( !s.async || xhr.readyState === 4 ) {
+						callback();
+					} else {
+						handle = ++xhrId;
+						if ( xhrOnUnloadAbort ) {
+							// Create the active xhrs callbacks list if needed
+							// and attach the unload handler
+							if ( !xhrCallbacks ) {
+								xhrCallbacks = {};
+								jQuery( window ).unload( xhrOnUnloadAbort );
+							}
+							// Add to list of active xhrs callbacks
+							xhrCallbacks[ handle ] = callback;
+						}
+						xhr.onreadystatechange = callback;
+					}
+				},
+
+				abort: function() {
+					if ( callback ) {
+						callback(0,1);
+					}
+				}
+			};
+		}
+	});
+}
+
+
+
+
+var elemdisplay = {},
+	iframe, iframeDoc,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+	timerId,
+	fxAttrs = [
+		// height animations
+		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+		// width animations
+		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+		// opacity animations
+		[ "opacity" ]
+	],
+	fxNow;
+
+jQuery.fn.extend({
+	show: function( speed, easing, callback ) {
+		var elem, display;
+
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("show", 3), speed, easing, callback );
+
+		} else {
+			for ( var i = 0, j = this.length; i < j; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.style ) {
+					display = elem.style.display;
+
+					// Reset the inline display of this element to learn if it is
+					// being hidden by cascaded rules or not
+					if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+						display = elem.style.display = "";
+					}
+
+					// Set elements which have been overridden with display: none
+					// in a stylesheet to whatever the default browser style is
+					// for such an element
+					if ( (display === "" && jQuery.css(elem, "display") === "none") ||
+						!jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+						jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+					}
+				}
+			}
+
+			// Set the display of most of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				elem = this[ i ];
+
+				if ( elem.style ) {
+					display = elem.style.display;
+
+					if ( display === "" || display === "none" ) {
+						elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
+					}
+				}
+			}
+
+			return this;
+		}
+	},
+
+	hide: function( speed, easing, callback ) {
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("hide", 3), speed, easing, callback);
+
+		} else {
+			var elem, display,
+				i = 0,
+				j = this.length;
+
+			for ( ; i < j; i++ ) {
+				elem = this[i];
+				if ( elem.style ) {
+					display = jQuery.css( elem, "display" );
+
+					if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) {
+						jQuery._data( elem, "olddisplay", display );
+					}
+				}
+			}
+
+			// Set the display of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				if ( this[i].style ) {
+					this[i].style.display = "none";
+				}
+			}
+
+			return this;
+		}
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+
+	toggle: function( fn, fn2, callback ) {
+		var bool = typeof fn === "boolean";
+
+		if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+			this._toggle.apply( this, arguments );
+
+		} else if ( fn == null || bool ) {
+			this.each(function() {
+				var state = bool ? fn : jQuery(this).is(":hidden");
+				jQuery(this)[ state ? "show" : "hide" ]();
+			});
+
+		} else {
+			this.animate(genFx("toggle", 3), fn, fn2, callback);
+		}
+
+		return this;
+	},
+
+	fadeTo: function( speed, to, easing, callback ) {
+		return this.filter(":hidden").css("opacity", 0).show().end()
+					.animate({opacity: to}, speed, easing, callback);
+	},
+
+	animate: function( prop, speed, easing, callback ) {
+		var optall = jQuery.speed( speed, easing, callback );
+
+		if ( jQuery.isEmptyObject( prop ) ) {
+			return this.each( optall.complete, [ false ] );
+		}
+
+		// Do not change referenced properties as per-property easing will be lost
+		prop = jQuery.extend( {}, prop );
+
+		function doAnimation() {
+			// XXX 'this' does not always have a nodeName when running the
+			// test suite
+
+			if ( optall.queue === false ) {
+				jQuery._mark( this );
+			}
+
+			var opt = jQuery.extend( {}, optall ),
+				isElement = this.nodeType === 1,
+				hidden = isElement && jQuery(this).is(":hidden"),
+				name, val, p, e, hooks, replace,
+				parts, start, end, unit,
+				method;
+
+			// will store per property easing and be used to determine when an animation is complete
+			opt.animatedProperties = {};
+
+			// first pass over propertys to expand / normalize
+			for ( p in prop ) {
+				name = jQuery.camelCase( p );
+				if ( p !== name ) {
+					prop[ name ] = prop[ p ];
+					delete prop[ p ];
+				}
+
+				if ( ( hooks = jQuery.cssHooks[ name ] ) && "expand" in hooks ) {
+					replace = hooks.expand( prop[ name ] );
+					delete prop[ name ];
+
+					// not quite $.extend, this wont overwrite keys already present.
+					// also - reusing 'p' from above because we have the correct "name"
+					for ( p in replace ) {
+						if ( ! ( p in prop ) ) {
+							prop[ p ] = replace[ p ];
+						}
+					}
+				}
+			}
+
+			for ( name in prop ) {
+				val = prop[ name ];
+				// easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
+				if ( jQuery.isArray( val ) ) {
+					opt.animatedProperties[ name ] = val[ 1 ];
+					val = prop[ name ] = val[ 0 ];
+				} else {
+					opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
+				}
+
+				if ( val === "hide" && hidden || val === "show" && !hidden ) {
+					return opt.complete.call( this );
+				}
+
+				if ( isElement && ( name === "height" || name === "width" ) ) {
+					// Make sure that nothing sneaks out
+					// Record all 3 overflow attributes because IE does not
+					// change the overflow attribute when overflowX and
+					// overflowY are set to the same value
+					opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+					// Set display property to inline-block for height/width
+					// animations on inline elements that are having width/height animated
+					if ( jQuery.css( this, "display" ) === "inline" &&
+							jQuery.css( this, "float" ) === "none" ) {
+
+						// inline-level elements accept inline-block;
+						// block-level elements need to be inline with layout
+						if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
+							this.style.display = "inline-block";
+
+						} else {
+							this.style.zoom = 1;
+						}
+					}
+				}
+			}
+
+			if ( opt.overflow != null ) {
+				this.style.overflow = "hidden";
+			}
+
+			for ( p in prop ) {
+				e = new jQuery.fx( this, opt, p );
+				val = prop[ p ];
+
+				if ( rfxtypes.test( val ) ) {
+
+					// Tracks whether to show or hide based on private
+					// data attached to the element
+					method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
+					if ( method ) {
+						jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
+						e[ method ]();
+					} else {
+						e[ val ]();
+					}
+
+				} else {
+					parts = rfxnum.exec( val );
+					start = e.cur();
+
+					if ( parts ) {
+						end = parseFloat( parts[2] );
+						unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
+
+						// We need to compute starting value
+						if ( unit !== "px" ) {
+							jQuery.style( this, p, (end || 1) + unit);
+							start = ( (end || 1) / e.cur() ) * start;
+							jQuery.style( this, p, start + unit);
+						}
+
+						// If a +=/-= token was provided, we're doing a relative animation
+						if ( parts[1] ) {
+							end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
+						}
+
+						e.custom( start, end, unit );
+
+					} else {
+						e.custom( start, val, "" );
+					}
+				}
+			}
+
+			// For JS strict compliance
+			return true;
+		}
+
+		return optall.queue === false ?
+			this.each( doAnimation ) :
+			this.queue( optall.queue, doAnimation );
+	},
+
+	stop: function( type, clearQueue, gotoEnd ) {
+		if ( typeof type !== "string" ) {
+			gotoEnd = clearQueue;
+			clearQueue = type;
+			type = undefined;
+		}
+		if ( clearQueue && type !== false ) {
+			this.queue( type || "fx", [] );
+		}
+
+		return this.each(function() {
+			var index,
+				hadTimers = false,
+				timers = jQuery.timers,
+				data = jQuery._data( this );
+
+			// clear marker counters if we know they won't be
+			if ( !gotoEnd ) {
+				jQuery._unmark( true, this );
+			}
+
+			function stopQueue( elem, data, index ) {
+				var hooks = data[ index ];
+				jQuery.removeData( elem, index, true );
+				hooks.stop( gotoEnd );
+			}
+
+			if ( type == null ) {
+				for ( index in data ) {
+					if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) {
+						stopQueue( this, data, index );
+					}
+				}
+			} else if ( data[ index = type + ".run" ] && data[ index ].stop ){
+				stopQueue( this, data, index );
+			}
+
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+					if ( gotoEnd ) {
+
+						// force the next step to be the last
+						timers[ index ]( true );
+					} else {
+						timers[ index ].saveState();
+					}
+					hadTimers = true;
+					timers.splice( index, 1 );
+				}
+			}
+
+			// start the next in the queue if the last step wasn't forced
+			// timers currently will call their complete callbacks, which will dequeue
+			// but only if they were gotoEnd
+			if ( !( gotoEnd && hadTimers ) ) {
+				jQuery.dequeue( this, type );
+			}
+		});
+	}
+
+});
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	setTimeout( clearFxNow, 0 );
+	return ( fxNow = jQuery.now() );
+}
+
+function clearFxNow() {
+	fxNow = undefined;
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, num ) {
+	var obj = {};
+
+	jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() {
+		obj[ this ] = type;
+	});
+
+	return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+	slideDown: genFx( "show", 1 ),
+	slideUp: genFx( "hide", 1 ),
+	slideToggle: genFx( "toggle", 1 ),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+});
+
+jQuery.extend({
+	speed: function( speed, easing, fn ) {
+		var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+			complete: fn || !fn && easing ||
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+		};
+
+		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+			opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+		// normalize opt.queue - true/undefined/null -> "fx"
+		if ( opt.queue == null || opt.queue === true ) {
+			opt.queue = "fx";
+		}
+
+		// Queueing
+		opt.old = opt.complete;
+
+		opt.complete = function( noUnmark ) {
+			if ( jQuery.isFunction( opt.old ) ) {
+				opt.old.call( this );
+			}
+
+			if ( opt.queue ) {
+				jQuery.dequeue( this, opt.queue );
+			} else if ( noUnmark !== false ) {
+				jQuery._unmark( this );
+			}
+		};
+
+		return opt;
+	},
+
+	easing: {
+		linear: function( p ) {
+			return p;
+		},
+		swing: function( p ) {
+			return ( -Math.cos( p*Math.PI ) / 2 ) + 0.5;
+		}
+	},
+
+	timers: [],
+
+	fx: function( elem, options, prop ) {
+		this.options = options;
+		this.elem = elem;
+		this.prop = prop;
+
+		options.orig = options.orig || {};
+	}
+
+});
+
+jQuery.fx.prototype = {
+	// Simple function for setting a style value
+	update: function() {
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this );
+	},
+
+	// Get the current size
+	cur: function() {
+		if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) {
+			return this.elem[ this.prop ];
+		}
+
+		var parsed,
+			r = jQuery.css( this.elem, this.prop );
+		// Empty strings, null, undefined and "auto" are converted to 0,
+		// complex values such as "rotate(1rad)" are returned as is,
+		// simple values such as "10px" are parsed to Float.
+		return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
+	},
+
+	// Start an animation from one number to another
+	custom: function( from, to, unit ) {
+		var self = this,
+			fx = jQuery.fx;
+
+		this.startTime = fxNow || createFxNow();
+		this.end = to;
+		this.now = this.start = from;
+		this.pos = this.state = 0;
+		this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
+
+		function t( gotoEnd ) {
+			return self.step( gotoEnd );
+		}
+
+		t.queue = this.options.queue;
+		t.elem = this.elem;
+		t.saveState = function() {
+			if ( jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
+				if ( self.options.hide ) {
+					jQuery._data( self.elem, "fxshow" + self.prop, self.start );
+				} else if ( self.options.show ) {
+					jQuery._data( self.elem, "fxshow" + self.prop, self.end );
+				}
+			}
+		};
+
+		if ( t() && jQuery.timers.push(t) && !timerId ) {
+			timerId = setInterval( fx.tick, fx.interval );
+		}
+	},
+
+	// Simple 'show' function
+	show: function() {
+		var dataShow = jQuery._data( this.elem, "fxshow" + this.prop );
+
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop );
+		this.options.show = true;
+
+		// Begin the animation
+		// Make sure that we start at a small width/height to avoid any flash of content
+		if ( dataShow !== undefined ) {
+			// This show is picking up where a previous hide or show left off
+			this.custom( this.cur(), dataShow );
+		} else {
+			this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() );
+		}
+
+		// Start by showing the element
+		jQuery( this.elem ).show();
+	},
+
+	// Simple 'hide' function
+	hide: function() {
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop );
+		this.options.hide = true;
+
+		// Begin the animation
+		this.custom( this.cur(), 0 );
+	},
+
+	// Each step of an animation
+	step: function( gotoEnd ) {
+		var p, n, complete,
+			t = fxNow || createFxNow(),
+			done = true,
+			elem = this.elem,
+			options = this.options;
+
+		if ( gotoEnd || t >= options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			options.animatedProperties[ this.prop ] = true;
+
+			for ( p in options.animatedProperties ) {
+				if ( options.animatedProperties[ p ] !== true ) {
+					done = false;
+				}
+			}
+
+			if ( done ) {
+				// Reset the overflow
+				if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+
+					jQuery.each( [ "", "X", "Y" ], function( index, value ) {
+						elem.style[ "overflow" + value ] = options.overflow[ index ];
+					});
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( options.hide ) {
+					jQuery( elem ).hide();
+				}
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( options.hide || options.show ) {
+					for ( p in options.animatedProperties ) {
+						jQuery.style( elem, p, options.orig[ p ] );
+						jQuery.removeData( elem, "fxshow" + p, true );
+						// Toggle data is no longer needed
+						jQuery.removeData( elem, "toggle" + p, true );
+					}
+				}
+
+				// Execute the complete function
+				// in the event that the complete function throws an exception
+				// we must ensure it won't be called twice. #5684
+
+				complete = options.complete;
+				if ( complete ) {
+
+					options.complete = false;
+					complete.call( elem );
+				}
+			}
+
+			return false;
+
+		} else {
+			// classical easing cannot be used with an Infinity duration
+			if ( options.duration == Infinity ) {
+				this.now = t;
+			} else {
+				n = t - this.startTime;
+				this.state = n / options.duration;
+
+				// Perform the easing function, defaults to swing
+				this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration );
+				this.now = this.start + ( (this.end - this.start) * this.pos );
+			}
+			// Perform the next step of the animation
+			this.update();
+		}
+
+		return true;
+	}
+};
+
+jQuery.extend( jQuery.fx, {
+	tick: function() {
+		var timer,
+			timers = jQuery.timers,
+			i = 0;
+
+		for ( ; i < timers.length; i++ ) {
+			timer = timers[ i ];
+			// Checks the timer has not already been removed
+			if ( !timer() && timers[ i ] === timer ) {
+				timers.splice( i--, 1 );
+			}
+		}
+
+		if ( !timers.length ) {
+			jQuery.fx.stop();
+		}
+	},
+
+	interval: 13,
+
+	stop: function() {
+		clearInterval( timerId );
+		timerId = null;
+	},
+
+	speeds: {
+		slow: 600,
+		fast: 200,
+		// Default speed
+		_default: 400
+	},
+
+	step: {
+		opacity: function( fx ) {
+			jQuery.style( fx.elem, "opacity", fx.now );
+		},
+
+		_default: function( fx ) {
+			if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+				fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+			} else {
+				fx.elem[ fx.prop ] = fx.now;
+			}
+		}
+	}
+});
+
+// Ensure props that can't be negative don't go there on undershoot easing
+jQuery.each( fxAttrs.concat.apply( [], fxAttrs ), function( i, prop ) {
+	// exclude marginTop, marginLeft, marginBottom and marginRight from this list
+	if ( prop.indexOf( "margin" ) ) {
+		jQuery.fx.step[ prop ] = function( fx ) {
+			jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit );
+		};
+	}
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.animated = function( elem ) {
+		return jQuery.grep(jQuery.timers, function( fn ) {
+			return elem === fn.elem;
+		}).length;
+	};
+}
+
+// Try to restore the default display value of an element
+function defaultDisplay( nodeName ) {
+
+	if ( !elemdisplay[ nodeName ] ) {
+
+		var body = document.body,
+			elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
+			display = elem.css( "display" );
+		elem.remove();
+
+		// If the simple way fails,
+		// get element's real default display by attaching it to a temp iframe
+		if ( display === "none" || display === "" ) {
+			// No iframe to use yet, so create it
+			if ( !iframe ) {
+				iframe = document.createElement( "iframe" );
+				iframe.frameBorder = iframe.width = iframe.height = 0;
+			}
+
+			body.appendChild( iframe );
+
+			// Create a cacheable copy of the iframe document on first call.
+			// IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+			// document to it; WebKit & Firefox won't allow reusing the iframe document.
+			if ( !iframeDoc || !iframe.createElement ) {
+				iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+				iframeDoc.write( ( jQuery.support.boxModel ? "<!doctype html>" : "" ) + "<html><body>" );
+				iframeDoc.close();
+			}
+
+			elem = iframeDoc.createElement( nodeName );
+
+			iframeDoc.body.appendChild( elem );
+
+			display = jQuery.css( elem, "display" );
+			body.removeChild( iframe );
+		}
+
+		// Store the correct default display
+		elemdisplay[ nodeName ] = display;
+	}
+
+	return elemdisplay[ nodeName ];
+}
+
+
+
+
+var getOffset,
+	rtable = /^t(?:able|d|h)$/i,
+	rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+	getOffset = function( elem, doc, docElem, box ) {
+		try {
+			box = elem.getBoundingClientRect();
+		} catch(e) {}
+
+		// Make sure we're not dealing with a disconnected DOM node
+		if ( !box || !jQuery.contains( docElem, elem ) ) {
+			return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+		}
+
+		var body = doc.body,
+			win = getWindow( doc ),
+			clientTop  = docElem.clientTop  || body.clientTop  || 0,
+			clientLeft = docElem.clientLeft || body.clientLeft || 0,
+			scrollTop  = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop,
+			scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
+			top  = box.top  + scrollTop  - clientTop,
+			left = box.left + scrollLeft - clientLeft;
+
+		return { top: top, left: left };
+	};
+
+} else {
+	getOffset = function( elem, doc, docElem ) {
+		var computedStyle,
+			offsetParent = elem.offsetParent,
+			prevOffsetParent = elem,
+			body = doc.body,
+			defaultView = doc.defaultView,
+			prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+			top = elem.offsetTop,
+			left = elem.offsetLeft;
+
+		while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+			if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+				break;
+			}
+
+			computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+			top  -= elem.scrollTop;
+			left -= elem.scrollLeft;
+
+			if ( elem === offsetParent ) {
+				top  += elem.offsetTop;
+				left += elem.offsetLeft;
+
+				if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+					top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+					left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+				}
+
+				prevOffsetParent = offsetParent;
+				offsetParent = elem.offsetParent;
+			}
+
+			if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+				top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+				left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+			}
+
+			prevComputedStyle = computedStyle;
+		}
+
+		if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+			top  += body.offsetTop;
+			left += body.offsetLeft;
+		}
+
+		if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
+			top  += Math.max( docElem.scrollTop, body.scrollTop );
+			left += Math.max( docElem.scrollLeft, body.scrollLeft );
+		}
+
+		return { top: top, left: left };
+	};
+}
+
+jQuery.fn.offset = function( options ) {
+	if ( arguments.length ) {
+		return options === undefined ?
+			this :
+			this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+	}
+
+	var elem = this[0],
+		doc = elem && elem.ownerDocument;
+
+	if ( !doc ) {
+		return null;
+	}
+
+	if ( elem === doc.body ) {
+		return jQuery.offset.bodyOffset( elem );
+	}
+
+	return getOffset( elem, doc, doc.documentElement );
+};
+
+jQuery.offset = {
+
+	bodyOffset: function( body ) {
+		var top = body.offsetTop,
+			left = body.offsetLeft;
+
+		if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+			top  += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+			left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+		}
+
+		return { top: top, left: left };
+	},
+
+	setOffset: function( elem, options, i ) {
+		var position = jQuery.css( elem, "position" );
+
+		// set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		var curElem = jQuery( elem ),
+			curOffset = curElem.offset(),
+			curCSSTop = jQuery.css( elem, "top" ),
+			curCSSLeft = jQuery.css( elem, "left" ),
+			calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+			props = {}, curPosition = {}, curTop, curLeft;
+
+		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( jQuery.isFunction( options ) ) {
+			options = options.call( elem, i, curOffset );
+		}
+
+		if ( options.top != null ) {
+			props.top = ( options.top - curOffset.top ) + curTop;
+		}
+		if ( options.left != null ) {
+			props.left = ( options.left - curOffset.left ) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+		} else {
+			curElem.css( props );
+		}
+	}
+};
+
+
+jQuery.fn.extend({
+
+	position: function() {
+		if ( !this[0] ) {
+			return null;
+		}
+
+		var elem = this[0],
+
+		// Get *real* offsetParent
+		offsetParent = this.offsetParent(),
+
+		// Get correct offsets
+		offset       = this.offset(),
+		parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+		// Subtract element margins
+		// note: when an element has margin: auto the offsetLeft and marginLeft
+		// are the same in Safari causing offset.left to incorrectly be 0
+		offset.top  -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+		offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+		// Add offsetParent borders
+		parentOffset.top  += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+		parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+		// Subtract the two offsets
+		return {
+			top:  offset.top  - parentOffset.top,
+			left: offset.left - parentOffset.left
+		};
+	},
+
+	offsetParent: function() {
+		return this.map(function() {
+			var offsetParent = this.offsetParent || document.body;
+			while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+			return offsetParent;
+		});
+	}
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+	var top = /Y/.test( prop );
+
+	jQuery.fn[ method ] = function( val ) {
+		return jQuery.access( this, function( elem, method, val ) {
+			var win = getWindow( elem );
+
+			if ( val === undefined ) {
+				return win ? (prop in win) ? win[ prop ] :
+					jQuery.support.boxModel && win.document.documentElement[ method ] ||
+						win.document.body[ method ] :
+					elem[ method ];
+			}
+
+			if ( win ) {
+				win.scrollTo(
+					!top ? val : jQuery( win ).scrollLeft(),
+					 top ? val : jQuery( win ).scrollTop()
+				);
+
+			} else {
+				elem[ method ] = val;
+			}
+		}, method, val, arguments.length, null );
+	};
+});
+
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ?
+		elem :
+		elem.nodeType === 9 ?
+			elem.defaultView || elem.parentWindow :
+			false;
+}
+
+
+
+
+// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+	var clientProp = "client" + name,
+		scrollProp = "scroll" + name,
+		offsetProp = "offset" + name;
+
+	// innerHeight and innerWidth
+	jQuery.fn[ "inner" + name ] = function() {
+		var elem = this[0];
+		return elem ?
+			elem.style ?
+			parseFloat( jQuery.css( elem, type, "padding" ) ) :
+			this[ type ]() :
+			null;
+	};
+
+	// outerHeight and outerWidth
+	jQuery.fn[ "outer" + name ] = function( margin ) {
+		var elem = this[0];
+		return elem ?
+			elem.style ?
+			parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
+			this[ type ]() :
+			null;
+	};
+
+	jQuery.fn[ type ] = function( value ) {
+		return jQuery.access( this, function( elem, type, value ) {
+			var doc, docElemProp, orig, ret;
+
+			if ( jQuery.isWindow( elem ) ) {
+				// 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+				doc = elem.document;
+				docElemProp = doc.documentElement[ clientProp ];
+				return jQuery.support.boxModel && docElemProp ||
+					doc.body && doc.body[ clientProp ] || docElemProp;
+			}
+
+			// Get document width or height
+			if ( elem.nodeType === 9 ) {
+				// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+				doc = elem.documentElement;
+
+				// when a window > document, IE6 reports a offset[Width/Height] > client[Width/Height]
+				// so we can't use max, as it'll choose the incorrect offset[Width/Height]
+				// instead we use the correct client[Width/Height]
+				// support:IE6
+				if ( doc[ clientProp ] >= doc[ scrollProp ] ) {
+					return doc[ clientProp ];
+				}
+
+				return Math.max(
+					elem.body[ scrollProp ], doc[ scrollProp ],
+					elem.body[ offsetProp ], doc[ offsetProp ]
+				);
+			}
+
+			// Get width or height on the element
+			if ( value === undefined ) {
+				orig = jQuery.css( elem, type );
+				ret = parseFloat( orig );
+				return jQuery.isNumeric( ret ) ? ret : orig;
+			}
+
+			// Set the width or height on the element
+			jQuery( elem ).css( type, value );
+		}, type, value, arguments.length, null );
+	};
+});
+
+
+
+
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+	define( "jquery", [], function () { return jQuery; } );
+}
+
+
+
+})( window );
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/jquery-1.7.2.pack.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,2 @@
+/*! jQuery v1.7 jquery.com | jquery.org/license */
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(N(w,x){P y=w.56,b7=w.b7,b5=w.b5;P z=(N(){P m=N(a,b){6 2i m.fn.4W(a,b,bq)},ga=w.5E,4b$=w.$,bq,dB=/^(?:[^#<]*(<[\\w\\W]+>)[^>]*$|#([\\w\\-]*)$)/,cZ=/\\S/,cD=/^\\s+/,cB=/\\s+$/,g7=/^<(\\w+)\\s*\\/?>(?:<\\/\\1>)?$/,dg=/^[\\],:{}\\s]*$/,dy=/\\\\(?:["\\\\\\/lu]|u[0-9a-fA-F]{4})/g,eu=/"[^"\\\\\\n\\r]*"|T|18|V|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/g,dJ=/(?:^|:|,)(?:\\s*\\[)+/g,e0=/(e2)[ \\/]([\\w.]+)/,eb=/(lp)(?:.*97)?[ \\/]([\\w.]+)/,eI=/(lo) ([\\w.]+)/,fw=/(ln)(?:.*? lm:([\\w.]+))?/,g9=/-([a-z]|[0-9])/ig,df=/^-cq-/,dx=N(a,b){6(b+"").9y()},bb=b7.bb,87,7S,4o,4Y=9D.2L.4Y,9F=9D.2L.ll,1y=3v.2L.1y,2g=3v.2L.2g,3H=9V.2L.3H,2w=3v.2L.2w,aU={};m.fn=m.2L={4X:m,4W:N(a,b,c){P d,U,1b,25;if(!a){6 O}if(a.1d){O.3g=O[0]=a;O.Q=1;6 O}if(a==="1t"&&!b&&y.1t){O.3g=y;O[0]=y.1t;O.2b=a;O.Q=1;6 O}if(1a a==="1v"){if(a.d5(0)==="<"&&a.d5(a.Q-1)===">"&&a.Q>=3){d=[V,a,V]}R{d=dB.23(a)}if(d&&(d[1]||!b)){if(d[1]){b=b 6Z m?b[0]:b;25=(b?b.2A||b:y);1b=g7.23(a);if(1b){if(m.7M(b)){a=[y.21(1b[1])];m.fn.3a.1h(a,b,T)}R{a=[25.21(1b[1])]}}R{1b=m.aA([d[1]],[25]);a=(1b.5O?m.2P(1b.2s):1b.2s).3D}6 m.6o(O,a)}R{U=y.5u(d[2]);if(U&&U.1g){if(U.id!==d[2]){6 c.2k(a)}O.Q=1;O[0]=U}O.3g=y;O.2b=a;6 O}}R if(!b||b.72){6(b||c).2k(a)}R{6 O.4X(b).2k(a)}}R if(m.1I(a)){6 c.3f(a)}if(a.2b!==x){O.2b=a.2b;O.3g=a.3g}6 m.4N(a,O)},2b:"",72:"1.7.2",Q:0,lk:N(){6 O.Q},cK:N(){6 2g.1h(O,0)},1M:N(a){6 a==V?O.cK():(a<0?O[O.Q+a]:O[a])},3i:N(a,b,c){P d=O.4X();if(m.2T(a)){1y.1S(d,a)}R{m.6o(d,a)}d.bI=O;d.3g=O.3g;if(b==="2k"){d.2b=O.2b+(O.2b?" ":"")+c}R if(b){d.2b=O.2b+"."+b+"("+c+")"}6 d},1i:N(a,b){6 m.1i(O,a,b)},3f:N(a){m.bz();7S.26(a);6 O},eq:N(i){i=+i;6 i===-1?O.2g(i):O.2g(i,i+1)},2r:N(){6 O.eq(0)},4l:N(){6 O.eq(-1)},2g:N(){6 O.3i(2g.1S(O,1n),"2g",2g.1h(1n).5x(","))},3k:N(b){6 O.3i(m.3k(O,N(a,i){6 b.1h(a,i,a)}))},38:N(){6 O.bI||O.4X(V)},1y:1y,5G:[].5G,3n:[].3n};m.fn.4W.2L=m.fn;m.1x=m.fn.1x=N(){P a,1k,3s,3x,a7,2P,1U=1n[0]||{},i=1,Q=1n.Q,4E=18;if(1a 1U==="7v"){4E=1U;1U=1n[1]||{};i=2}if(1a 1U!=="2l"&&!m.1I(1U)){1U={}}if(Q===i){1U=O;--i}12(;i<Q;i++){if((a=1n[i])!=V){12(1k in a){3s=1U[1k];3x=a[1k];if(1U===3x){5s}if(4E&&3x&&(m.7M(3x)||(a7=m.2T(3x)))){if(a7){a7=18;2P=3s&&m.2T(3s)?3s:[]}R{2P=3s&&m.7M(3s)?3s:{}}1U[1k]=m.1x(4E,2P,3x)}R if(3x!==x){1U[1k]=3x}}}}6 1U};m.1x({lj:N(a){if(w.$===m){w.$=4b$}if(a&&w.5E===m){w.5E=ga}6 m},a6:18,a5:1,li:N(a){if(a){m.a5++}R{m.3f(T)}},3f:N(a){if((a===T&&!--m.a5)||(a!==T&&!m.a6)){if(!y.1t){6 5I(m.3f,1)}m.a6=T;if(a!==T&&--m.a5>0){6}7S.6S(y,[m]);if(m.fn.2X){m(y).2X("3f").45("3f")}}},bz:N(){if(7S){6}7S=m.5A("4P 2o");if(y.48==="2S"){6 5I(m.3f,1)}if(y.51){y.51("4o",4o,18);w.51("7b",m.3f,18)}R if(y.4v){y.4v("74",4o);w.4v("a2",m.3f);P a=18;2e{a=w.lh==V}2d(e){}if(y.2c.g8&&a){aZ()}}},1I:N(a){6 m.14(a)==="N"},2T:3v.2T||N(a){6 m.14(a)==="gu"},5h:N(a){6 a!=V&&a==a.gN},a1:N(a){6!cR(22(a))&&lg(a)},14:N(a){6 a==V?9V(a):aU[4Y.1h(a)]||"2l"},7M:N(a){if(!a||m.14(a)!=="2l"||a.1d||m.5h(a)){6 18}2e{if(a.4X&&!9F.1h(a,"4X")&&!9F.1h(a.4X.2L,"lf")){6 18}}2d(e){6 18}P b;12(b in a){}6 b===x||9F.1h(a,b)},8d:N(a){12(P b in a){6 18}6 T},2m:N(a){b6 2i b9(a);},bw:N(a){if(1a a!=="1v"||!a){6 V}a=m.3H(a);if(w.a0&&w.a0.ej){6 w.a0.ej(a)}if(dg.1c(a.1m(dy,"@").1m(eu,"]").1m(dJ,""))){6(2i ey("6 "+a))()}m.2m("eB a0: "+a)},eH:N(a){if(1a a!=="1v"||!a){6 V}P b,3M;2e{if(w.fb){3M=2i fb();b=3M.le(a,"1Z/3c")}R{b=2i 9X("fO.ld");b.46="18";b.lc(a)}}2d(e){b=x}if(!b||!b.2c||b.28("ge").Q){m.2m("eB lb: "+a)}6 b},bi:N(){},bn:N(b){if(b&&cZ.1c(b)){(w.la||N(a){w["l9"].1h(w,a)})(b)}},4S:N(a){6 a.1m(df,"cq-").1m(g9,dx)},1j:N(a,b){6 a.1j&&a.1j.9y()===b.9y()},1i:N(a,b,c){P d,i=0,Q=a.Q,bM=Q===x||m.1I(a);if(c){if(bM){12(d in a){if(b.1S(a[d],c)===18){2h}}}R{12(;i<Q;){if(b.1S(a[i++],c)===18){2h}}}}R{if(bM){12(d in a){if(b.1h(a[d],d,a[d])===18){2h}}}R{12(;i<Q;){if(b.1h(a[i],i,a[i++])===18){2h}}}}6 a},3H:3H?N(a){6 a==V?"":3H.1h(a)}:N(a){6 a==V?"":a.4Y().1m(cD,"").1m(cB,"")},4N:N(a,b){P c=b||[];if(a!=V){P d=m.14(a);if(a.Q==V||d==="1v"||d==="N"||d==="l8"||m.5h(a)){1y.1h(c,a)}R{m.6o(c,a)}}6 c},5N:N(a,b,i){P c;if(b){if(2w){6 2w.1h(b,a,i)}c=b.Q;i=i?i<0?4r.5f(0,c+i):i:0;12(;i<c;i++){if(i in b&&b[i]===a){6 i}}}6-1},6o:N(a,b){P i=a.Q,j=0;if(1a b.Q==="3P"){12(P l=b.Q;j<l;j++){a[i++]=b[j]}}R{1Y(b[j]!==x){a[i++]=b[j++]}}a.Q=i;6 a},57:N(a,b,c){P d=[],b4;c=!!c;12(P i=0,Q=a.Q;i<Q;i++){b4=!!b(a[i],i);if(c!==b4){d.1y(a[i])}}6 d},3k:N(a,b,c){P d,2Q,1b=[],i=0,Q=a.Q,2T=a 6Z m||Q!==x&&1a Q==="3P"&&((Q>0&&a[0]&&a[Q-1])||Q===0||m.2T(a));if(2T){12(;i<Q;i++){d=b(a[i],i,c);if(d!=V){1b[1b.Q]=d}}}R{12(2Q in a){d=b(a[2Q],2Q,c);if(d!=V){1b[1b.Q]=d}}}6 1b.5X.1S([],1b)},1T:1,7N:N(a,b){if(1a b==="1v"){P c=a[b];b=a;a=c}if(!m.1I(a)){6 x}P d=2g.1h(1n,2),7N=N(){6 a.1S(b,d.5X(2g.1h(1n)))};7N.1T=a.1T=a.1T||7N.1T||m.1T++;6 7N},44:N(d,e,f,g,h,j,k){P l,cb=f==V,i=0,Q=d.Q;if(f&&1a f==="2l"){12(i in f){m.44(d,e,i,f[i],1,j,g)}h=1}R if(g!==x){l=k===x&&m.1I(g);if(cb){if(l){l=e;e=N(a,b,c){6 l.1h(m(a),c)}}R{e.1h(d,g);e=V}}if(e){12(;i<Q;i++){e(d[i],f,l?g.1h(d[i],i,e(d[i],f)):g,k)}}h=1}6 h?d:cb?e.1h(d):Q?e(d[0],f):j},3h:N(){6(2i cL()).fS()},fT:N(a){a=a.1s();P b=e0.23(a)||eb.23(a)||eI.23(a)||a.2w("l7")<0&&fw.23(a)||[];6{5a:b[1]||"",97:b[2]||"0"}},d2:N(){N 2Y(a,b){6 2i 2Y.fn.4W(a,b)}m.1x(T,2Y,O);2Y.l6=O;2Y.fn=2Y.2L=O();2Y.fn.4X=2Y;2Y.d2=O.d2;2Y.fn.4W=N 4W(a,b){if(b&&b 6Z m&&!(b 6Z 2Y)){b=2Y(b)}6 m.fn.4W.1h(O,a,b,c)};2Y.fn.4W.2L=2Y.fn;P c=2Y(y);6 2Y},5a:{}});m.1i("l5 l4 9V ey 3v cL 4Q 9D".2f(" "),N(i,a){aU["[2l "+a+"]"]=a.1s()});87=m.fT(bb);if(87.5a){m.5a[87.5a]=T;m.5a.97=87.97}if(m.5a.e2){m.5a.l2=T}if(cZ.1c("\\aG")){cD=/^[\\s\\aG]+/;cB=/[\\s\\aG]+$/}bq=m(y);if(y.51){4o=N(){y.7O("4o",4o,18);m.3f()}}R if(y.4v){4o=N(){if(y.48==="2S"){y.aT("74",4o);m.3f()}}}N aZ(){if(m.a6){6}2e{y.2c.g8("1o")}2d(e){5I(aZ,1);6}m.3f()}6 m})();P A={};N dn(a){P b=A[a]={},i,Q;a=a.2f(/\\s+/);12(i=0,Q=a.Q;i<Q;i++){b[a[i]]=T}6 b}z.5A=N(c){c=c?(A[c]||dn(c)):{};P d=[],4x=[],2o,6J,6H,9T,6w,5Z,26=N(a){P i,Q,U,14,l1;12(i=0,Q=a.Q;i<Q;i++){U=a[i];14=z.14(U);if(14==="gu"){26(U)}R if(14==="N"){if(!c.6j||!4c.9S(U)){d.1y(U)}}}},6l=N(a,b){b=b||[];2o=!c.2o||[a,b];6J=T;6H=T;5Z=9T||0;9T=0;6w=d.Q;12(;d&&5Z<6w;5Z++){if(d[5Z].1S(a,b)===18&&c.kQ){2o=T;2h}}6H=18;if(d){if(!c.4P){if(4x&&4x.Q){2o=4x.4I();4c.6S(2o[0],2o[1])}}R if(2o===T){4c.88()}R{d=[]}}},4c={26:N(){if(d){P a=d.Q;26(1n);if(6H){6w=d.Q}R if(2o&&2o!==T){9T=a;6l(2o[0],2o[1])}}6 O},2C:N(){if(d){P a=1n,9R=0,f9=a.Q;12(;9R<f9;9R++){12(P i=0;i<d.Q;i++){if(a[9R]===d[i]){if(6H){if(i<=6w){6w--;if(i<=5Z){5Z--}}}d.3n(i--,1);if(c.6j){2h}}}}}6 O},9S:N(a){if(d){P i=0,Q=d.Q;12(;i<Q;i++){if(a===d[i]){6 T}}}6 18},77:N(){d=[];6 O},88:N(){d=4x=2o=x;6 O},3A:N(){6!d},aa:N(){4x=x;if(!2o||2o===T){4c.88()}6 O},kP:N(){6!4x},6S:N(a,b){if(4x){if(6H){if(!c.4P){4x.1y([a,b])}}R if(!(c.4P&&2o)){6l(a,b)}}6 O},6l:N(){4c.6S(O,1n);6 O},6J:N(){6!!6J}};6 4c};P B=[].2g;z.1x({7f:N(h){P i=z.5A("4P 2o"),7n=z.5A("4P 2o"),7o=z.5A("2o"),27="kO",9Q={6x:i,9P:7n,aX:7o},2q={2B:i.26,6B:7n.26,9O:7o.26,27:N(){6 27},gB:i.6J,kN:7n.6J,9M:N(a,b,c){1X.2B(a).6B(b).9O(c);6 O},db:N(){1X.2B.1S(1X,1n).6B.1S(1X,1n);6 O},kM:N(e,f,g){6 z.7f(N(d){z.1i({2B:[e,"6x"],6B:[f,"9P"],9O:[g,"aX"]},N(a,b){P c=b[0],bB=b[1],6L;if(z.1I(c)){1X[a](N(){6L=c.1S(O,1n);if(6L&&z.1I(6L.2q)){6L.2q().9M(d.6x,d.9P,d.aX)}R{d[bB+"9H"](O===1X?d:O,[6L])}})}R{1X[a](d[bB])}})}).2q()},2q:N(a){if(a==V){a=2q}R{12(P b in 2q){a[b]=2q[b]}}6 a}},1X=2q.2q({}),2Q;12(2Q in 9Q){1X[2Q]=9Q[2Q].6l;1X[2Q+"9H"]=9Q[2Q].6S}1X.2B(N(){27="kL"},7n.88,7o.aa).6B(N(){27="kK"},i.88,7o.aa);if(h){h.1h(1X,1X)}6 1X},kJ:N(b){P c=B.1h(1n,0),i=0,Q=c.Q,cH=2i 3v(Q),3r=Q,kI=Q,1X=Q<=1&&b&&z.1I(b.2q)?b:z.7f(),2q=1X.2q();N dG(i){6 N(a){c[i]=1n.Q>1?B.1h(1n,0):a;if(!(--3r)){1X.7e(1X,c)}}}N dR(i){6 N(a){cH[i]=1n.Q>1?B.1h(1n,0):a;1X.kE(2q,cH)}}if(Q>1){12(;i<Q;i++){if(c[i]&&c[i].2q&&z.1I(c[i].2q)){c[i].2q().9M(dG(i),1X.9P,dR(i))}R{--3r}}if(!3r){1X.7e(1X,c)}}R if(1X!==b){1X.7e(1X,Q?[b]:[])}6 2q}});z.1q=(N(){P b,4L,a,2Z,7w,1D,2s,6Q,1P,7H,i,64,19=y.21("19"),2c=y.2c;19.41("1V","t");19.31="   <b3/><2E></2E><a 3p=\'/a\' 17=\'1G:9B;9A:1o;2p:.55;\'>a</a><1D 14=\'4n\'/>";4L=19.28("*");a=19.28("a")[0];if(!4L||!4L.Q||!a){6{}}2Z=y.21("2Z");7w=2Z.36(y.21("30"));1D=19.28("1D")[0];b={bL:(19.1W.1d===3),2z:!19.28("2z").Q,gr:!!19.28("b3").Q,17:/1G/.1c(a.2y("17")),gx:(a.2y("3p")==="/a"),2p:/^0.55/.1c(a.17.2p),7j:!!a.17.7j,gM:(1D.1w==="3b"),gV:7w.4i,7p:19.1V!=="t",9x:!!y.21("3N").9x,cU:y.21("9v").6t(T).9t!=="<:9v></:9v>",dp:T,dq:T,dr:18,6f:T,ac:T,af:18,ag:18,aj:T,aq:T};z.5L=b.5L=(y.kx==="kw");1D.2F=T;b.e7=1D.6t(T).2F;2Z.3A=T;b.e9=!7w.3A;2e{32 19.1c}2d(e){b.6f=18}if(!19.51&&19.4v&&19.ec){19.4v("ef",N(){b.ac=18});19.6t(T).ec("ef")}1D=y.21("1D");1D.1w="t";1D.41("14","4e");b.eo=1D.1w==="t";1D.41("2F","2F");1D.41("1k","t");19.36(1D);2s=y.9s();2s.36(19.9q);b.aR=2s.6t(T).6t(T).9q.2F;b.ex=1D.2F;2s.3j(1D);2s.36(19);if(19.4v){12(i in{5w:1,73:1,9p:1}){7H="3b"+i;64=(7H in 19);if(!64){19.41(7H,"6;");64=(1a 19[7H]==="N")}b[i+"kv"]=64}}2s.3j(19);2s=2Z=7w=19=1D=V;z(N(){P a,5U,3Q,2E,3G,71,5D,9o,17,2N,9l,9k,6X,1t=y.28("1t")[0];if(!1t){6}9o=1;6X="5Y:0;4m:0;63:";9l="37:c4;1G:0;1o:0;29:9B;3o:9B;";9k=6X+"0;gf:2H;";17="17=\'"+9l+6X+"gn ku #kt;";2N="<19 "+17+"1C:6V;\'><19 17=\'"+6X+"0;1C:6V;3B:2H;\'></19></19>"+"<2E "+17+"\' gG=\'0\' gK=\'0\'>"+"<4T><3G></3G></4T></2E>";a=y.21("19");a.17.9j=9k+"29:0;3o:0;37:9i;1G:0;4m-1G:"+9o+"42";1t.4p(a,1t.1W);19=y.21("19");a.36(19);19.31="<2E><4T><3G 17=\'"+6X+"0;1C:40\'></3G><3G>t</3G></4T></2E>";6Q=19.28("3G");64=(6Q[0].9h===0);6Q[0].17.1C="";6Q[1].17.1C="40";b.dd=64&&(6Q[0].9h===0);if(w.3R){19.31="";5D=y.21("19");5D.17.29="0";5D.17.6U="0";19.17.29="ks";19.36(5D);b.aj=(dj((w.3R(5D,V)||{6U:0}).6U,10)||0)===0}if(1a 19.17.6c!=="2I"){19.31="";19.17.29=19.17.5Y="9B";19.17.63=0;19.17.3B="2H";19.17.1C="82";19.17.6c=1;b.af=(19.6R===3);19.17.1C="6V";19.17.3B="ax";19.31="<19 17=\'29:gn;\'></19>";b.ag=(19.6R!==3)}19.17.9j=9l+9k;19.31=2N;5U=19.1W;3Q=5U.1W;3G=5U.3z.1W.1W;71={dv:(3Q.3X!==5),dA:(3G.3X===5)};3Q.17.37="8l";3Q.17.1G="kr";71.aO=(3Q.3X===20||3Q.3X===15);3Q.17.37=3Q.17.1G="";5U.17.3B="2H";5U.17.37="3W";71.dS=(3Q.3X===-5);71.dU=(1t.3X!==9o);if(w.3R){19.17.6O="1%";b.aq=(w.3R(19,V)||{6O:0}).6O!=="1%"}if(1a a.17.6c!=="2I"){a.17.6c=1}1t.3j(a);5D=19=a=V;z.1x(b,71)});6 b})();P C=/^(?:\\{.*\\}|\\[.*\\])$/,dX=/([A-Z])/g;z.1x({1B:{},e1:0,2x:"5E"+(z.fn.72+4r.e4()).1m(/\\D/g,""),b1:{"b2":T,"2l":"ko:kn-km-kl-kk-kj","ki":T},bh:N(a){a=a.1d?z.1B[a[z.2x]]:a[z.2x];6!!a&&!9c(a)},1f:N(a,b,c,d){if(!z.7s(a)){6}P e,3L,1b,3w=z.2x,bC=1a b==="1v",4w=a.1d,1B=4w?z.1B:a,id=4w?a[3w]:a[3w]&&3w,bK=b==="1P";if((!id||!1B[id]||(!bK&&!d&&!1B[id].1f))&&bC&&c===x){6}if(!id){if(4w){a[3w]=id=++z.e1}R{id=3w}}if(!1B[id]){1B[id]={};if(!4w){1B[id].fh=z.bi}}if(1a b==="2l"||1a b==="N"){if(d){1B[id]=z.1x(1B[id],b)}R{1B[id].1f=z.1x(1B[id].1f,b)}}e=3L=1B[id];if(!d){if(!3L.1f){3L.1f={}}3L=3L.1f}if(c!==x){3L[z.4S(b)]=c}if(bK&&!3L[b]){6 e.1P}if(bC){1b=3L[b];if(1b==V){1b=3L[z.4S(b)]}}R{1b=3L}6 1b},3S:N(a,b,c){if(!z.7s(a)){6}P d,i,l,3w=z.2x,4w=a.1d,1B=4w?z.1B:a,id=4w?a[3w]:3w;if(!1B[id]){6}if(b){d=c?1B[id]:1B[id].1f;if(d){if(!z.2T(b)){if(b in d){b=[b]}R{b=z.4S(b);if(b in d){b=[b]}R{b=b.2f(" ")}}}12(i=0,l=b.Q;i<l;i++){32 d[b[i]]}if(!(c?9c:z.8d)(d)){6}}}if(!c){32 1B[id].1f;if(!9c(1B[id])){6}}if(z.1q.6f||!1B.fr){32 1B[id]}R{1B[id]=V}if(4w){if(z.1q.6f){32 a[3w]}R if(a.49){a.49(3w)}R{a[3w]=V}}},1z:N(a,b,c){6 z.1f(a,b,c,T)},7s:N(a){if(a.1j){P b=z.b1[a.1j.1s()];if(b){6!(b===T||a.2y("kh")!==b)}}6 T}});z.fn.1x({1f:N(c,d){P e,7B,3a,1k,l,U=O[0],i=0,1f=V;if(c===x){if(O.Q){1f=z.1f(U);if(U.1d===1&&!z.1z(U,"fJ")){3a=U.c6;12(l=3a.Q;i<l;i++){1k=3a[i].1k;if(1k.2w("1f-")===0){1k=z.4S(1k.kg(5));c7(U,1k,1f[1k])}}z.1z(U,"fJ",T)}}6 1f}if(1a c==="2l"){6 O.1i(N(){z.1f(O,c)})}e=c.2f(".",2);e[1]=e[1]?"."+e[1]:"";7B=e[1]+"!";6 z.44(O,N(b){if(b===x){1f=O.96("fU"+7B,[e[0]]);if(1f===x&&U){1f=z.1f(U,c);1f=c7(U,c,1f)}6 1f===x&&e[1]?O.1f(e[0]):1f}e[1]=b;O.1i(N(){P a=z(O);a.96("g2"+7B,e);z.1f(O,c,b);a.96("g5"+7B,e)})},V,d,1n.Q>1,V,18)},3S:N(a){6 O.1i(N(){z.3S(O,a)})}});N c7(a,b,c){if(c===x&&a.1d===1){P d="1f-"+b.1m(dX,"-$1").1s();c=a.2y(d);if(1a c==="1v"){2e{c=c==="T"?T:c==="18"?18:c==="V"?V:z.a1(c)?+c:C.1c(c)?z.bw(c):c}2d(e){}z.1f(a,b,c)}R{c=x}}6 c}N 9c(a){12(P b in a){if(b==="1f"&&z.8d(a[b])){5s}if(b!=="fh"){6 18}}6 T}N cg(a,b,c){P d=b+"6I",7I=b+"24",7K=b+"68",6I=z.1z(a,d);if(6I&&(c==="24"||!z.1z(a,7I))&&(c==="68"||!z.1z(a,7K))){5I(N(){if(!z.1z(a,7I)&&!z.1z(a,7K)){z.3S(a,d,T);6I.6l()}},0)}}z.1x({gc:N(a,b){if(a){b=(b||"fx")+"68";z.1z(a,b,(z.1z(a,b)||0)+1)}},cN:N(a,b,c){if(a!==T){c=b;b=a;a=18}if(b){c=c||"fx";P d=c+"68",3r=a?0:((z.1z(b,d)||1)-1);if(3r){z.1z(b,d,3r)}R{z.3S(b,d,T);cg(b,c,"68")}}},24:N(a,b,c){P q;if(a){b=(b||"fx")+"24";q=z.1z(a,b);if(c){if(!q||z.2T(c)){q=z.1z(a,b,z.4N(c))}R{q.1y(c)}}6 q||[]}},6b:N(a,b){b=b||"fx";P c=z.24(a,b),fn=c.4I(),1J={};if(fn==="cX"){fn=c.4I()}if(fn){if(b==="fx"){c.4z("cX")}z.1z(a,b+".93",1J);fn.1h(a,N(){z.6b(a,b)},1J)}if(!c.Q){z.3S(a,b+"24 "+b+".93",T);cg(a,b,"24")}}});z.fn.1x({24:N(b,c){P d=2;if(1a b!=="1v"){c=b;b="fx";d--}if(1n.Q<d){6 z.24(O[0],b)}6 c===x?O:O.1i(N(){P a=z.24(O,b,c);if(b==="fx"&&a[0]!=="cX"){z.6b(O,b)}})},6b:N(a){6 O.1i(N(){z.6b(O,a)})},ke:N(d,e){d=z.fx?z.fx.7V[d]||d:d;e=e||"fx";6 O.24(e,N(a,b){P c=5I(a,d);b.5q=N(){gJ(c)}})},kd:N(a){6 O.24(a||"fx",[])},2q:N(a,b){if(1a a!=="1v"){b=a;a=x}a=a||"fx";P c=z.7f(),47=O,i=47.Q,3r=1,ad=a+"6I",7I=a+"24",7K=a+"68",3M;N 6x(){if(!(--3r)){c.7e(47,[47])}}1Y(i--){if((3M=z.1f(47[i],ad,x,T)||(z.1f(47[i],7I,x,T)||z.1f(47[i],7K,x,T))&&z.1f(47[i],ad,z.5A("4P 2o"),T))){3r++;3M.26(6x)}}6x();6 c.2q(b)}});P D=/[\\n\\t\\r]/g,7Z=/\\s+/,gQ=/\\r/g,gR=/^(?:2v|1D)$/i,gW=/^(?:2v|1D|2l|2Z|83)$/i,d9=/^a(?:kc)?$/i,ao=/^(?:kb|ka|46|2F|k9|6I|3A|2H|k6|av|aw|dl|k2|k1|4i)$/i,7p=z.1q.7p,43,aB,aC;z.fn.1x({3a:N(a,b){6 z.44(O,z.3a,a,b,1n.Q>1)},8e:N(a){6 O.1i(N(){z.8e(O,a)})},1O:N(a,b){6 z.44(O,z.1O,a,b,1n.Q>1)},k0:N(a){a=z.6A[a]||a;6 O.1i(N(){2e{O[a]=x;32 O[a]}2d(e){}})},aH:N(a){P b,i,l,U,8g,c,cl;if(z.1I(a)){6 O.1i(N(j){z(O).aH(a.1h(O,j,O.1V))})}if(a&&1a a==="1v"){b=a.2f(7Z);12(i=0,l=O.Q;i<l;i++){U=O[i];if(U.1d===1){if(!U.1V&&b.Q===1){U.1V=a}R{8g=" "+U.1V+" ";12(c=0,cl=b.Q;c<cl;c++){if(!~8g.2w(" "+b[c]+" ")){8g+=b[c]+" "}}U.1V=z.3H(8g)}}}}6 O},aP:N(a){P b,i,l,U,1V,c,cl;if(z.1I(a)){6 O.1i(N(j){z(O).aP(a.1h(O,j,O.1V))})}if((a&&1a a==="1v")||a===x){b=(a||"").2f(7Z);12(i=0,l=O.Q;i<l;i++){U=O[i];if(U.1d===1&&U.1V){if(a){1V=(" "+U.1V+" ").1m(D," ");12(c=0,cl=b.Q;c<cl;c++){1V=1V.1m(" "+b[c]+" "," ")}U.1V=z.3H(1V)}R{U.1V=""}}}}6 O},dI:N(b,c){P d=1a b,6z=1a c==="7v";if(z.1I(b)){6 O.1i(N(i){z(O).dI(b.1h(O,i,O.1V,c),c)})}6 O.1i(N(){if(d==="1v"){P a,i=0,4c=z(O),27=c,dM=b.2f(7Z);1Y((a=dM[i++])){27=6z?27:!4c.dO(a);4c[27?"aH":"aP"](a)}}R if(d==="2I"||d==="7v"){if(O.1V){z.1z(O,"dP",O.1V)}O.1V=O.1V||b===18?"":z.1z(O,"dP")||""}})},dO:N(a){P b=" "+a+" ",i=0,l=O.Q;12(;i<l;i++){if(O[i].1d===1&&(" "+O[i].1V+" ").1m(D," ").2w(b)>-1){6 T}}6 18},1L:N(c){P d,1b,1I,U=O[0];if(!1n.Q){if(U){d=z.4C[U.14]||z.4C[U.1j.1s()];if(d&&"1M"in d&&(1b=d.1M(U,"1w"))!==x){6 1b}1b=U.1w;6 1a 1b==="1v"?1b.1m(gQ,""):1b==V?"":1b}6}1I=z.1I(c);6 O.1i(N(i){P b=z(O),1L;if(O.1d!==1){6}if(1I){1L=c.1h(O,i,b.1L())}R{1L=c}if(1L==V){1L=""}R if(1a 1L==="3P"){1L+=""}R if(z.2T(1L)){1L=z.3k(1L,N(a){6 a==V?"":a+""})}d=z.4C[O.14]||z.4C[O.1j.1s()];if(!d||!("1A"in d)||d.1A(O,1L,"1w")===x){O.1w=1L}})}});z.1x({4C:{30:{1M:N(a){P b=a.c6.1w;6!b||b.aW?a.1w:a.1Z}},2Z:{1M:N(a){P b,i,5f,30,52=a.7d,91=[],1K=a.1K,5F=a.14==="2Z-5F";if(52<0){6 V}i=5F?52:0;5f=5F?52+1:1K.Q;12(;i<5f;i++){30=1K[i];if(30.4i&&(z.1q.e9?!30.3A:30.2y("3A")===V)&&(!30.1g.3A||!z.1j(30.1g,"dZ"))){b=z(30).1L();if(5F){6 b}91.1y(b)}}if(5F&&!91.Q&&1K.Q){6 z(1K[52]).1L()}6 91},1A:N(a,b){P c=z.4N(b);z(a).2k("30").1i(N(){O.4i=z.5N(z(O).1L(),c)>=0});if(!c.Q){a.7d=-1}6 c}}},8Y:{1L:T,1F:T,2N:T,1Z:T,1f:T,29:T,3o:T,39:T},3a:N(a,b,c,d){P e,1J,54,3Y=a.1d;if(!a||3Y===3||3Y===8||3Y===2){6}if(d&&b in z.8Y){6 z(a)[b](c)}if(1a a.2y==="2I"){6 z.1O(a,b,c)}54=3Y!==1||!z.7r(a);if(54){b=b.1s();1J=z.3U[b]||(ao.1c(b)?aB:43)}if(c!==x){if(c===V){z.8e(a,b);6}R if(1J&&"1A"in 1J&&54&&(e=1J.1A(a,c,b))!==x){6 e}R{a.41(b,""+c);6 c}}R if(1J&&"1M"in 1J&&54&&(e=1J.1M(a,b))!==V){6 e}R{e=a.2y(b);6 e===V?x:e}},8e:N(a,b){P c,8V,1k,l,6z,i=0;if(b&&a.1d===1){8V=b.1s().2f(7Z);l=8V.Q;12(;i<l;i++){1k=8V[i];if(1k){c=z.6A[1k]||1k;6z=ao.1c(1k);if(!6z){z.3a(a,1k,"")}a.49(7p?1k:c);if(6z&&c in a){a[c]=18}}}}},3U:{14:{1A:N(a,b){if(gR.1c(a.1j)&&a.1g){z.2m("14 8T jZ\'t be jY")}R if(!z.1q.eo&&b==="4e"&&z.1j(a,"1D")){P c=a.1w;a.41("14",b);if(c){a.1w=c}6 b}}},1w:{1M:N(a,b){if(43&&z.1j(a,"2v")){6 43.1M(a,b)}6 b in a?a.1w:V},1A:N(a,b,c){if(43&&z.1j(a,"2v")){6 43.1A(a,b,c)}a.1w=b}}},6A:{8R:"bH",dl:"jX","12":"ew","5S":"1V",jW:"jV",gK:"jR",gG:"jQ",jP:"jO",jN:"jL",jK:"jI",jH:"f5",f8:"jG"},1O:N(a,b,c){P d,1J,54,3Y=a.1d;if(!a||3Y===3||3Y===8||3Y===2){6}54=3Y!==1||!z.7r(a);if(54){b=z.6A[b]||b;1J=z.7G[b]}if(c!==x){if(1J&&"1A"in 1J&&(d=1J.1A(a,c,b))!==x){6 d}R{6(a[b]=c)}}R{if(1J&&"1M"in 1J&&(d=1J.1M(a,b))!==V){6 d}R{6 a[b]}}},7G:{bH:{1M:N(a){P b=a.5b("8R");6 b&&b.aW?dj(b.1w,10):gW.1c(a.1j)||d9.1c(a.1j)&&a.3p?0:x}}}});z.3U.8R=z.7G.bH;aB={1M:N(a,b){P c,8T=z.1O(a,b);6 8T===T||1a 8T!=="7v"&&(c=a.5b(b))&&c.5V!==18?b.1s():x},1A:N(a,b,c){P d;if(b===18){z.8e(a,c)}R{d=z.6A[c]||c;if(d in a){a[d]=T}a.41(c,c.1s())}6 c}};if(!7p){aC={1k:T,id:T,jF:T};43=z.4C.2v={1M:N(a,b){P c;c=a.5b(b);6 c&&(aC[b]?c.5V!=="":c.aW)?c.5V:x},1A:N(a,b,c){P d=a.5b(c);if(!d){d=y.jE(c);a.jD(d)}6(d.5V=b+"")}};z.3U.8R.1A=43.1A;z.1i(["29","3o"],N(i,c){z.3U[c]=z.1x(z.3U[c],{1A:N(a,b){if(b===""){a.41(c,"8O");6 b}}})});z.3U.f8={1M:43.1M,1A:N(a,b,c){if(b===""){b="18"}43.1A(a,b,c)}}}if(!z.1q.gx){z.1i(["3p","3s","29","3o"],N(i,c){z.3U[c]=z.1x(z.3U[c],{1M:N(a){P b=a.2y(c,2);6 b===V?x:b}})})}if(!z.1q.17){z.3U.17={1M:N(a){6 a.17.9j.1s()||x},1A:N(a,b){6(a.17.9j=""+b)}}}if(!z.1q.gV){z.7G.4i=z.1x(z.7G.4i,{1M:N(a){P b=a.1g;if(b){b.7d;if(b.1g){b.1g.7d}}6 V}})}if(!z.1q.9x){z.6A.9x="jB"}if(!z.1q.gM){z.1i(["4e","4n"],N(){z.4C[O]={1M:N(a){6 a.2y("1w")===V?"3b":a.1w}}})}z.1i(["4e","4n"],N(){z.4C[O]=z.1x(z.4C[O],{1A:N(a,b){if(z.2T(b)){6(a.2F=z.5N(z(a).1L(),b)>=0)}}})});P E=/^(?:83|1D|2Z)$/i,ci=/^([^\\.]*)?(?:\\.(.+))?$/,fI=/(?:^|\\s)ck(\\.\\S+)?\\b/,fK=/^2Q/,fM=/^(?:jA|fQ)|61/,cr=/^(?:jz|jw)$/,fV=/^(\\w*)(?:#([\\w\\-]+))?(?:\\.([\\w\\-]+))?$/,fZ=N(a){P b=fV.23(a);if(b){b[1]=(b[1]||"").1s();b[3]=b[3]&&2i 4Q("(?:^|\\\\s)"+b[3]+"(?:\\\\s|$)")}6 b},g1=N(a,m){P b=a.c6||{};6((!m[1]||a.1j.1s()===m[1])&&(!m[2]||(b.id||{}).1w===m[2])&&(!m[3]||m[3].1c((b["5S"]||{}).1w)))},cA=N(a){6 z.1l.1r.ck?a:a.1m(fI,"8M$1 8L$1")};z.1l={26:N(a,b,c,d,f){P g,4G,1P,t,4H,14,2K,1E,7X,8J,5i,1r;if(a.1d===3||a.1d===8||!b||!c||!(g=z.1z(a))){6}if(c.3C){7X=c;c=7X.3C;f=7X.2b}if(!c.1T){c.1T=z.1T++}1P=g.1P;if(!1P){g.1P=1P={}}4G=g.1R;if(!4G){g.1R=4G=N(e){6 1a z!=="2I"&&(!e||z.1l.8G!==e.14)?z.1l.8F.1S(4G.U,1n):x};4G.U=a}b=z.3H(cA(b)).2f(" ");12(t=0;t<b.Q;t++){4H=ci.23(b[t])||[];14=4H[1];2K=(4H[2]||"").2f(".").5G();1r=z.1l.1r[14]||{};14=(f?1r.6F:1r.8B)||14;1r=z.1l.1r[14]||{};1E=z.1x({14:14,4K:4H[1],1f:d,3C:c,1T:c.1T,2b:f,8J:f&&fZ(f),4d:2K.5x(".")},7X);5i=1P[14];if(!5i){5i=1P[14]=[];5i.4h=0;if(!1r.6g||1r.6g.1h(a,d,2K,4G)===18){if(a.51){a.51(14,4G,18)}R if(a.4v){a.4v("3b"+14,4G)}}}if(1r.26){1r.26.1h(a,1E);if(!1E.3C.1T){1E.3C.1T=c.1T}}if(f){5i.3n(5i.4h++,0,1E)}R{5i.1y(1E)}z.1l.6e[14]=T}a=V},6e:{},2C:N(a,b,c,d,e){P f=z.bh(a)&&z.1z(a),t,4H,14,4K,2K,ah,j,1P,1r,1R,4J,1E;if(!f||!(1P=f.1P)){6}b=z.3H(cA(b||"")).2f(" ");12(t=0;t<b.Q;t++){4H=ci.23(b[t])||[];14=4K=4H[1];2K=4H[2];if(!14){12(14 in 1P){z.1l.2C(a,14+b[t],c,d,T)}5s}1r=z.1l.1r[14]||{};14=(d?1r.6F:1r.8B)||14;4J=1P[14]||[];ah=4J.Q;2K=2K?2i 4Q("(^|\\\\.)"+2K.2f(".").5G().5x("\\\\.(?:.*\\\\.)?")+"(\\\\.|$)"):V;12(j=0;j<4J.Q;j++){1E=4J[j];if((e||4K===1E.4K)&&(!c||c.1T===1E.1T)&&(!2K||2K.1c(1E.4d))&&(!d||d===1E.2b||d==="**"&&1E.2b)){4J.3n(j--,1);if(1E.2b){4J.4h--}if(1r.2C){1r.2C.1h(a,1E)}}}if(4J.Q===0&&ah!==4J.Q){if(!1r.6i||1r.6i.1h(a,2K)===18){z.ak(a,14,f.1R)}32 1P[14]}}if(z.8d(1P)){1R=f.1R;if(1R){1R.U=V}z.3S(a,["1P","1R"],T)}},gS:{"fU":T,"g2":T,"g5":T},2X:N(a,b,c,d){if(c&&(c.1d===3||c.1d===8)){6}P e=a.14||a,2K=[],1B,8i,i,1e,2j,5j,1r,1R,67,7a;if(cr.1c(e+z.1l.8G)){6}if(e.2w("!")>=0){e=e.2g(0,-1);8i=T}if(e.2w(".")>=0){2K=e.2f(".");e=2K.4I();2K.5G()}if((!c||z.1l.gS[e])&&!z.1l.6e[e]){6}a=1a a==="2l"?a[z.2x]?a:2i z.5d(e,a):2i z.5d(e);a.14=e;a.7c=T;a.8i=8i;a.4d=2K.5x(".");a.ay=a.4d?2i 4Q("(^|\\\\.)"+2K.5x("\\\\.(?:.*\\\\.)?")+"(\\\\.|$)"):V;5j=e.2w(":")<0?"3b"+e:"";if(!c){1B=z.1B;12(i in 1B){if(1B[i].1P&&1B[i].1P[e]){z.1l.2X(a,b,1B[i].1R.U,T)}}6}a.35=x;if(!a.1U){a.1U=c}b=b!=V?z.4N(b):[];b.4z(a);1r=z.1l.1r[e]||{};if(1r.2X&&1r.2X.1S(c,b)===18){6}67=[[c,1r.8B||e]];if(!d&&!1r.di&&!z.5h(c)){7a=1r.6F||e;1e=cr.1c(7a+e)?c:c.1g;2j=V;12(;1e;1e=1e.1g){67.1y([1e,7a]);2j=1e}if(2j&&2j===c.2A){67.1y([2j.3d||2j.dk||w,7a])}}12(i=0;i<67.Q&&!a.8z();i++){1e=67[i][0];a.14=67[i][1];1R=(z.1z(1e,"1P")||{})[a.14]&&z.1z(1e,"1R");if(1R){1R.1S(1e,b)}1R=5j&&1e[5j];if(1R&&z.7s(1e)&&1R.1S(1e,b)===18){a.59()}}a.14=e;if(!d&&!a.7g()){if((!1r.4D||1r.4D.1S(c.2A,b)===18)&&!(e==="61"&&z.1j(c,"a"))&&z.7s(c)){if(5j&&c[e]&&((e!=="7i"&&e!=="8y")||a.1U.6R!==0)&&!z.5h(c)){2j=c[5j];if(2j){c[5j]=V}z.1l.8G=e;c[e]();z.1l.8G=x;if(2j){c[5j]=2j}}}}6 a.35},8F:N(a){a=z.1l.aK(a||w.1l);P b=((z.1z(O,"1P")||{})[a.14]||[]),4h=b.4h,aM=[].2g.1h(1n,0),du=!a.8i&&!a.4d,1r=z.1l.1r[a.14]||{},7k=[],i,j,1e,7l,1b,7m,6u,2V,1E,6v,7q;aM[0]=a;a.dH=O;if(1r.gX&&1r.gX.1h(O,a)===18){6}if(4h&&!(a.2v&&a.14==="61")){7l=z(O);7l.3g=O.2A||O;12(1e=a.1U;1e!=O;1e=1e.1g||O){if(1e.3A!==T){7m={};2V=[];7l[0]=1e;12(i=0;i<4h;i++){1E=b[i];6v=1E.2b;if(7m[6v]===x){7m[6v]=(1E.8J?g1(1e,1E.8J):7l.is(6v))}if(7m[6v]){2V.1y(1E)}}if(2V.Q){7k.1y({U:1e,2V:2V})}}}}if(b.Q>4h){7k.1y({U:O,2V:b.2g(4h)})}12(i=0;i<7k.Q&&!a.8z();i++){6u=7k[i];a.dK=6u.U;12(j=0;j<6u.2V.Q&&!a.aY();j++){1E=6u.2V[j];if(du||(!a.4d&&!1E.4d)||a.ay&&a.ay.1c(1E.4d)){a.1f=1E.1f;a.1E=1E;1b=((z.1l.1r[1E.4K]||{}).1R||1E.3C).1S(6u.U,aM);if(1b!==x){a.35=1b;if(1b===18){a.59();a.7t()}}}}}if(1r.b0){1r.b0.1h(O,a)}6 a.35},3E:"jv jt js dT jr jo jn dW dK jm ba 8x jl 1U bg jk 7E".2f(" "),8w:{},e5:{3E:"jj bo 2Q e8".2f(" "),1p:N(a,b){if(a.7E==V){a.7E=b.bo!=V?b.bo:b.e8}6 a}},ea:{3E:"2v lv bx ed 76 jg jf bF er je jd ev".2f(" "),1p:N(a,b){P c,25,1t,2v=b.2v,76=b.76;if(a.bF==V&&b.bx!=V){c=a.1U.2A||y;25=c.2c;1t=c.1t;a.bF=b.bx+(25&&25.3J||1t&&1t.3J||0)-(25&&25.6K||1t&&1t.6K||0);a.er=b.ed+(25&&25.3O||1t&&1t.3O||0)-(25&&25.6M||1t&&1t.6M||0)}if(!a.8x&&76){a.8x=76===a.1U?b.ev:76}if(!a.7E&&2v!==x){a.7E=(2v&1?1:(2v&2?3:(2v&4?2:0)))}6 a}},aK:N(a){if(a[z.2x]){6 a}P i,1O,4a=a,7Q=z.1l.8w[a.14]||{},3x=7Q.3E?O.3E.5X(7Q.3E):O.3E;a=z.5d(4a);12(i=3x.Q;i;){1O=3x[--i];a[1O]=4a[1O]}if(!a.1U){a.1U=4a.dT||y}if(a.1U.1d===3){a.1U=a.1U.1g}if(a.ba===x){a.ba=a.dW}6 7Q.1p?7Q.1p(a,4a):a},1r:{3f:{6g:z.bz},7b:{di:T},7i:{6F:"9p"},8y:{6F:"bQ"},jc:{6g:N(a,b,c){if(z.5h(O)){O.bR=c}},6i:N(a,b){if(O.bR===b){O.bR=V}}}},7R:N(a,b,c,d){P e=z.1x(2i z.5d(),c,{14:a,bW:T,4a:{}});if(d){z.1l.2X(e,V,b)}R{z.1l.8F.1h(b,e)}if(e.7g()){c.59()}}};z.1l.1R=z.1l.8F;z.ak=y.7O?N(a,b,c){if(a.7O){a.7O(b,c,18)}}:N(a,b,c){if(a.aT){a.aT("3b"+b,c)}};z.5d=N(a,b){if(!(O 6Z z.5d)){6 2i z.5d(a,b)}if(a&&a.14){O.4a=a;O.14=a.14;O.7g=(a.jb||a.eK===18||a.eP&&a.eP())?7T:5z}R{O.14=a}if(b){z.1x(O,b)}O.bg=a&&a.bg||z.3h();O[z.2x]=T};N 5z(){6 18}N 7T(){6 T}z.5d.2L={59:N(){O.7g=7T;P e=O.4a;if(!e){6}if(e.59){e.59()}R{e.eK=18}},7t:N(){O.8z=7T;P e=O.4a;if(!e){6}if(e.7t){e.7t()}e.ja=T},j8:N(){O.aY=7T;O.7t()},7g:5z,8z:5z,aY:5z};z.1i({8M:"eU",8L:"f2"},N(c,d){z.1l.1r[c]={6F:d,8B:d,1R:N(a){P b=O,7q=a.8x,1E=a.1E,2b=1E.2b,1b;if(!7q||(7q!==b&&!z.2W(b,7q))){a.14=1E.4K;1b=1E.3C.1S(O,1n);a.14=d}6 1b}}});if(!z.1q.dp){z.1l.1r.5w={6g:N(){if(z.1j(O,"3N")){6 18}z.1l.26(O,"61.8r f7.8r",N(e){P b=e.1U,3N=z.1j(b,"1D")||z.1j(b,"2v")?b.3N:x;if(3N&&!3N.c9){z.1l.26(3N,"5w.8r",N(a){a.ca=T});3N.c9=T}})},b0:N(a){if(a.ca){32 a.ca;if(O.1g&&!a.7c){z.1l.7R("5w",O.1g,a,T)}}},6i:N(){if(z.1j(O,"3N")){6 18}z.1l.2C(O,".8r")}}}if(!z.1q.dq){z.1l.1r.73={6g:N(){if(E.1c(O.1j)){if(O.14==="4n"||O.14==="4e"){z.1l.26(O,"j7.7W",N(a){if(a.4a.j6==="2F"){O.cd=T}});z.1l.26(O,"61.7W",N(a){if(O.cd&&!a.7c){O.cd=18;z.1l.7R("73",O,a,T)}})}6 18}z.1l.26(O,"j0.7W",N(e){P b=e.1U;if(E.1c(b.1j)&&!b.cf){z.1l.26(b,"73.7W",N(a){if(O.1g&&!a.bW&&!a.7c){z.1l.7R("73",O.1g,a,T)}});b.cf=T}})},1R:N(a){P b=a.1U;if(O!==b||a.bW||a.7c||(b.14!=="4e"&&b.14!=="4n")){6 a.1E.3C.1S(O,1n)}},6i:N(){z.1l.2C(O,".7W");6 E.1c(O.1j)}}}if(!z.1q.dr){z.1i({7i:"9p",8y:"bQ"},N(b,c){P d=0,3C=N(a){z.1l.7R(c,a.1U,z.1l.aK(a),T)};z.1l.1r[c]={6g:N(){if(d++===0){y.51(b,3C,T)}},6i:N(){if(--d===0){y.7O(b,3C,T)}}}})}z.fn.1x({3b:N(b,c,d,e,f){P g,14;if(1a b==="2l"){if(1a c!=="1v"){d=d||c;c=x}12(14 in b){O.3b(14,c,d,b[14],f)}6 O}if(d==V&&e==V){e=c;d=c=x}R if(e==V){if(1a c==="1v"){e=d;d=x}R{e=d;d=c;c=x}}if(e===18){e=5z}R if(!e){6 O}if(f===1){g=e;e=N(a){z().45(a);6 g.1S(O,1n)};e.1T=g.1T||(g.1T=z.1T++)}6 O.1i(N(){z.1l.26(O,b,e,d,c)})},5F:N(a,b,c,d){6 O.3b(a,b,c,d,1)},45:N(a,b,c){if(a&&a.59&&a.1E){P d=a.1E;z(a.dH).45(d.4d?d.4K+"."+d.4d:d.4K,d.2b,d.3C);6 O}if(1a a==="2l"){12(P e in a){O.45(e,b,a[e])}6 O}if(b===18||1a b==="N"){c=b;b=x}if(c===18){c=5z}6 O.1i(N(){z.1l.2C(O,a,c,b)})},iW:N(a,b,c){6 O.3b(a,V,b,c)},iV:N(a,b){6 O.45(a,V,b)},iU:N(a,b,c){z(O.3g).3b(a,O.2b,b,c);6 O},iT:N(a,b){z(O.3g).45(a,O.2b||"**",b);6 O},iS:N(a,b,c,d){6 O.3b(b,a,c,d)},iR:N(a,b,c){6 1n.Q==1?O.45(a,"**"):O.45(b,a,c)},2X:N(a,b){6 O.1i(N(){z.1l.2X(a,b,O)})},96:N(a,b){if(O[0]){6 z.1l.2X(a,b,O[0],T)}},3K:N(c){P d=1n,1T=c.1T||z.1T++,i=0,cm=N(a){P b=(z.1z(O,"fv"+c.1T)||0)%i;z.1z(O,"fv"+c.1T,b+1);a.59();6 d[b].1S(O,1n)||18};cm.1T=1T;1Y(i<d.Q){d[i++].1T=1T}6 O.61(cm)},ck:N(a,b){6 O.8M(a).8L(b||a)}});z.1i(("8y 7i 9p bQ 7b iO fy fz 61 iN "+"iK iI iH eU f2 8M 8L "+"73 2Z 5w iF f7 iE 2m fQ").2f(" "),N(i,c){z.fn[c]=N(a,b){if(b==V){b=a;a=V}6 1n.Q>0?O.3b(c,V,a,b):O.2X(c)};if(z.8Y){z.8Y[c]=T}if(fK.1c(c)){z.1l.8w[c]=z.1l.e5}if(fM.1c(c)){z.1l.8w[c]=z.1l.ea}});(N(){P k=/((?:\\((?:\\([^()]+\\)|[^()]+)+\\)|\\[(?:\\[[^\\[\\]]*\\]|[\'"][^\'"]*[\'"]|[^\\[\\]\'"]+)+\\]|\\\\.|[^ >+~,(\\[\\\\]+)+|[>+~])(\\s*,\\s*)?((?:.|\\r|\\n)*)/g,2x="iD"+(4r.e4()+\'\').1m(\'.\',\'\'),2B=0,4Y=9D.2L.4Y,84=18,cu=T,5C=/\\\\/g,fP=/\\r\\n/g,86=/\\W/;[0,0].5G(N(){cu=18;6 0});P n=N(a,b,c,d){c=c||[];b=b||y;P e=b;if(b.1d!==1&&b.1d!==9){6[]}if(!a||1a a!=="1v"){6 c}P m,1A,2M,8n,1b,1e,3I,i,cG=T,8a=n.70(b),1u=[],cM=a;do{k.23("");m=k.23(cM);if(m){cM=m[3];1u.1y(m[1]);if(m[2]){8n=m[3];2h}}}1Y(m);if(1u.Q>1&&q.23(a)){if(1u.Q===2&&p.3W[1u[0]]){1A=u(1u[0]+1u[1],b,d)}R{1A=p.3W[1u[0]]?[b]:n(1u.4I(),b);1Y(1u.Q){a=1u.4I();if(p.3W[a]){a+=1u.4I()}1A=u(a,1A,d)}}}R{if(!d&&1u.Q>1&&b.1d===9&&!8a&&p.2a.4k.1c(1u[0])&&!p.2a.4k.1c(1u[1u.Q-1])){1b=n.2k(1u.4I(),b,8a);b=1b.2D?n.1p(1b.2D,1b.1A)[0]:1b.1A[0]}if(b){1b=d?{2D:1u.3I(),1A:s(d)}:n.2k(1u.3I(),1u.Q===1&&(1u[0]==="~"||1u[0]==="+")&&b.1g?b.1g:b,8a);1A=1b.2D?n.1p(1b.2D,1b.1A):1b.1A;if(1u.Q>0){2M=s(1A)}R{cG=18}1Y(1u.Q){1e=1u.3I();3I=1e;if(!p.3W[1e]){1e=""}R{3I=1u.3I()}if(3I==V){3I=b}p.3W[1e](2M,3I,8a)}}R{2M=1u=[]}}if(!2M){2M=1A}if(!2M){n.2m(1e||a)}if(4Y.1h(2M)==="[2l 3v]"){if(!cG){c.1y.1S(c,2M)}R if(b&&b.1d===1){12(i=0;2M[i]!=V;i++){if(2M[i]&&(2M[i]===T||2M[i].1d===1&&n.2W(b,2M[i]))){c.1y(1A[i])}}}R{12(i=0;2M[i]!=V;i++){if(2M[i]&&2M[i].1d===1){c.1y(1A[i])}}}}R{s(2M,c)}if(8n){n(8n,e,c,d);n.cT(c)}6 c};n.cT=N(a){if(t){84=cu;a.5G(t);if(84){12(P i=1;i<a.Q;i++){if(a[i]===a[i-1]){a.3n(i--,1)}}}}6 a};n.2V=N(a,b){6 n(a,V,V,b)};n.8h=N(a,b){6 n(b,V,V,[a]).Q>0};n.2k=N(a,b,c){P d,i,75,2a,r,1o;if(!a){6[]}12(i=0,75=p.9f.Q;i<75;i++){r=p.9f[i];if((2a=p.8m[r].23(a))){1o=2a[1];2a.3n(1,1);if(1o.8k(1o.Q-1)!=="\\\\"){2a[1]=(2a[1]||"").1m(5C,"");d=p.2k[r](2a,b,c);if(d!=V){a=a.1m(p.2a[r],"");2h}}}}if(!d){d=1a b.28!=="2I"?b.28("*"):[]}6{1A:d,2D:a}};n.1p=N(a,b,c,d){P e,62,r,6Y,81,1p,1o,i,8p,2j=a,35=[],58=b,gw=b&&b[0]&&n.70(b[0]);1Y(a&&b.Q){12(r in p.1p){if((e=p.8m[r].23(a))!=V&&e[2]){1p=p.1p[r];1o=e[1];62=18;e.3n(1,1);if(1o.8k(1o.Q-1)==="\\\\"){5s}if(58===35){35=[]}if(p.ae[r]){e=p.ae[r](e,58,c,35,d,gw);if(!e){62=6Y=T}R if(e===T){5s}}if(e){12(i=0;(81=58[i])!=V;i++){if(81){6Y=1p(81,e,i,58);8p=d^6Y;if(c&&6Y!=V){if(8p){62=T}R{58[i]=18}}R if(8p){35.1y(81);62=T}}}}if(6Y!==x){if(!c){58=35}a=a.1m(p.2a[r],"");if(!62){6[]}2h}}}if(a===2j){if(62==V){n.2m(a)}R{2h}}2j=a}6 58};n.2m=N(a){b6 2i b9("iC 2m, iB iA: "+a);};P o=n.gI=N(a){P i,2J,1d=a.1d,1b="";if(1d){if(1d===1||1d===9||1d===11){if(1a a.8v===\'1v\'){6 a.8v}R if(1a a.al===\'1v\'){6 a.al.1m(fP,\'\')}R{12(a=a.1W;a;a=a.3z){1b+=o(a)}}}R if(1d===3||1d===4){6 a.5V}}R{12(i=0;(2J=a[i]);i++){if(2J.1d!==8){1b+=o(2J)}}}6 1b};P p=n.am={9f:["4k","an","6D"],2a:{4k:/#((?:[\\w\\5v-\\5K\\-]|\\\\.)+)/,6q:/\\.((?:[\\w\\5v-\\5K\\-]|\\\\.)+)/,an:/\\[1k=[\'"]*((?:[\\w\\5v-\\5K\\-]|\\\\.)+)[\'"]*\\]/,at:/\\[\\s*((?:[\\w\\5v-\\5K\\-]|\\\\.)+)\\s*(?:(\\S?=)\\s*(?:([\'"])(.*?)\\3|(#?(?:[\\w\\5v-\\5K\\-]|\\\\.)*)|)|)\\s*\\]/,6D:/^((?:[\\w\\5v-\\5K\\*\\-]|\\\\.)+)/,8A:/:(da|5n|4l|2r)-iz(?:\\(\\s*(8D|8E|(?:[+\\-]?\\d+|(?:[+\\-]?\\d*)?n\\s*(?:[+\\-]\\s*\\d+)?))\\s*\\))?/,5l:/:(5n|eq|gt|lt|2r|4l|8D|8E)(?:\\((\\d*)\\))?(?=[^\\-]|$)/,6k:/:((?:[\\w\\5v-\\5K\\-]|\\\\.)+)(?:\\(([\'"]?)((?:\\([^\\)]+\\)|[^\\(\\)]*)+)\\2\\))?/},8m:{},8H:{"5S":"1V","12":"ew"},8I:{3p:N(a){6 a.2y("3p")},14:N(a){6 a.2y("14")}},3W:{"+":N(a,b){P c=1a b==="1v",aD=c&&!86.1c(b),aE=c&&!aD;if(aD){b=b.1s()}12(P i=0,l=a.Q,U;i<l;i++){if((U=a[i])){1Y((U=U.6m)&&U.1d!==1){}a[i]=aE||U&&U.1j.1s()===b?U||18:U===b}}if(aE){n.1p(b,a,T)}},">":N(a,b){P c,8K=1a b==="1v",i=0,l=a.Q;if(8K&&!86.1c(b)){b=b.1s();12(;i<l;i++){c=a[i];if(c){P d=c.1g;a[i]=d.1j.1s()===b?d:18}}}R{12(;i<l;i++){c=a[i];if(c){a[i]=8K?c.1g:c.1g===b}}if(8K){n.1p(b,a,T)}}},"":N(a,b,c){P d,5e=2B++,6n=aI;if(1a b==="1v"&&!86.1c(b)){b=b.1s();d=b;6n=aJ}6n("1g",b,5e,a,d,c)},"~":N(a,b,c){P d,5e=2B++,6n=aI;if(1a b==="1v"&&!86.1c(b)){b=b.1s();d=b;6n=aJ}6n("6m",b,5e,a,d,c)}},2k:{4k:N(a,b,c){if(1a b.5u!=="2I"&&!c){P m=b.5u(a[1]);6 m&&m.1g?[m]:[]}},an:N(a,b){if(1a b.ds!=="2I"){P c=[],8N=b.ds(a[1]);12(P i=0,l=8N.Q;i<l;i++){if(8N[i].2y("1k")===a[1]){c.1y(8N[i])}}6 c.Q===0?V:c}},6D:N(a,b){if(1a b.28!=="2I"){6 b.28(a[1])}}},ae:{6q:N(a,b,c,d,e,f){a=" "+a[1].1m(5C,"")+" ";if(f){6 a}12(P i=0,U;(U=b[i])!=V;i++){if(U){if(e^(U.1V&&(" "+U.1V+" ").1m(/[\\t\\n\\r]/g," ").2w(a)>=0)){if(!c){d.1y(U)}}R if(c){b[i]=18}}}6 18},4k:N(a){6 a[1].1m(5C,"")},6D:N(a,b){6 a[1].1m(5C,"").1s()},8A:N(a){if(a[1]==="5n"){if(!a[2]){n.2m(a[0])}a[2]=a[2].1m(/^\\+|\\s*/g,\'\');P b=/(-?)(\\d*)(?:n([+\\-]?\\d*))?/.23(a[2]==="8D"&&"2n"||a[2]==="8E"&&"2n+1"||!/\\D/.1c(a[2])&&"iy+"+a[2]||a[2]);a[2]=(b[1]+(b[2]||1))-0;a[3]=b[3]-0}R if(a[2]){n.2m(a[0])}a[0]=2B++;6 a},at:N(a,b,c,d,e,f){P g=a[1]=a[1].1m(5C,"");if(!f&&p.8H[g]){a[1]=p.8H[g]}a[4]=(a[4]||a[5]||"").1m(5C,"");if(a[2]==="~="){a[4]=" "+a[4]+" "}6 a},6k:N(a,b,c,d,e){if(a[1]==="6p"){if((k.23(a[3])||"").Q>1||/^\\w/.1c(a[3])){a[3]=n(a[3],V,V,b)}R{P f=n.1p(a[3],b,c,T^e);if(!c){d.1y.1S(d,f)}6 18}}R if(p.2a.5l.1c(a[0])||p.2a.8A.1c(a[0])){6 T}6 a},5l:N(a){a.4z(T);6 a}},4F:{ix:N(a){6 a.3A===18&&a.14!=="2H"},3A:N(a){6 a.3A===T},2F:N(a){6 a.2F===T},4i:N(a){if(a.1g){a.1g.7d}6 a.4i===T},2t:N(a){6!!a.1W},77:N(a){6!a.1W},9S:N(a,i,b){6!!n(b[3],a).Q},dz:N(a){6(/h\\d/i).1c(a.1j)},1Z:N(a){P b=a.2y("14"),r=a.14;6 a.1j.1s()==="1D"&&"1Z"===r&&(b===r||b===V)},4e:N(a){6 a.1j.1s()==="1D"&&"4e"===a.14},4n:N(a){6 a.1j.1s()==="1D"&&"4n"===a.14},aQ:N(a){6 a.1j.1s()==="1D"&&"aQ"===a.14},8Q:N(a){6 a.1j.1s()==="1D"&&"8Q"===a.14},5w:N(a){P b=a.1j.1s();6(b==="1D"||b==="2v")&&"5w"===a.14},dC:N(a){6 a.1j.1s()==="1D"&&"dC"===a.14},dD:N(a){P b=a.1j.1s();6(b==="1D"||b==="2v")&&"dD"===a.14},2v:N(a){P b=a.1j.1s();6 b==="1D"&&"2v"===a.14||b==="2v"},1D:N(a){6(/1D|2Z|83|2v/i).1c(a.1j)},7i:N(a){6 a===a.2A.iw}},dF:{2r:N(a,i){6 i===0},4l:N(a,i,b,c){6 i===c.Q-1},8D:N(a,i){6 i%2===0},8E:N(a,i){6 i%2===1},lt:N(a,i,b){6 i<b[3]-0},gt:N(a,i,b){6 i>b[3]-0},5n:N(a,i,b){6 b[3]-0===i},eq:N(a,i,b){6 b[3]-0===i}},1p:{6k:N(a,b,i,c){P d=b[1],1p=p.4F[d];if(1p){6 1p(a,i,b,c)}R if(d==="2W"){6(a.8v||a.al||o([a])||"").2w(b[3])>=0}R if(d==="6p"){P e=b[3];12(P j=0,l=e.Q;j<l;j++){if(e[j]===a){6 18}}6 T}R{n.2m(d)}},8A:N(a,b){P c,4l,5e,2t,1B,3r,7D,r=b[1],2J=a;iv(r){8S"da":8S"2r":1Y((2J=2J.6m)){if(2J.1d===1){6 18}}if(r==="2r"){6 T}2J=a;8S"4l":1Y((2J=2J.3z)){if(2J.1d===1){6 18}}6 T;8S"5n":c=b[2];4l=b[3];if(c===1&&4l===0){6 T}5e=b[0];2t=a.1g;if(2t&&(2t[2x]!==5e||!a.aV)){3r=0;12(2J=2t.1W;2J;2J=2J.3z){if(2J.1d===1){2J.aV=++3r}}2t[2x]=5e}7D=a.aV-4l;if(c===0){6 7D===0}R{6(7D%c===0&&7D/c>=0)}}},4k:N(a,b){6 a.1d===1&&a.2y("id")===b},6D:N(a,b){6(b==="*"&&a.1d===1)||!!a.1j&&a.1j.1s()===b},6q:N(a,b){6(" "+(a.1V||a.2y("5S"))+" ").2w(b)>-1},at:N(a,b){P c=b[1],35=n.3a?n.3a(a,c):p.8I[c]?p.8I[c](a):a[c]!=V?a[c]:a.2y(c),1w=35+"",r=b[2],3t=b[4];6 35==V?r==="!=":!r&&n.3a?35!=V:r==="="?1w===3t:r==="*="?1w.2w(3t)>=0:r==="~="?(" "+1w+" ").2w(3t)>=0:!3t?1w&&35!==18:r==="!="?1w!==3t:r==="^="?1w.2w(3t)===0:r==="$="?1w.8k(1w.Q-3t.Q)===3t:r==="|="?1w===3t||1w.8k(0,3t.Q+1)===3t+"-":18},5l:N(a,b,i,c){P d=b[2],1p=p.dF[d];if(1p){6 1p(a,i,b,c)}}}};P q=p.2a.5l,dL=N(a,b){6"\\\\"+(b-0+1)};12(P r in p.2a){p.2a[r]=2i 4Q(p.2a[r].8U+(/(?![^\\[]*\\])(?![^\\(]*\\))/.8U));p.8m[r]=2i 4Q(/(^(?:.|\\r|\\n)*?)/.8U+p.2a[r].8U.1m(/\\\\(\\d+)/g,dL))}p.2a.dN=q;P s=N(a,b){a=3v.2L.2g.1h(a,0);if(b){b.1y.1S(b,a);6 b}6 a};2e{3v.2L.2g.1h(y.2c.3D,0)[0].1d}2d(e){s=N(a,b){P i=0,1b=b||[];if(4Y.1h(a)==="[2l 3v]"){3v.2L.1y.1S(1b,a)}R{if(1a a.Q==="3P"){12(P l=a.Q;i<l;i++){1b.1y(a[i])}}R{12(;a[i];i++){1b.1y(a[i])}}}6 1b}}P t,6s;if(y.2c.5H){t=N(a,b){if(a===b){84=T;6 0}if(!a.5H||!b.5H){6 a.5H?-1:1}6 a.5H(b)&4?-1:1}}R{t=N(a,b){if(a===b){84=T;6 0}R if(a.8X&&b.8X){6 a.8X-b.8X}P c,bl,ap=[],bp=[],8Z=a.1g,90=b.1g,1e=8Z;if(8Z===90){6 6s(a,b)}R if(!8Z){6-1}R if(!90){6 1}1Y(1e){ap.4z(1e);1e=1e.1g}1e=90;1Y(1e){bp.4z(1e);1e=1e.1g}c=ap.Q;bl=bp.Q;12(P i=0;i<c&&i<bl;i++){if(ap[i]!==bp[i]){6 6s(ap[i],bp[i])}}6 i===c?6s(a,bp[i],-1):6s(ap[i],b,1)};6s=N(a,b,c){if(a===b){6 c}P d=a.3z;1Y(d){if(d===b){6-1}d=d.3z}6 1}}(N(){P d=y.21("19"),id="1N"+(2i cL()).fS(),3Z=y.2c;d.31="<a 1k=\'"+id+"\'/>";3Z.4p(d,3Z.1W);if(y.5u(id)){p.2k.4k=N(a,b,c){if(1a b.5u!=="2I"&&!c){P m=b.5u(a[1]);6 m?m.id===a[1]||1a m.5b!=="2I"&&m.5b("id").5V===a[1]?[m]:x:[]}};p.1p.4k=N(a,b){P c=1a a.5b!=="2I"&&a.5b("id");6 a.1d===1&&c&&c.5V===b}}3Z.3j(d);3Z=d=V})();(N(){P e=y.21("19");e.36(y.iu(""));if(e.28("*").Q>0){p.2k.6D=N(a,b){P c=b.28(a[1]);if(a[1]==="*"){P d=[];12(P i=0;c[i];i++){if(c[i].1d===1){d.1y(c[i])}}c=d}6 c}}e.31="<a 3p=\'#\'></a>";if(e.1W&&1a e.1W.2y!=="2I"&&e.1W.2y("3p")!=="#"){p.8I.3p=N(a){6 a.2y("3p",2)}}e=V})();if(y.5B){(N(){P h=n,19=y.21("19"),id="it";19.31="<p 5S=\'dY\'></p>";if(19.5B&&19.5B(".dY").Q===0){6}n=N(a,b,c,d){b=b||y;if(!d&&!n.70(b)){P e=/^(\\w+$)|^\\.([\\w\\-]+$)|^#([\\w\\-]+$)/.23(a);if(e&&(b.1d===1||b.1d===9)){if(e[1]){6 s(b.28(a),c)}R if(e[2]&&p.2k.6q&&b.69){6 s(b.69(e[2]),c)}}if(b.1d===9){if(a==="1t"&&b.1t){6 s([b.1t],c)}R if(e&&e[3]){P f=b.5u(e[3]);if(f&&f.1g){if(f.id===e[3]){6 s([f],c)}}R{6 s([],c)}}2e{6 s(b.5B(a),c)}2d(ir){}}R if(b.1d===1&&b.1j.1s()!=="2l"){P g=b,2j=b.2y("id"),7F=2j||id,bd=b.1g,bf=/^\\s*[+~]/.1c(a);if(!2j){b.41("id",7F)}R{7F=7F.1m(/\'/g,"\\\\$&")}if(bf&&bd){b=b.1g}2e{if(!bf||bd){6 s(b.5B("[id=\'"+7F+"\'] "+a),c)}}2d(e3){}iq{if(!2j){g.49("id")}}}}6 h(a,b,c,d)};12(P i in h){n[i]=h[i]}19=V})()}(N(){P d=y.2c,2V=d.8h||d.ip||d.io||d.im;if(2V){P f=!2V.1h(y.21("19"),"19"),bk=18;2e{2V.1h(y.2c,"[1c!=\'\']:il")}2d(e3){bk=T}n.8h=N(a,b){b=b.1m(/\\=\\s*([^\'"\\]]*)\\s*\\]/g,"=\'$1\']");if(!n.70(a)){2e{if(bk||!p.2a.6k.1c(b)&&!/!=/.1c(b)){P c=2V.1h(a,b);if(c||!f||a.56&&a.56.1d!==11){6 c}}}2d(e){}}6 n(b,V,V,[a]).Q>0}}})();(N(){P d=y.21("19");d.31="<19 5S=\'1c e\'></19><19 5S=\'1c\'></19>";if(!d.69||d.69("e").Q===0){6}d.9q.1V="e";if(d.69("e").Q===1){6}p.9f.3n(1,0,"6q");p.2k.6q=N(a,b,c){if(1a b.69!=="2I"&&!c){6 b.69(a[1])}};d=V})();N aJ(a,b,c,d,e,f){12(P i=0,l=d.Q;i<l;i++){P g=d[i];if(g){P h=18;g=g[a];1Y(g){if(g[2x]===c){h=d[g.9b];2h}if(g.1d===1&&!f){g[2x]=c;g.9b=i}if(g.1j.1s()===b){h=g;2h}g=g[a]}d[i]=h}}}N aI(a,b,c,d,e,f){12(P i=0,l=d.Q;i<l;i++){P g=d[i];if(g){P h=18;g=g[a];1Y(g){if(g[2x]===c){h=d[g.9b];2h}if(g.1d===1){if(!f){g[2x]=c;g.9b=i}if(1a b!=="1v"){if(g===b){h=T;2h}}R if(n.1p(b,[g]).Q>0){h=g;2h}}g=g[a]}d[i]=h}}}if(y.2c.2W){n.2W=N(a,b){6 a!==b&&(a.2W?a.2W(b):T)}}R if(y.2c.5H){n.2W=N(a,b){6!!(a.5H(b)&16)}}R{n.2W=N(){6 18}}n.70=N(a){P b=(a?a.2A||a:0).2c;6 b?b.1j!=="ik":18};P u=N(a,b,c){P d,bs=[],bt="",3Z=b.1d?[b]:b;1Y((d=p.2a.6k.23(a))){bt+=d[0];a=a.1m(p.2a.6k,"")}a=p.3W[a]?a+"*":a;12(P i=0,l=3Z.Q;i<l;i++){n(a,3Z[i],bs,c)}6 n.1p(bt,bs)};n.3a=z.3a;n.am.8H={};z.2k=n;z.2D=n.am;z.2D[":"]=z.2D.4F;z.6j=n.cT;z.1Z=n.gI;z.7r=n.70;z.2W=n.2W})();P F=/ij$/,eg=/^(?:eh|ei|bv)/,ek=/,/,el=/^.[^:#\\[\\.,]*$/,2g=3v.2L.2g,5l=z.2D.2a.dN,em={en:T,4t:T,ep:T,4s:T};z.fn.1x({2k:N(a){P b=O,i,l;if(1a a!=="1v"){6 z(a).1p(N(){12(i=0,l=b.Q;i<l;i++){if(z.2W(b[i],O)){6 T}}})}P c=O.3i("","2k",a),Q,n,r;12(i=0,l=O.Q;i<l;i++){Q=c.Q;z.2k(a,O[i],c);if(i>0){12(n=Q;n<c.Q;n++){12(r=0;r<Q;r++){if(c[r]===c[n]){c.3n(n--,1);2h}}}}}6 c},9S:N(a){P b=z(a);6 O.1p(N(){12(P i=0,l=b.Q;i<l;i++){if(z.2W(O,b[i])){6 T}}})},6p:N(a){6 O.3i(by(O,a,18),"6p",a)},1p:N(a){6 O.3i(by(O,a,T),"1p",a)},is:N(a){6!!a&&(1a a==="1v"?5l.1c(a)?z(a,O.3g).52(O[0])>=0:z.1p(a,O).Q>0:O.1p(a).Q>0)},et:N(a,b){P c=[],i,l,1e=O[0];if(z.2T(a)){P d=1;1Y(1e&&1e.2A&&1e!==b){12(i=0;i<a.Q;i++){if(z(1e).is(a[i])){c.1y({2b:a[i],U:1e,ii:d})}}1e=1e.1g;d++}6 c}P e=5l.1c(a)||1a a!=="1v"?z(a,b||O.3g):0;12(i=0,l=O.Q;i<l;i++){1e=O[i];1Y(1e){if(e?e.52(1e)>-1:z.2k.8h(1e,a)){c.1y(1e);2h}R{1e=1e.1g;if(!1e||!1e.2A||1e===b||1e.1d===11){2h}}}}c=c.Q>1?z.6j(c):c;6 O.3i(c,"et",a)},52:N(a){if(!a){6(O[0]&&O[0].1g)?O.bv().Q:-1}if(1a a==="1v"){6 z.5N(O[0],z(a))}6 z.5N(a.72?a[0]:a,O)},26:N(a,b){P c=1a a==="1v"?z(a,b):z.4N(a&&a.1d?[a]:a),4L=z.6o(O.1M(),c);6 O.3i(bA(c[0])||bA(4L[0])?4L:z.6j(4L))},ih:N(){6 O.26(O.bI)}});N bA(a){6!a||!a.1g||a.1g.1d===11}z.1i({2t:N(a){P b=a.1g;6 b&&b.1d!==11?b:V},eh:N(a){6 z.5P(a,"1g")},ie:N(a,i,b){6 z.5P(a,"1g",b)},ep:N(a){6 z.5n(a,2,"3z")},4s:N(a){6 z.5n(a,2,"6m")},ic:N(a){6 z.5P(a,"3z")},bv:N(a){6 z.5P(a,"6m")},ib:N(a,i,b){6 z.5P(a,"3z",b)},ei:N(a,i,b){6 z.5P(a,"6m",b)},ia:N(a){6 z.bG((a.1g||{}).1W,a)},en:N(a){6 z.bG(a.1W)},4t:N(a){6 z.1j(a,"3m")?a.eC||a.eD.56:z.4N(a.3D)}},N(d,e){z.fn[d]=N(a,b){P c=z.3k(O,e,a);if(!F.1c(d)){b=a}if(b&&1a b==="1v"){c=z.1p(b,c)}c=O.Q>1&&!em[d]?z.6j(c):c;if((O.Q>1||ek.1c(b))&&eg.1c(d)){c=c.i9()}6 O.3i(c,d,2g.1h(1n).5x(","))}});z.1x({1p:N(a,b,c){if(c){a=":6p("+a+")"}6 b.Q===1?z.2k.8h(b[0],a)?[b[0]]:[]:z.2k.2V(a,b)},5P:N(a,b,c){P d=[],1e=a[b];1Y(1e&&1e.1d!==9&&(c===x||1e.1d!==1||!z(1e).is(c))){if(1e.1d===1){d.1y(1e)}1e=1e[b]}6 d},5n:N(a,b,c,d){b=b||1;P e=0;12(;a;a=a[c]){if(a.1d===1&&++e===b){2h}}6 a},bG:N(n,a){P r=[];12(;n;n=n.3z){if(n.1d===1&&n!==a){r.1y(n)}}6 r}});N by(c,d,e){d=d||0;if(z.1I(d)){6 z.57(c,N(a,i){P b=!!d.1h(a,i,a);6 b===e})}R if(d.1d){6 z.57(c,N(a,i){6(a===d)===e})}R if(1a d==="1v"){P f=z.57(c,N(a){6 a.1d===1});if(el.1c(d)){6 z.1p(d,f,!e)}R{d=z.1p(d,f)}}6 z.57(c,N(a,i){6(z.5N(a,d)>=0)===e})}N bJ(a){P b=G.2f("|"),9m=a.9s();if(9m.21){1Y(b.Q){9m.21(b.3I())}}6 9m}P G="i8|i6|i5|i3|i2|i1|1f|i0|hZ|hY|hX|hW|"+"dz|hT|68|hR|9v|hO|9O|hN|hM|eZ|hL",f1=/ 5E\\d+="(?:\\d+|V)"/g,9u=/^\\s+/,bT=/<(?!f4|br|f6|b2|hr|hI|1D|b3|hH|7u)(([\\w:]+)[^>]*)\\/>/ig,bZ=/<([\\w:]+)/,fc=/<2z/i,fd=/<|&#?\\w+;/,fe=/<(?:1N|17)/i,ff=/<(?:1N|2l|b2|30|17)/i,c0=2i 4Q("<(?:"+G+")[\\\\s/>]","i"),c1=/2F\\s*(?:[^=]|=\\s*.2F.)/i,c2=/\\/(hG|hF)1N/i,fl=/^\\s*<!(?:\\[hE\\[|\\-\\-)/,3e={30:[1,"<2Z av=\'av\'>","</2Z>"],hD:[1,"<fp>","</fp>"],fq:[1,"<2E>","</2E>"],4T:[2,"<2E><2z>","</2z></2E>"],3G:[3,"<2E><2z><4T>","</4T></2z></2E>"],f6:[2,"<2E><2z></2z><c8>","</c8></2E>"],f4:[1,"<3k>","</3k>"],4D:[0,"",""]},9C=bJ(y);3e.dZ=3e.30;3e.2z=3e.hC=3e.c8=3e.hB=3e.fq;3e.hA=3e.3G;if(!z.1q.gr){3e.4D=[1,"19<19>","</19>"]}z.fn.1x({1Z:N(b){6 z.44(O,N(a){6 a===x?z.1Z(O):O.77().4M((O[0]&&O[0].2A||y).ce(a))},V,b,1n.Q)},9G:N(b){if(z.1I(b)){6 O.1i(N(i){z(O).9G(b.1h(O,i))})}if(O[0]){P c=z(b,O[0].2A).eq(0).2P(T);if(O[0].1g){c.4p(O[0])}c.3k(N(){P a=O;1Y(a.1W&&a.1W.1d===1){a=a.1W}6 a}).4M(O)}6 O},fB:N(b){if(z.1I(b)){6 O.1i(N(i){z(O).fB(b.1h(O,i))})}6 O.1i(N(){P a=z(O),4t=a.4t();if(4t.Q){4t.9G(b)}R{a.4M(b)}})},6N:N(a){P b=z.1I(a);6 O.1i(N(i){z(O).9G(b?a.1h(O,i):a)})},hz:N(){6 O.2t().1i(N(){if(!z.1j(O,"1t")){z(O).8b(O.3D)}}).38()},4M:N(){6 O.65(1n,T,N(a){if(O.1d===1){O.36(a)}})},fG:N(){6 O.65(1n,T,N(a){if(O.1d===1){O.4p(a,O.1W)}})},9K:N(){if(O[0]&&O[0].1g){6 O.65(1n,18,N(a){O.1g.4p(a,O)})}R if(1n.Q){P b=z.9L(1n);b.1y.1S(b,O.cK());6 O.3i(b,"9K",1n)}},cn:N(){if(O[0]&&O[0].1g){6 O.65(1n,18,N(a){O.1g.4p(a,O.3z)})}R if(1n.Q){P b=O.3i(O,"cn",1n);b.1y.1S(b,z.9L(1n));6 b}},2C:N(a,b){12(P i=0,U;(U=O[i])!=V;i++){if(!a||z.1p(a,[U]).Q){if(!b&&U.1d===1){z.7Y(U.28("*"));z.7Y([U])}if(U.1g){U.1g.3j(U)}}}6 O},77:N(){12(P i=0,U;(U=O[i])!=V;i++){if(U.1d===1){z.7Y(U.28("*"))}1Y(U.1W){U.3j(U.1W)}}6 O},2P:N(a,b){a=a==V?18:a;b=b==V?a:b;6 O.3k(N(){6 z.2P(O,a,b)})},2N:N(c){6 z.44(O,N(a){P b=O[0]||{},i=0,l=O.Q;if(a===x){6 b.1d===1?b.31.1m(f1,""):V}if(1a a==="1v"&&!fe.1c(a)&&(z.1q.bL||!9u.1c(a))&&!3e[(bZ.23(a)||["",""])[1].1s()]){a=a.1m(bT,"<$1></$2>");2e{12(;i<l;i++){b=O[i]||{};if(b.1d===1){z.7Y(b.28("*"));b.31=a}}b=0}2d(e){}}if(b){O.77().4M(a)}},V,c,1n.Q)},8b:N(b){if(O[0]&&O[0].1g){if(z.1I(b)){6 O.1i(N(i){P a=z(O),2j=a.2N();a.8b(b.1h(O,i,2j))})}if(1a b!=="1v"){b=z(b).fL()}6 O.1i(N(){P a=O.3z,2t=O.1g;z(O).2C();if(a){z(a).9K(b)}R{z(2t).4M(b)}})}R{6 O.Q?O.3i(z(z.1I(b)?b():b),"8b",b):O}},fL:N(a){6 O.2C(a,T)},65:N(b,c,d){P e,2r,2s,2t,1w=b[0],9N=[];if(!z.1q.aR&&1n.Q===3&&1a 1w==="1v"&&c1.1c(1w)){6 O.1i(N(){z(O).65(b,c,d,T)})}if(z.1I(1w)){6 O.1i(N(i){P a=z(O);b[0]=1w.1h(O,i,c?a.2N():x);a.65(b,c,d)})}if(O[0]){2t=1w&&1w.1g;if(z.1q.1g&&2t&&2t.1d===11&&2t.3D.Q===O.Q){e={2s:2t}}R{e=z.aA(b,O,9N)}2s=e.2s;if(2s.3D.Q===1){2r=2s=2s.1W}R{2r=2s.1W}if(2r){c=c&&z.1j(2r,"4T");12(P i=0,l=O.Q,fN=l-1;i<l;i++){d.1h(c?3Z(O[i],2r):O[i],e.5O||(l>1&&i<fN)?z.2P(2s,T,T):2s)}}if(9N.Q){z.1i(9N,N(i,a){if(a.3s){z.5r({14:"7L",6e:18,1Q:a.3s,46:18,3u:"1N"})}R{z.bn((a.1Z||a.8v||a.31||"").1m(fl,"/*$0*/"))}if(a.1g){a.1g.3j(a)}})}}6 O}});N 3Z(a,b){6 z.1j(a,"2E")?(a.28("2z")[0]||a.36(a.2A.21("2z"))):a}N cv(a,b){if(b.1d!==1||!z.bh(a)){6}P c,i,l,cw=z.1z(a),6r=z.1z(b,cw),1P=cw.1P;if(1P){32 6r.1R;6r.1P={};12(c in 1P){12(i=0,l=1P[c].Q;i<l;i++){z.1l.26(b,c,1P[c][i])}}}if(6r.1f){6r.1f=z.1x({},6r.1f)}}N cy(a,b){P c;if(b.1d!==1){6}if(b.fW){b.fW()}if(b.fX){b.fX(a)}c=b.1j.1s();if(c==="2l"){b.9t=a.9t}R if(c==="1D"&&(a.14==="4n"||a.14==="4e")){if(a.2F){b.fY=b.2F=a.2F}if(b.1w!==a.1w){b.1w=a.1w}}R if(c==="30"){b.4i=a.hy}R if(c==="1D"||c==="83"){b.g0=a.g0}R if(c==="1N"&&b.1Z!==a.1Z){b.1Z=a.1Z}b.49(z.2x);b.49("c9");b.49("cf")}z.aA=N(a,b,c){P d,5O,6h,25,2r=a[0];if(b&&b[0]){25=b[0].2A||b[0]}if(!25.9s){25=y}if(a.Q===1&&1a 2r==="1v"&&2r.Q<hx&&25===y&&2r.d5(0)==="<"&&!ff.1c(2r)&&(z.1q.aR||!c1.1c(2r))&&(z.1q.cU||!c0.1c(2r))){5O=T;6h=z.cC[2r];if(6h&&6h!==1){d=6h}}if(!d){d=25.9s();z.9L(a,25,d,c)}if(5O){z.cC[2r]=6h?d:1}6{2s:d,5O:5O}};z.cC={};z.1i({g4:"4M",hw:"fG",4p:"9K",hv:"cn",hu:"8b"},N(d,e){z.fn[d]=N(a){P b=[],6T=z(a),2t=O.Q===1&&O[0].1g;if(2t&&2t.1d===11&&2t.3D.Q===1&&6T.Q===1){6T[e](O[0]);6 O}R{12(P i=0,l=6T.Q;i<l;i++){P c=(i>0?O.2P(T):O).1M();z(6T[i])[e](c);b=b.5X(c)}6 O.3i(b,d,6T.2b)}}});N 7U(a){if(1a a.28!=="2I"){6 a.28("*")}R if(1a a.5B!=="2I"){6 a.5B("*")}R{6[]}}N cI(a){if(a.14==="4n"||a.14==="4e"){a.fY=a.2F}}N cJ(a){P b=(a.1j||"").1s();if(b==="1D"){cI(a)}R if(b!=="1N"&&1a a.28!=="2I"){z.57(a.28("1D"),cI)}}N gb(a){P b=y.21("19");9C.36(b);b.31=a.9t;6 b.1W}z.1x({2P:N(a,b,c){P d,5y,i,2P=z.1q.cU||z.7r(a)||!c0.1c("<"+a.1j+">")?a.6t(T):gb(a);if((!z.1q.ac||!z.1q.e7)&&(a.1d===1||a.1d===11)&&!z.7r(a)){cy(a,2P);d=7U(a);5y=7U(2P);12(i=0;d[i];++i){if(5y[i]){cy(d[i],5y[i])}}}if(b){cv(a,2P);if(c){d=7U(a);5y=7U(2P);12(i=0;d[i];++i){cv(d[i],5y[i])}}}d=5y=V;6 2P},9L:N(b,c,d,e){P f,1N,j,1b=[];c=c||y;if(1a c.21==="2I"){c=c.2A||c[0]&&c[0].2A||y}12(P i=0,U;(U=b[i])!=V;i++){if(1a U==="3P"){U+=""}if(!U){5s}if(1a U==="1v"){if(!fd.1c(U)){U=c.ce(U)}R{U=U.1m(bT,"<$1></$2>");P g=(bZ.23(U)||["",""])[1].1s(),6N=3e[g]||3e.4D,gd=6N[0],19=c.21("19"),9Y=9C.3D,2C;if(c===y){9C.36(19)}R{bJ(c).36(19)}19.31=6N[1]+U+6N[2];1Y(gd--){19=19.9q}if(!z.1q.2z){P h=fc.1c(U),2z=g==="2E"&&!h?19.1W&&19.1W.3D:6N[1]==="<2E>"&&!h?19.3D:[];12(j=2z.Q-1;j>=0;--j){if(z.1j(2z[j],"2z")&&!2z[j].3D.Q){2z[j].1g.3j(2z[j])}}}if(!z.1q.bL&&9u.1c(U)){19.4p(c.ce(9u.23(U)[0]),19.1W)}U=19.3D;if(19){19.1g.3j(19);if(9Y.Q>0){2C=9Y[9Y.Q-1];if(2C&&2C.1g){2C.1g.3j(2C)}}}}}P k;if(!z.1q.ex){if(U[0]&&1a(k=U.Q)==="3P"){12(j=0;j<k;j++){cJ(U[j])}}R{cJ(U)}}if(U.1d){1b.1y(U)}R{1b=z.6o(1b,U)}}if(d){f=N(a){6!a.14||c2.1c(a.14)};12(i=0;1b[i];i++){1N=1b[i];if(e&&z.1j(1N,"1N")&&(!1N.14||c2.1c(1N.14))){e.1y(1N.1g?1N.1g.3j(1N):1N)}R{if(1N.1d===1){P l=z.57(1N.28("1N"),f);1b.3n.1S(1b,[i+1,0].5X(l))}d.36(1N)}}}6 1b},7Y:N(a){P b,id,1B=z.1B,1r=z.1l.1r,6f=z.1q.6f;12(P i=0,U;(U=a[i])!=V;i++){if(U.1j&&z.b1[U.1j.1s()]){5s}id=U[z.2x];if(id){b=1B[id];if(b&&b.1P){12(P c in b.1P){if(1r[c]){z.1l.2C(U,c)}R{z.ak(U,c,b.1R)}}if(b.1R){b.1R.U=V}}if(6f){32 U[z.2x]}R if(U.49){U.49(z.2x)}32 1B[id]}}}});P H=/gg\\([^)]*\\)/i,gh=/2p=([^)]*)/,gj=/([A-Z]|^cq)/g,gk=/^[\\-+]?(?:\\d*\\.)?\\d+$/i,9Z=/^-?(?:\\d*\\.)?\\d+(?!42)[^\\d\\s]+$/i,gm=/^([\\-+])=([\\-+.\\de]+)/,go=/^4m/,gp={37:"c4",gf:"2H",1C:"6V"},4V=["ht","hs","hq","hp"],4R,3R,2O;z.fn.1F=N(d,e){6 z.44(O,N(a,b,c){6 c!==x?z.17(a,b,c):z.1F(a,b)},d,e,1n.Q>1)};z.1x({4U:{2p:{1M:N(a,b){if(b){P c=4R(a,"2p");6 c===""?"1":c}R{6 a.17.2p}}}},a4:{"ho":T,"hn":T,"hm":T,"2p":T,"hk":T,"hf":T,"hd":T,"6c":T},cW:{"9A":z.1q.7j?"7j":"h5"},17:N(a,b,c,d){if(!a||a.1d===3||a.1d===8||!a.17){6}P f,14,8f=z.4S(b),17=a.17,1J=z.4U[8f];b=z.cW[8f]||8f;if(c!==x){14=1a c;if(14==="1v"&&(f=gm.23(c))){c=(+(f[1]+1)*+f[2])+22(z.1F(a,b));14="3P"}if(c==V||14==="3P"&&cR(c)){6}if(14==="3P"&&!z.a4[8f]){c+="42"}if(!1J||!("1A"in 1J)||(c=1J.1A(a,c))!==x){2e{17[b]=c}2d(e){}}}R{if(1J&&"1M"in 1J&&(f=1J.1M(a,18,d))!==x){6 f}6 17[b]}},1F:N(a,b,c){P d,1J;b=z.4S(b);1J=z.4U[b];b=z.cW[b]||b;if(b==="7j"){b="9A"}if(1J&&"1M"in 1J&&(d=1J.1M(a,T,c))!==x){6 d}R if(4R){6 4R(a,b)}},cY:N(a,b,c){P d={},1b,1k;12(1k in b){d[1k]=a.17[1k];a.17[1k]=b[1k]}1b=c.1h(a);12(1k in b){a.17[1k]=d[1k]}6 1b}});z.4R=z.1F;if(y.3d&&y.3d.3R){3R=N(a,b){P c,3d,7z,29,17=a.17;b=b.1m(gj,"-$1").1s();if((3d=a.2A.3d)&&(7z=3d.3R(a,V))){c=7z.h2(b);if(c===""&&!z.2W(a.2A.2c,a)){c=z.17(a,b)}}if(!z.1q.aq&&7z&&go.1c(b)&&9Z.1c(c)){29=17.29;17.29=c;c=7z.29;17.29=29}6 c}}if(y.2c.2O){2O=N(a,b){P c,79,d1,1b=a.2O&&a.2O[b],17=a.17;if(1b==V&&17&&(d1=17[b])){1b=d1}if(9Z.1c(1b)){c=17.1o;79=a.a8&&a.a8.1o;if(79){a.a8.1o=a.2O.1o}17.1o=b==="h0"?"gZ":1b;1b=17.jh+"42";17.1o=c;if(79){a.a8.1o=79}}6 1b===""?"8O":1b}}4R=3R||2O;N d4(a,b,c){P d=b==="29"?a.6R:a.9h,i=b==="29"?1:0,75=4;if(d>0){if(c!=="63"){12(;i<75;i+=2){if(!c){d-=22(z.1F(a,"5Y"+4V[i]))||0}if(c==="4m"){d+=22(z.1F(a,c+4V[i]))||0}R{d-=22(z.1F(a,"63"+4V[i]+"a9"))||0}}}6 d+"42"}d=4R(a,b);if(d<0||d==V){d=a.17[b]}if(9Z.1c(d)){6 d}d=22(d)||0;if(c){12(;i<75;i+=2){d+=22(z.1F(a,"5Y"+4V[i]))||0;if(c!=="5Y"){d+=22(z.1F(a,"63"+4V[i]+"a9"))||0}if(c==="4m"){d+=22(z.1F(a,c+4V[i]))||0}}}6 d+"42"}z.1i(["3o","29"],N(i,d){z.4U[d]={1M:N(a,b,c){if(b){if(a.6R!==0){6 d4(a,d,c)}R{6 z.cY(a,gp,N(){6 d4(a,d,c)})}}},1A:N(a,b){6 gk.1c(b)?b+"42":b}}});if(!z.1q.2p){z.4U.2p={1M:N(a,b){6 gh.1c((b&&a.2O?a.2O.1p:a.17.1p)||"")?(22(4Q.$1)/gT)+"":b?"1":""},1A:N(a,b){P c=a.17,2O=a.2O,2p=z.a1(b)?"gg(2p="+b*gT+")":"",1p=2O&&2O.1p||c.1p||"";c.6c=1;if(b>=1&&z.3H(1p.1m(H,""))===""){c.49("1p");if(2O&&!2O.1p){6}}c.1p=H.1c(1p)?1p.1m(H,2p):1p+" "+2p}}}z(N(){if(!z.1q.aj){z.4U.6U={1M:N(a,b){6 z.cY(a,{"1C":"82-6V"},N(){if(b){6 4R(a,"4m-gY")}R{6 a.17.6U}})}}}});if(z.2D&&z.2D.4F){z.2D.4F.2H=N(a){P b=a.6R,3o=a.9h;6(b===0&&3o===0)||(!z.1q.dd&&((a.17&&a.17.1C)||z.1F(a,"1C"))==="40")};z.2D.4F.ax=N(a){6!z.2D.4F.2H(a)}}z.1i({4m:"",5Y:"",63:"a9"},N(b,c){z.4U[b+c]={d6:N(a){P i,1u=1a a==="1v"?a.2f(" "):[a],d7={};12(i=0;i<4;i++){d7[b+4V[i]+c]=1u[i]||1u[i-2]||1u[0]}6 d7}}});P I=/%20/g,gU=/\\[\\]$/,d3=/\\r?\\n/g,gP=/#.*$/,gO=/^(.*?):[ \\t]*([^\\r\\n]*)\\r?$/h1,gL=/^(?:h3|h4|gH|gH-h6|h7|2H|h8|3P|8Q|h9|ha|hb|1Z|eZ|1Q|hc)$/i,gF=/^(?:he|gE|gE\\-hg|.+\\-hh|aQ|hi|hj):$/,gD=/^(?:7L|hl)$/,gC=/^\\/\\//,cV=/\\?/,gA=/<1N\\b[^<]*(?:(?!<\\/1N>)<[^<]*)*<\\/1N>/gi,gv=/^(?:2Z|83)/i,cQ=/\\s+/,gs=/([?&])4b=[^&]*/,cP=/^([\\w\\+\\.\\-]+:)(?:\\/\\/([^\\/?#:]*)(?::(\\d+))?)?/,cO=z.fn.7b,9U={},cE={},53,5k,cz=["*/"]+["*"];2e{53=b5.3p}2d(e){53=y.21("a");53.3p="";53=53.3p}5k=cP.23(53.1s())||[];N ch(d){6 N(a,b){if(1a a!=="1v"){b=a;a="*"}if(z.1I(b)){P c=a.1s().2f(cQ),i=0,Q=c.Q,3u,cc,9E;12(;i<Q;i++){3u=c[i];9E=/^\\+/.1c(3u);if(9E){3u=3u.8k(1)||"*"}cc=d[3u]=d[3u]||[];cc[9E?"4z":"1y"](b)}}}}N 7A(a,b,c,d,e,f){e=e||b.2u[0];f=f||{};f[e]=T;P g=a[e],i=0,Q=g?g.Q:0,9z=(a===9U),3F;12(;i<Q&&(9z||!3F);i++){3F=g[i](b,c,d);if(1a 3F==="1v"){if(!9z||f[3F]){3F=x}R{b.2u.4z(3F);3F=7A(a,b,c,d,3F,f)}}}if((9z||!3F)&&!f["*"]){3F=7A(a,b,c,d,"*",f)}6 3F}N c3(a,b){P c,4E,9w=z.5c.9w||{};12(c in b){if(b[c]!==x){(9w[c]?a:(4E||(4E={})))[c]=b[c]}}if(4E){z.1x(T,a,4E)}}z.fn.1x({7b:N(d,e,f){if(1a d!=="1v"&&cO){6 cO.1S(O,1n)}R if(!O.Q){6 O}P g=d.2w(" ");if(g>=0){P h=d.2g(g,d.Q);d=d.2g(0,g)}P i="7L";if(e){if(z.1I(e)){f=e;e=x}R if(1a e==="2l"){e=z.7u(e,z.5c.bV);i="hJ"}}P j=O;z.5r({1Q:d,14:i,3u:"2N",1f:e,2S:N(a,b,c){c=a.bU;if(a.gB()){a.2B(N(r){c=r});j.2N(h?z("<19>").4M(c.1m(gA,"")).2k(h):c)}if(f){j.1i(f,[c,b,a])}}});6 O},hK:N(){6 z.7u(O.f0())},f0:N(){6 O.3k(N(){6 O.47?z.4N(O.47):O}).1p(N(){6 O.1k&&!O.3A&&(O.2F||gv.1c(O.1j)||gL.1c(O.14))}).3k(N(i,b){P c=z(O).1L();6 c==V?V:z.2T(c)?z.3k(c,N(a,i){6{1k:b.1k,1w:a.1m(d3,"\\r\\n")}}):{1k:b.1k,1w:c.1m(d3,"\\r\\n")}}).1M()}});z.1i("eY eX eW hP hQ eV".2f(" "),N(i,o){z.fn[o]=N(f){6 O.3b(o,f)}});z.1i(["1M","hS"],N(i,e){z[e]=N(a,b,c,d){if(z.1I(b)){d=d||c;c=b;b=x}6 z.5r({14:e,1Q:a,1f:b,4g:c,3u:d})}});z.1x({hU:N(a,b){6 z.1M(a,x,b,"1N")},hV:N(a,b,c){6 z.1M(a,b,c,"4j")},9r:N(a,b){if(b){c3(a,z.5c)}R{b=a;a=z.5c}c3(a,b);6 a},5c:{1Q:53,bO:gF.1c(5k[1]),6e:T,14:"7L",7P:"5M/x-eO-3N-eN; eM=i4-8",eL:T,46:T,8j:{3c:"5M/3c, 1Z/3c",2N:"1Z/2N",1Z:"1Z/i7",4j:"5M/4j, 1Z/9n","*":cz},4t:{3c:/3c/,2N:/2N/,4j:/4j/},78:{3c:"ez",1Z:"bU"},3q:{"* 1Z":w.9V,"1Z 2N":T,"1Z 4j":z.bw,"1Z 3c":z.eH},9w:{3g:T,1Q:T}},bE:ch(9U),bD:ch(cE),5r:N(g,h){if(1a g==="2l"){h=g;g=x}h=h||{};P s=z.9r({},h),4q=s.3g||s,9g=4q!==s&&(4q.1d||4q 6Z z)?z(4q):z.1l,1X=z.7f(),bu=z.5A("4P 2o"),5m=s.5m||{},4f,bm={},bj={},99,4O,66,95,1u,27=0,6E,i,1H={48:0,5Q:N(a,b){if(!27){P c=a.1s();a=bj[c]=bj[c]||a;bm[a]=b}6 O},dE:N(){6 27===2?99:V},8P:N(a){P b;if(27===2){if(!4O){4O={};1Y((b=gO.23(99))){4O[b[1].1s()]=b[2]}}b=4O[a.1s()]}6 b===x?V:b},aL:N(a){if(!27){s.8C=a}6 O},5g:N(a){a=a||"5g";if(66){66.5g(a)}2B(0,a);6 O}};N 2B(a,b,c,d){if(27===2){6}27=2;if(95){gJ(95)}66=x;99=d||"";1H.48=a>0?4:0;P f,4g,2m,2R=b,gy=c?g3(s,1H,c):x,5J,6a;if(a>=8o&&a<iG||a===fF){if(s.fE){if((5J=1H.8P("iJ-fD"))){z.5J[4f]=5J}if((6a=1H.8P("iL"))){z.6a[4f]=6a}}if(a===fF){2R="iM";f=T}R{2e{4g=fC(s,gy);2R="4g";f=T}2d(e){2R="ge";2m=e}}}R{2m=2R;if(!2R||a){2R="2m";if(a<0){a=0}}}1H.co=a;1H.2R=""+(b||2R);if(f){1X.7e(4q,[4g,2R,1H])}R{1X.iP(4q,[1H,2R,2m])}1H.5m(5m);5m=x;if(6E){9g.2X("5r"+(f?"iQ":"b9"),[1H,s,f?4g:2m])}bu.6S(4q,[1H,2R]);if(6E){9g.2X("eW",[1H,s]);if(!(--z.cj)){z.1l.2X("eX")}}}1X.2q(1H);1H.4g=1H.2B;1H.2m=1H.6B;1H.2S=bu.26;1H.5m=N(a){if(a){P b;if(27<2){12(b in a){5m[b]=[5m[b],a[b]]}}R{b=a[1H.co];1H.9M(b,b)}}6 O};s.1Q=((g||s.1Q)+"").1m(gP,"").1m(gC,5k[1]+"//");s.2u=z.3H(s.3u||"*").1s().2f(cQ);if(s.6d==V){1u=cP.23(s.1Q.1s());s.6d=!!(1u&&(1u[1]!=5k[1]||1u[2]!=5k[2]||(1u[3]||(1u[1]==="fo:"?80:fm))!=(5k[3]||(5k[1]==="fo:"?80:fm))))}if(s.1f&&s.eL&&1a s.1f!=="1v"){s.1f=z.7u(s.1f,s.bV)}7A(9U,s,h,1H);if(27===2){6 18}6E=s.6e;s.14=s.14.9y();s.8q=!gD.1c(s.14);if(6E&&z.cj++===0){z.1l.2X("eY")}if(!s.8q){if(s.1f){s.1Q+=(cV.1c(s.1Q)?"&":"?")+s.1f;32 s.1f}4f=s.1Q;if(s.1B===18){P j=z.3h(),1b=s.1Q.1m(gs,"$iX="+j);s.1Q=1b+((1b===s.1Q)?(cV.1c(s.1Q)?"&":"?")+"4b="+j:"")}}if(s.1f&&s.8q&&s.7P!==18||h.7P){1H.5Q("iY-iZ",s.7P)}if(s.fE){4f=4f||s.1Q;if(z.5J[4f]){1H.5Q("fi-fD-j1",z.5J[4f])}if(z.6a[4f]){1H.5Q("fi-j2-j3",z.6a[4f])}}1H.5Q("j4",s.2u[0]&&s.8j[s.2u[0]]?s.8j[s.2u[0]]+(s.2u[0]!=="*"?", "+cz+"; q=0.j5":""):s.8j["*"]);12(i in s.fg){1H.5Q(i,s.fg[i])}if(s.fa&&(s.fa.1h(4q,1H,s)===18||27===2)){1H.5g();6 18}12(i in{4g:1,2m:1,2S:1}){1H[i](s[i])}66=7A(cE,s,h,1H);if(!66){2B(-1,"eT j9")}R{1H.48=1;if(6E){9g.2X("eV",[1H,s])}if(s.46&&s.c5>0){95=5I(N(){1H.5g("c5")},s.c5)}2e{27=1;66.8s(bm,2B)}2d(e){if(27<2){2B(-1,e)}R{b6 e;}}}6 1H},7u:N(a,c){P s=[],26=N(a,b){b=z.1I(b)?b():b;s[s.Q]=eF(a)+"="+eF(b)};if(c===x){c=z.5c.bV}if(z.2T(a)||(a.72&&!z.7M(a))){z.1i(a,N(){26(O.1k,O.1w)})}R{12(P d in a){8t(d,a[d],c,26)}}6 s.5x("&").1m(I,"+")}});N 8t(a,b,c,d){if(z.2T(b)){z.1i(b,N(i,v){if(c||gU.1c(a)){d(a,v)}R{8t(a+"["+(1a v==="2l"?i:"")+"]",v,c,d)}})}R if(!c&&z.14(b)==="2l"){12(P e in b){8t(a+"["+e+"]",b[e],c,d)}}R{d(a,b)}}z.1x({cj:0,5J:{},6a:{}});N g3(s,a,b){P c=s.4t,2u=s.2u,78=s.78,ct,14,4y,8u;12(14 in 78){if(14 in b){a[78[14]]=b[14]}}1Y(2u[0]==="*"){2u.4I();if(ct===x){ct=s.8C||a.8P("ji-14")}}if(ct){12(14 in c){if(c[14]&&c[14].1c(ct)){2u.4z(14);2h}}}if(2u[0]in b){4y=2u[0]}R{12(14 in b){if(!2u[0]||s.3q[14+" "+2u[0]]){4y=14;2h}if(!8u){8u=14}}4y=4y||8u}if(4y){if(4y!==2u[0]){2u.4z(4y)}6 b[4y]}}N fC(s,a){if(s.e6){a=s.e6(a,s.3u)}P b=s.2u,3q={},i,2Q,Q=b.Q,3M,4A=b[0],4s,7C,4B,4Z,50;12(i=1;i<Q;i++){if(i===1){12(2Q in s.3q){if(1a 2Q==="1v"){3q[2Q.1s()]=s.3q[2Q]}}}4s=4A;4A=b[i];if(4A==="*"){4A=4s}R if(4s!=="*"&&4s!==4A){7C=4s+" "+4A;4B=3q[7C]||3q["* "+4A];if(!4B){50=x;12(4Z in 3q){3M=4Z.2f(" ");if(3M[0]===4s||3M[0]==="*"){50=3q[3M[1]+" "+4A];if(50){4Z=3q[4Z];if(4Z===T){4B=50}R if(50===T){4B=4Z}2h}}}}if(!(4B||50)){z.2m("eT 7C jp "+7C.1m(" "," jq "))}if(4B!==T){a=4B?4B(a):50(4Z(a))}}}6 a}P J=z.3h(),7x=/(\\=)\\?(&|$)|\\?\\?/i;z.9r({6y:"ju",3l:N(){6 z.2x+"4b"+(J++)}});z.bE("4j 6y",N(s,b,c){P d=(1a s.1f==="1v")&&/^5M\\/x\\-eO\\-3N\\-eN/.1c(s.7P);if(s.2u[0]==="6y"||s.6y!==18&&(7x.1c(s.1Q)||d&&7x.1c(s.1f))){P e,3l=s.3l=z.1I(s.3l)?s.3l():s.3l,cx=w[3l],1Q=s.1Q,1f=s.1f,1m="$1"+3l+"$2";if(s.6y!==18){1Q=1Q.1m(7x,1m);if(s.1Q===1Q){if(d){1f=1f.1m(7x,1m)}if(s.1f===1f){1Q+=(/\\?/.1c(1Q)?"&":"?")+s.6y+"="+3l}}}s.1Q=1Q;s.1f=1f;w[3l]=N(a){e=[a]};c.db(N(){w[3l]=cx;if(e&&z.1I(cx)){w[3l](e[0])}});s.3q["1N 4j"]=N(){if(!e){z.2m(3l+" jx 6p jy")}6 e[0]};s.2u[0]="4j";6"1N"}});z.9r({8j:{1N:"1Z/9n, 5M/9n, 5M/cs, 5M/x-cs"},4t:{1N:/9n|cs/},3q:{"1Z 1N":N(a){z.bn(a);6 a}}});z.bE("1N",N(s){if(s.1B===x){s.1B=18}if(s.6d){s.14="7L";s.6e=18}});z.bD("1N",N(s){if(s.6d){P c,60=y.60||y.28("60")[0]||y.2c;6{8s:N(4b,b){c=y.21("1N");c.46="46";if(s.fu){c.eM=s.fu}c.3s=s.1Q;c.a2=c.74=N(4b,a){if(a||!c.48||/jC|2S/.1c(c.48)){c.a2=c.74=V;if(60&&c.1g){60.3j(c)}c=x;if(!a){b(8o,"4g")}}};60.4p(c,60.1W)},5g:N(){if(c){c.a2(0,1)}}}}});P K=w.9X?N(){12(P a in 5W){5W[a](0,1)}}:18,fk=0,5W;N bS(){2e{6 2i w.f3()}2d(e){}}N eS(){2e{6 2i w.9X("fO.jJ")}2d(e){}}z.5c.bP=w.9X?N(){6!O.bO&&bS()||eS()}:bS;(N(a){z.1x(z.1q,{5r:!!a,eQ:!!a&&("jM"in a)})})(z.5c.bP());if(z.1q.5r){z.bD(N(s){if(!s.6d||z.1q.eQ){P g;6{8s:N(c,d){P f=s.bP(),1R,i;if(s.eJ){f.aw(s.14,s.1Q,s.46,s.eJ,s.8Q)}R{f.aw(s.14,s.1Q,s.46)}if(s.bN){12(i in s.bN){f[i]=s.bN[i]}}if(s.8C&&f.aL){f.aL(s.8C)}if(!s.6d&&!c["X-eG-9H"]){c["X-eG-9H"]="f3"}2e{12(i in c){f.5Q(i,c[i])}}2d(4b){}f.8s((s.8q&&s.1f)||V);g=N(4b,a){P b,2R,4O,5T,3c;2e{if(g&&(a||f.48===4)){g=x;if(1R){f.74=z.bi;if(K){32 5W[1R]}}if(a){if(f.48!==4){f.5g()}}R{b=f.co;4O=f.dE();5T={};3c=f.ez;if(3c&&3c.2c){5T.3c=3c}2e{5T.1Z=f.bU}2d(4b){}2e{2R=f.2R}2d(e){2R=""}if(!b&&s.bO&&!s.6d){b=5T.1Z?8o:jS}R if(b===jT){b=jU}}}}2d(eA){if(!a){d(-1,eA)}}if(5T){d(b,2R,5T,4O)}};if(!s.46||f.48===4){g()}R{1R=++fk;if(K){if(!5W){5W={};z(w).fz(K)}5W[1R]=g}f.74=g}},5g:N(){if(g){g(0,1)}}}}})}P L={},3m,5R,es=/^(?:3K|2U|33)$/,dw=/^([+\\-]=)?([\\d+.\\-]+)([a-z%]*)$/i,8c,7y=[["3o","6O","k3","k4","k5"],["29","au","6U","k7","k8"],["2p"]],89;z.fn.1x({2U:N(a,b,c){P d,1C;if(a||a===0){6 O.6C(5t("2U",3),a,b,c)}R{12(P i=0,j=O.Q;i<j;i++){d=O[i];if(d.17){1C=d.17.1C;if(!z.1z(d,"85")&&1C==="40"){1C=d.17.1C=""}if((1C===""&&z.1F(d,"1C")==="40")||!z.2W(d.2A.2c,d)){z.1z(d,"85",ab(d.1j))}}}12(i=0;i<j;i++){d=O[i];if(d.17){1C=d.17.1C;if(1C===""||1C==="40"){d.17.1C=z.1z(d,"85")||""}}}6 O}},33:N(a,b,c){if(a||a===0){6 O.6C(5t("33",3),a,b,c)}R{P d,1C,i=0,j=O.Q;12(;i<j;i++){d=O[i];if(d.17){1C=z.1F(d,"1C");if(1C!=="40"&&!z.1z(d,"85")){z.1z(d,"85",1C)}}}12(i=0;i<j;i++){if(O[i].17){O[i].17.1C="40"}}6 O}},gz:z.fn.3K,3K:N(b,c,d){P e=1a b==="7v";if(z.1I(b)&&z.1I(c)){O.gz.1S(O,1n)}R if(b==V||e){O.1i(N(){P a=e?b:z(O).is(":2H");z(O)[a?"2U":"33"]()})}R{O.6C(5t("3K",3),b,c,d)}6 O},kf:N(a,b,c,d){6 O.1p(":2H").1F("2p",0).2U().38().6C({2p:b},a,c,d)},6C:N(b,c,d,f){P g=z.fR(c,d,f);if(z.8d(b)){6 O.1i(g.2S,[18])}b=z.1x({},b);N bY(){if(g.24===18){z.gc(O)}P a=z.1x({},g),bc=O.1d===1,2H=bc&&z(O).is(":2H"),1k,1L,p,e,1J,1m,1u,3y,38,3V,7h;a.5o={};12(p in b){1k=z.4S(p);if(p!==1k){b[1k]=b[p];32 b[p]}if((1J=z.4U[1k])&&"d6"in 1J){1m=1J.d6(b[1k]);32 b[1k];12(p in 1m){if(!(p in b)){b[p]=1m[p]}}}}12(1k in b){1L=b[1k];if(z.2T(1L)){a.5o[1k]=1L[1];1L=b[1k]=1L[0]}R{a.5o[1k]=a.ee&&a.ee[1k]||a.9d||\'dQ\'}if(1L==="33"&&2H||1L==="2U"&&!2H){6 a.2S.1h(O)}if(bc&&(1k==="3o"||1k==="29")){a.3B=[O.17.3B,O.17.kp,O.17.kq];if(z.1F(O,"1C")==="82"&&z.1F(O,"9A")==="40"){if(!z.1q.af||ab(O.1j)==="82"){O.17.1C="82-6V"}R{O.17.6c=1}}}}if(a.3B!=V){O.17.3B="2H"}12(p in b){e=2i z.fx(O,a,p);1L=b[p];if(es.1c(1L)){7h=z.1z(O,"3K"+p)||(1L==="3K"?2H?"2U":"33":0);if(7h){z.1z(O,"3K"+p,7h==="2U"?"33":"2U");e[7h]()}R{e[1L]()}}R{1u=dw.23(1L);3y=e.1e();if(1u){38=22(1u[2]);3V=1u[3]||(z.a4[p]?"":"42");if(3V!=="42"){z.17(O,p,(38||1)+3V);3y=((38||1)/e.1e())*3y;z.17(O,p,3y+3V)}if(1u[1]){38=((1u[1]==="-="?-1:1)*38)+3y}e.6P(3y,38,3V)}R{e.6P(3y,1L,"")}}}6 T}6 g.24===18?O.1i(bY):O.24(g.24,bY)},5q:N(f,g,h){if(1a f!=="1v"){h=g;g=f;f=x}if(g&&f!==18){O.24(f||"fx",[])}6 O.1i(N(){P e,ai=18,2G=z.2G,1f=z.1z(O);if(!h){z.cN(T,O)}N cF(a,b,c){P d=b[c];z.3S(a,c,T);d.5q(h)}if(f==V){12(e in 1f){if(1f[e]&&1f[e].5q&&e.2w(".93")===e.Q-4){cF(O,1f,e)}}}R if(1f[e=f+".93"]&&1f[e].5q){cF(O,1f,e)}12(e=2G.Q;e--;){if(2G[e].U===O&&(f==V||2G[e].24===f)){if(h){2G[e](T)}R{2G[e].eR()}ai=T;2G.3n(e,1)}}if(!(h&&ai)){z.6b(O,f)}})}});N as(){5I(dV,0);6(89=z.3h())}N dV(){89=x}N 5t(a,b){P c={};z.1i(7y.5X.1S([],7y.2g(0,b)),N(){c[O]=a});6 c}z.1i({ky:5t("2U",1),kz:5t("33",1),kA:5t("3K",1),kB:{2p:"2U"},kC:{2p:"33"},kD:{2p:"3K"}},N(d,e){z.fn[d]=N(a,b,c){6 O.6C(e,a,b,c)}});z.1x({fR:N(b,c,d){P e=b&&1a b==="2l"?z.1x({},b):{2S:d||!d&&c||z.1I(b)&&b,3T:b,9d:d&&c||c&&!z.1I(c)&&c};e.3T=z.fx.45?0:1a e.3T==="3P"?e.3T:e.3T in z.fx.7V?z.fx.7V[e.3T]:z.fx.7V.4D;if(e.24==V||e.24===T){e.24="fx"}e.2j=e.2S;e.2S=N(a){if(z.1I(e.2j)){e.2j.1h(O)}if(e.24){z.6b(O,e.24)}R if(a!==18){z.cN(O)}};6 e},9d:{kF:N(p){6 p},dQ:N(p){6(-4r.kG(p*4r.kH)/2)+0.5}},2G:[],fx:N(a,b,c){O.1K=b;O.U=a;O.1O=c;b.4u=b.4u||{}}});z.fx.2L={cp:N(){if(O.1K.5p){O.1K.5p.1h(O.U,O.3h,O)}(z.fx.5p[O.1O]||z.fx.5p.4D)(O)},1e:N(){if(O.U[O.1O]!=V&&(!O.U.17||O.U.17[O.1O]==V)){6 O.U[O.1O]}P a,r=z.1F(O.U,O.1O);6 cR(a=22(r))?!r||r==="8O"?0:r:a},6P:N(b,c,d){P e=O,fx=z.fx;O.bX=89||as();O.38=c;O.3h=O.3y=b;O.9I=O.27=0;O.3V=d||O.3V||(z.a4[O.1O]?"":"42");N t(a){6 e.5p(a)}t.24=O.1K.24;t.U=O.U;t.eR=N(){if(z.1z(e.U,"6G"+e.1O)===x){if(e.1K.33){z.1z(e.U,"6G"+e.1O,e.3y)}R if(e.1K.2U){z.1z(e.U,"6G"+e.1O,e.38)}}};if(t()&&z.2G.1y(t)&&!8c){8c=fr(fx.g6,fx.ft)}},2U:N(){P a=z.1z(O.U,"6G"+O.1O);O.1K.4u[O.1O]=a||z.17(O.U,O.1O);O.1K.2U=T;if(a!==x){O.6P(O.1e(),a)}R{O.6P(O.1O==="29"||O.1O==="3o"?1:0,O.1e())}z(O.U).2U()},33:N(){O.1K.4u[O.1O]=z.1z(O.U,"6G"+O.1O)||z.17(O.U,O.1O);O.1K.33=T;O.6P(O.1e(),0)},5p:N(c){P p,n,2S,t=89||as(),2B=T,U=O.U,1K=O.1K;if(c||t>=1K.3T+O.bX){O.3h=O.38;O.9I=O.27=1;O.cp();1K.5o[O.1O]=T;12(p in 1K.5o){if(1K.5o[p]!==T){2B=18}}if(2B){if(1K.3B!=V&&!z.1q.ag){z.1i(["","X","Y"],N(a,b){U.17["3B"+b]=1K.3B[a]})}if(1K.33){z(U).33()}if(1K.33||1K.2U){12(p in 1K.5o){z.17(U,p,1K.4u[p]);z.3S(U,"6G"+p,T);z.3S(U,"3K"+p,T)}}2S=1K.2S;if(2S){1K.2S=18;2S.1h(U)}}6 18}R{if(1K.3T==kR){O.3h=t}R{n=t-O.bX;O.27=n/1K.3T;O.9I=z.9d[1K.5o[O.1O]](O.27,n,0,1,1K.3T);O.3h=O.3y+((O.38-O.3y)*O.9I)}O.cp()}6 T}};z.1x(z.fx,{g6:N(){P a,2G=z.2G,i=0;12(;i<2G.Q;i++){a=2G[i];if(!a()&&2G[i]===a){2G.3n(i--,1)}}if(!2G.Q){z.fx.5q()}},ft:13,5q:N(){kS(8c);8c=V},7V:{kT:kU,kV:8o,4D:kW},5p:{2p:N(a){z.17(a.U,"2p",a.3h)},4D:N(a){if(a.U.17&&a.U.17[a.1O]!=V){a.U.17[a.1O]=a.3h+a.3V}R{a.U[a.1O]=a.3h}}}});z.1i(7y.5X.1S([],7y),N(i,b){if(b.2w("4m")){z.fx.5p[b]=N(a){z.17(a.U,b,4r.5f(0,a.3h)+a.3V)}}});if(z.2D&&z.2D.4F){z.2D.4F.kX=N(b){6 z.57(z.2G,N(a){6 b===a.U}).Q}}N ab(a){if(!L[a]){P b=y.1t,U=z("<"+a+">").g4(b),1C=U.1F("1C");U.2C();if(1C==="40"||1C===""){if(!3m){3m=y.21("3m");3m.f5=3m.29=3m.3o=0}b.36(3m);if(!5R||!3m.21){5R=(3m.eD||3m.eC).56;5R.kY((z.1q.5L?"<!kZ 2N>":"")+"<2N><1t>");5R.l0()}U=5R.21(a);5R.1t.36(U);1C=z.1F(U,"1C");b.3j(3m)}L[a]=1C}6 L[a]}P M,d8=/^t(?:l3|d|h)$/i,az=/^(?:1t|2N)$/i;if("gq"in y.2c){M=N(a,b,c,d){2e{d=a.gq()}2d(e){}if(!d||!z.2W(c,a)){6 d?{1G:d.1G,1o:d.1o}:{1G:0,1o:0}}P f=b.1t,ar=cS(b),6M=c.6M||f.6M||0,6K=c.6K||f.6K||0,3O=ar.dt||z.1q.5L&&c.3O||f.3O,3J=ar.dc||z.1q.5L&&c.3J||f.3J,1G=d.1G+3O-6M,1o=d.1o+3J-6K;6{1G:1G,1o:1o}}}R{M=N(a,b,c){P d,34=a.34,gl=a,1t=b.1t,3d=b.3d,6W=3d?3d.3R(a,V):a.2O,1G=a.3X,1o=a.9W;1Y((a=a.1g)&&a!==1t&&a!==c){if(z.1q.aO&&6W.37==="8l"){2h}d=3d?3d.3R(a,V):a.2O;1G-=a.3O;1o-=a.3J;if(a===34){1G+=a.3X;1o+=a.9W;if(z.1q.dv&&!(z.1q.dA&&d8.1c(a.1j))){1G+=22(d.d0)||0;1o+=22(d.aF)||0}gl=34;34=a.34}if(z.1q.dS&&d.3B!=="ax"){1G+=22(d.d0)||0;1o+=22(d.aF)||0}6W=d}if(6W.37==="3W"||6W.37==="9i"){1G+=1t.3X;1o+=1t.9W}if(z.1q.aO&&6W.37==="8l"){1G+=4r.5f(c.3O,1t.3O);1o+=4r.5f(c.3J,1t.3J)}6{1G:1G,1o:1o}}}z.fn.39=N(a){if(1n.Q){6 a===x?O:O.1i(N(i){z.39.dm(O,a,i)})}P b=O[0],25=b&&b.2A;if(!25){6 V}if(b===25.1t){6 z.39.fs(b)}6 M(b,25,25.2c)};z.39={fs:N(a){P b=a.3X,1o=a.9W;if(z.1q.dU){b+=22(z.1F(a,"6O"))||0;1o+=22(z.1F(a,"au"))||0}6{1G:b,1o:1o}},dm:N(a,b,i){P c=z.1F(a,"37");if(c==="9i"){a.17.37="3W"}P d=z(a),a3=d.39(),aN=z.1F(a,"1G"),b8=z.1F(a,"1o"),dh=(c==="c4"||c==="8l")&&z.5N("8O",[aN,b8])>-1,3E={},9J={},9e,98;if(dh){9J=d.37();9e=9J.1G;98=9J.1o}R{9e=22(aN)||0;98=22(b8)||0}if(z.1I(b)){b=b.1h(a,i,a3)}if(b.1G!=V){3E.1G=(b.1G-a3.1G)+9e}if(b.1o!=V){3E.1o=(b.1o-a3.1o)+98}if("fj"in b){b.fj.1h(a,3E)}R{d.1F(3E)}}};z.fn.1x({37:N(){if(!O[0]){6 V}P a=O[0],34=O.34(),39=O.39(),7J=az.1c(34[0].1j)?{1G:0,1o:0}:34.39();39.1G-=22(z.1F(a,"6O"))||0;39.1o-=22(z.1F(a,"au"))||0;7J.1G+=22(z.1F(34[0],"d0"))||0;7J.1o+=22(z.1F(34[0],"aF"))||0;6{1G:39.1G-7J.1G,1o:39.1o-7J.1o}},34:N(){6 O.3k(N(){P a=O.34||y.1t;1Y(a&&(!az.1c(a.1j)&&z.1F(a,"37")==="9i")){a=a.34}6 a})}});z.1i({3J:"dc",3O:"dt"},N(f,g){P h=/Y/.1c(g);z.fn[f]=N(e){6 z.44(O,N(a,b,c){P d=cS(a);if(c===x){6 d?(g in d)?d[g]:z.1q.5L&&d.56.2c[b]||d.56.1t[b]:a[b]}if(d){d.lq(!h?c:z(d).3J(),h?c:z(d).3O())}R{a[b]=c}},f,e,1n.Q,V)}});N cS(a){6 z.5h(a)?a:a.1d===9?a.3d||a.dk:18}z.1i({lr:"3o",a9:"29"},N(f,g){P h="ls"+f,94="fy"+f,aS="39"+f;z.fn["3Q"+f]=N(){P a=O[0];6 a?a.17?22(z.1F(a,g,"5Y")):O[g]():V};z.fn["5U"+f]=N(a){P b=O[0];6 b?b.17?22(z.1F(b,g,a?"4m":"63")):O[g]():V};z.fn[g]=N(e){6 z.44(O,N(a,b,c){P d,92,4u,1b;if(z.5h(a)){d=a.56;92=d.2c[h];6 z.1q.5L&&92||d.1t&&d.1t[h]||92}if(a.1d===9){d=a.2c;if(d[h]>=d[94]){6 d[h]}6 4r.5f(a.1t[94],d[94],a.1t[aS],d[aS])}if(c===x){4u=z.1F(a,b);1b=22(4u);6 z.a1(1b)?1b:4u}z(a).1F(b,c)},g,e,1n.Q,V)}});w.5E=w.$=z;if(1a 8W==="N"&&8W.fH&&8W.fH.5E){8W("72",[],N(){6 z})}})(gN);',62,1334,'||||||return|||||||||||||||||||||||||||||||||||||||||||function|this|var|length|else||true|elem|null|||||||for||type|||style|false|div|typeof|ret|test|nodeType|cur|data|parentNode|call|each|nodeName|name|event|replace|arguments|left|filter|support|special|toLowerCase|body|parts|string|value|extend|push|_data|set|cache|display|input|handleObj|css|top|jqXHR|isFunction|hooks|options|val|get|script|prop|events|url|handle|apply|guid|target|className|firstChild|deferred|while|text||createElement|parseFloat|exec|queue|doc|add|state|getElementsByTagName|width|match|selector|documentElement|catch|try|split|slice|break|new|old|find|object|error||memory|opacity|promise|first|fragment|parent|dataTypes|button|indexOf|expando|getAttribute|tbody|ownerDocument|done|remove|expr|table|checked|timers|hidden|undefined|node|namespaces|prototype|checkSet|html|currentStyle|clone|key|statusText|complete|isArray|show|matches|contains|trigger|jQuerySub|select|option|innerHTML|delete|hide|offsetParent|result|appendChild|position|end|offset|attr|on|xml|defaultView|wrapMap|ready|context|now|pushStack|removeChild|map|jsonpCallback|iframe|splice|height|href|converters|count|src|check|dataType|Array|internalKey|copy|start|nextSibling|disabled|overflow|handler|childNodes|props|selection|td|trim|pop|scrollLeft|toggle|thisCache|tmp|form|scrollTop|number|inner|getComputedStyle|removeData|duration|attrHooks|unit|relative|offsetTop|nType|root|none|setAttribute|px|nodeHook|access|off|async|elements|readyState|removeAttribute|originalEvent|_|self|namespace|radio|ifModifiedKey|success|delegateCount|selected|json|ID|last|margin|checkbox|DOMContentLoaded|insertBefore|callbackContext|Math|prev|contents|orig|attachEvent|isNode|stack|finalDataType|unshift|current|conv|valHooks|_default|deep|filters|eventHandle|tns|shift|eventType|origType|all|append|makeArray|responseHeaders|once|RegExp|curCSS|camelCase|tr|cssHooks|cssExpand|init|constructor|toString|conv1|conv2|addEventListener|index|ajaxLocation|notxml||document|grep|curLoop|preventDefault|browser|getAttributeNode|ajaxSettings|Event|doneName|max|abort|isWindow|handlers|ontype|ajaxLocParts|POS|statusCode|nth|animatedProperties|step|stop|ajax|continue|genFx|getElementById|u00c0|submit|join|destElements|returnFalse|Callbacks|querySelectorAll|rBackslash|marginDiv|jQuery|one|sort|compareDocumentPosition|setTimeout|lastModified|uFFFF|boxModel|application|inArray|cacheable|dir|setRequestHeader|iframeDoc|class|responses|outer|nodeValue|xhrCallbacks|concat|padding|firingIndex|head|click|anyFound|border|isSupported|domManip|transport|eventPath|mark|getElementsByClassName|etag|dequeue|zoom|crossDomain|global|deleteExpando|setup|cacheresults|teardown|unique|PSEUDO|fire|previousSibling|checkFn|merge|not|CLASS|curData|siblingCheck|cloneNode|matched|sel|firingLength|resolve|jsonp|isBool|propFix|fail|animate|TAG|fireGlobals|delegateType|fxshow|firing|defer|fired|clientLeft|returned|clientTop|wrap|marginTop|custom|tds|offsetWidth|fireWith|insert|marginRight|block|prevComputedStyle|paddingMarginBorder|found|instanceof|isXML|offsetSupport|jquery|change|onreadystatechange|len|fromElement|empty|responseFields|rsLeft|bubbleType|load|isTrigger|selectedIndex|resolveWith|Deferred|isDefaultPrevented|method|focus|cssFloat|handlerQueue|jqcur|selMatch|failList|progressList|getSetAttribute|related|isXMLDoc|acceptData|stopPropagation|param|boolean|opt|jsre|fxAttrs|computedStyle|inspectPrefiltersOrTransports|part|conversion|diff|which|nid|propHooks|eventName|queueDataKey|parentOffset|markDataKey|GET|isPlainObject|proxy|removeEventListener|contentType|fixHook|simulate|readyList|returnTrue|getAll|speeds|_change|handleObjIn|cleanData|rspace||item|inline|textarea|hasDuplicate|olddisplay|rNonWord|browserMatch|disable|fxNow|contextXML|replaceWith|timerId|isEmptyObject|removeAttr|origName|setClass|matchesSelector|exclusive|accepts|substr|fixed|leftMatch|extra|200|pass|hasContent|_submit|send|buildParams|firstDataType|textContent|fixHooks|relatedTarget|blur|isPropagationStopped|CHILD|bindType|mimeType|even|odd|dispatch|triggered|attrMap|attrHandle|quick|isPartStr|mouseleave|mouseenter|results|auto|getResponseHeader|password|tabindex|case|property|source|attrNames|define|sourceIndex|attrFn|aup|bup|values|docElemProp|run|scrollProp|timeoutTimer|triggerHandler|version|curLeft|responseHeadersString||sizset|isEmptyDataObject|easing|curTop|order|globalEventContext|offsetHeight|static|cssText|paddingMarginBorderVisibility|positionTopLeftWidthHeight|safeFrag|javascript|conMarginTop|focusin|lastChild|ajaxSetup|createDocumentFragment|outerHTML|rleadingWhitespace|nav|flatOptions|enctype|toUpperCase|executeOnly|float|1px|safeFragment|Object|placeBefore|hasOwn|wrapAll|With|pos|curPosition|before|clean|then|scripts|progress|reject|lists|argIndex|has|firingStart|prefilters|String|offsetLeft|ActiveXObject|safeChildNodes|rnumnonpx|JSON|isNumeric|onload|curOffset|cssNumber|readyWait|isReady|copyIsArray|runtimeStyle|Width|lock|defaultDisplay|noCloneEvent|deferDataKey|preFilter|inlineBlockNeedsLayout|shrinkWrapBlocks|origCount|hadTimers|reliableMarginRight|removeEvent|innerText|selectors|NAME|rboolean||pixelMargin|win|createFxNow|ATTR|marginLeft|multiple|open|visible|namespace_re|rroot|buildFragment|boolHook|fixSpecified|isTag|isPartStrNotTag|borderLeftWidth|xA0|addClass|dirCheck|dirNodeCheck|fix|overrideMimeType|args|curCSSTop|fixedPosition|removeClass|file|checkClone|offsetProp|detachEvent|class2type|nodeIndex|specified|notify|isImmediatePropagationStopped|doScrollCheck|postDispatch|noData|embed|link|retVal|location|throw|navigator|curCSSLeft|Error|metaKey|userAgent|isElement|hasParent||relativeHierarchySelector|timeStamp|hasData|noop|requestHeadersNames|pseudoWorks||requestHeaders|globalEval|charCode||rootjQuery||tmpSet|later|completeDeferred|prevAll|parseJSON|clientX|winnow|bindReady|isDisconnected|action|getByName|ajaxTransport|ajaxPrefilter|pageX|sibling|tabIndex|prevObject|createSafeFragment|isEvents|leadingWhitespace|isObj|xhrFields|isLocal|xhr|focusout|onbeforeunload|createStandardXHR|rxhtmlTag|responseText|traditional|isSimulated|startTime|doAnimation|rtagName|rnoshimcache|rchecked|rscriptType|ajaxExtend|absolute|timeout|attributes|dataAttr|colgroup|_submit_attached|_submit_bubble|bulk|list|_just_changed|createTextNode|_change_attached|handleQueueMarkDefer|addToPrefiltersOrTransports|rtypenamespace|active|hover||toggler|after|status|update|ms|rfocusMorph|ecmascript||baseHasDuplicate|cloneCopyEvent|oldData|previous|cloneFixAttributes|allTypes|hoverHack|trimRight|fragments|trimLeft|transports|stopQueue|prune|pValues|fixDefaultChecked|findInputs|toArray|Date|soFar|_unmark|_load|rurl|rspacesAjax|isNaN|getWindow|uniqueSort|html5Clone|rquery|cssProps|inprogress|swap|rnotwhite|borderTopWidth|uncomputed|sub|rCRLF|getWidthOrHeight|charAt|expand|expanded|rtable|rclickable|only|always|pageXOffset|reliableHiddenOffsets||rmsPrefix|rvalidchars|calculatePosition|noBubble|parseInt|parentWindow|readonly|setOffset|createFlags||submitBubbles|changeBubbles|focusinBubbles|getElementsByName|pageYOffset|run_all|doesNotAddBorder|rfxnum|fcamelCase|rvalidescape|header|doesAddBorderForTableAndCells|quickExpr|image|reset|getAllResponseHeaders|setFilters|resolveFunc|delegateTarget|toggleClass|rvalidbraces|currentTarget|fescape|classNames|globalPOS|hasClass|__className__|swing|progressFunc|subtractsBorderForOverflowNotVisible|srcElement|doesNotIncludeMarginInBodyOffset|clearFxNow|ctrlKey|rmultiDash|TEST|optgroup|rwebkit|uuid|webkit|pseudoError|random|keyHooks|dataFilter|noCloneChecked|keyCode|optDisabled|mouseHooks|ropera|fireEvent|clientY|specialEasing|onclick|rparentsprev|parents|prevUntil|parse|rmultiselector|isSimple|guaranteedUnique|children|radioValue|next||pageY|rfxtypes|closest|rvalidtokens|toElement|htmlFor|appendChecked|Function|responseXML|firefoxAccessException|Invalid|contentDocument|contentWindow||encodeURIComponent|Requested|parseXML|rmsie|username|returnValue|processData|charset|urlencoded|www|getPreventDefault|cors|saveState|createActiveXHR|No|mouseover|ajaxSend|ajaxComplete|ajaxStop|ajaxStart|time|serializeArray|rinlinejQuery|mouseout|XMLHttpRequest|area|frameBorder|col|keypress|contenteditable|argLength|beforeSend|DOMParser|rtbody|rhtml|rnoInnerhtml|rnocache|headers|toJSON|If|using|xhrId|rcleanScript|443||http|fieldset|thead|setInterval|bodyOffset|interval|scriptCharset|lastToggle|rmozilla||scroll|unload||wrapInner|ajaxConvert|Modified|ifModified|304|prepend|amd|rhoverHack|parsedAttrs|rkeyEvent|detach|rmouseEvent|lastIndex|Microsoft|rReturn|contextmenu|speed|getTime|uaMatch|getData|rquickIs|clearAttributes|mergeAttributes|defaultChecked|quickParse|defaultValue|quickIs|setData|ajaxHandleResponses|appendTo|changeData|tick|rsingleTag|doScroll|rdashAlpha|_jQuery|shimCloneNode|_mark|depth|parsererror|visibility|alpha|ropacity||rupper|rnum|prevOffsetParent|rrelNum|5px|rmargin|cssShow|getBoundingClientRect|htmlSerialize|rts||array|rselectTextarea|isXMLFilter|hrefNormalized|response|_toggle|rscript|isResolved|rprotocol|rnoContent|app|rlocalProtocol|cellpadding|datetime|getText|clearTimeout|cellspacing|rinput|checkOn|window|rheaders|rhash|rreturn|rtype|customEvent|100|rbracket|optSelected|rfocusable|preDispatch|right|1em|fontSize|mg|getPropertyValue|color|date|styleFloat|local|email|month|range|search|tel|week|zIndex|about|widows|storage|extension|res|widget|orphans|HEAD|lineHeight|fontWeight|fillOpacity|Left|Bottom||Right|Top|replaceAll|insertAfter|prependTo|512|defaultSelected|unwrap|th|caption|tfoot|legend|CDATA|ecma|java|meta|img|POST|serialize|video|summary|section|output|ajaxError|ajaxSuccess|meter|post|hgroup|getScript|getJSON|footer|figure|figcaption|details|datalist|canvas|bdi|audio|UTF|aside|article|plain|abbr|reverse|siblings|nextUntil|nextAll||parentsUntil|||andSelf|level|Until|HTML|sizzle|msMatchesSelector||webkitMatchesSelector|mozMatchesSelector|finally|qsaError||__sizzle__|createComment|switch|activeElement|enabled|0n|child|expression|unrecognized|Syntax|sizcache|keyup|keydown|300|mousemove|mouseup|Last|mousedown|Etag|notmodified|dblclick|resize|rejectWith|Success|undelegate|delegate|die|live|unbind|bind|1_|Content|Type|beforeactivate|Since|None|Match|Accept|01|propertyName|propertychange|stopImmediatePropagation|Transport|cancelBubble|defaultPrevented|beforeunload|screenY|screenX|offsetY|offsetX|pixelLeft|content|char|view|shiftKey|eventPhase|cancelable|bubbles|from|to|altKey|relatedNode|attrName|callback|attrChange|focusoutblur|was|called|focusinfocus|mouse|encoding|loaded|setAttributeNode|createAttribute|coords|contentEditable|frameborder|useMap|XMLHTTP|usemap|colSpan|withCredentials|colspan|rowSpan|rowspan|cellPadding|cellSpacing|404|1223|204|maxLength|maxlength|readOnly|changed|can|removeProp|scoped|required|marginBottom|paddingTop|paddingBottom|loop|paddingLeft|paddingRight|controls|autoplay|autofocus|rea|clearQueue|delay|fadeTo|substring|classid|applet|444553540000|96B8|11cf|AE6D|D27CDB6E|clsid|overflowX|overflowY|20px|2px|000|solid|Bubbles|CSS1Compat|compatMode|slideDown|slideUp|slideToggle|fadeIn|fadeOut|fadeToggle|notifyWith|linear|cos|PI|pCount|when|rejected|resolved|pipe|isRejected|pending|locked|stopOnFalse|Infinity|clearInterval|slow|600|fast|400|animated|write|doctype|close|actual|safari|able|Number|Boolean|superclass|compatible|regexp|eval|execScript|XML|loadXML|XMLDOM|parseFromString|isPrototypeOf|isFinite|frameElement|holdReady|noConflict|size|hasOwnProperty|rv|mozilla|msie|opera|scrollTo|Height|client||bfnrt|buttons'.split('|'),0,{}))
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/jscolor/jscolor.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,895 @@
+/**
+ * jscolor, JavaScript Color Picker
+ *
+ * @version 1.3.9
+ * @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html
+ * @author  Jan Odvarko, http://odvarko.cz
+ * @created 2008-06-15
+ * @updated 2011-07-28
+ * @link    http://jscolor.com
+ */
+
+
+var jscolor = {
+
+
+	dir : '', // location of jscolor directory (leave empty to autodetect)
+	bindClass : 'color', // class name
+	binding : true, // automatic binding via <input class="...">
+	preloading : true, // use image preloading?
+
+
+	install : function() {
+		jscolor.addEvent(window, 'load', jscolor.init);
+	},
+
+
+	init : function() {
+		if(jscolor.binding) {
+			jscolor.bind();
+		}
+		if(jscolor.preloading) {
+			jscolor.preload();
+		}
+	},
+
+
+	getDir : function() {
+		if(!jscolor.dir) {
+			var detected = jscolor.detectDir();
+			jscolor.dir = detected!==false ? detected : 'jscolor/';
+		}
+		return jscolor.dir;
+	},
+
+
+	detectDir : function() {
+		var base = location.href;
+
+		var e = document.getElementsByTagName('base');
+		for(var i=0; i<e.length; i+=1) {
+			if(e[i].href) { base = e[i].href; }
+		}
+
+		var e = document.getElementsByTagName('script');
+		for(var i=0; i<e.length; i+=1) {
+			if(e[i].src && /(^|\/)jscolor\.js([?#].*)?$/i.test(e[i].src)) {
+				var src = new jscolor.URI(e[i].src);
+				var srcAbs = src.toAbsolute(base);
+				srcAbs.path = srcAbs.path.replace(/[^\/]+$/, ''); // remove filename
+				srcAbs.query = null;
+				srcAbs.fragment = null;
+				return srcAbs.toString();
+			}
+		}
+		return false;
+	},
+
+
+	bind : function() {
+		var matchClass = new RegExp('(^|\\s)('+jscolor.bindClass+')\\s*(\\{[^}]*\\})?', 'i');
+		var e = document.getElementsByTagName('input');
+		for(var i=0; i<e.length; i+=1) {
+			var m;
+			if(!e[i].color && e[i].className && (m = e[i].className.match(matchClass))) {
+				var prop = {};
+				if(m[3]) {
+					try {
+						eval('prop='+m[3]);
+					} catch(eInvalidProp) {}
+				}
+				e[i].color = new jscolor.color(e[i], prop);
+			}
+		}
+	},
+
+
+	preload : function() {
+		for(var fn in jscolor.imgRequire) {
+			if(jscolor.imgRequire.hasOwnProperty(fn)) {
+				jscolor.loadImage(fn);
+			}
+		}
+	},
+
+
+	images : {
+		pad : [ 181, 101 ],
+		sld : [ 16, 101 ],
+		cross : [ 15, 15 ],
+		arrow : [ 7, 11 ]
+	},
+
+
+	imgRequire : {},
+	imgLoaded : {},
+
+
+	requireImage : function(filename) {
+		jscolor.imgRequire[filename] = true;
+	},
+
+
+	loadImage : function(filename) {
+		if(!jscolor.imgLoaded[filename]) {
+			jscolor.imgLoaded[filename] = new Image();
+			jscolor.imgLoaded[filename].src = jscolor.getDir()+filename;
+		}
+	},
+
+
+	fetchElement : function(mixed) {
+		return typeof mixed === 'string' ? document.getElementById(mixed) : mixed;
+	},
+
+
+	addEvent : function(el, evnt, func) {
+		if(el.addEventListener) {
+			el.addEventListener(evnt, func, false);
+		} else if(el.attachEvent) {
+			el.attachEvent('on'+evnt, func);
+		}
+	},
+
+
+	fireEvent : function(el, evnt) {
+		if(!el) {
+			return;
+		}
+		if(document.createEvent) {
+			var ev = document.createEvent('HTMLEvents');
+			ev.initEvent(evnt, true, true);
+			el.dispatchEvent(ev);
+		} else if(document.createEventObject) {
+			var ev = document.createEventObject();
+			el.fireEvent('on'+evnt, ev);
+		} else if(el['on'+evnt]) { // alternatively use the traditional event model (IE5)
+			el['on'+evnt]();
+		}
+	},
+
+
+	getElementPos : function(e) {
+		var e1=e, e2=e;
+		var x=0, y=0;
+		if(e1.offsetParent) {
+			do {
+				x += e1.offsetLeft;
+				y += e1.offsetTop;
+			} while(e1 = e1.offsetParent);
+		}
+		while((e2 = e2.parentNode) && e2.nodeName.toUpperCase() !== 'BODY') {
+			x -= e2.scrollLeft;
+			y -= e2.scrollTop;
+		}
+		return [x, y];
+	},
+
+
+	getElementSize : function(e) {
+		return [e.offsetWidth, e.offsetHeight];
+	},
+
+
+	getRelMousePos : function(e) {
+		var x = 0, y = 0;
+		if (!e) { e = window.event; }
+		if (typeof e.offsetX === 'number') {
+			x = e.offsetX;
+			y = e.offsetY;
+		} else if (typeof e.layerX === 'number') {
+			x = e.layerX;
+			y = e.layerY;
+		}
+		return { x: x, y: y };
+	},
+
+
+	getViewPos : function() {
+		if(typeof window.pageYOffset === 'number') {
+			return [window.pageXOffset, window.pageYOffset];
+		} else if(document.body && (document.body.scrollLeft || document.body.scrollTop)) {
+			return [document.body.scrollLeft, document.body.scrollTop];
+		} else if(document.documentElement && (document.documentElement.scrollLeft || document.documentElement.scrollTop)) {
+			return [document.documentElement.scrollLeft, document.documentElement.scrollTop];
+		} else {
+			return [0, 0];
+		}
+	},
+
+
+	getViewSize : function() {
+		if(typeof window.innerWidth === 'number') {
+			return [window.innerWidth, window.innerHeight];
+		} else if(document.body && (document.body.clientWidth || document.body.clientHeight)) {
+			return [document.body.clientWidth, document.body.clientHeight];
+		} else if(document.documentElement && (document.documentElement.clientWidth || document.documentElement.clientHeight)) {
+			return [document.documentElement.clientWidth, document.documentElement.clientHeight];
+		} else {
+			return [0, 0];
+		}
+	},
+
+
+	URI : function(uri) { // See RFC3986
+
+		this.scheme = null;
+		this.authority = null;
+		this.path = '';
+		this.query = null;
+		this.fragment = null;
+
+		this.parse = function(uri) {
+			var m = uri.match(/^(([A-Za-z][0-9A-Za-z+.-]*)(:))?((\/\/)([^\/?#]*))?([^?#]*)((\?)([^#]*))?((#)(.*))?/);
+			this.scheme = m[3] ? m[2] : null;
+			this.authority = m[5] ? m[6] : null;
+			this.path = m[7];
+			this.query = m[9] ? m[10] : null;
+			this.fragment = m[12] ? m[13] : null;
+			return this;
+		};
+
+		this.toString = function() {
+			var result = '';
+			if(this.scheme !== null) { result = result + this.scheme + ':'; }
+			if(this.authority !== null) { result = result + '//' + this.authority; }
+			if(this.path !== null) { result = result + this.path; }
+			if(this.query !== null) { result = result + '?' + this.query; }
+			if(this.fragment !== null) { result = result + '#' + this.fragment; }
+			return result;
+		};
+
+		this.toAbsolute = function(base) {
+			var base = new jscolor.URI(base);
+			var r = this;
+			var t = new jscolor.URI;
+
+			if(base.scheme === null) { return false; }
+
+			if(r.scheme !== null && r.scheme.toLowerCase() === base.scheme.toLowerCase()) {
+				r.scheme = null;
+			}
+
+			if(r.scheme !== null) {
+				t.scheme = r.scheme;
+				t.authority = r.authority;
+				t.path = removeDotSegments(r.path);
+				t.query = r.query;
+			} else {
+				if(r.authority !== null) {
+					t.authority = r.authority;
+					t.path = removeDotSegments(r.path);
+					t.query = r.query;
+				} else {
+					if(r.path === '') { // TODO: == or === ?
+						t.path = base.path;
+						if(r.query !== null) {
+							t.query = r.query;
+						} else {
+							t.query = base.query;
+						}
+					} else {
+						if(r.path.substr(0,1) === '/') {
+							t.path = removeDotSegments(r.path);
+						} else {
+							if(base.authority !== null && base.path === '') { // TODO: == or === ?
+								t.path = '/'+r.path;
+							} else {
+								t.path = base.path.replace(/[^\/]+$/,'')+r.path;
+							}
+							t.path = removeDotSegments(t.path);
+						}
+						t.query = r.query;
+					}
+					t.authority = base.authority;
+				}
+				t.scheme = base.scheme;
+			}
+			t.fragment = r.fragment;
+
+			return t;
+		};
+
+		function removeDotSegments(path) {
+			var out = '';
+			while(path) {
+				if(path.substr(0,3)==='../' || path.substr(0,2)==='./') {
+					path = path.replace(/^\.+/,'').substr(1);
+				} else if(path.substr(0,3)==='/./' || path==='/.') {
+					path = '/'+path.substr(3);
+				} else if(path.substr(0,4)==='/../' || path==='/..') {
+					path = '/'+path.substr(4);
+					out = out.replace(/\/?[^\/]*$/, '');
+				} else if(path==='.' || path==='..') {
+					path = '';
+				} else {
+					var rm = path.match(/^\/?[^\/]*/)[0];
+					path = path.substr(rm.length);
+					out = out + rm;
+				}
+			}
+			return out;
+		}
+
+		if(uri) {
+			this.parse(uri);
+		}
+
+	},
+
+
+	/*
+	 * Usage example:
+	 * var myColor = new jscolor.color(myInputElement)
+	 */
+
+	color : function(target, prop) {
+
+
+		this.required = true; // refuse empty values?
+		this.adjust = true; // adjust value to uniform notation?
+		this.hash = false; // prefix color with # symbol?
+		this.caps = true; // uppercase?
+		this.slider = true; // show the value/saturation slider?
+		this.valueElement = target; // value holder
+		this.styleElement = target; // where to reflect current color
+		this.hsv = [0, 0, 1]; // read-only  0-6, 0-1, 0-1
+		this.rgb = [1, 1, 1]; // read-only  0-1, 0-1, 0-1
+
+		this.pickerOnfocus = true; // display picker on focus?
+		this.pickerMode = 'HSV'; // HSV | HVS
+		this.pickerPosition = 'bottom'; // left | right | top | bottom
+		this.pickerButtonHeight = 20; // px
+		this.pickerClosable = false;
+		this.pickerCloseText = 'Close';
+		this.pickerButtonColor = 'ButtonText'; // px
+		this.pickerFace = 10; // px
+		this.pickerFaceColor = '#333'; // CSS color
+		this.pickerBorder = 1; // px
+		this.pickerBorderColor = '#777'; // CSS color
+		this.pickerInset = 1; // px
+		this.pickerInsetColor = '#999'; // CSS color
+		this.pickerZIndex = 10000;
+
+
+		for(var p in prop) {
+			if(prop.hasOwnProperty(p)) {
+				this[p] = prop[p];
+			}
+		}
+
+
+		this.hidePicker = function() {
+			if(isPickerOwner()) {
+				removePicker();
+			}
+		};
+
+
+		this.showPicker = function() {
+			if(!isPickerOwner()) {
+				var tp = jscolor.getElementPos(target); // target pos
+				var ts = jscolor.getElementSize(target); // target size
+				var vp = jscolor.getViewPos(); // view pos
+				var vs = jscolor.getViewSize(); // view size
+				var ps = getPickerDims(this); // picker size
+				var a, b, c;
+				switch(this.pickerPosition.toLowerCase()) {
+					case 'left': a=1; b=0; c=-1; break;
+					case 'right':a=1; b=0; c=1; break;
+					case 'top':  a=0; b=1; c=-1; break;
+					default:     a=0; b=1; c=1; break;
+				}
+				var l = (ts[b]+ps[b])/2;
+				var pp = [ // picker pos
+					-vp[a]+tp[a]+ps[a] > vs[a] ?
+						(-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) :
+						tp[a],
+					-vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ?
+						(-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) :
+						(tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c)
+				];
+				drawPicker(pp[a], pp[b]);
+			}
+		};
+
+
+		this.importColor = function() {
+			if(!valueElement) {
+				this.exportColor();
+			} else {
+				if(!this.adjust) {
+					if(!this.fromString(valueElement.value, leaveValue)) {
+						styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
+						styleElement.style.color = styleElement.jscStyle.color;
+						this.exportColor(leaveValue | leaveStyle);
+					}
+				} else if(!this.required && /^\s*$/.test(valueElement.value)) {
+					valueElement.value = '';
+					styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
+					styleElement.style.color = styleElement.jscStyle.color;
+					this.exportColor(leaveValue | leaveStyle);
+
+				} else if(this.fromString(valueElement.value)) {
+					// OK
+				} else {
+					this.exportColor();
+				}
+			}
+		};
+
+
+		this.exportColor = function(flags) {
+			if(!(flags & leaveValue) && valueElement) {
+				var value = this.toString();
+				if(this.caps) { value = value.toUpperCase(); }
+				if(this.hash) { value = '#'+value; }
+				valueElement.value = value;
+			}
+			if(!(flags & leaveStyle) && styleElement) {
+				styleElement.style.backgroundColor =
+					'#'+this.toString();
+				styleElement.style.color =
+					0.213 * this.rgb[0] +
+					0.715 * this.rgb[1] +
+					0.072 * this.rgb[2]
+					< 0.5 ? '#FFF' : '#000';
+			}
+			if(!(flags & leavePad) && isPickerOwner()) {
+				redrawPad();
+			}
+			if(!(flags & leaveSld) && isPickerOwner()) {
+				redrawSld();
+			}
+		};
+
+
+		this.fromHSV = function(h, s, v, flags) { // null = don't change
+			h<0 && (h=0) || h>6 && (h=6);
+			s<0 && (s=0) || s>1 && (s=1);
+			v<0 && (v=0) || v>1 && (v=1);
+			this.rgb = HSV_RGB(
+				h===null ? this.hsv[0] : (this.hsv[0]=h),
+				s===null ? this.hsv[1] : (this.hsv[1]=s),
+				v===null ? this.hsv[2] : (this.hsv[2]=v)
+			);
+			this.exportColor(flags);
+		};
+
+
+		this.fromRGB = function(r, g, b, flags) { // null = don't change
+			r<0 && (r=0) || r>1 && (r=1);
+			g<0 && (g=0) || g>1 && (g=1);
+			b<0 && (b=0) || b>1 && (b=1);
+			var hsv = RGB_HSV(
+				r===null ? this.rgb[0] : (this.rgb[0]=r),
+				g===null ? this.rgb[1] : (this.rgb[1]=g),
+				b===null ? this.rgb[2] : (this.rgb[2]=b)
+			);
+			if(hsv[0] !== null) {
+				this.hsv[0] = hsv[0];
+			}
+			if(hsv[2] !== 0) {
+				this.hsv[1] = hsv[1];
+			}
+			this.hsv[2] = hsv[2];
+			this.exportColor(flags);
+		};
+
+
+		this.fromString = function(hex, flags) {
+			var m = hex.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i);
+			if(!m) {
+				return false;
+			} else {
+				if(m[1].length === 6) { // 6-char notation
+					this.fromRGB(
+						parseInt(m[1].substr(0,2),16) / 255,
+						parseInt(m[1].substr(2,2),16) / 255,
+						parseInt(m[1].substr(4,2),16) / 255,
+						flags
+					);
+				} else { // 3-char notation
+					this.fromRGB(
+						parseInt(m[1].charAt(0)+m[1].charAt(0),16) / 255,
+						parseInt(m[1].charAt(1)+m[1].charAt(1),16) / 255,
+						parseInt(m[1].charAt(2)+m[1].charAt(2),16) / 255,
+						flags
+					);
+				}
+				return true;
+			}
+		};
+
+
+		this.toString = function() {
+			return (
+				(0x100 | Math.round(255*this.rgb[0])).toString(16).substr(1) +
+				(0x100 | Math.round(255*this.rgb[1])).toString(16).substr(1) +
+				(0x100 | Math.round(255*this.rgb[2])).toString(16).substr(1)
+			);
+		};
+
+
+		function RGB_HSV(r, g, b) {
+			var n = Math.min(Math.min(r,g),b);
+			var v = Math.max(Math.max(r,g),b);
+			var m = v - n;
+			if(m === 0) { return [ null, 0, v ]; }
+			var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m);
+			return [ h===6?0:h, m/v, v ];
+		}
+
+
+		function HSV_RGB(h, s, v) {
+			if(h === null) { return [ v, v, v ]; }
+			var i = Math.floor(h);
+			var f = i%2 ? h-i : 1-(h-i);
+			var m = v * (1 - s);
+			var n = v * (1 - s*f);
+			switch(i) {
+				case 6:
+				case 0: return [v,n,m];
+				case 1: return [n,v,m];
+				case 2: return [m,v,n];
+				case 3: return [m,n,v];
+				case 4: return [n,m,v];
+				case 5: return [v,m,n];
+			}
+		}
+
+
+		function removePicker() {
+			delete jscolor.picker.owner;
+			document.getElementsByTagName('body')[0].removeChild(jscolor.picker.boxB);
+		}
+
+
+		function drawPicker(x, y) {
+			if(!jscolor.picker) {
+				jscolor.picker = {
+					box : document.createElement('div'),
+					boxB : document.createElement('div'),
+					pad : document.createElement('div'),
+					padB : document.createElement('div'),
+					padM : document.createElement('div'),
+					sld : document.createElement('div'),
+					sldB : document.createElement('div'),
+					sldM : document.createElement('div'),
+					btn : document.createElement('div'),
+					btnS : document.createElement('span'),
+					btnT : document.createTextNode(THIS.pickerCloseText)
+				};
+				for(var i=0,segSize=4; i<jscolor.images.sld[1]; i+=segSize) {
+					var seg = document.createElement('div');
+					seg.style.height = segSize+'px';
+					seg.style.fontSize = '1px';
+					seg.style.lineHeight = '0';
+					jscolor.picker.sld.appendChild(seg);
+				}
+				jscolor.picker.sldB.appendChild(jscolor.picker.sld);
+				jscolor.picker.box.appendChild(jscolor.picker.sldB);
+				jscolor.picker.box.appendChild(jscolor.picker.sldM);
+				jscolor.picker.padB.appendChild(jscolor.picker.pad);
+				jscolor.picker.box.appendChild(jscolor.picker.padB);
+				jscolor.picker.box.appendChild(jscolor.picker.padM);
+				jscolor.picker.btnS.appendChild(jscolor.picker.btnT);
+				jscolor.picker.btn.appendChild(jscolor.picker.btnS);
+				jscolor.picker.box.appendChild(jscolor.picker.btn);
+				jscolor.picker.boxB.appendChild(jscolor.picker.box);
+			}
+
+			var p = jscolor.picker;
+
+			// controls interaction
+			p.box.onmouseup =
+			p.box.onmouseout = function() { target.focus(); };
+			p.box.onmousedown = function() { abortBlur=true; };
+			p.box.onmousemove = function(e) {
+				if (holdPad || holdSld) {
+					holdPad && setPad(e);
+					holdSld && setSld(e);
+					if (document.selection) {
+						document.selection.empty();
+					} else if (window.getSelection) {
+						window.getSelection().removeAllRanges();
+					}
+				}
+			};
+			p.padM.onmouseup =
+			p.padM.onmouseout = function() { if(holdPad) { holdPad=false; jscolor.fireEvent(valueElement,'change'); } };
+			p.padM.onmousedown = function(e) { holdPad=true; setPad(e); };
+			p.sldM.onmouseup =
+			p.sldM.onmouseout = function() { if(holdSld) { holdSld=false; jscolor.fireEvent(valueElement,'change'); } };
+			p.sldM.onmousedown = function(e) { holdSld=true; setSld(e); };
+
+			// picker
+			var dims = getPickerDims(THIS);
+			p.box.style.width = dims[0] + 'px';
+			p.box.style.height = dims[1] + 'px';
+
+			// picker border
+			p.boxB.style.position = 'absolute';
+			p.boxB.style.clear = 'both';
+			p.boxB.style.left = x+'px';
+			p.boxB.style.top = y+'px';
+			p.boxB.style.zIndex = THIS.pickerZIndex;
+			p.boxB.style.border = THIS.pickerBorder+'px solid';
+			p.boxB.style.borderColor = THIS.pickerBorderColor;
+			p.boxB.style.background = THIS.pickerFaceColor;
+			p.boxB.className = 'jscolor';
+
+			// pad image
+			p.pad.style.width = jscolor.images.pad[0]+'px';
+			p.pad.style.height = jscolor.images.pad[1]+'px';
+
+			// pad border
+			p.padB.style.position = 'absolute';
+			p.padB.style.left = THIS.pickerFace+'px';
+			p.padB.style.top = THIS.pickerFace+'px';
+			p.padB.style.border = THIS.pickerInset+'px solid';
+			p.padB.style.borderColor = THIS.pickerInsetColor;
+
+			// pad mouse area
+			p.padM.style.position = 'absolute';
+			p.padM.style.left = '0';
+			p.padM.style.top = '0';
+			p.padM.style.width = THIS.pickerFace + 2*THIS.pickerInset + jscolor.images.pad[0] + jscolor.images.arrow[0] + 'px';
+			p.padM.style.height = p.box.style.height;
+			p.padM.style.cursor = 'crosshair';
+
+			// slider image
+			p.sld.style.overflow = 'hidden';
+			p.sld.style.width = jscolor.images.sld[0]+'px';
+			p.sld.style.height = jscolor.images.sld[1]+'px';
+
+			// slider border
+			p.sldB.style.display = THIS.slider ? 'block' : 'none';
+			p.sldB.style.position = 'absolute';
+			p.sldB.style.right = THIS.pickerFace+'px';
+			p.sldB.style.top = THIS.pickerFace+'px';
+			p.sldB.style.border = THIS.pickerInset+'px solid';
+			p.sldB.style.borderColor = THIS.pickerInsetColor;
+
+			// slider mouse area
+			p.sldM.style.display = THIS.slider ? 'block' : 'none';
+			p.sldM.style.position = 'absolute';
+			p.sldM.style.right = '0';
+			p.sldM.style.top = '0';
+			p.sldM.style.width = jscolor.images.sld[0] + jscolor.images.arrow[0] + THIS.pickerFace + 2*THIS.pickerInset + 'px';
+			p.sldM.style.height = p.box.style.height;
+			try {
+				p.sldM.style.cursor = 'pointer';
+			} catch(eOldIE) {
+				p.sldM.style.cursor = 'hand';
+			}
+
+			// "close" button
+			function setBtnBorder() {
+				var insetColors = THIS.pickerInsetColor.split(/\s+/);
+				var pickerOutsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1];
+				p.btn.style.borderColor = pickerOutsetColor;
+			}
+			p.btn.style.display = THIS.pickerClosable ? 'block' : 'none';
+			p.btn.style.position = 'absolute';
+			p.btn.style.left = THIS.pickerFace + 'px';
+			p.btn.style.bottom = THIS.pickerFace + 'px';
+			p.btn.style.padding = '0 15px';
+			p.btn.style.height = '18px';
+			p.btn.style.border = THIS.pickerInset + 'px solid';
+			setBtnBorder();
+			p.btn.style.color = THIS.pickerButtonColor;
+			p.btn.style.font = '12px sans-serif';
+			p.btn.style.textAlign = 'center';
+			try {
+				p.btn.style.cursor = 'pointer';
+			} catch(eOldIE) {
+				p.btn.style.cursor = 'hand';
+			}
+			p.btn.onmousedown = function () {
+				THIS.hidePicker();
+			};
+			p.btnS.style.lineHeight = p.btn.style.height;
+
+			// load images in optimal order
+			switch(modeID) {
+				case 0: var padImg = 'hs.png'; break;
+				case 1: var padImg = 'hv.png'; break;
+			}
+			p.padM.style.backgroundImage = "url('"+jscolor.getDir()+"cross.gif')";
+			p.padM.style.backgroundRepeat = "no-repeat";
+			p.sldM.style.backgroundImage = "url('"+jscolor.getDir()+"arrow.gif')";
+			p.sldM.style.backgroundRepeat = "no-repeat";
+			p.pad.style.backgroundImage = "url('"+jscolor.getDir()+padImg+"')";
+			p.pad.style.backgroundRepeat = "no-repeat";
+			p.pad.style.backgroundPosition = "0 0";
+
+			// place pointers
+			redrawPad();
+			redrawSld();
+
+			jscolor.picker.owner = THIS;
+			document.getElementsByTagName('body')[0].appendChild(p.boxB);
+		}
+
+
+		function getPickerDims(o) {
+			var dims = [
+				2*o.pickerInset + 2*o.pickerFace + jscolor.images.pad[0] +
+					(o.slider ? 2*o.pickerInset + 2*jscolor.images.arrow[0] + jscolor.images.sld[0] : 0),
+				o.pickerClosable ?
+					4*o.pickerInset + 3*o.pickerFace + jscolor.images.pad[1] + o.pickerButtonHeight :
+					2*o.pickerInset + 2*o.pickerFace + jscolor.images.pad[1]
+			];
+			return dims;
+		}
+
+
+		function redrawPad() {
+			// redraw the pad pointer
+			switch(modeID) {
+				case 0: var yComponent = 1; break;
+				case 1: var yComponent = 2; break;
+			}
+			var x = Math.round((THIS.hsv[0]/6) * (jscolor.images.pad[0]-1));
+			var y = Math.round((1-THIS.hsv[yComponent]) * (jscolor.images.pad[1]-1));
+			jscolor.picker.padM.style.backgroundPosition =
+				(THIS.pickerFace+THIS.pickerInset+x - Math.floor(jscolor.images.cross[0]/2)) + 'px ' +
+				(THIS.pickerFace+THIS.pickerInset+y - Math.floor(jscolor.images.cross[1]/2)) + 'px';
+
+			// redraw the slider image
+			var seg = jscolor.picker.sld.childNodes;
+
+			switch(modeID) {
+				case 0:
+					var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 1);
+					for(var i=0; i<seg.length; i+=1) {
+						seg[i].style.backgroundColor = 'rgb('+
+							(rgb[0]*(1-i/seg.length)*100)+'%,'+
+							(rgb[1]*(1-i/seg.length)*100)+'%,'+
+							(rgb[2]*(1-i/seg.length)*100)+'%)';
+					}
+					break;
+				case 1:
+					var rgb, s, c = [ THIS.hsv[2], 0, 0 ];
+					var i = Math.floor(THIS.hsv[0]);
+					var f = i%2 ? THIS.hsv[0]-i : 1-(THIS.hsv[0]-i);
+					switch(i) {
+						case 6:
+						case 0: rgb=[0,1,2]; break;
+						case 1: rgb=[1,0,2]; break;
+						case 2: rgb=[2,0,1]; break;
+						case 3: rgb=[2,1,0]; break;
+						case 4: rgb=[1,2,0]; break;
+						case 5: rgb=[0,2,1]; break;
+					}
+					for(var i=0; i<seg.length; i+=1) {
+						s = 1 - 1/(seg.length-1)*i;
+						c[1] = c[0] * (1 - s*f);
+						c[2] = c[0] * (1 - s);
+						seg[i].style.backgroundColor = 'rgb('+
+							(c[rgb[0]]*100)+'%,'+
+							(c[rgb[1]]*100)+'%,'+
+							(c[rgb[2]]*100)+'%)';
+					}
+					break;
+			}
+		}
+
+
+		function redrawSld() {
+			// redraw the slider pointer
+			switch(modeID) {
+				case 0: var yComponent = 2; break;
+				case 1: var yComponent = 1; break;
+			}
+			var y = Math.round((1-THIS.hsv[yComponent]) * (jscolor.images.sld[1]-1));
+			jscolor.picker.sldM.style.backgroundPosition =
+				'0 ' + (THIS.pickerFace+THIS.pickerInset+y - Math.floor(jscolor.images.arrow[1]/2)) + 'px';
+		}
+
+
+		function isPickerOwner() {
+			return jscolor.picker && jscolor.picker.owner === THIS;
+		}
+
+
+		function blurTarget() {
+			if(valueElement === target) {
+				THIS.importColor();
+			}
+			if(THIS.pickerOnfocus) {
+				THIS.hidePicker();
+			}
+		}
+
+
+		function blurValue() {
+			if(valueElement !== target) {
+				THIS.importColor();
+			}
+		}
+
+
+		function setPad(e) {
+			var mpos = jscolor.getRelMousePos(e);
+			var x = mpos.x - THIS.pickerFace - THIS.pickerInset;
+			var y = mpos.y - THIS.pickerFace - THIS.pickerInset;
+			switch(modeID) {
+				case 0: THIS.fromHSV(x*(6/(jscolor.images.pad[0]-1)), 1 - y/(jscolor.images.pad[1]-1), null, leaveSld); break;
+				case 1: THIS.fromHSV(x*(6/(jscolor.images.pad[0]-1)), null, 1 - y/(jscolor.images.pad[1]-1), leaveSld); break;
+			}
+		}
+
+
+		function setSld(e) {
+			var mpos = jscolor.getRelMousePos(e);
+			var y = mpos.y - THIS.pickerFace - THIS.pickerInset;
+			switch(modeID) {
+				case 0: THIS.fromHSV(null, null, 1 - y/(jscolor.images.sld[1]-1), leavePad); break;
+				case 1: THIS.fromHSV(null, 1 - y/(jscolor.images.sld[1]-1), null, leavePad); break;
+			}
+		}
+
+
+		var THIS = this;
+		var modeID = this.pickerMode.toLowerCase()==='hvs' ? 1 : 0;
+		var abortBlur = false;
+		var
+			valueElement = jscolor.fetchElement(this.valueElement),
+			styleElement = jscolor.fetchElement(this.styleElement);
+		var
+			holdPad = false,
+			holdSld = false;
+		var
+			leaveValue = 1<<0,
+			leaveStyle = 1<<1,
+			leavePad = 1<<2,
+			leaveSld = 1<<3;
+
+		// target
+		jscolor.addEvent(target, 'focus', function() {
+			if(THIS.pickerOnfocus) { THIS.showPicker(); }
+		});
+		jscolor.addEvent(target, 'blur', function() {
+			if(!abortBlur) {
+				window.setTimeout(function(){ abortBlur || blurTarget(); abortBlur=false; }, 0);
+			} else {
+				abortBlur = false;
+			}
+		});
+
+		// valueElement
+		if(valueElement) {
+			var updateField = function() {
+				THIS.fromString(valueElement.value, leaveValue);
+			};
+			jscolor.addEvent(valueElement, 'keyup', updateField);
+			jscolor.addEvent(valueElement, 'input', updateField);
+			jscolor.addEvent(valueElement, 'blur', blurValue);
+			valueElement.setAttribute('autocomplete', 'off');
+		}
+
+		// styleElement
+		if(styleElement) {
+			styleElement.jscStyle = {
+				backgroundColor : styleElement.style.backgroundColor,
+				color : styleElement.style.color
+			};
+		}
+
+		// require images
+		switch(modeID) {
+			case 0: jscolor.requireImage('hs.png'); break;
+			case 1: jscolor.requireImage('hv.png'); break;
+		}
+		jscolor.requireImage('cross.gif');
+		jscolor.requireImage('arrow.gif');
+
+		this.importColor();
+	}
+
+};
+
+
+jscolor.install();
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/jscolor/jscolor.pack.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,11 @@
+/**
+ * jscolor, JavaScript Color Picker
+ *
+ * @version 1.3.9
+ * @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html
+ * @author  Jan Odvarko, http://odvarko.cz
+ * @created 2008-06-15
+ * @updated 2011-07-28
+ * @link    http://jscolor.com
+ */
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('D 8={24:\'\',3e:\'1g\',43:1d,3p:1d,3L:H(){8.1B(1m,\'5e\',8.3B)},3B:H(){G(8.43){8.3O()}G(8.3p){8.3S()}},1S:H(){G(!8.24){D a=8.34();8.24=a!==1f?a:\'8/\'}K 8.24},34:H(){D a=5c.2M;D e=I.1P(\'5a\');1v(D i=0;i<e.1j;i+=1){G(e[i].2M){a=e[i].2M}}D e=I.1P(\'59\');1v(D i=0;i<e.1j;i+=1){G(e[i].2q&&/(^|\\/)8\\.58([?#].*)?$/i.3I(e[i].2q)){D b=1F 8.2h(e[i].2q);D c=b.3Y(a);c.P=c.P.28(/[^\\/]+$/,\'\');c.17=J;c.1z=J;K c.1w()}}K 1f},3O:H(){D a=1F 56(\'(^|\\\\s)(\'+8.3e+\')\\\\s*(\\\\{[^}]*\\\\})?\',\'i\');D e=I.1P(\'3C\');1v(D i=0;i<e.1j;i+=1){D m;G(!e[i].1g&&e[i].2X&&(m=e[i].2X.2p(a))){D b={};G(m[3]){2z{55(\'54=\'+m[3])}2E(53){}}e[i].1g=1F 8.1g(e[i],b)}}},3S:H(){1v(D a 3c 8.2f){G(8.2f.3f(a)){8.3h(a)}}},O:{S:[52,3A],14:[16,3A],1Y:[15,15],1y:[7,11]},2f:{},2s:{},22:H(a){8.2f[a]=1d},3h:H(a){G(!8.2s[a]){8.2s[a]=1F 51();8.2s[a].2q=8.1S()+a}},2W:H(a){K 21 a===\'50\'?I.4Z(a):a},1B:H(a,b,c){G(a.37){a.37(b,c,1f)}N G(a.3d){a.3d(\'2o\'+b,c)}},2n:H(a,b){G(!a){K}G(I.3g){D c=I.3g(\'4Y\');c.4X(b,1d,1d);a.4M(c)}N G(I.3m){D c=I.3m();a.2n(\'2o\'+b,c)}N G(a[\'2o\'+b]){a[\'2o\'+b]()}},3n:H(e){D a=e,1D=e;D x=0,y=0;G(a.3v){4L{x+=a.4K;y+=a.4J}2Y(a=a.3v)}2Y((1D=1D.4I)&&1D.4H.3D()!==\'46\'){x-=1D.1X;y-=1D.1W}K[x,y]},3M:H(e){K[e.4C,e.4A]},2I:H(e){D x=0,y=0;G(!e){e=1m.4z}G(21 e.3W===\'26\'){x=e.3W;y=e.4y}N G(21 e.41===\'26\'){x=e.41;y=e.4u}K{x:x,y:y}},32:H(){G(21 1m.33===\'26\'){K[1m.4t,1m.33]}N G(I.1i&&(I.1i.1X||I.1i.1W)){K[I.1i.1X,I.1i.1W]}N G(I.1p&&(I.1p.1X||I.1p.1W)){K[I.1p.1X,I.1p.1W]}N{K[0,0]}},38:H(){G(21 1m.39===\'26\'){K[1m.39,1m.4s]}N G(I.1i&&(I.1i.2b||I.1i.2j)){K[I.1i.2b,I.1i.2j]}N G(I.1p&&(I.1p.2b||I.1p.2j)){K[I.1p.2b,I.1p.2j]}N{K[0,0]}},2h:H(d){C.1c=J;C.1k=J;C.P=\'\';C.17=J;C.1z=J;C.3j=H(a){D m=a.2p(/^(([A-3l-z][0-2O-3l-z+.-]*)(:))?((\\/\\/)([^\\/?#]*))?([^?#]*)((\\?)([^#]*))?((#)(.*))?/);C.1c=m[3]?m[2]:J;C.1k=m[5]?m[6]:J;C.P=m[7];C.17=m[9]?m[10]:J;C.1z=m[12]?m[13]:J;K C};C.1w=H(){D a=\'\';G(C.1c!==J){a=a+C.1c+\':\'}G(C.1k!==J){a=a+\'//\'+C.1k}G(C.P!==J){a=a+C.P}G(C.17!==J){a=a+\'?\'+C.17}G(C.1z!==J){a=a+\'#\'+C.1z}K a};C.3Y=H(a){D a=1F 8.2h(a);D r=C;D t=1F 8.2h;G(a.1c===J){K 1f}G(r.1c!==J&&r.1c.2c()===a.1c.2c()){r.1c=J}G(r.1c!==J){t.1c=r.1c;t.1k=r.1k;t.P=1O(r.P);t.17=r.17}N{G(r.1k!==J){t.1k=r.1k;t.P=1O(r.P);t.17=r.17}N{G(r.P===\'\'){t.P=a.P;G(r.17!==J){t.17=r.17}N{t.17=a.17}}N{G(r.P.19(0,1)===\'/\'){t.P=1O(r.P)}N{G(a.1k!==J&&a.P===\'\'){t.P=\'/\'+r.P}N{t.P=a.P.28(/[^\\/]+$/,\'\')+r.P}t.P=1O(t.P)}t.17=r.17}t.1k=a.1k}t.1c=a.1c}t.1z=r.1z;K t};H 1O(a){D b=\'\';2Y(a){G(a.19(0,3)===\'../\'||a.19(0,2)===\'./\'){a=a.28(/^\\.+/,\'\').19(1)}N G(a.19(0,3)===\'/./\'||a===\'/.\'){a=\'/\'+a.19(3)}N G(a.19(0,4)===\'/../\'||a===\'/..\'){a=\'/\'+a.19(4);b=b.28(/\\/?[^\\/]*$/,\'\')}N G(a===\'.\'||a===\'..\'){a=\'\'}N{D c=a.2p(/^\\/?[^\\/]*/)[0];a=a.19(c.1j);b=b+c}}K b}G(d){C.3j(d)}},1g:H(j,k){C.3E=1d;C.3F=1d;C.3G=1f;C.3H=1d;C.2k=1d;C.3J=j;C.U=j;C.T=[0,0,1];C.Y=[1,1,1];C.2Q=1d;C.3N=\'4p\';C.3P=\'40\';C.3U=20;C.2V=1f;C.3X=\'4o\';C.3Z=\'4m\';C.V=10;C.42=\'#4l\';C.44=1;C.45=\'#4k\';C.Z=1;C.2e=\'#4j\';C.35=4i;1v(D p 3c k){G(k.3f(p)){C[p]=k[p]}}C.2A=H(){G(1U()){3a()}};C.3b=H(){G(!1U()){D d=8.3n(j);D e=8.3M(j);D f=8.32();D g=8.38();D h=2J(C);D a,b,c;1r(C.3P.2c()){M\'1Q\':a=1;b=0;c=-1;R;M\'2P\':a=1;b=0;c=1;R;M\'1M\':a=0;b=1;c=-1;R;4h:a=0;b=1;c=1;R}D l=(e[b]+h[b])/2;D i=[-f[a]+d[a]+h[a]>g[a]?(-f[a]+d[a]+e[a]/2>g[a]/2&&d[a]+e[a]-h[a]>=0?d[a]+e[a]-h[a]:d[a]):d[a],-f[b]+d[b]+e[b]+h[b]-l+l*c>g[b]?(-f[b]+d[b]+e[b]/2>g[b]/2&&d[b]+e[b]-l-l*c>=0?d[b]+e[b]-l-l*c:d[b]+e[b]-l+l*c):(d[b]+e[b]-l+l*c>=0?d[b]+e[b]-l+l*c:d[b]+e[b]-l-l*c)];3o(i[a],i[b])}};C.2i=H(){G(!w){C.1A()}N{G(!C.3F){G(!C.2r(w.1C,A)){U.E.1u=U.1T.1u;U.E.1g=U.1T.1g;C.1A(A|25)}}N G(!C.3E&&/^\\s*$/.3I(w.1C)){w.1C=\'\';U.E.1u=U.1T.1u;U.E.1g=U.1T.1g;C.1A(A|25)}N G(C.2r(w.1C)){}N{C.1A()}}};C.1A=H(a){G(!(a&A)&&w){D b=C.1w();G(C.3H){b=b.3D()}G(C.3G){b=\'#\'+b}w.1C=b}G(!(a&25)&&U){U.E.1u=\'#\'+C.1w();U.E.1g=0.4g*C.Y[0]+0.4d*C.Y[1]+0.4c*C.Y[2]<0.5?\'#4b\':\'#4a\'}G(!(a&29)&&1U()){2B()}G(!(a&2a)&&1U()){2D()}};C.1N=H(h,s,v,a){h<0&&(h=0)||h>6&&(h=6);s<0&&(s=0)||s>1&&(s=1);v<0&&(v=0)||v>1&&(v=1);C.Y=2G(h===J?C.T[0]:(C.T[0]=h),s===J?C.T[1]:(C.T[1]=s),v===J?C.T[2]:(C.T[2]=v));C.1A(a)};C.2H=H(r,g,b,a){r<0&&(r=0)||r>1&&(r=1);g<0&&(g=0)||g>1&&(g=1);b<0&&(b=0)||b>1&&(b=1);D c=3K(r===J?C.Y[0]:(C.Y[0]=r),g===J?C.Y[1]:(C.Y[1]=g),b===J?C.Y[2]:(C.Y[2]=b));G(c[0]!==J){C.T[0]=c[0]}G(c[2]!==0){C.T[1]=c[1]}C.T[2]=c[2];C.1A(a)};C.2r=H(a,b){D m=a.2p(/^\\W*([0-2O-F]{3}([0-2O-F]{3})?)\\W*$/i);G(!m){K 1f}N{G(m[1].1j===6){C.2H(1J(m[1].19(0,2),16)/1t,1J(m[1].19(2,2),16)/1t,1J(m[1].19(4,2),16)/1t,b)}N{C.2H(1J(m[1].1G(0)+m[1].1G(0),16)/1t,1J(m[1].1G(1)+m[1].1G(1),16)/1t,1J(m[1].1G(2)+m[1].1G(2),16)/1t,b)}K 1d}};C.1w=H(){K((2L|1b.1E(1t*C.Y[0])).1w(16).19(1)+(2L|1b.1E(1t*C.Y[1])).1w(16).19(1)+(2L|1b.1E(1t*C.Y[2])).1w(16).19(1))};H 3K(r,g,b){D n=1b.3Q(1b.3Q(r,g),b);D v=1b.3R(1b.3R(r,g),b);D m=v-n;G(m===0){K[J,0,v]}D h=r===n?3+(b-g)/m:(g===n?5+(r-b)/m:1+(g-r)/m);K[h===6?0:h,m/v,v]}H 2G(h,s,v){G(h===J){K[v,v,v]}D i=1b.1Z(h);D f=i%2?h-i:1-(h-i);D m=v*(1-s);D n=v*(1-s*f);1r(i){M 6:M 0:K[v,n,m];M 1:K[n,v,m];M 2:K[m,v,n];M 3:K[m,n,v];M 4:K[n,m,v];M 5:K[v,m,n]}}H 3a(){48 8.L.2R;I.1P(\'1i\')[0].47(8.L.1h)}H 3o(x,y){G(!8.L){8.L={1a:I.1n(\'1o\'),1h:I.1n(\'1o\'),S:I.1n(\'1o\'),1x:I.1n(\'1o\'),1e:I.1n(\'1o\'),14:I.1n(\'1o\'),1s:I.1n(\'1o\'),18:I.1n(\'1o\'),X:I.1n(\'1o\'),2l:I.1n(\'4G\'),3T:I.49(q.3X)};1v(D i=0,2y=4;i<8.O.14[1];i+=2y){D c=I.1n(\'1o\');c.E.1q=2y+\'Q\';c.E.4e=\'4f\';c.E.3x=\'0\';8.L.14.1l(c)}8.L.1s.1l(8.L.14);8.L.1a.1l(8.L.1s);8.L.1a.1l(8.L.18);8.L.1x.1l(8.L.S);8.L.1a.1l(8.L.1x);8.L.1a.1l(8.L.1e);8.L.2l.1l(8.L.3T);8.L.X.1l(8.L.2l);8.L.1a.1l(8.L.X);8.L.1h.1l(8.L.1a)}D p=8.L;p.1a.2x=p.1a.2w=H(){j.31()};p.1a.2g=H(){u=1d};p.1a.4n=H(e){G(z||1I){z&&2U(e);1I&&30(e);G(I.3u){I.3u.4q()}N G(1m.3t){1m.3t().4r()}}};p.1e.2x=p.1e.2w=H(){G(z){z=1f;8.2n(w,\'3s\')}};p.1e.2g=H(e){z=1d;2U(e)};p.18.2x=p.18.2w=H(){G(1I){1I=1f;8.2n(w,\'3s\')}};p.18.2g=H(e){1I=1d;30(e)};D d=2J(q);p.1a.E.1R=d[0]+\'Q\';p.1a.E.1q=d[1]+\'Q\';p.1h.E.1L=\'1K\';p.1h.E.4v=\'4w\';p.1h.E.1Q=x+\'Q\';p.1h.E.1M=y+\'Q\';p.1h.E.4x=q.35;p.1h.E.23=q.44+\'Q 27\';p.1h.E.2t=q.45;p.1h.E.4B=q.42;p.1h.2X=\'8\';p.S.E.1R=8.O.S[0]+\'Q\';p.S.E.1q=8.O.S[1]+\'Q\';p.1x.E.1L=\'1K\';p.1x.E.1Q=q.V+\'Q\';p.1x.E.1M=q.V+\'Q\';p.1x.E.23=q.Z+\'Q 27\';p.1x.E.2t=q.2e;p.1e.E.1L=\'1K\';p.1e.E.1Q=\'0\';p.1e.E.1M=\'0\';p.1e.E.1R=q.V+2*q.Z+8.O.S[0]+8.O.1y[0]+\'Q\';p.1e.E.1q=p.1a.E.1q;p.1e.E.1V=\'4D\';p.14.E.4E=\'4F\';p.14.E.1R=8.O.14[0]+\'Q\';p.14.E.1q=8.O.14[1]+\'Q\';p.1s.E.2v=q.2k?\'2u\':\'2Z\';p.1s.E.1L=\'1K\';p.1s.E.2P=q.V+\'Q\';p.1s.E.1M=q.V+\'Q\';p.1s.E.23=q.Z+\'Q 27\';p.1s.E.2t=q.2e;p.18.E.2v=q.2k?\'2u\':\'2Z\';p.18.E.1L=\'1K\';p.18.E.2P=\'0\';p.18.E.1M=\'0\';p.18.E.1R=8.O.14[0]+8.O.1y[0]+q.V+2*q.Z+\'Q\';p.18.E.1q=p.1a.E.1q;2z{p.18.E.1V=\'3z\'}2E(3y){p.18.E.1V=\'3w\'}H 3k(){D a=q.2e.4N(/\\s+/);D b=a.1j<2?a[0]:a[1]+\' \'+a[0]+\' \'+a[0]+\' \'+a[1];p.X.E.2t=b}p.X.E.2v=q.2V?\'2u\':\'2Z\';p.X.E.1L=\'1K\';p.X.E.1Q=q.V+\'Q\';p.X.E.40=q.V+\'Q\';p.X.E.4O=\'0 4P\';p.X.E.1q=\'4Q\';p.X.E.23=q.Z+\'Q 27\';3k();p.X.E.1g=q.3Z;p.X.E.4R=\'4S 4T-4U\';p.X.E.4V=\'4W\';2z{p.X.E.1V=\'3z\'}2E(3y){p.X.E.1V=\'3w\'}p.X.2g=H(){q.2A()};p.2l.E.3x=p.X.E.1q;1r(t){M 0:D f=\'3i.2m\';R;M 1:D f=\'36.2m\';R}p.1e.E.2C="2T(\'"+8.1S()+"1Y.2d\')";p.1e.E.2F="2K-2S";p.18.E.2C="2T(\'"+8.1S()+"1y.2d\')";p.18.E.2F="2K-2S";p.S.E.2C="2T(\'"+8.1S()+f+"\')";p.S.E.2F="2K-2S";p.S.E.2N="0 0";2B();2D();8.L.2R=q;I.1P(\'1i\')[0].1l(p.1h)}H 2J(o){D a=[2*o.Z+2*o.V+8.O.S[0]+(o.2k?2*o.Z+2*8.O.1y[0]+8.O.14[0]:0),o.2V?4*o.Z+3*o.V+8.O.S[1]+o.3U:2*o.Z+2*o.V+8.O.S[1]];K a}H 2B(){1r(t){M 0:D a=1;R;M 1:D a=2;R}D x=1b.1E((q.T[0]/6)*(8.O.S[0]-1));D y=1b.1E((1-q.T[a])*(8.O.S[1]-1));8.L.1e.E.2N=(q.V+q.Z+x-1b.1Z(8.O.1Y[0]/2))+\'Q \'+(q.V+q.Z+y-1b.1Z(8.O.1Y[1]/2))+\'Q\';D b=8.L.14.57;1r(t){M 0:D d=2G(q.T[0],q.T[1],1);1v(D i=0;i<b.1j;i+=1){b[i].E.1u=\'Y(\'+(d[0]*(1-i/b.1j)*1H)+\'%,\'+(d[1]*(1-i/b.1j)*1H)+\'%,\'+(d[2]*(1-i/b.1j)*1H)+\'%)\'}R;M 1:D d,s,c=[q.T[2],0,0];D i=1b.1Z(q.T[0]);D f=i%2?q.T[0]-i:1-(q.T[0]-i);1r(i){M 6:M 0:d=[0,1,2];R;M 1:d=[1,0,2];R;M 2:d=[2,0,1];R;M 3:d=[2,1,0];R;M 4:d=[1,2,0];R;M 5:d=[0,2,1];R}1v(D i=0;i<b.1j;i+=1){s=1-1/(b.1j-1)*i;c[1]=c[0]*(1-s*f);c[2]=c[0]*(1-s);b[i].E.1u=\'Y(\'+(c[d[0]]*1H)+\'%,\'+(c[d[1]]*1H)+\'%,\'+(c[d[2]]*1H)+\'%)\'}R}}H 2D(){1r(t){M 0:D a=2;R;M 1:D a=1;R}D y=1b.1E((1-q.T[a])*(8.O.14[1]-1));8.L.18.E.2N=\'0 \'+(q.V+q.Z+y-1b.1Z(8.O.1y[1]/2))+\'Q\'}H 1U(){K 8.L&&8.L.2R===q}H 3q(){G(w===j){q.2i()}G(q.2Q){q.2A()}}H 3V(){G(w!==j){q.2i()}}H 2U(e){D a=8.2I(e);D x=a.x-q.V-q.Z;D y=a.y-q.V-q.Z;1r(t){M 0:q.1N(x*(6/(8.O.S[0]-1)),1-y/(8.O.S[1]-1),J,2a);R;M 1:q.1N(x*(6/(8.O.S[0]-1)),J,1-y/(8.O.S[1]-1),2a);R}}H 30(e){D a=8.2I(e);D y=a.y-q.V-q.Z;1r(t){M 0:q.1N(J,J,1-y/(8.O.14[1]-1),29);R;M 1:q.1N(J,1-y/(8.O.14[1]-1),J,29);R}}D q=C;D t=C.3N.2c()===\'5b\'?1:0;D u=1f;D w=8.2W(C.3J),U=8.2W(C.U);D z=1f,1I=1f;D A=1<<0,25=1<<1,29=1<<2,2a=1<<3;8.1B(j,\'31\',H(){G(q.2Q){q.3b()}});8.1B(j,\'3r\',H(){G(!u){1m.5d(H(){u||3q();u=1f},0)}N{u=1f}});G(w){D B=H(){q.2r(w.1C,A)};8.1B(w,\'5f\',B);8.1B(w,\'3C\',B);8.1B(w,\'3r\',3V);w.5g(\'5h\',\'5i\')}G(U){U.1T={1u:U.E.1u,1g:U.E.1g}}1r(t){M 0:8.22(\'3i.2m\');R;M 1:8.22(\'36.2m\');R}8.22(\'1Y.2d\');8.22(\'1y.2d\');C.2i()}};8.3L();',62,329,'||||||||jscolor||||||||||||||||||||||||||||||this|var|style||if|function|document|null|return|picker|case|else|images|path|px|break|pad|hsv|styleElement|pickerFace||btn|rgb|pickerInset|||||sld|||query|sldM|substr|box|Math|scheme|true|padM|false|color|boxB|body|length|authority|appendChild|window|createElement|div|documentElement|height|switch|sldB|255|backgroundColor|for|toString|padB|arrow|fragment|exportColor|addEvent|value|e2|round|new|charAt|100|holdSld|parseInt|absolute|position|top|fromHSV|removeDotSegments|getElementsByTagName|left|width|getDir|jscStyle|isPickerOwner|cursor|scrollTop|scrollLeft|cross|floor||typeof|requireImage|border|dir|leaveStyle|number|solid|replace|leavePad|leaveSld|clientWidth|toLowerCase|gif|pickerInsetColor|imgRequire|onmousedown|URI|importColor|clientHeight|slider|btnS|png|fireEvent|on|match|src|fromString|imgLoaded|borderColor|block|display|onmouseout|onmouseup|segSize|try|hidePicker|redrawPad|backgroundImage|redrawSld|catch|backgroundRepeat|HSV_RGB|fromRGB|getRelMousePos|getPickerDims|no|0x100|href|backgroundPosition|9A|right|pickerOnfocus|owner|repeat|url|setPad|pickerClosable|fetchElement|className|while|none|setSld|focus|getViewPos|pageYOffset|detectDir|pickerZIndex|hv|addEventListener|getViewSize|innerWidth|removePicker|showPicker|in|attachEvent|bindClass|hasOwnProperty|createEvent|loadImage|hs|parse|setBtnBorder|Za|createEventObject|getElementPos|drawPicker|preloading|blurTarget|blur|change|getSelection|selection|offsetParent|hand|lineHeight|eOldIE|pointer|101|init|input|toUpperCase|required|adjust|hash|caps|test|valueElement|RGB_HSV|install|getElementSize|pickerMode|bind|pickerPosition|min|max|preload|btnT|pickerButtonHeight|blurValue|offsetX|pickerCloseText|toAbsolute|pickerButtonColor|bottom|layerX|pickerFaceColor|binding|pickerBorder|pickerBorderColor|BODY|removeChild|delete|createTextNode|000|FFF|072|715|fontSize|1px|213|default|10000|999|777|333|ButtonText|onmousemove|Close|HSV|empty|removeAllRanges|innerHeight|pageXOffset|layerY|clear|both|zIndex|offsetY|event|offsetHeight|background|offsetWidth|crosshair|overflow|hidden|span|nodeName|parentNode|offsetTop|offsetLeft|do|dispatchEvent|split|padding|15px|18px|font|12px|sans|serif|textAlign|center|initEvent|HTMLEvents|getElementById|string|Image|181|eInvalidProp|prop|eval|RegExp|childNodes|js|script|base|hvs|location|setTimeout|load|keyup|setAttribute|autocomplete|off'.split('|'),0,{}))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/minmax.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,135 @@
+// minmax.js: make IE5+/Win support CSS min/max-width/height
+// version 1.0, 08-Aug-2003
+// written by Andrew Clover <and@doxdesk.com>, use freely
+
+/*@cc_on
+@if (@_win32 && @_jscript_version>4)
+
+var minmax_elements;
+
+minmax_props= new Array(
+  new Array('min-width', 'minWidth'),
+  new Array('max-width', 'maxWidth'),
+  new Array('min-height','minHeight'),
+  new Array('max-height','maxHeight')
+);
+
+// Binding. Called on all new elements. If <body>, initialise; check all
+// elements for minmax properties
+
+function minmax_bind(el) {
+  var i, em, ms;
+  var st= el.style, cs= el.currentStyle;
+
+  if (minmax_elements==window.undefined) {
+    // initialise when body element has turned up, but only on IE
+    if (!document.body || !document.body.currentStyle) return;
+    minmax_elements= new Array();
+    window.attachEvent('onresize', minmax_delayout);
+    // make font size listener
+    em= document.createElement('div');
+    em.setAttribute('id', 'minmax_em');
+    em.style.position= 'absolute'; em.style.visibility= 'hidden';
+    em.style.fontSize= 'xx-large'; em.style.height= '5em';
+    em.style.top='-5em'; em.style.left= '0';
+    if (em.style.setExpression) {
+      em.style.setExpression('width', 'minmax_checkFont()');
+      document.body.insertBefore(em, document.body.firstChild);
+    }
+  }
+
+  // transform hyphenated properties the browser has not caught to camelCase
+  for (i= minmax_props.length; i-->0;)
+    if (cs[minmax_props[i][0]])
+      st[minmax_props[i][1]]= cs[minmax_props[i][0]];
+  // add element with properties to list, store optimal size values
+  for (i= minmax_props.length; i-->0;) {
+    ms= cs[minmax_props[i][1]];
+    if (ms && ms!='auto' && ms!='none' && ms!='0' && ms!='') {
+      st.minmaxWidth= cs.width; st.minmaxHeight= cs.height;
+      minmax_elements[minmax_elements.length]= el;
+      // will need a layout later
+      minmax_delayout();
+      break;
+  } }
+}
+
+// check for font size changes
+
+var minmax_fontsize= 0;
+function minmax_checkFont() {
+  var fs= document.getElementById('minmax_em').offsetHeight;
+  if (minmax_fontsize!=fs && minmax_fontsize!=0)
+    minmax_delayout();
+  minmax_fontsize= fs;
+  return '5em';
+}
+
+// Layout. Called after window and font size-change. Go through elements we
+// picked out earlier and set their size to the minimum, maximum and optimum,
+// choosing whichever is appropriate
+
+function minmax_delayout() {
+  minmax_layout();
+}
+
+function minmax_layout() {
+  var i, el, st, cs, optimal, inrange;
+  for (i= minmax_elements.length; i-->0;) {
+    el= minmax_elements[i]; st= el.style; cs= el.currentStyle;
+
+    // horizontal size bounding
+    st.width= st.minmaxWidth; optimal= el.offsetWidth;
+    inrange= true;
+    if (inrange && cs.minWidth && cs.minWidth!='0' && cs.minWidth!='auto' && cs.minWidth!='') {
+      st.width= cs.minWidth;
+      inrange= (el.offsetWidth<optimal);
+    }
+    if (inrange && cs.maxWidth && cs.maxWidth!='none' && cs.maxWidth!='auto' && cs.maxWidth!='') {
+      st.width= cs.maxWidth;
+      inrange= (el.offsetWidth>optimal);
+    }
+    if (inrange) st.width= st.minmaxWidth;
+
+    // vertical size bounding
+    st.height= st.minmaxHeight; optimal= el.offsetHeight;
+    inrange= true;
+    if (inrange && cs.minHeight && cs.minHeight!='0' && cs.minHeight!='auto' && cs.minHeight!='') {
+      st.height= cs.minHeight;
+      inrange= (el.offsetHeight<optimal);
+    }
+    if (inrange && cs.maxHeight && cs.maxHeight!='none' && cs.maxHeight!='auto' && cs.maxHeight!='') {
+      st.height= cs.maxHeight;
+      inrange= (el.offsetHeight>optimal);
+    }
+    if (inrange) st.height= st.minmaxHeight;
+  }
+}
+
+// Scanning. Check document every so often until it has finished loading. Do
+// nothing until <body> arrives, then call main init. Pass any new elements
+// found on each scan to be bound   
+
+var minmax_SCANDELAY= 500;
+
+function minmax_scan() {
+  var el;
+  for (var i= 0; i<document.all.length; i++) {
+    el= document.all[i];
+    if (!el.minmax_bound) {
+      el.minmax_bound= true;
+      minmax_bind(el);
+  } }
+}
+
+var minmax_scanner;
+function minmax_stop() {
+  window.clearInterval(minmax_scanner);
+  minmax_scan();
+}
+
+minmax_scan();
+minmax_scanner=window.setInterval(minmax_scan, minmax_SCANDELAY);
+window.attachEvent('onload', minmax_stop);
+
+@end @*/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/nabbledropdown-2.4.1.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,356 @@
+var dropdownItems = [];
+var $visibleSubmenu = null;
+var INTERVAL = null;
+
+var itemColor;
+function getItemColor() {
+	if (!itemColor)
+		itemColor = $('span.dropdown-item').css('color');
+	return itemColor;
+};
+
+function closeDropdowns() {
+	for (var i = 0; i < dropdownItems.length; i++) {
+		var span = $('#'+dropdownItems[i]);
+		$('table,ul', span).fadeOut("fast");
+	}
+	$('ul.dropdown-submenu').fadeOut("fast");
+	clearTimeout(INTERVAL);
+	$visibleSubmenu = null;
+};
+
+function dropdownUpdateLink(columnId) {
+	var row = $('#'+columnId).parent().get();
+	_update(row);
+};
+
+function _update(row, checkdone) {
+	var $row = $(row);
+	if ($row.attr('done') && checkdone)
+		return;
+	var submenu = $row.attr('submenu')? '<img class="submenu" src="/images/submenu.gif" width=8 height=13 style="float:right;margin-top:.3em"/>':null;
+
+	var $link = $('a', row);
+	$link.wrapInner('<span class="dropdown-item"></span>');
+	if ($link && !$link.attr('ignore')) {
+		if (submenu) {
+			$link.css({display:'block',marginRight:'1em'}).parent().prepend(submenu);
+		} else {
+			$link
+				.css('width', '100%')
+				.css('display','block')
+				.css('color', getItemColor());
+		}
+	}
+	$row.attr('done','y');
+};
+
+function dropdownCleanup(id) {
+	$('#'+id).unbind().removeAttr('init');
+};
+
+function dropdownClick(o) {
+	var $this = $(this);
+	var id = $this.attr('id');
+	if (!$this.attr('init')) {
+		firstTime(id);
+		$this.attr('init', '1');
+	}
+	var submenu = $(o.target).parents('tr').attr('submenu');
+	if (submenu) {
+		o.stopPropagation();
+		return;
+	}
+	var $t = $('table', this);
+	closeDropdowns();
+	if (!$t.is(':visible')) {
+		var $link = $t.siblings().first();
+		var off = $link.offset();
+		var w = $(window).width();
+		var left = off.left;
+		var top = off.top + $link.outerHeight();
+		if (left + $t.outerWidth() > w) {
+			left = w - $t.outerWidth() - 10;
+		}
+		$t.css({ left: left,  top: top});
+		$t.show(function() {
+			if (left + $t.outerWidth() > w) {
+				left = w - $t.outerWidth() - 10;
+				$t.css('left', left);
+			}
+		});
+	}
+};
+
+function dropdownInit(id) {
+	var $drop = $('#'+id);
+	$drop.unbind().click(dropdownClick);
+};
+
+function firstTime(id) {
+	var $drop = $('#'+id);
+	dropdownItems.push(id);
+	var $t = $('table', $drop);
+	var colorNormal = null;
+	var colorSelected = null;
+
+	var $tr = $('tr', $drop);
+	$tr.each(function() {
+		if ($(this).hasClass('dropdown-separator'))
+			return;
+		_update(this);
+		var $row = $(this);
+
+		var submenuId = $(this).attr('submenu');
+		var $submenu = submenuId? $('#'+submenuId) : null;
+
+		if ($submenu) {
+			$('a', $submenu)
+				.css('display','block')
+				.css('width', '100%')
+				.css('color', getItemColor())
+				.wrapInner('<span class="dropdown-item"/>');
+
+			$('li',$submenu).hover(
+				function(e) {
+					if (colorSelected) $(this).css('background-color',colorSelected);
+					clearTimeout(INTERVAL);
+					e.stopPropagation();
+				},
+				function(e) {
+					if (colorNormal) $(this).css('background-color',colorNormal);
+					e.stopPropagation();
+				}
+			);
+		}
+
+		function handleSubmenu() {
+			clearTimeout(INTERVAL);
+			if ($submenu)
+				INTERVAL = setTimeout(openSubmenu, 400);
+			else
+				INTERVAL = setTimeout(closeSubmenu, 600);
+		};
+
+		function openSubmenu() {
+			if ($visibleSubmenu == $submenu)
+				return;
+			closeSubmenu();
+			$submenu.css('background-color',$t.css('background-color'));
+			var rh = $row.outerHeight();
+			rh = rh % 2 == 0? rh : rh-1;
+			var top = $row.offset().top - rh/2;
+			var left = $t.offset().left + $t.outerWidth();
+			if ($submenu.outerWidth() + left > $(window).width() - 20) {
+				left = $t.offset().left - $submenu.outerWidth();
+				left = left < 0? 0 : left;
+			}
+			$submenu.css({top:top,left:left});
+			$submenu.fadeIn('fast');
+			$visibleSubmenu = $submenu;
+		};
+
+		function closeSubmenu() {
+			if ($visibleSubmenu)
+				$visibleSubmenu.fadeOut();
+			$visibleSubmenu = null;
+		};
+
+		$(this).hover(
+			function(e) {
+				$(this).addClass('dark-bg-color');
+				if (!colorSelected) colorSelected = $(this).css('background-color');
+				handleSubmenu();
+				e.stopPropagation();
+			},
+			function(e) {
+				$(this).removeClass('dark-bg-color');
+				if (!colorNormal) colorNormal = $(this).css('background-color');
+				e.stopPropagation();
+			}
+		);
+	});
+	NabbleDropdown.showSeparators($tr.get(0));
+};
+
+$(document).ready(function() {
+	$(document).click(function(o){
+		var $tg = $(o.target);
+		var parents = $tg.parents('.dropdown');
+		if (parents.size() == 0 && !$tg.hasClass('dropdown'))
+			closeDropdowns();
+	});
+});
+
+/* Dropdown builder */
+
+function NabbleDropdown(id,linkText,linkTitle) {
+	this.id = id;
+	this.text = linkText;
+	this.title = linkTitle;
+	this.options = [];
+	this.submenus= [];
+	this.groupCounter = 0;
+	this.built = false;
+};
+
+NabbleDropdown.prototype.customButton = function(element) {
+	this.element = element;
+};
+
+NabbleDropdown.prototype.add = function(id,contents,style) {
+	var o = {id:id,contents:contents,style:style};
+	if (this.currentGroup) {
+		var lastGroup = this.submenus.length == 0? null : this.submenus[this.submenus.length-1];
+		if (lastGroup == null || lastGroup.id != this.currentGroup) {
+			lastGroup = {id:this.currentGroup, options: []};
+			this.submenus.push(lastGroup);
+		}
+		lastGroup.options.push(o);
+	} else
+		this.options.push(o);
+};
+
+NabbleDropdown.prototype.addSeparator = function() {
+	this.options.push('separator');
+};
+
+NabbleDropdown.prototype.startGroup = function(text) {
+	this.currentGroup = 'sm'+this.groupCounter;
+	this.options.push({id:this.currentGroup+'-id',contents:"<a href=\"javascript:void(0)\">"+text+"</a>",style:"display:none",submenu:this.currentGroup});
+	this.groupCounter++;
+};
+
+NabbleDropdown.prototype.endGroup = function() {
+	this.currentGroup = null;
+};
+
+NabbleDropdown.prototype.addCustomSubmenu = function(text, boxId) {
+	this.options.push({id:boxId+'-id',contents:"<a href=\"javascript:void(0)\">"+text+"</a>",submenu:boxId});
+	this.currentGroup = null;
+};
+
+NabbleDropdown.prototype.getHtml = function() {
+	var html = '<span id="'+this.id+'" class="dropdown">';
+	if (this.element)
+		html += this.element;
+	else {
+		html += '<a href="javascript:void(0)"' + (this.title?' title="'+this.title+'"':'') + '>'+this.text+'</a> ';
+		html += '<img src="/images/more.png" width="10" height="10"/>';
+	}
+	html += '<table class="light-bg-color medium-border-color drop-shadow" style="margin-top:1px;">';
+	html += '<tr id="_loading' +this.id+'" style="display:none"><td class="dropdown-simple-row"><span id="_loadingSpan'+this.id+'">Loading...</span></td></tr>';
+	for (var i=0; i<this.options.length;i++) {
+		var x = this.options[i];
+		if (x == 'separator') {
+			html += '<tr class="dropdown-separator"><td class="action-separator medium-border-color">&nbsp;</td></tr>';
+		} else {
+			html += '<tr';
+			if (x.id) html += ' id="'+x.id+'"';
+			if (x.style) html += ' style="'+x.style+'"';
+			if (x.submenu) html += ' submenu="'+x.submenu+'"';
+			html += '><td style="border:none">'+x.contents+'</td></tr>';
+		}
+	}
+	html += '</table>';
+	for (var i=0;i<this.submenus.length;i++) {
+		var x = this.submenus[i];
+		html += '<ul id="'+x.id+'" class="light-bg-color medium-border-color drop-shadow">';
+		for (var j=0;j<x.options.length;j++) {
+			var y = x.options[j];
+			html += '<li';
+			if (y.id) html += ' id="'+y.id+'"';
+			if (y.style) html += ' style="'+y.style+'"';
+			html += '>'+y.contents+'</li>';
+		}
+		html += '</ul>';
+	}
+	html += '</span>';
+	return html;
+};
+
+NabbleDropdown.prototype.build = function(elemId) {
+	$('#'+elemId).html(this.getHtml());
+	dropdownInit(this.id);
+};
+
+NabbleDropdown.prototype.loadingAnimation = function(id,action) {
+	var $span = $('#_loadingSpan'+id);
+	if (action == 'start') {
+		NabbleDropdown.show('_loading'+id);
+		function loading1() { $span.fadeTo(300,0.3,loading2);  };
+		function loading2() { $span.fadeTo(300,1,loading1);  };
+		loading1();
+	} else {
+		NabbleDropdown.hide('_loading'+id);
+		$span.stop();
+	}
+};
+
+NabbleDropdown.prototype.loadOnClick = function(scriptUrl) {
+	var _this = this;
+	var id = this.id;
+	var hasRequested = false;
+	var $drop = $('#'+this.id);
+	$drop.mouseover(function() {
+		if (!hasRequested) {
+			hasRequested = true;
+			_this.loadingAnimation(id, 'start');
+			$.getScript(scriptUrl, function() {
+				_this.loadingAnimation(id, 'stop');
+			});
+		}
+	});
+};
+
+NabbleDropdown.show = function(id) {
+	var e = document.getElementById(id);
+	if (e.nodeName == 'LI') {
+		e.style.display = 'list-item';
+		var p = document.getElementById(e.parentNode['id']+'-id');
+		p.style.display = $.browser.msie?'block':'table-row';
+		NabbleDropdown.showSeparators(p);
+	} else {
+		e.style.display = $.browser.msie?'block':'table-row';
+		NabbleDropdown.showSeparators(e);
+	}
+};
+
+NabbleDropdown.hide = function(id) {
+	var e = document.getElementById(id);
+	e.style.display = 'none';
+	NabbleDropdown.showSeparators(e);
+};
+
+NabbleDropdown.showSeparators = function(e) {
+	while (e.previousSibling)
+		e = e.previousSibling;
+	var separators = [];
+	while (e) {
+		if (e.nodeName != 'TR') return;
+		if (e.className == 'dropdown-separator')
+			separators.push(e);
+		else if (e.style.display != 'none')
+			separators.push('X');
+		e = e.nextSibling;
+	}
+	for (var i=0; i<separators.length;i++) {
+		if (typeof separators[i] != 'string') {
+			var prev = i > 0 && separators[i-1] == 'X';
+			var next = i < separators.length-1 && separators[i+1] == 'X';
+			if (next && prev) {
+				separators[i].style.display = $.browser.msie?'block':'table-row';
+			}
+		}
+	}
+};
+
+NabbleDropdown.addContents = function(id,contents) {
+	var $row = $('#'+id);
+	$row.html('<td>'+contents+'</td>').show();
+	_update($row.get(),false);
+};
+
+NabbleDropdown.replaceContents = function(id,contents) {
+	$('#'+id).html('<td class="dropdown-simple-row">'+contents+'</td>');
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/nabblegallery-1.2.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,73 @@
+function fixGalleryWidth(id) {
+	var $slider = $('#slider'+id);
+	var w = Math.min($slider.width(),$(window).width()-20);
+	$('#gallery'+id).width(w-60);
+	var $gview = $('#gallery-view'+id);
+	$slider.height($gview.height());
+	$gview.css({
+		position: 'absolute',
+		left: $slider.offset().left,
+		width: w
+	});
+	// hack for IE7
+	var ie7 = $.browser.msie && $.browser.version.indexOf('8.') == -1;
+	if (ie7) {
+		var $gv = $('#gallery-view'+id);
+		$gv.css('position','absolute');
+		$('#slider'+id).height($gv.height());
+	}
+};
+
+function galleryReady(id) {
+	$(document).ready(function() {
+		var $prev = $('#prev'+id);
+		var $next = $('#next'+id);
+		var $imgs = $('#images'+id);
+		var $dv = $('#gallery'+id);
+		var left = 0;
+		var inc = 300;
+
+		$(window).resize(function() {
+			fixGalleryWidth(id);
+			showArrows();
+		});
+
+		function showArrows() {
+			if ($imgs.width() <= $dv.width()) {
+				$prev.hide();
+				$next.hide();
+			} else if (left == 0) {
+				$prev.hide();
+				$next.show();
+			} else {
+				var tw = $imgs.width();
+				var vw = $dv.width();
+				var mw = tw-vw;
+				if (left <= -mw) {
+					$prev.show();
+					$next.hide();
+				} else {
+					$prev.show();
+					$next.show();
+				}
+			}
+		};
+
+		fixGalleryWidth(id);
+		showArrows();
+		setTimeout(showArrows, 2000);
+		$prev.click(function() {
+			left+=inc;
+			left = left > 0? 0 : left;
+			$imgs.animate({marginLeft: left+'px'}, 400, showArrows);
+		});
+		$next.click(function() {
+			left-=inc;
+			var tw = $imgs.width();
+			var vw = $dv.width();
+			var mw = tw-vw;
+			left = left < -mw? -mw : left;
+			$imgs.animate({marginLeft: left+'px'}, 400, showArrows);
+		});
+	});
+};
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/nabbletabs.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,41 @@
+#tabs {
+	text-align: left;
+	margin: 2em 0 1px;
+	font-size: 90%;
+	border-bottom-width: 1px;
+	border-bottom-style: solid;
+	list-style-type: none;
+	padding: 3px .5em;
+}
+
+#tabs li {
+	display: inline;
+	white-space:nowrap;
+}
+
+.tab-selected {
+	border-bottom: none;
+	border-top-width:3px;
+}
+
+#tabs li a.tab-link-selected {
+	position: relative;
+	top: -1px;
+	font-weight:bold;
+	border-top-width:3px;
+}
+
+#tabs li a {
+	position: relative;
+	top: -2px;
+	padding: 5px .8em;
+	border-width: 1px;
+	border-style: solid;
+	margin-right: .3em;
+	text-decoration: none;
+	border-bottom: none;
+	-moz-border-radius-topleft: 6px;
+	-webkit-border-top-left-radius: 6px;
+	-moz-border-radius-topright: 6px;
+	-webkit-border-top-right-radius: 6px;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/nabbletabs.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,62 @@
+var NabbleTabs = new Object();
+
+document.writeln('<link rel="stylesheet" href="/util/nabbletabs.css?1" type="text/css" />');
+
+NabbleTabs.startTabControl = function(isLive) {
+	NabbleTabs.isLive = isLive;
+	document.writeln('<ul id="tabs" class="medium-border-color">');
+};
+
+NabbleTabs.addLiveTab = function(elemId, text, selected,onclick) {
+	if (!NabbleTabs.isLive) alert('Live tab not allowed. Use static tabs only');
+	if (selected) {
+		NabbleTabs.selected = elemId;
+		document.writeln('<li id="li-' + elemId + '" class="tab-selected"><a id="a-' + elemId + '" class="tab-link-selected medium-border-color no-bg-color">' + text + '</a></li>');
+	} else
+		document.writeln('<li id="li-' + elemId + '"><a id="a-' + elemId + '" class="light-bg-color medium-border-color">' + text + '</a></li>');
+
+	if (onclick) {
+		$(document).ready(function() {
+			$('#li-'+elemId).click(function() { setTimeout(function() { onclick() },50); });
+		});
+	}
+};
+
+NabbleTabs.addTab = function(link, text, selected) {
+	if (NabbleTabs.isLive) alert('Static tab not allowed. Use live tabs only');
+	if (selected)
+		document.writeln('<li class="tab-selected"><a class="tab-link-selected medium-border-color no-bg-color">' + text + '</a></li>');
+	else
+		document.writeln('<li><a href="' + link + '" class="light-bg-color medium-border-color">' + text + '</a></li>');
+};
+
+NabbleTabs.endTabControl = function() {
+	document.writeln('</ul>');
+
+	$(document).ready(function() {
+		if (NabbleTabs.isLive) {
+			$('#tabs li').click(function() {
+				if ($(this).attr('id') != 'li-' + NabbleTabs.selected) {
+					$('#li-'+NabbleTabs.selected).removeClass('tab-selected').css('cursor', 'pointer');
+					$('#a-'+NabbleTabs.selected).removeClass('no-bg-color').removeClass('tab-link-selected').addClass('light-bg-color').css('cursor', 'pointer');
+					$('#'+NabbleTabs.selected).hide();
+					NabbleTabs.selected = $(this).attr('id').substring(3);
+					$('#li-'+NabbleTabs.selected).addClass('tab-selected').css('cursor', 'default');
+					$('#a-'+NabbleTabs.selected).removeClass('light-bg-color').addClass('no-bg-color').addClass('tab-link-selected').css('cursor', 'default');
+					$('#'+NabbleTabs.selected).show();
+					Nabble.resizeFrames();
+				}
+			});
+		} 
+		$('#tabs a').hover(
+				function() {
+					if (!$(this).hasClass('tab-link-selected'))
+						$(this).css('border-top-width', '3px');
+				},
+				function() {
+					if (!$(this).hasClass('tab-link-selected'))
+						$(this).css('border-top-width', '1px');
+				}
+		);
+	});
+};
\ No newline at end of file
Binary file src/nabble/view/web/util/tablesorter/asc.gif has changed
Binary file src/nabble/view/web/util/tablesorter/bg.gif has changed
Binary file src/nabble/view/web/util/tablesorter/desc.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/tablesorter/jquery.tablesorter.min.js	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,4 @@
+
+(function($){$.extend({tablesorter:new
+function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,rows,-1,i);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,rows,rowIndex,cellIndex){var l=parsers.length,node=false,nodeValue=false,keepLooking=true;while(nodeValue==''&&keepLooking){rowIndex++;if(rows[rowIndex]){node=getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex);nodeValue=trimAndGetNodeText(table.config,node);if(table.config.debug){log('Checking if value was empty on row:'+rowIndex);}}else{keepLooking=false;}}for(var i=1;i<l;i++){if(parsers[i].is(nodeValue,table,node)){return parsers[i];}}return parsers[0];}function getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex){return rows[rowIndex].cells[cellIndex];}function trimAndGetNodeText(config,node){return $.trim(getElementText(config,node));}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=$(table.tBodies[0].rows[i]),cols=[];if(c.hasClass(table.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue;}cache.row.push(c);for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c[0].cells[j]),table,c[0].cells[j]));}cols.push(cache.normalized.length);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){var text="";if(!node)return"";if(!config.supportsTextContent)config.supportsTextContent=node.textContent||false;if(config.textExtraction=="simple"){if(config.supportsTextContent){text=node.textContent;}else{if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){text=node.childNodes[0].innerHTML;}else{text=node.innerHTML;}}}else{if(typeof(config.textExtraction)=="function"){text=config.textExtraction(node);}else{text=$(node).text();}}return text;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){var pos=n[i][checkCell];rows.push(r[pos]);if(!table.config.appender){var l=r[pos].length;for(var j=0;j<l;j++){tableBody[0].appendChild(r[pos][j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false;var header_index=computeTableHeaderCellIndexes(table);$tableHeaders=$(table.config.selectorHeaders,table).each(function(index){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(table.config.sortInitialOrder);this.count=this.order;if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(checkHeaderOptionsSortingLocked(table,index))this.order=this.lockedOrder=checkHeaderOptionsSortingLocked(table,index);if(!this.sortDisabled){var $th=$(this).addClass(table.config.cssHeader);if(table.config.onRenderHeader)table.config.onRenderHeader.apply($th);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function computeTableHeaderCellIndexes(t){var matrix=[];var lookup={};var thead=t.getElementsByTagName('THEAD')[0];var trs=thead.getElementsByTagName('TR');for(var i=0;i<trs.length;i++){var cells=trs[i].cells;for(var j=0;j<cells.length;j++){var c=cells[j];var rowIndex=c.parentNode.rowIndex;var cellId=rowIndex+"-"+c.cellIndex;var rowSpan=c.rowSpan||1;var colSpan=c.colSpan||1
+var firstAvailCol;if(typeof(matrix[rowIndex])=="undefined"){matrix[rowIndex]=[];}for(var k=0;k<matrix[rowIndex].length+1;k++){if(typeof(matrix[rowIndex][k])=="undefined"){firstAvailCol=k;break;}}lookup[cellId]=firstAvailCol;for(var k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(matrix[k])=="undefined"){matrix[k]=[];}var matrixrow=matrix[k];for(var l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x";}}}}return lookup;}function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){return(v.toLowerCase()=="desc")?1:0;}else{return(v==1)?1:0;}}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(table.config.parsers[c].type=="text")?((order==0)?makeSortFunction("text","asc",c):makeSortFunction("text","desc",c)):((order==0)?makeSortFunction("numeric","asc",c):makeSortFunction("numeric","desc",c));var e="e"+i;dynamicExp+="var "+e+" = "+s;dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";if(table.config.debug){benchmark("Evaling expression:"+dynamicExp,new Date());}eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function makeSortFunction(type,direction,index){var a="a["+index+"]",b="b["+index+"]";if(type=='text'&&direction=='asc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+a+" < "+b+") ? -1 : 1 )));";}else if(type=='text'&&direction=='desc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+b+" < "+a+") ? -1 : 1 )));";}else if(type=='numeric'&&direction=='asc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+a+" - "+b+"));";}else if(type=='numeric'&&direction=='desc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+b+" - "+a+"));";}};function makeSortText(i){return"((a["+i+"] < b["+i+"]) ? -1 : ((a["+i+"] > b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this.onselectstart=function(){return false};return false;}});$this.bind("update",function(){var me=this;setTimeout(function(){me.config.parsers=buildParserCache(me,$headers);cache=buildCache(me);},1);}).bind("updateCell",function(e,cell){var config=this.config;var pos=[(cell.parentNode.rowIndex-1),cell.cellIndex];cache.normalized[pos[0]][pos[1]]=config.parsers[pos[1]].format(getElementText(config,cell),cell);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){return/^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g,'')));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLocaleLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[£$€?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}var $tr,row=-1,odd;$("tr:visible",table.tBodies[0]).each(function(i){$tr=$(this);if(!$tr.hasClass(table.config.cssChildRow))row++;odd=(row%2==0);$tr.removeClass(table.config.widgetZebra.css[odd?0:1]).addClass(table.config.widgetZebra.css[odd?1:0])});if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/util/tablesorter/style.css	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,37 @@
+/* tables */
+table.tablesorter {
+	font-family:arial;
+	background-color: #CDCDCD;
+	margin:10px 0pt 15px;
+	width: 100%;
+	text-align: left;
+}
+table.tablesorter thead tr th, table.tablesorter tfoot tr th {
+	background-color: #e6EEEE;
+	border: 1px solid #FFF;
+	padding: 4px;
+}
+table.tablesorter thead tr .header {
+	background-image: url(bg.gif);
+	background-repeat: no-repeat;
+	background-position: center right;
+	cursor: pointer;
+}
+table.tablesorter tbody td {
+	color: #3D3D3D;
+	padding: 4px;
+	background-color: #FFF;
+	vertical-align: top;
+}
+table.tablesorter tbody tr.odd td {
+	background-color:#F0F0F6;
+}
+table.tablesorter thead tr .headerSortUp {
+	background-image: url(asc.gif);
+}
+table.tablesorter thead tr .headerSortDown {
+	background-image: url(desc.gif);
+}
+table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp {
+background-color: #8dbdd8;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/w3c/P3PXML.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,55 @@
+
+package nabble.view.web.w3c;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.UrlMappable;
+import nabble.view.lib.Cache;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class P3PXML extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/w3c/p3p.xml$");
+
+	private static String path() {
+		return "/w3c/p3p.xml";
+	}
+
+	public Map<String, String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if (!m.find())
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+		response.setContentType("text/xml");
+		
+		out.print( "\r\n<META xmlns=\"http://www.w3.org/2002/01/P3Pv1\">\r\n	<POLICY-REFERENCES>\r\n		<POLICY-REF about=\"http://" );
+		out.print( (request.getHeader("host")) );
+		out.print( "/w3c/policy.xml#Policy\">\r\n			<INCLUDE>/*</INCLUDE>\r\n			<COOKIE-INCLUDE name=\"*\" value=\"*\" domain=\"" );
+		out.print( (request.getHeader("host")) );
+		out.print( "\"/>\r\n		</POLICY-REF>\r\n	</POLICY-REFERENCES>\r\n</META>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/w3c/P3PXML.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,58 @@
+<%
+package nabble.view.web.w3c;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.UrlMappable;
+import nabble.view.lib.Cache;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class P3PXML extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/w3c/p3p.xml$");
+
+	private static String path() {
+		return "/w3c/p3p.xml";
+	}
+
+	public Map<String, String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if (!m.find())
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+		response.setContentType("text/xml");
+		%>
+		<META xmlns="http://www.w3.org/2002/01/P3Pv1">
+			<POLICY-REFERENCES>
+				<POLICY-REF about="http://<%=request.getHeader("host")%>/w3c/policy.xml#Policy">
+					<INCLUDE>/*</INCLUDE>
+					<COOKIE-INCLUDE name="*" value="*" domain="<%=request.getHeader("host")%>"/>
+				</POLICY-REF>
+			</POLICY-REFERENCES>
+		</META>
+		<%
+	}
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/w3c/PolicyHTML.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,47 @@
+
+package nabble.view.web.w3c;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.Cache;
+import nabble.view.lib.UrlMappable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class PolicyHTML extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/w3c/policy.html$");
+
+	public Map<String, String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if (!m.find())
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+		response.setContentType("text/html");
+		
+		out.print( "\r\n<html>\r\n<head>\r\n<STYLE type=\"text/css\">\r\ntitle { color: #3333FF}\r\n</STYLE>\r\n<title>Privacy Statement for Nabble</title>\r\n</head>\r\n<body>\r\n<h1 class=\"title\">Privacy Policy</h1>\r\n<!-- \"About Us\" section of privacy policy -->\r\n<h2>About Us</h2>\r\n<p>This is a privacy policy for Nabble.\r\nOur homepage on the Web is located at <a href=\"https://www.nabble.com\">\r\nhttps://www.nabble.com</a>.\r\nThe full text of our privacy policy is available on the Web at\r\n<a href=\"https://www.nabble.com/w3c/policy.html\">\r\nhttps://www.nabble.com/w3c/policy.html</a>\r\nUsers may go to <a href=\"\">\r\n</a> for information on how to opt-in or opt-out of use of their information.\r\n<p>We invite you to contact us if you have questions about this policy.\r\nYou may contact us by email at\r\n<a href=\"mailto:support@nabble.com\">\r\nsupport@nabble.com</a>.\r\n<!-- \"Privacy Seals\" section of privacy policy -->\r\n<h2>Dispute Resolution and Privacy Seals</h2>\r\n<p>We have the following privacy seals and/or dispute resolution mechanisms.\r\nIf you think we have not followed our privacy policy in some way, they can help you resolve your concern.\r\n<ul>\r\n<li>\r\n<b>Disputes</b>:\r\nIf any question arises regarding the user experience, ability or inability to perform actions with the site, a user is advised to contact Nabble Support.\r\n</ul>\r\n<!-- \"Additional information\" section of privacy policy -->\r\n<h2>Additional Information</h2>\r\n<p>\r\nThis policy is valid for 1 day from the time that it is loaded by a client.\r\n</p>\r\n<!-- \"Data Collection\" section of privacy policy -->\r\n<h2>Data Collection</h2>\r\n<p>P3P policies declare the data they collect in groups (also referred to as \"statements\").\r\nThis policy contains 2 data groups.\r\nThe data practices of each group will be explained separately.\r\n<hr width=\"50%\" align=\"center\">\r\n<h3>Group \"User Information\"</h3>\r\n<p>At the user's option, we will collect the following data:\r\n<ul>\r\n<li>User's Name</li>\r\n<li>Preference data</li>\r\n<li>Home email address</li>\r\n<li>Work email address</li>\r\n</ul>\r\n<p>This data will be used for the following purposes:</p>\r\n<ul>\r\n<li>Other purposes<p>Display the user name</p> The user must <b>opt-in</b> to this usage.</li>\r\n</ul>\r\n<p>This data will be used by ourselves and our agents.\r\n<hr width=\"50%\" align=\"center\">\r\n<h3>Group \"Cookies\"</h3>\r\n<p>At the user's option, we will collect the following data:\r\n<ul>\r\n<li>HTTP cookies</li>\r\n</ul>\r\n<p>This data will be used for the following purposes:</p>\r\n<ul>\r\n<li>One-time tailoring. The user must <b>opt-in</b> to this usage.</li>\r\n<li>Anonymous user analysis. The user must <b>opt-in</b> to this usage.</li>\r\n<li>Anonymous user profiling and decision-making. The user must <b>opt-in</b> to this usage.</li>\r\n</ul>\r\n<p>This data will be used by ourselves and our agents.\r\n<p>The following explanation is provided for why this data is collected:</p>\r\n<blockquote>To keep users logged in and apply their preferences.</blockquote>\r\n<!-- \"Use of Cookies\" section of privacy policy -->\r\n<hr width=\"50%\" align=\"center\">\r\n<h2>Cookies</h2>\r\n<p>Cookies are a technology which can be used to provide you with tailored information from a Web site. A cookie is an element of data that a Web site can send to your browser, which may then store it on your system. You can set your browser to notify you when you receive a cookie, giving you the chance to decide whether to accept it.\r\n<p>Our site makes use of cookies.\r\nCookies are used for the following purposes:\r\n<ul>\r\n<li>User targeting\r\n<li>Pseudononymous analysis\r\n<li>Pseudonym-based decision-making\r\n</ul>\r\n<!-- \"Compact Policy Explanation\" section of privacy policy -->\r\n<hr width=\"50%\" align=\"center\">\r\n<h2>Compact Policy Summary</h2>\r\n<p>The compact policy which corresponds to this policy is:\r\n<pre>\r\n	CP=\"IDC DSP TAIi PSAi PSDi OTPi OUR IND PHY ONL UNI NAV DEM PRE LOC\"\r\n</pre>\r\n<p>The following table explains the meaning of each field in the compact policy.\r\n<center><table width=\"80%\" border=\"1\" cols=\"2\">\r\n<tr><td align=\"center\" valign=\"top\" width=\"20%\"><b>Field</b></td><td align=\"center\" valign=\"top\" width=\"80%\"><b>Meaning</b></td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>CP=</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">This is the compact policy header; it indicates that what follows is a P3P compact policy.</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>IDC</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nAccess is available to contact information.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>DSP</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nThe policy contains at least one dispute-resolution mechanism.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>TAIi</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nThe data is used for tailoring the site, if the user selects it.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>PSAi</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nThe data is used for pseudononymous analysis, if the user selects it.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>PSDi</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nThe data is used for pseudononymous decision-making, if the user selects it.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>OTPi</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nThe data is used for other purposes, if the user selects it.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>OUR</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nThe data is given to ourselves and our agents.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>IND</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nThe data will be kept indefinitely.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>PHY</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nPhysical contact information is collected.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>ONL</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nOnline contact information is collected.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>UNI</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nUnique identifiers are collected.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>NAV</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nNavigation and clickstream data is collected.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>DEM</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nDemographic and socioeconomic data is collected.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>PRE</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nPreference information is collected.\r\n</td></tr>\r\n<tr><td align=\"left\" valign=\"top\" width=\"20%\"><tt>LOC</tt></td>\r\n<td align=\"left\" valign=\"top\" width=\"80%\">\r\nCurrent location information is collected.\r\n</td></tr>\r\n</table></center>\r\n<p>The compact policy is sent by the Web server along with the cookies it describes.\r\nFor more information, see the P3P deployment guide at <a href=\"http://www.w3.org/TR/p3pdeployment\">http://www.w3.org/TR/p3pdeployment</a>.\r\n<!-- \"Policy Evaluation\" section of privacy policy -->\r\n<hr width=\"50%\" align=\"center\">\r\n<h2>Policy Evaluation</h2>\r\n<p>Microsoft Internet Explorer 6 will evaluate this policy's compact policy whenever it is used with a cookie.\r\nThe actions IE will take depend on what privacy level the user has selected in their browser (Low, Medium, Medium High, or High; the default is Medium.\r\nIn addition, IE will examine whether the cookie's policy is considered satisfactory or unsatisfactory, whether the cookie is a session cookie or a persistent cookie, and whether the cookie is used in a first-party or third-party context.\r\nThis section will attempt to evaluate this policy's compact policy against Microsoft's stated behavior for IE6.\r\n<p><b>Note:</b> this evaluation is currently experimental and should not be considered a substitute for testing with a real Web browser.\r\n<p><b>Satisfactory policy</b>: this compact policy is considered <em>satisfactory</em> according to the rules defined by Internet Explorer 6.\r\nIE6 will accept cookies accompanied by this policy under the High, Medium High, Medium, Low, and Accept All Cookies settings.\r\n</body></html>\r\n" );
+
+		}
+	}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/w3c/PolicyHTML.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,214 @@
+<%
+package nabble.view.web.w3c;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.Cache;
+import nabble.view.lib.UrlMappable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class PolicyHTML extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/w3c/policy.html$");
+
+	public Map<String, String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if (!m.find())
+			throw new RuntimeException();
+		Map<String,String[]> params = new HashMap<String,String[]>();
+		return params;
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+		response.setContentType("text/html");
+		%>
+		<html>
+		<head>
+		<STYLE type="text/css">
+		title { color: #3333FF}
+		</STYLE>
+		<title>Privacy Statement for Nabble</title>
+		</head>
+		<body>
+		<h1 class="title">Privacy Policy</h1>
+		<!-- "About Us" section of privacy policy -->
+		<h2>About Us</h2>
+		<p>This is a privacy policy for Nabble.
+		Our homepage on the Web is located at <a href="https://www.nabble.com">
+		https://www.nabble.com</a>.
+		The full text of our privacy policy is available on the Web at
+		<a href="https://www.nabble.com/w3c/policy.html">
+		https://www.nabble.com/w3c/policy.html</a>
+		Users may go to <a href="">
+		</a> for information on how to opt-in or opt-out of use of their information.
+		<p>We invite you to contact us if you have questions about this policy.
+		You may contact us by email at
+		<a href="mailto:support@nabble.com">
+		support@nabble.com</a>.
+		<!-- "Privacy Seals" section of privacy policy -->
+		<h2>Dispute Resolution and Privacy Seals</h2>
+		<p>We have the following privacy seals and/or dispute resolution mechanisms.
+		If you think we have not followed our privacy policy in some way, they can help you resolve your concern.
+		<ul>
+		<li>
+		<b>Disputes</b>:
+		If any question arises regarding the user experience, ability or inability to perform actions with the site, a user is advised to contact Nabble Support.
+		</ul>
+		<!-- "Additional information" section of privacy policy -->
+		<h2>Additional Information</h2>
+		<p>
+		This policy is valid for 1 day from the time that it is loaded by a client.
+		</p>
+		<!-- "Data Collection" section of privacy policy -->
+		<h2>Data Collection</h2>
+		<p>P3P policies declare the data they collect in groups (also referred to as "statements").
+		This policy contains 2 data groups.
+		The data practices of each group will be explained separately.
+		<hr width="50%" align="center">
+		<h3>Group "User Information"</h3>
+		<p>At the user's option, we will collect the following data:
+		<ul>
+		<li>User's Name</li>
+		<li>Preference data</li>
+		<li>Home email address</li>
+		<li>Work email address</li>
+		</ul>
+		<p>This data will be used for the following purposes:</p>
+		<ul>
+		<li>Other purposes<p>Display the user name</p> The user must <b>opt-in</b> to this usage.</li>
+		</ul>
+		<p>This data will be used by ourselves and our agents.
+		<hr width="50%" align="center">
+		<h3>Group "Cookies"</h3>
+		<p>At the user's option, we will collect the following data:
+		<ul>
+		<li>HTTP cookies</li>
+		</ul>
+		<p>This data will be used for the following purposes:</p>
+		<ul>
+		<li>One-time tailoring. The user must <b>opt-in</b> to this usage.</li>
+		<li>Anonymous user analysis. The user must <b>opt-in</b> to this usage.</li>
+		<li>Anonymous user profiling and decision-making. The user must <b>opt-in</b> to this usage.</li>
+		</ul>
+		<p>This data will be used by ourselves and our agents.
+		<p>The following explanation is provided for why this data is collected:</p>
+		<blockquote>To keep users logged in and apply their preferences.</blockquote>
+		<!-- "Use of Cookies" section of privacy policy -->
+		<hr width="50%" align="center">
+		<h2>Cookies</h2>
+		<p>Cookies are a technology which can be used to provide you with tailored information from a Web site. A cookie is an element of data that a Web site can send to your browser, which may then store it on your system. You can set your browser to notify you when you receive a cookie, giving you the chance to decide whether to accept it.
+		<p>Our site makes use of cookies.
+		Cookies are used for the following purposes:
+		<ul>
+		<li>User targeting
+		<li>Pseudononymous analysis
+		<li>Pseudonym-based decision-making
+		</ul>
+		<!-- "Compact Policy Explanation" section of privacy policy -->
+		<hr width="50%" align="center">
+		<h2>Compact Policy Summary</h2>
+		<p>The compact policy which corresponds to this policy is:
+		<pre>
+			CP="IDC DSP TAIi PSAi PSDi OTPi OUR IND PHY ONL UNI NAV DEM PRE LOC"
+		</pre>
+		<p>The following table explains the meaning of each field in the compact policy.
+		<center><table width="80%" border="1" cols="2">
+		<tr><td align="center" valign="top" width="20%"><b>Field</b></td><td align="center" valign="top" width="80%"><b>Meaning</b></td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>CP=</tt></td>
+		<td align="left" valign="top" width="80%">This is the compact policy header; it indicates that what follows is a P3P compact policy.</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>IDC</tt></td>
+		<td align="left" valign="top" width="80%">
+		Access is available to contact information.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>DSP</tt></td>
+		<td align="left" valign="top" width="80%">
+		The policy contains at least one dispute-resolution mechanism.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>TAIi</tt></td>
+		<td align="left" valign="top" width="80%">
+		The data is used for tailoring the site, if the user selects it.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>PSAi</tt></td>
+		<td align="left" valign="top" width="80%">
+		The data is used for pseudononymous analysis, if the user selects it.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>PSDi</tt></td>
+		<td align="left" valign="top" width="80%">
+		The data is used for pseudononymous decision-making, if the user selects it.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>OTPi</tt></td>
+		<td align="left" valign="top" width="80%">
+		The data is used for other purposes, if the user selects it.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>OUR</tt></td>
+		<td align="left" valign="top" width="80%">
+		The data is given to ourselves and our agents.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>IND</tt></td>
+		<td align="left" valign="top" width="80%">
+		The data will be kept indefinitely.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>PHY</tt></td>
+		<td align="left" valign="top" width="80%">
+		Physical contact information is collected.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>ONL</tt></td>
+		<td align="left" valign="top" width="80%">
+		Online contact information is collected.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>UNI</tt></td>
+		<td align="left" valign="top" width="80%">
+		Unique identifiers are collected.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>NAV</tt></td>
+		<td align="left" valign="top" width="80%">
+		Navigation and clickstream data is collected.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>DEM</tt></td>
+		<td align="left" valign="top" width="80%">
+		Demographic and socioeconomic data is collected.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>PRE</tt></td>
+		<td align="left" valign="top" width="80%">
+		Preference information is collected.
+		</td></tr>
+		<tr><td align="left" valign="top" width="20%"><tt>LOC</tt></td>
+		<td align="left" valign="top" width="80%">
+		Current location information is collected.
+		</td></tr>
+		</table></center>
+		<p>The compact policy is sent by the Web server along with the cookies it describes.
+		For more information, see the P3P deployment guide at <a href="http://www.w3.org/TR/p3pdeployment">http://www.w3.org/TR/p3pdeployment</a>.
+		<!-- "Policy Evaluation" section of privacy policy -->
+		<hr width="50%" align="center">
+		<h2>Policy Evaluation</h2>
+		<p>Microsoft Internet Explorer 6 will evaluate this policy's compact policy whenever it is used with a cookie.
+		The actions IE will take depend on what privacy level the user has selected in their browser (Low, Medium, Medium High, or High; the default is Medium.
+		In addition, IE will examine whether the cookie's policy is considered satisfactory or unsatisfactory, whether the cookie is a session cookie or a persistent cookie, and whether the cookie is used in a first-party or third-party context.
+		This section will attempt to evaluate this policy's compact policy against Microsoft's stated behavior for IE6.
+		<p><b>Note:</b> this evaluation is currently experimental and should not be considered a substitute for testing with a real Web browser.
+		<p><b>Satisfactory policy</b>: this compact policy is considered <em>satisfactory</em> according to the rules defined by Internet Explorer 6.
+		IE6 will accept cookies accompanied by this policy under the High, Medium High, Medium, Low, and Accept All Cookies settings.
+		</body></html>
+		<%
+		}
+	}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/w3c/PolicyXML.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,48 @@
+
+package nabble.view.web.w3c;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.UrlMappable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class PolicyXML extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/w3c/policy.xml$");
+
+	public Map<String, String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if (!m.find())
+			throw new RuntimeException();
+		return new HashMap<String,String[]>();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+		
+		out.print( "\r\n<?xml version=\"1.0\"?>\r\n<POLICIES xmlns=\"http://www.w3.org/2002/01/P3Pv1\">\r\n	<EXPIRY max-age=\"86400\"/>\r\n<POLICY\r\n	name=\"Policy\"\r\n	discuri=\"https://www.nabble.com/w3c/policy.html\"\r\n	xml:lang=\"en\">\r\n	<ENTITY>\r\n	<DATA-GROUP>\r\n<DATA ref=\"#business.contact-info.online.email\">support@nabble.com</DATA>\r\n<DATA ref=\"#business.contact-info.online.uri\">https://www.nabble.com</DATA>\r\n<DATA ref=\"#business.name\">Nabble</DATA>\r\n	</DATA-GROUP>\r\n	</ENTITY>\r\n	<ACCESS><ident-contact/></ACCESS>\r\n	<DISPUTES-GROUP>\r\n		<DISPUTES resolution-type=\"service\" service=\"" );
+		out.print( (Jtp.termsUrl(false)) );
+		out.print( "\" short-description=\"Disputes\">\r\n			<LONG-DESCRIPTION>If any question arises regarding the user experience, ability or inability to perform actions with the site, a user is advised to contact Nabble Support.</LONG-DESCRIPTION>\r\n		</DISPUTES>\r\n	</DISPUTES-GROUP>\r\n	<STATEMENT>\r\n		<EXTENSION optional=\"yes\">\r\n			<GROUP-INFO xmlns=\"http://www.software.ibm.com/P3P/editor/extension-1.0.html\" name=\"User Information\"/>\r\n		</EXTENSION>\r\n	<PURPOSE><other-purpose required=\"opt-in\">Display the user name</other-purpose></PURPOSE>\r\n	<RECIPIENT><ours/></RECIPIENT>\r\n	<RETENTION><indefinitely/></RETENTION>\r\n	<DATA-GROUP>\r\n	<DATA ref=\"#user.name\" optional=\"yes\"/>\r\n	<DATA ref=\"#dynamic.miscdata\" optional=\"yes\"><CATEGORIES><preference/></CATEGORIES></DATA>\r\n	<DATA ref=\"#user.home-info.online.email\" optional=\"yes\"/>\r\n	<DATA ref=\"#user.business-info.online.email\" optional=\"yes\"/>\r\n	</DATA-GROUP>\r\n</STATEMENT>\r\n	<STATEMENT>\r\n		<EXTENSION optional=\"yes\">\r\n			<GROUP-INFO xmlns=\"http://www.software.ibm.com/P3P/editor/extension-1.0.html\" name=\"Cookies\"/>\r\n		</EXTENSION>\r\n	<CONSEQUENCE>\r\nTo keep users logged in and apply their preferences.</CONSEQUENCE>\r\n	<PURPOSE><pseudo-analysis required=\"opt-in\"/><pseudo-decision required=\"opt-in\"/><tailoring required=\"opt-in\"/></PURPOSE>\r\n	<RECIPIENT><ours/></RECIPIENT>\r\n	<RETENTION><indefinitely/></RETENTION>\r\n	<DATA-GROUP>\r\n	<DATA ref=\"#dynamic.cookies\" optional=\"yes\"><CATEGORIES><location/><navigation/><preference/><uniqueid/></CATEGORIES></DATA>\r\n	</DATA-GROUP>\r\n</STATEMENT>\r\n</POLICY>\r\n</POLICIES>\r\n" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/w3c/PolicyXML.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,94 @@
+<%
+package nabble.view.web.w3c;
+
+import fschmidt.util.servlet.JtpContext;
+import nabble.view.lib.Cache;
+import nabble.view.lib.Jtp;
+import nabble.view.lib.UrlMappable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class PolicyXML extends HttpServlet implements UrlMappable {
+
+	private static final Pattern URL_PATTERN = Pattern.compile("/w3c/policy.xml$");
+
+	public Map<String, String[]> getParameterMapFromUrl(HttpServletRequest request,String mappedUrl) {
+		Matcher m = URL_PATTERN.matcher(mappedUrl);
+		if (!m.find())
+			throw new RuntimeException();
+		return new HashMap<String,String[]>();
+	}
+
+	public Pattern getUrlPattern() {
+		return URL_PATTERN;
+	}
+
+	protected void service(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+
+		JtpContext jtpContext = (JtpContext)getServletContext().getAttribute(JtpContext.attrName);
+		jtpContext.setEtag(request,response);
+		PrintWriter out = response.getWriter();
+		%>
+		<?xml version="1.0"?>
+		<POLICIES xmlns="http://www.w3.org/2002/01/P3Pv1">
+			<EXPIRY max-age="86400"/>
+		<POLICY
+			name="Policy"
+			discuri="https://www.nabble.com/w3c/policy.html"
+			xml:lang="en">
+			<ENTITY>
+			<DATA-GROUP>
+		<DATA ref="#business.contact-info.online.email">support@nabble.com</DATA>
+		<DATA ref="#business.contact-info.online.uri">https://www.nabble.com</DATA>
+		<DATA ref="#business.name">Nabble</DATA>
+			</DATA-GROUP>
+			</ENTITY>
+			<ACCESS><ident-contact/></ACCESS>
+			<DISPUTES-GROUP>
+				<DISPUTES resolution-type="service" service="<%=Jtp.termsUrl(false)%>" short-description="Disputes">
+					<LONG-DESCRIPTION>If any question arises regarding the user experience, ability or inability to perform actions with the site, a user is advised to contact Nabble Support.</LONG-DESCRIPTION>
+				</DISPUTES>
+			</DISPUTES-GROUP>
+			<STATEMENT>
+				<EXTENSION optional="yes">
+					<GROUP-INFO xmlns="http://www.software.ibm.com/P3P/editor/extension-1.0.html" name="User Information"/>
+				</EXTENSION>
+			<PURPOSE><other-purpose required="opt-in">Display the user name</other-purpose></PURPOSE>
+			<RECIPIENT><ours/></RECIPIENT>
+			<RETENTION><indefinitely/></RETENTION>
+			<DATA-GROUP>
+			<DATA ref="#user.name" optional="yes"/>
+			<DATA ref="#dynamic.miscdata" optional="yes"><CATEGORIES><preference/></CATEGORIES></DATA>
+			<DATA ref="#user.home-info.online.email" optional="yes"/>
+			<DATA ref="#user.business-info.online.email" optional="yes"/>
+			</DATA-GROUP>
+		</STATEMENT>
+			<STATEMENT>
+				<EXTENSION optional="yes">
+					<GROUP-INFO xmlns="http://www.software.ibm.com/P3P/editor/extension-1.0.html" name="Cookies"/>
+				</EXTENSION>
+			<CONSEQUENCE>
+		To keep users logged in and apply their preferences.</CONSEQUENCE>
+			<PURPOSE><pseudo-analysis required="opt-in"/><pseudo-decision required="opt-in"/><tailoring required="opt-in"/></PURPOSE>
+			<RECIPIENT><ours/></RECIPIENT>
+			<RETENTION><indefinitely/></RETENTION>
+			<DATA-GROUP>
+			<DATA ref="#dynamic.cookies" optional="yes"><CATEGORIES><location/><navigation/><preference/><uniqueid/></CATEGORIES></DATA>
+			</DATA-GROUP>
+		</STATEMENT>
+		</POLICY>
+		</POLICIES>
+		<%
+	}
+}
+%>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/y_key_11a47ceb3359dacc.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,21 @@
+
+package nabble.view.web;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+import java.io.IOException;
+import javax.servlet.ServletException;
+
+// Yahoo needs that url for site authentication
+public final class y_key_11a47ceb3359dacc extends HttpServlet {
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+	throws IOException, ServletException {
+		PrintWriter out = response.getWriter();
+		
+		out.print( "b4aae300dfd3d6d6" );
+
+	}
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/y_key_11a47ceb3359dacc.jtp	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,19 @@
+<%
+package nabble.view.web;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.PrintWriter;
+import java.io.IOException;
+import javax.servlet.ServletException;
+
+// Yahoo needs that url for site authentication
+public final class y_key_11a47ceb3359dacc extends HttpServlet {
+	protected void service(HttpServletRequest request,HttpServletResponse response)
+	throws IOException, ServletException {
+		PrintWriter out = response.getWriter();
+		%>b4aae300dfd3d6d6<%
+	}
+}
+%>